openapi: 3.0.3 info: {title: Ocean Booking API, version: 2.0.0} 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}/release: post: summary: Validate and permanently release a DRAFT description: Release validates the persisted aggregate and 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/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, and exact warehouseId, channelCode and containerType. 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: channelCode, in: query, required: true, schema: {type: string, maxLength: 64}} - {name: containerType, in: query, required: true, schema: {type: string, maxLength: 32}} - {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'}} 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'}}} 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, nullable: true} customerName: {type: string, nullable: true} channelCode: {type: string, nullable: true} warehouseId: {type: integer, format: int64, nullable: true} billOfLadingNo: {type: string, nullable: true} shippingCompanyId: {type: integer, format: int64, nullable: true} shippingCompanyName: {type: string, nullable: true} 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} serviceTerm: {type: string, nullable: true} freightTerm: {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} 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}}} ContainerRequirementRequest: {type: object, required: [containerType, containerKind, quantity, capacityCbm, maxWeightKg], properties: {containerType: {type: string}, containerKind: {type: string}, quantity: {type: integer, minimum: 1}, capacityCbm: {type: number, exclusiveMinimum: true, minimum: 0}, maxWeightKg: {type: number, exclusiveMinimum: true, minimum: 0}}} 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}}} 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}} LinkCandidate: type: object required: [oceanBookingId, bookingNo, requirementId, containerType, capacityCbm, maxWeightKg, requiredQuantity, allocatedQuantity, remainingQuantity, customerName, warehouseName, channelName] properties: oceanBookingId: {type: string, pattern: '^[0-9]+$'} bookingNo: {type: string} 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} 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, histories] properties: oceanBooking: type: object properties: bookingNo: {type: string, pattern: '^OB-[0-9]{6}-[0-9]{4}$', example: OB-260818-0001, description: 平台订舱号;按北京时间创建日逐日编号} parties: {type: array} containerRequirements: {type: array} containers: {type: array, description: Existing containers allocated after port pickup linking} histories: {type: array}