2026-09-24,用户提出(经多轮澄清):新增退货商品模块的目的,是把 yeeke 退货商品重新利用,与 SYB 待采购订单商品匹配;匹配成功后该 SYB 订单商品不再去 PDD 等平台采购。用户原话要点:
d0b8609
syb_product
shopee_item_id
target_color
target_size
yeeke_return_item
item_id
variation_id
variation_name
紫色,M 【建議43-53公斤】
颜色,尺码
白色,2XL
白色,2XL【建議65-75公斤】
destroy_dead_line
server/app/goauto/purchase/process_stage.go
syb_product.shopee_item_id = yeeke_return_item.item_id
target_color + "," + target_size
【】
()
()
[]
created_at
待确认
已确认
已取消
SYB 商品页
退货商品页
prototypes/338/v1/index.html
无。原两条已于 2026-09-24 由用户确认:
开始实施(服务端核心已完成,Web 页面本轮未实现,见下):
return_match
server/app/goauto/models/return_match.go
migrations.MigratedModels()
ActiveSYBProductID
ActiveYeekeReturnItemID
YeekeSyncRun.ActiveSlot
server/app/goauto/returnmatch/{normalize,candidate}.go
server/app/goauto/returnmatch/{service,handler,router}.go
POST /api/admin/v1/return-matches/batch-match
GET /api/admin/v1/return-matches
GET .../:id
POST .../:id/{confirm,cancel,remark}
admin/purchaser
yeeke.Handler.TriggerSync
server/app/goauto/purchase/return_match_block.go
rejectIfActiveReturnMatch
Service.create
BatchCreate
Create
process_stage.go
batch.go
activeReturnMatchBySYB
sys_opera_log
Web 页面(SYB 商品页新列/按钮/对话框、退货商品页新筛选/列)本轮未实现,超出当前会话可完成范围,留待后续提交或确认后再补。
测试:server/app/goauto/returnmatch/... 全部通过(含并发唯一约束测试);server/app/goauto/purchase/... 全部通过(含新增拦截回归测试);go build ./...、go vet ./app/goauto/...、gofmt 均通过;sybimport 既有 12 个失败用例数量核对不变(本次未触碰该包)。
server/app/goauto/returnmatch/...
server/app/goauto/purchase/...
go build ./...
go vet ./app/goauto/...
gofmt
sybimport
来源:用户本地试用后询问两次匹配结果是否正常;因匹配请求未记录提交范围,无法还原当次勾选。用户原话:「把每次批量匹配提交了哪些商品、各自是什么结果也写进日志」。
return_match_batch
1789800800000
GET /api/admin/v1/return-matches/batches?limit=&withItems=1
GET /api/admin/v1/return-matches/batches/:batchId
1e944b9
TestBatchMatch_RecordsSubmittedProductsAndOutcomes
TestBatchMatch_EmptySubmissionNotRecorded
另记:本次同时补了 1789800700000_return_match 迁移版本(20a8144)。原实现只把模型注册进 MigratedModels,本地迁移被框架检查拦截「迁移后仍缺少表」,未对库做任何改动;补版本后迁移成功。
1789800700000_return_match
20a8144
来源:全栈评审发现(P1-1)。本地 7 件退货在最近一次成功同步(15:20)中已不在 yeeke「已认领」列表,但销毁截止仍在未来(最早 09-25),可用池仍会把它们匹配出去。用户原话:「要,因为是已yeeke的数据为准,如果本地有的可匹配的退货商品,但yeeke上找不到,就应该更新本地的这些退货商品的状态.」
事实:yeeke/sync.go 每次全量分页同步(第 1 页到末页),只 upsert 不处理消失的记录;yeeke_return_package/item.sync_status 已存在且恒为 ok;last_synced_at 与 yeeke_sync_run.started_at 均用 time.Now().UTC() 写入,可直接比较。
yeeke/sync.go
yeeke_return_package/item.sync_status
ok
last_synced_at
yeeke_sync_run.started_at
time.Now().UTC()
方案
missing_since
sync_status
missing
version-local
Failed == 0
Pages
MaxPages
last_synced_at < run.started_at
sync_status='ok'
missing_since=now
error_message
missing_since=NULL
syncStatus
missingSince
验收补充
ff6e876
feat/339-syb-page-size
ddd875d
1789800900000_return_missing
补充(2026-09-24,用户确认「加这两列」):yeeke 同步记录页增加「标记不可用」「恢复可用」两列。
yeeke_sync_run
missing_marked_count
recovered_count
1789801000000_yeeke_sync_run_missing_counts
missingMarkedCount
recoveredCount
5ac8e4c
018e356
用户原话:「第2点,syb商品数量和退货商品数量不一致时还要匹配上;3,已经匹配的,过了销毁截至时间,标红提醒」
b72fa10
27d9560
pnpm run build:prod
No dependencies set.
The note is not visible to the blocked user.
来源与原始需求摘要
2026-09-24,用户提出(经多轮澄清):新增退货商品模块的目的,是把 yeeke 退货商品重新利用,与 SYB 待采购订单商品匹配;匹配成功后该 SYB 订单商品不再去 PDD 等平台采购。用户原话要点:
前置依赖
d0b8609)。当前事实(本地真实数据,2026-09-24 核对)
syb_product只有shopee_item_id(虾皮商品ID)和解析得到的target_color/target_size,不保存虾皮规格ID(variationId);无法用规格ID精确匹配。yeeke_return_item有item_id(与 SYBshopee_item_id同一编号)、variation_id、variation_name(颜色与尺码逗号拼接的自由文本,如紫色,M 【建議43-53公斤】)。颜色,尺码去掉括号备注后,绝大多数与 yeekevariation_name去掉括号备注后的结果一致(如白色,2XL↔白色,2XL【建議65-75公斤】)。destroy_dead_line当前无空值(0 条)。server/app/goauto/purchase/process_stage.go)。目标
匹配规则(程序只按规格匹配)
syb_product.shopee_item_id = yeeke_return_item.item_id,且双方规格文字归一化后相等。归一化规则:target_color + "," + target_size;yeeke 侧为variation_name;【】、()、()、[]等括号及其中内容,去除空白,全角转半角,转小写;created_at倒序逐条处理,成功即标记,已被占用的退货商品不再参与本轮后续及以后的匹配。状态与处理阶段
待确认→已确认或已取消;已确认也可取消(取消后 SYB 恢复可采购)。数据设计(初步,实施前确认)
syb_product、yeeke_return_item核心表上直接加外键字段,保持模块边界。页面(须先出原型并经用户确认后才能写生产代码)
SYB 商品页
退货商品页
非目标
验收
created_at倒序处理;已占用退货不重复分配;多件同规格时优先销毁截止最早的。风险
设计证据
prototypes/338/v1/index.html;用户原话:「原型通过」)prototypes/338/v1/index.html(用户要求导出,未提交;导出器不处理画面偏移,快照内已加坐标换算,内容与线上一致)。文档影响
更新 Wiki:业务规则(退货匹配规则、处理阶段新增两项、采购拦截规则)、架构与代码图(新表、新接口)、Admin 模块说明。
待确认
无。原两条已于 2026-09-24 由用户确认:
开始实施(服务端核心已完成,Web 页面本轮未实现,见下):
return_match(server/app/goauto/models/return_match.go,已加入migrations.MigratedModels()):matched/confirmed/cancelled 状态,ActiveSYBProductID/ActiveYeekeReturnItemID两个可空唯一索引列(复用YeekeSyncRun.ActiveSlot的模式)保证同一时刻每侧只有一条有效匹配,取消匹配清空两列即可支持重新配对。server/app/goauto/returnmatch/{normalize,candidate}.go+ 单元测试:规格文字归一化(去除【】()()[] 及其内容、去空白、全角转半角、转小写,含真实样本回测)和候选选择(阶段过滤、销毁截止晚于当前时间、按 created_at 倒序处理、同规格多候选取最早截止、同批次已占用不复用),含同订单多颜色交叉配对测试。server/app/goauto/returnmatch/{service,handler,router}.go:POST /api/admin/v1/return-matches/batch-match(手动触发,仅勾选行,无定时/自动触发)、GET /api/admin/v1/return-matches(按 SYB 商品/退货商品/状态筛选,供两个页面复用)、GET .../:id、POST .../:id/{confirm,cancel,remark};写操作复用admin/purchaser角色门禁(与yeeke.Handler.TriggerSync一致)。server/app/goauto/purchase/return_match_block.go的rejectIfActiveReturnMatch挂在Service.create锁定 SYB 行之后;BatchCreate本就逐行调用Create并把创建失败视为单行跳过,因此单条与批量创建都经这一个校验点拦截,批量只跳过命中行。process_stage.go+batch.go新增有界查询activeReturnMatchBySYB),零匹配时该查询为空,其余阶段计算逻辑不变(对应验收项 11 的回归测试)。sys_opera_log(按路由自动记录管理端请求),本次新增接口按现有路由注册方式接入即自动获得该记录,未另建专用日志表。Web 页面(SYB 商品页新列/按钮/对话框、退货商品页新筛选/列)本轮未实现,超出当前会话可完成范围,留待后续提交或确认后再补。
测试:
server/app/goauto/returnmatch/...全部通过(含并发唯一约束测试);server/app/goauto/purchase/...全部通过(含新增拦截回归测试);go build ./...、go vet ./app/goauto/...、gofmt均通过;sybimport既有 12 个失败用例数量核对不变(本次未触碰该包)。范围补充(2026-09-24,用户确认)
来源:用户本地试用后询问两次匹配结果是否正常;因匹配请求未记录提交范围,无法还原当次勾选。用户原话:「把每次批量匹配提交了哪些商品、各自是什么结果也写进日志」。
return_match_batch(迁移版本1789800800000):每次点击「匹配退货」写一条,含操作人、时间、提交数/成功数/跳过数、按提交顺序的每条 SYB 商品结果(是否匹配、原因码、原因、匹配ID),批次中途失败时记录错误。GET /api/admin/v1/return-matches/batches?limit=&withItems=1、GET /api/admin/v1/return-matches/batches/:batchId。本次不做页面入口。1e944b9;测试TestBatchMatch_RecordsSubmittedProductsAndOutcomes、TestBatchMatch_EmptySubmissionNotRecorded通过,returnmatch/purchase/migrations/yeeke 回归通过。另记:本次同时补了
1789800700000_return_match迁移版本(20a8144)。原实现只把模型注册进 MigratedModels,本地迁移被框架检查拦截「迁移后仍缺少表」,未对库做任何改动;补版本后迁移成功。范围补充:yeeke 已不存在的退货同步为不可用(2026-09-24,用户确认)
来源:全栈评审发现(P1-1)。本地 7 件退货在最近一次成功同步(15:20)中已不在 yeeke「已认领」列表,但销毁截止仍在未来(最早 09-25),可用池仍会把它们匹配出去。用户原话:「要,因为是已yeeke的数据为准,如果本地有的可匹配的退货商品,但yeeke上找不到,就应该更新本地的这些退货商品的状态.」
事实:
yeeke/sync.go每次全量分页同步(第 1 页到末页),只 upsert 不处理消失的记录;yeeke_return_package/item.sync_status已存在且恒为ok;last_synced_at与yeeke_sync_run.started_at均用time.Now().UTC()写入,可直接比较。方案
missing_since(可空,package 与 item 各一),sync_status新增取值missing;新迁移版本(version-local下新文件)。Failed == 0、因自然结束退出分页(空页 / 不足一页 / 到达Pages),不含重复页指纹中断和MaxPages耗尽。满足时在一个事务里:本次未刷新(last_synced_at < run.started_at)且sync_status='ok'的 item/package 置missing+missing_since=now。ok数量的 20%,不标记,并把原因写入 run 的error_message(run 状态仍为成功),防止接口异常导致大面积误伤。sync_status='ok'、missing_since=NULL。sync_status='ok'的 item(且 package 为 ok)。syncStatus/missingSince;SYB 页匹配列与对比页标红「退货已不在 yeeke 列表」;yeeke 退货页该行显示「不可用(yeeke 列表中已不存在)」。验收补充
缺失退货标记:实施与本地真实同步验证(2026-09-24)
ff6e876(feat/338-return-match);已合并进feat/339-syb-page-size(ddd875d)。迁移版本1789800900000_return_missing:两张表各加missing_since列与sync_status索引,本地迁移仅此改动。补充(2026-09-24,用户确认「加这两列」):yeeke 同步记录页增加「标记不可用」「恢复可用」两列。
yeeke_sync_run新增missing_marked_count、recovered_count(迁移1789801000000_yeeke_sync_run_missing_counts,本地已执行,仅加两列);同步结束时写入,同步记录接口返回missingMarkedCount/recoveredCount。5ac8e4c(feat/338-return-match),已合并进 feat/339(018e356);测试补充持久化断言,yeeke/returnmatch/migrations/version-local 全部通过。评审遗留两点的用户决定(2026-09-24)
用户原话:「第2点,syb商品数量和退货商品数量不一致时还要匹配上;3,已经匹配的,过了销毁截至时间,标红提醒」
b72fa10,已合并进 feat/339(27d9560),pnpm run build:prod通过;本地页面已更新。