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

11 KiB
Raw Blame History

样本运费批次 — 实现设计(方案 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

样本返回例如:

"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)。