openapi: 3.0.3 info: title: Phase 3 Container PDA Picking API version: 1.0.0 paths: /api/platform/v1/first-leg/pda-loading/tasks: get: summary: Find the active container picking task in one platform warehouse parameters: - {name: cabinetNo, in: query, required: false, schema: {type: string}} - {name: loadingPlanId, in: query, required: false, schema: {type: integer, format: int64}} - {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}} responses: '200': description: Task with assignment, review, stable carton codes and item-level versions content: {application/json: {schema: {$ref: '#/components/schemas/Task'}}} /api/platform/v1/first-leg/pda-loading/tasks/pending: get: summary: List active tasks available to the authenticated PDA operator parameters: - {name: keyword, in: query, required: false, schema: {type: string, maxLength: 64}} responses: '200': description: Unassigned tasks and tasks assigned to the current operator, including AWAITING_REVIEW tasks with zero unresolved items so PDA can enter evidence and final completion; no customer selector required content: application/json: schema: type: array items: {$ref: '#/components/schemas/PendingTask'} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}: get: summary: Read one PDA task by stable task identity parameters: - {$ref: '#/components/parameters/TaskId'} - {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}} responses: '200': description: Task with current transport display and ACTIVE container lifecycle fields content: {application/json: {schema: {$ref: '#/components/schemas/Task'}}} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/claim: post: summary: Atomically claim an unassigned task for the authenticated operator description: Claiming the canonical PDA task idempotently starts its plan and advances every associated inbound from WAIT_SPLIT to LOADING with optimistic locking; workflowStatus may later become AWAITING_REVIEW while the task remains LOADING until manual completion. parameters: [{$ref: '#/components/parameters/TaskId'}] requestBody: required: true content: {application/json: {schema: {$ref: '#/components/schemas/ClaimRequest'}}} responses: '200': {description: Claimed task or A0409 when another operator owns it} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/inventory-snapshot: get: summary: Read the current Warehouse inventory snapshot before atomic submission parameters: - {$ref: '#/components/parameters/TaskId'} - {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}} responses: '200': description: Current occupied resources grouped by inbound order with a stable snapshot token content: {application/json: {schema: {$ref: '#/components/schemas/InventorySnapshot'}}} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/assign: post: summary: Assign or reassign a task with optimistic locking parameters: [{$ref: '#/components/parameters/TaskId'}] requestBody: required: true content: {application/json: {schema: {$ref: '#/components/schemas/AssignRequest'}}} responses: '200': {description: Assigned task or A0409 on assignment version conflict} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/items/{itemId}/pick: post: summary: Increment a picked item owned by the authenticated assignee description: The first successful pick also repairs the same WAIT_SPLIT to LOADING transition when the plan was already started, without using containerNo as an association key. parameters: [{$ref: '#/components/parameters/TaskId'}, {$ref: '#/components/parameters/ItemId'}] responses: {'200': {description: Updated task}} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/items/{itemId}/partial: post: summary: Record the final partial quantity for one assigned item parameters: [{$ref: '#/components/parameters/TaskId'}, {$ref: '#/components/parameters/ItemId'}] responses: {'200': {description: Task awaiting review when every item is resolved}} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/items/{itemId}/skip: post: summary: Resolve an untouched assigned item as skipped parameters: [{$ref: '#/components/parameters/TaskId'}, {$ref: '#/components/parameters/ItemId'}] responses: {'200': {description: Task awaiting review when every item is resolved}} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/batch: post: summary: Atomically submit the complete online PDA draft once description: A batch containing PICK or PARTIAL idempotently advances associated inbound process status to LOADING before evidence operations become available. parameters: - {$ref: '#/components/parameters/TaskId'} - {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}} requestBody: required: true content: {application/json: {schema: {$ref: '#/components/schemas/BatchRequest'}}} responses: '200': {description: 'Completed task, identical replay result, or A0409 when any snapshot changed'} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/review: post: summary: 人工确认装柜完成的兼容入口 parameters: [{$ref: '#/components/parameters/TaskId'}] requestBody: required: true content: {application/json: {schema: {$ref: '#/components/schemas/ReviewRequest'}}} responses: '200': {description: 操作员明确确认后完成装柜} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/complete: post: summary: PDA拣货全部处理后人工确认装柜完成 description: PDA拣货只锁定数量和仓储资源。本接口先无写入校验全部关联入库单的RELEASED订舱、稳定bridge和五类ACTIVE证据;拖车状态不是前置门禁。若当前稳定containerBookingId没有有效拖车则直接继续;恰有一条有效拖车时由服务端加锁推进为COMPLETED并写历史;多条有效拖车或并发修改返回A0409且全部Firstleg本地状态回滚。随后在同一事务内结算资源、完成task/plan并推进inbound为LOADED。同operationId重放返回冻结结果,不重复推进拖车或写历史。 parameters: [{$ref: '#/components/parameters/TaskId'}] requestBody: required: true content: {application/json: {schema: {$ref: '#/components/schemas/CompleteLoadingRequest'}}} responses: '200': {description: 完成任务;存在未处理明细或版本冲突时返回A0409} /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/operations: get: summary: Read immutable task-level claim, assignment and review audit records parameters: [{$ref: '#/components/parameters/TaskId'}] responses: '200': {description: Ordered operation history} components: parameters: TaskId: {name: taskId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}} ItemId: {name: itemId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}} schemas: Task: type: object required: [id, loadingPlanId, containerBookingId, inboundId, inboundVersion, loadingVersion, cabinetNo, sourceType, recordStatus, taskStatus, workflowStatus, version, items] properties: id: {type: integer, format: int64} loadingPlanId: {type: integer, format: int64} containerBookingId: {type: integer, format: int64} inboundId: {type: integer, format: int64, description: Stable inbound identity selected from the task item; evidence identity is taskId+inboundId+loadingPlanId+containerBookingId} inboundVersion: {type: integer, minimum: 0, description: Current fl_inbound_order.version for inboundId; never inferred from PDA task version} loadingVersion: {type: integer, minimum: 0, description: Compatibility alias of inboundVersion from the same fl_inbound_order row; /loading/complete uses this inbound loading-stage version} cabinetNo: {type: string} currentContainerNo: {type: string, nullable: true, description: Mutable transport display field; changing it never resynchronizes the task or item versions and it is never task identity} sourceType: {type: string, enum: [SIMULATION, OCEAN_BOOKING], description: Source of the surviving stable container identity} recordStatus: {type: string, enum: [ACTIVE], description: Task detail is served only from the ACTIVE container booking currently owned by the loading plan} mergedIntoContainerBookingId: {type: integer, format: int64, nullable: true, description: Always null for the surviving ACTIVE operational container; populated only on hidden MERGED source history} taskStatus: {type: string, enum: [LOADING, COMPLETED]} workflowStatus: {type: string, enum: [UNASSIGNED, IN_PROGRESS, AWAITING_REVIEW, COMPLETED]} version: {type: integer, minimum: 0} assignment: {type: object, nullable: true} review: {type: object, nullable: true} items: type: array items: {$ref: '#/components/schemas/PickItem'} PickItem: type: object required: [id, loadingPlanItemId, tenantId, inboundId, inboundDetailId, boxCodes, plannedPackageCount, pickedPackageCount, itemStatus, version] properties: id: {type: integer, format: int64} loadingPlanItemId: {type: integer, format: int64} tenantId: {type: integer, format: int64, description: Hidden isolation key for subsequent PDA evidence calls} inboundId: {type: integer, format: int64} inboundDetailId: {type: integer, format: int64} orderNo: {type: string, nullable: true} inboundCode: {type: string, nullable: true} cargoName: {type: string, nullable: true} boxCodes: type: array description: Stable carton label codes; one successful scan represents one carton items: {type: string} plannedPackageCount: {type: integer, minimum: 1} pickedPackageCount: {type: integer, minimum: 0} itemStatus: {type: string, enum: [PENDING, PICKING, PICKED, PARTIAL, SKIPPED]} version: {type: integer, minimum: 0} PendingTask: type: object required: [taskId, loadingPlanId, containerBookingId, cabinetNo, sourceType, recordStatus, warehouseId, taskStatus, workflowStatus, version, assignmentVersion, itemCount, pendingItemCount] properties: taskId: {type: integer, format: int64} loadingPlanId: {type: integer, format: int64} containerBookingId: {type: integer, format: int64} cabinetNo: {type: string} currentContainerNo: {type: string, nullable: true} sourceType: {type: string, enum: [SIMULATION, OCEAN_BOOKING]} recordStatus: {type: string, enum: [ACTIVE], description: Enforced by the server-side pending-task query; PDA clients must not infer or relax this filter} mergedIntoContainerBookingId: {type: integer, format: int64, nullable: true, description: Always null for an ACTIVE pending task} warehouseId: {type: integer, format: int64} warehouseName: {type: string, nullable: true} taskStatus: {type: string, enum: [LOADING]} workflowStatus: {type: string, enum: [UNASSIGNED, IN_PROGRESS, AWAITING_REVIEW], description: Server projection aligned with task detail; assigned LOADING tasks with pendingItemCount=0 remain visible as AWAITING_REVIEW} version: {type: integer, minimum: 0} assignmentVersion: {type: integer, minimum: 0} itemCount: {type: integer, minimum: 1} pendingItemCount: {type: integer, minimum: 0} ClaimRequest: type: object required: [expectedAssignmentVersion, operationId] properties: expectedAssignmentVersion: {type: integer, minimum: 0} operationId: {type: string, maxLength: 128} AssignRequest: type: object required: [assigneeId, assigneeName, expectedAssignmentVersion, operationId] properties: assigneeId: {type: integer, format: int64, minimum: 1} assigneeName: {type: string, maxLength: 64} expectedAssignmentVersion: {type: integer, minimum: 0} operationId: {type: string, maxLength: 128} BatchRequest: type: object required: [warehouseId, loadingPlanId, containerBookingId, expectedVersion, inventorySnapshotToken, items] properties: warehouseId: {type: integer, format: int64, minimum: 1} loadingPlanId: {type: integer, format: int64, minimum: 1} containerBookingId: {type: integer, format: int64, minimum: 1} expectedVersion: {type: integer, minimum: 0} inventorySnapshotToken: {type: string, minLength: 64, maxLength: 64} items: type: array minItems: 1 maxItems: 500 items: {$ref: '#/components/schemas/BatchItemRequest'} BatchItemRequest: type: object required: [itemId, action, version] properties: itemId: {type: integer, format: int64, minimum: 1} action: {type: string, enum: [PICK, PARTIAL, SKIP]} pickedPackageCount: {type: integer, minimum: 1, description: Incremental count required by PICK} finalPickedPackageCount: {type: integer, minimum: 1, description: Final actual count required by PARTIAL} releaseMode: {type: string, enum: [AUTO, MANUAL]} reservationIds: type: array items: {type: integer, format: int64, minimum: 1} version: {type: integer, minimum: 0} reason: {type: string, maxLength: 500, description: Required by SKIP} ReviewRequest: type: object required: [expectedTaskVersion, operationId] properties: expectedTaskVersion: {type: integer, minimum: 0} operationId: {type: string, maxLength: 128} remark: {type: string, maxLength: 500} CompleteLoadingRequest: type: object required: [expectedTaskVersion, operationId] properties: expectedTaskVersion: {type: integer, minimum: 0} operationId: {type: string, maxLength: 128} remark: {type: string, maxLength: 500} InventorySnapshot: type: object required: [taskId, loadingPlanId, containerBookingId, cabinetNo, taskVersion, warehouseId, capturedAt, snapshotToken, inbounds] properties: taskId: {type: integer, format: int64} loadingPlanId: {type: integer, format: int64} containerBookingId: {type: integer, format: int64} cabinetNo: {type: string} taskVersion: {type: integer, minimum: 0} currentContainerNo: {type: string, nullable: true} warehouseId: {type: integer, format: int64} capturedAt: {type: string, format: date-time} snapshotToken: {type: string, minLength: 64, maxLength: 64} inbounds: type: array items: {$ref: '#/components/schemas/InboundInventory'} InboundInventory: type: object required: [inboundId, availablePackageCount, resources] properties: inboundId: {type: integer, format: int64} availablePackageCount: {type: integer, minimum: 0} resources: type: array items: {$ref: '#/components/schemas/InventoryResource'} InventoryResource: type: object required: [reservationId, occupancyId, palletId, locationId, sharedOverflow, availablePackageCount] properties: reservationId: {type: integer, format: int64} occupancyId: {type: integer, format: int64} palletId: {type: integer, format: int64} locationId: {type: integer, format: int64} locationCode: {type: string, nullable: true} sharedOverflow: {type: boolean} availablePackageCount: {type: integer, minimum: 1}