2026-05-24-channel-service-order-design.md 23 KB

渠道服务购买订单 — 功能设计文档

日期:2026-05-24 状态:待审阅 模块:中试服务管理 参考页面:system/order/index.vue(复杂列表)、cell-storage/payment-confirm/index.vue(财务确认)


1. 业务背景

渠道商(如医院、合作机构)通过签订渠道协议,以协议价格购买细胞产品服务。渠道服务购买订单支持两种模式:

  1. 充值(预付款):渠道向账户充值金额,财务确认后余额增加。后续可用于购买产品。
  2. 购买产品:根据渠道协议价格购买细胞产品,直接从渠道账户余额扣减,同时增加产品份数库存。

后续中试服务单会从渠道产品份数库存中扣减。


2. 业务流程

2.1 充值订单 (order_type = yfk)

创建充值(待确认 dqr) → 财务确认(已确认 yqr, 余额+) → 发起退费(退费确认中 tfqrz) → 财务确认退费(已退费 ytf, 余额-)
状态 编码 操作角色 余额影响
待确认 dqr 采集人员创建 无
已确认 yqr 财务确认 +prepay_amount
退费确认中 tfqrz 采集人员发起退费 无
已退费 ytf 财务确认退费 -refund_amount

2.2 购买产品订单 (order_type = gmcp)

创建购买(生效中 sxz, 余额-, 份数+) → 作废(已作废 yzf, 余额+, 份数-)
状态 编码 操作角色 余额影响 份数影响
生效中 sxz 采集人员创建时直接扣减 -purchase_total_amount +各产品 buy_qty
已作废 yzf 采集人员/审核人员作废 +purchase_total_amount -各产品 buy_qty

3. 数据库设计

3.1 现有表扩展 — biz_channel_service_order

现有 DDL(docs/sql/zkxw.sql 行 827-847)基础上新增以下字段:

ALTER TABLE biz_channel_service_order
  ADD COLUMN channel_agreement_id VARCHAR(64) DEFAULT NULL COMMENT '渠道协议ID(购买订单必填,关联 biz_channel_agreement.id)',
  ADD COLUMN balance_before DECIMAL(18,2) DEFAULT NULL COMMENT '操作前余额快照',
  ADD COLUMN balance_after DECIMAL(18,2) DEFAULT NULL COMMENT '操作后余额快照',
  ADD COLUMN finance_confirm_time DATETIME DEFAULT NULL COMMENT '财务确认时间',
  ADD COLUMN finance_confirm_attachments VARCHAR(4000) DEFAULT NULL COMMENT '财务确认附件(逗号分隔URL)',
  ADD COLUMN finance_remark VARCHAR(512) DEFAULT NULL COMMENT '财务确认备注',
  ADD COLUMN refund_apply_by VARCHAR(64) DEFAULT NULL COMMENT '退费申请人ID',
  ADD COLUMN refund_apply_time DATETIME DEFAULT NULL COMMENT '退费申请时间',
  ADD COLUMN refund_amount DECIMAL(18,2) DEFAULT NULL COMMENT '退费金额',
  ADD COLUMN refund_remark VARCHAR(512) DEFAULT NULL COMMENT '退费备注',
  ADD COLUMN refund_attachments VARCHAR(4000) DEFAULT NULL COMMENT '退费附件(逗号分隔URL)',
  ADD COLUMN refund_finance_confirm_time DATETIME DEFAULT NULL COMMENT '退费财务确认时间',
  ADD COLUMN refund_finance_confirm_attachments VARCHAR(4000) DEFAULT NULL COMMENT '退费财务确认附件(逗号分隔URL)',
  ADD COLUMN refund_finance_remark VARCHAR(512) DEFAULT NULL COMMENT '退费财务确认备注';

设计决策: 财务确认字段直接放主表(1:1 关系),不再使用现有空表 biz_channel_service_order_finance_confirm。余额变动历史通过 balance_before / balance_after 可追溯。

完整字段清单(含原有 + 新增):

字段 类型 说明 充值 购买
id VARCHAR(64) PK 主键UUID ✅ ✅
channel_id VARCHAR(64) NOT NULL 渠道ID ✅ ✅
channel_name VARCHAR(200) 渠道名称冗余 ✅ ✅
dept_name VARCHAR(200) 科室/部门 ✅ ✅
order_type VARCHAR(16) NOT NULL yfk预付款 / gmcp购买产品 ✅ ✅
prepay_amount DECIMAL(18,2) 预付金额 ✅ —
purchase_total_amount DECIMAL(18,2) 购买总金额 — ✅
order_status VARCHAR(16) NOT NULL DEFAULT 'sxz' 订单状态 dqr/yqr/tfqrz/ytf sxz/yzf
channel_agreement_id VARCHAR(64) 渠道协议ID — ✅ 必填
balance_before DECIMAL(18,2) 操作前余额 财务确认时填 创建时填
balance_after DECIMAL(18,2) 操作后余额 财务确认时填 创建时填
finance_confirm_time DATETIME 财务确认时间 ✅ —
finance_confirm_attachments VARCHAR(4000) 财务确认附件 ✅ —
finance_remark VARCHAR(512) 财务确认备注 ✅ —
refund_apply_by VARCHAR(64) 退费申请人 ✅ —
refund_apply_time DATETIME 退费申请时间 ✅ —
refund_amount DECIMAL(18,2) 退费金额 ✅ —
refund_remark VARCHAR(512) 退费备注 ✅ —
refund_attachments VARCHAR(4000) 退费附件 ✅ —
refund_finance_confirm_time DATETIME 退费确认时间 ✅ —
refund_finance_confirm_attachments VARCHAR(4000) 退费确认附件 ✅ —
refund_finance_remark VARCHAR(512) 退费确认备注 ✅ —

3.2 现有表保持 — biz_channel_service_order_product

现有 DDL(行 888-909)无需改动。字段:service_order_id, project_code, project_name, cell_type, spec_text, agreement_unit_price, buy_qty, buy_amount。

3.3 新增表 — biz_channel_account(渠道账户余额)

CREATE TABLE biz_channel_account (
  id VARCHAR(64) NOT NULL COMMENT '主键ID',
  channel_id VARCHAR(64) NOT NULL COMMENT '渠道ID',
  balance_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当前余额(元)',
  tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
  order_num INT DEFAULT NULL COMMENT '排序号',
  remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
  create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人id',
  create_dept_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
  update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  del_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除',
  PRIMARY KEY (id),
  UNIQUE KEY uk_account_channel (channel_id, del_flag),
  KEY idx_ca_tenant (tenant_no, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道账户余额';

业务规则:

  • 一个渠道一条记录(UNIQUE KEY)
  • balance_amount 实时更新,不可为负
  • 余额变动历史通过 biz_channel_service_order.balance_before / balance_after 追溯
  • 首次充值/购买时自动创建账户记录

3.4 新增表 — biz_channel_product_quota(渠道产品份数库存)

CREATE TABLE biz_channel_product_quota (
  id VARCHAR(64) NOT NULL COMMENT '主键ID',
  channel_id VARCHAR(64) NOT NULL COMMENT '渠道ID',
  project_code VARCHAR(64) DEFAULT NULL COMMENT '项目编码',
  project_name VARCHAR(200) DEFAULT NULL COMMENT '项目名称',
  cell_type_code VARCHAR(64) DEFAULT NULL COMMENT '细胞类型编码',
  cell_type_name VARCHAR(200) DEFAULT NULL COMMENT '细胞类型名称',
  spec_text VARCHAR(200) DEFAULT NULL COMMENT '规格',
  total_qty DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '总购买份数',
  used_qty DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '已使用份数(中试服务扣减)',
  remaining_qty DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '剩余份数 = total_qty - used_qty',
  tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
  order_num INT DEFAULT NULL COMMENT '排序号',
  remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
  create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人id',
  create_dept_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
  update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  del_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除',
  PRIMARY KEY (id),
  UNIQUE KEY uk_quota_channel_product (channel_id, project_code, cell_type_code, spec_text, del_flag),
  KEY idx_cpq_tenant (tenant_no, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道产品份数库存';

业务规则:

  • 同一渠道同一产品规格只有一条库存记录(UNIQUE KEY)
  • 购买订单创建时:total_qty += buy_qty, remaining_qty += buy_qty
  • 购买订单作废时:total_qty -= buy_qty, remaining_qty -= buy_qty(需校验 remaining_qty >= buy_qty,防止已使用部分被退回)
  • 后续中试服务单扣减时:used_qty += qty, remaining_qty -= qty(本次不实现,但预留字段)

3.5 现有空表 — biz_channel_service_order_finance_confirm

保留不删(当前无数据)。本次不使用,财务确认字段已放入主表。

3.6 表关系图

biz_channel (渠道)
  ├── biz_channel_agreement (协议)
  │     └── biz_channel_agreement_product_price (协议产品价格)
  ├── biz_channel_account (账户余额) [NEW]
  ├── biz_channel_product_quota (产品份数库存) [NEW]
  └── biz_channel_service_order (服务购买订单) [EXTEND]
        ├── biz_channel_service_order_product (产品明细)
        └── (finance_confirm 字段已在主表)

4. 后端设计

4.1 SQLAlchemy 模型

在 backend/app/models/models.py 中新增:

  1. BizChannelAccount — 对应 biz_channel_account 表
  2. BizChannelProductQuota — 对应 biz_channel_product_quota 表
  3. BizChannelServiceOrder — 扩展现有模型(如果已有则更新字段),对应 biz_channel_service_order + 新增字段
  4. BizChannelServiceOrderProduct — 对应 biz_channel_service_order_product(如果已有则保持)

4.2 API 端点

在 backend/app/api/pilot_service.py 中扩展。前缀 /web/admin/。

4.2.1 订单 CRUD

方法 URL 说明 权限
GET /channel-service-orders 订单列表(支持 order_type, order_status, channel_id, keyword 筛选) 中试服务
GET /channel-service-orders/{id} 订单详情(含产品明细列表) 中试服务
POST /channel-service-orders 创建订单 中试服务
PUT /channel-service-orders/{id} 修改订单(仅 dqr/sxz 状态可改) 中试服务
DELETE /channel-service-orders/{id} 删除订单(仅 dqr 状态可删,软删除) 中试服务

4.2.2 状态流转操作

方法 URL 前置状态 目标状态 说明 操作角色
POST /channel-service-orders/{id}/confirm-receipt dqr yqr 财务确认充值(余额+) 财务
POST /channel-service-orders/{id}/submit-refund yqr tfqrz 发起退费申请 中试服务
POST /channel-service-orders/{id}/confirm-refund tfqrz ytf 财务确认退费(余额-) 财务
PUT /channel-service-orders/{id}/void sxz yzf 作废购买订单(余额+, 份数-) 中试服务

4.2.3 渠道账户 & 份数查询

方法 URL 说明
GET /channels/{channelId}/account 获取渠道账户余额(不存在则返回 balance=0)
GET /channels/{channelId}/product-quotas 获取渠道产品份数列表

4.3 CRUD 函数

在 backend/app/crud/pilot_service.py 中新增。遵循现有 _row_to_dict_* 模式。

核心函数:

函数名 说明
_row_to_dict_channel_service_order 订单行转字典(单一真相源)
list_channel_service_orders 列表查询(带筛选、分页、数据权限)
get_channel_service_order 获取单条(含产品明细)
create_channel_service_order 创建订单(充值直接创建;购买需扣余额+增份数)
update_channel_service_order 修改订单(校验状态、业务字段)
soft_delete_channel_service_order 软删除(仅 dqr 状态)
confirm_receipt 财务确认充值(dqr→yqr,余额+)
submit_refund 发起退费(yqr→tfqrz,记录退费信息)
confirm_refund 财务确认退费(tfqrz→ytf,余额-)
void_order 作废购买订单(sxz→yzf,余额+,份数-)
_get_or_create_channel_account 获取或创建渠道账户
_upsert_channel_product_quota 新增或更新渠道产品份数
_reduce_channel_product_quota 扣减渠道产品份数(作废时用)

4.4 核心业务逻辑

创建充值订单

1. 校验 channel_id 存在
2. 校验 prepay_amount > 0
3. 创建订单 (order_type=yfk, order_status=dqr)
4. 不动余额

创建购买订单

1. 校验 channel_id 存在
2. 校验 channel_agreement_id 存在且 agreement_status='sxz'
3. 从 biz_channel_agreement_product_price 加载协议价格
4. 校验所有产品明细的项目编码+细胞类型+规格在协议价格表中存在
5. 允许用户修改单价(agreement_unit_price 可与协议价不同)
6. 计算总金额 = sum(buy_qty * agreement_unit_price)
7. 获取渠道账户,校验 balance_amount >= purchase_total_amount
8. 扣减余额 (account.balance_amount -= total)
9. 记录 balance_before / balance_after
10. 创建订单 (order_type=gmcp, order_status=sxz) + 产品明细
11. 对每个产品明细,upsert biz_channel_product_quota (total_qty+=, remaining_qty+=)

财务确认充值

1. 校验 order_status == 'dqr'
2. 获取/创建渠道账户
3. account.balance_amount += prepay_amount
4. 记录 balance_before, balance_after, finance_confirm_time, finance_confirm_attachments, finance_remark
5. order_status = 'yqr'

发起退费

1. 校验 order_status == 'yqr'
2. 记录 refund_apply_by, refund_apply_time, refund_amount(=prepay_amount), refund_remark, refund_attachments
3. order_status = 'tfqrz'

财务确认退费

1. 校验 order_status == 'tfqrz'
2. account.balance_amount -= refund_amount
3. 记录 balance_before, balance_after, refund_finance_confirm_time, refund_finance_confirm_attachments, refund_finance_remark
4. order_status = 'ytf'

作废购买订单

1. 校验 order_status == 'sxz'
2. 查询产品明细列表
3. 对每个产品明细:
   a. 校验 remaining_qty >= buy_qty(防止已使用部分被退回)
   b. 如果 remaining_qty < buy_qty → 返回错误("产品 XXX 已使用 N 份,无法作废")
4. account.balance_amount += purchase_total_amount
5. 记录 balance_before, balance_after
6. 对每个产品明细:total_qty -= buy_qty, remaining_qty -= buy_qty
7. order_status = 'yzf'

5. 前端设计

5.1 菜单配置

在系统菜单管理中新增(中试服务管理 下):

菜单名称 路由路径 组件 说明
渠道服务购买订单 /pilot-service/channel-service-order pilot-service/channel-service-order/index.vue 订单管理
渠道服务购买订单(财务确认) /pilot-service/channel-service-order-finance pilot-service/channel-service-order-finance/index.vue 财务确认

5.2 订单管理页

参考页面: system/order/index.vue(复杂列表)

搜索条件:

  • 订单类型(下拉:充值/购买产品)
  • 订单状态(下拉:根据类型动态切换选项)
  • 渠道名称(模糊搜索)
  • 创建时间范围

列表列:

列 字段 宽度 说明
订单编号 id 后 6 位 min-width 120
渠道名称 channelName min-width 160
订单类型 orderType min-width 100 Tag: yfk=充值(info), gmcp=购买产品(primary)
订单金额 prepayAmount / purchaseTotalAmount min-width 120 根据 orderType 展示对应字段
订单状态 orderStatus min-width 100 Tag,颜色见 5.5
操作前余额 balanceBefore min-width 120
操作后余额 balanceAfter min-width 120
创建时间 createTime min-width 160
操作 — min-width 260, fixed="right" 按状态显示按钮

操作按钮规则:

状态 可用操作
dqr(待确认) 编辑、删除
yqr(已确认) 发起退费、查看
tfqrz(退费确认中) 查看
ytf(已退费) 查看
sxz(生效中) 查看、作废
yzf(已作废) 查看

新增/编辑弹窗:

  • 选择渠道(下拉搜索,fetchChannelList)
  • 订单类型(充值/购买产品,单选,新建后不可修改)
  • 充值模式字段: 预付金额(number input, > 0)
  • 购买模式字段:
    • 选择渠道协议(下拉,仅显示该渠道 agreement_status='sxz' 的协议)
    • 产品明细表格(动态行):
    • 「添加产品」按钮 → 弹窗选择协议产品价格 → 填入行
    • 每行:项目名称(只读)、细胞类型(只读)、规格(只读)、协议单价(可修改)、购买数量(必填)、购买金额(自动计算 = 单价 × 数量)
    • 可删除行
    • 底部汇总:合计金额 + 当前渠道余额(余额不足时红色提示)
  • 备注(textarea)

查看弹窗:

  • 展示订单基础信息 + 产品明细(只读表格)
  • 充值订单:展示财务确认信息(确认时间、附件、备注)、退费信息(如有)
  • 购买订单:展示余额变动信息

作废确认弹窗:

  • 展示订单信息和产品明细
  • 填写作废原因(textarea)
  • 确认作废

发起退费弹窗:

  • 展示充值金额
  • 退费金额(默认=充值金额,可修改但不能超过充值金额)
  • 退费备注(textarea)
  • 退费附件(上传)
  • 确认提交

5.3 财务确认页

参考页面: cell-storage/payment-confirm/index.vue

Tab 1:待确认

  • 列表显示所有 order_status IN ('dqr', 'tfqrz') 的订单
  • 操作:
    • dqr 订单 → 「确认收款」按钮 → 弹窗(只读订单信息 + 财务备注 textarea + 附件上传)
    • tfqrz 订单 → 「确认退费」按钮 → 弹窗(只读订单信息+退费信息 + 退费确认备注 + 退费确认附件)

Tab 2:已确认

  • 列表显示所有 order_status IN ('yqr', 'ytf') 的订单
  • 「查看详情」按钮 → 查看确认信息

5.4 渠道信息页扩展

在 pilot-service/channel/index.vue 的编辑弹窗或查看弹窗中新增「账户信息」Tab:

  • 展示当前余额
  • 展示产品份数库存列表(表格:项目名称、细胞类型、规格、总份数、已使用、剩余份数)

5.5 状态标签注册

在 web-admin/src/utils/status-tag-registry.ts 中新增:

serviceOrderType: {
  yfk: { label: '充值', tagType: 'info' },
  gmcp: { label: '购买产品', tagType: 'primary' },
},

serviceOrderStatus: {
  dqr: { label: '待确认', tagType: 'info' },
  yqr: { label: '已确认', tagType: 'success' },
  tfqrz: { label: '退费确认中', tagType: 'warning' },
  ytf: { label: '已退费', tagType: 'danger' },
  sxz: { label: '生效中', tagType: 'success' },
  yzf: { label: '已作废', tagType: 'danger' },
},

5.6 前端 API 封装

在 web-admin/src/api/admin.ts 中新增:

// 渠道服务购买订单
fetchChannelServiceOrderList(params)       // GET /channel-service-orders
fetchChannelServiceOrderDetail(id)         // GET /channel-service-orders/{id}
fetchCreateChannelServiceOrder(data)       // POST /channel-service-orders
fetchUpdateChannelServiceOrder(id, data)   // PUT /channel-service-orders/{id}
fetchDeleteChannelServiceOrder(id)         // DELETE /channel-service-orders/{id}

// 状态流转
fetchConfirmReceipt(id, data)              // POST /channel-service-orders/{id}/confirm-receipt
fetchSubmitRefund(id, data)                // POST /channel-service-orders/{id}/submit-refund
fetchConfirmRefund(id, data)               // POST /channel-service-orders/{id}/confirm-refund
fetchVoidChannelServiceOrder(id, data)     // PUT /channel-service-orders/{id}/void

// 渠道账户 & 份数
fetchChannelAccount(channelId)             // GET /channels/{channelId}/account
fetchChannelProductQuotas(channelId)       // GET /channels/{channelId}/product-quotas

6. 开发范围边界

本次实现 ✅

  1. 数据库:2 张新表 + 1 张表扩展字段
  2. 后端:4 个新 SQLAlchemy 模型 + CRUD 函数 + API 端点
  3. 前端:2 个新页面(订单管理 + 财务确认)+ 渠道信息页扩展(账户 Tab)
  4. 状态标签注册
  5. 菜单数据初始化 SQL

后续实现 ❌(不在本次范围)

  1. 中试服务单(biz_channel_pilot_service)的创建和产品份数扣减
  2. 中试服务单与渠道购买订单的关联
  3. 渠道账户对账报表
  4. 渠道协议授信额度校验(可后续增强)

7. 注意事项

  1. 并发安全: 余额扣减和份数更新需使用数据库行锁(SELECT ... FOR UPDATE)或乐观锁,防止并发创建订单导致余额超扣。
  2. 作废校验: 购买订单作废时,需校验产品份数的 remaining_qty >= buy_qty。如果部分份数已被中试服务消耗(后续功能),则不允许作废或只能部分退回。
  3. 数据权限: 所有查询需应用 apply_data_scope() 数据范围过滤。
  4. 租户隔离: 所有操作带 tenant_no 过滤。
  5. 操作日志: 建议参考采集协议单的模式,记录订单状态变更日志(可选,后续增强)。
  6. 前端参考页面: 新页面严格遵循 docs/web-admin-ui-spec-fresh-mint.md 的 Fresh Mint 色彩规范。
  7. order_status 默认值: 现有 DDL 中 order_status DEFAULT 'sxz',但充值订单初始状态为 dqr。后端 create 函数必须根据 order_type 显式设置 order_status,不依赖 DDL 默认值:order_type='yfk' → order_status='dqr',order_type='gmcp' → order_status='sxz'。
  8. cell_type 字段映射: biz_channel_service_order_product.cell_type 存储细胞类型名称(冗余文本),但 biz_channel_product_quota 使用 cell_type_code + cell_type_name 分开存储。创建份数库存时,cell_type_code 和 cell_type_name 从 biz_channel_agreement_product_price 协议产品价格表获取,不从订单明细的 cell_type 取值。
  9. 并发方案确定: 余额扣减使用 SELECT ... FOR UPDATE 悲观行锁。在创建购买订单和财务确认充值时,先锁定 biz_channel_account 行再更新余额,防止并发超扣。整个余额操作在一个数据库事务内完成。