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. 放舱不要求上传订舱确认文件或提单文件。 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}/estimated-arrival: put: summary: 放舱后维护预计到港 description: 仅允许RELEASED订舱按当前版本与幂等键维护estimatedArrivalAt,不得清空或早于预计开船时间;其他运输、预报、关系方、柜型与附件仍冻结,保存前后快照及操作人。不修改实际到港或计划到仓。 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/EstimatedArrivalRequest'}}}} responses: {'200': {description: 已保存的RELEASED订舱详情,包含当前版本和预计到港}} /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: 放舱校验已保存的订舱业务资料,不以订舱确认文件或提单文件作为前置条件。 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, description: 草稿可留空,放舱前必填;不得早于预计开船时间,关联柜次后作为到港日历及默认到仓日期来源} 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: 统一货重上限}}} EstimatedArrivalRequest: type: object additionalProperties: false required: [expectedVersion, estimatedArrivalAt] properties: expectedVersion: {type: integer, minimum: 0} estimatedArrivalAt: {type: string, format: date-time} 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}