feat(fleet): bind quote versions and reconcile actual return quantities
This commit is contained in:
@@ -6,6 +6,7 @@ info:
|
||||
平台资料入口的customerId必须为真实派送所属客户;客户只读入口范围只取Token,只公开当前派送单已保存的RECEIPT签收文件,不提供上传、修改、历史版本、BOL或装车照片下载。
|
||||
GET /delivery-orders 及 GET /delivery-orders/{deliveryNo} 的 order 行新增 bolNo 和 documentSummary,version/allowedActions 保持原契约。
|
||||
RECEIPT 签收文件只有在关联出库单已OUTBOUNDED、PUT保存进入权威资料快照后才自动完成当前派送单;仅上传未保存不会完成。发车前可保存BOL及装车资料,不能通过提前保存签收文件绕过实际出库。allowedActions 不再返回人工 COMPLETE_DELIVERY。
|
||||
首次签收冻结signedPackages为原实发箱数减去此前实际退回入账箱数;退回异常仍在处理时不得签收余货。后续资料修改或退货不改写原signedAt和signedPackages。部分退回后的余货签收保留国内回写待处理操作记录,不发送会把整票入仓单标记已签收的OVERSEAS_DELIVERY_COMPLETED事件;该跨服务分支尚未闭环。
|
||||
START_DELIVERY=DELIVERING(派送中);COMPLETE_DELIVERY=DELIVERED(完成并记录签收时间、生成待回访)。同状态重放不重复副作用。
|
||||
资料不设必传门禁,不以三类齐全作为派送完成条件。业务失败使用统一 ApiResponse(A0422参数、A0409冲突),通常HTTP200。
|
||||
security: [{ bearerAuth: [] }]
|
||||
|
||||
@@ -37,6 +37,7 @@ paths:
|
||||
deliveryNo: { type: string }
|
||||
status: { type: string }
|
||||
packages: { type: integer }
|
||||
remainingPackages: { type: integer, minimum: 0, description: 原运输尚未签收且尚未实际退回的箱数 }
|
||||
terminalAt: { type: string, format: date-time, nullable: true }
|
||||
transport: { $ref: '#/components/schemas/Transport' }
|
||||
latestHandover:
|
||||
@@ -222,6 +223,16 @@ components:
|
||||
destinationKey: { type: string }
|
||||
packages: { type: integer }
|
||||
terminalAt: { type: string, format: date-time, nullable: true }
|
||||
returnedCargo:
|
||||
type: array
|
||||
description: 由实际退回入账流水映射到原发运明细;部分退回按此扣减箱数,未清空实托保留原测量载荷。
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
itemId: { type: string }
|
||||
palletId: { type: string }
|
||||
packages: { type: integer, minimum: 1 }
|
||||
receivedAt: { type: string, format: date-time }
|
||||
Inspection:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: 外接销售报价候选与来源快照
|
||||
version: 1.0.0
|
||||
description: >-
|
||||
候选只读取有效销售报价,不创建承运、库存或费用。客户、起运城市、目的仓码、币种、计价单位和日期明确匹配;
|
||||
空价排除,零价保留,重叠有效期不自动选价。PALLET展示LOOSE_DELIVERY,ORDER展示FULL_DELIVERY价项;
|
||||
返回整车派送价项不表示已作为拼载成交价采用,必须明确选择报价号、版本及价项。
|
||||
日期由调用方根据真实车次计划起运时间和明确业务时区产生,不猜测机器或浏览器时区。
|
||||
security:
|
||||
- bearerAuth: []
|
||||
paths:
|
||||
/api/platform/v1/last-leg/fleet/quotes/sale-candidates:
|
||||
get:
|
||||
description: platform:fleet:quote:list;同路线原始候选超过5000条明确拒绝,不能截断后声称无报价。
|
||||
parameters:
|
||||
- { name: customerId, in: query, required: true, schema: { type: integer, format: int64, minimum: 1 } }
|
||||
- { name: originCity, in: query, required: true, schema: { type: string, minLength: 1, maxLength: 128 } }
|
||||
- { name: destinationCode, in: query, required: true, schema: { type: string, minLength: 1, maxLength: 64 } }
|
||||
- { name: currency, in: query, required: true, schema: { type: string, enum: [CNY, USD] } }
|
||||
- { name: effectiveOn, in: query, required: true, schema: { type: string, format: date } }
|
||||
- { name: priceBasis, in: query, required: true, schema: { type: string, enum: [ORDER, PALLET] } }
|
||||
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
|
||||
- { name: size, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
|
||||
responses:
|
||||
'200':
|
||||
description: 成功业务码为200;失败保留业务码及requestId/traceId,不以空页冒充目录可用。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
code: { type: string }
|
||||
message: { type: string }
|
||||
requestId: { type: string }
|
||||
traceId: { type: string }
|
||||
data:
|
||||
type: object
|
||||
required: [records, page, size, total, pages]
|
||||
properties:
|
||||
records: { type: array, items: { $ref: '#/components/schemas/SourceSnapshot' } }
|
||||
page: { type: integer, minimum: 1 }
|
||||
size: { type: integer, minimum: 1 }
|
||||
total: { type: integer, minimum: 0 }
|
||||
pages: { type: integer, minimum: 0 }
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }
|
||||
schemas:
|
||||
QuoteRef:
|
||||
type: object
|
||||
required: [quoteNo, recordVersion, priceItem]
|
||||
properties:
|
||||
quoteNo: { type: string, minLength: 1, maxLength: 64 }
|
||||
recordVersion: { type: integer, format: int64, minimum: 0 }
|
||||
priceItem: { type: string, enum: [FULL_DELIVERY, LOOSE_DELIVERY] }
|
||||
SourceSnapshot:
|
||||
type: object
|
||||
required: [quoteNo, recordVersion, priceItem, customerId, customerName, originCity, destinationCode, distanceMiles, currency, priceBasis, quotedUnitPrice, validFrom, validTo, effectiveOn]
|
||||
properties:
|
||||
quoteNo: { type: string }
|
||||
recordVersion: { type: integer, format: int64, minimum: 0 }
|
||||
priceItem: { type: string, enum: [FULL_DELIVERY, LOOSE_DELIVERY] }
|
||||
customerId: { type: string }
|
||||
customerName: { type: string, description: 报价版本中的客户名称快照 }
|
||||
originCity: { type: string }
|
||||
destinationCode: { type: string }
|
||||
distanceMiles: { type: number, minimum: 0 }
|
||||
currency: { type: string, enum: [CNY, USD] }
|
||||
priceBasis: { type: string, enum: [ORDER, PALLET] }
|
||||
quotedUnitPrice: { type: number, minimum: 0, description: 正式报价单价;不是承运方最终成交单价或实际应收 }
|
||||
validFrom: { type: string, format: date }
|
||||
validTo: { type: string, format: date, description: 包含当天 }
|
||||
effectiveOn: { type: string, format: date }
|
||||
x-booking-integration:
|
||||
method: FleetQuoteCandidateService.lockAndResolve(Context requested, QuoteRef reference)
|
||||
transaction: MANDATORY;由承运写事务调用。SELECT FOR UPDATE锁住原报价直到外层提交,禁止事务外解析后再单独保存。
|
||||
context: customerId/originCity/destinationCode/currency/effectiveOn/priceBasis;必须由承运服务按客户、真实路线、计划起运时间和明确业务时区核验,不能原样信任浏览器条件。
|
||||
validation: 重新核对SALE、ACTIVE、精确版本、客户、路线、币种、日期及明确选择的价项。未知价拒绝,零价允许;采购、自提、停用或陈旧报价不能引用。
|
||||
snapshot: 保存返回的SourceSnapshot,禁止使用浏览器提交的来源快照。来源单价与承运实际成交单价、计价量、金额分别保存,后续报价改价不覆盖历史。
|
||||
manual: 未引用时不调用解析方法,保留手填成交价能力;引用报价不自动生成应收、成本或付款。
|
||||
ownership: 提供端不修改FleetBooking、共享UI或SQL;接入方负责写权限、幂等重放优先返回原结果和实际成交快照持久化。
|
||||
@@ -65,6 +65,37 @@ paths:
|
||||
description: '权限:platform:fleet:trip:list;读取IAM当前客户主档;不使用采购历史或允许输入不存在的客户。'
|
||||
parameters: [{name: keyword, in: query, schema: {type: string}}]
|
||||
responses: {'200': {description: 统一响应data为客户id/code/name/contactName/contactPhone数组}}
|
||||
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings/quote-candidates:
|
||||
parameters: [{$ref: '#/components/parameters/TripNo'}]
|
||||
get:
|
||||
summary: 按本车真实起运仓及当地计划日期查询客户销售报价
|
||||
description: '同时要求platform:fleet:trip:list与platform:fleet:quote:list。城市来自有效公共海外仓的唯一明确地址城市,日期为计划起运UTC时间转换到仓库明确时区;不接收浏览器城市或日期。目的地须为本车既有FBA卸货站;私人地址、缺失或歧义城市/时区明确拒绝查询,仍可手工询价。'
|
||||
parameters:
|
||||
- {name: customerId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
- {name: destinationKey, in: query, required: true, schema: {type: string, maxLength: 160}}
|
||||
- {name: currency, in: query, required: true, schema: {type: string, enum: [CNY, USD]}}
|
||||
- {name: priceBasis, in: query, required: true, schema: {type: string, enum: [PALLET, ORDER]}}
|
||||
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
|
||||
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 20}}
|
||||
responses:
|
||||
'200':
|
||||
description: 统一业务响应,data为报价来源候选分页;无价不列入,零价保留
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/Envelope'}
|
||||
- properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
records: {type: array, items: {$ref: 'fleet-quote-candidates-v1.yaml#/components/schemas/SourceSnapshot'}}
|
||||
page: {type: integer}
|
||||
size: {type: integer}
|
||||
total: {type: integer}
|
||||
pages: {type: integer}
|
||||
'403': {description: 缺少任一读取权限}
|
||||
'409': {description: 无法确定本车报价查询上下文}
|
||||
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings:
|
||||
parameters: [{$ref: '#/components/parameters/TripNo'}]
|
||||
get:
|
||||
@@ -379,6 +410,7 @@ components:
|
||||
properties:
|
||||
tripRecordVersion: {type: integer, format: int64, minimum: 0}
|
||||
customerId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
quoteRef: {$ref: '#/components/schemas/BookingQuoteRef'}
|
||||
cargo: {$ref: '#/components/schemas/BookingCargo'}
|
||||
idempotencyKey: {type: string, minLength: 8, maxLength: 128}
|
||||
BookingUpdate:
|
||||
@@ -387,7 +419,13 @@ components:
|
||||
properties:
|
||||
tripRecordVersion: {type: integer, format: int64, minimum: 0}
|
||||
recordVersion: {type: integer, format: int64, minimum: 0}
|
||||
quoteRef: {$ref: '#/components/schemas/BookingQuoteRef'}
|
||||
cargo: {$ref: '#/components/schemas/BookingCargo'}
|
||||
BookingQuoteRef:
|
||||
type: object
|
||||
nullable: true
|
||||
description: '选填;null或省略为手工询价。引用或清除已有来源另要求platform:fleet:quote:list。服务端在承运写事务内锁定并核对精确版本、SALE/ACTIVE、客户、路线、币种、仓库当地计划日期及价项;只保存可信来源,不接收浏览器快照。PALLET选LOOSE_DELIVERY,ORDER选FULL_DELIVERY。'
|
||||
allOf: [{$ref: 'fleet-quote-candidates-v1.yaml#/components/schemas/QuoteRef'}]
|
||||
BookingCommand:
|
||||
type: object
|
||||
required: [tripRecordVersion, recordVersion, idempotencyKey]
|
||||
@@ -406,6 +444,11 @@ components:
|
||||
customerName: {type: string}
|
||||
status: {type: string, enum: [DRAFT, CONFIRMED, RECEIVED, BOUND, CANCELLED]}
|
||||
cargo: {$ref: '#/components/schemas/BookingCargo'}
|
||||
quoteSource:
|
||||
type: object
|
||||
nullable: true
|
||||
description: '可信报价来源,独立于cargo.unitPrice及agreedAmount。确认承运重新核对报价及当前计划日期;确认后的报价变更或实收议价不覆盖已保存来源。'
|
||||
allOf: [{$ref: 'fleet-quote-candidates-v1.yaml#/components/schemas/SourceSnapshot'}]
|
||||
destinationLabel: {type: string}
|
||||
agreedAmount: {type: number, nullable: true, description: 约定运费,非财务应收或实收}
|
||||
incomingNo: {type: string, nullable: true}
|
||||
@@ -421,8 +464,8 @@ components:
|
||||
recordVersion: {type: integer, format: int64}
|
||||
updatedAt: {type: string, format: date-time}
|
||||
Outcome:
|
||||
description: packages保留原实发量;signedPackages为不可变实签量,returnedPackages只计原运输实际退回,remainingPackages必须为零且三者守恒才可结案。签收后的另行退货不重开原运输。
|
||||
type: object
|
||||
description: 本车原运输的结案依据;已签收后另发起退货仍保留本次运输DELIVERED及原signedAt,不修改后续退货单据状态。
|
||||
properties:
|
||||
dispatchNo: {type: string}
|
||||
deliveryNo: {type: string}
|
||||
@@ -431,6 +474,9 @@ components:
|
||||
status: {type: string}
|
||||
packages: {type: integer}
|
||||
terminalAt: {type: string, format: date-time, nullable: true}
|
||||
signedPackages: {type: integer, minimum: 0}
|
||||
returnedPackages: {type: integer, minimum: 0}
|
||||
remainingPackages: {type: integer, minimum: 0}
|
||||
CompletionEnvelope:
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/Envelope'}
|
||||
|
||||
@@ -898,7 +898,7 @@ paths:
|
||||
/api/platform/v1/last-leg/exception-orders/{exceptionNo}/complete:
|
||||
post:
|
||||
summary: 完成异常处理并原子恢复退回库存
|
||||
description: 权限 platform:overseas-warehouse:exception-order:process;仅 PROCESSING→COMPLETED。退回海外仓必须提交证据和实收目标,库存 RETURN_STOCK_IN 成功后才完成异常单和派送退回。
|
||||
description: 权限 platform:overseas-warehouse:exception-order:process;退回海外仓支持分批实收,每批使用新幂等键及当前版本,累计不得超过批准量。库存 RETURN_STOCK_IN 成功后累计实收,未收齐保留 PROCESSING,批准量收齐才转 COMPLETED。详情 returnSources 只返回剩余可收数量。派送仅在累计实际退回覆盖整单实发数量时写 RETURNED 和 RETURN_COMPLETED;非海外仓异常结案不证明货物交接完成。原 signedAt 保留,车次及资源不由工单关闭释放。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: exceptionNo, in: path, required: true, schema: { type: string } }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ExceptionComplete' } } } }
|
||||
@@ -906,7 +906,7 @@ paths:
|
||||
/api/platform/v1/last-leg/exception-orders/{exceptionNo}/cancel:
|
||||
post:
|
||||
summary: 取消未处理异常订单
|
||||
description: 权限 platform:overseas-warehouse:exception-order:process;仅 PENDING→CANCELLED。
|
||||
description: 权限 platform:overseas-warehouse:exception-order:process;仅 PENDING→CANCELLED,已有实收禁止取消。退回单按创建时冻结的派送及关联尾程状态原子恢复,保留原签收事实;缺少冻结状态或当前状态已变化时拒绝猜测恢复。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: exceptionNo, in: path, required: true, schema: { type: string } }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ExceptionCancel' } } } }
|
||||
@@ -927,7 +927,7 @@ paths:
|
||||
/api/platform/v1/last-leg/delivery-orders/{deliveryNo}/mark-return:
|
||||
post:
|
||||
summary: 将派送单标记为退回入仓
|
||||
description: 权限 platform:overseas-warehouse:delivery:return;items 必须来自 return-inbound-options,服务端冻结源出库明细与批准退回上限。
|
||||
description: 权限 platform:overseas-warehouse:delivery:return;items 必须来自 return-inbound-options,服务端扣除历史实际退回量后冻结源出库明细与批准退回上限。同一派送同时只允许一个 PENDING/PROCESSING 退回异常,取消或完成后可为剩余货物新建;RETURN_PENDING 不允许绕过尚未完成的退回异常。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: deliveryNo, in: path, required: true, schema: { type: string } }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/DeliveryMarkReturn' } } } }
|
||||
|
||||
Reference in New Issue
Block a user