diff --git a/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java index f4d561e1c..d1182edb7 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java @@ -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业务已付"); diff --git a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml index d01bd539c..0a6161332 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml @@ -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]