日期:2026-05-24 状态:待审阅 模块:中试服务管理 参考页面:
system/order/index.vue(复杂列表)、cell-storage/payment-confirm/index.vue(财务确认)
渠道商(如医院、合作机构)通过签订渠道协议,以协议价格购买细胞产品服务。渠道服务购买订单支持两种模式:
后续中试服务单会从渠道产品份数库存中扣减。
创建充值(待确认 dqr) → 财务确认(已确认 yqr, 余额+) → 发起退费(退费确认中 tfqrz) → 财务确认退费(已退费 ytf, 余额-)
| 状态 | 编码 | 操作角色 | 余额影响 |
|---|---|---|---|
| 待确认 | dqr |
采集人员创建 | 无 |
| 已确认 | yqr |
财务确认 | +prepay_amount |
| 退费确认中 | tfqrz |
采集人员发起退费 | 无 |
| 已退费 | ytf |
财务确认退费 | -refund_amount |
创建购买(生效中 sxz, 余额-, 份数+) → 作废(已作废 yzf, 余额+, 份数-)
| 状态 | 编码 | 操作角色 | 余额影响 | 份数影响 |
|---|---|---|---|---|
| 生效中 | sxz |
采集人员创建时直接扣减 | -purchase_total_amount | +各产品 buy_qty |
| 已作废 | yzf |
采集人员/审核人员作废 | +purchase_total_amount | -各产品 buy_qty |
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) | 退费确认备注 | ✅ | — |
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。
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='渠道账户余额';
业务规则:
balance_amount 实时更新,不可为负biz_channel_service_order.balance_before / balance_after 追溯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='渠道产品份数库存';
业务规则:
total_qty += buy_qty, remaining_qty += buy_qtytotal_qty -= buy_qty, remaining_qty -= buy_qty(需校验 remaining_qty >= buy_qty,防止已使用部分被退回)used_qty += qty, remaining_qty -= qty(本次不实现,但预留字段)biz_channel_service_order_finance_confirm保留不删(当前无数据)。本次不使用,财务确认字段已放入主表。
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 字段已在主表)
在 backend/app/models/models.py 中新增:
BizChannelAccount — 对应 biz_channel_account 表BizChannelProductQuota — 对应 biz_channel_product_quota 表BizChannelServiceOrder — 扩展现有模型(如果已有则更新字段),对应 biz_channel_service_order + 新增字段BizChannelServiceOrderProduct — 对应 biz_channel_service_order_product(如果已有则保持)在 backend/app/api/pilot_service.py 中扩展。前缀 /web/admin/。
| 方法 | 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 状态可删,软删除) | 中试服务 |
| 方法 | 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 | 作废购买订单(余额+, 份数-) | 中试服务 |
| 方法 | URL | 说明 |
|---|---|---|
| GET | /channels/{channelId}/account |
获取渠道账户余额(不存在则返回 balance=0) |
| GET | /channels/{channelId}/product-quotas |
获取渠道产品份数列表 |
在 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 |
扣减渠道产品份数(作废时用) |
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'
在系统菜单管理中新增(中试服务管理 下):
| 菜单名称 | 路由路径 | 组件 | 说明 |
|---|---|---|---|
| 渠道服务购买订单 | /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 |
财务确认 |
参考页面: 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)agreement_status='sxz' 的协议)查看弹窗:
作废确认弹窗:
发起退费弹窗:
参考页面: cell-storage/payment-confirm/index.vue
Tab 1:待确认
order_status IN ('dqr', 'tfqrz') 的订单Tab 2:已确认
order_status IN ('yqr', 'ytf') 的订单在 pilot-service/channel/index.vue 的编辑弹窗或查看弹窗中新增「账户信息」Tab:
在 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' },
},
在 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
SELECT ... FOR UPDATE)或乐观锁,防止并发创建订单导致余额超扣。remaining_qty >= buy_qty。如果部分份数已被中试服务消耗(后续功能),则不允许作废或只能部分退回。apply_data_scope() 数据范围过滤。tenant_no 过滤。docs/web-admin-ui-spec-fresh-mint.md 的 Fresh Mint 色彩规范。order_status DEFAULT 'sxz',但充值订单初始状态为 dqr。后端 create 函数必须根据 order_type 显式设置 order_status,不依赖 DDL 默认值:order_type='yfk' → order_status='dqr',order_type='gmcp' → order_status='sxz'。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 取值。SELECT ... FOR UPDATE 悲观行锁。在创建购买订单和财务确认充值时,先锁定 biz_channel_account 行再更新余额,防止并发超扣。整个余额操作在一个数据库事务内完成。