openapi: 3.0.3 info: {title: Phase 3 Ocean Booking API, version: 1.1.0} paths: /api/platform/v1/ocean-bookings/channels: get: summary: List logistics channels from the current ACTIVE price book for an origin warehouse description: 'Requires platform:firstleg:ocean-booking:list. Returns only channels with an ACTIVE price row; the selected channel name is frozen server-side when the offline-confirmed booking is registered.' parameters: - {name: originWarehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}} responses: {'200': {description: Available channel options, content: {application/json: {schema: {type: array, items: {$ref: '#/components/schemas/ChannelOption'}}}}}} /api/platform/v1/ocean-bookings: get: summary: List only real confirmed booking records description: 'Requires platform:firstleg:ocean-booking:list. The operational list exposes only CONFIRMED, CHANGE_PENDING, RELEASED and CANCELLED bookings; DRAFT is internal to the create transaction and legacy SUBMITTED/FAILED values are invalid.' parameters: - {name: customerId, in: query, schema: {type: integer, format: int64, minimum: 1}} - {name: status, in: query, schema: {type: string, enum: [CONFIRMED, CHANGE_PENDING, RELEASED, CANCELLED]}} - {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: Operational real booking page}} post: summary: Register an offline-confirmed ocean booking with complete transport snapshots description: 'Requires platform:firstleg:ocean-booking:create. Registers every real container unit atomically, creates one stable fl_container_booking plus one requirement/allocation per container, and persists the booking directly as CONFIRMED. RELEASED remains a separate later action.' parameters: [{$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CreateRequest'}}}} responses: {'200': {description: CONFIRMED real booking detail, content: {application/json: {schema: {$ref: '#/components/schemas/Detail'}}}}} /api/platform/v1/ocean-bookings/{oceanBookingId}/release: post: summary: Release an already registered booking and synchronize associated cabinet transport snapshots description: 'The transport fields were registered at create time. The request must repeat the same bill of lading, vessel, voyage, ETD and ETA values; mismatches are rejected. This action does not create cabinets, change channels, replace cabinetNo, or recreate loading plans.' parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ReleaseRequest'}}}} responses: {'200': {description: RELEASED detail}} /api/platform/v1/ocean-bookings/{oceanBookingId}: get: summary: Get an ocean booking with its real container units description: Requires platform:firstleg:ocean-booking:list. parameters: [{$ref: '#/components/parameters/OceanBookingId'}] responses: {'200': {description: Booking detail, content: {application/json: {schema: {$ref: '#/components/schemas/Detail'}}}}} /api/platform/v1/ocean-bookings/{oceanBookingId}/cancel: post: summary: Cancel a confirmed or change-pending real booking description: Requires platform:firstleg:ocean-booking:cancel. A non-blank reason is required; RELEASED bookings are irreversible. parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}}} responses: {'200': {description: CANCELLED detail}} /api/platform/v1/ocean-bookings/{oceanBookingId}/change-request: post: summary: Request a voyage change for a confirmed booking description: Requires platform:firstleg:ocean-booking:change. Current transport snapshots remain active until change-confirm succeeds. parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ChangeRequest'}}}} responses: {'200': {description: CHANGE_PENDING detail}} /api/platform/v1/ocean-bookings/{oceanBookingId}/change-confirm: post: summary: Confirm a pending voyage change description: Requires platform:firstleg:ocean-booking:change. parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}}} responses: {'200': {description: CONFIRMED detail with the pending voyage snapshots applied}} components: parameters: OceanBookingId: {name: oceanBookingId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}} IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}} schemas: CreateRequest: type: object required: [expectedVersion, data] properties: expectedVersion: {type: integer, enum: [0]} data: {$ref: '#/components/schemas/DraftData'} DraftData: type: object required: [channelCode, warehouseId, billOfLadingNo, vesselName, voyageNo, serviceTerm, freightTerm, estimatedSailingAt, estimatedArrivalAt, parties, containers] properties: customerId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional customer snapshot; must be supplied together with customerName.} customerName: {type: string, maxLength: 128, nullable: true, description: Optional customer snapshot; must be supplied together with customerId.} channelCode: {type: string, maxLength: 64, description: Selected from GET /api/platform/v1/ocean-bookings/channels; server validates it against the current ACTIVE price book and freezes channelName.} warehouseId: {type: integer, format: int64, minimum: 1, description: ACTIVE domestic transit warehouse ID; warehouseName is resolved and frozen server-side.} shippingCompanyId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional shipping-company master-data ID; the carrier is independent from the logistics channel and booking-agent Supplier.} shippingCompanyName: {type: string, maxLength: 128, nullable: true, description: Optional shipping-company name snapshot.} bookingAgentId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional ACTIVE Warehouse supplier ID validated only as category BOOKING; booking suppliers do not use domestic/international scope.} bookingAgentName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint only; the persisted snapshot always comes from Warehouse and cannot be supplied without bookingAgentId.} serviceTerm: {type: string, maxLength: 32, description: Required free-text Service Term snapshot, for example CY-CY.} freightTerm: {type: string, maxLength: 32, description: Required free-text Freight Term snapshot, for example NINGBO.} billOfLadingNo: {type: string, maxLength: 128, description: Required offline-confirmed bill of lading snapshot.} vesselName: {type: string, maxLength: 128, description: Required offline-confirmed vessel snapshot.} voyageNo: {type: string, maxLength: 64, description: Required offline-confirmed voyage snapshot.} departurePortCode: {type: string, maxLength: 32, nullable: true} departurePortName: {type: string, maxLength: 128, nullable: true} destinationPortCode: {type: string, maxLength: 32, nullable: true} destinationPortName: {type: string, maxLength: 128, nullable: true} placeOfDelivery: {type: string, maxLength: 128, nullable: true} estimatedSailingAt: {type: string, format: date-time, description: Required offline-confirmed ETD snapshot.} estimatedArrivalAt: {type: string, format: date-time, description: Required offline-confirmed ETA snapshot; cannot be earlier than ETD.} remark: {type: string, maxLength: 1000, nullable: true} parties: {type: array, minItems: 3, maxItems: 3, items: {$ref: '#/components/schemas/PartyRequest'}} containers: {type: array, minItems: 1, maxItems: 20, items: {$ref: '#/components/schemas/ContainerRequest'}} PartyRequest: type: object required: [partyType, partyName] properties: partyType: {type: string, enum: [SHIPPER, CONSIGNEE, NOTIFY_PARTY]} partyName: {type: string, maxLength: 255} address: {type: string, maxLength: 1000, nullable: true} contactName: {type: string, maxLength: 128, nullable: true} contactPhone: {type: string, maxLength: 64, nullable: true} ContainerRequest: type: object required: [containerNo, containerType, containerKind, capacityCbm, maxWeightKg] additionalProperties: false properties: containerNo: {type: string, minLength: 1, maxLength: 64, description: Required real container number; unique across all ACTIVE and unmerged container records.} containerType: {type: string, enum: [20GP, 40GP, 40HC, 40NR, 45HC], description: Platform-controlled container type} containerKind: {type: string, maxLength: 32} capacityCbm: {type: number, exclusiveMinimum: true, minimum: 0, description: Must match the fixed platform specification for containerType} maxWeightKg: {type: number, exclusiveMinimum: true, minimum: 0, description: Must match the fixed platform specification for containerType} Detail: type: object required: [oceanBooking, parties, containers, histories] properties: oceanBooking: {type: object, description: Ocean booking aggregate and frozen transport snapshots.} parties: {type: array, items: {type: object}} containers: {type: array, items: {$ref: '#/components/schemas/RealContainer'}} histories: {type: array, items: {type: object}} RealContainer: type: object required: [containerBookingId, cabinetNo, containerNo, requirementId, allocationId, containerType, containerKind, capacityCbm, maxWeightKg, warehouseId, warehouseName, channelCode, channelName] properties: containerBookingId: {type: integer, format: int64, minimum: 1, description: Current ACTIVE operational container identity.} cabinetNo: {type: string, maxLength: 64, description: Stable server-generated internal cabinet number.} containerNo: {type: string, maxLength: 64, description: Unique real container number.} requirementId: {type: integer, format: int64, minimum: 1} allocationId: {type: integer, format: int64, minimum: 1} containerType: {type: string, enum: [20GP, 40GP, 40HC, 40NR, 45HC]} containerKind: {type: string, maxLength: 32} capacityCbm: {type: number} maxWeightKg: {type: number} warehouseId: {type: integer, format: int64, minimum: 1} warehouseName: {type: string, maxLength: 128} channelCode: {type: string, maxLength: 64} channelName: {type: string, maxLength: 128} customerId: {type: integer, format: int64, minimum: 1, nullable: true} customerName: {type: string, maxLength: 128, nullable: true} ChannelOption: type: object required: [priceBookVersionId, channelCode, channelName, priceModel, pricingTaxModes] properties: priceBookVersionId: {type: integer, format: int64} channelCode: {type: string, maxLength: 64} channelName: {type: string, maxLength: 128} priceModel: {type: string} pricingTaxModes: {type: array, items: {type: string}} ActionRequest: type: object required: [expectedVersion] properties: {expectedVersion: {type: integer, minimum: 0}, reason: {type: string, maxLength: 1000, nullable: true}} ChangeRequest: type: object required: [expectedVersion, vesselName, voyageNo, reason] properties: expectedVersion: {type: integer, minimum: 0} vesselName: {type: string, maxLength: 128} voyageNo: {type: string, maxLength: 64} estimatedSailingAt: {type: string, format: date-time, nullable: true} estimatedArrivalAt: {type: string, format: date-time, nullable: true} reason: {type: string, maxLength: 1000} ReleaseRequest: type: object required: [expectedVersion, billOfLadingNo, vesselName, voyageNo, estimatedSailingAt, estimatedArrivalAt] properties: expectedVersion: {type: integer, minimum: 0} billOfLadingNo: {type: string, maxLength: 128} vesselName: {type: string, maxLength: 128} voyageNo: {type: string, maxLength: 64} estimatedSailingAt: {type: string, format: date-time} estimatedArrivalAt: {type: string, format: date-time} reason: {type: string, maxLength: 1000}