7.4 KiB
7.4 KiB
协议号自动生成设计文档
日期:2026-05-27
方案:B — 提交时后端生成
状态:待评审
背景
采集协议单录入/管理、捐赠协议单录入/管理 4 个界面,新增弹窗中协议号当前为下拉选择号池(biz_collection_protocol_no)。需改为按时间序列自动编号,生成后不可修改。
已确认决策
| 项 | 决策 |
|---|---|
| 实现方案 | 方案 B:保存(create)时由后端原子生成,前端新增态不预占号 |
| 采集协议前缀 | XBCC |
| 捐赠协议前缀 | JZCC |
| 日期段 | YYYYMMDD(服务器本地时区,建议 Asia/Shanghai) |
| 序号 | 6 位左补零,当日从 000001 递增 |
| 示例 | 采集:XBCC20260527000001;捐赠:JZCC20260527000003 |
| 序号范围 | 按前缀独立计数(XBCC 与 JZCC 各自维护当日序号,互不影响) |
| 不可修改 | 创建后协议号只读;编辑/更新接口拒绝修改 |
编号格式
{PREFIX}{YYYYMMDD}{SEQ6}
PREFIX 采集 = XBCC | 捐赠 = JZCC
YYYYMMDD 创建时刻服务器日期
SEQ6 同租户、同前缀、同日期下递增,000001–999999
总长度:4 + 8 + 6 = 18 字符(现有字段足够)。
现状与改动范围
共用组件(改 2 个弹窗覆盖 4 页)
| 页面 | 弹窗 |
|---|---|
| 采集协议单录入 | web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue |
| 采集协议单管理 | 同上 |
| 捐赠协议单录入 | web-admin/src/views/research-donation/list/DonationFormDialog.vue |
| 捐赠协议单管理 | 同上 |
后端
| 模块 | 改动 |
|---|---|
backend/app/crud/collection_protocol_no.py 或新建 biz_no_generator.py |
原子发号函数 |
backend/app/crud/collection_protocol.py |
create_collection_protocol 内发号;update 禁止改 protocol_no |
backend/app/crud/research_donation.py |
create_research_donation 内发号;update 禁止改 donation_no |
backend/app/api/collection_protocol.py |
CreateBody 中 protocolNo / protocolNoId 改为可选或忽略 |
backend/app/api/research_donation.py |
CreateBody 中 donationNo 改为可选或忽略 |
方案 B 数据流
sequenceDiagram
participant U as 用户
participant FE as 前端弹窗
participant API as 后端 create
participant DB as MySQL
U->>FE: 打开新增弹窗
FE->>U: 协议号展示「保存后自动生成」(只读占位)
U->>FE: 填写表单并提交
FE->>API: POST create(不传 protocolNo / donationNo)
API->>DB: 事务:查当日最大序号 + 1
DB-->>API: 新号 XBCC/JZCC...
API->>DB: INSERT 协议单/捐赠单
API-->>FE: 返回含 protocolNo / donationNo
FE->>U: 成功提示,列表刷新
为何选方案 B
- 用户取消新增不产生跳号
- 并发安全:序号仅在 create 事务内分配
- 跨日边界以提交时刻为准,无「打开弹窗到保存跨零点」歧义
发号算法
序号来源(同前缀、同租户、同日期)
在事务内查询以下三处 protocol_no / donation_no 的最大后缀,取 MAX + 1:
biz_collection_protocol.protocol_no— 匹配XBCC{date}%(仅采集发号时)biz_research_donation.donation_no— 匹配JZCC{date}%(仅捐赠发号时)biz_collection_protocol_no.protocol_no— 同前缀同日期(兼容历史号池数据,避免撞号)
过滤条件:tenant_no = 当前租户,del_flag = 0。
并发控制
- 使用
SELECT ... FOR UPDATE锁定序列表行,或 - 新建
biz_biz_no_sequence表:(tenant_no, prefix, biz_date, last_seq)唯一键,update 时last_seq = last_seq + 1
推荐序列表(清晰、可扩展):
CREATE TABLE biz_biz_no_sequence (
id VARCHAR(64) NOT NULL PRIMARY KEY,
tenant_no VARCHAR(64) NOT NULL,
prefix VARCHAR(16) NOT NULL COMMENT 'XBCC / JZCC',
biz_date CHAR(8) NOT NULL COMMENT 'YYYYMMDD',
last_seq INT NOT NULL DEFAULT 0 COMMENT '当日已分配最大序号',
-- 公共字段 tenant_no 已在业务字段,其余 public fields 按 database-conventions
...
UNIQUE KEY uk_tenant_prefix_date (tenant_no, prefix, biz_date)
);
发号:INSERT ... ON DUPLICATE KEY UPDATE last_seq = last_seq + 1,再读回 last_seq 拼完整编号。
唯一性兜底
生成后仍调用现有 assert_biz_no_available(采集与捐赠跨表校验),防止极端竞态。
前端 UI
新增态
- 协议号字段:
el-input,disabled,placeholder / 固定文案:「保存后自动生成」 - 移除:
el-select远程检索、protocolNoOptions、remoteSearchProtocolNos、占用/下发校验 - 表单校验:
protocolNo/donationNo新增时不必填(由后端生成) - 提交 payload:不传
protocolNo/protocolNoId/donationNo(或传空,后端忽略)
编辑态
- 协议号:
el-input只读展示已有值 - 提交 payload:不传协议号字段
创建成功
- 使用接口返回的
protocolNo/donationNo刷新列表(现有逻辑一般已满足)
后端 API 契约变更
采集协议单 POST create
- Before:
protocolNo必填,protocolNoId可选 - After:
protocolNo、protocolNoId均可选;若客户端传入非空protocolNo,忽略并服务端生成(或 400 拒绝客户端传号,推荐忽略以降低前端耦合) - Response:保持返回完整协议对象,含生成的
protocolNo
科研捐赠 POST create
- Before:
donationNo必填 - After:
donationNo可选,服务端生成 - Response:含
donationNo
PUT update
- 若 body 含
protocolNo/donationNo且与库中不一致 → 400「协议号不可修改」 - 或不接受该字段(Pydantic 从 UpdateBody 移除)
特殊场景
| 场景 | 行为 |
|---|---|
| 协议转科研 / 转存储 | 保留原号,不走新发号(现有 protocol_donation_convert 逻辑不变) |
| 历史数据 | 旧号格式不受影响;新规则仅作用于新 create |
| 号池 Tab(采集协议号管理) | 短期保留,与新自动发号并行;新单不再依赖下拉选号 |
protocol_no_id |
新单可置 NULL;若需审计可发号时同步写入号池并标记已使用 |
| 单日超 999999 条 | 返回业务错误「当日序号已用尽」 |
测试要点
- 同租户同日前后 create 两条采集单 →
...000001、...000002 - 同租户同日采集 + 捐赠 → XBCC 与 JZCC 序号各自从 1 起
- 并发 10 次 create → 无重复号
- 编辑提交不改号;强行 API 改号 → 400
- 转科研/转存储后编号与源单一致
不在本次范围
- 改造「采集协议号」Tab 的批量生成/下发流程
- 历史协议单批量洗号
- 小程序端(若存在同类选号逻辑,另开任务)
实现文件清单(预估)
后端
docs/sql/biz_biz_no_sequence.sql(序列表 DDL)backend/app/crud/biz_no_generator.py(新建)backend/app/crud/collection_protocol.pybackend/app/crud/research_donation.pybackend/app/api/collection_protocol.pybackend/app/api/research_donation.pybackend/app/models/models.py(序列表 Model,可选)
前端
web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vueweb-admin/src/views/research-donation/list/DonationFormDialog.vueweb-admin/src/types/api.d.ts(CreateBody 字段可选化)