openapi: 3.0.3 info: title: Ocean Booking API version: 2.2.0 description: Booking-owned files use File businessType OCEAN_BOOKING_DOCUMENT and businessId=oceanBookingId. Release requires at least one active BOOKING_CONFIRMATION or BILL_OF_LADING file. Released transport and declared cargo snapshots and file links are read-only. Manual actual cargo may be supplemented with version, idempotency and audit reason. paths: /api/platform/v1/ocean-bookings: get: summary: List DRAFT and RELEASED bookings description: containerCount is the sum of quantity across all container requirements for the booking. It is zero when no requirement exists and never represents linked or ACTIVE containers. responses: '200': description: Booking page; each row uses the frozen requirement-total containerCount semantic post: summary: Create an editable DRAFT description: Creates only the booking aggregate and container requirements; no containerNo, container row, or allocation is created. parameters: [{$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CreateRequest'}}}} responses: {'200': {description: DRAFT detail}} /api/platform/v1/ocean-bookings/{oceanBookingId}: get: {summary: Get booking detail, parameters: [{$ref: '#/components/parameters/OceanBookingId'}], responses: {'200': {description: Detail}}} put: summary: Replace a DRAFT parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/UpdateRequest'}}}} responses: {'200': {description: Updated DRAFT}} delete: summary: Delete a DRAFT; RELEASED is immutable parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}}} responses: {'200': {description: Deleted}} /api/platform/v1/ocean-bookings/{oceanBookingId}/actual-cargo: put: summary: Supplement manual actual cargo on a RELEASED booking description: Only actual package count, gross weight and volume may change. Transport and declared cargo remain frozen. Requires expectedVersion, Idempotency-Key and a reason; saves before/after audit snapshots atomically. Null means not recorded, never zero or a fallback to declared cargo. x-permission: platform:firstleg:ocean-booking:edit parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActualCargoRequest'}}}} responses: {'200': {description: Updated RELEASED detail}, '409': {description: State or version conflict}} /api/platform/v1/ocean-bookings/{oceanBookingId}/release: post: summary: Validate and permanently release a DRAFT description: Release validates the persisted aggregate plus at least one active booking-owned BOOKING_CONFIRMATION or BILL_OF_LADING file. The customer is optional; a supplied customer must remain ACTIVE. Null denotes no single customer snapshot, never public access. It never requires or creates a real container number. 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}/documents: get: summary: List current and invalidated booking-owned file references x-permission: platform:firstleg:ocean-booking:list parameters: [{$ref: '#/components/parameters/OceanBookingId'}] responses: '200': {description: Booking document history, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentListResponse'}}}} post: summary: Reference an existing File object in a DRAFT booking description: File scope must be platform tenantId=0 + OCEAN_BOOKING_DOCUMENT + oceanBookingId, independent of the optional customer snapshot. The booking version is incremented atomically. x-permission: platform:firstleg:ocean-booking:edit parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentRequest'}}}} responses: '200': {description: Created reference and latest booking version, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentMutationResponse'}}}} /api/platform/v1/ocean-bookings/{oceanBookingId}/documents/upload: post: summary: Upload and associate a file with a DRAFT booking description: Uploads through File businessType OCEAN_BOOKING_DOCUMENT and increments the booking version atomically. x-permission: platform:firstleg:ocean-booking:edit parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: required: true content: multipart/form-data: schema: type: object required: [expectedVersion, documentType, file] properties: expectedVersion: {type: integer, minimum: 0} documentType: {$ref: '#/components/schemas/DocumentType'} file: {type: string, format: binary} responses: '200': {description: Created reference and latest booking version, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentMutationResponse'}}}} /api/platform/v1/ocean-bookings/{oceanBookingId}/documents/{documentId}: delete: summary: Logically invalidate a booking file reference while DRAFT description: The File object and immutable reference history are retained; RELEASED bookings are read-only. x-permission: platform:firstleg:ocean-booking:edit parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/DocumentId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentActionRequest'}}}} responses: '200': {description: Invalidation result and latest booking version, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentInvalidationResponse'}}}} /api/platform/v1/ocean-bookings/{oceanBookingId}/documents/{documentId}/download: get: summary: Download an active booking-owned file x-permission: platform:firstleg:ocean-booking:list parameters: [{$ref: '#/components/parameters/OceanBookingId'}, {$ref: '#/components/parameters/DocumentId'}] responses: '200': {description: Original file bytes, content: {application/octet-stream: {schema: {type: string, format: binary}}}} /api/platform/v1/ocean-bookings/{oceanBookingId}/prints/shipping-instruction: get: summary: Render the current saved booking as an export shipping instruction PDF description: Firstleg maps the authoritative saved aggregate (DRAFT or RELEASED, with missing fields displayed as —) to SEA_FREIGHT_SHIPPING_INSTRUCTION_A4_V1 and synchronously proxies the generated PDF. Cargo uses saved declared values; container requirements describe requested containers only. Count in words is generated from the declared count and packageUnit. x-permission: platform:firstleg:ocean-booking:list parameters: [{$ref: '#/components/parameters/OceanBookingId'}] responses: '200': {description: PDF or a business-error JSON envelope, content: {application/pdf: {schema: {type: string, format: binary}}, application/json: {schema: {type: object}}}} /api/platform/v1/ocean-bookings/{oceanBookingId}/prints/bill-of-lading-confirmation: get: summary: Render the current saved booking as a bill-of-lading confirmation PDF description: Firstleg maps the authoritative saved aggregate (DRAFT or RELEASED, with missing fields displayed as —) to BILL_OF_LADING_CONFIRMATION_A4_V1 and synchronously proxies the generated PDF. Cargo uses saved manually entered actual values only; missing actual data displays — and never falls back to declared values. This is a confirmation copy; the externally issued bill of lading remains an uploaded attachment. x-permission: platform:firstleg:ocean-booking:list parameters: [{$ref: '#/components/parameters/OceanBookingId'}] responses: '200': {description: PDF or a business-error JSON envelope, content: {application/pdf: {schema: {type: string, format: binary}}, application/json: {schema: {type: object}}}} /api/platform/v1/ocean-bookings/link-candidates: get: summary: Page released booking requirement candidates for one container description: >- Requires platform:firstleg:ocean-booking:list. Returns only linkable requirement candidates: RELEASED, remainingQuantity greater than zero, exact warehouseId and containerType, and a concrete booking channel belonging to channelGroupCode. Capacity and max weight are derived display snapshots and never eligibility fields. Allocated quantity counts only ACTIVE containers still pointing to the same booking requirement and is calculated by one bounded set query; booking detail calls are not used. An empty result is a normal successful page. Final target-container mutability is revalidated by the link command. x-permission: platform:firstleg:ocean-booking:list parameters: - {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}} - {name: channelGroupCode, in: query, required: true, schema: {type: string, maxLength: 64}} - {name: containerType, in: query, required: true, schema: {type: string, enum: [40GP, 40HQ, 45HQ]}} - {name: page, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, default: 1}} - {name: size, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}} responses: '200': description: Business envelope containing a bounded candidate page content: application/json: schema: {$ref: '#/components/schemas/LinkCandidateResponse'} /api/platform/v1/ocean-bookings/channels: get: summary: List ACTIVE channels for an origin warehouse description: Requires platform:firstleg:ocean-booking:list. Channel names are resolved from the current ACTIVE price book and frozen by the server on save. parameters: - {name: originWarehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}} responses: '200': description: Channel options content: application/json: schema: {type: array, items: {$ref: '#/components/schemas/ChannelOption'}} /api/platform/v1/ocean-bookings/booking-agents: get: summary: List ACTIVE BOOKING agents associated with one carrier code description: Requires platform:firstleg:ocean-booking:list. Filtering is enforced by Warehouse master data; clients must not infer associations. x-permission: platform:firstleg:ocean-booking:list parameters: - {name: carrierCode, in: query, required: true, schema: {type: string, enum: [MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER]}} responses: '200': description: Server-filtered booking agent options content: application/json: schema: {type: array, items: {$ref: '#/components/schemas/BookingAgentOption'}} components: parameters: OceanBookingId: {name: oceanBookingId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}} DocumentId: {name: documentId, 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'}}} UpdateRequest: {type: object, required: [expectedVersion, data], properties: {expectedVersion: {type: integer, minimum: 0}, data: {$ref: '#/components/schemas/DraftData'}}} DraftData: type: object description: Fields may be incomplete in DRAFT. RELEASE validates the complete persisted aggregate. properties: customerId: {type: integer, format: int64, minimum: 1, nullable: true, description: 可选启用客户ID;多客户订舱留空,空值不是公开访问授权} customerName: {type: string, nullable: true, description: 可选名称提示;有客户ID时由服务端校验并保存客户主数据名称,清空客户时同时置null} channelCode: {type: string, nullable: true, description: 固定计划物流渠道组编码} warehouseId: {type: integer, format: int64, nullable: true} billOfLadingNo: {type: string, nullable: true, description: 全局唯一提单号;草稿未取得时可为空} shippingCompanyId: {type: integer, format: int64, nullable: true} shippingCompanyName: {type: string, nullable: true} carrierCode: type: string enum: [MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER] nullable: true description: Frozen standard carrier code. Required before RELEASE; OTHER means manual carrier-node tracking. bookingAgentId: type: integer format: int64 nullable: true description: Warehouse supplier ID; release validates ACTIVE category BOOKING; booking suppliers do not use domestic/international scope. bookingAgentName: type: string nullable: true description: Read-only consistency hint; the persisted snapshot always comes from Warehouse. vesselName: {type: string, nullable: true} voyageNo: {type: string, nullable: true} departurePortCode: {type: string, nullable: true} departurePortName: {type: string, nullable: true} destinationPortCode: {type: string, nullable: true} destinationPortName: {type: string, nullable: true} placeOfDelivery: {type: string, nullable: true} estimatedSailingAt: {type: string, format: date-time, nullable: true} estimatedArrivalAt: {type: string, format: date-time, nullable: true} remark: {type: string, nullable: true} freightTerm: {type: string, maxLength: 64, nullable: true, description: 运费条款;新建页面默认FREIGHT PREPAID} serviceTerm: {type: string, maxLength: 64, nullable: true, description: 运输条款;新建页面默认CY-CY} packageUnit: {type: string, enum: [CARTONS, PCS, PALLETS], nullable: true} cargoDescription: {type: string, maxLength: 1000, nullable: true} marksAndNos: {type: string, maxLength: 1000, nullable: true} declaredPackageCount: {type: integer, minimum: 1, maximum: 999999999, nullable: true} declaredGrossWeightKg: {type: number, minimum: 0, exclusiveMinimum: true, maximum: 999999999999.999, multipleOf: 0.001, nullable: true} declaredVolumeCbm: {type: number, minimum: 0, exclusiveMinimum: true, maximum: 999999999999.999, multipleOf: 0.001, nullable: true} actualPackageCount: {type: integer, minimum: 1, maximum: 999999999, nullable: true} actualGrossWeightKg: {type: number, minimum: 0, exclusiveMinimum: true, maximum: 999999999999.999, multipleOf: 0.001, nullable: true} actualVolumeCbm: {type: number, minimum: 0, exclusiveMinimum: true, maximum: 999999999999.999, multipleOf: 0.001, nullable: true} parties: {type: array, nullable: true, items: {$ref: '#/components/schemas/PartyRequest'}} containerRequirements: {type: array, nullable: true, items: {$ref: '#/components/schemas/ContainerRequirementRequest'}} PartyRequest: {type: object, required: [partyType, partyName], properties: {partyType: {type: string, enum: [SHIPPER, CONSIGNEE, NOTIFY_PARTY]}, partyName: {type: string}, address: {type: string, nullable: true}, contactName: {type: string, nullable: true}, contactPhone: {type: string, nullable: true}, contactEmail: {type: string, format: email, maxLength: 255, nullable: true}, taxNo: {type: string, maxLength: 128, nullable: true}}} ContainerRequirementRequest: {type: object, required: [containerType, containerKind, quantity, capacityCbm, maxWeightKg], properties: {containerType: {type: string, enum: [40GP, 40HQ, 45HQ], default: 40HQ}, containerKind: {type: string}, quantity: {type: integer, minimum: 1}, capacityCbm: {type: number, exclusiveMinimum: true, minimum: 0, description: 统一实装上限;40GP=58、40HQ=68、45HQ=86}, maxWeightKg: {type: number, enum: [19500], description: 统一货重上限}}} ActualCargoRequest: type: object additionalProperties: false required: [expectedVersion, reason] properties: expectedVersion: {type: integer, minimum: 0} reason: {type: string, minLength: 1, maxLength: 1000} actualPackageCount: {type: integer, minimum: 1, maximum: 999999999, nullable: true} actualGrossWeightKg: {type: number, minimum: 0, exclusiveMinimum: true, maximum: 999999999999.999, multipleOf: 0.001, nullable: true} actualVolumeCbm: {type: number, minimum: 0, exclusiveMinimum: true, maximum: 999999999999.999, multipleOf: 0.001, nullable: true} ReleaseRequest: {type: object, required: [expectedVersion], properties: {expectedVersion: {type: integer, minimum: 0}, reason: {type: string, nullable: true}}} ActionRequest: {type: object, required: [expectedVersion], properties: {expectedVersion: {type: integer, minimum: 0}, reason: {type: string, nullable: true}}} DocumentType: {type: string, enum: [BOOKING_CONFIRMATION, BILL_OF_LADING]} DocumentRequest: type: object required: [expectedVersion, fileId, documentType] properties: expectedVersion: {type: integer, minimum: 0} fileId: {type: integer, format: int64, minimum: 1} documentType: {$ref: '#/components/schemas/DocumentType'} DocumentActionRequest: {type: object, required: [expectedVersion], properties: {expectedVersion: {type: integer, minimum: 0}}} OceanBookingDocument: type: object required: [id, oceanBookingId, fileId, documentType, fileName, valid, uploadedBy, uploadedAt] properties: id: {type: integer, format: int64} oceanBookingId: {type: integer, format: int64} fileId: {type: integer, format: int64} documentType: {$ref: '#/components/schemas/DocumentType'} fileName: {type: string} fileHash: {type: string, nullable: true} sizeBytes: {type: integer, format: int64, nullable: true} contentType: {type: string, nullable: true} valid: {type: boolean} uploadedBy: {type: integer, format: int64} uploadedByName: {type: string, nullable: true} uploadedAt: {type: string, format: date-time} invalidatedBy: {type: integer, format: int64, nullable: true} invalidatedByName: {type: string, nullable: true} invalidatedAt: {type: string, format: date-time, nullable: true} DocumentMutation: type: object required: [document, bookingVersion] properties: document: {$ref: '#/components/schemas/OceanBookingDocument'} bookingVersion: {type: integer, minimum: 1} DocumentInvalidation: type: object required: [documentId, bookingVersion] properties: documentId: {type: integer, format: int64} bookingVersion: {type: integer, minimum: 1} DocumentListResponse: type: object properties: {code: {type: string, example: '200'}, data: {type: array, items: {$ref: '#/components/schemas/OceanBookingDocument'}}} DocumentMutationResponse: type: object properties: {code: {type: string, example: '200'}, data: {$ref: '#/components/schemas/DocumentMutation'}} DocumentInvalidationResponse: type: object properties: {code: {type: string, example: '200'}, data: {$ref: '#/components/schemas/DocumentInvalidation'}} ChannelOption: type: object required: [priceBookVersionId, channelCode, channelName, priceModel, pricingTaxModes] properties: priceBookVersionId: {type: integer, format: int64} channelCode: {type: string} channelName: {type: string} priceModel: {type: string} pricingTaxModes: {type: array, items: {type: string}} BookingAgentOption: type: object required: [supplierId, name, carrierCodes] properties: supplierId: {type: integer, format: int64} name: {type: string} carrierCodes: {type: array, items: {type: string, enum: [MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER]}} LinkCandidate: type: object required: [oceanBookingId, bookingNo, billOfLadingNo, requirementId, containerType, capacityCbm, maxWeightKg, requiredQuantity, allocatedQuantity, remainingQuantity, customerName, warehouseName, channelName] properties: oceanBookingId: {type: string, pattern: '^[0-9]+$'} bookingNo: {type: string} billOfLadingNo: {type: string, description: 已发布订舱的全局唯一提单号} requirementId: {type: string, pattern: '^[0-9]+$'} containerType: {type: string} capacityCbm: {type: number} maxWeightKg: {type: number} requiredQuantity: {type: integer, minimum: 1} allocatedQuantity: {type: integer, format: int64, minimum: 0} remainingQuantity: {type: integer, format: int64, minimum: 1} customerName: {type: string, nullable: true} warehouseName: {type: string} channelName: {type: string} LinkCandidatePage: type: object required: [records, page, size, total, pages] properties: records: {type: array, items: {$ref: '#/components/schemas/LinkCandidate'}} page: {type: integer, format: int64, minimum: 1} size: {type: integer, format: int64, minimum: 1} total: {type: integer, format: int64, minimum: 0} pages: {type: integer, format: int64, minimum: 0} LinkCandidateResponse: type: object required: [code, data] properties: code: {type: string, example: '200'} message: {type: string, nullable: true} data: {$ref: '#/components/schemas/LinkCandidatePage'} OceanBookingListRow: type: object properties: containerCount: type: integer format: int64 minimum: 0 description: Sum of all container requirement quantities; independent of allocation, linking or unlinking. Detail: type: object required: [oceanBooking, parties, containerRequirements, containers, documents, histories] properties: oceanBooking: allOf: - {$ref: '#/components/schemas/DraftData'} type: object properties: bookingNo: {type: string, pattern: '^OB[0-9]{12}$', example: OB202608180001, description: 平台订舱号;按北京时间创建日逐日编号} parties: {type: array} containerRequirements: {type: array} containers: {type: array, description: Existing containers allocated after port pickup linking} documents: {type: array, items: {$ref: '#/components/schemas/OceanBookingDocument'}, description: Current and invalidated booking-owned file references} histories: {type: array}