feat: close overseas wallet and pallet cost flow

This commit is contained in:
2026-09-05 22:26:25 +08:00
parent f62283fb7f
commit 5fbd2bbd04
69 changed files with 1598 additions and 53 deletions
@@ -1421,6 +1421,12 @@ CREATE TABLE IF NOT EXISTS ll_relabel_application (
item_count INT NOT NULL DEFAULT 0 COMMENT '明细数量',
total_package_count INT NOT NULL DEFAULT 0 COMMENT '总箱数',
notified_package_count INT NOT NULL DEFAULT 0 COMMENT '已通知箱数',
charge_amount DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '换标费用CNY',
charge_currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '费用币种',
wallet_status VARCHAR(32) NOT NULL DEFAULT 'FREEZE_PENDING' COMMENT '资金状态',
wallet_error_code VARCHAR(64) NULL COMMENT '资金错误码',
wallet_error_message VARCHAR(500) NULL COMMENT '资金错误说明',
wallet_command_event_id VARCHAR(64) NULL COMMENT '当前资金指令事件ID',
idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键',
request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希',
created_by VARCHAR(128) NULL COMMENT '创建人ID',
@@ -1879,3 +1885,65 @@ INSERT IGNORE INTO ll_security_projection (
'baseline-platform-admin-user-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000'),
('AUTHORIZATION','1000001',0,NULL,0,0,SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0',256),
'baseline-platform-admin-authorization-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000');
CREATE TABLE IF NOT EXISTS ll_wallet_event_inbox (
event_id VARCHAR(64) NOT NULL COMMENT '国内Finance资金结果事件ID',
command_event_id VARCHAR(64) NOT NULL COMMENT '原海外资金指令事件ID',
payload_hash CHAR(64) NOT NULL COMMENT '规范化载荷SHA-256',
payload_json JSON NOT NULL COMMENT '冻结事件信封',
status VARCHAR(16) NOT NULL COMMENT 'RECEIVED或PROCESSED',
received_at DATETIME(3) NOT NULL COMMENT '接收时间',
processed_at DATETIME(3) NULL COMMENT '处理完成时间',
PRIMARY KEY (event_id),
UNIQUE KEY uk_ll_wallet_command_result (command_event_id),
KEY idx_ll_wallet_event_status (status,received_at)
) ENGINE=InnoDB COMMENT='跨区客户资金结果幂等收件箱';
CREATE TABLE IF NOT EXISTS ll_procurement_daily_sequence (
customer_id VARCHAR(128) NOT NULL COMMENT '客户ID',
business_date DATE NOT NULL COMMENT '北京时间业务日期',
sequence_value INT NOT NULL COMMENT '客户当日已分配最大序号',
PRIMARY KEY (customer_id,business_date),
CONSTRAINT chk_ll_procurement_sequence CHECK (sequence_value BETWEEN 1 AND 9999)
) ENGINE=InnoDB COMMENT='海外采购客户日序列';
CREATE TABLE IF NOT EXISTS ll_overseas_procurement_file (
id BIGINT NOT NULL COMMENT '主键ID', order_id BIGINT NOT NULL COMMENT '采购订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID',
category VARCHAR(16) NOT NULL COMMENT 'RECEIPT签收文件或VOUCHER费用凭证', file_id BIGINT NOT NULL COMMENT 'File服务文件ID',
file_name VARCHAR(255) NOT NULL COMMENT '文件名快照', content_type VARCHAR(128) NULL COMMENT '媒体类型', size_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小',
created_by VARCHAR(128) NULL COMMENT '上传人', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上传时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_procurement_file(order_id,file_id), KEY idx_ll_procurement_file_order(customer_id,order_id,category,created_at),
CONSTRAINT chk_ll_procurement_file_category CHECK(category IN ('RECEIPT','VOUCHER'))
) ENGINE=InnoDB COMMENT='海外采购签收文件及费用凭证';
CREATE TABLE IF NOT EXISTS ll_overseas_procurement_fee (
id BIGINT NOT NULL COMMENT '主键ID', order_id BIGINT NOT NULL COMMENT '采购订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID',
fee_no VARCHAR(64) NOT NULL COMMENT '费用业务号', fee_item_code VARCHAR(32) NOT NULL COMMENT '费用项编码', fee_item_name VARCHAR(128) NOT NULL COMMENT '费用项名称',
amount DECIMAL(20,2) NOT NULL COMMENT 'CNY金额', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '费用币种', voucher_file_id BIGINT NOT NULL COMMENT '凭证文件ID',
voucher_file_name VARCHAR(255) NOT NULL COMMENT '凭证文件名', remark VARCHAR(500) NULL COMMENT '说明',
wallet_status VARCHAR(32) NOT NULL COMMENT '资金状态', wallet_error_code VARCHAR(64) NULL COMMENT '资金错误码', wallet_error_message VARCHAR(500) NULL COMMENT '资金错误说明',
wallet_command_event_id VARCHAR(64) NULL COMMENT '当前资金指令事件ID', created_by VARCHAR(128) NULL COMMENT '登记人',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_procurement_fee_no(fee_no), KEY idx_ll_procurement_fee_order(customer_id,order_id,created_at),
CONSTRAINT chk_ll_procurement_fee_amount CHECK(amount>0), CONSTRAINT chk_ll_procurement_fee_currency CHECK(currency='CNY')
) ENGINE=InnoDB COMMENT='海外采购费用明细及资金生命周期';
CREATE TABLE IF NOT EXISTS ll_destuff_pallet_cost (
id BIGINT NOT NULL COMMENT '主键ID', source_container_booking_id VARCHAR(128) NOT NULL COMMENT '来源柜次业务ID', container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', warehouse_id BIGINT NULL COMMENT '海外仓ID',
pallet_count INT NOT NULL COMMENT '实际打托数', total_actual_volume_cbm DECIMAL(20,6) NOT NULL COMMENT '客户实收总体积', total_cost DECIMAL(20,2) NOT NULL COMMENT '打托总成本', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '成本币种',
status VARCHAR(16) NOT NULL COMMENT '生成状态', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id),
UNIQUE KEY uk_ll_destuff_cost_booking(source_container_booking_id), CONSTRAINT chk_ll_destuff_cost CHECK(pallet_count>0 AND total_actual_volume_cbm>0 AND total_cost>0 AND currency='CNY')
) ENGINE=InnoDB COMMENT='拆柜打托实际成本汇总';
CREATE TABLE IF NOT EXISTS ll_destuff_pallet_cost_allocation (
id BIGINT NOT NULL COMMENT '主键ID', cost_id BIGINT NOT NULL COMMENT '打托成本汇总ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID', actual_volume_cbm DECIMAL(20,6) NOT NULL COMMENT '客户实收体积',
allocated_amount DECIMAL(20,2) NOT NULL COMMENT '客户分摊金额', fee_source_id BIGINT NOT NULL COMMENT '海外费用来源ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_destuff_customer(cost_id,customer_id), UNIQUE KEY uk_ll_destuff_fee_source(fee_source_id),
CONSTRAINT chk_ll_destuff_allocation CHECK(actual_volume_cbm>0 AND allocated_amount>=0)
) ENGINE=InnoDB COMMENT='拆柜打托成本按客户实收体积分摊';
@@ -678,6 +678,27 @@ paths:
required: true
content: { application/json: { schema: { $ref: '#/components/schemas/ProcurementReview' } } }
responses: { '200': { description: 统一 ApiResponse 包装的采购订单详情 } }
/api/platform/v1/last-leg/overseas-procurements/{orderNo}/files/{category}:
post:
summary: 上传采购签收文件或费用凭证
description: 权限 platform:overseas-business:purchase:edit;category 仅允许 RECEIPT 或 VOUCHER。
security: [{ bearerAuth: [] }]
parameters:
- { name: orderNo, in: path, required: true, schema: { type: string } }
- { name: category, in: path, required: true, schema: { type: string, enum: [RECEIPT, VOUCHER] } }
- { name: customerId, in: query, required: true, schema: { type: string, pattern: '^[1-9][0-9]*$' } }
requestBody: { required: true, content: { multipart/form-data: { schema: { type: object, required: [file], properties: { file: { type: string, format: binary } } } } } }
responses: { '200': { description: 已保存文件元数据 } }
/api/platform/v1/last-leg/overseas-procurements/{orderNo}/fees:
post:
summary: 登记采购费用并发起资金冻结
description: 权限 platform:overseas-business:purchase:edit;订单必须已审核通过,费用凭证必须属于本订单。
security: [{ bearerAuth: [] }]
parameters:
- { name: orderNo, in: path, required: true, schema: { type: string } }
- { name: customerId, in: query, required: true, schema: { type: string, pattern: '^[1-9][0-9]*$' } }
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProcurementFeeCreate' } } } }
responses: { '200': { description: 已创建费用并进入冻结流程 }, '409': { description: 订单状态或凭证不允许 } }
/api/merchant/v1/last-leg/delivery-orders:
get:
summary: 客户分页查询自己的派送单
@@ -1641,13 +1662,14 @@ components:
RelabelStatus: { type: string, enum: [PENDING_PROCESSING, PROCESSING, COMPLETED, REJECTED, CANCELLED] }
RelabelCreate:
type: object
required: [customerOrderNo, overseasWarehouseId, labelDeadline, contactName, contactPhone, idempotencyKey, items]
required: [customerOrderNo, overseasWarehouseId, labelDeadline, contactName, contactPhone, chargeAmount, idempotencyKey, items]
properties:
customerOrderNo: { type: string, maxLength: 128 }
overseasWarehouseId: { type: string, maxLength: 128 }
labelDeadline: { type: string, format: date-time }
contactName: { type: string, maxLength: 128 }
contactPhone: { type: string, maxLength: 64 }
chargeAmount: { type: number, minimum: 0.01, description: 换标费用,币种固定 CNY }
remark: { type: string, nullable: true, maxLength: 1000 }
idempotencyKey: { type: string, maxLength: 255 }
items: { type: array, minItems: 1, maxItems: 500, items: { $ref: '#/components/schemas/RelabelItem' } }
@@ -1764,7 +1786,7 @@ components:
updatedAt: { type: string, format: date-time }
ProcurementDetail:
type: object
required: [summary, histories, outboundDetails]
required: [summary, histories, outboundDetails, files, fees]
properties:
summary: { $ref: '#/components/schemas/ProcurementSummary' }
productUrl: { type: string, nullable: true }
@@ -1783,6 +1805,42 @@ components:
reviewedAt: { type: string, format: date-time, nullable: true }
histories: { type: array, items: { $ref: '#/components/schemas/BusinessHistory' } }
outboundDetails: { type: array, items: { $ref: '#/components/schemas/ProcurementOutboundDetail' } }
files: { type: array, items: { $ref: '#/components/schemas/ProcurementFile' } }
fees: { type: array, items: { $ref: '#/components/schemas/ProcurementFee' } }
ProcurementFeeCreate:
type: object
required: [feeItemCode, feeItemName, amount, voucherFileId]
properties:
feeItemCode: { type: string, maxLength: 64 }
feeItemName: { type: string, maxLength: 128 }
amount: { type: number, minimum: 0.01 }
voucherFileId: { type: string, pattern: '^[0-9]+$' }
remark: { type: string, nullable: true, maxLength: 500 }
ProcurementFile:
type: object
properties:
id: { type: string }
category: { type: string, enum: [RECEIPT, VOUCHER] }
fileId: { type: string }
fileName: { type: string }
contentType: { type: string, nullable: true }
sizeBytes: { type: integer, format: int64 }
createdAt: { type: string, format: date-time }
ProcurementFee:
type: object
properties:
id: { type: string }
feeNo: { type: string }
feeItemCode: { type: string }
feeItemName: { type: string }
amount: { type: number }
currency: { type: string, enum: [CNY] }
voucherFileId: { type: string }
voucherFileName: { type: string }
remark: { type: string, nullable: true }
walletStatus: { type: string }
walletErrorMessage: { type: string, nullable: true }
createdAt: { type: string, format: date-time }
ProcurementOutboundDetail:
type: object
properties: