提交
This commit is contained in:
1 parent
13e4c9530a
commit
575e63e7e8
62 files changed
+28759
-9636
No files matched your search
@@ -0,0 +1,633 @@
|
||||
# 渠道服务订单多次操作流水模型 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将渠道服务订单从单次操作模型改造为多次操作流水模型,支持多次充值、退款、购买产品、按份数退订。
|
||||
|
||||
**Architecture:** 订单主表(biz_channel_service_order)管汇总,新增操作流水表(biz_channel_service_order_txn)管每笔操作的明细和财务确认流程,产品持仓表(biz_channel_service_order_product)改造为追踪累计购买/退订/当前持有份数。
|
||||
|
||||
**Tech Stack:** Python 3.9 / FastAPI / SQLAlchemy / Pydantic v2 / MySQL 5.7 / Vue 3 / Element Plus / TypeScript
|
||||
|
||||
**Design Spec:** `docs/superpowers/specs/2026-05-24-channel-service-order-multi-txn-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
### 新增文件
|
||||
- `docs/sql/channel-service-order-txn-migration.sql` — 数据库迁移 SQL
|
||||
- `backend/app/models/channel_service_order_txn.py` — 流水 Model(或合并到 models.py)
|
||||
|
||||
### 修改文件
|
||||
- `backend/app/models/models.py` — 新增 BizChannelServiceOrderTxn model,改造 BizChannelServiceOrder/BizChannelServiceOrderProduct
|
||||
- `backend/app/crud/pilot_service.py` — 新增流水 CRUD + 改造订单 CRUD
|
||||
- `backend/app/api/pilot_service.py` — 新增流水 API + Pydantic schemas
|
||||
- `web-admin/src/api/admin.ts` — 新增流水相关 API 调用函数
|
||||
- `web-admin/src/views/pilot-service/channel-service-order/index.vue` — 订单管理页改造
|
||||
- `web-admin/src/views/pilot-service/channel-service-order-finance/index.vue` — 财务确认页改造
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 数据库迁移 — 流水表 + 主表改造 + 产品表改造
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/sql/channel-service-order-txn-migration.sql`
|
||||
|
||||
- [ ] **Step 1: 编写迁移 SQL**
|
||||
|
||||
```sql
|
||||
-- ============================================================
|
||||
-- 渠道服务订单:多次操作流水模型迁移
|
||||
-- ============================================================
|
||||
|
||||
-- 1. 新增操作流水表
|
||||
CREATE TABLE `biz_channel_service_order_txn` (
|
||||
`id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID',
|
||||
`service_order_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联订单ID',
|
||||
`txn_type` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'recharge/refund/purchase/unsubscribe',
|
||||
`amount` decimal(18,2) DEFAULT NULL COMMENT '本次操作金额',
|
||||
`txn_status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dsx' COMMENT 'dsx待生效 dqr待财务确认 yqr已确认 yzf已作废',
|
||||
`products_json` text COLLATE utf8mb4_unicode_ci COMMENT '产品明细JSON(purchase/unsubscribe类型)',
|
||||
`remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
|
||||
`finance_submit_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发起确认人ID',
|
||||
`finance_submit_time` datetime DEFAULT NULL COMMENT '发起确认时间',
|
||||
`finance_submit_remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发起确认备注',
|
||||
`finance_submit_attachments` text COLLATE utf8mb4_unicode_ci COMMENT '发起确认附件',
|
||||
`finance_confirm_time` datetime DEFAULT NULL COMMENT '财务确认时间',
|
||||
`finance_confirm_attachments` text COLLATE utf8mb4_unicode_ci COMMENT '财务确认附件',
|
||||
`finance_remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '财务确认备注',
|
||||
`balance_before` decimal(18,2) DEFAULT NULL COMMENT '操作前余额(yfk类型)',
|
||||
`balance_after` decimal(18,2) DEFAULT NULL COMMENT '操作后余额(yfk类型)',
|
||||
`tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
|
||||
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
|
||||
`remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
|
||||
`create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id',
|
||||
`create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(64) COLLATE utf8mb4_unicode_ci 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 '删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_txn_order` (`service_order_id`, `del_flag`),
|
||||
KEY `idx_txn_status` (`txn_status`, `tenant_no`, `del_flag`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道服务订单操作流水';
|
||||
|
||||
-- 2. 订单主表新增汇总字段
|
||||
ALTER TABLE `biz_channel_service_order`
|
||||
ADD COLUMN `total_recharge_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计充值总额' AFTER `channel_agreement_id`,
|
||||
ADD COLUMN `total_refund_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计退款总额' AFTER `total_recharge_amount`,
|
||||
ADD COLUMN `current_balance` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '当前余额' AFTER `total_refund_amount`;
|
||||
|
||||
-- 3. 产品持仓表新增字段
|
||||
ALTER TABLE `biz_channel_service_order_product`
|
||||
ADD COLUMN `total_buy_qty` decimal(18,4) NOT NULL DEFAULT 0.0000 COMMENT '累计购买数量' AFTER `buy_amount`,
|
||||
ADD COLUMN `total_unsubscribe_qty` decimal(18,4) NOT NULL DEFAULT 0.0000 COMMENT '累计退订数量' AFTER `total_buy_qty`,
|
||||
ADD COLUMN `current_qty` decimal(18,4) NOT NULL DEFAULT 0.0000 COMMENT '当前持有数量' AFTER `total_unsubscribe_qty`,
|
||||
ADD COLUMN `total_unsubscribe_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计退订金额' AFTER `current_qty`;
|
||||
|
||||
-- 4. 迁移现有订单数据到流水表(一次性,保留历史数据)
|
||||
-- 注意:仅迁移有操作记录的订单(有 finance_submit_time 或 refund_apply_time 的)
|
||||
INSERT INTO `biz_channel_service_order_txn` (
|
||||
`id`, `service_order_id`, `txn_type`, `amount`, `txn_status`,
|
||||
`remark`,
|
||||
`finance_submit_by`, `finance_submit_time`, `finance_submit_remark`, `finance_submit_attachments`,
|
||||
`finance_confirm_time`, `finance_confirm_attachments`, `finance_remark`,
|
||||
`balance_before`, `balance_after`,
|
||||
`tenant_no`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
UUID_SHORT() AS `id`,
|
||||
o.`id` AS `service_order_id`,
|
||||
'recharge' AS `txn_type`,
|
||||
o.`prepay_amount` AS `amount`,
|
||||
CASE
|
||||
WHEN o.`finance_confirm_time` IS NOT NULL THEN 'yqr'
|
||||
WHEN o.`finance_submit_time` IS NOT NULL THEN 'dqr'
|
||||
ELSE 'dsx'
|
||||
END AS `txn_status`,
|
||||
o.`remark`,
|
||||
o.`finance_submit_by`, o.`finance_submit_time`, o.`finance_submit_remark`, o.`finance_submit_attachments`,
|
||||
o.`finance_confirm_time`, o.`finance_confirm_attachments`, o.`finance_remark`,
|
||||
o.`balance_before`, o.`balance_after`,
|
||||
o.`tenant_no`, o.`create_by`, o.`create_dept_id`, o.`create_time`, o.`update_by`, o.`update_time`, o.`del_flag`
|
||||
FROM `biz_channel_service_order` o
|
||||
WHERE o.`finance_submit_time` IS NOT NULL OR o.`prepay_amount` IS NOT NULL;
|
||||
|
||||
-- 5. 迁移退款记录到流水表
|
||||
INSERT INTO `biz_channel_service_order_txn` (
|
||||
`id`, `service_order_id`, `txn_type`, `amount`, `txn_status`,
|
||||
`remark`,
|
||||
`finance_submit_by`, `finance_submit_time`, `finance_submit_remark`, `finance_submit_attachments`,
|
||||
`finance_confirm_time`, `finance_confirm_attachments`, `finance_remark`,
|
||||
`tenant_no`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
UUID_SHORT() AS `id`,
|
||||
o.`id` AS `service_order_id`,
|
||||
'refund' AS `txn_type`,
|
||||
o.`refund_amount` AS `amount`,
|
||||
CASE
|
||||
WHEN o.`refund_finance_confirm_time` IS NOT NULL THEN 'yqr'
|
||||
ELSE 'dqr'
|
||||
END AS `txn_status`,
|
||||
o.`refund_remark` AS `remark`,
|
||||
o.`refund_apply_by` AS `finance_submit_by`,
|
||||
o.`refund_apply_time` AS `finance_submit_time`,
|
||||
o.`refund_remark` AS `finance_submit_remark`,
|
||||
o.`refund_attachments` AS `finance_submit_attachments`,
|
||||
o.`refund_finance_confirm_time` AS `finance_confirm_time`,
|
||||
o.`refund_finance_confirm_attachments` AS `finance_confirm_attachments`,
|
||||
o.`refund_finance_remark` AS `finance_remark`,
|
||||
o.`tenant_no`, o.`refund_apply_by` AS `create_by`, o.`create_dept_id`,
|
||||
COALESCE(o.`refund_apply_time`, o.`update_time`) AS `create_time`,
|
||||
o.`update_by`, o.`update_time`, o.`del_flag`
|
||||
FROM `biz_channel_service_order` o
|
||||
WHERE o.`refund_amount` IS NOT NULL AND o.`refund_amount` > 0;
|
||||
|
||||
-- 6. 回填主表汇总字段
|
||||
UPDATE `biz_channel_service_order` o
|
||||
SET
|
||||
`total_recharge_amount` = COALESCE(o.`prepay_amount`, 0),
|
||||
`total_refund_amount` = COALESCE(o.`refund_amount`, 0),
|
||||
`current_balance` = COALESCE(o.`prepay_amount`, 0) - COALESCE(o.`refund_amount`, 0)
|
||||
WHERE o.`order_type` = 'yfk' AND o.`prepay_amount` IS NOT NULL;
|
||||
|
||||
-- 7. 回填产品持仓表的累计字段
|
||||
UPDATE `biz_channel_service_order_product` p
|
||||
SET
|
||||
`total_buy_qty` = COALESCE(p.`buy_qty`, 0),
|
||||
`current_qty` = COALESCE(p.`buy_qty`, 0);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在数据库执行迁移**
|
||||
|
||||
Run: 手动在 MySQL 中执行上述 SQL,或逐步执行确认无误。
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 后端 Model — 新增流水 Model + 改造主表/产品 Model
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/models/models.py` (line ~1762, BizChannelServiceOrder class)
|
||||
|
||||
- [ ] **Step 1: 在 models.py 中 BizChannelServiceOrder 类新增字段**
|
||||
|
||||
在 `BizChannelServiceOrder` 类的 `channel_agreement_id` 字段后面添加三个汇总字段:
|
||||
|
||||
```python
|
||||
total_recharge_amount = Column(Numeric(18, 2), nullable=False, default=0, comment="累计充值总额")
|
||||
total_refund_amount = Column(Numeric(18, 2), nullable=False, default=0, comment="累计退款总额")
|
||||
current_balance = Column(Numeric(18, 2), nullable=False, default=0, comment="当前余额")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在 BizChannelServiceOrderProduct 类新增字段**
|
||||
|
||||
在 `buy_amount` 字段后面添加:
|
||||
|
||||
```python
|
||||
total_buy_qty = Column(Numeric(18, 4), nullable=False, default=0, comment="累计购买数量")
|
||||
total_unsubscribe_qty = Column(Numeric(18, 4), nullable=False, default=0, comment="累计退订数量")
|
||||
current_qty = Column(Numeric(18, 4), nullable=False, default=0, comment="当前持有数量")
|
||||
total_unsubscribe_amount = Column(Numeric(18, 2), nullable=False, default=0, comment="累计退订金额")
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 在 BizChannelServiceOrderProduct 类后面新增 BizChannelServiceOrderTxn 类**
|
||||
|
||||
```python
|
||||
class BizChannelServiceOrderTxn(Base):
|
||||
"""渠道服务订单操作流水 biz_channel_service_order_txn"""
|
||||
|
||||
__tablename__ = "biz_channel_service_order_txn"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
service_order_id = Column(String(64), nullable=False, comment="关联订单ID")
|
||||
txn_type = Column(String(16), nullable=False, comment="recharge/refund/purchase/unsubscribe")
|
||||
amount = Column(Numeric(18, 2), nullable=True, comment="本次操作金额")
|
||||
txn_status = Column(String(16), nullable=False, default="dsx", comment="dsx待生效 dqr待财务确认 yqr已确认 yzf已作废")
|
||||
products_json = Column(Text, nullable=True, comment="产品明细JSON(purchase/unsubscribe类型)")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
finance_submit_by = Column(String(64), nullable=True, comment="发起确认人ID")
|
||||
finance_submit_time = Column(DateTime, nullable=True, comment="发起确认时间")
|
||||
finance_submit_remark = Column(String(512), nullable=True, comment="发起确认备注")
|
||||
finance_submit_attachments = Column(Text, nullable=True, comment="发起确认附件")
|
||||
finance_confirm_time = Column(DateTime, nullable=True, comment="财务确认时间")
|
||||
finance_confirm_attachments = Column(Text, nullable=True, comment="财务确认附件")
|
||||
finance_remark = Column(String(512), nullable=True, comment="财务确认备注")
|
||||
balance_before = Column(Numeric(18, 2), nullable=True, comment="操作前余额")
|
||||
balance_after = Column(Numeric(18, 2), nullable=True, comment="操作后余额")
|
||||
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark_col = Column("remark", String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
|
||||
```
|
||||
|
||||
注意:Model 中 `remark` 字段和表里的 `remark` 列名冲突(流水表有两个 remark 用途)—— 实际上流水表只有一个 `remark` 列。Model 中只保留一个 `remark` 即可,去掉上面的 `remark_col`,直接用 `remark`。
|
||||
|
||||
- [ ] **Step 4: 验证 import**
|
||||
|
||||
确保 `models.py` 文件头部已有 `from sqlalchemy import ... Column, String, Numeric, Text, DateTime, SmallInteger, Integer` 等导入。
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 后端 CRUD — 流水 CRUD + 订单 CRUD 改造
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/crud/pilot_service.py` (line ~2513+)
|
||||
|
||||
- [ ] **Step 1: 新增流水 _row_to_dict 函数**
|
||||
|
||||
在 `_row_to_dict_service_order_product` 函数后面添加:
|
||||
|
||||
```python
|
||||
def _row_to_dict_service_order_txn(row, db=None, tenant_no=None) -> Dict[str, Any]:
|
||||
tn = tenant_no or row.tenant_no
|
||||
d = {
|
||||
"id": row.id,
|
||||
"serviceOrderId": row.service_order_id or "",
|
||||
"txnType": row.txn_type or "",
|
||||
"amount": float(row.amount) if row.amount else 0,
|
||||
"txnStatus": row.txn_status or "",
|
||||
"productsJson": row.products_json or "",
|
||||
"remark": row.remark or "",
|
||||
"financeSubmitBy": row.finance_submit_by or "",
|
||||
"financeSubmitTime": row.finance_submit_time.strftime("%Y-%m-%d %H:%M:%S") if row.finance_submit_time else "",
|
||||
"financeSubmitRemark": row.finance_submit_remark or "",
|
||||
"financeSubmitAttachments": row.finance_submit_attachments or "",
|
||||
"financeSubmitAttachmentFiles": urls_to_attachment_list(row.finance_submit_attachments, db, tn) if db and row.finance_submit_attachments else [],
|
||||
"financeConfirmTime": row.finance_confirm_time.strftime("%Y-%m-%d %H:%M:%S") if row.finance_confirm_time else "",
|
||||
"financeConfirmAttachments": row.finance_confirm_attachments or "",
|
||||
"financeConfirmAttachmentFiles": urls_to_attachment_list(row.finance_confirm_attachments, db, tn) if db and row.finance_confirm_attachments else [],
|
||||
"financeRemark": row.finance_remark or "",
|
||||
"balanceBefore": float(row.balance_before) if row.balance_before else 0,
|
||||
"balanceAfter": float(row.balance_after) if row.balance_after else 0,
|
||||
"tenantNo": row.tenant_no or "",
|
||||
"createBy": row.create_by or "",
|
||||
"createTime": row.create_time.strftime("%Y-%m-%d %H:%M:%S") if row.create_time else "",
|
||||
"updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "",
|
||||
}
|
||||
if row.products_json:
|
||||
try:
|
||||
d["products"] = json.loads(row.products_json)
|
||||
except Exception:
|
||||
d["products"] = []
|
||||
else:
|
||||
d["products"] = []
|
||||
return d
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 改造 _row_to_dict_service_order,新增汇总字段、移除旧字段**
|
||||
|
||||
将 `prepayAmount`、`purchaseTotalAmount` 等保留但标记为兼容字段(从汇总计算),新增:
|
||||
- `totalRechargeAmount`
|
||||
- `totalRefundAmount`
|
||||
- `currentBalance`
|
||||
- `transactions`(列表,由调用方填充)
|
||||
- `products`(列表,由调用方填充)
|
||||
|
||||
同时在详情查询时附带流水列表和产品持仓列表。
|
||||
|
||||
- [ ] **Step 3: 新增流水 CRUD 函数**
|
||||
|
||||
```python
|
||||
def list_service_order_txns(db, *, service_order_id=None, txn_status=None, txn_type=None, page=1, page_size=20, tenant_no=None):
|
||||
"""查询操作流水列表"""
|
||||
...
|
||||
|
||||
def get_service_order_txn(db, txn_id, *, tenant_no=None):
|
||||
"""获取单条流水"""
|
||||
...
|
||||
|
||||
def create_recharge_txn(db, order_id, *, amount, remark=None, tenant_no=None, user_id=None):
|
||||
"""创建充值流水(yfk订单)"""
|
||||
...
|
||||
|
||||
def create_refund_txn(db, order_id, *, amount, remark=None, tenant_no=None, user_id=None):
|
||||
"""创建退款流水(yfk订单),校验 amount <= current_balance"""
|
||||
...
|
||||
|
||||
def create_purchase_txn(db, order_id, *, products, remark=None, tenant_no=None, user_id=None):
|
||||
"""创建购买产品流水(gmcp订单),同时创建/更新产品持仓"""
|
||||
...
|
||||
|
||||
def create_unsubscribe_txn(db, order_id, *, products, remark=None, tenant_no=None, user_id=None):
|
||||
"""创建退订产品流水(gmcp订单),校验退订份数 <= current_qty"""
|
||||
...
|
||||
|
||||
def submit_txn_finance(db, txn_id, *, finance_submit_remark=None, finance_submit_attachments=None, tenant_no=None, user_id=None):
|
||||
"""发起流水财务确认:dsx → dqr"""
|
||||
...
|
||||
|
||||
def confirm_txn(db, txn_id, *, finance_remark=None, finance_confirm_attachments=None, tenant_no=None, user_id=None):
|
||||
"""财务确认流水:dqr → yqr,更新主表汇总和产品持仓"""
|
||||
...
|
||||
|
||||
def void_txn(db, txn_id, *, tenant_no=None, user_id=None):
|
||||
"""作废流水:dsx → yzf"""
|
||||
...
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 改造 _row_to_dict_service_order_product,新增持仓字段**
|
||||
|
||||
在返回字典中新增:
|
||||
```python
|
||||
"totalBuyQty": float(row.total_buy_qty) if row.total_buy_qty else 0,
|
||||
"totalUnsubscribeQty": float(row.total_unsubscribe_qty) if row.total_unsubscribe_qty else 0,
|
||||
"currentQty": float(row.current_qty) if row.current_qty else 0,
|
||||
"totalUnsubscribeAmount": float(row.total_unsubscribe_amount) if row.total_unsubscribe_amount else 0,
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 后端 API — 流水接口 + Pydantic Schemas
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/api/pilot_service.py` (line ~1552+)
|
||||
|
||||
- [ ] **Step 1: 新增 Pydantic Schemas**
|
||||
|
||||
在现有 `RefundConfirmBody` 后面添加:
|
||||
|
||||
```python
|
||||
class TxnRechargeBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
amount: float = Field(..., gt=0, alias="amount")
|
||||
remark: Optional[str] = Field(None, alias="remark")
|
||||
|
||||
class TxnRefundBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
amount: float = Field(..., gt=0, alias="amount")
|
||||
remark: Optional[str] = Field(None, alias="remark")
|
||||
|
||||
class TxnProductItem(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
project_code: Optional[str] = Field(None, alias="projectCode")
|
||||
project_name: Optional[str] = Field(None, alias="projectName")
|
||||
cell_type: Optional[str] = Field(None, alias="cellType")
|
||||
spec_text: Optional[str] = Field(None, alias="specText")
|
||||
agreement_unit_price: Optional[float] = Field(None, alias="agreementUnitPrice")
|
||||
qty: Optional[float] = Field(None, alias="qty")
|
||||
amount: Optional[float] = Field(None, alias="amount")
|
||||
|
||||
class TxnPurchaseBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
products: List[TxnProductItem] = Field(..., alias="products")
|
||||
remark: Optional[str] = Field(None, alias="remark")
|
||||
|
||||
class TxnUnsubscribeBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
products: List[TxnProductItem] = Field(..., alias="products")
|
||||
remark: Optional[str] = Field(None, alias="remark")
|
||||
|
||||
class TxnFinanceSubmitBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
finance_submit_remark: Optional[str] = Field(None, alias="financeSubmitRemark")
|
||||
finance_submit_attachments: Optional[str] = Field(None, alias="financeSubmitAttachments")
|
||||
|
||||
class TxnFinanceConfirmBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
finance_remark: Optional[str] = Field(None, alias="financeRemark")
|
||||
finance_confirm_attachments: Optional[str] = Field(None, alias="financeConfirmAttachments")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 新增流水 API 路由**
|
||||
|
||||
```python
|
||||
@router.get("/channel-service-order-txns")
|
||||
async def list_txns(...) # 列表查询,支持 status/type/order_id 筛选
|
||||
|
||||
@router.get("/channel-service-order-txns/{txn_id}")
|
||||
async def get_txn_detail(...) # 流水详情
|
||||
|
||||
@router.post("/channel-service-orders/{order_id}/recharge")
|
||||
async def create_recharge(...) # 创建充值流水
|
||||
|
||||
@router.post("/channel-service-orders/{order_id}/refund")
|
||||
async def create_refund(...) # 创建退款流水
|
||||
|
||||
@router.post("/channel-service-orders/{order_id}/purchase")
|
||||
async def create_purchase(...) # 创建购买流水
|
||||
|
||||
@router.post("/channel-service-orders/{order_id}/unsubscribe")
|
||||
async def create_unsubscribe(...) # 创建退订流水
|
||||
|
||||
@router.post("/channel-service-order-txns/{txn_id}/submit-finance")
|
||||
async def submit_txn_finance(...) # 发起财务确认
|
||||
|
||||
@router.post("/channel-service-order-txns/{txn_id}/confirm")
|
||||
async def confirm_txn(...) # 财务确认
|
||||
|
||||
@router.put("/channel-service-order-txns/{txn_id}/void")
|
||||
async def void_txn(...) # 作废流水
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 改造订单详情 API**
|
||||
|
||||
修改 `get_channel_service_order_detail` 接口,在返回数据中附带:
|
||||
- `transactions`: 该订单的所有流水列表
|
||||
- `products`: 该订单的产品持仓列表(gmcp)
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 前端 API — 新增流水相关调用函数
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/api/admin.ts` (line ~1900+)
|
||||
|
||||
- [ ] **Step 1: 新增流水 API 函数**
|
||||
|
||||
在现有 `fetchVoidServiceOrder` 后面添加:
|
||||
|
||||
```typescript
|
||||
// ========== 渠道服务订单操作流水 ==========
|
||||
|
||||
/** 流水列表 */
|
||||
export function fetchServiceOrderTxnList(params?: Record<string, any>) {
|
||||
return request.get({ url: '/web/admin/channel-service-order-txns', params })
|
||||
}
|
||||
|
||||
/** 流水详情 */
|
||||
export function fetchServiceOrderTxnDetail(txnId: string) {
|
||||
return request.get({ url: `/web/admin/channel-service-order-txns/${txnId}` })
|
||||
}
|
||||
|
||||
/** 发起充值 */
|
||||
export function fetchCreateRechargeTxn(orderId: string, data: Record<string, any>) {
|
||||
return request.post({ url: `/web/admin/channel-service-orders/${orderId}/recharge`, params: data })
|
||||
}
|
||||
|
||||
/** 申请退款 */
|
||||
export function fetchCreateRefundTxn(orderId: string, data: Record<string, any>) {
|
||||
return request.post({ url: `/web/admin/channel-service-orders/${orderId}/refund`, params: data })
|
||||
}
|
||||
|
||||
/** 购买产品 */
|
||||
export function fetchCreatePurchaseTxn(orderId: string, data: Record<string, any>) {
|
||||
return request.post({ url: `/web/admin/channel-service-orders/${orderId}/purchase`, params: data })
|
||||
}
|
||||
|
||||
/** 退订产品 */
|
||||
export function fetchCreateUnsubscribeTxn(orderId: string, data: Record<string, any>) {
|
||||
return request.post({ url: `/web/admin/channel-service-orders/${orderId}/unsubscribe`, params: data })
|
||||
}
|
||||
|
||||
/** 流水发起财务确认 */
|
||||
export function fetchSubmitTxnFinance(txnId: string, data: Record<string, any>) {
|
||||
return request.post({ url: `/web/admin/channel-service-order-txns/${txnId}/submit-finance`, params: data })
|
||||
}
|
||||
|
||||
/** 流水财务确认 */
|
||||
export function fetchConfirmTxn(txnId: string, data: Record<string, any>) {
|
||||
return request.post({ url: `/web/admin/channel-service-order-txns/${txnId}/confirm`, params: data })
|
||||
}
|
||||
|
||||
/** 作废流水 */
|
||||
export function fetchVoidTxn(txnId: string) {
|
||||
return request.put({ url: `/web/admin/channel-service-order-txns/${txnId}/void` })
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 保留旧 API 函数但标记废弃**
|
||||
|
||||
不删除 `fetchSubmitServiceOrderFinanceConfirm`、`fetchConfirmServiceOrderReceipt`、`fetchSubmitServiceOrderRefund`、`fetchConfirmServiceOrderRefund`,保持向后兼容,但前端页面不再调用。
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 前端订单管理页改造
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pilot-service/channel-service-order/index.vue`
|
||||
|
||||
这是改动最大的文件,核心变更:
|
||||
|
||||
- [ ] **Step 1: 更新 import**
|
||||
|
||||
新增导入流水相关 API 函数:`fetchCreateRechargeTxn`, `fetchCreateRefundTxn`, `fetchCreatePurchaseTxn`, `fetchCreateUnsubscribeTxn`, `fetchSubmitTxnFinance`,移除旧的 `fetchSubmitServiceOrderFinanceConfirm`, `fetchSubmitServiceOrderRefund` 调用。
|
||||
|
||||
- [ ] **Step 2: 更新操作按钮**
|
||||
|
||||
将操作列的按钮替换为:
|
||||
- yfk + dsx → 编辑、删除、**发起充值**
|
||||
- yfk + sxz → **发起充值**、**申请退款**
|
||||
- gmcp + dsx → 编辑、删除、**购买产品**
|
||||
- gmcp + sxz → **购买产品**、**退订产品**
|
||||
- 任意 + yzf → 仅查看
|
||||
|
||||
- [ ] **Step 3: 改造详情弹窗(SystemViewDialog)**
|
||||
|
||||
在 viewSections computed 中:
|
||||
- 基本信息section 新增 `currentBalance`(yfk)或产品持仓汇总(gmcp)
|
||||
- 移除旧的"发起财务确认""财务确认信息""退款信息"平铺 sections
|
||||
- 新增"操作记录"section,用 inner-table 展示流水列表(类型Tag、金额、状态Tag、时间)
|
||||
- gmcp 类型新增"产品持仓"section,显示各产品当前持有份数
|
||||
|
||||
- [ ] **Step 4: 替换弹窗**
|
||||
|
||||
移除旧的"发起财务确认"弹窗和"申请退款"弹窗,新增:
|
||||
- **发起充值弹窗**:金额输入、备注、凭证上传
|
||||
- **申请退款弹窗**:金额输入(max=当前余额)、备注、凭证上传
|
||||
- **购买产品弹窗**:选项目/规格、输入份数、自动算金额(类似现有的新增订单弹窗中产品明细部分)
|
||||
- **退订产品弹窗**:选已持有项目(下拉,显示当前份数)、输入退订份数(≤持有量)、自动算退款金额
|
||||
|
||||
- [ ] **Step 5: 操作按钮点击后,可选择是否立即发起财务确认**
|
||||
|
||||
创建流水后,弹出提示"是否立即发起财务确认?",用户点确认则调用 `fetchSubmitTxnFinance`。
|
||||
|
||||
---
|
||||
|
||||
## Task 7: 前端财务确认页改造
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pilot-service/channel-service-order-finance/index.vue`
|
||||
|
||||
- [ ] **Step 1: 改为查流水表**
|
||||
|
||||
将 `fetchChannelServiceOrderList` 替换为 `fetchServiceOrderTxnList`,参数传递 `txnStatus=dqr`(待处理)或 `txnStatus=yqr`(已处理)。
|
||||
|
||||
- [ ] **Step 2: 表格列调整**
|
||||
|
||||
列表表格列改为:
|
||||
- 序号、渠道名称、订单类型Tag、流水类型Tag(充值/退款/购买/退订)、金额、状态Tag、提交人、提交时间、操作
|
||||
|
||||
流水类型标签映射:
|
||||
```typescript
|
||||
const txnTypeLabel = (t: string) => {
|
||||
const map: Record<string, string> = { recharge: '充值', refund: '退款', purchase: '购买产品', unsubscribe: '退订产品' }
|
||||
return map[t] || t
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Tab 切换调整**
|
||||
|
||||
已有两 tab(待处理/已处理),改为:
|
||||
- 待处理:`txnStatus=dqr`
|
||||
- 已处理:`txnStatus=yqr`
|
||||
|
||||
- [ ] **Step 4: 确认弹窗改造**
|
||||
|
||||
"确认收款"弹窗和"确认退款"弹窗合并为一个"确认操作"弹窗:
|
||||
- 上半部分展示流水详情(渠道、金额/产品明细、提交人/时间/备注/凭证)
|
||||
- 下半部分输入财务确认备注 + 上传确认凭证
|
||||
- 调用 `fetchConfirmTxn(txnId, data)`
|
||||
|
||||
- [ ] **Step 5: 详情弹窗**
|
||||
|
||||
保留 SystemViewDialog 查看详情,展示流水完整信息。
|
||||
|
||||
---
|
||||
|
||||
## Task 8: 状态标签注册更新
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/utils/status-tag-registry.ts`
|
||||
|
||||
- [ ] **Step 1: 新增流水类型标签**
|
||||
|
||||
在 `status-tag-registry.ts` 中新增:
|
||||
```typescript
|
||||
// 流水类型
|
||||
export const txnTypeLabel = (type: string): string => {
|
||||
const map: Record<string, string> = {
|
||||
recharge: '充值',
|
||||
refund: '退款',
|
||||
purchase: '购买产品',
|
||||
unsubscribe: '退订产品',
|
||||
}
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
export const txnTypeTagType = (type: string): string => {
|
||||
const map: Record<string, string> = {
|
||||
recharge: '',
|
||||
refund: 'danger',
|
||||
purchase: 'success',
|
||||
unsubscribe: 'warning',
|
||||
}
|
||||
return map[type] || 'info'
|
||||
}
|
||||
|
||||
// 流水状态
|
||||
export const txnStatusLabel = (status: string): string => {
|
||||
const map: Record<string, string> = {
|
||||
dsx: '待生效',
|
||||
dqr: '待财务确认',
|
||||
yqr: '已确认',
|
||||
yzf: '已作废',
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
export const txnStatusTagType = (status: string): string => {
|
||||
const map: Record<string, string> = {
|
||||
dsx: 'info',
|
||||
dqr: 'warning',
|
||||
yqr: 'success',
|
||||
yzf: 'danger',
|
||||
}
|
||||
return map[status] || 'info'
|
||||
}
|
||||
```
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,491 @@
|
||||
# 渠道服务购买订单 — 功能设计文档
|
||||
|
||||
> 日期: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)基础上新增以下字段:
|
||||
|
||||
```sql
|
||||
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`(渠道账户余额)
|
||||
|
||||
```sql
|
||||
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`(渠道产品份数库存)
|
||||
|
||||
```sql
|
||||
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` 中新增:
|
||||
|
||||
```typescript
|
||||
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` 中新增:
|
||||
|
||||
```typescript
|
||||
// 渠道服务购买订单
|
||||
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` 行再更新余额,防止并发超扣。整个余额操作在一个数据库事务内完成。
|
||||
@@ -0,0 +1,256 @@
|
||||
# 渠道服务订单 — 多次操作流水模型设计
|
||||
|
||||
## 背景
|
||||
|
||||
现有渠道服务订单(`biz_channel_service_order`)为单次操作模型:一个订单 = 一次充值或一次购买,退款只能做一次。业务要求每个订单支持多次充值、多次退款、多次购买产品、按份数退订产品,且所有涉及金额的操作都需走财务确认流程。
|
||||
|
||||
## 业务模型
|
||||
|
||||
```
|
||||
渠道服务订单(运营手动创建,选渠道+协议,选类型 yfk/gmcp)
|
||||
├── 充值型(yfk):独立充钱、独立退钱,走渠道账户余额
|
||||
│ ├── 多次充值 → 每次独立金额 + 财务确认 → 余额增加
|
||||
│ └── 多次退款 → 每次独立金额 + 财务确认 → 余额减少
|
||||
│
|
||||
└── 购买型(gmcp):独立买产品、独立退订,与余额无关
|
||||
├── 多次购买 → 每次独立购买产品(份数) + 财务确认 → 持仓增加
|
||||
└── 多次退订 → 按份数退订 + 财务确认 → 持仓减少
|
||||
```
|
||||
|
||||
两种类型完全独立。充值管渠道余额,购买管产品份数。
|
||||
|
||||
## 方案:操作流水子表
|
||||
|
||||
核心思路:订单主表管汇总,新增操作流水表管明细,产品表管持仓。
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据模型
|
||||
|
||||
### 1.1 订单主表 `biz_channel_service_order`(改造)
|
||||
|
||||
**保留字段:**
|
||||
- `id`, `channel_id`, `channel_name`, `dept_name`, `order_type`(yfk/gmcp), `order_status`(dsx/sxz/yzf), `channel_agreement_id`
|
||||
- 公共字段:`tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
|
||||
**移除字段(迁移到流水表):**
|
||||
- `prepay_amount`, `purchase_total_amount`
|
||||
- `finance_submit_by`, `finance_submit_time`, `finance_submit_remark`, `finance_submit_attachments`
|
||||
- `finance_confirm_time`, `finance_confirm_attachments`, `finance_remark`
|
||||
- `refund_apply_by`, `refund_apply_time`, `refund_amount`, `refund_remark`, `refund_attachments`
|
||||
- `refund_finance_confirm_time`, `refund_finance_confirm_attachments`, `refund_finance_remark`
|
||||
- `balance_before`, `balance_after`
|
||||
|
||||
**新增汇总字段:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `total_recharge_amount` | decimal(18,2) DEFAULT 0 | 累计充值总额(yfk) |
|
||||
| `total_refund_amount` | decimal(18,2) DEFAULT 0 | 累计退款总额(yfk) |
|
||||
| `current_balance` | decimal(18,2) DEFAULT 0 | 当前余额(yfk),= 累计充值 - 累计退款 |
|
||||
|
||||
### 1.2 操作流水表 `biz_channel_service_order_txn`(新增)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `biz_channel_service_order_txn` (
|
||||
`id` varchar(64) NOT NULL COMMENT '主键ID',
|
||||
`service_order_id` varchar(64) NOT NULL COMMENT '关联订单ID',
|
||||
`txn_type` varchar(16) NOT NULL COMMENT 'recharge/refund/purchase/unsubscribe',
|
||||
`amount` decimal(18,2) DEFAULT NULL COMMENT '本次操作金额',
|
||||
`txn_status` varchar(16) NOT NULL DEFAULT 'dsx' COMMENT 'dsx待生效 dqr待财务确认 yqr已确认 yzf已作废',
|
||||
`products_json` text COMMENT '产品明细JSON(purchase/unsubscribe类型)',
|
||||
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
|
||||
-- 发起财务确认
|
||||
`finance_submit_by` varchar(64) DEFAULT NULL COMMENT '发起确认人ID',
|
||||
`finance_submit_time` datetime DEFAULT NULL COMMENT '发起确认时间',
|
||||
`finance_submit_remark` varchar(512) DEFAULT NULL COMMENT '发起确认备注',
|
||||
`finance_submit_attachments` text COMMENT '发起确认附件',
|
||||
-- 财务确认
|
||||
`finance_confirm_time` datetime DEFAULT NULL COMMENT '财务确认时间',
|
||||
`finance_confirm_attachments` text COMMENT '财务确认附件',
|
||||
`finance_remark` varchar(512) DEFAULT NULL COMMENT '财务确认备注',
|
||||
-- 余额快照(仅 yfk 类型)
|
||||
`balance_before` decimal(18,2) DEFAULT NULL COMMENT '操作前余额',
|
||||
`balance_after` decimal(18,2) DEFAULT NULL COMMENT '操作后余额',
|
||||
-- 公共字段
|
||||
`tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
|
||||
`order_num` int(11) 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 '删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_txn_order` (`service_order_id`, `del_flag`),
|
||||
KEY `idx_txn_status` (`txn_status`, `tenant_no`, `del_flag`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='渠道服务订单操作流水';
|
||||
```
|
||||
|
||||
### 1.3 产品持仓表 `biz_channel_service_order_product`(改造)
|
||||
|
||||
在现有字段基础上新增:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `total_buy_qty` | decimal(18,4) DEFAULT 0 | 累计购买数量 |
|
||||
| `total_unsubscribe_qty` | decimal(18,4) DEFAULT 0 | 累计退订数量 |
|
||||
| `current_qty` | decimal(18,4) DEFAULT 0 | 当前持有数量 |
|
||||
| `total_unsubscribe_amount` | decimal(18,2) DEFAULT 0 | 累计退订金额 |
|
||||
|
||||
保留现有字段:`agreement_unit_price`, `buy_qty`(首次购买), `buy_amount`。
|
||||
|
||||
### 1.4 关系图
|
||||
|
||||
```
|
||||
biz_channel_service_order(订单主表)
|
||||
│
|
||||
├── biz_channel_service_order_txn(操作流水)× N
|
||||
│ 每条流水 = 一次充值/退款/购买/退订
|
||||
│ 每条流水独立走财务确认流程
|
||||
│
|
||||
└── biz_channel_service_order_product(产品持仓)× N
|
||||
购买时增加 current_qty,退订时减少
|
||||
```
|
||||
|
||||
### 1.5 废弃表
|
||||
|
||||
`biz_channel_service_order_finance_confirm` — 已有但未使用,保留不删,不再使用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 状态机
|
||||
|
||||
### 2.1 订单主表状态
|
||||
|
||||
| 状态 | 含义 | 说明 |
|
||||
|------|------|------|
|
||||
| `dsx` | 待生效 | 刚创建,尚无已确认的流水 |
|
||||
| `sxz` | 生效中 | 至少有一笔流水已确认 |
|
||||
| `yzf` | 已作废 | 手动作废,不再允许新操作 |
|
||||
|
||||
### 2.2 流水状态
|
||||
|
||||
| 状态 | 含义 | 可转至 |
|
||||
|------|------|--------|
|
||||
| `dsx` | 待生效 | `dqr`(发起确认)、`yzf`(作废) |
|
||||
| `dqr` | 待财务确认 | `yqr`(财务确认) |
|
||||
| `yqr` | 已确认(终态) | — |
|
||||
| `yzf` | 已作废(终态) | — |
|
||||
|
||||
### 2.3 操作流转
|
||||
|
||||
**充值型(yfk):**
|
||||
|
||||
```
|
||||
创建订单 → [dsx]
|
||||
├─ 发起充值 → txn(recharge, dsx)
|
||||
│ └─ 发起确认 → txn(dqr) → 财务确认 → txn(yqr)
|
||||
│ └─ order.total_recharge_amount += amount
|
||||
│ order.current_balance += amount
|
||||
│ order.status → sxz (首次确认时)
|
||||
│
|
||||
└─ 申请退款 → txn(refund, dsx), amount ≤ current_balance
|
||||
└─ 发起确认 → txn(dqr) → 财务确认 → txn(yqr)
|
||||
└─ order.total_refund_amount += amount
|
||||
order.current_balance -= amount
|
||||
```
|
||||
|
||||
**购买型(gmcp):**
|
||||
|
||||
```
|
||||
创建订单 → [dsx]
|
||||
├─ 购买产品 → txn(purchase, dsx) + 创建/更新 product 持仓
|
||||
│ └─ 发起确认 → txn(dqr) → 财务确认 → txn(yqr)
|
||||
│ └─ product.total_buy_qty += qty
|
||||
│ product.current_qty += qty
|
||||
│ order.status → sxz (首次确认时)
|
||||
│
|
||||
└─ 退订产品 → txn(unsubscribe, dsx), qty ≤ product.current_qty
|
||||
└─ 发起确认 → txn(dqr) → 财务确认 → txn(yqr)
|
||||
└─ product.total_unsubscribe_qty += qty
|
||||
product.current_qty -= qty
|
||||
product.total_unsubscribe_amount += qty × unit_price
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. API 设计
|
||||
|
||||
### 3.1 订单管理(`/api/admin/channel-service-orders`)
|
||||
|
||||
| 接口 | 方法 | 说明 | 变更 |
|
||||
|------|------|------|------|
|
||||
| `/` | GET | 订单列表 | 不变 |
|
||||
| `/{order_id}` | GET | 订单详情 + 流水列表 + 产品持仓 | **扩展返回** |
|
||||
| `/` | POST | 创建订单 | 不变 |
|
||||
| `/{order_id}` | PUT | 编辑订单基本信息 | 不变 |
|
||||
| `/{order_id}` | DELETE | 删除 dsx 状态订单 | 不变 |
|
||||
| `/{order_id}/void` | PUT | 作废订单 | 不变 |
|
||||
| `/{order_id}/recharge` | POST | **新增** 发起充值流水 | 新增 |
|
||||
| `/{order_id}/refund` | POST | **新增** 申请退款流水 | 替代原退款 |
|
||||
| `/{order_id}/purchase` | POST | **新增** 购买产品流水 | 新增 |
|
||||
| `/{order_id}/unsubscribe` | POST | **新增** 退订产品流水 | 新增 |
|
||||
|
||||
### 3.2 流水操作
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/channel-service-order-txns/{txn_id}/submit-finance` | POST | 发起财务确认 |
|
||||
| `/channel-service-order-txns/{txn_id}/confirm` | POST | 财务确认(收款/退付) |
|
||||
| `/channel-service-order-txns/{txn_id}/void` | PUT | 作废流水 |
|
||||
|
||||
### 3.3 财务确认页
|
||||
|
||||
| 接口 | 方法 | 说明 | 变更 |
|
||||
|------|------|------|------|
|
||||
| `/channel-service-order-txns` | GET | 查流水列表(按 status 筛选) | **改为查流水表** |
|
||||
| `/channel-service-order-txns/{txn_id}` | GET | 流水详情 | 新增 |
|
||||
| 确认 | POST | 复用 3.2 的 confirm 接口 | — |
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端改造
|
||||
|
||||
### 4.1 订单管理页(`channel-service-order/index.vue`)
|
||||
|
||||
**操作按钮:**
|
||||
|
||||
| 条件 | 按钮 |
|
||||
|------|------|
|
||||
| yfk + dsx | 编辑、删除、发起充值 |
|
||||
| yfk + sxz | 发起充值、申请退款 |
|
||||
| gmcp + dsx | 编辑、删除、购买产品 |
|
||||
| gmcp + sxz | 购买产品、退订产品 |
|
||||
| 任意 + yzf | 仅查看 |
|
||||
|
||||
**详情弹窗改造:**
|
||||
- Section 1:基本信息(含余额/汇总金额)
|
||||
- Section 2:操作记录(流水时间线列表)
|
||||
- Section 3:产品持仓(gmcp 类型,显示当前持有份数)
|
||||
|
||||
**新增弹窗:**
|
||||
- 发起充值:金额、备注、凭证上传
|
||||
- 申请退款:金额(≤余额)、备注、凭证上传
|
||||
- 购买产品:选项目/规格、输入份数、自动算金额
|
||||
- 退订产品:选已持有项目、输入退订份数(≤持有量)
|
||||
|
||||
### 4.2 财务确认页(`channel-service-order-finance/index.vue`)
|
||||
|
||||
**改为以流水为维度展示:**
|
||||
|
||||
**待处理 Tab(status=dqr):**
|
||||
- 显示:流水类型标签、渠道名称、金额、产品明细、提交人/时间
|
||||
- 操作:查看详情、确认收款/退付
|
||||
|
||||
**已处理 Tab(status=yqr):**
|
||||
- 显示同上,操作仅查看
|
||||
|
||||
**确认弹窗:**
|
||||
- 上半部分展示流水详情(发起确认信息)
|
||||
- 下半部分输入财务确认备注+凭证
|
||||
|
||||
### 4.3 废弃功能
|
||||
|
||||
- 移除原订单级别的「发起财务确认」「申请退款」按钮和弹窗(改为流水级别操作)
|
||||
- 移除订单主表上平铺的 finance/refund 字段展示
|
||||
Reference in new issue
Block a user