openapi: 3.0.3 info: title: 头程装柜与完成装柜清单接口 version: 1.0.0 description: 装柜计划仅允许 EDITING 到 LOADING 再到 COMPLETED 单向推进;EDITING 和 LOADING 均可维护未拣货明细,COMPLETED 后计划、明细与货柜运输信息永久只读,不提供回退或撤回接口。 paths: /api/platform/v1/first-leg/loading-plans: get: summary: 分页查询预装清单 description: 默认返回全部仓库的装柜计划;可按客户和仓库筛选。 x-permission: platform:firstleg:preload:list parameters: - {$ref: '#/components/parameters/Page'} - {$ref: '#/components/parameters/Size'} - {$ref: '#/components/parameters/Keyword'} - {$ref: '#/components/parameters/CustomerId'} - name: status in: query description: 预装状态 schema: {type: string, enum: [EDITING, LOADING]} - {$ref: '#/components/parameters/OptionalWarehouseId'} - {$ref: '#/components/parameters/CreatedFrom'} - {$ref: '#/components/parameters/CreatedTo'} responses: '200': {$ref: '#/components/responses/PreloadPageResponse'} /api/platform/v1/first-leg/loading-plans/{planId}: get: summary: 查询预装清单详情 x-permission: platform:firstleg:preload:list description: 返回同一装柜计划的汇总、冻结明细和完整操作历史。 parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}] responses: '200': {$ref: '#/components/responses/PreloadDetailResponse'} /api/platform/v1/first-leg/loading-plans/{planId}/transport: patch: summary: 修改预装运输信息 description: 已推送且仍为 EDITING 的预装清单可补录或修改集装箱号与航次;LOADING 阶段按稳定柜次身份规则维护,不能清空已创建 PDA 任务的柜号。 x-permission: platform:firstleg:preload:edit parameters: - {$ref: '#/components/parameters/PlanId'} - {$ref: '#/components/parameters/WarehouseId'} - {$ref: '#/components/parameters/IdempotencyKey'} requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/TransportUpdateRequest'} responses: '200': {$ref: '#/components/responses/PreloadDetailResponse'} /api/platform/v1/first-leg/loading-plans/{planId}/confirm: post: summary: 原子确认整份预装计划 description: 以 loadingPlanId 为唯一范围,原子确认全部已分配入库预装票据。要求计划已 push 且仍为 EDITING、真实集装箱号已录入、稳定 bridge 完整、真实订舱已关联并为 RELEASED;任一票不满足均不写入。成功后唯一创建待领取 PDA task,claim 后才推进 LOADING。同一 Idempotency-Key 与相同请求精确重放原响应,不同 Key 重复确认返回业务冲突。 x-permission: platform:firstleg:preload:edit parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: required: true content: {application/json: {schema: {$ref: '#/components/schemas/ConfirmPreloadRequest'}}} responses: '200': {$ref: '#/components/responses/PreloadConfirmationResponse'} /api/platform/v1/first-leg/loading-lists: get: summary: 分页查询装柜清单 description: 默认返回全部仓库的 COMPLETED 装柜计划;可按客户和仓库筛选。 x-permission: platform:firstleg:loading-list:list parameters: - {$ref: '#/components/parameters/Page'} - {$ref: '#/components/parameters/Size'} - {$ref: '#/components/parameters/Keyword'} - {$ref: '#/components/parameters/CustomerId'} - {$ref: '#/components/parameters/OptionalWarehouseId'} - {$ref: '#/components/parameters/CreatedFrom'} - {$ref: '#/components/parameters/CreatedTo'} responses: '200': {$ref: '#/components/responses/ConfirmedPageResponse'} /api/platform/v1/first-leg/loading-lists/{planId}: get: summary: 查询装柜清单详情 description: 返回已确认计划的汇总、冻结明细和操作历史,全部只读;查看权限与分页列表权限独立。 x-permission: platform:firstleg:loading-list:list 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: name: planId in: path required: true description: 装柜计划 ID schema: {$ref: '#/components/schemas/LongId'} IdempotencyKey: name: Idempotency-Key in: header required: true description: 写操作幂等键,同一业务操作重试时必须保持不变 schema: {type: string, minLength: 1, maxLength: 128} Page: name: page in: query description: 页码,从 1 开始 schema: {type: integer, format: int64, minimum: 1, default: 1} Size: name: size in: query description: 每页条数 schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20} Keyword: name: keyword in: query description: 按预装单号、集装箱号或柜号模糊查询 schema: {type: string, maxLength: 128} CustomerId: name: customerId in: query required: false description: 仅返回包含该客户货物的清单 schema: {$ref: '#/components/schemas/LongId'} WarehouseId: name: warehouseId in: query required: true description: 仓库 ID;同一仓库内全部客户单据均可见 schema: {$ref: '#/components/schemas/LongId'} OptionalWarehouseId: name: warehouseId in: query required: false description: 可选仓库 ID;不传时查询全部仓库 schema: {$ref: '#/components/schemas/LongId'} CreatedFrom: name: createdFrom in: query description: 创建时间起点,包含该时间 schema: {type: string, format: date-time} CreatedTo: name: createdTo in: query description: 创建时间终点,包含该时间 schema: {type: string, format: date-time} responses: PreloadPageResponse: description: 预装清单分页查询成功或业务失败,按响应体 code 判断 content: {application/json: {schema: {$ref: '#/components/schemas/PreloadPageEnvelope'}}} PreloadDetailResponse: description: 预装清单详情查询或操作成功,也可能返回业务失败 content: {application/json: {schema: {$ref: '#/components/schemas/PreloadDetailEnvelope'}}} PreloadConfirmationResponse: description: 计划级预装确认成功、精确重放或业务失败 content: {application/json: {schema: {$ref: '#/components/schemas/PreloadConfirmationEnvelope'}}} ConfirmedPageResponse: description: 装柜清单分页查询成功或业务失败,按响应体 code 判断 content: {application/json: {schema: {$ref: '#/components/schemas/ConfirmedPageEnvelope'}}} ConfirmedDetailResponse: description: 装柜清单详情查询或确认成功,也可能返回业务失败 content: {application/json: {schema: {$ref: '#/components/schemas/ConfirmedDetailEnvelope'}}} BusinessDocumentResponse: description: 报关文档查询或操作结果 content: {application/json: {schema: {$ref: '#/components/schemas/BusinessDocumentEnvelope'}}} schemas: LongId: type: string pattern: '^[1-9][0-9]*$' example: '1900000000000000001' description: 长整型 ID,JSON 中使用字符串避免前端精度丢失 NullableLongId: allOf: [{$ref: '#/components/schemas/LongId'}] nullable: true ApiEnvelope: type: object required: [code, message, timestamp] properties: code: {type: string, description: 业务结果码,仅字符串 200 表示成功, example: '200'} message: {type: string, description: 结果说明} requestId: {type: string, nullable: true, description: 请求标识} traceId: {type: string, nullable: true, description: 链路标识} timestamp: {type: string, format: date-time, description: 服务端响应时间} PreloadPageEnvelope: allOf: - {$ref: '#/components/schemas/ApiEnvelope'} - type: object properties: data: allOf: [{$ref: '#/components/schemas/PreloadPage'}] nullable: true PreloadDetailEnvelope: allOf: - {$ref: '#/components/schemas/ApiEnvelope'} - type: object properties: data: allOf: [{$ref: '#/components/schemas/PreloadDetail'}] nullable: true PreloadConfirmationEnvelope: allOf: - {$ref: '#/components/schemas/ApiEnvelope'} - type: object properties: data: allOf: [{$ref: '#/components/schemas/PreloadConfirmation'}] nullable: true ConfirmedPageEnvelope: allOf: - {$ref: '#/components/schemas/ApiEnvelope'} - type: object properties: data: allOf: [{$ref: '#/components/schemas/ConfirmedPage'}] nullable: true ConfirmedDetailEnvelope: allOf: - {$ref: '#/components/schemas/ApiEnvelope'} - type: object properties: 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] properties: records: {type: array, items: {$ref: '#/components/schemas/PreloadSummary'}} page: {type: string, pattern: '^[1-9][0-9]*$', description: 当前页码} size: {type: string, pattern: '^[1-9][0-9]*$', description: 每页条数} total: {type: string, pattern: '^[0-9]+$', description: 总记录数} pages: {type: string, pattern: '^[0-9]+$', description: 总页数} ConfirmedPage: type: object required: [records, page, size, total, pages] properties: records: {type: array, items: {$ref: '#/components/schemas/ConfirmedSummary'}} page: {type: string, pattern: '^[1-9][0-9]*$', description: 当前页码} size: {type: string, pattern: '^[1-9][0-9]*$', description: 每页条数} total: {type: string, pattern: '^[0-9]+$', description: 总记录数} pages: {type: string, pattern: '^[0-9]+$', description: 总页数} Customer: type: object required: [accountId, customerName] properties: accountId: {$ref: '#/components/schemas/LongId'} customerName: {type: string, nullable: true, description: 客户名称} PreloadSummary: type: object required: [planId, containerBookingId, preloadNo, customers, status, skuCount, totalPackageCount, totalVolumeCbm, totalGrossWeightKg, createdAt, version, readiness] properties: planId: {$ref: '#/components/schemas/LongId'} containerBookingId: {$ref: '#/components/schemas/LongId'} preloadNo: {type: string, pattern: '^LP-[0-9]{6}-[0-9]{4}$', example: LP-260818-0001, description: 预装单号;按北京时间创建日逐日编号,确认后继续作为同一装柜清单号使用} containerNo: {type: string, nullable: true, description: 集装箱号} cabinetNo: {type: string, nullable: true, pattern: '^CAB-[0-9]{6}-[0-9]{4}$', example: CAB-260818-0001, description: 稳定柜次号;不得与真实集装箱号混用} customers: {type: array, description: 计划包含的客户, items: {$ref: '#/components/schemas/Customer'}} warehouseId: {$ref: '#/components/schemas/NullableLongId'} warehouseName: {type: string, nullable: true, description: 仓库名称} vesselName: {type: string, nullable: true, description: 船名} voyageNo: {type: string, nullable: true, description: 航次} containerType: {type: string, nullable: true, description: 货柜类型} status: {type: string, enum: [EDITING, LOADING], description: 装柜计划状态} skuCount: {type: string, pattern: '^[0-9]+$', description: SKU 种类数} totalPackageCount: {type: string, pattern: '^[0-9]+$', description: 总箱数} totalVolumeCbm: {type: number, description: 总体积,单位立方米} totalGrossWeightKg: {type: number, description: 总毛重,单位千克} createdAt: {type: string, format: date-time, description: 创建时间} version: {type: integer, format: int32, minimum: 0, description: 乐观锁版本} readiness: {$ref: '#/components/schemas/PreloadReadiness'} PreloadReadiness: type: object required: [preloadPushed, containerNoReady, preloadLinkCount, confirmedPreloadCount, confirmationStatus, canConfirm, readyForPda, blockingReasons] properties: preloadPushed: {type: boolean, description: 是否已完成 push-preload} containerNoReady: {type: boolean, description: 是否已录入真实集装箱号 containerNo} preloadLinkCount: {type: integer, format: int32, minimum: 0, description: 计划关联的入库预装票据数} confirmedPreloadCount: {type: integer, format: int32, minimum: 0, description: 已确认预装票据数} oceanBookingId: {$ref: '#/components/schemas/NullableLongId'} oceanBookingStatus: {type: string, nullable: true, enum: [CONFIRMED, CHANGE_PENDING, RELEASED, CANCELLED], description: 真实订舱当前状态} pdaTaskId: {$ref: '#/components/schemas/NullableLongId'} pdaTaskStatus: {type: string, nullable: true, description: PDA task 当前状态} confirmationStatus: {type: string, enum: [PENDING, PARTIALLY_CONFIRMED, CONFIRMED], description: 整份计划的预装确认状态} canConfirm: {type: boolean, description: 当前是否可调用计划级确认接口} readyForPda: {type: boolean, description: PDA 创建前置条件是否全部满足;task 已创建后仍为 true} blockingReasons: type: array description: 阻止计划确认的稳定原因码 items: {type: string, enum: [PRELOAD_NOT_PUSHED, LOADING_PLAN_NOT_EDITING, CONTAINER_NO_MISSING, PRELOAD_LINK_MISSING, OCEAN_BOOKING_NOT_LINKED, OCEAN_BOOKING_NOT_RELEASED, PDA_TASK_ALREADY_CREATED, PRELOAD_ALREADY_CONFIRMED]} ConfirmPreloadRequest: type: object required: [expectedVersion] additionalProperties: false properties: expectedVersion: {type: integer, format: int32, minimum: 0, description: 当前装柜计划 version} PreloadConfirmation: type: object required: [planId, containerBookingId, confirmationStatus, version, readiness] properties: planId: {$ref: '#/components/schemas/LongId'} containerBookingId: {$ref: '#/components/schemas/LongId'} confirmationStatus: {type: string, enum: [CONFIRMED]} version: {type: integer, format: int32, minimum: 0, description: 确认后的装柜计划 version} readiness: {$ref: '#/components/schemas/PreloadReadiness'} ConfirmedSummary: type: object required: [planId, containerBookingId, loadingListNo, status, totalPackageCount, totalGrossWeightKg, totalNetWeightKg, totalVolumeCbm, totalQuantity, createdAt, version] properties: planId: {$ref: '#/components/schemas/LongId'} containerBookingId: {$ref: '#/components/schemas/LongId'} loadingListNo: {type: string, pattern: '^LP-[0-9]{6}-[0-9]{4}$', example: LP-260818-0001, description: 装柜清单号;沿用预装阶段生成的同一编号} warehouseId: {$ref: '#/components/schemas/NullableLongId'} warehouseName: {type: string, nullable: true, description: 仓库名称} containerNo: {type: string, nullable: true, description: 集装箱号} cabinetNo: {type: string, nullable: true, pattern: '^CAB-[0-9]{6}-[0-9]{4}$', example: CAB-260818-0001, description: 稳定柜次号} status: {type: string, enum: [COMPLETED], description: 装柜清单状态} totalPackageCount: {type: string, pattern: '^[0-9]+$', description: 总箱数} totalGrossWeightKg: {type: number, description: 总毛重,单位千克} totalNetWeightKg: {type: number, description: 总净重,单位千克} totalVolumeCbm: {type: number, description: 总体积,单位立方米} totalQuantity: {type: string, pattern: '^[0-9]+$', description: 产品总数量} remark: {type: string, nullable: true, description: 备注} createdBy: {$ref: '#/components/schemas/NullableLongId'} createdByName: {type: string, nullable: true, description: 创建人姓名} createdAt: {type: string, format: date-time, description: 创建时间} version: {type: integer, format: int32, minimum: 0, description: 乐观锁版本} LoadingItem: type: object required: [itemId, accountId, orderId, orderNo, inboundId, inboundDetailId, packageCount, palletCount, grossWeightKg, netWeightKg, volumeCbm, dimensionalWeightKg, productQuantity, unitsPerPackage, loadSequence, itemVersion] properties: itemId: {$ref: '#/components/schemas/LongId'} accountId: {$ref: '#/components/schemas/LongId'} customerName: {type: string, nullable: true, description: 客户名称快照} orderId: {$ref: '#/components/schemas/LongId'} orderNo: {type: string, description: 订单号快照} inboundId: {$ref: '#/components/schemas/LongId'} inboundDetailId: {$ref: '#/components/schemas/LongId'} inboundCode: {type: string, nullable: true, description: 入仓码} containerNo: {type: string, nullable: true, description: 当前 ACTIVE 柜次的真实集装箱号} skuCode: {type: string, nullable: true, description: SKU 编码快照} destinationWarehouse: {type: string, nullable: true, description: 目的仓名称快照} chineseName: {type: string, nullable: true, description: 中文品名快照} englishName: {type: string, nullable: true, description: 英文品名快照} goodsImageUrl: {type: string, nullable: true, description: 产品图片地址快照} packageCount: {type: integer, format: int32, minimum: 0, description: 箱数} palletCount: {type: integer, format: int32, minimum: 0, description: 托盘数} grossWeightKg: {type: number, description: 毛重,单位千克} netWeightKg: {type: number, description: 净重,单位千克} volumeCbm: {type: number, description: 体积,单位立方米} dimensionalWeightKg: {type: number, description: 材积重,单位千克} productQuantity: {type: string, pattern: '^[0-9]+$', description: 产品数量} unitsPerPackage: {type: integer, format: int32, minimum: 0, description: 每箱数量} fbaTrackingNo: {type: string, nullable: true, description: FBA 跟踪编号} shipmentNo: {type: string, nullable: true, description: 货件号} shippingMark: {type: string, nullable: true, description: 唛头} internalRemark: {type: string, nullable: true, description: 内部备注} customerRemark: {type: string, nullable: true, description: 客户备注} warehouseRemark: {type: string, nullable: true, description: 仓库备注} loadSequence: {type: integer, format: int32, minimum: 0, description: 装柜顺序} frozenAt: {type: string, format: date-time, nullable: true, description: 明细冻结时间} itemVersion: {type: integer, format: int32, minimum: 0, description: 明细乐观锁版本;更新仓库备注时作为 expectedItemVersion} OperationHistory: type: object required: [historyId, actionType, operatorId, occurredAt] properties: historyId: {$ref: '#/components/schemas/LongId'} itemId: {$ref: '#/components/schemas/NullableLongId'} actionType: {type: string, description: 操作类型} fromStatus: {type: string, nullable: true, description: 操作前状态} toStatus: {type: string, nullable: true, description: 操作后状态} beforeSnapshot: {type: string, nullable: true, description: 操作前快照} afterSnapshot: {type: string, nullable: true, description: 操作后快照} operatorId: {$ref: '#/components/schemas/LongId'} operatorName: {type: string, nullable: true, description: 操作人姓名} operationReason: {type: string, nullable: true, description: 操作原因} occurredAt: {type: string, format: date-time, description: 操作时间} PreloadDetail: type: object required: [summary, items, histories] properties: summary: {$ref: '#/components/schemas/PreloadSummary'} items: {type: array, description: 装柜中明细,可维护未拣货项, items: {$ref: '#/components/schemas/LoadingItem'}} histories: {type: array, description: 计划操作历史, items: {$ref: '#/components/schemas/OperationHistory'}} ConfirmedDetail: type: object required: [summary, items, histories] properties: summary: {$ref: '#/components/schemas/ConfirmedSummary'} items: {type: array, description: 已完成且永久只读的装柜明细, items: {$ref: '#/components/schemas/LoadingItem'}} histories: {type: array, description: 计划操作历史, items: {$ref: '#/components/schemas/OperationHistory'}} TransportUpdateRequest: type: object required: [expectedVersion] additionalProperties: false properties: containerNo: {type: string, maxLength: 64, nullable: true, description: 真实集装箱号;PDA任务创建前可空,任务创建后允许替换但禁止清空} 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}