提交
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
Reference in new issue
Block a user