T47 店铺管理与 SYB 同步店铺过滤 #49

Closed
opened 2026-08-20 10:37:51 +08:00 by ila · 5 comments
Owner

背景

SYB 账号下的货运单包含该账号全部店铺的数据,而客户只需要其中一部分。本工单增加店铺管理模块,并在 SYB 同步时按启用的店铺过滤。

硬约束:过滤必须在完整性校验之后

[必须] 顺序固定为:listTotal 查总数并做容量熔断 → 翻完整页 → 校验完整性(分页行数、分页前后总数漂移、明细响应缺单)→ 再按店铺过滤 → 只为接受的货运单拉明细和入库。

不能先过滤再校验:非目标店铺的分页漂移会掩盖目标店铺的漏单。这条来自上游 cmautobuy 文档 §8,见 SYB-ERP-Interface-Contract。

由此产生的预期:过滤不减少列表请求,只减少明细请求和落库量。max_matches 仍按原始总数计算——实测最近 7 天 7404 张,这个数字不会因过滤变小。「过滤了就能选更大范围」不成立。

店铺名不手工输入,改为自动发现

匹配靠字符串比对,SYB 那边改个店铺名就会静默失配、数据无声消失——这是最糟糕的失败方式(不报错,只是少数据)。而同步过程本来就看得见每张货运单的 shopName。

  • 同步时记录遇到的所有店铺名及条数,店铺管理页面直接列出,勾选启用
  • 提供「从 SYB 发现店铺」:拉一小段日期范围,只提取 distinct shopName
  • 这同时解决鸡生蛋:首次使用时用户不知道店铺叫什么,而「没启用店铺就拒绝同步」会把人卡死

数据模型

syb_shop:

字段 说明
display_name 面向用户的店铺名
normalized_name 匹配键:去首尾空白、统一全半角、忽略大小写。唯一索引建在这上面(用户已确认)
enabled 是否参与同步
last_seen_at 最近一次在 SYB 数据中出现的时间
deleted_at 软删除

[必须] 只做停用/软删除,不做硬删除:同步记录会引用店铺名,硬删会让历史对不上。

行为约定

  • [必须] 没有启用任何店铺时拒绝同步,提示去店铺管理启用(用户已确认)。不做「静默全量导入」——那会让人误以为过滤生效了。
  • [必须] 拒绝发生在任何 SYB 请求之前,不浪费一次登录和验证码。
  • [必须] 明细响应里再校验一次 shopName:列表通过但明细里变空或变成非允许店铺时拦住。成本几乎为零,防列表/明细不一致。
  • 未知店铺不得静默丢弃,须计入同步记录的跳过统计(本工单产出字段,展示在 #50)。

权限

[必须] 店铺的增改删和启用仅管理员;其他角色只读(用户已确认)。

已导入数据的清理

用户已确认「先清空已导入的数据」。判据(见工单评论):

  • syb_product 全量删除——纯导入派生,重新导入可完整重建
  • shopee_product 仅删除 last_create_request_id IS NULL AND last_update_request_id IS NULL 的行,即导入创建且从未被人工碰过

[必须] 删数据是不可逆操作。执行前须用户明确确认具体语句和影响行数;本仓库不自动执行。

[待定] 已合并进人工创建档案的规格值无法回退,这部分不可逆。

门禁

  • Stage A 原型:店铺管理页面(列表、启用开关、发现店铺、新增/改名/停用)
  • Stage B:模型+迁移 → 服务端 API → 同步过滤接入 → Admin 页面 → 文档

[必须] 新增模型必须同时新建 version-local 迁移文件,见 Common-Changes。

## 背景 SYB 账号下的货运单包含**该账号全部店铺**的数据,而客户只需要其中一部分。本工单增加店铺管理模块,并在 SYB 同步时按启用的店铺过滤。 ## 硬约束:过滤必须在完整性校验之后 `[必须]` 顺序固定为:`listTotal` 查总数并做容量熔断 → 翻完整页 → **校验完整性**(分页行数、分页前后总数漂移、明细响应缺单)→ **再**按店铺过滤 → 只为接受的货运单拉明细和入库。 不能先过滤再校验:非目标店铺的分页漂移会掩盖目标店铺的漏单。这条来自上游 cmautobuy 文档 §8,见 [SYB-ERP-Interface-Contract](https://git.ilapage.cn/OPC/goauto/wiki/SYB-ERP-Interface-Contract)。 **由此产生的预期**:过滤**不减少列表请求**,只减少明细请求和落库量。`max_matches` 仍按原始总数计算——实测最近 7 天 7404 张,这个数字不会因过滤变小。「过滤了就能选更大范围」不成立。 ## 店铺名不手工输入,改为自动发现 匹配靠字符串比对,SYB 那边改个店铺名就会静默失配、数据无声消失——这是最糟糕的失败方式(不报错,只是少数据)。而同步过程本来就看得见每张货运单的 `shopName`。 - 同步时记录遇到的所有店铺名及条数,店铺管理页面直接列出,勾选启用 - 提供「从 SYB 发现店铺」:拉一小段日期范围,只提取 distinct `shopName` - 这同时解决鸡生蛋:首次使用时用户不知道店铺叫什么,而「没启用店铺就拒绝同步」会把人卡死 ## 数据模型 `syb_shop`: | 字段 | 说明 | |---|---| | `display_name` | 面向用户的店铺名 | | `normalized_name` | 匹配键:去首尾空白、统一全半角、**忽略大小写**。唯一索引建在这上面(用户已确认) | | `enabled` | 是否参与同步 | | `last_seen_at` | 最近一次在 SYB 数据中出现的时间 | | `deleted_at` | 软删除 | `[必须]` 只做停用/软删除,不做硬删除:同步记录会引用店铺名,硬删会让历史对不上。 ## 行为约定 - `[必须]` **没有启用任何店铺时拒绝同步**,提示去店铺管理启用(用户已确认)。不做「静默全量导入」——那会让人误以为过滤生效了。 - `[必须]` 拒绝发生在**任何 SYB 请求之前**,不浪费一次登录和验证码。 - `[必须]` 明细响应里**再校验一次** `shopName`:列表通过但明细里变空或变成非允许店铺时拦住。成本几乎为零,防列表/明细不一致。 - 未知店铺不得静默丢弃,须计入同步记录的跳过统计(本工单产出字段,展示在 #50)。 ## 权限 `[必须]` 店铺的增改删和启用**仅管理员**;其他角色只读(用户已确认)。 ## 已导入数据的清理 用户已确认「先清空已导入的数据」。判据(见工单评论): - `syb_product` 全量删除——纯导入派生,重新导入可完整重建 - `shopee_product` 仅删除 `last_create_request_id IS NULL AND last_update_request_id IS NULL` 的行,即导入创建且从未被人工碰过 `[必须]` 删数据是不可逆操作。执行前须用户明确确认具体语句和影响行数;本仓库不自动执行。 `[待定]` 已合并进**人工创建**档案的规格值无法回退,这部分不可逆。 ## 门禁 - Stage A 原型:店铺管理页面(列表、启用开关、发现店铺、新增/改名/停用) - Stage B:模型+迁移 → 服务端 API → 同步过滤接入 → Admin 页面 → 文档 `[必须]` 新增模型必须同时新建 `version-local` 迁移文件,见 [Common-Changes](https://git.ilapage.cn/OPC/goauto/wiki/Common-Changes)。
Author
Owner

Stage A 原型已生成

QuantUX 应用:GoAuto - 店铺管理原型(app_id 6a866921191a826306a7ef55)
导出校验:PASS,88 个控件,12 处跳转,0 运行时错误。

(预览链接需带 MCP key,按仓库规则不写入工单,见对话中给出的地址。)

7 个页面

页面 内容
1 店铺管理(管理员) 列表:店铺名、启用开关、最近出现、最近一次同步条数、改名/删除
2 从 SYB 发现店铺 选日期范围 → 拉列表提取店铺名 → 只有「新发现」可勾选
3 新增/改名 店铺名输入 + 启用开关,附匹配规则说明
4 删除确认 明确「已导入的数据不受影响」
5 未启用店铺时导入被拒绝 在 SYB 商品页拦截,并说明未消耗验证码
6 采购员只读视角 按钮置灰,开关变成只读标记
7 模块边界说明 标注 #49 / #50 / #48 各自负责什么

原型里表达的几个决定

  • 「从 SYB 发现」是主路径,手工输入是补充。 新增弹窗里直接提示:名字对不上不会报错,只是数据静默不进来,所以优先用发现。
  • 已在列表中的店铺不可勾选,避免重复添加;已停用的要恢复得回列表开开关,不通过发现流程。
  • 拒绝导入的提示写明「还没有发起任何 SYB 请求」,对应工单里「拒绝发生在任何 SYB 请求之前,不浪费登录和验证码」。
  • 删除确认强调已导入数据不受影响,并说明同步记录里仍会显示这个店铺名。
  • 只读视角单独出一屏,把「仅管理员可配置」落到具体控件状态上,而不是只写在文档里。
  • 列表页底部解释了为什么过滤不能让日期范围变大——这条容易被误解成「过滤了就省事了」。

待确认

  • 「最近一次同步」这一列的数据依赖 #50 的同步记录。#49 先做的话,这列在 #50 完成前会是空的。可以接受,还是先不放这列?
  • 店铺名的匹配归一化范围:目前写的是「去首尾空白 + 全半角统一」,要不要也忽略大小写?中文店铺名一般用不上,但如果有英文店铺名就有影响。
## Stage A 原型已生成 QuantUX 应用:`GoAuto - 店铺管理原型`(app_id `6a866921191a826306a7ef55`) 导出校验:`PASS`,88 个控件,12 处跳转,0 运行时错误。 (预览链接需带 MCP key,按仓库规则不写入工单,见对话中给出的地址。) ## 7 个页面 | 页面 | 内容 | |---|---| | 1 店铺管理(管理员) | 列表:店铺名、启用开关、最近出现、最近一次同步条数、改名/删除 | | 2 从 SYB 发现店铺 | 选日期范围 → 拉列表提取店铺名 → 只有「新发现」可勾选 | | 3 新增/改名 | 店铺名输入 + 启用开关,附匹配规则说明 | | 4 删除确认 | 明确「已导入的数据不受影响」 | | 5 未启用店铺时导入被拒绝 | 在 SYB 商品页拦截,并说明未消耗验证码 | | 6 采购员只读视角 | 按钮置灰,开关变成只读标记 | | 7 模块边界说明 | 标注 #49 / #50 / #48 各自负责什么 | ## 原型里表达的几个决定 - **「从 SYB 发现」是主路径,手工输入是补充。** 新增弹窗里直接提示:名字对不上不会报错,只是数据静默不进来,所以优先用发现。 - **已在列表中的店铺不可勾选**,避免重复添加;已停用的要恢复得回列表开开关,不通过发现流程。 - **拒绝导入的提示写明「还没有发起任何 SYB 请求」**,对应工单里「拒绝发生在任何 SYB 请求之前,不浪费登录和验证码」。 - **删除确认强调已导入数据不受影响**,并说明同步记录里仍会显示这个店铺名。 - **只读视角单独出一屏**,把「仅管理员可配置」落到具体控件状态上,而不是只写在文档里。 - 列表页底部解释了为什么过滤不能让日期范围变大——这条容易被误解成「过滤了就省事了」。 ## 待确认 - 「最近一次同步」这一列的数据依赖 #50 的同步记录。#49 先做的话,这列在 #50 完成前会是空的。可以接受,还是先不放这列? - 店铺名的匹配归一化范围:目前写的是「去首尾空白 + 全半角统一」,要不要也忽略大小写?中文店铺名一般用不上,但如果有英文店铺名就有影响。
Author
Owner

原型确认结果

用户对两个待确认项的答复:

  1. 「最近一次同步」列在 #50 完成前为空——接受。 #49 保留这一列,数据源由 #50 提供。
  2. 店铺名归一化忽略大小写。 归一化规则最终定为:去首尾空白 → 统一全半角 → 转小写。唯一索引建在归一化结果上。

原型已按第 2 条更新(新增/改名弹窗的提示文案),重新导出校验仍为 PASS。

[必须] 实现时归一化函数必须与这里一致,且要有测试覆盖:" ABC店 "、"abc店"、"abc店" 必须归一到同一个键,因而不能重复添加。

## 原型确认结果 用户对两个待确认项的答复: 1. **「最近一次同步」列在 #50 完成前为空——接受。** #49 保留这一列,数据源由 #50 提供。 2. **店铺名归一化忽略大小写。** 归一化规则最终定为:去首尾空白 → 统一全半角 → 转小写。唯一索引建在归一化结果上。 原型已按第 2 条更新(新增/改名弹窗的提示文案),重新导出校验仍为 `PASS`。 `[必须]` 实现时归一化函数必须与这里一致,且要有测试覆盖:`" ABC店 "`、`"abc店"`、`"abc店"` 必须归一到同一个键,因而不能重复添加。
Author
Owner

原型重建:屏幕尺寸错误已修

用户反馈页面只能看到左边。原因是应用建在了 375×667(手机默认尺寸) 上,224px 宽的侧栏之后全被裁掉。

两处参数名写错,而且都是静默失败:

  • quantux_create_app 的尺寸参数是 width / height,我没传,于是用了默认的 375×667
  • quantux_add_screen 同样是 width / height,我传的是 w / h,被直接忽略

多余参数不会报错,导出校验也照样是 PASS——因为控件确实都创建成功了,只是画布装不下。

屏幕尺寸在创建时固定,没有改尺寸的接口,所以重建了应用。

新 app_id:6a866e98191a826306a7ef78(旧的 6a866921191a826306a7ef55 已删除,链接失效)

已加防护:screen() 创建后立即回读 dump 断言宽度为 1440,尺寸没生效会直接抛错。上一次连流程时的参数名错误也是同类问题,已在 qux.call 里加了工具级错误识别。

重建后核验

项 结果
screenSize {"w": 1440, "h": 900}
7 个屏幕宽度 全部 1440
控件 x 最大值 1260(右侧内容在画布内)
导出校验 PASS,88 控件,19 处跳转,0 运行时错误
大小写归一化提示 已包含
## 原型重建:屏幕尺寸错误已修 用户反馈页面只能看到左边。原因是应用建在了 **375×667(手机默认尺寸)** 上,224px 宽的侧栏之后全被裁掉。 两处参数名写错,而且**都是静默失败**: - `quantux_create_app` 的尺寸参数是 `width` / `height`,我没传,于是用了默认的 375×667 - `quantux_add_screen` 同样是 `width` / `height`,我传的是 `w` / `h`,被直接忽略 多余参数不会报错,导出校验也照样是 `PASS`——因为控件确实都创建成功了,只是画布装不下。 屏幕尺寸在创建时固定,没有改尺寸的接口,所以重建了应用。 **新 app_id:`6a866e98191a826306a7ef78`**(旧的 `6a866921191a826306a7ef55` 已删除,链接失效) 已加防护:`screen()` 创建后立即回读 dump 断言宽度为 1440,尺寸没生效会直接抛错。上一次连流程时的参数名错误也是同类问题,已在 `qux.call` 里加了工具级错误识别。 ## 重建后核验 | 项 | 结果 | |---|---| | `screenSize` | `{"w": 1440, "h": 900}` | | 7 个屏幕宽度 | 全部 1440 | | 控件 x 最大值 | 1260(右侧内容在画布内) | | 导出校验 | `PASS`,88 控件,19 处跳转,0 运行时错误 | | 大小写归一化提示 | 已包含 |
Author
Owner

实施完成,等待用户验收

实现

  • 新增 syb_shop 模型、version-local 迁移、软删除和店铺名规范化(去首尾空白、全半角统一、忽略大小写)。
  • 新增店铺列表、新增、改名、启停、删除和“从 SYB 发现店铺”API;发现只读,不自动新增。
  • 同步保持“原始全量完整性校验 → 启用店铺过滤 → 明细二次校验”的顺序;明细店铺为空或不允许时不落库并计入跳过数。
  • 导入入口在读取凭据、连接、登录和 OCR 前检查启用店铺;Admin 导入入口也提供可读前置提示。
  • 新增 Admin 店铺管理页面;管理员可维护,其他角色只读,后端授权继续由 go-admin/Casbin 配置。
  • QuantUX App ID:6a866e98191a826306a7ef78;本地快照:prototypes/49/v1/index.html,导出与交互检查通过。

提交

  • cda37b4 模型、迁移和规范化
  • 7bd8166 店铺服务和启用名称查询
  • 1b67495 店铺 API、SYB 发现和同步过滤
  • 6c7a729 Admin 页面、导入前置门禁、测试、原型和文档

验证

  • go test ./app/goauto/sybshop ./app/goauto/sybimport:通过。
  • scripts/verify.ps1 -Component server:服务端全量测试与构建通过。
  • scripts/verify.ps1 -Component web:lint 0 错误(30 个既有警告),生产构建通过。
  • pnpm exec playwright test tests/e2e/syb-shop-module.spec.ts:3 条通过(管理员维护、采购员只读、无启用店铺阻止导入)。
  • python dev_scripts/harness.py check --strict、sync --check:通过。

文档

  • 核心 Wiki revision:f92d8e7。
  • 待验收任务归档 revision:0c7b8fb。
  • 核心 docs/ 镜像已同步并纳入 6c7a729。

未验证 / 未执行

  • 未执行真实 MySQL 迁移、真实 SYB 发现与导入、生产 Casbin 角色授权验收。
  • 未执行既有导入数据清理。该操作不可逆,必须先查询并展示影响行数、确认精确 SQL 后另行执行。
  • 无 Android/APK 改动。

工单保持打开,等待用户验收。

## 实施完成,等待用户验收 ### 实现 - 新增 `syb_shop` 模型、version-local 迁移、软删除和店铺名规范化(去首尾空白、全半角统一、忽略大小写)。 - 新增店铺列表、新增、改名、启停、删除和“从 SYB 发现店铺”API;发现只读,不自动新增。 - 同步保持“原始全量完整性校验 → 启用店铺过滤 → 明细二次校验”的顺序;明细店铺为空或不允许时不落库并计入跳过数。 - 导入入口在读取凭据、连接、登录和 OCR 前检查启用店铺;Admin 导入入口也提供可读前置提示。 - 新增 Admin 店铺管理页面;管理员可维护,其他角色只读,后端授权继续由 go-admin/Casbin 配置。 - QuantUX App ID:`6a866e98191a826306a7ef78`;本地快照:`prototypes/49/v1/index.html`,导出与交互检查通过。 ### 提交 - `cda37b4` 模型、迁移和规范化 - `7bd8166` 店铺服务和启用名称查询 - `1b67495` 店铺 API、SYB 发现和同步过滤 - `6c7a729` Admin 页面、导入前置门禁、测试、原型和文档 ### 验证 - `go test ./app/goauto/sybshop ./app/goauto/sybimport`:通过。 - `scripts/verify.ps1 -Component server`:服务端全量测试与构建通过。 - `scripts/verify.ps1 -Component web`:lint 0 错误(30 个既有警告),生产构建通过。 - `pnpm exec playwright test tests/e2e/syb-shop-module.spec.ts`:3 条通过(管理员维护、采购员只读、无启用店铺阻止导入)。 - `python dev_scripts/harness.py check --strict`、`sync --check`:通过。 ### 文档 - 核心 Wiki revision:`f92d8e7`。 - 待验收任务归档 revision:`0c7b8fb`。 - 核心 `docs/` 镜像已同步并纳入 `6c7a729`。 ### 未验证 / 未执行 - 未执行真实 MySQL 迁移、真实 SYB 发现与导入、生产 Casbin 角色授权验收。 - 未执行既有导入数据清理。该操作不可逆,必须先查询并展示影响行数、确认精确 SQL 后另行执行。 - 无 Android/APK 改动。 工单保持打开,等待用户验收。
Author
Owner

??? 2026-08-20 ???????????:Task-49-?????-SYB-??????;Wiki revision:a627417c412e44fbc6b0816f15395e131760522f??????

??? 2026-08-20 ???????????:Task-49-?????-SYB-??????;Wiki revision:a627417c412e44fbc6b0816f15395e131760522f??????
ila closed this issue 2026-08-20 16:34:14 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#49