openapi: 3.0.3 info: title: 派送单三类资料 version: 1.0.0 description: | 所有入口只允许平台,customerId 必须为真实派送所属客户;不得按 BOL 文本查找或复用其他客户/派送单文件。 GET /delivery-orders 及 GET /delivery-orders/{deliveryNo} 的 order 行新增 bolNo 和 documentSummary,version/allowedActions 保持原契约。 状态 PUT /delivery-orders/{deliveryNo}/status 请求保持 {status,expectedVersion,remark},expectedVersion 必传。 START_DELIVERY=DELIVERING(派送中);COMPLETE_DELIVERY=DELIVERED(完成并记录签收时间、生成待回访)。同状态重放不重复副作用。 资料不设必传门禁,不以三类齐全作为派送完成条件。业务失败使用统一 ApiResponse(A0422参数、A0409冲突),通常HTTP200。 security: [{ bearerAuth: [] }] paths: /api/platform/v1/last-leg/delivery-orders/{deliveryNo}/documents: parameters: - { $ref: '#/components/parameters/DeliveryNo' } - { $ref: '#/components/parameters/CustomerId' } get: summary: 查询已保存三类资料 description: 权限 platform:overseas-warehouse:delivery:list;空资料仍返回全部三类MISSING。上传未保存不计入摘要。 responses: '200': { description: 当前快照, content: { application/json: { schema: { $ref: '#/components/schemas/DetailResponse' } } } } put: summary: 原子保存全部类别绑定与BOL号 description: | 权限 platform:overseas-warehouse:delivery:documents。documents为全量快照,遗漏类别清空;Admin须提交完整三类。 同一派送单锁下校验expectedVersion、全部文件客户/派送单/类别归属后一起提交,任一失败不写任何绑定。 保存增加派送version,与状态修改共享版本;幂等键按客户+派送单限定,同键同请求重放原结果、异请求冲突。 替换/删除通过修改fileIds并保存表达,只解除当前绑定,历史对象和版本快照不可变保留。 requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/Save' } } } responses: '200': { description: 保存结果或统一业务错误, content: { application/json: { schema: { $ref: '#/components/schemas/DetailResponse' } } } } /api/platform/v1/last-leg/delivery-orders/{deliveryNo}/documents/{category}/files: parameters: - { $ref: '#/components/parameters/DeliveryNo' } - { $ref: '#/components/parameters/CustomerId' } - { $ref: '#/components/parameters/Category' } post: summary: 上传一个待绑定文件 description: | 权限 platform:overseas-warehouse:delivery:documents。FormData唯一file,无额外幂等头。 File沿用ARBITRARY任意格式、非空且不超过52428800字节(50MiB),每类别最多20个文件由保存校验。 上传仅产生不可变对象,不修改派送version或摘要;取消弹框/未保存不落绑定,不物理删除File对象。 requestBody: required: true content: multipart/form-data: schema: { type: object, required: [file], properties: { file: { type: string, format: binary } } } responses: '200': description: 单文件权威元数据 content: application/json: schema: allOf: - { $ref: '#/components/schemas/Envelope' } - type: object properties: { data: { $ref: '#/components/schemas/File' } } /api/platform/v1/last-leg/delivery-orders/{deliveryNo}/documents/history: parameters: - { $ref: '#/components/parameters/DeliveryNo' } - { $ref: '#/components/parameters/CustomerId' } get: summary: 查看不可变资料保存历史 description: 权限 platform:overseas-warehouse:delivery:list;版本倒序,仅当前客户和派送单。 responses: '200': description: 历史快照 content: application/json: schema: allOf: - { $ref: '#/components/schemas/Envelope' } - type: object properties: data: type: array items: type: object properties: version: { type: integer, format: int64 } operatorId: { type: string } savedAt: { type: string, format: date-time } snapshot: { $ref: '#/components/schemas/Detail' } /api/platform/v1/last-leg/delivery-orders/{deliveryNo}/documents/{category}/files/{fileId}/download: parameters: - { $ref: '#/components/parameters/DeliveryNo' } - { $ref: '#/components/parameters/CustomerId' } - { $ref: '#/components/parameters/Category' } - { in: path, name: fileId, required: true, schema: { type: string, pattern: '^[1-9][0-9]*$' } } - { in: query, name: revisionVersion, required: false, schema: { type: integer, format: int64, minimum: 1 } } get: summary: 下载当前或指定保存版本已绑定文件 description: 权限 platform:overseas-warehouse:delivery:list;省略revisionVersion只允许当前绑定。历史版本仍严格校验客户/单据/类别。未保存或跨单文件拒绝;附件响应no-store/nosniff。 responses: '200': { description: 文件内容, content: { application/octet-stream: { schema: { type: string, format: binary } } } } components: securitySchemes: bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } parameters: DeliveryNo: { in: path, name: deliveryNo, required: true, schema: { type: string } } CustomerId: { in: query, name: customerId, required: true, schema: { type: string, pattern: '^[1-9][0-9]*$' } } Category: { in: path, name: category, required: true, schema: { $ref: '#/components/schemas/CategoryCode' } } schemas: CategoryCode: type: string enum: [LOADING_PHOTO, BOL, RECEIPT] description: 依次为装车照片、BOL文件(含签名)、签收文件(POD签收/快递签收)。BOL签名由用户提供,不进行OCR或电子签署验证。 Save: type: object required: [expectedVersion, idempotencyKey, documents] properties: expectedVersion: { type: integer, format: int64, minimum: 0 } idempotencyKey: { type: string, minLength: 1, maxLength: 128 } bolNo: { type: string, nullable: true, maxLength: 128, description: 不作为文件复用或归属匹配依据;空值清空 } documents: type: array maxItems: 3 items: type: object required: [category, fileIds] properties: category: { $ref: '#/components/schemas/CategoryCode' } fileIds: { type: array, maxItems: 20, uniqueItems: true, items: { type: string, pattern: '^[1-9][0-9]*$' } } File: type: object required: [fileId, fileName, sizeBytes, uploadedAt] properties: fileId: { type: string, pattern: '^[1-9][0-9]*$' } fileName: { type: string } contentType: { type: string, nullable: true } sizeBytes: { type: integer, format: int64, minimum: 1, maximum: 52428800 } uploadedAt: { type: string, format: date-time, description: File持久化的真实创建时间 } CategorySummary: type: object required: [category, status, fileCount] properties: category: { $ref: '#/components/schemas/CategoryCode' } status: { type: string, enum: [UPLOADED, MISSING] } fileCount: { type: integer, minimum: 0, maximum: 20 } DocumentSummary: type: object required: [uploadedCategoryCount, totalCategoryCount, categories] properties: uploadedCategoryCount: { type: integer, minimum: 0, maximum: 3 } totalCategoryCount: { type: integer, enum: [3] } categories: { type: array, minItems: 3, maxItems: 3, items: { $ref: '#/components/schemas/CategorySummary' } } Detail: type: object required: [deliveryNo, customerId, version, uploadedCategoryCount, totalCategoryCount, categories, allowedActions, replayed] properties: deliveryNo: { type: string } customerId: { type: string } version: { type: integer, format: int64 } bolNo: { type: string, nullable: true } uploadedCategoryCount: { type: integer, minimum: 0, maximum: 3 } totalCategoryCount: { type: integer, enum: [3] } categories: type: array minItems: 3 maxItems: 3 items: allOf: - { $ref: '#/components/schemas/CategorySummary' } - type: object required: [files] properties: { files: { type: array, maxItems: 20, items: { $ref: '#/components/schemas/File' } } } allowedActions: { type: array, items: { type: string, enum: [MANAGE_DOCUMENTS] }, description: 返回时按当前认证授权裁剪;只读用户为空,历史和幂等重放也不能沿用旧权限 } replayed: { type: boolean } Envelope: type: object properties: code: { type: string } message: { type: string } DetailResponse: allOf: - { $ref: '#/components/schemas/Envelope' } - type: object properties: { data: { $ref: '#/components/schemas/Detail' } }