Files
baiyue-firstleg/firstleg/src/main/resources/openapi/carrier-schedule-query-v1.yaml
T

283 lines
15 KiB
YAML

openapi: 3.0.3
info:
title: Carrier Schedule Query API
version: 1.0.0
description: Phase-4 carrier tracking from domestic outbound confirmation through actual empty return.
paths:
/api/platform/v1/carrier-schedule-queries/carriers:
get:
summary: List official carrier sources and compliant access modes
x-permission: platform:firstleg:port-schedule-query:list
responses:
'200': {description: 'MATSON, SML, ZIM, COSCO, EMC and HEDE descriptors'}
/api/platform/v1/carrier-schedule-queries/container-actual-facts:
get:
summary: Batch query actual carrier-node facts for container workbenches
description: Returns at most one row for each container that has a carrier-schedule query. Missing rows mean that no query exists. Estimated facts are never returned or inferred.
x-permission: platform:firstleg:port-schedule-query:list
parameters:
- name: containerBookingIds
in: query
required: true
schema:
type: array
minItems: 1
maxItems: 200
uniqueItems: true
items: {type: integer, format: int64, minimum: 1}
responses:
'200':
description: Actual-only summaries ordered by containerBookingId
content: {application/json: {schema: {$ref: '#/components/schemas/ContainerActualFactsListEnvelope'}}}
/api/platform/v1/carrier-schedule-queries:
get:
summary: Query carrier schedule tasks
x-permission: platform:firstleg:port-schedule-query:list
parameters:
- {name: customerId, in: query, schema: {type: integer, format: int64, minimum: 1}}
- {name: oceanBookingId, in: query, schema: {type: integer, format: int64, minimum: 1}}
- {name: containerBookingId, in: query, schema: {type: integer, format: int64, minimum: 1}}
- {name: carrierCode, in: query, schema: {$ref: '#/components/schemas/TaskCarrierCode'}}
- {name: status, in: query, schema: {$ref: '#/components/schemas/QueryStatus'}}
- {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: Query tasks with source and freshness fields; normal execution uses fixed Shanghai slots while retryable technical failures may retry within the current slot}
post:
summary: Create a query task linked to a real booking and allocated container
x-permission: platform:firstleg:port-schedule-query:create
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/CreateRequest'}
responses:
'200': {description: Created task or idempotent replay}
/api/platform/v1/carrier-schedule-queries/{queryId}:
parameters: [{$ref: '#/components/parameters/QueryId'}]
get:
summary: Get task, standardized node facts and immutable business history
x-permission: platform:firstleg:port-schedule-query:list
responses:
'200':
description: Stable task detail
content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}
/api/platform/v1/carrier-schedule-queries/{queryId}/history:
parameters: [{$ref: '#/components/parameters/QueryId'}]
get:
summary: Get immutable task history
x-permission: platform:firstleg:port-schedule-query:list
responses:
'200': {description: Immutable business history; scheduler attempts are not exposed}
/api/platform/v1/carrier-schedule-queries/{queryId}/manual-results:
parameters: [{$ref: '#/components/parameters/QueryId'}]
post:
summary: Link a validated manual port-schedule observation without pretending automated success
description: This legacy port-schedule association does not append a standard node and never stops tracking; phase-4 manual facts use /manual-nodes.
x-permission: platform:firstleg:port-schedule-query:manual
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ManualResultRequest'}
responses:
'200':
description: Successful manual fallback and next query time
content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}
/api/platform/v1/carrier-schedule-queries/{queryId}/manual-nodes:
parameters: [{$ref: '#/components/parameters/QueryId'}]
post:
summary: Append one immutable manual estimated or actual standard-node fact
x-permission: platform:firstleg:port-schedule-query:manual
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ManualNodeRequest'}
responses:
'200':
description: Idempotent fact append; estimated facts never advance actual progress, while a manual actual fact must have a strictly greater standard-node sequence than latestActualNode and actual EMPTY_RETURNED stops
'409':
description: A0409 when expectedVersion is stale or a manual actual node is equal to or earlier than latestActualNode
content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}
components:
parameters:
QueryId: {name: queryId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, minLength: 1, maxLength: 128}}
schemas:
CarrierCode: {type: string, enum: [MATSON, SML, ZIM, COSCO, EMC, HEDE]}
TaskCarrierCode:
type: string
enum: [MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER]
description: OTHER is frozen on the ocean booking and creates a manual-only task after outbound confirmation.
TrackingType: {type: string, enum: [CONTAINER_NO], description: "Only the normalized physical containerNo frozen by Firstleg is accepted; cabinetNo, orderNo, bookingNo and bill of lading are never fallbacks."}
QueryStatus: {type: string, enum: [READY, MANUAL_REQUIRED, STOPPED]}
StandardNode:
type: string
enum: [DEPARTED_WAREHOUSE, AT_ORIGIN_PORT, LOADED_ON_VESSEL, DEPARTED_ORIGIN_PORT, IN_TRANSIT, ARRIVED_DESTINATION_PORT, DISCHARGED_FROM_VESSEL, PICKUP_READY, PICKED_UP, EMPTY_RETURNED]
ExceptionCode: {type: string, enum: [CUSTOMS_HOLD, TERMINAL_HOLD, DOCUMENT_HOLD, UNKNOWN_HOLD]}
CreateRequest:
type: object
required: [expectedVersion, customerId, oceanBookingId, containerBookingId, carrierCode, trackingType, trackingNo]
properties:
expectedVersion: {type: integer, minimum: 0, maximum: 0}
customerId: {type: integer, format: int64, minimum: 1}
oceanBookingId: {type: integer, format: int64, minimum: 1}
containerBookingId: {type: integer, format: int64, minimum: 1}
carrierCode: {$ref: '#/components/schemas/CarrierCode'}
trackingType: {$ref: '#/components/schemas/TrackingType'}
trackingNo: {type: string, minLength: 1, maxLength: 128, description: Normalized physical containerNo; must equal the linked Firstleg container snapshot.}
remark: {type: string, nullable: true, maxLength: 1000}
ManualResultRequest:
type: object
required: [expectedVersion, portScheduleId, reason]
properties:
expectedVersion: {type: integer, minimum: 0}
portScheduleId: {type: integer, format: int64, minimum: 1}
reason: {type: string, minLength: 1, maxLength: 1000}
ManualNodeRequest:
type: object
required: [expectedVersion, standardNode, observedAt, reason]
description: estimatedAt and actualAt cannot both be null; estimatedAt never advances the latest actual node; when actualAt is present, standardNode must have a strictly greater sequence than latestActualNode.
properties:
expectedVersion: {type: integer, minimum: 0}
standardNode: {$ref: '#/components/schemas/StandardNode'}
observedAt: {type: string, format: date-time}
estimatedAt: {type: string, format: date-time, nullable: true}
actualAt: {type: string, format: date-time, nullable: true}
locationCode: {type: string, maxLength: 32, nullable: true}
locationName: {type: string, maxLength: 128, nullable: true}
reason: {type: string, minLength: 1, maxLength: 1000}
Task:
type: object
required: [id, queryNo, customerId, oceanBookingId, containerBookingId, carrierCode, trackingType, trackingNo, status, triggerSource, version]
properties:
id: {type: integer, format: int64}
queryNo: {type: string}
customerId: {type: integer, format: int64}
oceanBookingId: {type: integer, format: int64}
containerBookingId: {type: integer, format: int64}
carrierCode: {$ref: '#/components/schemas/TaskCarrierCode'}
trackingType: {$ref: '#/components/schemas/TrackingType'}
trackingNo: {type: string}
officialUrl: {type: string, nullable: true}
accessMode:
type: string
enum: [PUBLIC_FORM, PUBLIC_WEB_PROXY, PUBLIC_HTML, HCAPTCHA, OFFICIAL_API, MANUAL]
nullable: true
description: MANUAL tasks preserve physical outbound confirmation and accept platform manual node facts without scheduler/provider calls.
status: {$ref: '#/components/schemas/QueryStatus'}
triggerSource: {type: string}
latestActualNode: {allOf: [{$ref: '#/components/schemas/StandardNode'}], nullable: true}
latestActualAt: {type: string, format: date-time, nullable: true}
lastSuccessAt: {type: string, format: date-time, nullable: true}
sourceUpdatedAt: {type: string, format: date-time, nullable: true}
expiresAt: {type: string, format: date-time, nullable: true}
resultPortScheduleId: {type: integer, format: int64, nullable: true}
manualReason:
type: string
nullable: true
description: Explicit provider-disabled or unsupported-carrier reason; unsupported outbound carriers retain the booking shippingCompanyId/name here and through oceanBookingId.
lastErrorCode: {type: string, nullable: true}
lastErrorMessage: {type: string, nullable: true}
stoppedAt: {type: string, format: date-time, nullable: true}
stopReason: {type: string, nullable: true}
version: {type: integer, minimum: 0}
QueryView:
type: object
required: [query, expired]
properties:
query: {$ref: '#/components/schemas/Task'}
expired: {type: boolean}
ContainerActualFacts:
type: object
required: [containerBookingId, queryId, queryVersion, queryStatus]
description: Actual facts only. All node times are null until an immutable fact with actualAt exists; estimatedAt is intentionally absent.
properties:
containerBookingId: {type: integer, format: int64}
queryId: {type: integer, format: int64}
queryVersion: {type: integer, minimum: 0}
queryStatus: {$ref: '#/components/schemas/QueryStatus'}
latestActualNode: {allOf: [{$ref: '#/components/schemas/StandardNode'}], nullable: true}
latestActualAt: {type: string, format: date-time, nullable: true}
departedWarehouseAt: {type: string, format: date-time, nullable: true}
departedOriginPortAt: {type: string, format: date-time, nullable: true}
arrivedDestinationPortAt: {type: string, format: date-time, nullable: true}
pickupReadyAt: {type: string, format: date-time, nullable: true}
pickedUpAt: {type: string, format: date-time, nullable: true}
emptyReturnedAt: {type: string, format: date-time, nullable: true}
ContainerActualFactsListEnvelope:
type: object
required: [code, data]
properties:
code: {type: string, example: '200'}
message: {type: string, nullable: true}
data: {type: array, items: {$ref: '#/components/schemas/ContainerActualFacts'}}
NodeFact:
type: object
required: [id, providerCode, providerEventId, providerNodeCode, standardNode, nodeSequence, observedAt, rawPayloadHash, createdAt]
properties:
id: {type: integer, format: int64}
providerCode: {type: string, maxLength: 32}
providerEventId: {type: string, maxLength: 128}
providerNodeCode: {type: string, maxLength: 64}
standardNode: {$ref: '#/components/schemas/StandardNode'}
nodeSequence: {type: integer, enum: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]}
observedAt: {type: string, format: date-time}
estimatedAt: {type: string, format: date-time, nullable: true}
actualAt: {type: string, format: date-time, nullable: true}
locationCode: {type: string, maxLength: 32, nullable: true}
locationName: {type: string, maxLength: 128, nullable: true}
sourceReference: {type: string, maxLength: 500, nullable: true}
rawPayloadHash: {type: string, pattern: '^[0-9a-f]{64}$'}
createdAt: {type: string, format: date-time}
ExceptionFact:
type: object
required: [id, providerCode, providerEventId, providerStatus, exceptionCode, active, observedAt, rawPayloadHash, createdAt]
properties:
id: {type: integer, format: int64}
providerCode: {type: string, maxLength: 32}
providerEventId: {type: string, maxLength: 128}
providerStatus: {type: string, maxLength: 128}
exceptionCode: {$ref: '#/components/schemas/ExceptionCode'}
active: {type: boolean, description: true means hold occurred; false means released}
observedAt: {type: string, format: date-time}
locationCode: {type: string, maxLength: 32, nullable: true}
locationName: {type: string, maxLength: 128, nullable: true}
sourceReference: {type: string, maxLength: 500, nullable: true}
rawPayloadHash: {type: string, pattern: '^[0-9a-f]{64}$'}
createdAt: {type: string, format: date-time}
History:
type: object
required: [id, action, createdAt]
properties:
id: {type: integer, format: int64}
action: {type: string, enum: [CREATE, AUTO_CREATE, MANUAL_RESULT, MANUAL_NODE, NODE_OBSERVED, EXCEPTION_OBSERVED, PROVIDER_QUERY]}
fromVersion: {type: integer, nullable: true}
toVersion: {type: integer, nullable: true}
reason: {type: string, nullable: true}
operatorId: {type: integer, format: int64, nullable: true}
operatorName: {type: string, nullable: true}
createdAt: {type: string, format: date-time}
Detail:
type: object
required: [query, nodes, exceptions, histories]
description: Scheduler lease, attempt and internal execution fields are intentionally absent.
properties:
query: {$ref: '#/components/schemas/QueryView'}
nodes: {type: array, items: {$ref: '#/components/schemas/NodeFact'}}
exceptions: {type: array, items: {$ref: '#/components/schemas/ExceptionFact'}}
histories: {type: array, items: {$ref: '#/components/schemas/History'}}
DetailEnvelope:
type: object
required: [code, data]
properties:
code: {type: string, example: '200'}
message: {type: string, nullable: true}
data: {$ref: '#/components/schemas/Detail'}