Files
zkxw/docs/superpowers/specs/2026-10-07-sample-freight-batch-design.md
T
zlwuandCursor a6138d60bc docs: add sample freight batch design for shared transport cost
Capture the agreed approach: freight batch entity linked at sample level from protocol forms, dictionary-backed transport modes, internal cost only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 23:09:40 +08:00

260 lines
11 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.
# 样本运费批次 — 实现设计(方案 1)
> 日期:2026-10-07
> 状态:已确认(2026-10-07)
> 关联:细胞存储 / 科研捐赠「采集协议单」样本及运输信息;内部成本登记(不向客户收取)
---
## 1. 背景
### 1.1 现状
细胞存储与科研捐赠协议单共用 `SampleTransportFormSection`:
- 运输挂在**每个样本**上(`biz_protocol_sample_transport` / `biz_research_donation_sample_transport`)。
- 运输类型为固定枚举:`kdys`(快递运输)/ `ryxd`(人员携带)。
- 快递场景下人工填写快递公司、单号、目的站点、**样本级快递费用**、预计到达等。
- 批量「配置运输信息」只能把同一套信息复制到**当前协议单内**勾选样本,无法表达「多协议单共用一笔运费、只收一次」。
### 1.2 客户诉求
- **运输方式**:默认「顺丰快递」「专线车」;支持填写其他方式;若字典中不存在则自动加入字典,后续可选。
- **运费**:单位元;可对应多个样本(含跨多个协议单),例如同一专线车只登记一次费用。
- 本阶段**只登记内部成本**,不向客户收取,不进合同金额 / 缴费。
- 旧物流细节(单号、站点等)本阶段不要;后续再扩展。
### 1.3 已确认决策
| 项 | 结论 |
|----|------|
| 录入时机 | 录采集协议单时填写(已知会与其他单共用) |
| 关联粒度 | 样本级(可跨协议;单内也可仅部分样本) |
| 与旧运输 | 替换样本快递费用与旧运输录入;单号/站点等本阶段不做 |
| 费用性质 | 仅内部成本登记 |
| 架构 | 运费批次实体 + 协议单内新建/关联(方案 1);暂不做独立运费中心 |
---
## 2. 目标与非目标
### 2.1 目标
- 新建「运费批次」:运输方式(字典)+ 运费金额;多样本可关联同一批次。
- 支持跨细胞存储 / 科研捐赠协议单的样本共用一笔运费。
- 协议单 Tab「样本及运输信息」提供「配置运费」:新建 / 关联已有 / 修改方式与金额 / 取消关联。
- 运输方式字典预置「顺丰快递」「专线车」;手输不存在项时自动写入字典。
- 列表与查看弹窗展示运输方式 + 运费(及共用信息);合同/缴费逻辑不读运费。
### 2.2 非目标
- 独立运费菜单/业务中心(后续可加轻量查询列表)。
- 运费计入合同金额、缴费、向客户分摊或开票。
- 快递单号、目的站点、预计到达、携带人等物流字段。
- 旧运输记录自动迁移为运费批次。
- 小程序端改造。
- 物理删除旧运输表(保留兼容历史只读数据)。
---
## 3. 数据模型
### 3.1 运费批次主表 `biz_sample_freight`
| 字段 | 说明 |
|------|------|
| `id` | UUID PK |
| `transport_mode` | 运输方式字典值(`sample_transport_mode`) |
| `freight_amount` | 运费(元),`Numeric(18,2)`,≥ 0 |
| 公共字段 | `tenant_no`、`order_num`、`remark`、`create_by`、`create_dept_id`、`create_time`、`update_by`、`update_time`、`del_flag` |
字段顺序遵循 database-conventions:`id` → 业务字段 → 公共字段。
### 3.2 样本关联表 `biz_sample_freight_item`
| 字段 | 说明 |
|------|------|
| `id` | UUID PK |
| `freight_id` | 运费批次 ID |
| `sample_source` | `cell_storage` \| `research_donation` |
| `sample_id` | 样本主键 |
| `sample_code` | 样本编码快照(可选,便于展示) |
| `protocol_id` | 所属协议单 ID(便于反查) |
| 公共字段 | 同上 |
**约束(业务层强制,建议唯一索引):**
同一租户下,`(sample_source, sample_id)` 且 `del_flag=0` 至多对应一条关联(一个样本最多挂一笔运费)。
### 3.3 字典 `sample_transport_mode`
- 预置两项:
| dict_value | dict_label |
|------------|------------|
| `sfkd` | 顺丰快递 |
| `zxc` | 专线车 |
- 创建/更新运费时:前端可传 `transportMode`(已有 value)或手输文案。后端解析顺序:
1. 按 `dict_value` 命中 → 使用该项;
2. 否则按 `dict_label` trim 后精确匹配 → 使用已有项;
3. 否则自动新增:`dict_label` = trim 后文案,`dict_value` = 同文案(若 value 冲突则加短后缀保证唯一),再保存运费。
- 仅当前租户自动新增;不做跨租户共享。
### 3.4 与旧运输表
- `biz_protocol_sample_transport`、`biz_research_donation_sample_transport`:**保留不删**。
- 本阶段协议单保存路径**不再写入**旧运输明细。
- 历史数据不自动迁移;需补录时人工「配置运费」。
- 下游若仍读旧运输字段:无数据则展示空,不阻断主流程;物流扩展时再对齐。
---
## 4. 协议单交互
细胞存储 `ProtocolFormDialog` / `ProtocolViewDialog` 与科研捐赠 `DonationFormDialog` / `DonationViewDialog` 行为一致;公共组件由「配置运输信息」改为「配置运费」(可演进现有 `SampleTransportFormSection`)。
### 4.1 列表
- 移除列:运输类型、目的站点、预计到达、快递/携带摘要。
- 新增:运输方式、运费(元);共用时显示同一金额,并标注共用(如关联样本数或「共用」)。
- 未关联运费显示「—」。
### 4.2 工具栏
1. **添加样本**(不变)。
2. **配置运费**:须先勾选样本,否则禁用。
3. **取消运费关联**:对勾选且已关联的样本解除关联;批次下若无剩余样本则软删批次。
### 4.3 「配置运费」弹窗
**模式 A · 新建运费**
- 运输方式:可搜索下拉,允许手输;提交时字典无则自动新增。
- 运费(元):必填,≥ 0。
- 确定:创建批次并挂上当前勾选样本。
**模式 B · 关联已有运费**
- 分页检索近期批次:运输方式、金额、已关联样本数、创建时间。
- 确定:将勾选样本挂到该批次;若样本已有运费,提示将替换原关联后执行。
默认「新建」;可切换「关联已有」。
### 4.4 编辑与跨单
- 勾选样本若同属一批次,可在弹窗中改运输方式/金额;保存前提示「将同步 N 个关联样本」。
- 协议单 B 可通过「关联已有」挂到协议单 A 创建的批次。
- 查看态仅展示运输方式 + 运费(及共用信息)。
### 4.5 样本价格(细胞存储)
- 样本「原价格 / 优惠价格」与合同金额联动逻辑保持不变。
- 运费与合同金额、缴费**无联动**。
---
## 5. 接口与保存规则
### 5.1 运费 API(前缀 `/api/admin`)
| 方法 | 路径 | 用途 |
|------|------|------|
| GET | `/sample-freights` | 分页检索(关联已有) |
| GET | `/sample-freights/{id}` | 详情(含关联样本:来源、协议、编码) |
| POST | `/sample-freights` | 新建批次并挂样本 |
| PUT | `/sample-freights/{id}` | 更新方式/金额 |
| POST | `/sample-freights/{id}/link` | 追加关联 |
| POST | `/sample-freights/{id}/unlink` | 取消关联;无样本则软删批次 |
请求/响应字段使用 camelCase(`transportMode`、`freightAmount`、`sampleSource`、`sampleId`、`protocolId`、`sampleCode`、`linkedSampleCount` 等)。
创建/更新时执行运输方式字典自动入库。
### 5.2 与协议单保存
**已落库样本**(编辑已有协议,或协议已保存过):
- 「配置运费」确定后**立即**调用运费 API。
- 协议单保存不再嵌套旧 `transport` 整包写入。
**未落库草稿样本**(新建协议未保存):
- 前端用 `freightDraft.clientKey` 让多个草稿样本共用同一笔草稿运费。
- 协议单创建/更新 body 中样本可带:
- `freightId`:关联已有批次;或
- `freightDraft: { clientKey, transportMode, freightAmount }`:同一 `clientKey` 只创建一笔批次再挂样本。
- 保存成功后详情返回真实 `freightId`;之后改价走独立 API。
### 5.3 校验
- `transportMode`、`freightAmount` 必填;金额 ≥ 0。
- 同一租户样本不可同时挂两笔运费;重新配置 = 解旧链再挂新链(须提示)。
- 全部查询/写入按当前用户 `tenant_no` 隔离,并套数据权限(与协议样本一致)。
### 5.4 协议详情 enrichment
样本返回例如:
```json
"freight": {
"id": "...",
"transportMode": "zxc",
"transportModeLabel": "专线车",
"freightAmount": 800,
"linkedSampleCount": 3
}
```
### 5.5 历史数据
- 旧运输只读保留,不迁移。
- 新编辑以运费批次为准。
---
## 6. 前端改造要点
| 位置 | 改动 |
|------|------|
| `sample-transport-form-section` | 改为运费配置(或拆出 `SampleFreightFormSection`);列表列与弹窗按 §4 |
| `ProtocolFormDialog` / `DonationFormDialog` | 接入运费草稿与保存 payload;去掉对旧 transport 必填依赖 |
| `ProtocolViewDialog` / `DonationViewDialog` | 样本运输区改为方式 + 运费 |
| API 模块 | 新增 `sample-freight.ts`(或等价) |
| 字典 | 初始化 `sample_transport_mode`;下拉允许创建 |
UI 遵循 Fresh Mint / 现有协议弹窗约定:`label-position="top"`、`dialog-form-layout`、无 backdrop blur、按钮带图标。
---
## 7. 风险与对策
| 风险 | 对策 |
|------|------|
| 新建协议样本无服务端 ID | `freightDraft.clientKey`,协议保存时建批次 |
| 跨单改价影响他人样本 | 改价前提示同步关联样本数 |
| 字典手输脏数据/重复 | 租户内按 trim 后的 label 去重;实现阶段可再加强规范化 |
| 下游仍读旧运输 | 空值展示,不阻断;后续扩展再对齐 |
| 历史仅有旧运输 | 不迁移;人工补录运费 |
---
## 8. 验收标准
1. 预置「顺丰快递」「专线车」可选;手输新方式保存后,再次打开下拉可选到。
2. 同一协议单内多样本可共用一笔运费,金额只录一次。
3. 不同协议单(含细胞存储与科研捐赠)的样本可关联同一笔运费。
4. 修改批次金额/方式后,所有关联样本展示一致。
5. 取消关联后样本运费为空;批次无样本后软删。
6. 合同金额、缴费明细不因运费变化。
7. 新保存协议单不再依赖旧运输明细写入;查看弹窗不再展示单号/站点等旧字段。
---
## 9. 实现顺序(建议)
1. SQL:字典类型/数据 + `biz_sample_freight` + `biz_sample_freight_item`(及唯一索引)。
2. 后端:model / crud / api;字典自动入库;协议详情 enrichment;协议保存处理 `freightId` / `freightDraft`,停止写旧运输。
3. 前端:API + 协议单公共运费组件 + 查看态。
4. 测试:共用、跨来源关联、草稿 clientKey、字典自动新增、合同金额不受影响。
5. 按仓库约定增量更新 graphify(backend / web-admin)。