Files
baiyue-firstleg/firstleg/src/main/resources/openapi/firstleg-v1.yaml
T
2026-08-06 15:48:21 +08:00

564 lines
27 KiB
YAML

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: 无分配权限}
/api/platform/v1/containers:
get:
summary: 分页查询平台货柜
description: 需要 platform:firstleg:container:list 权限;货柜为平台级数据,不传 tenant/account
parameters:
- {$ref: '#/components/parameters/Page'}
- {$ref: '#/components/parameters/Size'}
- {name: keyword, in: query, schema: {type: string, maxLength: 128}}
- {name: containerNo, in: query, schema: {type: string, maxLength: 64}}
- {name: cabinetNo, in: query, schema: {type: string, maxLength: 64}}
- {name: warehouseId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: status, in: query, schema: {type: string, enum: [EDITING, LOADING, COMPLETED]}}
responses:
'200':
description: 成功或业务失败,按响应体 code 判断
content:
application/json:
schema: {$ref: '#/components/schemas/ContainerBookingPageEnvelope'}
'403': {description: 无货柜查询权限}
post:
summary: 新增平台货柜
description: 需要 platform:firstleg:container:create 权限;新增后聚合状态为 EDITING
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ContainerSaveRequest'}
responses:
'200': {description: 成功或重复号、参数校验等业务失败}
'403': {description: 无新增按钮权限}
/api/platform/v1/containers/{id}:
get:
summary: 查询平台货柜详情
description: 需要 platform:firstleg:container:view 权限
parameters:
- {$ref: '#/components/parameters/ContainerBookingId'}
responses:
'200': {description: 成功或货柜不存在}
'403': {description: 无货柜查询权限}
put:
summary: 草稿阶段完整编辑货柜
description: 需要 platform:firstleg:container:edit 权限;仅无装柜计划或计划为 EDITING 时允许
parameters:
- {$ref: '#/components/parameters/ContainerBookingId'}
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ContainerFullUpdateRequest'}
responses:
'200': {description: 成功或状态、版本、重复号冲突}
'403': {description: 无货柜编辑权限}
/api/platform/v1/containers/{id}/transport:
patch:
summary: 修改集装箱号和航次
description: 需要 platform:firstleg:container:edit 权限;LOADING 仅开放此白名单,COMPLETED 全部只读
parameters:
- {$ref: '#/components/parameters/ContainerBookingId'}
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ContainerTransportUpdateRequest'}
responses:
'200': {description: 成功或状态、版本、重复号冲突}
'403': {description: 无货柜编辑权限}
/api/platform/v1/first-leg/smart-loading/plans/by-booking/{bookingId}:
get:
summary: 按货柜恢复智能装柜草稿
description: 需要 platform:firstleg:smart-loading:list 权限;仅返回 EDITING 草稿
parameters:
- {name: bookingId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
responses:
'200': {description: 成功、草稿不存在或计划已进入预装流程}
'403': {description: 无智能装柜查询权限}
/api/platform/v1/first-leg/smart-loading/plans/{planId}:
get:
summary: 查询智能装柜草稿详情
description: 需要 platform:firstleg:smart-loading:list 权限;返回草稿及当前未删除明细
parameters:
- {name: planId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
responses:
'200': {description: 成功、计划不存在或计划已进入预装流程}
'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,该字段当前表示客户 ID
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: 成功;publishedFeeDetail 是后台和客户共同读取的同一条已发布入库运费明细,latestFee 仅为兼容保留的计费快照
content:
application/json:
schema: {$ref: '#/components/schemas/MerchantInboundBillingEnvelope'}
'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}
ContainerBookingId:
name: id
in: path
required: true
schema: {type: string, pattern: '^[1-9][0-9]*$'}
schemas:
ContainerBookingPageEnvelope:
type: object
properties:
code: {type: string, example: '200'}
message: {type: string}
data:
type: object
properties:
records: {type: array, items: {$ref: '#/components/schemas/ContainerBooking'}}
page: {type: string, pattern: '^[1-9][0-9]*$'}
size: {type: string, pattern: '^[1-9][0-9]*$'}
total: {type: string, pattern: '^[0-9]+$'}
pages: {type: string, pattern: '^[0-9]+$'}
ContainerBooking:
type: object
required: [id, bookingNo, cabinetNo, warehouseId, warehouseName, containerType, planStatus, version]
properties:
id: {type: string, pattern: '^[1-9][0-9]*$'}
planId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 关联智能装柜计划ID;未创建计划时为空}
bookingNo: {type: string, maxLength: 40}
containerNo: {type: string, maxLength: 64, nullable: true}
cabinetNo: {type: string, maxLength: 64}
warehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
warehouseName: {type: string, maxLength: 128}
billOfLadingNo: {type: string, maxLength: 128, nullable: true}
vesselName: {type: string, maxLength: 128, nullable: true}
voyageNo: {type: string, maxLength: 64, nullable: true}
containerType: {type: string, maxLength: 32}
shippingCompanyId: {type: string, nullable: true}
shippingCompanyName: {type: string, maxLength: 128, nullable: true}
departurePortCode: {type: string, maxLength: 32, nullable: true}
departurePortName: {type: string, maxLength: 128, nullable: true}
destinationPortCode: {type: string, maxLength: 32, nullable: true}
destinationPortName: {type: string, maxLength: 128, nullable: true}
destinationWarehouseCode: {type: string, maxLength: 64, nullable: true}
destinationWarehouseName: {type: string, maxLength: 128, nullable: true}
capacityCbm: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
maxWeightKg: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
terminalTruckingCompany: {type: string, maxLength: 128, nullable: true}
loadingAt: {type: string, format: date-time, nullable: true}
estimatedSailingAt: {type: string, format: date-time, nullable: true}
actualSailingAt: {type: string, format: date-time, nullable: true}
estimatedPortArrivalAt: {type: string, format: date-time, nullable: true}
actualPortArrivalAt: {type: string, format: date-time, nullable: true}
estimatedWarehouseArrivalAt: {type: string, format: date-time, nullable: true}
actualWarehouseArrivalAt: {type: string, format: date-time, nullable: true}
planStatus: {type: string, enum: [EDITING, LOADING, COMPLETED]}
version: {type: integer, minimum: 0}
ContainerSaveRequest:
type: object
required: [cabinetNo, warehouseId, warehouseName, containerType]
properties:
containerNo: {type: string, maxLength: 64, nullable: true}
cabinetNo: {type: string, maxLength: 64}
warehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
warehouseName: {type: string, maxLength: 128}
billOfLadingNo: {type: string, maxLength: 128, nullable: true}
vesselName: {type: string, maxLength: 128, nullable: true}
voyageNo: {type: string, maxLength: 64, nullable: true}
containerType: {type: string, maxLength: 32}
shippingCompanyId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
shippingCompanyName: {type: string, maxLength: 128, nullable: true}
departurePortCode: {type: string, maxLength: 32, nullable: true}
departurePortName: {type: string, maxLength: 128, nullable: true}
destinationPortCode: {type: string, maxLength: 32, nullable: true}
destinationPortName: {type: string, maxLength: 128, nullable: true}
destinationWarehouseCode: {type: string, maxLength: 64, nullable: true}
destinationWarehouseName: {type: string, maxLength: 128, nullable: true}
capacityCbm: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
maxWeightKg: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
terminalTruckingCompany: {type: string, maxLength: 128, nullable: true}
loadingAt: {type: string, format: date-time, nullable: true}
estimatedSailingAt: {type: string, format: date-time, nullable: true}
actualSailingAt: {type: string, format: date-time, nullable: true}
estimatedPortArrivalAt: {type: string, format: date-time, nullable: true}
actualPortArrivalAt: {type: string, format: date-time, nullable: true}
estimatedWarehouseArrivalAt: {type: string, format: date-time, nullable: true}
actualWarehouseArrivalAt: {type: string, format: date-time, nullable: true}
ContainerFullUpdateRequest:
type: object
required: [version, data]
properties:
version: {type: integer, minimum: 0}
data: {$ref: '#/components/schemas/ContainerSaveRequest'}
ContainerTransportUpdateRequest:
type: object
required: [version]
properties:
containerNo: {type: string, maxLength: 64, nullable: true}
voyageNo: {type: string, maxLength: 64, nullable: true}
version: {type: integer, minimum: 0}
MerchantInboundBillingEnvelope:
type: object
properties:
code: {type: string, example: '200'}
message: {type: string}
data:
type: object
properties:
inbound: {type: object, description: 入库单详情}
measurementSummary: {type: object, description: 测量汇总}
measurementItems: {type: array, items: {type: object}}
weightResults: {type: array, items: {type: object}}
measurementImages: {type: array, items: {type: object}}
weightImages: {type: array, items: {type: object}}
billingBasis: {type: object, description: 计费依据}
latestFee: {type: object, nullable: true, description: 兼容保留的最新计费快照}
publishedFeeDetail:
allOf:
- {$ref: '#/components/schemas/PublishedFreightFeeDetail'}
nullable: true
description: 后台与客户共用的已发布FBA头程运费应收明细
PublishedFreightFeeDetail:
type: object
description: 审核发布后绑定入库单的FBA头程运费应收明细
properties:
id: {type: integer, format: int64, description: 入库费用明细ID}
inboundId: {type: integer, format: int64}
inboundCode: {type: string}
sourceFeeId: {type: integer, format: int64, description: 来源计费快照ID}
sourceFeeVersion: {type: integer}
sourceFeeNo: {type: string}
direction: {type: string, enum: [RECEIVABLE]}
businessDate: {type: string, format: date}
customerId: {type: integer, format: int64}
customerName: {type: string}
invoiceTitle: {type: string, nullable: true}
channelCode: {type: string, nullable: true}
channelName: {type: string, nullable: true}
destinationCode: {type: string, nullable: true}
destinationName: {type: string, nullable: true}
packageCount: {type: integer, nullable: true}
actualGrossWeightKg: {type: number, format: double, nullable: true}
actualVolumeCbm: {type: number, format: double, nullable: true}
volumeWeightKg: {type: number, format: double, nullable: true}
chargeableWeightKg: {type: number, format: double, nullable: true}
chargeableVolumeCbm: {type: number, format: double, nullable: true}
chargeableQuantity: {type: number, format: double}
chargeUnit: {type: string, enum: [KG, CBM]}
feeTypeName: {type: string, example: FBA头程运费}
unitPrice: {type: number, format: double}
currency: {type: string, example: CNY}
amount: {type: number, format: double, description: 客户应收金额}
reviewStatus: {type: string, enum: [APPROVED]}
publicationStatus: {type: string, enum: [PUBLISHED]}
publishedAt: {type: string, format: date-time}
inboundedAt: {type: string, format: date-time, nullable: true}
settlementIncluded: {type: boolean, example: true}
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}