feat(rock): 关联业务工单升级为双单号清晰展示(系统工单号 + 创建商户单号)

1. 后端批量反查商户单号:在 RockService.ListTokens 中支持批量反查关联订单创建时由调用方传入的 ClientOrderNo(商户单号),并拓展支持通过商户单号进行全局关键词模糊搜索;
2. 管理后台双单号排版升级:在 Rock 管理表格中将「关联业务工单」升级为双行呈现——第一行高亮展示内部统一系统工单号 (FO2026...),第二行辅助展示外部电商/商户原始单号 (如 test4 / 拼多多单号),两行均支持一键独立复制;
3. 单元测试全覆盖:补充商户单号自动挂载关联与按商户单号模糊检索的自动化单测,确保对单溯源精准无误。
This commit is contained in:
yml2213
2026-10-05 19:18:52 +08:00
parent 262376e603
commit 9e260059ee
5 changed files with 104 additions and 11 deletions
+1
View File
@@ -56,6 +56,7 @@ type RockToken struct {
Region string `gorm:"size:64;not null;default:'Q/V可用'" json:"region"`
Status string `gorm:"size:32;not null;default:'available';index" json:"status"`
AssignedOrderNo string `gorm:"size:128;index" json:"assigned_order_no"`
ClientOrderNo string `gorm:"-" json:"client_order_no,omitempty"` // 关联的创建订单商户单号
ExpectedUID string `gorm:"size:64" json:"expected_uid"`
ActualRoleName string `gorm:"size:128" json:"actual_role_name"`
ActualRoleID string `gorm:"size:64" json:"actual_role_id"`
@@ -459,4 +459,41 @@ func TestRockService_StatsAndPurchase(t *testing.T) {
if total != 2 || len(list) != 2 {
t.Fatalf("期望查询到 2 条,实际为 total=%d len=%d", total, len(list))
}
// 6. 测试关联履约订单与创建订单商户单号反查
orderNo := "FO_TEST_ROCK_999"
clientOrderNo := "SHOP_ORDER_PDD_888"
_ = db.Create(&model.FulfillmentOrder{
OrderNo: orderNo,
ClientOrderNo: clientOrderNo,
ProductSKU: "rock_black_tshirt",
ProductName: "黑色ROCK长袖T恤",
}).Error
_ = db.Model(&model.RockToken{}).Where("id = ?", list[0].ID).Update("assigned_order_no", orderNo).Error
listWithClient, _, err := rockSvc.ListTokens(context.Background(), 1, 10, "", "")
if err != nil {
t.Fatalf("list tokens with client: %v", err)
}
found := false
for _, tk := range listWithClient {
if tk.ID == list[0].ID {
found = true
if tk.ClientOrderNo != clientOrderNo {
t.Fatalf("期望商户单号为 %s,实际为 %s", clientOrderNo, tk.ClientOrderNo)
}
}
}
if !found {
t.Fatalf("未找到对应的 Token 记录")
}
// 7. 测试按商户单号关键词搜索
searchList, searchTotal, err := rockSvc.ListTokens(context.Background(), 1, 10, "", "PDD_888")
if err != nil {
t.Fatalf("search tokens by client_order_no: %v", err)
}
if searchTotal != 1 || len(searchList) != 1 {
t.Fatalf("期望按商户单号搜索出 1 条,实际为 total=%d len=%d", searchTotal, len(searchList))
}
}
+30 -2
View File
@@ -643,8 +643,8 @@ func (s *RockService) ListTokens(ctx context.Context, page, pageSize int, status
}
if strings.TrimSpace(search) != "" {
term := "%" + strings.TrimSpace(search) + "%"
db = db.Where("assigned_order_no LIKE ? OR expected_uid LIKE ? OR actual_role_name LIKE ? OR actual_role_id LIKE ? OR redeem_code LIKE ?",
term, term, term, term, term)
db = db.Where("assigned_order_no LIKE ? OR expected_uid LIKE ? OR actual_role_name LIKE ? OR actual_role_id LIKE ? OR redeem_code LIKE ? OR assigned_order_no IN (SELECT order_no FROM fulfillment_orders WHERE client_order_no LIKE ?)",
term, term, term, term, term, term)
}
var total int64
@@ -661,6 +661,34 @@ func (s *RockService) ListTokens(ctx context.Context, page, pageSize int, status
return nil, 0, err
}
// 批量反查关联订单的商户单号(创建订单时的 ClientOrderNo)
orderNos := make([]string, 0, len(tokens))
for _, t := range tokens {
if t.AssignedOrderNo != "" {
orderNos = append(orderNos, t.AssignedOrderNo)
}
}
if len(orderNos) > 0 {
var orders []struct {
OrderNo string `gorm:"column:order_no"`
ClientOrderNo string `gorm:"column:client_order_no"`
}
if scanErr := s.db.WithContext(ctx).Table("fulfillment_orders").
Select("order_no, client_order_no").
Where("order_no IN ?", orderNos).
Scan(&orders).Error; scanErr == nil {
clientMap := make(map[string]string, len(orders))
for _, o := range orders {
clientMap[o.OrderNo] = o.ClientOrderNo
}
for i := range tokens {
if cNo, ok := clientMap[tokens[i].AssignedOrderNo]; ok && cNo != "" {
tokens[i].ClientOrderNo = cNo
}
}
}
}
return tokens, total, nil
}
+1
View File
@@ -301,6 +301,7 @@ export interface RockTokenItem {
region: string
status: string
assigned_order_no?: string
client_order_no?: string
expected_uid?: string
actual_role_name?: string
actual_role_id?: string
+35 -9
View File
@@ -357,15 +357,41 @@ export default function PlatformRock() {
{
title: '关联业务工单',
dataIndex: 'assigned_order_no',
width: 190,
render: (orderNo) =>
orderNo ? (
<Text code copyable={{ text: orderNo }} style={{ fontWeight: 600, color: '#1d4ed8' }}>
{orderNo}
</Text>
) : (
<Text type="secondary">-</Text>
),
width: 220,
render: (orderNo, record) => {
if (!orderNo) {
return <Text type="secondary">-</Text>
}
return (
<Space direction="vertical" size={3}>
{/* 系统履约工单号 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<Text
code
copyable={{ text: orderNo }}
style={{ fontWeight: 600, color: '#1d4ed8', fontSize: 12 }}
title="系统履约工单号"
>
{orderNo}
</Text>
</div>
{/* 创建订单时的商户单号 */}
{record.client_order_no ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12 }}>
<span style={{ color: '#64748b' }}>商户:</span>
<Text
copyable={{ text: record.client_order_no }}
type="secondary"
style={{ fontFamily: 'monospace', fontSize: 12 }}
title="创建订单时的商户单号 (ClientOrderNo)"
>
{record.client_order_no}
</Text>
</div>
) : null}
</Space>
)
},
},
{
title: '预期 UID vs 扫码角色 (风控门禁)',