完善商城订单模拟快照与空值展示

This commit is contained in:
czl231
2026-08-16 01:17:41 +08:00
parent 2942d9a71c
commit 255028e983
6 changed files with 117 additions and 29 deletions

View File

@@ -312,14 +312,18 @@ func MockData(database *gorm.DB) error {
ecOrder := models.EcOrder{ ecOrder := models.EcOrder{
Entity: entity(30, common.StatusEnable), OrderStatus: mockEcOrderPaidStatus(), OrderNo: "MOCK-ECORDER-001", Entity: entity(30, common.StatusEnable), OrderStatus: mockEcOrderPaidStatus(), OrderNo: "MOCK-ECORDER-001",
UserAccountID: user.ID, GasStationID: gas.ID, DeliveryPointID: delivery.ID, RequestNo: "MOCK-REQ-ECORDER-001", UserAccountID: user.ID,
GasStationID: gas.ID, DeliveryPointID: delivery.ID, UserAddressID: address.ID,
Address: address.Address, Longitude: address.Longitude, Latitude: address.Latitude,
ContactName: user.Name, ContactPhone: user.Phone, Remark: "模拟已支付商城订单",
ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount, ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount,
TotalAmount: ecProduct.PriceAmount, PaidAt: &now, TotalAmount: ecProduct.PriceAmount, PaidAt: &now,
} }
desiredEcOrder := ecOrder
if err := put(tx, &ecOrder); err != nil { if err := put(tx, &ecOrder); err != nil {
return err return err
} }
if err := repairMockEcOrder(tx, ecOrder, mockEcOrderPaidStatus(), ecProduct.PriceAmount, &now); err != nil { if err := repairMockEcOrder(tx, desiredEcOrder, mockEcOrderPaidStatus(), ecProduct.PriceAmount, &now); err != nil {
return err return err
} }
@@ -728,15 +732,19 @@ func seedAdditionalCoreScenarios(database *gorm.DB, passwordHash string, now tim
ecOrder := models.EcOrder{ ecOrder := models.EcOrder{
Entity: entity(sequence+16, common.StatusEnable), OrderStatus: mockEcOrderPendingStatus(), Entity: entity(sequence+16, common.StatusEnable), OrderStatus: mockEcOrderPendingStatus(),
OrderNo: "MOCK-ECORDER-" + suffix, UserAccountID: user.ID, OrderNo: "MOCK-ECORDER-" + suffix, RequestNo: "MOCK-REQ-ECORDER-" + suffix,
GasStationID: gas.ID, DeliveryPointID: delivery.ID, UserAccountID: user.ID, GasStationID: gas.ID, DeliveryPointID: delivery.ID,
UserAddressID: address.ID, Address: address.Address,
Longitude: address.Longitude, Latitude: address.Latitude,
ContactName: user.Name, ContactPhone: user.Phone, Remark: "模拟待支付商城订单",
ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount, ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount,
TotalAmount: ecProduct.PriceAmount, TotalAmount: ecProduct.PriceAmount,
} }
desiredEcOrder := ecOrder
if err := put(database, &ecOrder); err != nil { if err := put(database, &ecOrder); err != nil {
return err return err
} }
if err := repairMockEcOrder(database, ecOrder, mockEcOrderPendingStatus(), ecProduct.PriceAmount, nil); err != nil { if err := repairMockEcOrder(database, desiredEcOrder, mockEcOrderPendingStatus(), ecProduct.PriceAmount, nil); err != nil {
return err return err
} }
@@ -786,7 +794,11 @@ func repairMockEcOrder(database *gorm.DB, order models.EcOrder, status int, amou
Where("identity = ? AND order_no LIKE ?", order.Identity, "MOCK-ECORDER-%"). Where("identity = ? AND order_no LIKE ?", order.Identity, "MOCK-ECORDER-%").
Updates(map[string]any{ Updates(map[string]any{
"order_status": status, "product_amount": amount, "payable_amount": amount, "order_status": status, "product_amount": amount, "payable_amount": amount,
"total_amount": amount, "paid_at": paidAt, "total_amount": amount, "paid_at": paidAt, "request_no": order.RequestNo,
"user_address_id": order.UserAddressID, "address": order.Address,
"longitude": order.Longitude, "latitude": order.Latitude,
"contact_name": order.ContactName, "contact_phone": order.ContactPhone,
"remark": order.Remark,
}) })
if result.Error != nil { if result.Error != nil {
return fmt.Errorf("repair mock ec order: %w", result.Error) return fmt.Errorf("repair mock ec order: %w", result.Error)
@@ -795,30 +807,57 @@ func repairMockEcOrder(database *gorm.DB, order models.EcOrder, status int, amou
} }
type mockEcOrderRepairSpec struct { type mockEcOrderRepairSpec struct {
identity string identity string
orderNo string orderNo string
status int requestNo string
amount int64 userIdentity string
paidAt *time.Time addressIdentity string
contactName string
contactPhone string
address string
longitude string
latitude string
status int
amount int64
paidAt *time.Time
remark string
} }
// mockEcOrderRepairSpecs 返回固定 Mock 商城订单的文档化交易状态与金额。 // mockEcOrderRepairSpecs 返回固定 Mock 商城订单的文档化交易状态与金额。
func mockEcOrderRepairSpecs() []mockEcOrderRepairSpec { func mockEcOrderRepairSpecs() []mockEcOrderRepairSpec {
paidAt := time.Date(2026, time.July, 1, 9, 0, 0, 0, time.Local) paidAt := time.Date(2026, time.July, 1, 9, 0, 0, 0, time.Local)
specs := []mockEcOrderRepairSpec{{ specs := []mockEcOrderRepairSpec{{
identity: entity(30, common.StatusEnable).Identity, identity: entity(30, common.StatusEnable).Identity,
orderNo: "MOCK-ECORDER-001", orderNo: "MOCK-ECORDER-001",
status: mockEcOrderPaidStatus(), requestNo: "MOCK-REQ-ECORDER-001",
amount: 10300, userIdentity: entity(7, common.StatusEnable).Identity,
paidAt: &paidAt, addressIdentity: entity(8, common.StatusEnable).Identity,
contactName: "陈女士",
contactPhone: "13800000001",
address: "上海市浦东新区客户路 88 号",
longitude: "121.5500",
latitude: "31.2250",
status: mockEcOrderPaidStatus(),
amount: 10300,
paidAt: &paidAt,
remark: "模拟已支付商城订单",
}} }}
for scenario := 2; scenario <= 10; scenario++ { for scenario := 2; scenario <= 10; scenario++ {
sequence := 1000 + scenario*100 sequence := 1000 + scenario*100
specs = append(specs, mockEcOrderRepairSpec{ specs = append(specs, mockEcOrderRepairSpec{
identity: entity(sequence+16, common.StatusEnable).Identity, identity: entity(sequence+16, common.StatusEnable).Identity,
orderNo: fmt.Sprintf("MOCK-ECORDER-%03d", scenario), orderNo: fmt.Sprintf("MOCK-ECORDER-%03d", scenario),
status: mockEcOrderPendingStatus(), requestNo: fmt.Sprintf("MOCK-REQ-ECORDER-%03d", scenario),
amount: int64(9500 + scenario*100), userIdentity: entity(sequence+4, common.StatusEnable).Identity,
addressIdentity: entity(sequence+5, common.StatusEnable).Identity,
contactName: fmt.Sprintf("示例客户%d", scenario),
contactPhone: "138" + fmt.Sprintf("%08d", scenario),
address: fmt.Sprintf("上海市客户路 %d 号", scenario),
longitude: fmt.Sprintf("121.5%03d", scenario),
latitude: fmt.Sprintf("31.2%03d", scenario),
status: mockEcOrderPendingStatus(),
amount: int64(9500 + scenario*100),
remark: "模拟待支付商城订单",
}) })
} }
return specs return specs
@@ -829,12 +868,34 @@ func RepairMockEcOrders(database *gorm.DB) (int64, error) {
var repaired int64 var repaired int64
err := database.Transaction(func(tx *gorm.DB) error { err := database.Transaction(func(tx *gorm.DB) error {
for _, spec := range mockEcOrderRepairSpecs() { for _, spec := range mockEcOrderRepairSpecs() {
var user models.UserAccount
if err := tx.Where("identity = ?", spec.userIdentity).First(&user).Error; err != nil {
return fmt.Errorf("load mock ec order user %s: %w", spec.orderNo, err)
}
if err := tx.Model(&user).Updates(map[string]any{
"name": spec.contactName, "phone": spec.contactPhone,
}).Error; err != nil {
return fmt.Errorf("repair mock ec order user %s: %w", spec.orderNo, err)
}
var address models.UserAddress
if err := tx.Where("identity = ?", spec.addressIdentity).First(&address).Error; err != nil {
return fmt.Errorf("load mock ec order address %s: %w", spec.orderNo, err)
}
if err := tx.Model(&address).Updates(map[string]any{
"address": spec.address, "longitude": spec.longitude, "latitude": spec.latitude,
}).Error; err != nil {
return fmt.Errorf("repair mock ec order address %s: %w", spec.orderNo, err)
}
result := tx.Model(&models.EcOrder{}). result := tx.Model(&models.EcOrder{}).
Where("identity = ? AND order_no = ?", spec.identity, spec.orderNo). Where("identity = ? AND order_no = ?", spec.identity, spec.orderNo).
Updates(map[string]any{ Updates(map[string]any{
"order_status": spec.status, "product_amount": spec.amount, "order_status": spec.status, "product_amount": spec.amount,
"payable_amount": spec.amount, "total_amount": spec.amount, "payable_amount": spec.amount, "total_amount": spec.amount,
"paid_at": spec.paidAt, "paid_at": spec.paidAt, "request_no": spec.requestNo,
"user_address_id": address.ID, "address": spec.address,
"longitude": spec.longitude, "latitude": spec.latitude,
"contact_name": spec.contactName, "contact_phone": spec.contactPhone,
"remark": spec.remark,
}) })
if result.Error != nil { if result.Error != nil {
return fmt.Errorf("repair mock ec order %s: %w", spec.orderNo, result.Error) return fmt.Errorf("repair mock ec order %s: %w", spec.orderNo, result.Error)

View File

@@ -117,10 +117,17 @@ func TestMockEcOrderRepairSpecs(t *testing.T) {
t.Fatalf("首条已支付 Mock 订单修复配置错误:%#v", specs[0]) t.Fatalf("首条已支付 Mock 订单修复配置错误:%#v", specs[0])
} }
for _, spec := range specs[1:] { for _, spec := range specs[1:] {
if spec.status != 16 || spec.paidAt != nil || spec.amount <= 0 { if spec.status != 16 || spec.paidAt != nil || spec.amount <= 0 ||
spec.contactName == "" || spec.contactPhone == "" || spec.address == "" ||
spec.longitude == "" || spec.latitude == "" {
t.Fatalf("待支付 Mock 订单修复配置错误:%#v", spec) t.Fatalf("待支付 Mock 订单修复配置错误:%#v", spec)
} }
} }
last := specs[len(specs)-1]
if last.contactName != "示例客户10" || last.address != "上海市客户路 10 号" ||
last.longitude != "121.5010" || last.latitude != "31.2010" {
t.Fatalf("第十组 Mock 快照格式错误:%#v", last)
}
} }
// TestMockTrackPointSampleRequiredFields 验证演示轨迹点符合真实上报的必填字段和可读展示要求。 // TestMockTrackPointSampleRequiredFields 验证演示轨迹点符合真实上报的必填字段和可读展示要求。

View File

@@ -19,8 +19,11 @@
3. 将 Mock 待支付订单从 `10` 修复为 `16`,已支付订单从 `35` 修复为 `18` 3. 将 Mock 待支付订单从 `10` 修复为 `16`,已支付订单从 `35` 修复为 `18`
4. 补齐 Mock 订单商品金额、应付金额及已支付订单的支付时间。 4. 补齐 Mock 订单商品金额、应付金额及已支付订单的支付时间。
5. 待支付订单的空支付时间明确显示为“未支付”,不再显示无语义短横线。 5. 待支付订单的空支付时间明确显示为“未支付”,不再显示无语义短横线。
6. 增加独立幂等修复命令,只更新固定十条商城 Mock 订单 6. 补齐 Mock 订单的请求流水号、地址、经纬度、联系人、联系电话和备注快照
7. 增加前端展示契约与后端种子测试,防止跨领域状态再次混入商城订单 7. 修复第十组既有 Mock 用户名称、电话、地址及经纬度的错误值
8. 待发货、未收货及无备注字段使用明确业务文案,不再统一显示短横线。
9. 增加独立幂等修复命令,只更新固定十条商城 Mock 订单及其用户、地址快照来源。
10. 增加前端展示契约与后端种子测试,防止跨领域状态再次混入商城订单。
## 行为变化 ## 行为变化

View File

@@ -65,4 +65,4 @@ frontend/platform_admin/
- v1.2:树形资源统一改为树形表格,明确展示名称、排序、状态、系统标识和操作,唯一标识降级为复制入口。 - v1.2:树形资源统一改为树形表格,明确展示名称、排序、状态、系统标识和操作,唯一标识降级为复制入口。
- v1.2.1:补齐 Arco 表格 `columns` 插槽,修复树形表格容器存在但列和数据不渲染的问题。 - v1.2.1:补齐 Arco 表格 `columns` 插槽,修复树形表格容器存在但列和数据不渲染的问题。
- v1.3购物车列表的用户与商品关系列统一展示可读名称UUID 降级为复制和排障信息。 - v1.3购物车列表的用户与商品关系列统一展示可读名称UUID 降级为复制和排障信息。
- v1.4:商城订单列表统一展示用户名称,未支付时间明确显示“未支付”Mock 订单改用商城独立交易状态,并补齐金额支付时间。 - v1.4:商城订单统一展示用户名称及空值业务文案Mock 订单改用商城独立交易状态,并补齐金额支付时间和下单快照

View File

@@ -136,6 +136,20 @@ for (const invalidStatus of [10, 35]) {
if (!orderDefinition.includes("f('paid_at', { emptyText: '未支付' })")) { if (!orderDefinition.includes("f('paid_at', { emptyText: '未支付' })")) {
throw new Error('商城订单空支付时间必须明确显示为未支付'); throw new Error('商城订单空支付时间必须明确显示为未支付');
} }
for (const displayContract of [
"f('request_no', { emptyText: '未记录' })",
"f('contact_name', { emptyText: '未记录' })",
"f('contact_phone', { emptyText: '未记录' })",
"f('logistics_no', { emptyText: '未发货' })",
"f('logistics_company', { emptyText: '未发货' })",
"f('shipped_at', { emptyText: '未发货' })",
"f('received_at', { emptyText: '未收货' })",
"f('remark', { emptyText: '无备注' })",
]) {
if (!orderDefinition.includes(displayContract)) {
throw new Error(`商城订单详情缺少空值业务文案:${displayContract}`);
}
}
if (/title="ID"|data-index="id"/.test(listPage)) { if (/title="ID"|data-index="id"/.test(listPage)) {
throw new Error('标准资源列表不得把数据库ID作为业务列展示'); throw new Error('标准资源列表不得把数据库ID作为业务列展示');

View File

@@ -706,7 +706,7 @@ export const resources: ResourceUiDefinition[] = [
f('quantity'), f('selected'), f('quantity'), f('selected'),
]), ]),
define('ec_order', '商城订单', 'readonly', [ define('ec_order', '商城订单', 'readonly', [
f('order_no'), f('request_no'), f('order_status', { type: 'select', options: [ f('order_no'), f('request_no', { emptyText: '未记录' }), f('order_status', { type: 'select', options: [
{ label: '待支付', value: 16 }, { label: '待支付', value: 16 },
{ label: '已支付', value: 18 }, { label: '已支付', value: 18 },
{ label: '已取消', value: 22 }, { label: '已取消', value: 22 },
@@ -716,9 +716,12 @@ export const resources: ResourceUiDefinition[] = [
displayRelationLabel: true, showIdentityCopy: true, displayRelationLabel: true, showIdentityCopy: true,
}), relation('gas_basic_identity', '/gas_basic'), }), relation('gas_basic_identity', '/gas_basic'),
relation('delivery_basic_identity', '/delivery_basic'), relation('user_address_identity', '/user_address'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_address_identity', '/user_address'),
f('contact_name'), f('contact_phone'), f('product_amount'), f('discount_amount'), f('payable_amount'), f('contact_name', { emptyText: '未记录' }), f('contact_phone', { emptyText: '未记录' }),
f('total_amount'), f('paid_at', { emptyText: '未支付' }), f('logistics_no'), f('logistics_company'), f('logistics_status'), f('product_amount'), f('discount_amount'), f('payable_amount'),
f('shipped_at'), f('received_at'), f('remark'), f('total_amount'), f('paid_at', { emptyText: '未支付' }),
f('logistics_no', { emptyText: '未发货' }), f('logistics_company', { emptyText: '未发货' }),
f('logistics_status'), f('shipped_at', { emptyText: '未发货' }),
f('received_at', { emptyText: '未收货' }), f('remark', { emptyText: '无备注' }),
]), ]),
define('ec_order_item', '商城订单明细', 'readonly', [ define('ec_order_item', '商城订单明细', 'readonly', [
relation('ec_order_identity', '/ec_order'), relation('ec_product_identity', '/ec_product'), relation('ec_order_identity', '/ec_order'), relation('ec_product_identity', '/ec_product'),