feat(firstleg): close phase3 outbound customs and schedule gates

This commit is contained in:
2026-08-08 22:36:41 +08:00
parent 789d97675b
commit 66d1aa0d3d
81 changed files with 5196 additions and 36 deletions
@@ -0,0 +1,93 @@
openapi: 3.0.3
info:
title: Phase 3 Customs Collaboration API
version: 1.0.0
description: Immutable checklist and file versions, supplement requests, frozen agent batches, receipts and history.
paths:
/api/platform/v1/customs/checklist-versions:
post:
summary: Create an immutable customs document checklist version
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
responses: {'200': {description: Checklist version}}
/api/platform/v1/customs/cases:
get:
summary: Page customs cases
responses: {'200': {description: Page result}}
post:
summary: Create a customer/order/inbound/container scoped customs case
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
responses: {'200': {description: Customs case detail}}
/api/platform/v1/customs/cases/{caseId}:
parameters: [{$ref: '#/components/parameters/CaseId'}]
get:
summary: Query complete customs detail and stable history
responses: {'200': {description: Detail}}
/api/platform/v1/customs/cases/{caseId}/documents:
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
post:
summary: Reference a file validated against customer, CUSTOMS_DOCUMENT and case ID
responses: {'200': {description: New document version}}
/api/platform/v1/customs/cases/{caseId}/documents/upload:
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
post:
summary: Upload a document and create a new immutable version
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [itemCode, expectedVersion, file]
properties:
itemCode: {type: string}
expectedVersion: {type: integer, minimum: 0}
file: {type: string, format: binary}
responses: {'200': {description: New document version}}
/api/platform/v1/customs/cases/{caseId}/documents/{documentId}/review:
parameters:
- {$ref: '#/components/parameters/CaseId'}
- {name: documentId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {$ref: '#/components/parameters/IdempotencyKey'}
post:
summary: Approve a current file version or return it with a supplement task
responses: {'200': {description: Updated detail}}
/api/platform/v1/customs/cases/{caseId}/submission-batches:
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
post:
summary: Submit to customs agent and freeze approved file versions in a batch snapshot
responses: {'200': {description: Submission batch}}
/api/platform/v1/customs/submission-batches/{batchId}/receipts:
parameters:
- {name: batchId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {$ref: '#/components/parameters/IdempotencyKey'}
post:
summary: Reference a customer-owned receipt file with explicit visibility
responses: {'200': {description: Receipt version}}
/api/platform/v1/customs/submission-batches/{batchId}/receipts/upload:
post:
summary: Upload a receipt version; RELEASED closes the customs dispatch gate
responses: {'200': {description: Receipt version}}
/api/merchant/v1/customs/orders/{orderId}:
get:
summary: Query the authenticated customer's customs case by order
responses: {'200': {description: Customer-filtered detail}}
/api/merchant/v1/customs/cases/{caseId}/documents/upload:
post:
summary: Upload or replace a checklist document within authenticated customer scope
responses: {'200': {description: New document version}}
/api/merchant/v1/customs/cases/{caseId}/receipts/{receiptId}/download:
get:
summary: Download only CUSTOMER-visible receipts from the authenticated customer's case
responses: {'200': {description: Authorized file stream}}
components:
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema: {type: string, minLength: 1, maxLength: 128}
CaseId:
name: caseId
in: path
required: true
schema: {type: string, pattern: '^[1-9][0-9]*$'}
@@ -0,0 +1,86 @@
openapi: 3.0.3
info:
title: Phase 3 Loading Evidence and Picking API
version: 1.0.0
paths:
/api/platform/v1/inbounds/{inboundId}/loading/evidence:
get:
summary: List loading evidence bound to one customer inbound
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}]
responses:
'200': {description: Business envelope containing the stable evidence array}
post:
summary: Bind one real File service object to a loading evidence slot
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/UpsertEvidenceRequest'}}
responses:
'200': {description: Business envelope; ownership, type and version failures use business error codes}
/api/platform/v1/inbounds/{inboundId}/loading/picking/start:
post:
summary: Start the inbound-level PDA picking review task
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/PickingRequest'}}
responses:
'200': {description: Business envelope containing IN_PROGRESS task}
/api/platform/v1/inbounds/{inboundId}/loading/picking/complete:
post:
summary: Complete a started PDA picking task when planned and picked counts match
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/PickingRequest'}}
responses:
'200': {description: Business envelope containing COMPLETED task}
/api/platform/v1/inbounds/{inboundId}/loading/picking:
get:
summary: Read the inbound-level PDA picking task
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}]
responses:
'200': {description: Business envelope containing task or null}
/api/platform/v1/inbounds/{inboundId}/loading/complete:
post:
summary: Complete loading only after picking and PHOTO_STEP_1..4 plus SEAL evidence are complete
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/CompleteLoadingRequest'}}
responses:
'200': {description: Business envelope containing LOADED status}
components:
parameters:
InboundId: {name: inboundId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
TenantId: {name: tenantId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}}
schemas:
UpsertEvidenceRequest:
type: object
required: [expectedVersion, evidenceType, fileId]
properties:
expectedVersion: {type: integer, minimum: 0}
evidenceType: {type: string, enum: [PHOTO_STEP_1, PHOTO_STEP_2, PHOTO_STEP_3, PHOTO_STEP_4, SEAL]}
fileId: {type: integer, format: int64, minimum: 1}
fileName: {type: string, maxLength: 255, deprecated: true, description: Ignored; server uses File metadata}
sealNo: {type: string, maxLength: 64, description: Required when evidenceType is SEAL}
remark: {type: string, maxLength: 1000}
PickingRequest:
type: object
required: [expectedVersion, expectedCount, pickedCount]
properties:
expectedVersion: {type: integer, minimum: 0}
expectedCount: {type: integer, minimum: 1}
pickedCount: {type: integer, minimum: 0}
remark: {type: string, maxLength: 1000}
CompleteLoadingRequest:
type: object
required: [expectedVersion]
properties:
expectedVersion: {type: integer, minimum: 0}
remark: {type: string, maxLength: 1000}
@@ -0,0 +1,69 @@
openapi: 3.0.3
info:
title: Phase 3 Domestic Outbound Dispatch API
version: 1.0.0
paths:
/api/platform/v1/first-leg/outbound-dispatches:
get:
summary: Query outbound aggregates by order, inbound or container
parameters:
- {$ref: '#/components/parameters/OrderId'}
- {$ref: '#/components/parameters/InboundId'}
- {$ref: '#/components/parameters/ContainerBookingId'}
responses:
'200': {description: Business envelope containing matching aggregates}
/api/platform/v1/first-leg/outbound-dispatches/detail:
get:
summary: Query one aggregate and its unified timeline
parameters:
- {$ref: '#/components/parameters/OrderId'}
- {$ref: '#/components/parameters/InboundId'}
- {$ref: '#/components/parameters/ContainerBookingId'}
responses:
'200': {description: Business envelope containing aggregate detail}
/api/platform/v1/first-leg/outbound-dispatches/check-gate:
get:
summary: Evaluate the fail-closed domestic outbound gate
parameters:
- {$ref: '#/components/parameters/OrderId'}
- {$ref: '#/components/parameters/InboundId'}
- {$ref: '#/components/parameters/ContainerBookingId'}
responses:
'200': {description: Business envelope containing every gate item}
/api/platform/v1/first-leg/outbound-dispatches/confirm-dispatch:
post:
summary: Confirm domestic dispatch and enter WAITING_FOR_LOADING
parameters:
- {$ref: '#/components/parameters/IdempotencyKey'}
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}
responses:
'200': {description: Business envelope; gate or version failures use business error code}
/api/platform/v1/first-leg/outbound-dispatches/reverse-dispatch:
post:
summary: Reverse WAITING_FOR_LOADING to LOADED
parameters:
- {$ref: '#/components/parameters/IdempotencyKey'}
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}
responses:
'200': {description: Business envelope containing reverted aggregate}
components:
parameters:
OrderId: {name: orderId, in: query, schema: {type: integer, format: int64, minimum: 1}}
InboundId: {name: inboundId, in: query, schema: {type: integer, format: int64, minimum: 1}}
ContainerBookingId: {name: containerBookingId, in: query, schema: {type: integer, format: int64, minimum: 1}}
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}}
schemas:
ActionRequest:
type: object
required: [inboundId, containerBookingId, expectedVersion]
properties:
inboundId: {type: integer, format: int64, minimum: 1}
containerBookingId: {type: integer, format: int64, minimum: 1}
expectedVersion: {type: integer, minimum: 0}
remark: {type: string, maxLength: 500}
@@ -0,0 +1,114 @@
openapi: 3.0.3
info:
title: Platform Port Schedule API
version: 1.0.0
description: Independent port and vessel schedule observations linked to ocean bookings and allocated containers.
paths:
/api/platform/v1/port-schedules:
get:
summary: Query schedules by customer, ocean booking, allocated container or expiry
parameters:
- {name: customerId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: oceanBookingId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: containerBookingId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: expired, in: query, schema: {type: boolean}}
- {name: keyword, in: query, schema: {type: string, maxLength: 128}}
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}
responses:
'200':
description: HTTP 200 business envelope containing a page of schedule views
post:
summary: Create a source observation linked to a real ocean booking
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/CreateRequest'}
responses:
'200': {description: Created detail in the standard business envelope}
/api/platform/v1/port-schedules/{portScheduleId}:
parameters: [{$ref: '#/components/parameters/PortScheduleId'}]
get:
summary: Get schedule and immutable history
responses:
'200': {description: Schedule detail with computed expired flag}
put:
summary: Update source observation with optimistic locking
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/UpdateRequest'}
responses:
'200': {description: Updated detail or A0409 conflict in the business envelope}
/api/platform/v1/port-schedules/{portScheduleId}/history:
parameters: [{$ref: '#/components/parameters/PortScheduleId'}]
get:
summary: Query immutable source observation history
responses:
'200': {description: Ordered CREATE and UPDATE history records}
components:
parameters:
PortScheduleId:
name: portScheduleId
in: path
required: true
schema: {type: string, pattern: '^[1-9][0-9]*$'}
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema: {type: string, minLength: 1, maxLength: 128}
schemas:
ScheduleData:
type: object
required:
- customerId
- oceanBookingId
- sourceType
- sourceName
- sourceRef
- vesselName
- voyageNo
- departurePortCode
- destinationPortCode
- observedAt
- expiresAt
properties:
customerId: {type: string, pattern: '^[1-9][0-9]*$'}
oceanBookingId: {type: string, pattern: '^[1-9][0-9]*$'}
containerBookingId: {type: string, nullable: true, pattern: '^[1-9][0-9]*$'}
sourceType: {type: string, enum: [MANUAL, CARRIER, TERMINAL, API]}
sourceName: {type: string, maxLength: 128}
sourceRef: {type: string, maxLength: 255}
vesselName: {type: string, maxLength: 128}
voyageNo: {type: string, maxLength: 64}
departurePortCode: {type: string, maxLength: 32}
departurePortName: {type: string, nullable: true, maxLength: 128}
destinationPortCode: {type: string, maxLength: 32}
destinationPortName: {type: string, nullable: true, maxLength: 128}
etdAt: {type: string, format: date-time, nullable: true}
etaAt: {type: string, format: date-time, nullable: true}
bookingCutoffAt: {type: string, format: date-time, nullable: true, description: Booking cutoff}
customsCutoffAt: {type: string, format: date-time, nullable: true, description: Customs cutoff}
portCutoffAt: {type: string, format: date-time, nullable: true, description: Port gate cutoff}
observedAt: {type: string, format: date-time}
expiresAt: {type: string, format: date-time}
remark: {type: string, nullable: true, maxLength: 1000}
CreateRequest:
type: object
required: [expectedVersion, data]
properties:
expectedVersion: {type: integer, minimum: 0, maximum: 0}
data: {$ref: '#/components/schemas/ScheduleData'}
reason: {type: string, nullable: true, maxLength: 1000}
UpdateRequest:
type: object
required: [expectedVersion, data]
properties:
expectedVersion: {type: integer, minimum: 0}
data: {$ref: '#/components/schemas/ScheduleData'}
reason: {type: string, nullable: true, maxLength: 1000}