修复商城订单列表展示与模拟数据
This commit is contained in:
@@ -64,6 +64,13 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Printf("mock gasorder statuses repaired: %d\n", count)
|
fmt.Printf("mock gasorder statuses repaired: %d\n", count)
|
||||||
|
case "repair-mock-ec-order":
|
||||||
|
count, err := repairMockEcOrder()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("mock ec orders repaired: %d\n", count)
|
||||||
case "repair-mock-track-point":
|
case "repair-mock-track-point":
|
||||||
count, err := repairMockTrackPoint()
|
count, err := repairMockTrackPoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,7 +92,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printUsage() {
|
func printUsage() {
|
||||||
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract|gas-resource-contract|delivery-resource-contract|migrate|mock-data|repair-mock-gasorder-status|repair-mock-track-point>")
|
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract|gas-resource-contract|delivery-resource-contract|migrate|mock-data|repair-mock-gasorder-status|repair-mock-ec-order|repair-mock-track-point>")
|
||||||
}
|
}
|
||||||
|
|
||||||
type route struct {
|
type route struct {
|
||||||
@@ -216,6 +223,27 @@ func repairMockGasorderStatus() (int64, error) {
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// repairMockEcOrder 使用独立幂等命令修复商城 Mock 订单,不补写其他模拟资源。
|
||||||
|
func repairMockEcOrder() (int64, error) {
|
||||||
|
config.New(serviceKey)
|
||||||
|
if config.Spec.Databases == nil {
|
||||||
|
return 0, fmt.Errorf("database configuration is required")
|
||||||
|
}
|
||||||
|
databaseService, err := database.NewDatabase(
|
||||||
|
config.Spec.Databases.Driver,
|
||||||
|
config.Spec.Databases.Source,
|
||||||
|
dbsql.SetOptions(nil),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("connect database: %w", err)
|
||||||
|
}
|
||||||
|
count, err := seed.RepairMockEcOrders(databaseService)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("repair mock ec orders: %w", err)
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
// repairMockTrackPoint 使用独立幂等命令补全固定演示轨迹点,不触发其他模拟数据写入。
|
// repairMockTrackPoint 使用独立幂等命令补全固定演示轨迹点,不触发其他模拟数据写入。
|
||||||
func repairMockTrackPoint() (int64, error) {
|
func repairMockTrackPoint() (int64, error) {
|
||||||
config.New(serviceKey)
|
config.New(serviceKey)
|
||||||
|
|||||||
@@ -311,13 +311,17 @@ func MockData(database *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ecOrder := models.EcOrder{
|
ecOrder := models.EcOrder{
|
||||||
Entity: entity(30, common.StatusEnable), OrderStatus: common.StatusPaid, OrderNo: "MOCK-ECORDER-001",
|
Entity: entity(30, common.StatusEnable), OrderStatus: mockEcOrderPaidStatus(), OrderNo: "MOCK-ECORDER-001",
|
||||||
UserAccountID: user.ID, GasStationID: gas.ID, DeliveryPointID: delivery.ID,
|
UserAccountID: user.ID, GasStationID: gas.ID, DeliveryPointID: delivery.ID,
|
||||||
TotalAmount: ecProduct.PriceAmount,
|
ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount,
|
||||||
|
TotalAmount: ecProduct.PriceAmount, PaidAt: &now,
|
||||||
}
|
}
|
||||||
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ecOrderItem := models.EcOrderItem{
|
ecOrderItem := models.EcOrderItem{
|
||||||
Entity: entity(31, common.StatusEnable), EcOrderID: ecOrder.ID, EcProductID: ecProduct.ID,
|
Entity: entity(31, common.StatusEnable), EcOrderID: ecOrder.ID, EcProductID: ecProduct.ID,
|
||||||
@@ -723,13 +727,18 @@ func seedAdditionalCoreScenarios(database *gorm.DB, passwordHash string, now tim
|
|||||||
}
|
}
|
||||||
|
|
||||||
ecOrder := models.EcOrder{
|
ecOrder := models.EcOrder{
|
||||||
Entity: entity(sequence+16, common.StatusEnable), OrderStatus: common.StatusPending,
|
Entity: entity(sequence+16, common.StatusEnable), OrderStatus: mockEcOrderPendingStatus(),
|
||||||
OrderNo: "MOCK-ECORDER-" + suffix, UserAccountID: user.ID,
|
OrderNo: "MOCK-ECORDER-" + suffix, UserAccountID: user.ID,
|
||||||
GasStationID: gas.ID, DeliveryPointID: delivery.ID, TotalAmount: ecProduct.PriceAmount,
|
GasStationID: gas.ID, DeliveryPointID: delivery.ID,
|
||||||
|
ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount,
|
||||||
|
TotalAmount: ecProduct.PriceAmount,
|
||||||
}
|
}
|
||||||
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
wallet := models.WalletBasic{
|
wallet := models.WalletBasic{
|
||||||
Entity: entity(sequence+17, common.StatusEnable), OwnerType: "user", OwnerID: user.ID,
|
Entity: entity(sequence+17, common.StatusEnable), OwnerType: "user", OwnerID: user.ID,
|
||||||
@@ -767,6 +776,76 @@ func mockGasorderInitialStatus() int {
|
|||||||
return common.StatusCreated
|
return common.StatusCreated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mock 商城订单使用商城交易状态,不复用其他领域含义相近的通用状态。
|
||||||
|
func mockEcOrderPendingStatus() int { return 16 }
|
||||||
|
func mockEcOrderPaidStatus() int { return 18 }
|
||||||
|
|
||||||
|
// repairMockEcOrder 幂等修复固定 Mock 商城订单的状态、金额与支付时间。
|
||||||
|
func repairMockEcOrder(database *gorm.DB, order models.EcOrder, status int, amount int64, paidAt *time.Time) error {
|
||||||
|
result := database.Model(&models.EcOrder{}).
|
||||||
|
Where("identity = ? AND order_no LIKE ?", order.Identity, "MOCK-ECORDER-%").
|
||||||
|
Updates(map[string]any{
|
||||||
|
"order_status": status, "product_amount": amount, "payable_amount": amount,
|
||||||
|
"total_amount": amount, "paid_at": paidAt,
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("repair mock ec order: %w", result.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockEcOrderRepairSpec struct {
|
||||||
|
identity string
|
||||||
|
orderNo string
|
||||||
|
status int
|
||||||
|
amount int64
|
||||||
|
paidAt *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockEcOrderRepairSpecs 返回固定 Mock 商城订单的文档化交易状态与金额。
|
||||||
|
func mockEcOrderRepairSpecs() []mockEcOrderRepairSpec {
|
||||||
|
paidAt := time.Date(2026, time.July, 1, 9, 0, 0, 0, time.Local)
|
||||||
|
specs := []mockEcOrderRepairSpec{{
|
||||||
|
identity: entity(30, common.StatusEnable).Identity,
|
||||||
|
orderNo: "MOCK-ECORDER-001",
|
||||||
|
status: mockEcOrderPaidStatus(),
|
||||||
|
amount: 10300,
|
||||||
|
paidAt: &paidAt,
|
||||||
|
}}
|
||||||
|
for scenario := 2; scenario <= 10; scenario++ {
|
||||||
|
sequence := 1000 + scenario*100
|
||||||
|
specs = append(specs, mockEcOrderRepairSpec{
|
||||||
|
identity: entity(sequence+16, common.StatusEnable).Identity,
|
||||||
|
orderNo: fmt.Sprintf("MOCK-ECORDER-%03d", scenario),
|
||||||
|
status: mockEcOrderPendingStatus(),
|
||||||
|
amount: int64(9500 + scenario*100),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return specs
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepairMockEcOrders 仅修复固定 Mock 商城订单,不触发其他模拟资源写入。
|
||||||
|
func RepairMockEcOrders(database *gorm.DB) (int64, error) {
|
||||||
|
var repaired int64
|
||||||
|
err := database.Transaction(func(tx *gorm.DB) error {
|
||||||
|
for _, spec := range mockEcOrderRepairSpecs() {
|
||||||
|
result := tx.Model(&models.EcOrder{}).
|
||||||
|
Where("identity = ? AND order_no = ?", spec.identity, spec.orderNo).
|
||||||
|
Updates(map[string]any{
|
||||||
|
"order_status": spec.status, "product_amount": spec.amount,
|
||||||
|
"payable_amount": spec.amount, "total_amount": spec.amount,
|
||||||
|
"paid_at": spec.paidAt,
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("repair mock ec order %s: %w", spec.orderNo, result.Error)
|
||||||
|
}
|
||||||
|
repaired += result.RowsAffected
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return repaired, err
|
||||||
|
}
|
||||||
|
|
||||||
// repairMockGasorderInitialStatus 幂等修复历史种子数据中误写为“待处理”的 Mock 配送订单。
|
// repairMockGasorderInitialStatus 幂等修复历史种子数据中误写为“待处理”的 Mock 配送订单。
|
||||||
func repairMockGasorderInitialStatus(database *gorm.DB, order models.GasorderBasic) error {
|
func repairMockGasorderInitialStatus(database *gorm.DB, order models.GasorderBasic) error {
|
||||||
result := database.Model(&models.GasorderBasic{}).
|
result := database.Model(&models.GasorderBasic{}).
|
||||||
|
|||||||
@@ -94,6 +94,35 @@ func TestMockGasorderInitialStatusMatchesWorkflow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMockEcOrderStatusesMatchWorkflow 验证 Mock 商城订单遵循商城独立交易状态。
|
||||||
|
func TestMockEcOrderStatusesMatchWorkflow(t *testing.T) {
|
||||||
|
if got := mockEcOrderPendingStatus(); got != 16 {
|
||||||
|
t.Fatalf("Mock 商城待支付状态 = %d, want 16", got)
|
||||||
|
}
|
||||||
|
if got := mockEcOrderPaidStatus(); got != 18 {
|
||||||
|
t.Fatalf("Mock 商城已支付状态 = %d, want 18", got)
|
||||||
|
}
|
||||||
|
if mockEcOrderPendingStatus() == common.StatusPending || mockEcOrderPaidStatus() == common.StatusPaid {
|
||||||
|
t.Fatal("Mock 商城订单不得复用跨领域通用状态")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMockEcOrderRepairSpecs 验证修复命令只覆盖固定十条商城 Mock 订单。
|
||||||
|
func TestMockEcOrderRepairSpecs(t *testing.T) {
|
||||||
|
specs := mockEcOrderRepairSpecs()
|
||||||
|
if len(specs) != 10 {
|
||||||
|
t.Fatalf("Mock 商城订单修复数量 = %d, want 10", len(specs))
|
||||||
|
}
|
||||||
|
if specs[0].orderNo != "MOCK-ECORDER-001" || specs[0].status != 18 || specs[0].paidAt == nil {
|
||||||
|
t.Fatalf("首条已支付 Mock 订单修复配置错误:%#v", specs[0])
|
||||||
|
}
|
||||||
|
for _, spec := range specs[1:] {
|
||||||
|
if spec.status != 16 || spec.paidAt != nil || spec.amount <= 0 {
|
||||||
|
t.Fatalf("待支付 Mock 订单修复配置错误:%#v", spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestMockTrackPointSampleRequiredFields 验证演示轨迹点符合真实上报的必填字段和可读展示要求。
|
// TestMockTrackPointSampleRequiredFields 验证演示轨迹点符合真实上报的必填字段和可读展示要求。
|
||||||
func TestMockTrackPointSampleRequiredFields(t *testing.T) {
|
func TestMockTrackPointSampleRequiredFields(t *testing.T) {
|
||||||
if mockTrackPointRequestNo == "" {
|
if mockTrackPointRequestNo == "" {
|
||||||
|
|||||||
56
docs/操作日志_商城订单列表展示修复_20260816.md
Normal file
56
docs/操作日志_商城订单列表展示修复_20260816.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# 商城订单列表展示修复操作日志
|
||||||
|
|
||||||
|
操作时间:2026-08-16
|
||||||
|
|
||||||
|
操作类型:修改
|
||||||
|
|
||||||
|
影响模块:平台总后台电商平台管理、商城订单只读列表
|
||||||
|
|
||||||
|
## 操作前状态
|
||||||
|
|
||||||
|
- 商城订单用户关系按普通唯一标识字段展示,管理员只能看到 UUID。
|
||||||
|
- Mock 数据误用跨领域通用状态 `10`、`35`,与商城正式实现的独立交易状态不一致。
|
||||||
|
- 已支付 Mock 订单缺少支付时间,部分 Mock 订单的商品金额和应付金额为零。
|
||||||
|
|
||||||
|
## 具体操作
|
||||||
|
|
||||||
|
1. 将商城订单用户列改为名称优先展示,并保留 UUID 复制与详情跳转。
|
||||||
|
2. 保持商城独立交易状态:`16=待支付`、`18=已支付`、`22=已取消`。
|
||||||
|
3. 将 Mock 待支付订单从 `10` 修复为 `16`,已支付订单从 `35` 修复为 `18`。
|
||||||
|
4. 补齐 Mock 订单商品金额、应付金额及已支付订单的支付时间。
|
||||||
|
5. 待支付订单的空支付时间明确显示为“未支付”,不再显示无语义短横线。
|
||||||
|
6. 增加独立幂等修复命令,只更新固定十条商城 Mock 订单。
|
||||||
|
7. 增加前端展示契约与后端种子测试,防止跨领域状态再次混入商城订单。
|
||||||
|
|
||||||
|
## 行为变化
|
||||||
|
|
||||||
|
- 修改前:用户 UUID 作为主要信息;Mock 订单状态、金额和支付时间与商城流程不一致。
|
||||||
|
- 修改后:用户名称作为主要信息,待支付记录明确显示“未支付”;重新执行 Mock 数据命令时会幂等修复既有 Mock 订单。
|
||||||
|
- 商城订单仍为只读资源,正式业务接口、权限和非 Mock 订单数据均未改变。
|
||||||
|
|
||||||
|
## 代码变更
|
||||||
|
|
||||||
|
- `frontend/platform_admin/src/api/resources.ts`:更新商城订单用户展示契约。
|
||||||
|
- `frontend/platform_admin/scripts/check-resource-display-contracts.mjs`:增加商城订单回归检查。
|
||||||
|
- `backend/api/internal/seed/mock.go`:修正并幂等修复 Mock 商城订单状态、金额和支付时间。
|
||||||
|
- `backend/api/internal/seed/mock_test.go`:增加商城独立状态回归测试。
|
||||||
|
- `backend/api/cmd/cli/main.go`:增加 `repair-mock-ec-order` 独立修复命令。
|
||||||
|
- `docs/项目文档_平台资源中文展示统一_v1.0.md`:记录 v1.4 变更。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- `pnpm resource-display-contracts:check`:通过。
|
||||||
|
- `pnpm type:check`:通过。
|
||||||
|
- `pnpm build`:通过,Vite 成功完成 2621 个模块的生产构建。
|
||||||
|
- `go test ./internal/seed ./cmd/cli`:通过。
|
||||||
|
- `go test ./...`:通过。
|
||||||
|
- `go vet ./...`:通过。
|
||||||
|
- `go build ./cmd/main/main.go`:通过。
|
||||||
|
- `go run ./cmd/cli repair-mock-ec-order`:通过,固定十条商城 Mock 订单已全部修复。
|
||||||
|
- 完整 `mock-data` 命令曾因既有 `mock_producer_010` 用户名冲突失败,事务已整体回滚;随后使用独立修复命令成功处理商城订单,未修改冲突的生产商数据。
|
||||||
|
|
||||||
|
## 风险评估
|
||||||
|
|
||||||
|
- 仅固定 `MOCK-ECORDER-*` 与 Mock identity 范围的数据会被种子逻辑修复。
|
||||||
|
- 关系加载失败时继续显示“名称加载失败”,UUID 可用于排障。
|
||||||
|
- 既有重复生产商 Mock 账号仍需单独排查,不属于本次商城订单展示修复范围。
|
||||||
@@ -65,3 +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 订单改用商城独立交易状态,并补齐金额与支付时间。
|
||||||
|
|||||||
@@ -102,6 +102,41 @@ for (const [key, label] of [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 商城订单必须遵循独立交易状态,并以名称展示用户关系。
|
||||||
|
const orderDefinition = resources.match(
|
||||||
|
/define\('ec_order',[\s\S]*?\n\s*\]\),/,
|
||||||
|
)?.[0];
|
||||||
|
if (!orderDefinition) throw new Error('无法读取商城订单资源定义');
|
||||||
|
for (const [value, label] of [
|
||||||
|
[16, '待支付'],
|
||||||
|
[18, '已支付'],
|
||||||
|
[22, '已取消'],
|
||||||
|
]) {
|
||||||
|
if (!orderDefinition.includes(`{ label: '${label}', value: ${value} }`)) {
|
||||||
|
throw new Error(`商城订单状态 ${value} 缺少中文展示:${label}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const orderUserField = orderDefinition.match(
|
||||||
|
/relation\('user_account_identity'[\s\S]*?\}\)/,
|
||||||
|
)?.[0];
|
||||||
|
if (
|
||||||
|
!orderUserField ||
|
||||||
|
!orderUserField.includes("listLabel: '用户'") ||
|
||||||
|
!orderUserField.includes('listRelationNameOnly: true') ||
|
||||||
|
!orderUserField.includes('displayRelationLabel: true') ||
|
||||||
|
!orderUserField.includes('showIdentityCopy: true')
|
||||||
|
) {
|
||||||
|
throw new Error('商城订单用户列必须以用户名称展示并保留唯一标识复制入口');
|
||||||
|
}
|
||||||
|
for (const invalidStatus of [10, 35]) {
|
||||||
|
if (orderDefinition.includes(`value: ${invalidStatus}`)) {
|
||||||
|
throw new Error(`商城订单不得把跨领域通用状态 ${invalidStatus} 当作有效交易状态`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!orderDefinition.includes("f('paid_at', { emptyText: '未支付' })")) {
|
||||||
|
throw new Error('商城订单空支付时间必须明确显示为未支付');
|
||||||
|
}
|
||||||
|
|
||||||
if (/title="ID"|data-index="id"/.test(listPage)) {
|
if (/title="ID"|data-index="id"/.test(listPage)) {
|
||||||
throw new Error('标准资源列表不得把数据库ID作为业务列展示');
|
throw new Error('标准资源列表不得把数据库ID作为业务列展示');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -711,10 +711,13 @@ export const resources: ResourceUiDefinition[] = [
|
|||||||
{ label: '已支付', value: 18 },
|
{ label: '已支付', value: 18 },
|
||||||
{ label: '已取消', value: 22 },
|
{ label: '已取消', value: 22 },
|
||||||
], unknownValueLabel: '未知订单状态' }),
|
], unknownValueLabel: '未知订单状态' }),
|
||||||
relation('user_account_identity', '/user_account'), relation('gas_basic_identity', '/gas_basic'),
|
relation('user_account_identity', '/user_account', false, {
|
||||||
|
label: '用户', listLabel: '用户', listRelationNameOnly: true,
|
||||||
|
displayRelationLabel: true, showIdentityCopy: true,
|
||||||
|
}), 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'), f('contact_phone'), f('product_amount'), f('discount_amount'), f('payable_amount'),
|
||||||
f('total_amount'), f('paid_at'), f('logistics_no'), f('logistics_company'), f('logistics_status'),
|
f('total_amount'), f('paid_at', { emptyText: '未支付' }), f('logistics_no'), f('logistics_company'), f('logistics_status'),
|
||||||
f('shipped_at'), f('received_at'), f('remark'),
|
f('shipped_at'), f('received_at'), f('remark'),
|
||||||
]),
|
]),
|
||||||
define('ec_order_item', '商城订单明细', 'readonly', [
|
define('ec_order_item', '商城订单明细', 'readonly', [
|
||||||
|
|||||||
Reference in New Issue
Block a user