openapi: 3.0.3 info: title: Truck Appointment API version: 2.0.0 description: A truck appointment belongs to one RELEASED ocean booking, independently of when or how that booking's internal containers are linked. Each booking may have at most one non-terminal appointment. The bill-of-lading number and warehouse are frozen from the booking, which must own at least one active BOOKING_CONFIRMATION or BILL_OF_LADING file. Container identities and real container numbers remain in the container/loading modules and are not part of this API. PDA loading completion alone does not complete the appointment. All required containers must be allocated and confirmed departed from the domestic warehouse. Completion atomically creates approved ON_ACCOUNT container payable fees allocated by loaded volume and publishes their immutable snapshots through the existing Finance Outbox. Create, edit, submit and reschedule also recheck departure facts, covering late appointments. Zero amounts create no fee. Cancelled and exception appointments are excluded. RMB is the only appointment currency and maps to CNY in Finance. paths: /api/platform/v1/truck-appointments: get: summary: Query truck appointments responses: '200': description: Paged booking-level truck appointments with stable booking snapshots and attachment count content: application/json: schema: type: object properties: data: type: object properties: records: {type: array, items: {$ref: '#/components/schemas/DetailStableReferences'}} post: summary: Create a truck appointment for a released booking description: If the booking already has an active or completed appointment, the server returns A0409 with the existing appointment number and instructs the operator to continue editing. The database booking-level unique key provides the same guarantee for concurrent creates. requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/SaveRequest'} responses: '200': {description: 'Created appointment', content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}} '409': {description: 'A0409 - this booking already has an active or completed appointment; message includes its appointment number and asks the operator to continue editing'} /api/platform/v1/truck-appointments/candidates: get: summary: Query authoritative create-time ocean-booking candidates description: Requires platform:firstleg:truck-appointment:list. Returns one row per RELEASED booking with a bill of lading and an active booking-owned confirmation or bill file, excluding bookings that already have an active or completed truck appointment. Container allocation and loading status do not affect eligibility. Edit pages display their current booking from appointment detail rather than injecting it into this create-only result. parameters: - {name: keyword, in: query, required: false, schema: {type: string, maxLength: 64}, description: Matches booking number or bill of lading} - {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: Paged booking candidates, content: {application/json: {schema: {$ref: '#/components/schemas/CandidatePageEnvelope'}}}} /api/platform/v1/truck-appointments/{id}: parameters: [{$ref: '#/components/parameters/AppointmentId'}] get: summary: Query a truck appointment responses: '200': {description: 'Appointment detail', content: {application/json: {schema: {$ref: '#/components/schemas/DetailStableReferences'}}}} put: summary: Update an editable truck appointment with optimistic locking requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/SaveRequest'} responses: '200': {description: Updated appointment} /api/platform/v1/truck-appointments/{id}/{action}: parameters: - {$ref: '#/components/parameters/AppointmentId'} - {$ref: '#/components/parameters/Action'} - {$ref: '#/components/parameters/IdempotencyKey'} - {$ref: '#/components/parameters/RequestHash'} post: summary: Apply an idempotent appointment lifecycle action description: All actions transition the booking-level appointment as one aggregate. PICKUP does not accept or write a container number. ARRIVE and LOAD persist their actual event timestamps. Manual DEPART and COMPLETE actions are not supported; confirmation of all required containers departing the domestic warehouse completes the appointment automatically. requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/ActionRequest'} responses: '200': {description: Updated or replayed appointment} /api/platform/v1/truck-appointments/{id}/reschedule: parameters: - {$ref: '#/components/parameters/AppointmentId'} - {$ref: '#/components/parameters/IdempotencyKey'} - {$ref: '#/components/parameters/RequestHash'} - name: plannedLoadingAt in: query required: true schema: {type: string, format: date-time} post: summary: Idempotently reschedule an appointment requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/ActionRequest'} responses: '200': {description: Rescheduled or replayed appointment} /api/platform/v1/truck-appointments/{id}/history: parameters: [{$ref: '#/components/parameters/AppointmentId'}] get: summary: Query immutable appointment status history responses: '200': {description: 'History entries including action, from/to status, operator, reason and time'} /api/platform/v1/truck-appointments/{id}/attachments: parameters: [{$ref: '#/components/parameters/AppointmentId'}] get: summary: Query non-booking operational attachment references owned by the appointment description: 'Requires platform:firstleg:truck-appointment:list.' responses: '200': {description: 'Attachment references', content: {application/json: {schema: {type: array, items: {$ref: '#/components/schemas/Attachment'}}}}} post: summary: Reference a platform-owned file already scoped to this appointment description: 'Requires platform:firstleg:truck-appointment:edit. The file must belong to platform scope + TRUCK_APPOINTMENT_ATTACHMENT + appointmentId.' requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/AttachmentRequest'}}}} responses: '200': {description: 'Created attachment reference', content: {application/json: {schema: {$ref: '#/components/schemas/Attachment'}}}} /api/platform/v1/truck-appointments/{id}/attachments/upload: parameters: [{$ref: '#/components/parameters/AppointmentId'}] post: summary: Upload and reference a truck appointment attachment description: 'Requires platform:firstleg:truck-appointment:edit.' requestBody: required: true content: multipart/form-data: schema: type: object required: [attachmentType, file] properties: attachmentType: {type: string, enum: [DISPATCH_ORDER, DRIVER_DOCUMENT, VEHICLE_DOCUMENT, OTHER], description: Booking confirmations and bills of lading belong to the ocean-booking aggregate} file: {type: string, format: binary} responses: '200': {description: Uploaded attachment reference} /api/platform/v1/truck-appointments/{id}/attachments/{attachmentId}: parameters: - {$ref: '#/components/parameters/AppointmentId'} - {$ref: '#/components/parameters/AttachmentId'} delete: summary: Delete an attachment reference owned by the appointment description: 'Requires platform:firstleg:truck-appointment:edit. Deletes the appointment reference only; the File object is retained.' responses: '200': {description: Reference deleted} components: parameters: AppointmentId: {name: id, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} AttachmentId: {name: attachmentId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} Action: name: action in: path required: true schema: type: string enum: [SUBMIT, DISPATCH, PICKUP, ARRIVE, LOAD, EXCEPTION, CANCEL] IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}} RequestHash: {name: X-Request-Hash, in: header, required: false, schema: {type: string, pattern: '^[0-9a-fA-F]{64}$'}} schemas: CandidatePageEnvelope: type: object required: [code, message, data, timestamp] properties: code: {type: string, example: '200'} message: {type: string} data: type: object required: [records, page, size, total, pages] properties: records: {type: array, items: {$ref: '#/components/schemas/Candidate'}} page: {type: integer, format: int64} size: {type: integer, format: int64} total: {type: integer, format: int64} pages: {type: integer, format: int64} requestId: {type: string, nullable: true} traceId: {type: string, nullable: true} timestamp: {type: string, format: date-time} Candidate: type: object required: [oceanBookingId, oceanBookingNo, warehouseId, bookingDocumentReady] properties: oceanBookingId: {type: integer, format: int64} oceanBookingNo: {type: string} billOfLadingNo: {type: string, nullable: true} warehouseId: {type: integer, format: int64} warehouseName: {type: string, nullable: true} channelCode: {type: string, nullable: true} channelName: {type: string, nullable: true} bookingDocumentReady: {type: boolean, enum: [true]} DetailEnvelope: type: object required: [code, message, data, timestamp] properties: code: {type: string, example: '200'} message: {type: string} data: allOf: [{$ref: '#/components/schemas/DetailStableReferences'}] nullable: true requestId: {type: string, nullable: true} traceId: {type: string, nullable: true} timestamp: {type: string, format: date-time} AttachmentRequest: type: object required: [fileId, attachmentType] properties: fileId: {type: integer, format: int64, minimum: 1} attachmentType: {type: string, enum: [DISPATCH_ORDER, DRIVER_DOCUMENT, VEHICLE_DOCUMENT, OTHER]} fileName: {type: string, maxLength: 255, description: Ignored; authoritative metadata comes from File service} fileHash: {type: string, maxLength: 128, description: Ignored; authoritative metadata comes from File service} Attachment: type: object properties: id: {type: integer, format: int64} appointmentId: {type: integer, format: int64} fileId: {type: integer, format: int64} attachmentType: {type: string, enum: [DISPATCH_ORDER, DRIVER_DOCUMENT, VEHICLE_DOCUMENT, OTHER]} fileName: {type: string} fileHash: {type: string} visibility: {type: string, enum: [PLATFORM]} uploadedBy: {type: integer, format: int64} uploadedAt: {type: string, format: date-time} ActionRequest: type: object required: [expectedVersion] properties: expectedVersion: {type: integer, minimum: 0} reason: {type: string, maxLength: 500} SaveRequest: type: object required: [oceanBookingId, carrierId, plannedLoadingAt, expectedVersion] properties: outboundOrderId: {type: integer, format: int64, minimum: 1, nullable: true} oceanBookingId: {type: integer, format: int64, minimum: 1, description: Required RELEASED booking; must have a bill of lading and own an active BOOKING_CONFIRMATION or BILL_OF_LADING file.} warehouseId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional consistency hint. The persisted warehouse is frozen from the selected booking.} carrierId: {type: integer, format: int64, minimum: 1, description: Required ACTIVE Warehouse supplier ID validated as TRUCKING with truckingScope DOMESTIC (国内拖车).} carrierName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint only; the persisted snapshot comes from Warehouse supplier master data.} billOfLadingNo: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint; the persisted snapshot is always frozen from the selected RELEASED booking.} plannedLoadingAt: {type: string, format: date-time} payableAmount: {type: number, format: double, minimum: 0, nullable: true, description: 应付拖车费,最多两位小数;全部订舱需求货柜确认国内离仓后按实装体积分摊生成应付来源;零金额不生成费用。} currency: {type: string, enum: [RMB], default: RMB, description: 人民币拖车费;完成时以CNY进入应付费用来源。} expectedVersion: {type: integer, minimum: 0, description: Must be 0 on create; current version on edit.} DetailStableReferences: type: object required: [appointmentNo, carrierId, carrierName, attachmentCount] description: Stable booking and supplier references returned by appointment detail. Container identities are intentionally absent. properties: appointmentNo: {type: string, pattern: '^TA-[0-9]{6}-[0-9]{4}$', example: TA-260818-0001, description: 派车单号;按北京时间创建日逐日编号} oceanBookingId: {type: integer, format: int64, nullable: true} oceanBookingNo: {type: string, nullable: true, pattern: '^OB-[0-9]{6}-[0-9]{4}$', example: OB-260818-0001} warehouseId: {type: integer, format: int64, nullable: true} warehouseName: {type: string, nullable: true} shippingCompanyId: {type: integer, format: int64, nullable: true} shippingCompanyName: {type: string, nullable: true} bookingAgentId: {type: integer, format: int64, nullable: true} bookingAgentName: {type: string, nullable: true} carrierId: {type: integer, format: int64} carrierName: {type: string} billOfLadingNo: {type: string, maxLength: 128, nullable: true, description: Frozen bill of lading snapshot used by dispatch readiness.} payableAmount: {type: number, format: double, minimum: 0, nullable: true} currency: {type: string, enum: [RMB]} attachmentCount: {type: integer, format: int64, minimum: 0, description: Number of attachment references owned by this appointment}