Merge origin/dev into test preserving warehouse and PDA workflows

This commit is contained in:
2026-09-14 11:03:47 +08:00
103 changed files with 3033 additions and 225 deletions
@@ -114,7 +114,7 @@ CREATE TABLE IF NOT EXISTS wh_pallet_operation (
request_hash VARCHAR(128) NOT NULL COMMENT '状态变更请求摘要', warehouse_id BIGINT NOT NULL COMMENT '实际海外仓ID',
pallet_id BIGINT NOT NULL COMMENT '实物托盘ID', pallet_code VARCHAR(128) NOT NULL COMMENT '实物托盘码快照',
before_status VARCHAR(20) NOT NULL COMMENT '变更前托盘状态', after_status VARCHAR(20) NOT NULL COMMENT '变更后托盘状态',
before_location_id BIGINT NOT NULL COMMENT '变更前库位ID', after_location_id BIGINT NOT NULL COMMENT '变更后库位ID',
before_location_id BIGINT NULL COMMENT '变更前库位ID,无库位托盘为空', after_location_id BIGINT NULL COMMENT '变更后库位ID,无库位托盘为空',
before_version INT NOT NULL COMMENT '变更前托盘版本', after_version INT NOT NULL COMMENT '变更后托盘版本',
operation_status VARCHAR(32) NOT NULL COMMENT '操作状态:APPLIED已应用、COMPENSATED已补偿',
reason VARCHAR(500) NULL COMMENT '操作原因', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
@@ -123,6 +123,10 @@ CREATE TABLE IF NOT EXISTS wh_pallet_operation (
KEY idx_wh_pallet_operation_pallet (warehouse_id,pallet_id,created_at)
) ENGINE=InnoDB COMMENT='实物托盘状态幂等变更与补偿记录';
ALTER TABLE wh_pallet_operation
MODIFY COLUMN before_location_id BIGINT NULL COMMENT '变更前库位ID,无库位托盘为空',
MODIFY COLUMN after_location_id BIGINT NULL COMMENT '变更后库位ID,无库位托盘为空';
CREATE TABLE IF NOT EXISTS wh_pallet_batch (
id BIGINT NOT NULL COMMENT '批量托盘预留ID',
warehouse_id BIGINT NOT NULL COMMENT '实际海外仓ID',
@@ -0,0 +1,132 @@
openapi: 3.0.3
info:
title: Overseas warehouse PDA pallet transition internal API
version: 1.0.0
paths:
/internal/v1/warehouse-pallets/resolve/{palletCode}:
get:
summary: Resolve a public pallet for PDA operations
description: Location fields are nullable because overseas tail-leg operations may work by pallet only.
security: [{ internalServiceToken: [] }]
parameters:
- in: path
name: palletCode
required: true
schema: { type: string }
responses:
'200':
description: Pallet resolution result
content:
application/json:
schema: { $ref: '#/components/schemas/PalletResolveApiResponse' }
/internal/v1/pda-pallet-transitions/{palletId}:
post:
summary: Apply an idempotent PDA pallet state transition
security: [{ internalServiceToken: [] }]
parameters:
- in: path
name: palletId
required: true
schema: { type: integer, format: int64, minimum: 1 }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/PalletTransitionApply' }
responses:
'200':
description: Transition result
content:
application/json:
schema: { $ref: '#/components/schemas/PalletTransitionApiResponse' }
/internal/v1/pda-pallet-transitions/{palletId}/{operationKey}/compensate:
post:
summary: Compensate an applied PDA pallet transition
security: [{ internalServiceToken: [] }]
parameters:
- in: path
name: palletId
required: true
schema: { type: integer, format: int64, minimum: 1 }
- in: path
name: operationKey
required: true
schema: { type: string, maxLength: 128 }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/PalletTransitionCompensate' }
responses:
'200':
description: Compensation result
content:
application/json:
schema: { $ref: '#/components/schemas/PalletTransitionApiResponse' }
components:
securitySchemes:
internalServiceToken: { type: apiKey, in: header, name: X-Internal-Service-Token }
schemas:
PalletTransitionApply:
type: object
required: [warehouseId, expectedVersion, targetStatus, operationKey]
properties:
warehouseId: { type: string, pattern: '^[1-9][0-9]*$' }
expectedVersion: { type: integer, minimum: 0 }
targetStatus: { type: string, enum: [IDLE, OCCUPIED] }
targetLocationId:
type: string
pattern: '^[1-9][0-9]*$'
nullable: true
description: Optional; omitted for pallet-only operations without location management
operationKey: { type: string, maxLength: 128 }
reason: { type: string, maxLength: 500, nullable: true }
PalletTransitionCompensate:
type: object
required: [warehouseId]
properties:
warehouseId: { type: string, pattern: '^[1-9][0-9]*$' }
PalletResolveResult:
type: object
required: [palletId, palletCode, warehouseId, status, unitPrice, version, reservable]
properties:
palletId: { type: string, pattern: '^[1-9][0-9]*$' }
palletCode: { type: string }
warehouseId: { type: string, pattern: '^[1-9][0-9]*$' }
locationId: { type: string, pattern: '^[1-9][0-9]*$', nullable: true }
status: { type: string }
fbaWarehouseCode: { type: string, nullable: true }
unitPrice: { type: number }
version: { type: integer, minimum: 0 }
locationType: { type: string, nullable: true }
locationStatus: { type: string, nullable: true }
reservable: { type: boolean }
reservationBlockReason: { type: string, nullable: true }
PalletTransitionResult:
type: object
required: [operationKey, palletId, palletCode, warehouseId, beforeStatus, afterStatus, beforeVersion, afterVersion, operationStatus, replayed]
properties:
operationKey: { type: string }
palletId: { type: string, pattern: '^[1-9][0-9]*$' }
palletCode: { type: string }
warehouseId: { type: string, pattern: '^[1-9][0-9]*$' }
beforeStatus: { type: string }
afterStatus: { type: string }
beforeLocationId: { type: string, pattern: '^[1-9][0-9]*$', nullable: true }
afterLocationId: { type: string, pattern: '^[1-9][0-9]*$', nullable: true }
beforeVersion: { type: integer }
afterVersion: { type: integer }
operationStatus: { type: string }
replayed: { type: boolean }
PalletTransitionApiResponse:
type: object
properties:
code: { type: string, example: '200' }
message: { type: string }
data: { $ref: '#/components/schemas/PalletTransitionResult' }
PalletResolveApiResponse:
type: object
properties:
code: { type: string, example: '200' }
message: { type: string }
data: { $ref: '#/components/schemas/PalletResolveResult' }