feat(firstleg): implement container customs workflow

This commit is contained in:
2026-08-28 16:48:50 +08:00
parent bb9673358d
commit 0ea4aeecc9
92 changed files with 3165 additions and 2754 deletions
@@ -1,127 +1,135 @@
openapi: 3.0.3
info:
title: Phase 3 Customs Collaboration API
version: 1.0.0
description: Immutable checklist and file versions, supplement requests, frozen agent batches, receipts and history.
info: {title: Firstleg Container Customs API, version: 2.0.0}
paths:
/api/platform/v1/customs/checklist-versions:
post:
summary: Create an immutable customs document checklist version
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
responses: {'200': {description: Checklist version}}
/api/platform/v1/customs/cases:
/api/platform/v1/customs/loading-plans/{planId}:
get:
summary: Page customs cases
responses: {'200': {description: Page result}}
summary: 查询整柜报关状态、缺资料票及文件映射
x-permission: platform:firstleg:customs:list
parameters: [{$ref: '#/components/parameters/PlanId'}]
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
/api/platform/v1/customs/loading-plans/{planId}/start:
post:
summary: Create a customer/order/inbound/container scoped customs case
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
responses: {'200': {description: Customs case detail}}
/api/platform/v1/customs/cases/{caseId}:
parameters: [{$ref: '#/components/parameters/CaseId'}]
summary: 确认开始整柜报关并冻结全部当前资料版本和覆盖范围
x-permission: platform:firstleg:customs:manage
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody: {$ref: '#/components/requestBodies/ActionBody'}
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
/api/platform/v1/customs/loading-plans/{planId}/complete-all-buy:
post:
summary: 二次确认全买单柜报关完成
x-permission: platform:firstleg:customs:manage
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody: {$ref: '#/components/requestBodies/ActionBody'}
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
/api/platform/v1/customs/loading-plans/documents/{documentId}/completion/upload:
post:
summary: 上传或覆盖冻结客户资料的一对一完成文件
description: 完成文件自动继承冻结入仓码且不可删除;最后一份须finalConfirmation=true,File调用前服务端预检。
x-permission: platform:firstleg:customs:manage
parameters:
- {$ref: '#/components/parameters/DocumentId'}
- {$ref: '#/components/parameters/IdempotencyKey'}
- {name: expectedVersion, in: query, required: true, schema: {type: integer, minimum: 0}}
- {name: finalConfirmation, in: query, required: true, schema: {type: boolean}}
requestBody: {$ref: '#/components/requestBodies/FileBody'}
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
/api/platform/v1/customs/loading-plans/documents/{documentId}/download:
get:
summary: Query complete customs detail and stable history
responses: {'200': {description: Detail}}
/api/platform/v1/customs/cases/{caseId}/documents:
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
post:
summary: Reference a file validated against customer, CUSTOMS_DOCUMENT and case ID
responses: {'200': {description: New document version}}
/api/platform/v1/customs/cases/{caseId}/documents/upload:
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
post:
summary: Upload a document and create a new immutable version
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [itemCode, expectedVersion, file]
properties:
itemCode: {type: string}
expectedVersion: {type: integer, minimum: 0}
file: {type: string, format: binary}
responses: {'200': {description: New document version}}
/api/platform/v1/customs/cases/{caseId}/documents/{documentId}/review:
parameters:
- {$ref: '#/components/parameters/CaseId'}
- {name: documentId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {$ref: '#/components/parameters/IdempotencyKey'}
post:
summary: Approve a current file version or return it with a supplement task
responses: {'200': {description: Updated detail}}
/api/platform/v1/customs/cases/{caseId}/submission-batches:
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
post:
summary: Submit to customs agent and freeze approved file versions in a batch snapshot
description: customsAgentId must identify an ACTIVE Warehouse supplier in category CUSTOMS; the persisted agent name is resolved server-side and free text cannot bypass supplier validation.
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/SubmitBatchRequest'}}}}
responses:
'200':
description: Submission batch
content:
application/json:
schema: {$ref: '#/components/schemas/CustomsSubmissionBatch'}
/api/platform/v1/customs/submission-batches/{batchId}/receipts:
parameters:
- {name: batchId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {$ref: '#/components/parameters/IdempotencyKey'}
post:
summary: Reference a customer-owned receipt file with explicit visibility
responses: {'200': {description: Receipt version}}
/api/platform/v1/customs/submission-batches/{batchId}/receipts/upload:
post:
summary: Upload a receipt version; RELEASED closes the customs dispatch gate
responses: {'200': {description: Receipt version}}
summary: 平台下载客户报送资料
x-permission: platform:firstleg:customs:list
parameters: [{$ref: '#/components/parameters/DocumentId'}]
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
/api/platform/v1/customs/loading-plans/documents/{documentId}/completion/download:
get:
summary: 平台下载报关完成文件
x-permission: platform:firstleg:customs:list
parameters: [{$ref: '#/components/parameters/DocumentId'}]
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
/api/merchant/v1/customs/orders/{orderId}:
get:
summary: Query the authenticated customer's customs case by order
responses: {'200': {description: Customer-filtered detail}}
/api/merchant/v1/customs/cases/{caseId}/documents/upload:
summary: 客户订单列表查询当前完成装柜退税报关四态
x-permission: merchant:order:view
parameters: [{$ref: '#/components/parameters/OrderId'}]
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
/api/merchant/v1/customs/loading-plans/{planId}/documents/upload:
post:
summary: Upload or replace a checklist document within authenticated customer scope
responses: {'200': {description: New document version}}
/api/merchant/v1/customs/cases/{caseId}/receipts/{receiptId}/download:
summary: 上传资料并关联当前柜内本客户一个或多个退税入仓码
x-permission: merchant:order:edit
parameters:
- {$ref: '#/components/parameters/PlanId'}
- {$ref: '#/components/parameters/IdempotencyKey'}
- {$ref: '#/components/parameters/InboundIds'}
requestBody: {$ref: '#/components/requestBodies/FileBody'}
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
/api/merchant/v1/customs/documents/{documentId}/upload:
post:
summary: 报关开始前覆盖资料并同步修改同柜关联票
x-permission: merchant:order:edit
parameters:
- {$ref: '#/components/parameters/DocumentId'}
- {$ref: '#/components/parameters/IdempotencyKey'}
- {$ref: '#/components/parameters/InboundIds'}
- {name: expectedVersion, in: query, required: true, schema: {type: integer, minimum: 1}}
requestBody: {$ref: '#/components/requestBodies/FileBody'}
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
/api/merchant/v1/customs/documents/{documentId}/inbounds:
put:
summary: 报关开始前修改资料关联的同柜退税入仓码
x-permission: merchant:order:edit
parameters: [{$ref: '#/components/parameters/DocumentId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ScopeRequest'}}}}
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
/api/merchant/v1/customs/documents/{documentId}:
delete:
summary: 报关开始前逻辑删除误传资料并保留不可变版本
x-permission: merchant:order:edit
parameters:
- {$ref: '#/components/parameters/DocumentId'}
- {$ref: '#/components/parameters/IdempotencyKey'}
- {name: expectedVersion, in: query, required: true, schema: {type: integer, minimum: 1}}
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
/api/merchant/v1/customs/documents/{documentId}/download:
get:
summary: Download only CUSTOMER-visible receipts from the authenticated customer's case
responses: {'200': {description: Authorized file stream}}
summary: 客户下载自己当前报送资料
x-permission: merchant:order:view
parameters: [{$ref: '#/components/parameters/DocumentId'}]
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
/api/merchant/v1/customs/documents/{documentId}/completion/download:
get:
summary: 整柜完成后客户下载对应完成文件
x-permission: merchant:order:view
parameters: [{$ref: '#/components/parameters/DocumentId'}]
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
/internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches:
post:
summary: File导出中心按loadingPlanId分页获取已完成装柜买单实际行
parameters: [{name: X-Internal-Service-Token, in: header, required: true, schema: {type: string}}]
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ExportBatchRequest'}}}}
responses: {'200': {description: 成功, content: {application/json: {schema: {$ref: '#/components/schemas/ExportBatchEnvelope'}}}}}
components:
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema: {type: string, minLength: 1, maxLength: 128}
CaseId:
name: caseId
in: path
required: true
schema: {type: string, pattern: '^[1-9][0-9]*$'}
PlanId: {name: planId, in: path, required: true, schema: {$ref: '#/components/schemas/LongId'}}
OrderId: {name: orderId, in: path, required: true, schema: {$ref: '#/components/schemas/LongId'}}
DocumentId: {name: documentId, in: path, required: true, schema: {$ref: '#/components/schemas/LongId'}}
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, minLength: 1, maxLength: 128}}
InboundIds: {name: inboundIds, in: query, required: true, schema: {type: array, minItems: 1, maxItems: 200, items: {$ref: '#/components/schemas/LongId'}}}
requestBodies:
ActionBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}}}
FileBody: {required: true, content: {multipart/form-data: {schema: {type: object, required: [file], properties: {file: {type: string, format: binary, description: 任意格式且最大50MB}}}}}}
responses:
ContainerResponse: {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/ContainerEnvelope'}}}}
DocumentResponse: {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentEnvelope'}}}}
BinaryResponse: {description: 文件二进制, headers: {Content-Disposition: {schema: {type: string}}, Cache-Control: {schema: {type: string, example: no-store}}}, content: {application/octet-stream: {schema: {type: string, format: binary}}}}
schemas:
SubmitBatchRequest:
type: object
required: [customsAgentId, expectedVersion]
properties:
customsAgentId: {type: integer, format: int64, minimum: 1, description: ACTIVE Warehouse supplier ID validated as CUSTOMS.}
customsAgentName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint; persisted name always comes from Warehouse.}
externalReference: {type: string, maxLength: 128, nullable: true}
note: {type: string, maxLength: 500, nullable: true}
expectedVersion: {type: integer, minimum: 0}
CustomsSubmissionBatch:
type: object
required: [id, customsCaseId, batchNo, batchVersion, customsAgentName, status, submittedBy, submittedAt]
properties:
id: {type: string, pattern: '^[1-9][0-9]*$'}
customsCaseId: {type: string, pattern: '^[1-9][0-9]*$'}
batchNo: {type: string, maxLength: 40}
batchVersion: {type: integer, minimum: 1}
customsAgentId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: Server-validated CUSTOMS supplier ID snapshot; null only for non-migrated historical rows.}
customsAgentName: {type: string, maxLength: 128, description: Server-frozen Warehouse supplier name snapshot.}
externalReference: {type: string, maxLength: 128, nullable: true}
fileSnapshotJson: {type: string}
status: {type: string, enum: [SUBMITTED, ACKNOWLEDGED, REJECTED, COMPLETED]}
note: {type: string, maxLength: 500, nullable: true}
submittedBy: {type: string, pattern: '^[1-9][0-9]*$'}
submittedByName: {type: string, maxLength: 64, nullable: true}
submittedAt: {type: string, format: date-time}
LongId: {type: string, pattern: '^[1-9][0-9]*$', description: Long ID序列化为字符串}
ApiEnvelope: {type: object, required: [code, message, timestamp], properties: {code: {type: string}, message: {type: string}, requestId: {type: string, nullable: true}, traceId: {type: string, nullable: true}, timestamp: {type: string, format: date-time}}}
ContainerEnvelope: {allOf: [{$ref: '#/components/schemas/ApiEnvelope'}, {type: object, properties: {data: {$ref: '#/components/schemas/ContainerView'}}}]}
DocumentEnvelope: {allOf: [{$ref: '#/components/schemas/ApiEnvelope'}, {type: object, properties: {data: {$ref: '#/components/schemas/DocumentView'}}}]}
ActionRequest: {type: object, required: [expectedVersion, confirm], additionalProperties: false, properties: {expectedVersion: {type: integer, minimum: 0}, confirm: {type: boolean}, remark: {type: string, maxLength: 500, nullable: true}}}
ScopeRequest: {type: object, required: [inboundIds, expectedVersion], additionalProperties: false, properties: {inboundIds: {type: array, minItems: 1, maxItems: 200, items: {$ref: '#/components/schemas/LongId'}}, expectedVersion: {type: integer, minimum: 1}}}
FileView: {type: object, required: [fileId, fileName, fileHash, sizeBytes, version, uploadedAt], properties: {fileId: {$ref: '#/components/schemas/LongId'}, fileName: {type: string}, fileHash: {type: string}, contentType: {type: string, nullable: true}, sizeBytes: {type: integer, format: int64}, version: {type: integer}, uploadedAt: {type: string, format: date-time}}}
InboundView: {type: object, required: [inboundId, orderId, orderNo, inboundCode, tenantId, customerName, taxMode, covered], properties: {inboundId: {$ref: '#/components/schemas/LongId'}, orderId: {$ref: '#/components/schemas/LongId'}, orderNo: {type: string}, inboundCode: {type: string}, tenantId: {$ref: '#/components/schemas/LongId'}, customerName: {type: string}, taxMode: {type: string, enum: [TAX_REFUND, BUY_DECLARATION]}, covered: {type: boolean}}}
DocumentView: {type: object, required: [documentId, tenantId, source, inboundIds, inboundCodes, frozen], properties: {documentId: {$ref: '#/components/schemas/LongId'}, tenantId: {$ref: '#/components/schemas/LongId'}, source: {$ref: '#/components/schemas/FileView'}, inboundIds: {type: array, items: {$ref: '#/components/schemas/LongId'}}, inboundCodes: {type: array, items: {type: string}}, frozen: {type: boolean}, completion: {allOf: [{$ref: '#/components/schemas/FileView'}], nullable: true}}}
ContainerView: {type: object, required: [aggregateId, loadingPlanId, containerBookingId, status, version, taxRefundCount, coveredTaxRefundCount, inbounds, documents], properties: {aggregateId: {$ref: '#/components/schemas/LongId'}, loadingPlanId: {$ref: '#/components/schemas/LongId'}, containerBookingId: {$ref: '#/components/schemas/LongId'}, planNo: {type: string}, cabinetNo: {type: string}, containerNo: {type: string, nullable: true}, status: {type: string, enum: [WAITING_CUSTOMER_DOCUMENTS, READY_TO_START, DECLARING, COMPLETED]}, merchantStatus: {type: string, enum: [WAITING_UPLOAD, DOCUMENT_UPLOADED, DECLARING, CUSTOMS_COMPLETED], nullable: true}, version: {type: integer}, taxRefundCount: {type: integer}, coveredTaxRefundCount: {type: integer}, inbounds: {type: array, items: {$ref: '#/components/schemas/InboundView'}}, documents: {type: array, items: {$ref: '#/components/schemas/DocumentView'}}, startedAt: {type: string, format: date-time, nullable: true}, completedAt: {type: string, format: date-time, nullable: true}}}
ExportBatchRequest: {type: object, required: [taskId, tenantId, appCode, requestedBy, dispatchVersion, parameters, limit], properties: {taskId: {type: string}, tenantId: {type: string, enum: ['0']}, appCode: {type: string, enum: [PLATFORM]}, requestedBy: {type: string}, dispatchVersion: {type: integer}, parameters: {type: object, required: [loadingPlanId], additionalProperties: false, properties: {loadingPlanId: {type: string}}}, cursor: {type: string, nullable: true}, snapshotToken: {type: string, nullable: true}, limit: {type: integer, minimum: 1, maximum: 500}}}
ExportBatchEnvelope: {allOf: [{$ref: '#/components/schemas/ApiEnvelope'}, {type: object, properties: {data: {type: object, properties: {schemaVersion: {type: integer}, schemaHash: {type: string}, snapshotToken: {type: string}, columns: {type: array, items: {type: object}}, rows: {type: array, items: {type: object}}, nextCursor: {type: string}, hasMore: {type: boolean}, totalRows: {type: integer, format: int64, nullable: true}}}}}]}
@@ -81,70 +81,6 @@ paths:
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}]
responses:
'200': {$ref: '#/components/responses/ConfirmedDetailResponse'}
/api/platform/v1/first-leg/loading-lists/{planId}/customs:
get:
summary: 查询装柜清单出口报关文档
x-permission: platform:firstleg:loading-list:list
description: 查询出口报关文档;写入与完成继续要求 platform:firstleg:customs:manage。
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}]
responses:
'200': {$ref: '#/components/responses/BusinessDocumentResponse'}
/api/platform/v1/first-leg/loading-lists/{planId}/customs/document:
post:
summary: 上传或替换装柜清单出口报关文档
x-permission: platform:firstleg:customs:manage
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentAttachRequest'}}}
responses:
'200': {$ref: '#/components/responses/BusinessDocumentResponse'}
/api/platform/v1/first-leg/loading-lists/{planId}/customs/document/{fileId}/download:
get:
summary: 下载装柜清单出口报关附件
description: 完成态仍可下载。服务端校验 planId、warehouseId、当前 CUSTOMS_DECLARATION 的 fileId,以及 File 元数据 tenantId=0、businessType=LOADING_CUSTOMS_DECLARATION、businessId=planId;错误 fileId 或归属返回 A0404。响应使用 attachment 文件名并禁止缓存。
x-permission: platform:firstleg:loading-list:list
parameters:
- {$ref: '#/components/parameters/PlanId'}
- {$ref: '#/components/parameters/WarehouseId'}
- name: fileId
in: path
required: true
description: File 服务长整型文件 ID
schema: {$ref: '#/components/schemas/LongId'}
responses:
'200':
description: 报关附件二进制内容;包含 Content-Disposition,Cache-Control 为 no-store
headers:
Content-Disposition: {schema: {type: string}}
Cache-Control: {schema: {type: string, example: no-store}}
content:
application/octet-stream:
schema: {type: string, format: binary}
'404': {description: 装柜计划、仓库、当前附件或 File 归属不匹配}
/api/platform/v1/first-leg/loading-lists/{planId}/customs/document/upload:
post:
summary: 直接选择文件上传或替换装柜清单出口报关文档
description: Firstleg 使用内部服务令牌上传 File,浏览器不得传递 fileId 或内部令牌;旧 JSON document 接口仅兼容存量调用。
x-permission: platform:firstleg:customs:manage
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
multipart/form-data:
schema: {$ref: '#/components/schemas/DocumentUploadRequest'}
responses:
'200': {$ref: '#/components/responses/BusinessDocumentResponse'}
/api/platform/v1/first-leg/loading-lists/{planId}/customs/complete:
post:
summary: 点击完成出口报关
x-permission: platform:firstleg:customs:manage
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentCompleteRequest'}}}
responses:
'200': {$ref: '#/components/responses/BusinessDocumentResponse'}
components:
parameters:
PlanId:
@@ -218,9 +154,6 @@ components:
ConfirmedDetailResponse:
description: 装柜清单详情查询或确认成功,也可能返回业务失败
content: {application/json: {schema: {$ref: '#/components/schemas/ConfirmedDetailEnvelope'}}}
BusinessDocumentResponse:
description: 报关文档查询或操作结果
content: {application/json: {schema: {$ref: '#/components/schemas/BusinessDocumentEnvelope'}}}
schemas:
LongId:
type: string
@@ -279,14 +212,6 @@ components:
data:
allOf: [{$ref: '#/components/schemas/ConfirmedDetail'}]
nullable: true
BusinessDocumentEnvelope:
allOf:
- {$ref: '#/components/schemas/ApiEnvelope'}
- type: object
properties:
data:
allOf: [{$ref: '#/components/schemas/BusinessDocument'}]
nullable: true
PreloadPage:
type: object
required: [records, page, size, total, pages]
@@ -464,42 +389,3 @@ components:
voyageNo: {type: string, maxLength: 64, nullable: true, description: 航次}
expectedVersion: {type: integer, format: int32, minimum: 0, description: 当前乐观锁版本}
operationReason: {type: string, maxLength: 500, nullable: true, description: 操作原因}
DocumentAttachRequest:
type: object
required: [fileId, expectedVersion]
additionalProperties: false
properties:
fileId: {$ref: '#/components/schemas/LongId'}
expectedVersion: {type: integer, format: int32, minimum: 0}
operationReason: {type: string, maxLength: 500, nullable: true}
DocumentCompleteRequest:
type: object
required: [expectedVersion]
additionalProperties: false
properties:
expectedVersion: {type: integer, format: int32, minimum: 0}
operationReason: {type: string, maxLength: 500, nullable: true}
DocumentUploadRequest:
type: object
required: [file, expectedVersion]
additionalProperties: false
properties:
file: {type: string, format: binary, description: 用户直接选择的报关文档}
expectedVersion: {type: integer, format: int32, minimum: 0, description: 当前文档乐观锁版本,首次上传为 0}
operationReason: {type: string, maxLength: 500, nullable: true}
BusinessDocument:
type: object
required: [id, documentType, fileId, fileName, fileHash, uploadedBy, uploadedAt, version]
properties:
id: {$ref: '#/components/schemas/LongId'}
documentType: {type: string, enum: [CUSTOMS_DECLARATION]}
fileId: {$ref: '#/components/schemas/LongId'}
fileName: {type: string}
fileHash: {type: string, pattern: '^[0-9a-fA-F]{64}$'}
uploadedBy: {$ref: '#/components/schemas/LongId'}
uploadedByName: {type: string, nullable: true}
uploadedAt: {type: string, format: date-time}
completedBy: {$ref: '#/components/schemas/NullableLongId'}
completedByName: {type: string, nullable: true}
completedAt: {type: string, format: date-time, nullable: true}
version: {type: integer, format: int32, minimum: 0}
@@ -45,7 +45,7 @@ paths:
components:
parameters:
ContainerId: {name: id, in: path, required: true, schema: {type: integer, format: int64}}
DocumentType: {name: documentType, in: path, required: true, schema: {type: string, enum: [DELIVERY_ORDER, PICKUP_ORDER]}, description: '通用单证仅支持DO/PU;清关使用 /clearance,出口报关使用 loading-list 专用接口'}
DocumentType: {name: documentType, in: path, required: true, schema: {type: string, enum: [DELIVERY_ORDER, PICKUP_ORDER]}, description: '通用单证仅支持DO/PU;目的港清关使用 /clearance;出口报关由 customs-v1 柜级聚合接口承载'}
requestBodies:
ExceptionRequest: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ExceptionRequest'}}}}
schemas:
@@ -57,7 +57,7 @@ components:
container: {$ref: '#/components/schemas/Container'}
loadingPlanId: {type: integer, format: int64}
pda: {$ref: '#/components/schemas/PdaSummary'}
documents: {type: array, description: '按 documentType 稳定排序返回 DESTINATION_CLEARANCE、CUSTOMS_DECLARATION、DELIVERY_ORDER、PICKUP_ORDER 的当前唯一记录和真实 version', items: {$ref: '#/components/schemas/Document'}}
documents: {type: array, description: '按 documentType 稳定排序返回 DESTINATION_CLEARANCE、DELIVERY_ORDER、PICKUP_ORDER 的当前唯一记录和真实 version;出口报关只由 customs-v1 柜级聚合承载', items: {$ref: '#/components/schemas/Document'}}
exception: {$ref: '#/components/schemas/ExceptionState'}
exceptionHistory: {type: array, items: {$ref: '#/components/schemas/ExceptionHistory'}}
timeline: {type: array, items: {$ref: '#/components/schemas/TimelineEntry'}}
@@ -65,7 +65,7 @@ components:
PdaSummary: {type: object, properties: {taskId: {type: integer, format: int64}, loadingPlanId: {type: integer, format: int64}, containerBookingId: {type: integer, format: int64}, taskStatus: {type: string, description: '持久化任务态:LOADING/COMPLETED'}, workflowStatus: {type: string, enum: [IN_PROGRESS, AWAITING_REVIEW, COMPLETED], description: 'PDA作业展示态'}, taskVersion: {type: integer}, itemCount: {type: integer}, completedItemCount: {type: integer}, unresolvedItemCount: {type: integer}, photoCounts: {type: array, items: {$ref: '#/components/schemas/PhotoCount'}}, photos: {type: array, items: {$ref: '#/components/schemas/Photo'}}, exceptionReasons: {type: array, items: {type: string}}}}
PhotoCount: {type: object, properties: {evidenceType: {type: string}, count: {type: integer}}}
Photo: {type: object, properties: {evidenceId: {type: integer, format: int64}, tenantId: {type: integer, format: int64}, inboundId: {type: integer, format: int64}, evidenceType: {type: string}, fileId: {type: integer, format: int64}, fileName: {type: string}, status: {type: string, enum: [ACTIVE, VOID]}, downloadPath: {type: string, nullable: true, description: '仅 ACTIVE 照片提供鉴权下载路径;VOID 历史照片为空'}, uploadedAt: {type: string, format: date-time}}}
Document: {type: object, properties: {id: {type: integer, format: int64}, documentType: {type: string, enum: [DESTINATION_CLEARANCE, CUSTOMS_DECLARATION, DELIVERY_ORDER, PICKUP_ORDER]}, fileId: {type: integer, format: int64}, fileName: {type: string}, fileHash: {type: string}, uploadedBy: {type: integer, format: int64}, uploadedByName: {type: string}, uploadedAt: {type: string, format: date-time}, version: {type: integer, description: '当前唯一记录的真实乐观锁版本;更新时必须原样作为 expectedVersion'}, completed: {type: boolean}}}
Document: {type: object, properties: {id: {type: integer, format: int64}, documentType: {type: string, enum: [DESTINATION_CLEARANCE, DELIVERY_ORDER, PICKUP_ORDER]}, fileId: {type: integer, format: int64}, fileName: {type: string}, fileHash: {type: string}, uploadedBy: {type: integer, format: int64}, uploadedByName: {type: string}, uploadedAt: {type: string, format: date-time}, version: {type: integer, description: '当前唯一记录的真实乐观锁版本;更新时必须原样作为 expectedVersion'}, completed: {type: boolean}}}
ExceptionState: {type: object, properties: {active: {type: boolean}, reason: {type: string}, markedBy: {type: integer, format: int64}, markedByName: {type: string}, markedAt: {type: string, format: date-time}}}
ExceptionHistory: {type: object, properties: {id: {type: integer, format: int64}, action: {type: string, enum: [MARK, CANCEL]}, reason: {type: string}, operatorId: {type: integer, format: int64}, operatorName: {type: string}, occurredAt: {type: string, format: date-time}}}
TimelineEntry: {type: object, properties: {eventType: {type: string}, detail: {type: string}, operatorId: {type: integer, format: int64}, operatorName: {type: string}, occurredAt: {type: string, format: date-time}}}
@@ -37,7 +37,7 @@ paths:
/api/platform/v1/first-leg/outbound-dispatches/{containerBookingId}/gate:
get:
summary: Evaluate every inbound binding in the fail-closed container outbound gate
description: Loading evidence is evaluated once for the physical container/loading plan and the same result applies to every inbound binding. The customs gate is resolved once from the current container CUSTOMS_DECLARATION whose completedAt must be non-null; upload alone remains blocked and fl_customs_case is not an alternative gate fact. A missing standalone sailing schedule is reference-only and never affects gate.passed.
description: Loading evidence is evaluated once for the physical container/loading plan and the same result applies to every inbound binding. The customs gate is resolved once from fl_customs_container and only status=COMPLETED passes; customer upload, READY_TO_START and DECLARING remain blocked. A missing standalone sailing schedule is reference-only and never affects gate.passed. Container customs completion attempts system confirmation immediately and a bounded scheduler retries after later gates become ready.
x-permission: platform:firstleg:outbound-dispatch:check
parameters:
- {$ref: '#/components/parameters/ContainerBookingIdPath'}