Files

808 lines
35 KiB
Markdown
Raw Permalink Normal View History

2026-08-31 11:33:59 +08:00
# 订单模式多人分摊数量与防重复录入方案
> **方案状态:待业务确认,尚未实施。**
>
> **要解决的问题:** 同一个订单号代表多台产品,多个操作人员可以在同一个步骤分别提交不同数量。系统必须允许同一人员多次提交、允许不同人员协作,同时严格保证每个步骤累计数量不超过该订单的目标数量。
>
> **示例订单:** 订单号 `6721678`,订单总数量 `8` 台:
>
> - 步骤一「拼板」:操作员 A 提交 `4` 台,操作员 B 提交 `4` 台;
> - 步骤二「组装」:操作员 C、D、E、F 各提交 `2` 台;
> - 每一台实物均使用订单号 `6721678`,没有可扫描的独立序列号。
---
## 1. 问题结论
在当前订单模式中,**不能仅依靠现有“不允许重复扫码”配置解决该场景**。
根本原因是:当前订单模式把一次扫码视为一条“订单号 + 工序 + 步骤”的单件记录,`produce_scan_data` 没有“本次提交数量”字段,也没有“订单总数量”或“操作员分摊数量”概念。后端重复校验也会直接禁止同一订单号在同工序、同步骤下再次录入。
因此,若订单号 `6721678` 已被 A 在「拼板」提交过一次,B 再以相同订单号在「拼板」提交时,当前规则会命中重复扫码限制,而不是将其识别为“不同人员提交另一部分数量”。
### 推荐结论
推荐将订单模式升级为:
> **按订单号 + 工序 + 步骤汇总数量,按每次提交保存人员和数量明细;不以扫码次数代表数量,不限制同一人员在同一步骤的提交次数,只限制步骤累计数量不能超出订单目标数量。**
核心原则:
1. **拷贝是订单的首次数量录入步骤,由拷贝首次有效提交建立订单总数量;**
2. **同一订单同一步骤允许多人协作,人员之间不要求均分;**
3. **同一人员在同一订单、同一步骤下允许多次提交,不设置“同人只能一笔”的限制;**
4. **每次提交数量必须为正整数,且该步骤所有有效提交数量之和不得超过订单总数量;**
5. **本步骤累计提交数量达到订单总数量后,禁止任何人继续提交该步骤;**
6. **拼板、组装等下游步骤不要求等待上一步数量满额或进入业务完成状态,可按现场进度直接录入;**
7. **步骤数量达到订单总数量只表示该步骤数量已录满;非末步骤的业务完成状态由下一个步骤数量满额后的状态转换处理,末步骤数量满额后在同一事务内按终态规则完成自身及前序步骤的状态转换;订单完成以整条步骤状态转换链完成为准。**
这里的“防重复”不再表示禁止同一订单号再次扫码,而是:
> 同一订单号的重复扫码必须转化为一次有数量的提交,并通过事务、幂等请求号和步骤累计上限防止重复计数与超报。
---
## 2. 当前实现核查
### 2.1 当前订单模式的记录粒度
当前订单模式页面:
```text
frontend/scanapp/pages/entry/order.vue
```
提交数据只有:
```json
{
"order_no": "6721678",
"user_code": "当前用户编码",
"process_id": "当前工序ID",
"step_id": "当前步骤ID",
"scan_result": 1
}
```
订单模式没有输入“本次数量”,每次提交只创建一条扫码记录。
订单记录表:`produce_scan_data`,当前字段包括订单号、用户、工序、步骤、步骤状态、扫码结果和扫码时间,但**不包括**:
- 订单计划总数量;
- 当前提交数量;
- 当前人员已分摊数量;
- 当前步骤累计提交数量;
- 该订单步骤数量是否已满、业务状态是否已完成的独立汇总信息。
### 2.2 当前重复扫码规则
后端入口:
```text
backend/plugin/produce/app/api/controller/DataEntryController.php
```
当前重复校验按以下条件查找最新记录:
```text
order_no + process_id + step_id
```
只要该组合已经有一条历史记录,系统就进入重复扫码策略;当当前规则设为“不允许重复扫码”时,后续任何人使用相同订单号进入同一工序、同一步骤,都会被拒绝。
当前逻辑没有将 `user_code` 作为重复判定条件,也没有数量概念。
### 2.3 当前前置步骤规则的局限
当前下一步骤的前置校验只判断订单号是否存在前置步骤记录,或是否存在终态记录。
本业务确认下,拷贝、拼板、组装之间**不设置“上一步进入业务完成状态后下一步才能录入”的强制门槛**。因此:
- 拷贝首次提交数量后即可形成订单总量;
- 拼板可以多人、多次提交;
- 组装不要求等待拼板数量满额或进入业务完成状态,可按现场实际进度录入;
- 每个步骤仍必须独立执行累计数量上限校验,不能因为下游开放而放宽本步骤的防超报规则。
因此,方案需要取消原有的“前置步骤必须数量满额”及“前置步骤必须业务完成”校验,但保留步骤顺序、步骤配置和进度展示,用于业务提示、统计和订单完成判定。
### 2.4 整机模式不能直接作为替代方案
整机模式的 `produce_product_record` 已有 `quantity` 字段,确实支持每次提交数量。
但它的数据模型和当前订单模式不同:
- 使用 `process_order_no + product_code`;
- 当前接口未执行订单模式的前置步骤校验、重复扫码校验和下一步骤自动状态流转;
- 无法直接替代以订单号 `6721678` 为核心的订单流程追溯。
因此,不建议仅因为整机模式有数量字段,就把当前订单场景切换为整机模式。应在订单模式中补齐数量与分摊能力。
---
## 3. 业务目标与边界
### 3.1 必须满足的目标
1. 一个订单号可以对应多台产品;
2. 多位人员可以在同一工序、同一步骤,对同一订单分担数量;
3. 每笔录入明确归属到具体操作人员;
4. 每个步骤累计数量不得超过订单总数量;
5. 下游步骤可以按现场进度录入,不以“上一步必须满量”为前提;
6. 即使多人并发提交,也不能超报;
7. 防止同一客户端请求因网络重试被重复累计;
8. 能统计每名人员在每个订单步骤的实际提交数量和提交明细;
9. 能追溯更正、撤销或返工对数量和状态的影响。
### 3.2 本方案的业务假设
本方案采用以下假设;若与现场规则不符,应在实施前调整:
1. 拷贝是第一道数量录入步骤,拷贝首次有效提交的数量建立订单总数量;
2. 同一订单号下的多台产品按数量批次管理,不要求逐台唯一序列号;
3. 拼板、组装等步骤的目标数量均等于订单总数量;
4. 同一人员在同一订单、同一步骤下可以多次提交,每次提交都形成独立明细;
5. 各步骤不强制等待上一步数量满额或进入业务完成状态,但每个步骤独立限制累计数量不得超过订单总数量;
6. 分摊数量必须为正整数,不允许 0 或负数;
7. 后台管理员负责更正、撤销和异常处理;
8. 初版不对每台产品建立独立身份;若未来需要逐台追溯,应升级为序列号、子条码或批次号方案。
---
## 4. 推荐方案:订单总量 + 人员分摊明细
### 4.1 总体设计
将订单生产记录拆分为三个层次:
```text
订单主档(订单总数量)
└─ 订单步骤汇总(当前步骤累计数量、业务完成状态)
└─ 订单步骤人员明细(某人员本次提交数量)
```
| 层次 | 核心职责 | 推荐表 |
| --- | --- | --- |
| 订单主档 | 维护订单号、计划数量、订单整体状态 | `produce_order` |
| 订单步骤汇总 | 按订单、工序、步骤汇总数量和步骤业务完成状态 | `produce_order_step` |
| 人员分摊明细 | 记录谁在该订单步骤提交多少数量 | `produce_order_step_entry` |
现有 `produce_scan_data` 建议继续保留为原始扫码/操作审计记录,或者在迁移稳定后作为兼容历史表使用;不建议继续将它作为数量汇总的唯一数据来源。
### 4.2 为什么不只给 `produce_scan_data` 加一个 `quantity`
仅增加 `quantity` 字段虽然改动较小,但不能可靠解决以下问题:
- 订单总数量从哪里取得;
- 如何保证多用户并发提交不超过总数量;
- 如何保证同一网络请求重试时不被重复累计;
- 如何保存每个步骤的累计数量、数量完成标记和业务完成状态;
- 如何支持更正、撤销以及审计;
- 如何在不阻塞下游录入的前提下管理各步骤数量进度。
可以把 `quantity` 加入 `produce_scan_data` 作为辅助审计字段,但核心业务约束应落在“订单步骤汇总 + 人员分摊明细”的结构中。
---
## 5. 数据设计
### 5.1 订单主档:`produce_order`
作用:定义订单 `6721678` 的总数量,作为每个步骤的数量上限。
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `id` | bigint | 雪花主键 |
| `order_no` | varchar(100) | 订单号,唯一 |
| `planned_quantity` | integer | 订单计划总数量,例如 8 |
| `status` | tinyint | 订单状态,例如进行中、已完成、关闭;仅完整步骤状态转换链完成后置为已完成 |
| `completed_at` | bigint nullable | 订单完整状态转换链完成时间 |
| `remark` | text nullable | 备注 |
| 审计字段 | bigint | `created_by`、`updated_by`、`created_at`、`updated_at`、`deleted_at` |
关键约束:
```text
同一未删除订单号只能有一条订单主档。
planned_quantity 必须大于 0。
```
订单总数量由拷贝步骤的首次有效提交数量建立:
1. 首次拷贝提交时,创建订单主档并将 `entry_quantity` 写入 `planned_quantity`;
2. 后续拷贝、拼板、组装等步骤均使用该固定总数量作为上限;
3. 普通移动端人员不能修改已建立的订单总数量;首笔拷贝数量有误时,由后台管理员按更正流程处理。
### 5.2 订单步骤汇总:`produce_order_step`
作用:为一个订单在一个工序步骤下维护总数量进度和业务完成状态。
唯一业务维度:
```text
order_id + process_id + step_id
```
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `id` | bigint | 雪花主键 |
| `order_id` | bigint | 关联订单主档 |
| `order_no` | varchar(100) | 冗余订单号,方便查询与审计 |
| `process_id` | bigint | 工序 ID |
| `step_id` | bigint | 步骤 ID |
| `planned_quantity` | integer | 从订单主档快照的步骤目标数量,初始为订单总数量 |
| `completed_quantity` | integer | 已成功累计数量 |
| `status_id` | bigint nullable | 当前步骤状态 |
| `is_completed` | tinyint | 是否业务已完成:0 否,1 是;由状态转换规则更新 |
| `completed_at` | bigint nullable | 本步骤业务进入完成/终态的时间;由状态转换规则写入 |
| 审计字段 | bigint | 标准创建、更新、软删除字段 |
关键约束:
```text
同一未删除的 order_id + process_id + step_id 只能有一行。
0 <= completed_quantity <= planned_quantity。
```
### 5.3 订单步骤人员分摊明细:`produce_order_step_entry`
作用:记录某操作员在一个订单步骤下的一次数量提交。
业务查询维度:
```text
order_step_id + user_code
```
同一人员可以在同一步骤产生多条有效提交明细,不能使用 `order_step_id + user_code` 作为唯一约束。
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `id` | bigint | 雪花主键 |
| `order_step_id` | bigint | 关联订单步骤汇总 |
| `entry_quantity` | integer | 本次提交数量,例如拼板时为 4,组装时为 2 |
| `user_code` | varchar(50) | 操作用户编码 |
| `client_request_id` | varchar(100) | 客户端请求幂等号 |
| `operator` | varchar(100) | 操作人员快照 |
| `operator_id` | bigint | 会员 ID |
| `status` | tinyint | 有效、已撤销等业务状态 |
| `submitted_at` | bigint | 提交时间 |
| `voided_at` / `void_reason` | bigint/text nullable | 撤销时间和原因 |
| `remark` | text nullable | 备注 |
| 审计字段 | bigint | 标准审计字段 |
关键约束:
```text
同一 order_step_id 下允许同一 user_code 存在多条有效提交明细。
同一 client_request_id 在同一订单步骤下只能成功计入一次。
entry_quantity 必须大于 0。
所有有效明细数量之和必须不超过订单步骤 planned_quantity。
```
新的“防重复”技术落点不是限制人员,而是限制计数:
> 同一人员可以多次提交;同一网络请求重试不能重复累计;所有人员、所有提交的有效数量之和不能超过该步骤目标数量。
### 5.4 与现有 `produce_scan_data` 的关系
建议第一阶段不删除或改写历史 `produce_scan_data`。新增方案可采用以下兼容策略:
| 数据 | 第一阶段处理 |
| --- | --- |
| 既有历史订单记录 | 继续按原表查询与展示 |
| 新的数量分摊订单 | 以新增三张表作为权威数量数据 |
| 每次人员分摊提交 | 可同步追加一条 `produce_scan_data` 审计记录,并增加 `quantity`、`order_step_entry_id` 等可选关联字段 |
| 前置步骤与自动流转 | 对新订单改从 `produce_order_step` 判断;旧订单保持原规则或设定迁移日期 |
为避免新旧逻辑混杂,推荐增加订单处理模式,例如 `tracking_mode`:
```text
legacy_scan:旧的单次扫码模式
quantity_split:新的数量分摊模式
```
订单主档建单时确定模式;不要在同一个订单中途切换。
---
## 6. 新的录入与防重复规则
### 6.1 移动端页面调整
订单模式页面需要增加:
1. **订单总数量与当前进度展示**
- 例如:`订单总量:8 台`
- 例如:`拼板数量进度:4 / 8 台`
- 例如:`本次最多可录:4 台`
2. **本次提交数量输入**
- 默认建议为 `1`;
- 支持加减和数字输入;
- 最大值为当前步骤剩余未分配数量;
- 仅允许正整数。
3. **当前人员已提交提示**
- 未提交:可录入;
- 已提交:显示“您已提交 4 台,共 2 笔,仍可继续录入”;
- 不再显示为模糊的“重复扫码”。
4. **步骤进度提示**
- 下游不因上一步未进入业务完成状态而锁定;
- 可显示:`拼板数量进行中(4/8),组装可按现场进度录入。`
5. **步骤人员分摊明细(可选)**
- 可显示人员名称/编码和数量;
- 是否对普通操作员可见需要由现场管理规则决定。
### 6.2 提交接口建议
新增专用接口,而不是直接修改旧接口语义:
```text
POST /appapi/produce/order-step-entry/record
```
请求建议:
```json
{
"order_no": "6721678",
"process_id": 101,
"step_id": 201,
"entry_quantity": 4,
"client_request_id": "4b0c4c11-05bd-4cc1-947e-76f9a6c71356",
"remark": ""
}
```
后端从当前登录态取得操作人员并生成操作人快照,不接受客户端传入 `user_code` 或操作人员名称。
成功响应建议包含实时进度:
```json
{
"order_no": "6721678",
"step_id": 201,
"entry_quantity": 4,
"completed_quantity": 8,
"planned_quantity": 8,
"remaining_quantity": 0,
"quantity_completed": true,
"step_completed": false,
"order_completed": false
}
```
其中,`quantity_completed` 表示当前步骤数量已达到目标;`step_completed` 表示当前步骤已进入业务完成状态:非末步骤由下一个步骤数量满额触发的状态转换写入,末步骤由自身数量满额触发的状态转换事务写入;二者不能混用。
### 6.3 后端原子校验顺序
为防止两个操作员同时提交导致总数超报,必须在数据库事务中完成以下步骤,并锁定订单步骤汇总行:
1. 校验请求字段、用户身份、订单存在性;
2. 查询订单主档并确认 `planned_quantity > 0`;
3. 若当前步骤不是拷贝,确认订单总数量已经由拷贝首次有效提交建立;不校验上一步是否已进入业务完成状态;
4. 获取或创建当前订单步骤汇总行;
5. 对订单步骤汇总行执行行锁(`lockForUpdate`);
6. 根据 `client_request_id` 检查当前请求是否已经成功处理;若已处理,直接返回首次结果;
7. 允许当前人员已有历史有效分摊明细,按本次提交继续追加;
8. 计算:
```text
remaining_quantity = planned_quantity - completed_quantity
```
9. 若 `entry_quantity > remaining_quantity`:拒绝,提示“本步骤剩余可录数量为 N 台”;
10. 新建本次人员分摊明细;
11. 累加订单步骤汇总的 `completed_quantity`;
12. 若累计数量达到目标数量:
- 标记当前步骤数量已完成并禁止该步骤继续提交;
- 非末步骤不直接将业务步骤状态切换为完成,也不以此拦截下游录入;
- 仅刷新数量进度和展示状态,或在末步骤场景进入后续状态转换;
13. 若本次提交使末步骤累计数量达到订单总数量,则执行状态转换:末步骤作为状态转换链起点,先按末步骤终态规则完成自身状态转换,再驱动其上一步骤进入完成/终态;
14. 按步骤顺序继续向前执行状态转换,并写入每个受影响步骤的业务完成时间;
15. 检查完整步骤状态转换链是否已经完成;仅链完整时将订单标记为已完成并写入订单完成时间;
16. 写入审计日志并提交事务。
### 6.4 新的防重复判定矩阵
| 情况 | 是否允许 | 原因/提示 |
| --- | --- | --- |
| A 首次在拼板提交 4 台 | 允许 | 当前人员首次分摊,剩余数量充足 |
| B 首次在拼板提交 4 台 | 允许 | 不同人员分摊,累计正好 8 台 |
| A 再次在拼板提交 1 台 | 允许 | 同一人员允许多次提交,且未超过步骤剩余数量 |
| C 在拼板提交 1 台(拼板已 8/8) | 不允许 | 当前步骤数量已满,不可继续录入 |
| C 在组装提交 2 台(拼板仅 4/8) | 允许 | 下游不强制等待上游业务完成 |
| D 在组装提交 2 台 | 允许 | 剩余数量充足,与人员是否已提交无关 |
| G 在组装提交 1 台(组装已 8/8) | 不允许 | 当前步骤数量已满,不可继续录入 |
| C 在组装提交 3 台(组装剩余仅 2 台) | 不允许 | 本次数量超过剩余数量 |
| 网络重试同一个 `client_request_id` | 只计一次 | 幂等控制,不能重复累计 |
---
## 7. 示例:订单 6721678 的完整流程
假设:订单 `6721678` 的计划数量由拷贝首次有效提交建立为 `8`,流程为:
```text
拷贝 → 拼板 → 组装
```
### 7.1 初始数据
```text
订单:6721678
订单总量:未建立
拷贝:0 / -
拼板:0 / -
组装:0 / -
```
### 7.2 拷贝首次建立订单总量
| 顺序 | 操作人员 | 步骤 | 本次数量 | 拷贝累计 | 结果 |
| --- | --- | --- | ---: | ---: | --- |
| 1 | A | 拷贝 | 8 | 8 / 8 | 成功,建立订单总量 8;后续步骤目标均为 8 |
### 7.3 拼板分摊
| 顺序 | 操作人员 | 步骤 | 本次数量 | 拼板累计 | 结果 |
| --- | --- | --- | ---: | ---: | --- |
| 2 | A | 拼板 | 4 | 4 / 8 | 成功,组装可按现场进度录入 |
| 3 | B | 拼板 | 4 | 8 / 8 | 成功,拼板数量完成,禁止继续录入;待组装数量满额后按规则转为业务完成 |
此时拼板人员明细:
| 人员 | 数量 |
| --- | ---: |
| A | 4 |
| B | 4 |
| 合计 | 8 |
### 7.4 组装分摊
| 顺序 | 操作人员 | 步骤 | 本次数量 | 组装累计 | 结果 |
| --- | --- | --- | ---: | ---: | --- |
| 4 | C | 组装 | 2 | 2 / 8 | 成功;即使拼板未进入业务完成状态也可录入 |
| 5 | D | 组装 | 2 | 4 / 8 | 成功 |
| 6 | E | 组装 | 2 | 6 / 8 | 成功 |
| 7 | F | 组装 | 2 | 8 / 8 | 成功,组装数量完成,禁止继续录入;在同一事务内完成组装自身终态转换并触发前序步骤状态转换,整条状态链完成后订单完成 |
组装人员明细:
| 人员 | 数量 |
| --- | ---: |
| C | 2 |
| D | 2 |
| E | 2 |
| F | 2 |
| 合计 | 8 |
### 7.5 多次提交与数量满额后的处理
若 C 在组装已提交 2 台后再次提交 1 台:
```text
允许:本次追加 1 台,C 在组装累计提交 3 台。
```
若拼板累计数量尚未达到 8 台,C 仍可在组装提交数量;若组装累计数量已达到 8 台,任何人员继续提交都必须拒绝。
若 G 尝试在组装提交:
```text
拒绝:步骤“组装”数量已满(8 / 8),不可继续录入。
```
这样既保证多人分摊和同一人员正常追加,又避免网络重试重复计数或总数超过 8 台。
---
## 8. 状态和流转调整方案
### 8.1 步骤状态建议
现有步骤状态可以继续使用,但业务状态含义应与数量进度区分:
| 状态建议 | 数量条件 | 说明 |
| --- | --- | --- |
| 未开始 | `completed_quantity = 0` | 无人员提交 |
| 进行中 | `0 < completed_quantity < planned_quantity` | 已有人累计提交部分数量 |
| 数量已完成 | `completed_quantity = planned_quantity` | 步骤数量满额;禁止继续提交,但不直接等同于业务步骤完成 |
| 业务已完成(非末步骤) | 下一个步骤数量满额并触发状态转换 | 当前步骤按状态转换规则进入完成/终态 |
| 业务已完成(末步骤) | 末步骤数量满额后触发状态转换事务 | 末步骤先按终态规则进入完成/终态,再驱动前序步骤转换 |
若现有每个步骤只配置“初始状态”和“终态”,可补充一个“进行中”状态;非末步骤数量满额时仅更新自身数量进度,并由下一个步骤数量满额后的状态转换驱动其进入终态;末步骤数量满额时在同一状态转换事务内按终态规则进入终态。页面应独立显示数量进度和业务状态。
### 8.2 前置步骤判定调整
本业务不要求下游等待上游数量满额或进入业务完成状态,因此数量分摊模式不再执行“前置步骤必须数量满额”或“前置步骤必须业务完成”的拦截。
规则调整为:
```text
进入任意已配置步骤
= 订单总数量已经由拷贝首次提交建立
且当前步骤自身数量尚未满额
```
步骤顺序仍用于展示、统计和状态转换链判定,不作为下游录入的强制阻断条件。若未来需要实现“累计录入 2 台才能流转 2 台”的批次级约束,必须另行引入批次或逐台标识,本方案不实现该限制。
### 8.3 自动流转调整
现有状态流转应保留下游驱动上游的业务语义,并将触发条件从“下一步骤第一笔扫码”调整为“下一步骤数量满额”:
1. 非末步骤累计数量达到订单总数量时,只标记该步骤数量已完成并禁止继续提交;该步骤自身不直接进入业务完成状态,但应按状态转换规则驱动其上一步骤进入完成/终态;
2. 下游步骤可以在上游未进入业务完成状态时录入,不执行前置业务完成拦截;
3. 当最后一个业务步骤累计数量达到订单总数量时,必须在同一事务内作为状态转换链起点:先按末步骤终态规则完成自身状态转换,再依次驱动前序步骤进入完成/终态;
4. 状态转换应沿步骤顺序继续向前传递;只有整条步骤状态转换链完成后,订单才进入已完成状态;
5. 最后一个业务步骤数量满额仅触发状态转换事务,不应脱离该事务单独作为订单完成的唯一判定。
因此,数量完成和业务状态完成必须分开:数量由当前步骤自身累计决定;非末步骤的业务状态完成由下一个步骤数量满额后的状态转换决定;末步骤由其数量满额触发状态转换事务后按终态规则完成;订单完成由完整状态转换链决定。
---
## 9. 更正、撤销与返工
### 9.1 多次提交与错误处理
同一人员多次提交是正常业务,不应被系统当作重复错误。每次提交都必须带数量并形成明细,系统只校验:
- 本次数量为正整数;
- 本步骤累计数量不超过订单总数量;
- 同一个客户端请求不会重复计数。
录入错误不通过“禁止再次提交”解决,而由后台管理员使用更正、撤销或返工功能处理,并保留完整审计记录。
### 9.2 更正方案(推荐)
建议增加仅后台管理员可用的更正接口:
```text
POST /adminapi/produce/order-step-entry/{id}/adjust
```
请求内容:
```json
{
"entry_quantity": 3,
"reason": "原录入 4 台,其中 1 台返工,数量更正为 3 台"
}
```
更正规则:
1. 仅后台管理员可操作;
2. 必须填写原因;
3. 记录更正前后数量、操作人和时间;
4. 事务内同步重算步骤累计数量;
5. 若步骤数量已完成且更正后不足量,应撤销该步骤的数量完成标记;若该步骤此前已由下游触发业务完成,还应按状态转换规则重算并撤销相应业务完成状态;
6. 下游不以该步骤满量为录入前提;已有下游数据时仍须保留更正审计,并由后台管理员按返工/逆向流程处理业务影响。
### 9.3 撤销方案
取消某个人员的整笔分摊时,应进行软撤销而非物理删除:
- 设置明细为已撤销;
- 记录撤销人、撤销时间和撤销原因;
- 从步骤汇总 `completed_quantity` 中扣除该笔数量;
- 根据剩余数量重新计算步骤数量完成状态,并按状态转换规则重算业务完成状态;
- 写入不可篡改的操作日志。
---
## 10. 并发与数据一致性要求
多人同时提交是本问题的关键风险,必须由后端数据库事务保证,前端不能承担最终防线。
### 10.1 典型并发问题
订单 `6721678` 的组装累计数量为 `6 / 8` 台,剩余 2 台:
- E 读取到剩余 2 台,准备提交 2 台;
- F 同时也读取到剩余 2 台,准备提交 2 台;
- 如果两次请求都只在前端判断,就会写入 10 / 8 台。
### 10.2 必须采用的后端控制
后端对 `produce_order_step` 的当前订单步骤记录执行事务加锁:
```text
开始事务
↓
锁定 6721678 + 当前工序 + 当前步骤 的汇总行
↓
重新读取 completed_quantity
↓
校验请求幂等性与剩余数量
↓
写入人员分摊明细
↓
更新汇总数量、数量完成标记与受本次事件影响的业务状态
↓
提交事务
```
数据库幂等唯一索引与行锁必须同时具备:
- 幂等唯一索引负责保证同一个 `client_request_id` 只成功计入一次;
- 行锁负责保证多人、多次累计不超过订单总量。
### 10.3 幂等性建议
移动端在网络抖动时可能自动重试。建议请求携带 `client_request_id`(UUID),后端对“订单步骤 + 用户 + 请求 ID”做幂等控制:
- 同一请求重复到达时直接返回首次结果;
- 不重复累计数量;
- 不限制同一人员后续发起新的、不同请求号的数量提交。
---
## 11. 实施范围与阶段计划
### 第一阶段:订单总量与步骤进度基础
1. 新增订单主档、订单步骤汇总、人员分摊明细表;
2. 增加订单导入/创建能力,确保订单总数量可用;
3. 增加按订单查询进度的 API;
4. 管理端增加订单和步骤进度查询页面;
5. 明确拷贝首次提交建立订单总量的规则,并保留原订单扫码模式兼容历史订单。
验收:能够建立订单 `6721678`,维护计划数量 `8`,并查询每个步骤的 `数量完成/总量` 与独立的业务完成状态。
### 第二阶段:移动端分摊录入
1. 订单模式页面增加数量字段和进度展示;
2. 新增订单步骤人员分摊提交接口;
3. 后端实现重复请求幂等、步骤剩余数量限制和事务并发控制;
4. 增加“我的累计提交”和“提交明细”提示,不限制同一人员再次提交;
5. 新订单切换到数量分摊模式。
验收:拷贝首次录入 8 台建立订单总量;拼板可由两人各录入 4 台,组装可由四人各录入 2 台;同一人员可在同一步骤再次追加;任何人、任何次数提交都不能使步骤累计超过 8 台,数量满额不直接判定为业务完成。
### 第三阶段:流程与状态集成
1. 拷贝首次有效提交建立订单总数量;
2. 任一步骤数量满额后标记为数量已完成并禁止继续提交;非末步骤不直接进入业务终态,末步骤则进入状态转换事务;
3. 取消下游必须等待上游数量满额或业务完成的拦截;
4. 保留并调整“下一个步骤数量满额后驱动上一个步骤完成”的状态转换逻辑,并定义末步骤数量满额时自身终态转换及向前驱动规则;
5. 以完整步骤状态转换链作为订单完成条件,并补齐步骤进度和人员产量统计。
验收:拷贝建立订单总量后,拼板累计数量未达到 8 台时组装仍可录入;各步骤独立累加且均不可超过 8 台;组装数量满额后,在同一事务内先完成组装自身终态转换,再按状态转换规则驱动拼板、拷贝进入业务完成状态;整条状态转换链完成后订单进入已完成状态。
### 第四阶段:更正、撤销、返工与迁移
1. 实现管理端更正和撤销;
2. 建立返工场景的逆向流程;
3. 为移动端重试增加幂等键;
4. 确定旧记录查询与新模式查询的兼容策略;
5. 在试点订单和用户上进行并发压力验证。
---
## 12. 验收测试用例
### 12.1 基础分摊
| 编号 | 操作 | 预期 |
| --- | --- | --- |
| T01 | 创建订单 `6721678`,数量 8 | 创建成功 |
| T02 | A 在拼板录入 4 | 拼板进度 4/8,步骤进行中 |
| T03 | B 在拼板录入 4 | 拼板进度 8/8,数量完成并禁止继续录入;待组装数量满额后按规则转为业务完成 |
| T04 | C 在组装录入 2 | 组装进度 2/8 |
| T05 | D、E、F 分别在组装录入 2 | 组装数量最终 8/8,禁止继续录入;在同一事务内完成组装自身终态转换,并按状态转换规则依次驱动拼板、拷贝进入业务完成状态;完整转换链完成后更新订单状态 |
### 12.2 防重复与防超报
| 编号 | 操作 | 预期 |
| --- | --- | --- |
| T06 | A 已录拼板 4 后,再录拼板 1 | 允许,拼板累计为 5/8 |
| T07 | 拼板已 8/8 后,C 再录拼板 1 | 拒绝:步骤数量已满,不可继续录入;不影响其业务完成状态按规则判定 |
| T08 | 组装 6/8 时,G 录入 3 | 拒绝:超过剩余 2 台 |
| T09 | 组装 6/8 时,E、F 并发各录 2 | 按先后仅允许不超出剩余数量,最终不超过 8/8 |
| T10 | 同一请求因网络重试两次 | 只累计一次 |
### 12.3 流程控制
| 编号 | 操作 | 预期 |
| --- | --- | --- |
| T11 | 拷贝已建立总量 8,拼板仅 4/8 时尝试组装录入 | 允许,不受上游未进入业务完成状态的限制 |
| T12 | 拼板达到 8/8 后尝试组装录入 | 允许,拼板数量完成不构成下游录入门槛 |
| T13 | 拼板未数量完成时,组装录入 2 台 | 允许,按组装步骤独立累计 |
| T14 | 后台管理员将已提交明细更正或撤销 | 重算对应步骤数量状态,并按状态转换规则重算步骤完成链和订单完成状态,记录原因 |
---
## 13. 业务规则确认结果
以下规则已按当前业务意见纳入方案:
1. **订单总数量来源**:由拷贝步骤首次提交数量建立;拷贝是第一道扫码录入步骤。
2. **同一人员是否只能提交一次**:不限制;同一人员可以在同一订单、同一步骤下多次提交。
3. **每个步骤目标数量**:是,均等于订单总数量。
4. **下游是否等待上游业务完成**:不需要;拼板、组装可按现场进度独立录入。
5. **是否逐台追溯**:当前不需要;按订单号和数量进行汇总追踪。
6. **更正和撤销权限**:由后台管理员执行,是否审批可作为后台权限配置,不影响移动端正常追加录入。
7. **订单完成定义**:不是简单判断最后一个步骤数量是否满额后立即完成,而是按照状态转换规则判定;非末步骤由下一个步骤数量满额后驱动进入业务完成状态,末步骤数量满额后在同一事务内完成自身终态转换并向前驱动,整条步骤链完成后订单才完成。
补充约束:订单总数量建立后不可由普通移动端人员随意修改;如拷贝首笔数量录入错误,由后台管理员按更正流程处理。
---
## 14. 不推荐的处理方式
### 14.1 直接关闭“不允许重复扫码”
不推荐。直接关闭限制而不增加数量字段、步骤累计上限和请求幂等控制,会让任意重复扫描都被计入,数量和产量无法可信统计。
### 14.2 将用户名直接加入现有重复判断,但不增加数量
不推荐作为最终方案。虽然能允许不同人员同单同步骤各扫一次,但系统只能知道“有几次操作”,无法知道:
- A 做了 4 台还是 1 台;
- B 做了多少;
- 累计是否恰好为 8 台;
- 各步骤数量是否已满、业务状态是否已完成,以及何时可判定订单完成。
### 14.3 每台都反复扫同一个订单号,把扫码次数当数量
不推荐。订单号相同且无单台唯一标识时,无法区分“正常第 2 台”与“误扫第 2 次”;应显式填写本次数量,并以请求幂等号和步骤累计上限控制重复计数。
### 14.4 仅使用整机模式替换订单模式
不推荐。整机模式虽有 `quantity`,但当前未继承订单模式的前置步骤、重复控制和自动状态流转;直接替换会丢失订单流程规则。
---
## 15. 实施后的目标状态
订单 `6721678` 在系统中的可见结果应为:
```text
订单总量:8 台
拷贝:8 / 8(数量已完成;已由后续步骤数量满额触发进入业务完成状态)
A:8 台
拼板:8 / 8(数量已完成;已由组装数量满额触发进入业务完成状态)
A:4 台
B:4 台
组装:8 / 8(数量已完成;已在同一状态转换事务内进入业务终态,并触发前序业务状态转换)
C:2 台
D:2 台
E:2 台
F:2 台
订单状态:组装数量满额后,在同一状态转换事务内完成组装自身终态转换并依次驱动拼板、拷贝完成;完整转换链完成后订单才为已完成
```
系统可以同时回答:
- 订单做了多少台?
- 每个步骤做了多少台、还差多少台?
- 谁做了多少台?
- 某个人累计提交了多少、每笔何时提交?
- 当前步骤数量是否已满、是否允许继续录入、还可录入多少?
- 订单总量是否已经由拷贝建立?
- 订单何时真正完成?
---
## 16. 实际代码定位
| 项目 | 位置 |
| --- | --- |
| 订单模式页面 | `frontend/scanapp/pages/entry/order.vue` |
| 整机模式页面(数量输入参考) | `frontend/scanapp/pages/entry/machine.vue` |
| 移动端生产 API 封装 | `frontend/scanapp/api/produce/data-entry.js` |
| 订单模式提交与重复校验 | `backend/plugin/produce/app/api/controller/DataEntryController.php` |
| APP API 委托控制器 | `backend/plugin/produce/app/appapi/controller/DataEntryController.php` |
| 现有订单扫码模型 | `backend/plugin/produce/app/model/ScanData.php` |
| 现有订单扫码迁移 | `backend/plugin/produce/resource/database/migrations/2026_07_31_000003_create_produce_config_and_scan_data_tables.php` |
| 现有整机记录模型 | `backend/plugin/produce/app/model/ProductRecord.php` |
| 现有整机记录迁移 | `backend/plugin/produce/resource/database/migrations/2026_08_06_000001_create_produce_product_record_table.php` |
| 现有步骤、状态、关系迁移 | `backend/plugin/produce/resource/database/migrations/2026_07_30_000001_create_produce_basic_tables.php` |