Merge remote-tracking branch 'origin/test' into dev

# Conflicts:
#	lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml
This commit is contained in:
2026-09-09 17:07:34 +08:00
2 changed files with 23 additions and 227 deletions
@@ -44,7 +44,7 @@ class FinanceSqlCommentContractTest {
}
}
assertThat(tableCount).isEqualTo(23);
assertThat(columnCount).isEqualTo(350);
assertThat(columnCount).isEqualTo(351);
assertThat(missing).as("缺少中文业务注释的Finance字段").isEmpty();
}
@@ -52,7 +52,7 @@ class FinanceSqlCommentContractTest {
void keyIsolationStatusSnapshotAndIdempotencyCommentsUseFrozenMeaning() throws Exception {
String sql = Files.readString(Path.of("src/main/resources/db/finance.sql"), StandardCharsets.UTF_8);
assertThat(sql).contains("tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键'");
assertThat(sql).contains("AVAILABLE可结算、SUPERSEDED已被新版本替代、VOIDED已作废");
assertThat(sql).contains("AVAILABLE可结算、SETTLED资金账户已结清、SUPERSEDED已被新版本替代、VOIDED已作废");
assertThat(sql).contains("RECEIVED已接收、PROCESSED已完成、FAILED处理失败");
assertThat(sql).contains("Firstleg费用来源完整不可变JSON快照");
assertThat(sql).contains("ON_ACCOUNT账期应付、DIRECT_PAID业务已付");
@@ -347,7 +347,15 @@ paths:
application/json:
schema: { $ref: '#/components/schemas/ChangeReceivingWarehouseRequest' }
responses:
'200': { description: 更新后的柜级摘要, content: { application/json: { schema: { type: object, properties: { code: { type: string, enum: ['200'] }, data: { $ref: '#/components/schemas/ContainerPlanProgressSummary' } } } } } }
'200':
description: 更新后的柜级摘要
content:
application/json:
schema:
type: object
properties:
code: { type: string, enum: ['200'] }
data: { $ref: '#/components/schemas/ContainerPlanProgressSummary' }
'400': { description: 请求无效或目标不是有效 OVERSEAS 仓 }
'409': { description: 版本冲突、幂等键冲突或PDA接柜会话已建立 }
/api/platform/v1/last-leg/orders/{id}/destination:
@@ -625,7 +633,7 @@ paths:
description: 权限 platform:overseas-warehouse:outbound:list;只按全局唯一 outboundNo 定位并从记录解析客户范围,不接收 customerId。
security: [{ bearerAuth: [] }]
parameters: [{ name: outboundNo, in: path, required: true, schema: { type: string } }]
responses: { '200': { description: "出库单、出库托盘及关联派送单详情;pallets[].palletNo 为 Warehouse 权威实物托盘码" }, '404': { description: 出库单不存在 } }
responses: { '200': { description: '出库单、出库托盘及关联派送单详情;pallets[].palletNo 为 Warehouse 权威实物托盘码' }, '404': { description: 出库单不存在 } }
/api/platform/v1/last-leg/outbound-orders/{outboundNo}/cancel:
post:
summary: 取消未执行出库单
@@ -1173,207 +1181,8 @@ paths:
- { name: relabelNo, in: path, required: true, schema: { type: string, maxLength: 64 } }
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/RelabelStatusChange' } } } }
responses: { '200': { description: 统一 ApiResponse 包装的换标单详情 }, '409': { description: 非法状态迁移或关联尾程单缺失 } }
/api/platform/v1/last-leg/relabel-applications/{relabelNo}/files:
post:
summary: 上传总换标PDF或明细换标PDF
description: 权限 platform:overseas-warehouse:relabel-order:create。仅PENDING_PROCESSING/PROCESSING可替换,PDF内容且不超过10MiB。客户从申请单解析,ITEM必须属于该申请单同客户。File类别分别为OVERSEAS_RELABEL_TOTAL_DOCUMENT(application.id)及OVERSEAS_RELABEL_ITEM_DOCUMENT(item.id)。成功保存File返回的资料元数据,旧资料不再允许通过当前绑定下载。
security: [{ bearerAuth: [] }]
parameters:
- { name: relabelNo, in: path, required: true, schema: { type: string } }
- { name: scope, in: query, required: true, schema: { type: string, enum: [TOTAL, ITEM] } }
- { name: itemId, in: query, description: ITEM必填,TOTAL不得传入, schema: { type: integer, format: int64, minimum: 1 } }
requestBody:
required: true
content:
multipart/form-data:
schema: { type: object, required: [file], properties: { file: { type: string, format: binary } } }
responses:
'200': { description: 换标详情;application包含totalFileId/totalFileName/totalFileUrl,items包含relabelFileId/relabelFileName/relabelFileUrl。URL为受权下载路径,文件ID按字符串返回 }
'400': { description: 非PDF、超限或资料范围无效 }
'404': { description: 申请或明细不属于当前客户 }
'409': { description: 换标状态不允许修改资料 }
/api/platform/v1/last-leg/relabel-applications/{relabelNo}/files/{fileId}/download:
get:
summary: 读取当前绑定的换标PDF
description: 权限 platform:overseas-warehouse:relabel-order:list;按申请单客户、资料类别、application.id或item.id及fileId精确授权。仅当前已保存资料可读,未绑定或替换的文件拒绝。响应Cache-Control为no-store。
security: [{ bearerAuth: [] }]
parameters:
- { name: relabelNo, in: path, required: true, schema: { type: string } }
- { name: fileId, in: path, required: true, schema: { type: integer, format: int64, minimum: 1 } }
- { name: scope, in: query, required: true, schema: { type: string, enum: [TOTAL, ITEM] } }
- { name: itemId, in: query, schema: { type: integer, format: int64, minimum: 1 } }
responses:
'200': { description: PDF字节, content: { application/pdf: { schema: { type: string, format: binary } } } }
'404': { description: 资料未绑定或归属不匹配 }
/api/merchant/v1/last-leg/return-orders:
get:
summary: 客户分页查询退货签收订单
description: 客户范围只取认证 Token。可按退货单号、快递单号、SKU 和签收状态过滤;实收数量以仓库 PDA 签收为准。
security: [{ bearerAuth: [] }]
parameters:
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
- { name: size, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 20 } }
- { name: returnNo, in: query, schema: { type: string } }
- { name: trackingNo, in: query, schema: { type: string } }
- { name: skuCode, in: query, schema: { type: string } }
- { name: status, in: query, schema: { type: string, enum: [PENDING_RECEIPT, PARTIALLY_RECEIVED, COMPLETED] } }
responses: { '200': { description: 当前客户退货订单分页 } }
post:
summary: 客户创建退货预报订单
description: 客户只预报不重复的 SKU 和预计数量,不要求提前知道快递单号;仓库 PDA 实收数量才是实际入库事实。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReturnOrderCreate' } } } }
responses: { '200': { description: 创建后的退货订单详情 }, '409': { description: 幂等键冲突 } }
/api/merchant/v1/last-leg/return-orders/{returnNo}:
get:
summary: 客户查询退货订单与签收批次
description: 返回各 SKU 的预报和实收数量,以及每次 PDA 签收产生的批次号和可选快递单号。
security: [{ bearerAuth: [] }]
parameters: [{ name: returnNo, in: path, required: true, schema: { type: string } }]
responses: { '200': { description: 当前客户退货订单详情 }, '404': { description: 当前客户下退货订单不存在 } }
/api/pda/v1/last-leg/return-receipts/start:
post:
summary: PDA 以退件单号开始签收
description: 仓库先从包裹收件信息取得客户填写的退件单号。仅当前绑定仓的 PENDING_RECEIPT(待签收)和 PARTIALLY_RECEIVED(签收中)退货单可开始签收,并返回该单尚未签收的 SKU 明细。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [returnNo], properties: { returnNo: { type: string, maxLength: 128 } } } } } }
responses: { '200': { description: 可开始签收的退货单及未签收 SKU 明细 }, '404': { description: 当前仓没有待签收或签收中的退货单 } }
/api/pda/v1/last-leg/return-receipts/sku-candidates:
post:
summary: PDA 扫描 SKU 查询候选退货单
description: 仅查询当前 PDA 绑定仓内待签收或签收中的退货单,最多返回 50 个候选。仓库人员根据退货单号、客户和数量选择后,再调用 start 进入统一签收流程。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [skuCode], properties: { skuCode: { type: string, maxLength: 128 } } } } } }
responses: { '200': { description: SKU 及候选退货单列表;没有匹配时 candidates 为空数组 } }
/api/pda/v1/last-leg/return-receipts/confirm:
post:
summary: PDA 确认退货签收
description: PDA 仅能操作绑定海外仓。startedBy 记录通过退货单号或 SKU 候选进入;trackingNo 为本次包裹可选快递单号。按预报可签收数量校验、禁止超收;每次成功确认创建唯一签收单及仓储计费批次。全部预报 SKU 数量吻合时自动 COMPLETED。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReturnReceiptConfirm' } } } }
responses: { '200': { description: 签收结果及自动完成状态 }, '409': { description: 超收、错绑或幂等键冲突 } }
/api/merchant/v1/last-leg/return-inventory:
get:
summary: 客户查询退货库存
description: 客户范围只取认证 Token。库存按客户、海外仓、SKU 和箱号查询,数量以仓库 PDA 实收上架数量为准。
security: [{ bearerAuth: [] }]
parameters:
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
- { name: size, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 20 } }
- { name: overseasWarehouseId, in: query, schema: { type: string, maxLength: 128 } }
- { name: skuCode, in: query, schema: { type: string, maxLength: 128 } }
- { name: cartonMark, in: query, schema: { type: string, maxLength: 128 } }
responses: { '200': { description: 当前客户退货库存分页 } }
/api/merchant/v1/last-leg/return-inventory/summary:
get:
summary: 客户查询退货库存 SKU 汇总
description: 客户范围只取认证 Token。按海外仓和 SKU 汇总 PDA 实收上架数量及可用箱数。
security: [{ bearerAuth: [] }]
parameters:
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
- { name: size, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 20 } }
- { name: overseasWarehouseId, in: query, schema: { type: string, maxLength: 128 } }
- { name: skuCode, in: query, schema: { type: string, maxLength: 128 } }
responses: { '200': { description: 当前客户 SKU 汇总库存分页 } }
/api/merchant/v1/last-leg/return-inventory/cartons/{cartonMark}:
get:
summary: 客户查询退货箱明细
description: 仅返回当前客户的箱号、签收批次和箱内 SKU 数量。
security: [{ bearerAuth: [] }]
parameters: [{ name: cartonMark, in: path, required: true, schema: { type: string, maxLength: 128 } }]
responses: { '200': { description: 当前客户退货箱明细 }, '404': { description: 当前客户下退货箱不存在 } }
/api/merchant/v1/last-leg/return-inventory/cartons/{cartonMark}/label:
get:
summary: 客户获取退货箱唛
description: 返回当前客户退货箱的可打印标签及 SKU 数量,客户可据此打印或复核箱唛。
security: [{ bearerAuth: [] }]
parameters: [{ name: cartonMark, in: path, required: true, schema: { type: string, maxLength: 128 } }]
responses: { '200': { description: 当前客户退货箱唛 }, '404': { description: 当前客户下退货箱不存在 } }
/api/pda/v1/last-leg/return-inventory/cartons/merge:
post:
summary: PDA 合并退货箱
description: 仅当前 PDA 绑定海外仓可操作。选择同一客户的已完成退货单,按查询出的 SKU 填写本箱数量并录入箱规;系统扣减可用库存并生成合箱箱号。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReturnInventoryMerge' } } } }
responses: { '200': { description: 新箱号、SKU 汇总数量及可打印箱唛 }, '409': { description: 箱状态、客户或 SKU 不满足合箱条件,或幂等键冲突 } }
/api/pda/v1/last-leg/return-inventory/packing/orders:
get:
summary: PDA 查询可合箱退货单和 SKU 库存
security: [{ bearerAuth: [] }]
parameters: [{ name: returnNo, in: query, required: false, schema: { type: string, maxLength: 128 } }]
responses: { '200': { description: 当前仓已完成签收退货单及可用 SKU 数量 } }
/api/pda/v1/last-leg/return-inventory/cartons/{cartonMark}/label:
get:
summary: PDA 获取退货箱唛
description: 返回当前绑定仓退货箱的结构化数据及 100mm x 150mm 可打印 HTML,包含箱号、仓库、客户、签收批次及 SKU 数量。
security: [{ bearerAuth: [] }]
parameters: [{ name: cartonMark, in: path, required: true, schema: { type: string, maxLength: 128 } }]
responses: { '200': { description: 退货箱唛打印数据 }, '404': { description: 当前仓没有该退货箱 } }
components:
schemas:
ReturnOrderCreate:
type: object
required: [overseasWarehouseId, needRelabel, idempotencyKey, items]
properties:
overseasWarehouseId: { type: string, maxLength: 128 }
customerReferenceNo: { type: string, maxLength: 128 }
needRelabel: { type: boolean, default: false, description: 是否需要换标 }
remark: { type: string, maxLength: 1000 }
idempotencyKey: { type: string, maxLength: 255 }
items:
type: array
minItems: 1
maxItems: 500
items:
type: object
required: [skuCode, expectedQuantity]
properties:
skuCode: { type: string, maxLength: 128 }
productNameZh: { type: string, maxLength: 255 }
expectedQuantity: { type: integer, minimum: 1 }
ReturnReceiptConfirm:
type: object
required: [returnNo, startedBy, items, idempotencyKey]
properties:
returnNo: { type: string, maxLength: 128 }
trackingNo: { type: string, nullable: true, maxLength: 128, description: '本次签收包裹有快递面单时扫描绑定;没有时为空' }
startedBy: { type: string, enum: [RETURN_NO, SKU], description: '本次通过退货单号查询或 SKU 候选开始签收' }
cartonMark: { type: string, nullable: true, maxLength: 128, description: PDA 实际绑定的箱号;不传时由系统生成 }
idempotencyKey: { type: string, maxLength: 255 }
items:
type: array
minItems: 1
maxItems: 500
items:
type: object
required: [skuCode, receivedQuantity]
properties:
skuCode: { type: string, maxLength: 128 }
receivedQuantity: { type: integer, minimum: 1 }
ReturnInventoryMerge:
type: object
required: [returnNos, items, length, width, height, weight, idempotencyKey]
properties:
returnNos:
type: array
minItems: 1
maxItems: 100
items: { type: string, minLength: 1, maxLength: 128 }
items:
type: array
minItems: 1
maxItems: 500
items:
type: object
required: [skuCode, quantity]
properties:
skuCode: { type: string, maxLength: 128 }
quantity: { type: integer, minimum: 1 }
length: { type: number, minimum: 0, exclusiveMinimum: true, description: '箱长,CM' }
width: { type: number, minimum: 0, exclusiveMinimum: true, description: '箱宽,CM' }
height: { type: number, minimum: 0, exclusiveMinimum: true, description: '箱高,CM' }
weight: { type: number, minimum: 0, exclusiveMinimum: true, description: '箱重,KG' }
idempotencyKey: { type: string, minLength: 1, maxLength: 255 }
InventoryBalance:
type: object
required: [id, customerId, totalPackages, availablePackages, holdPackages, allocatedPackages, pickedPackages, inTransitPackages, receivingPackages, procurementPackages]
@@ -1543,9 +1352,9 @@ components:
supplierName: { type: string, maxLength: 200 }
supplierCategory: { type: string, maxLength: 32 }
settlementMode: { type: string, enum: [ON_ACCOUNT, DIRECT_PAID] }
chargeableQuantity: { type: number, exclusiveMinimum: 0 }
chargeableQuantity: { type: number, minimum: 0, exclusiveMinimum: true }
quantityUnit: { type: string, maxLength: 32 }
unitPrice: { type: number, exclusiveMinimum: 0 }
unitPrice: { type: number, minimum: 0, exclusiveMinimum: true }
periodStart: { type: string, format: date }
periodEnd: { type: string, format: date }
LastMileFee:
@@ -2064,14 +1873,14 @@ components:
feeName: { type: string, maxLength: 128 }
direction: { type: string, enum: [RECEIVABLE, PAYABLE] }
currency: { type: string, enum: [CNY, USD] }
amount: { type: number, exclusiveMinimum: 0 }
amount: { type: number, minimum: 0, exclusiveMinimum: true }
supplierId: { type: string, pattern: '^[1-9][0-9]*$' }
supplierName: { type: string, maxLength: 200 }
supplierCategory: { type: string, maxLength: 32 }
settlementMode: { type: string, enum: [ON_ACCOUNT, DIRECT_PAID] }
chargeableQuantity: { type: number, exclusiveMinimum: 0 }
chargeableQuantity: { type: number, minimum: 0, exclusiveMinimum: true }
quantityUnit: { type: string, maxLength: 32 }
unitPrice: { type: number, exclusiveMinimum: 0 }
unitPrice: { type: number, minimum: 0, exclusiveMinimum: true }
periodStart: { type: string, format: date }
periodEnd: { type: string, format: date }
remark: { type: string, nullable: true, maxLength: 500 }
@@ -2161,7 +1970,7 @@ components:
required: [customerOrderNo, labelDeadline, contactName, contactPhone, chargeAmount, idempotencyKey, items]
properties:
customerOrderNo: { type: string, maxLength: 128 }
operationWarehouseId: { type: string, maxLength: 128, description: 换标操作所在海外中转仓,和FBA目的仓分离 }
overseasWarehouseId: { type: string, nullable: true, maxLength: 128, description: 统一目的海外仓;明细未填写目的仓时使用 }
labelDeadline: { type: string, format: date-time }
contactName: { type: string, maxLength: 128 }
contactPhone: { type: string, maxLength: 64 }
@@ -2171,11 +1980,8 @@ components:
items: { type: array, minItems: 1, maxItems: 500, items: { $ref: '#/components/schemas/RelabelItem' } }
RelabelItem:
type: object
required: [sourceInventoryBalanceId, sourceInventoryPositionId, sourceInboundOrderId, productNameZh, totalPackageCount, notifiedPackageCount, shipmentNo, cartonMark]
required: [productNameZh, totalPackageCount, notifiedPackageCount, shipmentNo, newSkuCode, cartonMark, fbaCartonMark]
properties:
sourceInventoryBalanceId: { type: string, pattern: '^[0-9]+$', description: 来源库存余额ID }
sourceInventoryPositionId: { type: string, pattern: '^[0-9]+$', description: 来源库存仓位ID;同箱任务唯一冻结依据 }
sourceInboundOrderId: { type: string, pattern: '^[0-9]+$', description: 来源历史入仓单ID }
skuCode: { type: string, nullable: true, maxLength: 128, description: 商品 SKU(选填) }
productImageFileId: { type: string, nullable: true, maxLength: 128 }
productImageFileName: { type: string, nullable: true, maxLength: 255 }
@@ -2185,24 +1991,14 @@ components:
notifiedPackageCount: { type: integer, minimum: 1 }
shipmentNo: { type: string, maxLength: 128 }
trackingCode: { type: string, nullable: true, maxLength: 255 }
newSkuCode: { type: string, nullable: true, maxLength: 128, description: 箱唛换标时的单个新SKU兼容字段 }
newSkuCode: { type: string, maxLength: 128 }
labelRequirement: { type: string, nullable: true, maxLength: 1000 }
relabelFileId: { type: string, nullable: true, maxLength: 128 }
relabelFileName: { type: string, nullable: true, maxLength: 255 }
relabelFileUrl: { type: string, nullable: true, maxLength: 1000 }
cartonMark: { type: string, maxLength: 255, description: 我方箱唛;同一申请内不可重复,整箱库存只冻结一次 }
fbaCartonMark: { type: string, nullable: true, maxLength: 255, description: 新FBA箱唛;箱唛或组合换标必填 }
destinationFbaWarehouseCode: { type: string, nullable: true, maxLength: 128, description: FBA目的仓编码;箱唛或组合换标必填 }
operationType: { type: string, enum: [CARTON_LABEL, SKU_LABEL, CARTON_AND_SKU], description: 箱唛换标、SKU换标或组合换标 }
skuItems: { type: array, maxItems: 100, description: 同一箱唛内SKU换标子明细,数量按件;SKU或组合换标必填, items: { $ref: '#/components/schemas/RelabelSkuItem' } }
RelabelSkuItem:
type: object
required: [skuCode, newSkuCode, quantity]
properties:
skuCode: { type: string, maxLength: 128, description: 原SKU }
newSkuCode: { type: string, maxLength: 128, description: 换标后SKU }
quantity: { type: integer, minimum: 1, description: 换标件数 }
remark: { type: string, nullable: true, maxLength: 1000 }
cartonMark: { type: string, maxLength: 255, description: 客户自有箱唛(不在编辑列表展示) }
fbaCartonMark: { type: string, maxLength: 255, description: 关联 FBA 箱唛 }
destinationWarehouseId: { type: string, nullable: true, maxLength: 128, description: 该箱目的海外仓;为空时使用申请统一目的仓 }
RelabelStatusChange:
type: object
required: [status]