Files
cmautobuy/docs/admin/01-requirements.md
T

471 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 01 Admin 产品需求基线
- 文档状态:基线草案,待需求评审
- 适用范围:`admin/`
- 产品类型:本地运行的 Web 管理端(Go + Gin + HTML 模板)
本文档中 `[必须]` / `[建议]` / `[待定]` 的含义见 [文档索引](../README.md#文档标注说明)。
没有标注的默认是 `[必须]`。看不懂的词查 [术语表](00-glossary.md)。
## 1. 背景与目标
我们在**蝦皮**(台湾)卖货,在**拼多多**(大陆)进货,中间由**顺运宝**提供货运单。
Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行的采购任务。
产品目标:
1. 导入并维护蝦皮商品档案,为每个商品登记对应的拼多多链接。
2. 同步顺运宝货运单,知道"这单该买什么"。
3. 把蝦皮的颜色尺码,对应到拼多多的颜色尺码,**且这个对应关系可复用**。
4. 生成采购任务并分配给指定客户端,跟踪执行结果。
5. 管理客户端清单,知道谁在干活。
6. 通过登录保护 Admin 网页,并由管理员维护采购员账号。
## 2. 用户与外部系统
- **管理员:**首次初始化 Admin,执行全部业务操作,并创建、禁用或重置采购员账号。
- **采购员:**登录后执行现有业务操作,但不能管理用户。
- **Client:**领取任务、在安卓设备上执行、提交结果。契约见 [Client 侧文档](../client/04-admin-api-contract.md)。
- **蝦皮:**只提供 Excel 报表导出,**没有接口对接**。
- **顺运宝:**提供货运单,`[待定]` 同步方式待确认,MVP 先做占位按钮。
- **拼多多:**由 Client 操作,Admin 不直接接触。
## 3. 完整业务链路
这条链路是理解全部五个模块的关键,先看懂它:
```text
蝦皮报表 ──导入──→ 商品表 + SKU 表
│
编辑弹窗:人工填 PDD 链接
│
↓
PDD 商品页 ──────→ PDD 商品表
│ 点【创建采集任务】
↓
创建采集任务(不指定 Client)→ 谁领到算谁的
↓
Client 采回 PDD 的颜色尺码和价格
↓
存入 PDD 商品表.skus_json
│
顺运宝货运单 ─同步─→ 货运单表
│ 双击行
↓
匹配弹窗:蝦皮规格 ←→ PDD 规格
(匹配过的自动带出,只有新规格要手工做)
↓
创建采购任务 → 分配 Client → 被领取
↓
Client 下单 → 提交结果 → 人工付款
```
两个要点:
- **采集是商品级的**,一个 PDD 商品采一次,所有相关订单共用结果。
- **匹配是 SKU 级的且可复用**,同一个蝦皮 SKU 只需人工匹配一次。
## 4. 产品范围
顶级导航固定五个模块,顺序不变:
| # | 模块 | 职责 |
|---|---|---|
| 1 | 蝦皮数据 | 商品档案、PDD 链接、发起采集 |
| 2 | PDD 商品 | PDD 商品档案、发起采集、查看采回来的规格价格 |
| 3 | 顺运宝数据 | 货运单、规格匹配、生成采购任务 |
| 4 | 采集采购 | 采集任务和采购任务的执行进度跟踪 |
| 5 | 客户端列表 | 客户端注册与状态 |
五个模块**统一使用三段式页面布局**:顶部工具条 / 中间带勾选的表格 / 底部状态条。
详见 [05 界面规范](05-ui-specification.md)。
PDD 商品之所以单独一个模块,是因为它在数据上就是**独立实体**
(自己的表、自己的采集状态,可以被多个蝦皮商品共用),
见 [03 数据模型 §4](03-data-model.md)。挂在蝦皮模块下面的话,
同一个 PDD 商品被两个蝦皮商品引用时就说不清该显示在谁名下。
### 4.1 蝦皮数据模块
**顶部工具条:** 导入按钮、状态筛选(全部 / 待补规格 / 未填 PDD 链接 / 已填链接)、
商品 ID 搜索框、搜索按钮、删除按钮、批量采集按钮。
`[必须]` 状态筛选是**刚需**,不是锦上添花(工单 #43):本页的主要用途是维护
(找出需要补规格的商品、找出还没关联 PDD 链接的商品),只靠商品 ID 搜索的话,
操作员得先知道 ID 才能搜——而"哪些商品需要处理"恰恰是不知道 ID 的时候才要问的。
实测样本 5195 个商品里只有 6 个待补规格,只做分页要翻 260 页才能找全,等于找不到。
界面细节和筛选条件的 SQL 见 [05 界面规范](05-ui-specification.md) §4.1。
**中间表格**(按 SKU 展开显示,数据来自商品表和 SKU 表联查):
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量采集 |
| 商品 ID | 蝦皮商品编号 |
| 商品名称 | |
| 颜色 / 尺码 / 建议 | 从规格原文解析,解析不出来留空并标记 |
| PDD 链接 | 人工填写,空的要显眼 |
| 采集状态 | 见 §6 |
| 更新时间 | |
**双击行打开编辑弹窗**,这是**从蝦皮商品出发**填 PDD 链接、发起采集的入口:
- 可编辑:PDD 链接、颜色、尺码、建议;
- 弹窗内提供【采集】按钮,紧挨 PDD 链接输入框;
- `[必须]` 采集按钮**在本页只出现在弹窗里**,不要放在每个表格行——
一个商品有 N 个 SKU 行,放行上就是 N 个按钮干同一件事,还会建出 N 个重复任务。
`[注意]` PDD 商品模块(§4.2)也能录入链接和发起采集,两处并存。
本页的入口目前**还是骨架**(点了返回 501),
两者要不要合并、以哪个为准,属于「蝦皮↔PDD 关联入口」的范围,
由那张工单统一决定,不要在本模块单独改。
**其他要求:**
- `[必须]` 支持**手动新增**一条 SKU。报表只含有销售成绩的 SKU(样本里平均每商品仅 1.17 个),
订单来了查无此 SKU 是常态,必须能补。
- `[必须]` 导入是 **upsert**,绝不允许先清空再导入,否则人工填的 PDD 链接会被洗掉。
- `[必须]` 规格原文永远保留,解析失败留空,不要猜。
**底部状态条:** 最近一次导入的时间、条数、失败行数。
### 4.2 PDD 商品模块
维护拼多多商品档案,并发起采集。这条链路**不依赖蝦皮和顺运宝的任何数据**,
可以单独跑通:建商品 → 建采集任务 → Client 领走执行 → 提交结果 → 页面显示已采集。
**顶部工具条:** 创建按钮、创建采集任务按钮、采集状态筛选、商品 ID/链接搜索框、搜索按钮、删除按钮。
**中间表格:**
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量建采集任务 |
| 商品 ID | 从链接解析出来的 `goods_id` |
| 标题 | 采集回来的,未采集时显示占位文案 |
| PDD 链接 | 截断显示,可点开 |
| 采集状态 | 见 §6.1,用中文文字,不能只靠颜色 |
| 规格数 | 从 `skus_json` 算 |
| 采集时间 | 未采集显示 `—` |
| 更新时间 | |
`[必须]` **规格数必须显示。** 采到 1 个和采到 20 个差别很大——
只采到 1 个通常意味着客户端没点开规格面板,是采集有问题。
不显示这一列的话,要点进每个商品才能发现。
**创建:** `[必须]` 只填 PDD 链接,其余字段全靠采集回填。
`[必须]` **链接必须能解析出 `goods_id`,解析不出来直接报错并且不写库**。
不接受短链接——`goods_id` 上有 UNIQUE 约束,防重全靠它,
拿不到就没法查重,同一个商品会存成好几行。
**双击行打开弹窗:** 查看商品信息,可编辑链接,并显示采回来的规格和价格。
- `[必须]` **规格和价格必须显示**,这是采集结果的全部价值所在——
不显示的话操作员没法确认"采得对不对、是不是我要的那个商品"。
- `[必须]` 维度顺序按 `skus_json` 里的 `dimensions`(Go 的 map 无序,必须靠它定顺序)。
- `[必须]` 价格显示成 `¥12.56`,底层存整数分;`price_cent` 为 `null` 时显示"未采到",
**不得显示成 ¥0.00**。
- `[必须]` 编辑链接时新链接必须还是**同一个商品**。换成别的商品要新建一条:
这一行上挂着采集结果和 SKU 映射,`goods_id` 一换那些数据就全指到错的商品上了。
**创建采集任务:**
- `[必须]` **不指定客户端**,谁领到就在领取时标记谁。采集是纯读取操作,
哪台机器跑都一样,指定了反而会在那台机器关着的时候干等。
- `[必须]` 按 `goods_id` 去重;`collecting` 状态的跳过,并在结果里说明跳过了几个。
- `[必须]` 按钮文案用「**创建采集任务**」,不要用「采集」。它做的是建一个任务,
不是立刻去采——真正的采集要等 Client 来领、去手机上跑,可能几秒也可能几分钟。
**删除:** 软删除。二次确认要写明"重新创建同一链接可恢复,**但采集结果会清空**"。
不硬删是因为 SKU 映射指向它,硬删会把人工攒了很久的匹配成果一起带走。
**底部状态条:** 各采集状态的条数统计。
### 4.3 顺运宝数据模块
**顶部工具条:** 同步按钮、订单号搜索框、搜索按钮、创建采购任务按钮、删除按钮。
- `[待定]` 同步按钮 MVP 阶段只做占位:点击后提示"同步功能待接入",不发请求。
**中间表格:**
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量建任务 |
| 货运单 ID | |
| 订单号 | |
| 商品标题 | |
| 蝦皮商品 ID | 关联蝦皮数据模块 |
| 规格 SKU | 蝦皮的规格编号 |
| 数量 | |
| 价格 | 蝦皮售价,**台币分**,见 §7 |
| 图片 | 存 URL,表格里显示缩略图 |
| 匹配状态 | 已匹配 / 待匹配 |
| 更新时间 | |
原始的完整货运单 JSON 存 `syb_data` 字段,**不作为表格列显示**,在详情里看。
**双击行打开匹配弹窗:**
- 左边显示蝦皮的颜色尺码,右边显示该商品 `pdd_data` 里的 PDD 颜色尺码;
- 操作员选好对应关系,点保存;
- `[必须]` 保存的是**可复用的 SKU 映射**,不是这一张订单的临时数据。
下次遇到同一个蝦皮 SKU 自动带出,操作员只需确认。
- `[必须]` 该商品尚未采集(`pdd_data` 为空)时,弹窗要明确提示"请先到蝦皮数据模块采集",
而不是显示一个空列表让人困惑。
**创建采购任务:** 勾选若干行 → 点按钮 → 校验通过后生成任务。校验规则见 §5。
**底部状态条:** 最近同步时间、待匹配条数。
### 4.4 采集采购模块
`tasks` 是**一张表**,用 `task_type` 区分采集和采购。这个模块把两种任务
放在同一个列表里显示——分开成两个页面的话,采集任务建出来了,
操作员却只能盯着 PDD 商品的 `collect_status` 猜任务本身怎么样了
(被谁领了、领了多久、失败在哪一步),完全看不出任务这一层的信息。
`[必须]` 名字是「**采集采购**」,不是「任务」。它直接点出这一页装的是
哪两类任务,比泛称「任务」更能让操作员一眼知道点进去看什么。
`[必须]` 路由 `/tasks` 不变,避免已有的书签和文档链接失效。
**顶部工具条:**
```text
[类型▾ 全部] [状态▾ 全部] 关键词[____________] [搜索] [删除]
```
- **类型**:全部 / 采集 / 采购。
- **状态**:全部 + 7 个状态,见 §6.2。
- **关键词**:同时匹配任务编号、订单号、PDD 商品 ID。
`[必须]` 筛选比搜索更常用:这个页面最常被问的问题是"有没有卡住的任务",
不是"订单 SO-001 怎么样了",所以类型和状态筛选要放在最前面。
**中间表格:**
两种任务的业务字段完全不同——采购有订单号/颜色尺码/数量/价格上限,
采集只有 PDD 链接。并列显示的话采集任务行会有一半是空列,
所以改成固定列 + 一列「目标」概括业务信息:
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除 |
| 任务编号 | `task_id` |
| 类型 | 采集 / 采购,文字,不能只靠颜色 |
| **目标** | 采集:`PDD <pdd_goods_id>`(能 join 到未删除的商品标题时追加显示);采购:`<order_no> · <颜色/尺码> · <数量>件 · ≤<价格上限>` |
| 状态 | 中文,7 个取值见 §6.2 |
| 客户端 | `assigned_client`;**无主任务显示 `—`**(#17 之后采集任务默认无主,这一列会大量为空) |
| 更新时间 | 本地时区 |
`[必须]` 目标列所需字段全在 `tasks` 表上(`pdd_goods_id` / `order_no` /
`pdd_options` / `quantity` / `max_price_cent`),不需要 join。
`[建议]` 采集任务的目标 join `pdd_products` 取标题显示更友好,
但 join 不到或商品已软删除时必须退回只显示 `pdd_goods_id`,不能空着。
`[必须]` 目标列的拼接逻辑在 service 层组装成一个字符串,模板只负责显示——
散在模板里没人维护得住。
`[必须]` 价格上限显示成 `¥42.00`,底层是整数分。
**双击行打开详情弹窗:**
`[必须]` **只读**。改派 / 重试 / 取消是后续工单的范围,本页不提供入口。
弹窗显示:类型、状态、分配客户端、领取时间、完成时间;执行参数
(PDD 链接,采购任务额外显示目标规格、数量、价格上限);错误信息
(错误码、错误说明,没有错误时不显示这一段)。
`[必须]` 采购专有字段(数量、价格上限、目标规格)在采集任务的弹窗里
**整段隐藏**,不显示空行。
`[建议]` `result_data`(Client 提交的完整结果)默认折叠,提供展开查看;
过长时截断显示。
**底部状态条:**
```text
共 42 条 · 待分配 3 · 待领取 5 · 已领取 2 · 成功 30 · 需人工 1 · 失败 1 · 已取消 0
```
`[必须]` 统计要**跟随当前筛选**。筛了「采集」就只统计采集任务,
否则数字和表格对不上,操作员会以为页面出错。
### 4.5 客户端列表模块
**顶部工具条:** 客户端名称搜索框、搜索按钮;管理员另有删除按钮。
**中间表格:** 名称、序列号、状态、当前负责人、最近活动时间、更新时间;
管理员另有勾选和操作列。
**注册方式:**
- `[必须]` 设置页通过独立登记接口幂等新增或更新 Client;该接口不得领取或修改任务。
- `[必须]` 领取接口保留隐式登记作为旧 Client 的兼容兜底。
- `[必须]` 不设心跳接口,在线状态由登记、领取和提交产生的最近活动时间派生。
- `[必须]` `状态` 是**派生字段,不存库**:
最近活动时间在 N 分钟内算"在线",否则"离线"。`[建议]` N 默认 10 分钟。
- 名称由客户端上报,操作员可以在 Admin 这边改成好记的名字。
**采购员归属:**
- 一个采购员可以绑定多台客户端;一台客户端同一时间最多绑定一个采购员,也允许未绑定。
- 只有管理员能绑定、转交、解绑和删除客户端;采购员只读看到当前绑定给自己的客户端。
- 转交和解绑保留负责人、操作管理员、开始/结束时间等完整历史。
- 归属只影响 Web 可见范围和采购任务创建时的客户端候选,不改变 Client 四接口,
也不改动已分配、领取或执行中的任务。
- 新绑定目标必须是启用中的采购员。已禁用采购员的历史不删除。
**底部状态条:** 在线 / 离线数量统计。
## 5. 创建采购任务的校验
`[必须]` 下面任何一条不满足就不允许创建,并明确告诉操作员缺什么:
1. 该蝦皮商品已填 PDD 链接;
2. 该蝦皮商品已采集成功(`pdd_data` 非空);
3. 该蝦皮 SKU 已有 PDD 规格映射;
4. 数量大于 0;
5. **价格上限已填且大于 0** —— 默认从 `pdd_data` 里该 SKU 的价格带出,操作员可改,但不允许为空;
6. 已选择分配的客户端。
第 5 条是硬要求:Client 契约规定采购任务必须带明确的价格保护
(见 [Client 契约](../client/04-admin-api-contract.md) §4),没有它 Client 会拒绝执行。
## 6. 状态定义
### 6.1 采集状态
界面上显示 5 种,但**数据来自两张表**——采集的对象是 PDD 商品,
所以状态存在 `pdd_products` 上,不在蝦皮商品上。
| 界面显示 | 怎么判断 |
|---|---|
| 未填链接 | `shopee_products.pdd_goods_id` 为空 |
| 未采集 | `pdd_products.collect_status = 'pending'` |
| 采集中 | `= 'collecting'` |
| 已采集 | `= 'collected'` |
| 采集失败 | `= 'failed'`,原因在 `collect_msg` |
`[必须]` `collecting` 状态的商品不允许再建采集任务,按钮置灰并提示"已在采集中"。
`[必须]` 状态挂在 PDD 商品上,好处是**两个蝦皮商品指向同一个 PDD 链接时,
状态只有一份**——不会各记一份还可能不一致,也不会把同一个商品采两遍。
### 6.2 任务状态(采集任务和采购任务通用)
这是 **Admin 侧的状态**,和 Client 本地的 8 个状态是两套,不要混
(Client 侧见 [03 数据模型](../client/03-data-model.md) §7)。
| 值 | 中文 | 什么时候 |
|---|---|---|
| `pending` | 待分配 | 刚创建,还没指定客户端 |
| `assigned` | 待领取 | 已分配给某个客户端 |
| `claimed` | 已领取 | 客户端领走了,正在执行 |
| `succeeded` | 成功 | 收到结果 |
| `manual_review` | 需人工 | 客户端报告需要人处理 |
| `failed` | 失败 | 客户端报告失败 |
| `cancelled` | 已取消 | 人工取消 |
`[必须]` **Admin 看不到客户端执行到哪一步**(没有心跳,是有意的)。
`claimed` 之后就只能等结果。想知道细节看 Client 那边的界面。
客户端提交失败时带的 `status`,按下表落地:
| 客户端报告 | Admin 置为 |
|---|---|
| `retry_wait` | `assigned`(等它再来领) |
| `manual_review` | `manual_review` |
| `failed` | `failed` |
| `cancelled` | `cancelled` |
## 7. 金额与币种
`[必须]` **一律用整数存,禁止浮点。** 字段名带单位后缀。
| 场景 | 币种 | 字段示例 |
|---|---|---|
| 蝦皮售价 | 台币 | `price_twd_cent` |
| PDD 采购价、价格上限 | 人民币 | `max_price_cent` |
`[必须]` **两种币种不得混用,不得互相换算后覆盖原值。**
采购任务里的价格上限是**人民币**,来源是采集回来的 PDD 价格,
和蝦皮的台币售价没有换算关系。
`[待定]` 是否需要展示汇率或利润,待业务确认。MVP 不做。
## 8. MVP 范围
MVP 包含:
- 五模块页面框架和统一的三段式布局;
- SQLite 建库与迁移;
- 蝦皮 Excel 导入(upsert)、搜索、批量删除、手动新增、编辑弹窗;
- PDD 链接录入与发起采集;
- 顺运宝数据的**手工录入或造数**(同步按钮占位);
- 规格匹配弹窗与可复用映射;
- 创建采购任务并分配客户端;
- 给 Client 的四个接口:登记、领取、提交结果、提交失败;
- 客户端自动注册与在线状态;
- 第一次启动时初始化一个管理员;
- Admin 网页登录、退出和 Session;
- 管理员创建、禁用和重置采购员账号。
- 管理员绑定、转交和解绑客户端;采购员只读查看自己的客户端。
MVP 之后:
- 接入顺运宝真实同步;
- 打包成 exe;
- 统计报表。
## 9. 非目标
- 不做面向外部的公网服务,只在内网/本机运行。
- 不直接对接蝦皮和拼多多的接口。
- 不自动决定买哪个商品——PDD 链接和规格匹配都由人确认。
- 不自动付款。
- 不开放用户自助注册;第一个管理员只能通过首次初始化创建。
- 不做复杂 RBAC、细粒度页面权限、单点登录和第三方登录。
- 本阶段不做 Client 登录或 API Key,不改变 Client 四接口契约。
## 10. 非功能需求
- 单机运行,操作人员规模是个位数,不追求高并发。
- 页面在 1366×768 上可正常使用。
- 导入 1 万行 Excel 应在可接受时间内完成,并显示进度或结果统计。
- 所有写操作有 CSRF 防护,所有 SQL 参数化。
- 密码只保存成熟算法生成的哈希;Session 有过期、退出和账号禁用失效机制。
- 首次管理员、采购员初始密码和重置密码统一要求至少 6 个字符,最多 72 个字节;
前端提示与服务端校验必须一致。
- 日志、页面、导出不含 token、密码、Cookie。
- 数据放程序旁边的 `data/`,便携模式,见 [02 架构](02-architecture.md) §6。
## 11. 待确认事项
不许因此停工。先按临时默认值做,定了再按工单改。
| # | 待确认什么 | 临时默认值 |
|---|---|---|
| 1 | 顺运宝同步方式(接口?导出文件?) | 按钮占位,数据靠手工录入或造数 |
| 2 | 蝦皮有无全量规格导出 | 没有。靠反复导入累积 + 手动新增补齐 |
| 3 | Admin 是否需要登录 | 已定案:MVP 实现管理员初始化、网页登录和最小账号管理 |
| 4 | 是否需要利润和汇率展示 | 不做 |
**已定案:**
- PDD 链接**人工填写**,不自动抓取。
- PDD 商品是**独立模块**,创建商品和创建采集任务都在那里;
蝦皮数据模块的编辑弹窗负责的是「这个蝦皮商品对应哪个 PDD 商品」。
- **采集任务不指定客户端**,谁领到算谁的;采购任务仍可分配,也允许留空。
- 任务**分配给指定客户端**,Client 只领分给自己的。
- **不加心跳接口**;设置页显式登记,领取接口保留兼容登记,在线状态由最近活动时间派生。
- SKU 映射**独立成表且可复用**,同一蝦皮 SKU 只人工匹配一次。
- 货运单的"规格 SKU"**直接对应蝦皮 `商品規格ID`**,不需要转换。
但**不加外键**——本地查不到该 SKU 是常态,见 [03 数据模型](03-data-model.md) §4。
- Admin 不提供固定默认密码;第一次启动由用户创建第一个管理员,之后初始化入口永久关闭。
- 账号只有 `admin`(管理员)和 `purchaser`(采购员)两种固定角色;采购员不能管理用户。
- Web 登录只保护 HTML 页面,`/api/v1/client/*` 不使用网页登录 Session,现有 Client 行为保持不变。
- Go 版本固定 **1.23.0**。