feat(yeeke): 退货商品与 SYB 待采购订单商品匹配,匹配后拦截采购 #338

Open
opened 2026-09-24 10:36:37 +08:00 by ila · 6 comments
Owner

来源与原始需求摘要

2026-09-24,用户提出(经多轮澄清):新增退货商品模块的目的,是把 yeeke 退货商品重新利用,与 SYB 待采购订单商品匹配;匹配成功后该 SYB 订单商品不再去 PDD 等平台采购。用户原话要点:

  • "增加退货商品模块的目的是为了和syb订单商品做匹配,匹配成功后不去pdd或其他平台采购"
  • "匹配成功,不再参与采购,等待人工确认是否合理,如果不合理去掉匹配,继续参与采购"
  • "程序只按照规格匹配";"匹配退货只比 SYB 和 yeeke 两边的蝦皮商品id和规格文字,不需要先关联"
  • "新增两个处理阶段:退货待确认,已用退货"

前置依赖

  • #336(yeeke 退货只读同步,已合并 main,未部署线上)
  • #337(yeeke 退货 Admin 页面)——已满足:2026-09-24 验收通过并合并 main(d0b8609)。
  • 不可并行:与 #337 修改同一批页面文件。

当前事实(本地真实数据,2026-09-24 核对)

  • SYB 商品表 syb_product 只有 shopee_item_id(虾皮商品ID)和解析得到的 target_color/target_size,不保存虾皮规格ID(variationId);无法用规格ID精确匹配。
  • yeeke 退货商品 yeeke_return_item 有 item_id(与 SYB shopee_item_id 同一编号)、variation_id、variation_name(颜色与尺码逗号拼接的自由文本,如 紫色,M 【建議43-53公斤】)。
  • 同订单号 + 同虾皮商品ID 的真实配对 840 组对比:SYB 的 颜色,尺码 去掉括号备注后,绝大多数与 yeeke variation_name 去掉括号备注后的结果一致(如 白色,2XL ↔ 白色,2XL【建議65-75公斤】)。
  • 同一订单买同一商品多个颜色的情况真实存在(抽样 40 组中至少 4 组);仅按商品ID匹配会交叉配错,规格文字比对是必须条件。
  • SYB 订单商品数量:1 件占 95.7%(14334/14976),2 件及以上 642 条(最多 9 件)。yeeke 退货商品数量 1~4,平均约 1.03。
  • destroy_dead_line 当前无空值(0 条)。
  • SYB 商品页现有"处理阶段":待人工处理、未关联 PDD、PDD 待采集/采集中/采集失败、规格待匹配、可创建采购、已创建任务、采购成功、待人工核对(server/app/goauto/purchase/process_stage.go)。

目标

  1. SYB 商品页可勾选订单商品(整个商品,或按订单号批量筛选后勾选),点击「匹配退货」,与所有可用退货商品自动匹配。
  2. 匹配成功即标记为「退货待确认」,该 SYB 订单商品立即不能创建采购任务,对应退货商品不再参与后续匹配。
  3. 人工审查:合理 → 确认为「已用退货」;不合理 → 取消匹配,SYB 订单商品立即恢复可采购,退货商品回到可用池。
  4. SYB 商品页与退货商品页都能筛选、查看匹配状态并操作确认/取消,支持备注。

匹配规则(程序只按规格匹配)

  1. 参与匹配的 SYB 订单商品:所勾选的行中,处于"待采购"范围的处理阶段,且当前没有有效匹配。
    • 参与:未关联 PDD、PDD 待采集、PDD 采集中、PDD 采集失败、规格待匹配、可创建采购。
    • 不参与:已创建任务、采购成功、待人工核对(已在采购中或已下单);退货待确认、已用退货。
    • 已确认(用户 2026-09-24):「待人工处理」不参与(该阶段已经匹配过)。
    • 不需要先关联 PDD、不需要先采集或规格映射。
  2. 可用退货商品:没有有效匹配(非待确认、非已确认),且销毁截止时间 晚于今天(北京时间);销毁截止为空视为不可用。
  3. 配对条件:syb_product.shopee_item_id = yeeke_return_item.item_id,且双方规格文字归一化后相等。归一化规则:
    • SYB 侧为 target_color + "," + target_size;yeeke 侧为 variation_name;
    • 去除 【】、()、()、[] 等括号及其中内容,去除空白,全角转半角,转小写;
    • 实施时以本地 840 组真实配对样本回测归一化规则,并在工单回写命中率。
  4. 匹配顺序:按 SYB 订单商品 created_at 倒序逐条处理,成功即标记,已被占用的退货商品不再参与本轮后续及以后的匹配。
  5. 多件同规格退货可选时:优先使用销毁截止最早的那件(快过期先用)。已确认(用户 2026-09-24)。
  6. 数量不参与判断:一条 SYB 订单商品匹配一件退货商品记录(该记录整条被占用,不论数量);数量差异由人工处理。
  7. 取消匹配后再次点击「匹配退货」若配回同一对,允许(用户确认)。
  8. 不使用 AI;后续如需 AI 判断另行建单。

状态与处理阶段

  • 匹配记录状态:待确认 → 已确认 或 已取消;已确认 也可取消(取消后 SYB 恢复可采购)。
  • SYB 商品页新增两个处理阶段:退货待确认、已用退货;二者均不可创建采购任务。取消匹配后恢复为按原规则计算的阶段。
  • 拦截点:只在创建采购任务前拦截(单条创建、批量创建均须服务端校验,不只靠前端);不影响已创建的采购任务,不改动采购任务状态机。
  • 不影响采集流程,不影响 PDD 关联、规格映射。
  • 不回写 SYB。

数据设计(初步,实施前确认)

  • 新表:退货匹配记录(SYB 商品ID、退货商品ID、状态、匹配时的双方规格快照、备注、创建/确认/取消人与时间)。
  • 新表:匹配操作日志(匹配、确认、取消、修改备注的操作人、时间、前后状态)。
  • 数据库约束保证:同一退货商品同时最多一条有效匹配(待确认/已确认);同一 SYB 订单商品同时最多一条有效匹配。并发点击时后到者跳过。
  • 不在 syb_product、yeeke_return_item 核心表上直接加外键字段,保持模块边界。
  • 需要数据库迁移。

页面(须先出原型并经用户确认后才能写生产代码)

SYB 商品页

  • 新增「匹配退货」按钮,对勾选行生效,完成后提示:处理 N 条、匹配成功 N 条、跳过原因分布。
  • 新增筛选「退货匹配状态」:未匹配 / 待确认 / 已确认。
  • 新增列:匹配到的退货商品(缩略图、退货订单号、规格)。
  • 待确认/已确认行提供「确认」「取消匹配」与备注编辑。
  • 处理阶段显示新增的「退货待确认」「已用退货」。

退货商品页

  • 新增筛选「占用状态」:可用 / 待确认 / 已确认 / 已过期。
  • 新增列:被哪条 SYB 订单商品占用(订单号,可跳转)。
  • 提供「确认」「取消匹配」与备注编辑;两边操作同一条匹配记录,状态一致。
  • 待确认期间若已过销毁截止,标红提示,不自动解除。

非目标

  • 不处理 SYB 订单数量与退货数量不一致(人工处理)。
  • 不追踪 yeeke 二次销售、销毁事件(沿用 #336 非目标);匹配后退货在 yeeke 侧被处理掉的风险由人工巡查。
  • 不调用 yeeke 任何写接口,不回写 SYB。
  • 不撤销、不修改已创建的采购任务。
  • 不做 AI 匹配。
  • 不做按采购员的权限隔离(内部系统不设门禁,靠操作日志留痕)。
  • 不影响采集。

验收

  1. 勾选的 SYB 订单商品中,只有"待采购"范围的阶段参与匹配;采购中、已下单、已匹配的不参与。
  2. 按虾皮商品ID + 归一化规格文字配对;同订单多颜色时不交叉配错(以真实样本构造测试)。
  3. 按 SYB created_at 倒序处理;已占用退货不重复分配;多件同规格时优先销毁截止最早的。
  4. 销毁截止不晚于今天的退货不参与匹配。
  5. 匹配成功后该 SYB 商品处理阶段为「退货待确认」,单条与批量创建采购均被服务端拒绝并给出明确原因。
  6. 确认后为「已用退货」,仍不可创建采购;取消匹配后立即恢复可创建采购,退货回到可用池。
  7. 两个页面的筛选、显示、确认、取消、备注均可用且状态一致。
  8. 所有匹配、确认、取消、备注修改均有操作日志。
  9. 并发匹配同一批数据不产生重复占用(数据库约束 + 测试)。
  10. 已创建的采购任务不受影响;采集、PDD 关联、规格映射不受影响。
  11. 零匹配时行为不变(回归):从未点击「匹配退货」时,采集、采购(单条与批量创建)、处理阶段计算与筛选统计结果与改动前完全一致;匹配仅由人工勾选并点击触发,无定时任务,也不在 yeeke 同步或 SYB 导入时自动触发。采购创建新增的匹配校验在查询出错时须明确报错,不得放行已匹配商品,也不得静默改变未匹配商品的行为;两处改动(采购创建校验、处理阶段计算)须有回归测试覆盖。
  12. 服务端与 Web 测试、构建通过;用本地真实同步数据验证匹配命中率并回写工单。

风险

  • 直接影响"是否创建采购订单",属项目规则中的高风险范围;新增数据库迁移。实施前须人工确认。
  • 匹配即拦截(先拦后审):从错配到人工发现之间,该订单不会被采购。需要采购员定期巡查「退货待确认」。
  • 规格文字为自由文本,归一化规则存在误配/漏配可能,须用真实样本回测。
  • 退货在 yeeke 侧被二次销售或销毁时本系统无感知。

设计证据

  • 链接:http://192.168.0.224:8082/#/apps/6ab49326191a826306a7ffa2
  • App ID:6ab49326191a826306a7ffa2
  • 状态:已确认(确认人:用户;确认时间:2026-09-24;审核版本:线上 App 6ab49326191a826306a7ffa2 当前版本 = 本地快照 prototypes/338/v1/index.html;用户原话:「原型通过」)
  • 日期:2026-09-24
  • 覆盖范围(6 个画面):
    1. SYB 订单商品页-正常(勾选、处理阶段筛选含新增两阶段、匹配退货按钮、匹配列、确认/取消入口、备注)
    2. 批量匹配结果对话框(成功/无候选/阶段不参与跳过/并发冲突四类计数与逐行原因;含两条假设的(待确认)标注)
    3. 匹配详情对比-确认或取消(SYB 与 yeeke 退货双方规格/图片并排对比、归一化文字展示、备注、操作日志、确认/取消按钮)
    4. 采购拦截提示(单条/批量创建采购命中匹配时的服务端拒绝提示与跳转)
    5. yeeke 退货页-匹配状态筛选与匹配信息(未匹配/退货待确认/已用退货筛选、被占用 SYB 订单商品列、确认/取消入口)
    6. 状态一览:空 / 加载中 / 失败 / 禁用 / 权限边界(五种状态集中标注)
  • 修订(2026-09-24,Claude 审核后):修正与已定方案不一致的文案(假设②、并发冲突、加载态、禁用态);修复显示不全(文字字段改用 QuantUX 的 label、画面在画布上错开、样式改数值、多行文字拆行、补齐缺失列与表头对齐)。修订后按导出 HTML 实测 6 个画面无文字溢出。
  • 本地离线快照:prototypes/338/v1/index.html(用户要求导出,未提交;导出器不处理画面偏移,快照内已加坐标换算,内容与线上一致)。
  • 两条假设已由用户于 2026-09-24 确认,原型画面 2 的「(待确认)」标注已改为「已确认」。

文档影响

  • 合并 #337 遗留的 Wiki 更新(用户 2026-09-24 决定「等 #338 完成后一起更新」):本单待验收前的唯一一轮 Wiki 同步同时覆盖 #336/#337 的 yeeke 退货同步与 Admin 模块(架构/代码图、业务规则、Admin 模块清单),并在 #337 补评论记录页面与 revision。
    更新 Wiki:业务规则(退货匹配规则、处理阶段新增两项、采购拦截规则)、架构与代码图(新表、新接口)、Admin 模块说明。

待确认

无。原两条已于 2026-09-24 由用户确认:

  1. 「待人工处理」不参与匹配——用户原话:"待人工处理阶段是已经匹配了,所以它不参与匹配"。
  2. 多件同规格退货时销毁截止最早优先——用户:"是的"。
## 来源与原始需求摘要 2026-09-24,用户提出(经多轮澄清):新增退货商品模块的目的,是把 yeeke 退货商品重新利用,与 SYB 待采购订单商品匹配;匹配成功后该 SYB 订单商品不再去 PDD 等平台采购。用户原话要点: - "增加退货商品模块的目的是为了和syb订单商品做匹配,匹配成功后不去pdd或其他平台采购" - "匹配成功,不再参与采购,等待人工确认是否合理,如果不合理去掉匹配,继续参与采购" - "程序只按照规格匹配";"匹配退货只比 SYB 和 yeeke 两边的蝦皮商品id和规格文字,不需要先关联" - "新增两个处理阶段:退货待确认,已用退货" ## 前置依赖 - #336(yeeke 退货只读同步,已合并 main,未部署线上) - #337(yeeke 退货 Admin 页面)——**已满足**:2026-09-24 验收通过并合并 main(`d0b8609`)。 - 不可并行:与 #337 修改同一批页面文件。 ## 当前事实(本地真实数据,2026-09-24 核对) - SYB 商品表 `syb_product` 只有 `shopee_item_id`(虾皮商品ID)和解析得到的 `target_color`/`target_size`,**不保存虾皮规格ID(variationId)**;无法用规格ID精确匹配。 - yeeke 退货商品 `yeeke_return_item` 有 `item_id`(与 SYB `shopee_item_id` 同一编号)、`variation_id`、`variation_name`(颜色与尺码逗号拼接的自由文本,如 `紫色,M 【建議43-53公斤】`)。 - 同订单号 + 同虾皮商品ID 的真实配对 840 组对比:SYB 的 `颜色,尺码` 去掉括号备注后,绝大多数与 yeeke `variation_name` 去掉括号备注后的结果一致(如 `白色,2XL` ↔ `白色,2XL【建議65-75公斤】`)。 - 同一订单买同一商品多个颜色的情况真实存在(抽样 40 组中至少 4 组);仅按商品ID匹配会交叉配错,**规格文字比对是必须条件**。 - SYB 订单商品数量:1 件占 95.7%(14334/14976),2 件及以上 642 条(最多 9 件)。yeeke 退货商品数量 1~4,平均约 1.03。 - `destroy_dead_line` 当前无空值(0 条)。 - SYB 商品页现有"处理阶段":待人工处理、未关联 PDD、PDD 待采集/采集中/采集失败、规格待匹配、可创建采购、已创建任务、采购成功、待人工核对(`server/app/goauto/purchase/process_stage.go`)。 ## 目标 1. SYB 商品页可勾选订单商品(整个商品,或按订单号批量筛选后勾选),点击「匹配退货」,与所有可用退货商品自动匹配。 2. 匹配成功即标记为「退货待确认」,该 SYB 订单商品**立即不能创建采购任务**,对应退货商品不再参与后续匹配。 3. 人工审查:合理 → 确认为「已用退货」;不合理 → 取消匹配,SYB 订单商品立即恢复可采购,退货商品回到可用池。 4. SYB 商品页与退货商品页都能筛选、查看匹配状态并操作确认/取消,支持备注。 ## 匹配规则(程序只按规格匹配) 1. **参与匹配的 SYB 订单商品**:所勾选的行中,处于"待采购"范围的处理阶段,且当前没有有效匹配。 - 参与:未关联 PDD、PDD 待采集、PDD 采集中、PDD 采集失败、规格待匹配、可创建采购。 - 不参与:已创建任务、采购成功、待人工核对(已在采购中或已下单);退货待确认、已用退货。 - **已确认(用户 2026-09-24)**:「待人工处理」不参与(该阶段已经匹配过)。 - 不需要先关联 PDD、不需要先采集或规格映射。 2. **可用退货商品**:没有有效匹配(非待确认、非已确认),且销毁截止时间 **晚于今天**(北京时间);销毁截止为空视为不可用。 3. **配对条件**:`syb_product.shopee_item_id = yeeke_return_item.item_id`,且双方规格文字归一化后相等。归一化规则: - SYB 侧为 `target_color + "," + target_size`;yeeke 侧为 `variation_name`; - 去除 `【】`、`()`、`()`、`[]` 等括号及其中内容,去除空白,全角转半角,转小写; - 实施时以本地 840 组真实配对样本回测归一化规则,并在工单回写命中率。 4. **匹配顺序**:按 SYB 订单商品 `created_at` **倒序**逐条处理,成功即标记,已被占用的退货商品不再参与本轮后续及以后的匹配。 5. **多件同规格退货可选时**:优先使用**销毁截止最早**的那件(快过期先用)。**已确认(用户 2026-09-24)**。 6. **数量不参与判断**:一条 SYB 订单商品匹配一件退货商品记录(该记录整条被占用,不论数量);数量差异由人工处理。 7. 取消匹配后再次点击「匹配退货」若配回同一对,允许(用户确认)。 8. 不使用 AI;后续如需 AI 判断另行建单。 ## 状态与处理阶段 - 匹配记录状态:`待确认` → `已确认` 或 `已取消`;`已确认` 也可取消(取消后 SYB 恢复可采购)。 - SYB 商品页新增两个处理阶段:**退货待确认**、**已用退货**;二者均**不可创建采购任务**。取消匹配后恢复为按原规则计算的阶段。 - **拦截点**:只在创建采购任务前拦截(单条创建、批量创建均须服务端校验,不只靠前端);**不影响已创建的采购任务**,不改动采购任务状态机。 - 不影响采集流程,不影响 PDD 关联、规格映射。 - 不回写 SYB。 ## 数据设计(初步,实施前确认) - 新表:退货匹配记录(SYB 商品ID、退货商品ID、状态、匹配时的双方规格快照、备注、创建/确认/取消人与时间)。 - 新表:匹配操作日志(匹配、确认、取消、修改备注的操作人、时间、前后状态)。 - 数据库约束保证:同一退货商品同时最多一条有效匹配(待确认/已确认);同一 SYB 订单商品同时最多一条有效匹配。并发点击时后到者跳过。 - 不在 `syb_product`、`yeeke_return_item` 核心表上直接加外键字段,保持模块边界。 - 需要数据库迁移。 ## 页面(须先出原型并经用户确认后才能写生产代码) **SYB 商品页** - 新增「匹配退货」按钮,对勾选行生效,完成后提示:处理 N 条、匹配成功 N 条、跳过原因分布。 - 新增筛选「退货匹配状态」:未匹配 / 待确认 / 已确认。 - 新增列:匹配到的退货商品(缩略图、退货订单号、规格)。 - 待确认/已确认行提供「确认」「取消匹配」与备注编辑。 - 处理阶段显示新增的「退货待确认」「已用退货」。 **退货商品页** - 新增筛选「占用状态」:可用 / 待确认 / 已确认 / 已过期。 - 新增列:被哪条 SYB 订单商品占用(订单号,可跳转)。 - 提供「确认」「取消匹配」与备注编辑;两边操作同一条匹配记录,状态一致。 - 待确认期间若已过销毁截止,标红提示,不自动解除。 ## 非目标 - 不处理 SYB 订单数量与退货数量不一致(人工处理)。 - 不追踪 yeeke 二次销售、销毁事件(沿用 #336 非目标);匹配后退货在 yeeke 侧被处理掉的风险由人工巡查。 - 不调用 yeeke 任何写接口,不回写 SYB。 - 不撤销、不修改已创建的采购任务。 - 不做 AI 匹配。 - 不做按采购员的权限隔离(内部系统不设门禁,靠操作日志留痕)。 - 不影响采集。 ## 验收 1. 勾选的 SYB 订单商品中,只有"待采购"范围的阶段参与匹配;采购中、已下单、已匹配的不参与。 2. 按虾皮商品ID + 归一化规格文字配对;同订单多颜色时不交叉配错(以真实样本构造测试)。 3. 按 SYB `created_at` 倒序处理;已占用退货不重复分配;多件同规格时优先销毁截止最早的。 4. 销毁截止不晚于今天的退货不参与匹配。 5. 匹配成功后该 SYB 商品处理阶段为「退货待确认」,单条与批量创建采购均被服务端拒绝并给出明确原因。 6. 确认后为「已用退货」,仍不可创建采购;取消匹配后立即恢复可创建采购,退货回到可用池。 7. 两个页面的筛选、显示、确认、取消、备注均可用且状态一致。 8. 所有匹配、确认、取消、备注修改均有操作日志。 9. 并发匹配同一批数据不产生重复占用(数据库约束 + 测试)。 10. 已创建的采购任务不受影响;采集、PDD 关联、规格映射不受影响。 11. **零匹配时行为不变(回归)**:从未点击「匹配退货」时,采集、采购(单条与批量创建)、处理阶段计算与筛选统计结果与改动前完全一致;匹配仅由人工勾选并点击触发,无定时任务,也不在 yeeke 同步或 SYB 导入时自动触发。采购创建新增的匹配校验在查询出错时须明确报错,不得放行已匹配商品,也不得静默改变未匹配商品的行为;两处改动(采购创建校验、处理阶段计算)须有回归测试覆盖。 12. 服务端与 Web 测试、构建通过;用本地真实同步数据验证匹配命中率并回写工单。 ## 风险 - 直接影响"是否创建采购订单",属项目规则中的高风险范围;新增数据库迁移。实施前须人工确认。 - 匹配即拦截(先拦后审):从错配到人工发现之间,该订单不会被采购。需要采购员定期巡查「退货待确认」。 - 规格文字为自由文本,归一化规则存在误配/漏配可能,须用真实样本回测。 - 退货在 yeeke 侧被二次销售或销毁时本系统无感知。 ## 设计证据 - 链接:http://192.168.0.224:8082/#/apps/6ab49326191a826306a7ffa2 - App ID:6ab49326191a826306a7ffa2 - 状态:**已确认**(确认人:用户;确认时间:2026-09-24;审核版本:线上 App 6ab49326191a826306a7ffa2 当前版本 = 本地快照 `prototypes/338/v1/index.html`;用户原话:「原型通过」) - 日期:2026-09-24 - 覆盖范围(6 个画面): 1. SYB 订单商品页-正常(勾选、处理阶段筛选含新增两阶段、匹配退货按钮、匹配列、确认/取消入口、备注) 2. 批量匹配结果对话框(成功/无候选/阶段不参与跳过/并发冲突四类计数与逐行原因;含两条假设的(待确认)标注) 3. 匹配详情对比-确认或取消(SYB 与 yeeke 退货双方规格/图片并排对比、归一化文字展示、备注、操作日志、确认/取消按钮) 4. 采购拦截提示(单条/批量创建采购命中匹配时的服务端拒绝提示与跳转) 5. yeeke 退货页-匹配状态筛选与匹配信息(未匹配/退货待确认/已用退货筛选、被占用 SYB 订单商品列、确认/取消入口) 6. 状态一览:空 / 加载中 / 失败 / 禁用 / 权限边界(五种状态集中标注) - 修订(2026-09-24,Claude 审核后):修正与已定方案不一致的文案(假设②、并发冲突、加载态、禁用态);修复显示不全(文字字段改用 QuantUX 的 label、画面在画布上错开、样式改数值、多行文字拆行、补齐缺失列与表头对齐)。修订后按导出 HTML 实测 6 个画面无文字溢出。 - 本地离线快照:`prototypes/338/v1/index.html`(用户要求导出,未提交;导出器不处理画面偏移,快照内已加坐标换算,内容与线上一致)。 - 两条假设已由用户于 2026-09-24 确认,原型画面 2 的「(待确认)」标注已改为「已确认」。 ## 文档影响 - **合并 #337 遗留的 Wiki 更新**(用户 2026-09-24 决定「等 #338 完成后一起更新」):本单待验收前的唯一一轮 Wiki 同步同时覆盖 #336/#337 的 yeeke 退货同步与 Admin 模块(架构/代码图、业务规则、Admin 模块清单),并在 #337 补评论记录页面与 revision。 更新 Wiki:业务规则(退货匹配规则、处理阶段新增两项、采购拦截规则)、架构与代码图(新表、新接口)、Admin 模块说明。 ## 待确认 无。原两条已于 2026-09-24 由用户确认: 1. 「待人工处理」不参与匹配——用户原话:"待人工处理阶段是已经匹配了,所以它不参与匹配"。 2. 多件同规格退货时销毁截止最早优先——用户:"是的"。
Author
Owner

开始实施(服务端核心已完成,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 倒序处理、同规格多候选取最早截止、同批次已占用不复用),含同订单多颜色交叉配对测试。
  • DB 服务层与接口 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 的回归测试)。
  • 操作日志:本仓库现有的操作日志机制是 go-admin 框架级 sys_opera_log(按路由自动记录管理端请求),本次新增接口按现有路由注册方式接入即自动获得该记录,未另建专用日志表。

Web 页面(SYB 商品页新列/按钮/对话框、退货商品页新筛选/列)本轮未实现,超出当前会话可完成范围,留待后续提交或确认后再补。

测试:server/app/goauto/returnmatch/... 全部通过(含并发唯一约束测试);server/app/goauto/purchase/... 全部通过(含新增拦截回归测试);go build ./...、go vet ./app/goauto/...、gofmt 均通过;sybimport 既有 12 个失败用例数量核对不变(本次未触碰该包)。

开始实施(服务端核心已完成,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 倒序处理、同规格多候选取最早截止、同批次已占用不复用),含同订单多颜色交叉配对测试。 - DB 服务层与接口 `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 的回归测试)。 - 操作日志:本仓库现有的操作日志机制是 go-admin 框架级 `sys_opera_log`(按路由自动记录管理端请求),本次新增接口按现有路由注册方式接入即自动获得该记录,未另建专用日志表。 Web 页面(SYB 商品页新列/按钮/对话框、退货商品页新筛选/列)本轮未实现,超出当前会话可完成范围,留待后续提交或确认后再补。 测试:`server/app/goauto/returnmatch/...` 全部通过(含并发唯一约束测试);`server/app/goauto/purchase/...` 全部通过(含新增拦截回归测试);`go build ./...`、`go vet ./app/goauto/...`、`gofmt` 均通过;`sybimport` 既有 12 个失败用例数量核对不变(本次未触碰该包)。
Author
Owner

范围补充(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 回归通过。
  • 本地库已迁移(新执行 1 个版本,仅新建该表),本地 API 已重启。

另记:本次同时补了 1789800700000_return_match 迁移版本(20a8144)。原实现只把模型注册进 MigratedModels,本地迁移被框架检查拦截「迁移后仍缺少表」,未对库做任何改动;补版本后迁移成功。

## 范围补充(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 回归通过。 - 本地库已迁移(新执行 1 个版本,仅新建该表),本地 API 已重启。 另记:本次同时补了 `1789800700000_return_match` 迁移版本(`20a8144`)。原实现只把模型注册进 MigratedModels,本地迁移被框架检查拦截「迁移后仍缺少表」,未对库做任何改动;补版本后迁移成功。
Author
Owner

范围补充: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() 写入,可直接比较。

方案

  1. 新增列 missing_since(可空,package 与 item 各一),sync_status 新增取值 missing;新迁移版本(version-local 下新文件)。
  2. 仅在完整同步后标记:run 成功、Failed == 0、因自然结束退出分页(空页 / 不足一页 / 到达 Pages),不含重复页指纹中断和 MaxPages 耗尽。满足时在一个事务里:本次未刷新(last_synced_at < run.started_at)且 sync_status='ok' 的 item/package 置 missing + missing_since=now。
  3. 安全闸:若本次将标记数量 > 当前 ok 数量的 20%,不标记,并把原因写入 run 的 error_message(run 状态仍为成功),防止接口异常导致大面积误伤。
  4. 同步中再次出现的记录,upsert 时恢复 sync_status='ok'、missing_since=NULL。
  5. 匹配可用池只取 sync_status='ok' 的 item(且 package 为 ok)。
  6. 已存在的有效匹配不自动取消:匹配列表/详情返回退货的 syncStatus/missingSince;SYB 页匹配列与对比页标红「退货已不在 yeeke 列表」;yeeke 退货页该行显示「不可用(yeeke 列表中已不存在)」。
  7. 不删除任何数据;不标记为「已销毁」(无法确知原因)。

验收补充

  • 完整同步后缺失记录被标记;失败/中断/重复页中断/MaxPages 耗尽不标记;超过 20% 触发安全闸不标记。
  • 缺失退货不参与匹配;重新出现自动恢复可用。
  • 已匹配的缺失退货保持匹配并在两处页面标红。
  • 迁移只新增列;本地迁移后真实同步一次,核对被标记的记录与上述 7 件一致(或按当时数据说明差异)。
## 范围补充: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()` 写入,可直接比较。 **方案** 1. 新增列 `missing_since`(可空,package 与 item 各一),`sync_status` 新增取值 `missing`;新迁移版本(`version-local` 下新文件)。 2. 仅在**完整同步**后标记:run 成功、`Failed == 0`、因自然结束退出分页(空页 / 不足一页 / 到达 `Pages`),**不含**重复页指纹中断和 `MaxPages` 耗尽。满足时在一个事务里:本次未刷新(`last_synced_at < run.started_at`)且 `sync_status='ok'` 的 item/package 置 `missing` + `missing_since=now`。 3. **安全闸**:若本次将标记数量 > 当前 `ok` 数量的 20%,不标记,并把原因写入 run 的 `error_message`(run 状态仍为成功),防止接口异常导致大面积误伤。 4. 同步中再次出现的记录,upsert 时恢复 `sync_status='ok'`、`missing_since=NULL`。 5. 匹配可用池只取 `sync_status='ok'` 的 item(且 package 为 ok)。 6. 已存在的有效匹配**不自动取消**:匹配列表/详情返回退货的 `syncStatus`/`missingSince`;SYB 页匹配列与对比页标红「退货已不在 yeeke 列表」;yeeke 退货页该行显示「不可用(yeeke 列表中已不存在)」。 7. 不删除任何数据;不标记为「已销毁」(无法确知原因)。 **验收补充** - 完整同步后缺失记录被标记;失败/中断/重复页中断/MaxPages 耗尽不标记;超过 20% 触发安全闸不标记。 - 缺失退货不参与匹配;重新出现自动恢复可用。 - 已匹配的缺失退货保持匹配并在两处页面标红。 - 迁移只新增列;本地迁移后真实同步一次,核对被标记的记录与上述 7 件一致(或按当时数据说明差异)。
Author
Owner

缺失退货标记:实施与本地真实同步验证(2026-09-24)

  • 提交 ff6e876(feat/338-return-match);已合并进 feat/339-syb-page-size(ddd875d)。迁移版本 1789800900000_return_missing:两张表各加 missing_since 列与 sync_status 索引,本地迁移仅此改动。
  • 测试:新增 yeeke/sync_missing_test.go(7 例:完整同步标记、失败页/取消/重复页/MaxPages 不标记、20% 安全闸、重新出现恢复)、returnmatch/missing_test.go(4 例);yeeke、returnmatch、purchase、migrations、version-local 全部通过。
  • 本地真实同步(run #29,16:09 手动触发,成功):同步前 7 件退货(502、636、736、1705、1907、2197、2198)已不在 yeeke 列表但仍为 ok;同步后恰好这 7 件标记为 missing(对应包裹 6 个,2197/2198 同包裹),其余 2251 件仍为 ok,不在列表却仍为 ok 的记录数为 0;未触发安全闸。
## 缺失退货标记:实施与本地真实同步验证(2026-09-24) - 提交 `ff6e876`(feat/338-return-match);已合并进 `feat/339-syb-page-size`(`ddd875d`)。迁移版本 `1789800900000_return_missing`:两张表各加 `missing_since` 列与 `sync_status` 索引,本地迁移仅此改动。 - 测试:新增 yeeke/sync_missing_test.go(7 例:完整同步标记、失败页/取消/重复页/MaxPages 不标记、20% 安全闸、重新出现恢复)、returnmatch/missing_test.go(4 例);yeeke、returnmatch、purchase、migrations、version-local 全部通过。 - **本地真实同步**(run #29,16:09 手动触发,成功):同步前 7 件退货(502、636、736、1705、1907、2197、2198)已不在 yeeke 列表但仍为 ok;同步后恰好这 7 件标记为 missing(对应包裹 6 个,2197/2198 同包裹),其余 2251 件仍为 ok,不在列表却仍为 ok 的记录数为 0;未触发安全闸。
Author
Owner

补充(2026-09-24,用户确认「加这两列」):yeeke 同步记录页增加「标记不可用」「恢复可用」两列。

  • yeeke_sync_run 新增 missing_marked_count、recovered_count(迁移 1789801000000_yeeke_sync_run_missing_counts,本地已执行,仅加两列);同步结束时写入,同步记录接口返回 missingMarkedCount/recoveredCount。
  • 「恢复可用」按本次同步中由 missing 变回 ok 的退货商品数统计;触发 20% 安全闸时「标记不可用」为 0,原因仍在错误信息列。
  • 提交 5ac8e4c(feat/338-return-match),已合并进 feat/339(018e356);测试补充持久化断言,yeeke/returnmatch/migrations/version-local 全部通过。
  • 注:run #29(标记了 7 件)早于该列上线,页面显示为 0;此后的同步正常记录。
补充(2026-09-24,用户确认「加这两列」):yeeke 同步记录页增加「标记不可用」「恢复可用」两列。 - `yeeke_sync_run` 新增 `missing_marked_count`、`recovered_count`(迁移 `1789801000000_yeeke_sync_run_missing_counts`,本地已执行,仅加两列);同步结束时写入,同步记录接口返回 `missingMarkedCount`/`recoveredCount`。 - 「恢复可用」按本次同步中由 missing 变回 ok 的退货商品数统计;触发 20% 安全闸时「标记不可用」为 0,原因仍在错误信息列。 - 提交 `5ac8e4c`(feat/338-return-match),已合并进 feat/339(`018e356`);测试补充持久化断言,yeeke/returnmatch/migrations/version-local 全部通过。 - 注:run #29(标记了 7 件)早于该列上线,页面显示为 0;此后的同步正常记录。
Author
Owner

评审遗留两点的用户决定(2026-09-24)

用户原话:「第2点,syb商品数量和退货商品数量不一致时还要匹配上;3,已经匹配的,过了销毁截至时间,标红提醒」

  1. 数量不一致仍然匹配(评审 P1-2):维持现行为,匹配不看数量,无代码改动。
  2. 已匹配的退货过了销毁截止只标红提醒(评审 P2-3):不自动取消匹配,仍拦截采购。按退货包裹当前销毁截止判断(同步改期后提醒随之变化):SYB 商品页匹配列「退货已过销毁截止」、对比页「退货已过销毁截止,请核实退货是否仍在库」、yeeke 退货页被占用列「已过销毁截止」。提交 b72fa10,已合并进 feat/339(27d9560),pnpm run build:prod 通过;本地页面已更新。
## 评审遗留两点的用户决定(2026-09-24) 用户原话:「第2点,syb商品数量和退货商品数量不一致时还要匹配上;3,已经匹配的,过了销毁截至时间,标红提醒」 1. **数量不一致仍然匹配**(评审 P1-2):维持现行为,匹配不看数量,无代码改动。 2. **已匹配的退货过了销毁截止只标红提醒**(评审 P2-3):不自动取消匹配,仍拦截采购。按退货包裹**当前**销毁截止判断(同步改期后提醒随之变化):SYB 商品页匹配列「退货已过销毁截止」、对比页「退货已过销毁截止,请核实退货是否仍在库」、yeeke 退货页被占用列「已过销毁截止」。提交 `b72fa10`,已合并进 feat/339(`27d9560`),`pnpm run build:prod` 通过;本地页面已更新。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#338