merge: integrate TEST updates and verify appointment release

This commit is contained in:
2026-10-15 17:08:30 +08:00
72 changed files with 2804 additions and 164 deletions
@@ -9,9 +9,44 @@ info:
photo sets are immutable; identical replay does not deduct stock or create evidence again.
Platform and merchant details expose only submitted photos, including photos of completed
pallets while other pallets are still pending. Historic applications are not backfilled.
Approval atomically creates one DISCARD_OUTBOUND document and planned SKU/pallet snapshots.
Platform/merchant summary and PDA pallet task include nullable outboundNo. PDA completion
synchronizes actual quantities and OUTBOUNDED status without a second inventory deduction.
Cancellation must use the discard application workflow. PDA resolve optionally accepts
discardNo together with palletNo; ambiguous scans require selection of a discard application.
Candidates include INVENTORY positions from every document type with available warehouse stock,
plus DROPSHIP and RETURN_CARTON sources. Create items identify exactly one of orderId,
returnCartonId or inventoryPositionId and carry selected cartonIds for partial whole-carton disposal; quantities must match those IDs.
RETURN_CARTON selections consume every SKU in the selected physical carton.
Candidates return cartons, cartonSelectionSupported and cartonSelectionBlockReason.
Stock without complete carton/pallet ownership remains available for full disposal only.
PDA tasks return scanMode PALLET or CARTON and the real cartonMarks for loose stock.
Bound pallets require their pallet number; loose stock requires every distinct carton mark scanned,
or confirmAllCartons=true. The loose group uses 散箱 as its API path key, never as a scan code.
The application may select a subset of available cartons. PDA must finish every selected carton;
short actual quantities are rejected. At least one photo remains required.
Partial bound-pallet tasks use CARTON scanMode with the real palletNo and selected cartonMarks.
PDA completion leaves the application in PDA_COMPLETED (waiting for customer confirmation).
Only the authenticated merchant may confirm completion after reviewing submitted PDA photos.
Confirmation changes the application to COMPLETED, records customerConfirmedAt, and never deducts
inventory again. Identical repeated confirmation preserves the first timestamp and history.
Platform users cannot close the application on behalf of the customer.
security:
- bearerAuth: []
paths:
/api/merchant/v1/last-leg/cargo-discards/{discardNo}/confirm-completion:
parameters:
- $ref: '#/components/parameters/DiscardNo'
post:
summary: 客户确认废弃完结
description: >-
Customer scope is taken only from the authenticated merchant token.
Requires PDA_COMPLETED; repeated COMPLETED calls return the existing detail.
No request customerId, stock adjustment, or new photo upload is accepted.
The client shows submitted PDA photos before the customer confirms.
x-permissions: [merchant:overseas-warehouse:discard:confirm]
responses:
'200': { description: ApiResponse with updated Detail; summary contains customerConfirmedAt }
/api/pda/v1/last-leg/cargo-discards/{discardNo}/pallets/{palletNo}/photos/files:
parameters:
- $ref: '#/components/parameters/DiscardNo'
@@ -68,7 +103,16 @@ paths:
required: [itemId, actualPackages]
properties:
itemId: { type: string, pattern: '^[1-9][0-9]*$' }
actualPackages: { type: integer, minimum: 0 }
actualPackages: { type: integer, minimum: 1, description: Must equal plannedPackages; must finish the application selection }
scannedCartonMarks:
type: array
uniqueItems: true
items: { type: string }
description: All task cartonMarks must be scanned unless confirmAllCartons is true.
confirmAllCartons:
type: boolean
default: false
description: Explicitly confirm all loose cartons without individual scans; photos remain mandatory.
photoFileIds:
type: array
minItems: 1
@@ -4,6 +4,67 @@ info:
description: 派送资料完整契约见同目录 delivery-documents-v1.yaml;派送列表/详情行返回 bolNo、documentSummary(三类状态与x/3),状态仍使用 version 和 allowedActions。
version: 1.0.0
paths:
/api/pda/v1/last-leg/warehouse-transfers/{transferNo}/carton-label:
get:
summary: PDA读取转运箱标签
description: 需要 lastleg:pda:operate 与 OVERSEAS_WAREHOUSE_PDA_OPERATOR;只可读取Token绑定仓的转运单标签,包含新增实收箱号。
parameters:
- {name: transferNo, in: path, required: true, schema: {type: string}}
responses:
'200': {description: 统一业务响应,成功data为包含printableHtml的箱标签}
/api/pda/v1/last-leg/warehouse-transfers/{transferNo}/pallets/drafts:
post:
summary: 先创建转运签收托盘草稿
description: 仅绑定仓PDA可操作,提交幂等键创建空托盘草稿,再调用generate生成实物托盘号。不接收手工箱数、尺寸、体积和重量,箱子清点时按归属自动汇总。
parameters:
- {name: transferNo, in: path, required: true, schema: {type: string}}
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [idempotencyKey]
properties:
palletId: {type: integer, format: int64, nullable: true}
idempotencyKey: {type: string, maxLength: 255}
responses:
'200': {description: 统一业务响应,data为含空托盘草稿的转运详情}
/api/pda/v1/last-leg/warehouse-transfers/{transferNo}/cartons/count:
post:
summary: 转运逐箱测量与清点
description: 仅限 lastleg:pda:operate 与 OVERSEAS_WAREHOUSE_PDA_OPERATOR,绑定仓由统一Token确定。先创建本单托盘号,每箱选择所属托盘并按申报SKU记录实点件数,托盘体积重量由分配箱子汇总,全部实收箱子测量清点后才可完成打托入库,打托箱数与已清点实收箱数核平;短收箱保留未收到事实,超收生成系统箱号;差异需原因。事务锁定转运单;原键原载荷重试不重复写入,同键不同载荷拒绝,已确认事实不可覆盖。
parameters:
- {name: transferNo, in: path, required: true, schema: {type: string}}
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [idempotencyKey, boxes]
properties:
idempotencyKey: {type: string, maxLength: 255}
discrepancyReason: {type: string, maxLength: 1000, description: 短收超收时必填差异原因}
boxes:
type: array
minItems: 1
maxItems: 5000
items:
type: object
required: [transferPalletId, length, width, height, weight, skuCode, quantity]
properties:
cartonMark: {type: string, maxLength: 255, nullable: true, description: 已有箱号;新增超收箱不传,由服务端生成}
transferItemId: {type: integer, format: int64, nullable: true, description: 新增超收箱所属本单申报明细}
transferPalletId: {type: integer, format: int64, description: 本单已生成的转运托盘事实ID,逐箱关联,托盘箱数体积重量由箱子汇总}
length: {type: number, minimum: 0.01, description: 箱长厘米}
width: {type: number, minimum: 0.01, description: 箱宽厘米}
height: {type: number, minimum: 0.01, description: 箱高厘米}
weight: {type: number, minimum: 0.01, description: 箱重千克}
skuCode: {type: string, maxLength: 255}
quantity: {type: integer, minimum: 1, maximum: 2147483647, description: 箱内实点件数}
responses:
'200': {description: 统一业务响应,成功data为转运详情,cartons含逐箱尺寸重量件数及清点操作人时间}
/api/platform/v1/last-leg/inventory/balances/{balanceId}:
get:
summary: 查询单条库存余额详情
@@ -1214,7 +1275,7 @@ paths:
responses: { '200': { description: 当前客户仓储转运单分页 }, '403': { description: 缺少仓储转运查看权限 } }
post:
summary: 客户创建仓储转运单
description: 权限 merchant:overseas-warehouse:transfer:create;客户范围只取认证 Token。目的去向可暂未确定(destinationType 为 NONE 或不传),允许保存草稿、编辑和提交;入库确认后可在出库时绑定 FBA 仓或私人仓,也可选择入库库存发起贴标申请出库;普通转运出库前必须补齐目的仓。
description: 权限 merchant:overseas-warehouse:transfer:create;客户范围只取认证 Token。货物 items[].quantity 为 SKU 申报件数(正整数),items[].packageCount 为预计箱数,整单 packageCount 等于明细预计箱数合计;件数与箱数独立,实际签收箱数和每箱件数以 PDA 清点事实为准,支持短收、超收并记录差异原因。目的去向可暂未确定(destinationType 为 NONE 或不传),允许保存草稿、编辑和提交;入库确认后可在出库时绑定 FBA 仓或私人仓,也可选择入库库存发起贴标申请出库;普通转运出库前必须补齐目的仓。
security: [{ bearerAuth: [] }]
responses: { '200': { description: 创建后的仓储转运单 }, '403': { description: 缺少仓储转运创建权限 }, '409': { description: 幂等、库存或状态冲突 } }
/api/merchant/v1/last-leg/warehouse-transfers/{transferNo}:
@@ -1883,6 +1944,15 @@ components:
description: 保留原转运详情字段;新增持久实收命令投影供平台与当前客户恢复。成功实收返回本地事务完成后的命令状态,不暴露内部资源锁或reservationKey。
required: [receiptCommands]
properties:
packageCount: { type: integer, description: 下单预计箱数,不随实际签收覆盖 }
items:
type: array
items:
type: object
properties:
quantity: { type: integer, format: int64, nullable: true, description: SKU 申报件数,历史未申报为 null }
packageCount: { type: integer, description: 该 SKU 预计箱数 }
cartons: { type: array, items: { type: object }, description: 实际签收仅统计状态 COUNTED 的箱子 }
receiptCommands:
type: array
items: { $ref: '#/components/schemas/WarehouseTransferReceiptCommand' }
@@ -3214,7 +3284,7 @@ components:
chargeAmount: { type: number, nullable: true, minimum: 0.01, description: 本次贴标费用(CNY);未传时不冻结或结算资金 }
remark: { type: string, nullable: true, maxLength: 1000 }
idempotencyKey: { type: string, maxLength: 255 }
deliveryMethod: { type: string, nullable: true, enum: [TRUCK, EXPRESS, TRUCK_PICKUP, EXPRESS_PICKUP, PICKUP], description: 只换箱唛或SKU与箱唛同时换时必填;只换SKU时不传 }
deliveryMethod: { type: string, nullable: true, enum: [TRUCK, EXPRESS, AMAZON_PICKUP, CUSTOMER_PICKUP, PICKUP], description: 只换箱唛或SKU与箱唛同时换时必填;只换SKU时不传 }
printShippingLabel: { type: boolean, default: false, description: 仅快递派送可选,客户确认前必须已上传面单 }
items: { type: array, minItems: 1, maxItems: 500, items: { $ref: '#/components/schemas/RelabelItem' } }
RelabelItem:
@@ -415,9 +415,10 @@ paths:
/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/complete:
post:
summary: 提交现场任务到复核池并直接等待客户确认
description: 仓储转运换标须先确认所有涉及托盘的有效实测尺寸和重量,含拆托后原托盘剩余货物;手动完成也不能跳过实测校验。缺失或库存版本变化导致实测失效时,保持处理中并提示补测。
parameters: [{ name: relabelNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/RelabelComplete' } } } }
responses: { '200': { description: WAIT_REVIEW任务, content: { application/json: { schema: { $ref: '#/components/schemas/RelabelDetailResponse' } } } }, '409': { description: 箱数未核平、照片或版本冲突 } }
responses: { '200': { description: WAIT_REVIEW任务, content: { application/json: { schema: { $ref: '#/components/schemas/RelabelDetailResponse' } } } }, '409': { description: 托盘实测缺失或失效、箱数未核平、照片或版本冲突 } }
/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/review:
post:
summary: 已停用;客户确认完成复核,退回重贴由平台执行