openapi: 3.0.3 info: title: 柏悦头程履约 API description: 业务成功和失败均使用 HTTP 200;仅响应体 code 为字符串 "200" 时表示成功,其他 code 表示失败。 version: 1.1.0 security: - bearerAuth: [] paths: /api/platform/v1/first-leg/tasks: get: summary: 分页查询履约任务 description: 需要 platform:firstleg:task:list 权限 parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/Size' - {name: keyword, in: query, schema: {type: string, maxLength: 64}} - {name: status, in: query, schema: {type: string, enum: [PENDING_ASSIGNMENT, ASSIGNED, PROCESSING, COMPLETED, CANCELLED]}} - {name: assigneeId, in: query, schema: {type: integer, format: int64}} responses: '200': {description: 成功或业务失败,按响应体 code 判断} '403': {description: 无查询权限} /api/platform/v1/first-leg/tasks/{taskId}: get: summary: 查询履约任务详情 description: 需要 platform:firstleg:task:view 权限 parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/TaskId' responses: '200': {description: 成功或业务失败,按响应体 code 判断} '403': {description: 无查看权限} /api/platform/v1/first-leg/tasks/by-order/{orderId}: get: summary: 按订单查询履约任务 description: 需要 platform:firstleg:task:view 权限 parameters: - $ref: '#/components/parameters/TenantId' - {name: orderId, in: path, required: true, schema: {type: integer, format: int64}} responses: '200': {description: 成功或业务失败,按响应体 code 判断} '403': {description: 无查看权限} /api/platform/v1/first-leg/tasks/{taskId}/assignment-history: get: summary: 分页查询任务分配历史 description: 需要 platform:firstleg:assignment:history 权限 parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/TaskId' - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/Size' responses: '200': {description: 成功或业务失败,按响应体 code 判断} '403': {description: 无历史查看权限} /api/platform/v1/first-leg/tasks/{taskId}/assignment: post: summary: 分配或改派履约任务 description: 需要 platform:firstleg:assignment:assign 权限 parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/TaskId' requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/AssignRequest'} responses: '200': {description: 成功或业务失败,按响应体 code 判断} '403': {description: 无分配权限} /openapi/v1/inbound-measurements: post: summary: 提交一条第三方箱规测量结果和一张图片 description: >- data 中的 inboundCode 匹配入库单,inboundDetailId 必须匹配该入库单关联订单中的已有货物明细; 每次只保存一条箱规明细和一张图片,不审核、不计费、不入库,也不改变入库单状态。 签名串依次为 METHOD、原始 PATH、X-Timestamp、X-Nonce、Idempotency-Key、 签名body的 SHA-256 小写十六进制,六部分使用 LF(\n)连接且末尾不追加换行。 签名body = data原始UTF-8文本 + LF + image文件内容SHA-256小写十六进制。 三层语义:Nonce 防短时重放;Idempotency-Key 标识一次 HTTP 写操作; appKey + eventNo 标识第三方业务事件。 security: - measurementAppKey: [] measurementTimestamp: [] measurementNonce: [] measurementSignature: [] parameters: - {$ref: '#/components/parameters/XTimestamp'} - {$ref: '#/components/parameters/XNonce'} - {$ref: '#/components/parameters/IdempotencyKey'} requestBody: required: true content: multipart/form-data: schema: type: object required: [data, image] properties: data: type: string description: InboundMeasurementCallbackRequest 的原始UTF-8 JSON文本 image: type: string format: binary description: 当前测量明细唯一对应图片,JPG/PNG/BMP且不超过5MB responses: '200': {description: 成功或业务失败,按响应体字符串 code 判断;非法签名返回 A0401} '413': {description: 请求体超过网关限制} '415': {description: Content-Type 不支持} /openapi/v1/inbound-measurements/events/{eventNo}: get: summary: 查询第三方测量事件状态 description: GET 签名串的 Idempotency-Key 行为空字符串,body 摘要为空字节 SHA-256。 security: - measurementAppKey: [] measurementTimestamp: [] measurementNonce: [] measurementSignature: [] parameters: - name: eventNo in: path required: true schema: {type: string, pattern: '^[A-Za-z0-9._:-]{1,128}$'} - {$ref: '#/components/parameters/XTimestamp'} - {$ref: '#/components/parameters/XNonce'} responses: '200': {description: 成功或业务失败,按响应体字符串 code 判断} /api/platform/inbounds: get: summary: 分页查询入库单 description: 需要 platform:firstleg:inbound:list 权限;默认查询全部企业,可选 tenantId 筛选;仅使用 V2 四个页签 parameters: - {name: tenantId, in: query, required: false, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - {name: tab, in: query, schema: {type: string, enum: [WAIT_MEASURE, WAIT_REVIEW, WAIT_FEE_SEND, INBOUNDED]}} responses: '200': description: 成功或业务失败,按响应体 code 判断;每条记录携带后续租户隔离操作所需的 tenantId content: application/json: schema: {$ref: '#/components/schemas/InboundOrderPageEnvelope'} '403': {description: 无查询权限} /api/platform/inbounds/{inboundId}: get: summary: 查询入库单详情 description: 需要 platform:firstleg:inbound:view 权限 parameters: - {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} responses: '200': {description: 成功或业务失败,按响应体 code 判断} '403': {description: 无查看权限} /api/platform/v1/inbounds/{inboundId}/measurements/approve: post: summary: 审核通过测量 description: 需要 firstleg:inbound:measurement-review 权限;客服审核通过后自动计费、发布费用给客户并完成入库,客户无需确认。 parameters: - {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - {name: Idempotency-Key, in: header, required: true, schema: {type: string}} responses: '200': {description: 成功或业务失败} /api/platform/v1/inbounds/{inboundId}/measurements/reject: post: summary: 驳回测量 description: 需要 firstleg:inbound:measurement-review 权限;只退回测量数据修改,已确认托盘和库位占用保持不动。 parameters: - {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - {name: Idempotency-Key, in: header, required: true, schema: {type: string}} responses: '200': {description: 成功或业务失败} /api/platform/v1/inbounds/{inboundId}/measurements/submit: post: summary: 提交测量完成 description: 测量提交后立即按总体积拆分普通托盘、分配并确认占用库位,再进入客服待审核;无普通空闲位时共享 A0。 parameters: - {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} responses: '200': {description: 成功时 processStatus=MEASURE_PENDING、measureReviewStatus=PENDING,且库位已 OCCUPIED} '403': {description: 无测量提交权限} /api/merchant/v1/first-leg/orders/{orderId}/inbound-billing: get: summary: 客户查询已完成入库结果和最新已发布费用 description: 需要 merchant:order:view 权限;客户只读查看,不需要确认费用,未完成客服审核发布时不可见。 parameters: - {name: orderId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} responses: '200': {description: 成功或业务失败,按响应体 code 判断} '403': {description: 非本租户或无订单查看权限} /api/platform/v1/inbounds/{inboundId}/measurements/exception-release: post: summary: 测量异常放行 description: 需要 firstleg:inbound:exception-release 权限 parameters: - {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - {name: Idempotency-Key, in: header, required: true, schema: {type: string}} responses: '200': {description: 成功或业务失败} components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT measurementAppKey: type: apiKey in: header name: X-App-Key description: 分配给测量服务商的应用标识 measurementTimestamp: type: apiKey in: header name: X-Timestamp description: Unix 毫秒时间戳 measurementNonce: type: apiKey in: header name: X-Nonce description: 每次请求唯一的随机串 measurementSignature: type: apiKey in: header name: X-Signature description: HMAC-SHA256 小写十六进制签名 parameters: XTimestamp: name: X-Timestamp in: header required: true schema: {type: string, pattern: '^[0-9]{13}$', example: '1785551400000'} XNonce: name: X-Nonce in: header required: true schema: {type: string, minLength: 1, maxLength: 128} IdempotencyKey: name: Idempotency-Key in: header required: true schema: {type: string, minLength: 1, maxLength: 128} TenantId: name: tenantId in: query required: true schema: {type: integer, format: int64, minimum: 1} TaskId: name: taskId in: path required: true schema: {type: integer, format: int64} Page: name: page in: query schema: {type: integer, format: int64, minimum: 1, default: 1} Size: name: size in: query schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20} schemas: InboundOrderPageEnvelope: type: object properties: code: {type: string, example: '200'} message: {type: string} data: {$ref: '#/components/schemas/InboundOrderPage'} InboundOrderPage: type: object properties: records: type: array items: {$ref: '#/components/schemas/InboundOrderSummary'} page: {type: integer, minimum: 1} size: {type: integer, minimum: 1} total: {type: integer, format: int64, minimum: 0} pages: {type: integer, format: int64, minimum: 0} InboundOrderSummary: type: object required: [id, tenantId] properties: id: {type: string, pattern: '^[1-9][0-9]*$'} tenantId: {type: string, pattern: '^[1-9][0-9]*$', description: 记录所属企业租户 ID,用于详情和处理接口的租户隔离} orderId: {type: string, nullable: true} orderNo: {type: string, nullable: true} inboundCode: {type: string, nullable: true} processStatus: {type: string} customerId: {type: string, nullable: true} companyId: {type: string, nullable: true} InboundMeasurementCallbackRequest: type: object required: [eventNo, externalDetailNo, warehouseCode, inboundCode, inboundDetailId, deviceCode, measuredAt, boxSpecification, packageCount, lengthCm, widthCm, heightCm] properties: eventNo: {type: string, maxLength: 128, pattern: '^[A-Za-z0-9._:-]+$', example: M202608010001} externalDetailNo: {type: string, maxLength: 128, pattern: '^[A-Za-z0-9._:-]+$', example: BOX-20260801-0001} warehouseCode: {type: string, maxLength: 50, example: CN-YW-01} inboundCode: {type: string, maxLength: 40, example: RK202608010001} inboundDetailId: {type: string, example: '190000000000000001', description: 柏悦订单货物明细ID} deviceCode: {type: string, maxLength: 64, example: DEVICE-01} measuredAt: {type: string, format: date-time, example: '2026-08-01T10:30:00+08:00'} boxSpecification: {type: string, maxLength: 64, example: '40x30x47cm'} lengthCm: {type: number, exclusiveMinimum: true, minimum: 0, example: 50.20} widthCm: {type: number, exclusiveMinimum: true, minimum: 0, example: 40.10} heightCm: {type: number, exclusiveMinimum: true, minimum: 0, example: 30.00} packageCount: {type: integer, minimum: 1, maximum: 1000000, example: 50} unitWeightKg: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true, example: 20} remark: {type: string, maxLength: 500, nullable: true} InboundMeasurementEventResponse: type: object properties: eventNo: {type: string} inboundCode: {type: string} inboundDetailId: {type: string} measurementId: {type: string} measurementItemId: {type: string} imageFileId: {type: string} accepted: {type: boolean} status: {type: string, enum: [RECEIVED]} receivedAt: {type: string, format: date-time} AssignRequest: type: object required: [assigneeId, assigneeName] properties: assigneeId: {type: integer, format: int64} assigneeName: {type: string, maxLength: 64} reason: {type: string, maxLength: 500}