feat: complete finance and overseas receiving workflow

This commit is contained in:
2026-09-10 22:43:11 +08:00
parent e531ae9407
commit 56635ad4e4
66 changed files with 1601 additions and 417 deletions
@@ -660,8 +660,9 @@ CREATE TABLE IF NOT EXISTS ll_inbound_plan_pallet (
capacity_cbm DECIMAL(18,4) NOT NULL DEFAULT 2.0000 COMMENT '托盘容量,固定为2立方米',
allocated_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '已分配体积(立方米)',
sequence_no INT NOT NULL COMMENT '计划内托盘序号',
version INT NOT NULL DEFAULT 0 COMMENT '收货累计值乐观锁版本',
PRIMARY KEY (id),
UNIQUE KEY uk_ll_inbound_pallet_no (pallet_no),
KEY idx_ll_inbound_pallet_no (pallet_no),
UNIQUE KEY uk_ll_inbound_pallet_sequence (plan_id, sequence_no),
KEY idx_ll_inbound_pallet_plan (plan_id),
KEY idx_ll_inbound_pallet_customer_plan (customer_id, plan_id)
@@ -996,7 +997,7 @@ CREATE TABLE IF NOT EXISTS ll_receiving_container_session (
source_container_booking_id VARCHAR(128) NOT NULL COMMENT '稳定来源柜预订ID', container_fact_hash VARCHAR(64) NOT NULL COMMENT '领取时整柜订单与计划事实摘要',
container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', overseas_warehouse_id BIGINT NOT NULL COMMENT '平台预分配并由操作员Token匹配的海外接柜仓ID', overseas_warehouse_code VARCHAR(128) NOT NULL COMMENT '海外接柜仓业务编码快照', overseas_warehouse_name VARCHAR(255) NOT NULL COMMENT '仓库服务校验后的海外接柜仓名称快照', operator_key VARCHAR(128) NOT NULL COMMENT '操作账号唯一标识',
operator_name VARCHAR(255) NULL COMMENT '操作账号名称', active_operator_key VARCHAR(255) NULL COMMENT '未完成会话平台操作账号全局唯一锁键',
active_container_key VARCHAR(255) NULL COMMENT '未完成会话柜号全局唯一锁键', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_SIGN' COMMENT '会话状态:WAIT_SIGN、SIGNED、RECEIVING、COMPLETED',
active_container_key VARCHAR(255) NULL COMMENT '未完成会话柜号全局唯一锁键', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_SIGN' COMMENT '会话状态:WAIT_SIGN、SIGNED、RECEIVING、COMPLETED、COMPLETED_WITH_DIFFERENCE',
sign_photos JSON NULL COMMENT '签收卸柜照片角色与文件ID列表', prepared_at DATETIME(3) NULL COMMENT '选择柜号时间',
signed_at DATETIME(3) NULL COMMENT '签收时间', receiving_at DATETIME(3) NULL COMMENT '首次收货时间',
completed_at DATETIME(3) NULL COMMENT '完成时间', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
@@ -1019,6 +1020,16 @@ CREATE TABLE IF NOT EXISTS ll_receiving_sku_receipt (
KEY idx_ll_receiving_sku_plan (customer_id,plan_id,line_id)
) ENGINE=InnoDB COMMENT='海外PDA柜号FBA SKU实收进度';
CREATE TABLE IF NOT EXISTS ll_receiving_fba_pallet_count (
id BIGINT NOT NULL COMMENT 'FBA托盘现场累计箱数ID', session_id BIGINT NOT NULL COMMENT '柜号签收会话ID',
group_key VARCHAR(255) NOT NULL COMMENT '柜号与FBA仓组成的收货组键', pallet_code VARCHAR(128) NOT NULL COMMENT '现有FBA托盘码',
actual_packages INT NOT 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_receiving_fba_count(session_id,group_key,pallet_code),
KEY idx_ll_receiving_fba_group(session_id,group_key)
) ENGINE=InnoDB COMMENT='海外PDA普通FBA托盘现场累计与超收标记';
CREATE TABLE IF NOT EXISTS ll_receiving_container_operation (
id BIGINT NOT NULL COMMENT '柜号收货操作ID', session_id BIGINT NOT NULL COMMENT '柜号签收会话ID',
operator_key VARCHAR(128) NOT NULL COMMENT '操作账号唯一标识', action VARCHAR(32) NOT NULL COMMENT '操作动作', idempotency_key VARCHAR(255) NOT NULL COMMENT '客户端幂等键',
@@ -1799,11 +1810,14 @@ CREATE TABLE IF NOT EXISTS ll_dropship_order_profile (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', order_id BIGINT NOT NULL COMMENT '订单ID',
warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号',
source_pallet_no VARCHAR(128) NOT NULL COMMENT '来源托盘号快照',
manifest_version INT NOT NULL COMMENT '整托清单版本', manifest_package_count INT NOT NULL COMMENT '整托总箱数',
manifest_fragment_count INT NOT NULL COMMENT '整托客户分片总数', manifest_hash CHAR(64) NOT NULL COMMENT '整托完整清单摘要',
inventory_position_id BIGINT NULL COMMENT '库存仓位记录ID', status VARCHAR(32) NOT NULL DEFAULT 'PENDING_INBOUND' COMMENT '业务状态',
idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', version BIGINT NOT NULL DEFAULT 0 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_dropship_profile_order_pallet(customer_id,order_id,source_pallet_no),
UNIQUE KEY uk_ll_dropship_profile_key(customer_id,idempotency_key), KEY idx_ll_dropship_profile_order(customer_id,order_id)
UNIQUE KEY uk_ll_dropship_profile_key(customer_id,idempotency_key), KEY idx_ll_dropship_profile_order(customer_id,order_id),
KEY idx_ll_dropship_profile_manifest(source_pallet_no,manifest_hash)
) ENGINE=InnoDB COMMENT='一件代发托盘档案';
CREATE TABLE IF NOT EXISTS ll_dropship_carton (
@@ -96,24 +96,24 @@ paths:
/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve:
post:
summary: 解析当前柜箱唛
description: 权限 lastleg:pda:operate;FBA 从完整 cartonMark 的第一个 # 前提取固定入仓码,并精确匹配当前锁定柜唯一 FBA line;不要求逐箱 manifest,不校验 SKU 或箱序号。OVERSEAS 必须命中真实 manifest。两者均不返回 SKU,也不登记收货。
description: 权限 lastleg:pda:operate;一件代发扫描 DP 或任一箱码解析整托并校验跨客户完整清单;普通 FBA 扫任一箱码后按当前柜+FBA目的仓聚合所有入仓码;海外仓中转仍逐箱解析。响应不返回客户信息。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [cartonMark], properties: { cartonMark: { type: string } } } } } }
responses: { '200': { description: 箱唛与入仓码信息, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingCartonResolvedApiResponse' } } } }, '404': { description: 入仓码未命中当前柜,或 OVERSEAS 箱码未命中真实 manifest }, '409': { description: '缺少 #、空入仓码前缀、跨柜或当前柜多重命中' } }
/api/pda/v1/last-leg/receiving-containers/current/pallets/resolve:
post:
summary: 解析当前柜实物托盘码
description: 权限 lastleg:pda:operate;托盘固定从 Warehouse Overseas 公共资源作用域解析并校验实际仓与目的仓。
description: 权限 lastleg:pda:operate;普通 FBA 只解析当前柜该 FBA 目的仓已经生成的“柜号-FBA仓-序号”托盘,返回版本与该托现场累计箱数。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [palletCode, inboundCode], properties: { palletCode: { type: string }, inboundCode: { type: string } } } } } }
responses: { '200': { description: 可用托盘与目的匹配结果, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingPalletResolvedApiResponse' } } } }, '409': { description: 仓库、目的仓或托盘状态不符 } }
/api/pda/v1/last-leg/receiving-containers/current/receipts/bulk:
post:
summary: FBA 入仓码按箱数收货
description: 权限 lastleg:pda:operate;只允许 destinationType=FBA,OVERSEAS 在服务端硬拒绝;服务端按当前入仓码所属计划的 line.id ASC 和冻结 SKU 明细顺序拆分箱数并记录真实 SKU 收货;不接收 SKU、customerId、planNo、allocationId、warehouseId、locationId。
description: 权限 lastleg:pda:operate;只允许普通 FBA。packageCount 是该托现场累计箱数,服务端按当前柜+FBA目的仓稳定分摊到全部关联入仓码;超出计划部分只记差异,不生成可用库存。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingBulk' } } } }
responses: { '200': { description: 本次与累计实收进度, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingReceiptApiResponse' } } } }, '409': { description: 海外仓批量、超收或托盘不匹配 } }
responses: { '200': { description: 本次与累计实收进度,超收返回 DIFFERENCE_RECORDED, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingReceiptApiResponse' } } } }, '409': { description: 非普通FBA、累计数回退、版本变化或托盘不匹配 } }
/api/pda/v1/last-leg/receiving-containers/current/receipts/carton:
post:
summary: 海外仓入仓码逐箱收货
@@ -124,17 +124,17 @@ paths:
/api/pda/v1/last-leg/receiving-containers/current/receipts/pallet:
post:
summary: 一件代发按完整来源托盘收货
description: 权限 lastleg:pda:operate;只允许 ONE_CLICK_DROPSHIP,扫描国内封托后冻结的来源托盘号,一次接收该托全部箱和 SKU 快照;不允许逐箱或拆分接收。
description: 权限 lastleg:pda:operate;只允许 ONE_CLICK_DROPSHIP,扫描 DP 或该托任一箱码,一次接收跨客户完整 DP;所有客户事件分片清单一致且齐全后才允许收货。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingWholePallet' } } } }
responses: { '200': { description: 本次与累计实收进度, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingReceiptApiResponse' } } } }, '404': { description: 来源托盘不属于当前柜 }, '409': { description: 非一件代发、来源托盘重复或已操作 } }
/api/pda/v1/last-leg/receiving-containers/current/complete:
post:
summary: 完成当前柜收货并入库存
description: 权限 lastleg:pda:operate;仅柜内每个入仓码实收总数精确相等后完成,库存、计划、尾程状态与卸柜 Outbox 同事务落地。
description: 权限 lastleg:pda:operate;无差异时正常入库;allowDifference=true 时允许短少或超收现场结束,差异计划进入 PENDING_RECONCILIATION 且不生成客户可用库存。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingAction' } } } }
responses: { '200': { description: COMPLETED 柜号会话, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingSessionApiResponse' } } } }, '409': { description: 数量未核平或状态冲突 } }
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingComplete' } } } }
responses: { '200': { description: COMPLETED 或 COMPLETED_WITH_DIFFERENCE 柜号会话, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingSessionApiResponse' } } } }, '409': { description: 未确认差异或状态冲突 } }
/api/pda/v1/last-leg/inventory/resolve:
post:
summary: PDA按SKU、箱唛或入仓码扫码查询库存
@@ -1781,6 +1781,12 @@ components:
type: object
required: [idempotencyKey]
properties: { idempotencyKey: { type: string, minLength: 1, maxLength: 255 } }
ReceivingComplete:
type: object
required: [allowDifference, idempotencyKey]
properties:
allowDifference: { type: boolean }
idempotencyKey: { type: string, minLength: 1, maxLength: 255 }
ReceivingSign:
type: object
required: [photos, idempotencyKey]
@@ -1807,6 +1813,7 @@ components:
inboundCode: { type: string, minLength: 1 }
palletCode: { type: string, minLength: 1 }
packageCount: { type: integer, minimum: 1 }
expectedPalletVersion: { type: integer, minimum: 0, nullable: true }
idempotencyKey: { type: string, minLength: 1, maxLength: 255 }
ReceivingCarton:
type: object
@@ -1883,13 +1890,13 @@ components:
currentAccount: { type: boolean }
ReceivingSession:
type: object
required: [containerNo, overseasWarehouseId, overseasWarehouseCode, overseasWarehouseName, status, version, photos, totalPackages, receivedPackages, pendingPackages, inbounds]
required: [containerNo, overseasWarehouseId, overseasWarehouseCode, overseasWarehouseName, status, version, photos, totalPackages, receivedPackages, pendingPackages, hasDifference, inbounds]
properties:
containerNo: { type: string }
overseasWarehouseId: { type: string, pattern: '^\d+$', description: 平台预分配并与PDA Token匹配的海外接柜仓ID }
overseasWarehouseCode: { type: string, description: 海外接柜仓业务编码快照 }
overseasWarehouseName: { type: string, description: Warehouse Overseas服务校验后的共享实际海外收货仓名称快照 }
status: { type: string, enum: [WAIT_SIGN, SIGNED, RECEIVING, COMPLETED] }
status: { type: string, enum: [WAIT_SIGN, SIGNED, RECEIVING, COMPLETED, COMPLETED_WITH_DIFFERENCE] }
operatorName: { type: string, nullable: true }
version: { type: integer }
photos: { type: array, maxItems: 3, items: { $ref: '#/components/schemas/ReceivingSignedSessionPhoto' } }
@@ -1900,6 +1907,7 @@ components:
totalPackages: { type: integer }
receivedPackages: { type: integer }
pendingPackages: { type: integer }
hasDifference: { type: boolean, description: 是否存在短少或普通FBA超收差异 }
inbounds: { type: array, items: { $ref: '#/components/schemas/ReceivingInbound' } }
ReceivingPhoto:
type: object
@@ -1913,12 +1921,16 @@ components:
allOf:
- { $ref: '#/components/schemas/ReceivingInbound' }
- type: object
required: [cartonMark]
required: [cartonMark, operationMode, groupKey, inboundCodes]
properties:
cartonMark: { type: string }
operationMode: { type: string, enum: [DROPSHIP_PALLET, FBA_GROUP], nullable: true }
groupKey: { type: string, nullable: true }
inboundCodes: { type: array, items: { type: string } }
sourcePalletNo: { type: string, nullable: true }
ReceivingPalletResolved:
type: object
required: [palletCode, warehouseId, status, inboundCode, destinationType, compatible]
required: [palletCode, warehouseId, status, inboundCode, destinationType, compatible, version, currentReceivedPackages]
properties:
palletCode: { type: string }
warehouseId: { type: integer, format: int64 }
@@ -1927,6 +1939,8 @@ components:
inboundCode: { type: string }
destinationType: { type: string, enum: [FBA, OVERSEAS] }
compatible: { type: boolean }
version: { type: integer, minimum: 0 }
currentReceivedPackages: { type: integer, minimum: 0 }
ReceivingReceipt:
type: object
required: [result, containerNo, inboundCode, destinationType, palletCode, acceptedPackages, receivedPackages, pendingPackages, sessionStatus, assigned, mergeAllowed, urgent]
@@ -1946,6 +1960,8 @@ components:
mergeHint: { type: string, nullable: true }
orderType: { type: string, nullable: true }
wholePalletInbound: { type: boolean, description: 一件代发必须按完整来源托盘扫描收货 }
groupKey: { type: string, nullable: true }
inboundCodes: { type: array, items: { type: string } }
ReceivingCandidateListApiResponse:
allOf:
- { $ref: '#/components/schemas/ReceivingApiResponseBase' }