fix(order): make merchant draft edits and cancellations versioned and idempotent

This commit is contained in:
2026-10-02 21:33:39 +08:00
parent 4b531c7e22
commit 58dfcc21f6
10 changed files with 1227 additions and 28 deletions
@@ -585,6 +585,44 @@ paths:
code: {type: string, example: '200'}
data: {$ref: '#/components/schemas/InboundCodePolicy'}
'403': {description: 需要 merchant:order:list、merchant:order:create 或 merchant:order:edit 权限之一}
/api/merchant/v1/orders/first-leg/{id}:
put:
summary: 客户版本化编辑草稿
description: >-
仅允许本客户 DRAFT/REJECTED 订单。必须携带读取详情时的 expectedVersion 和 Idempotency-Key;
不接受旧平铺保存请求。原键同载荷重放先返回首次成功,不再检查已递增版本或修改明细与历史;
同键不同订单/版本/内容、首次版本过期或状态不允许返回业务冲突 A0409。
版本或 draft 缺失为参数错误。主表、子明细、历史和幂等结果同事务提交或回滚。
parameters:
- $ref: '#/components/parameters/OrderId'
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/MerchantDraftUpdateRequest'}
responses:
'200': {description: 成功或业务失败,按响应体 code 判断}
'403': {description: 需要 merchant:order:edit 权限和本客户订单归属}
/api/merchant/v1/orders/first-leg/{id}/cancel:
post:
summary: 客户版本化幂等取消订单
description: >-
必须携带当前 expectedVersion、原因和 Idempotency-Key。原键同载荷返回首次成功,
不重复取消、聚合根订单、写历史或 Outbox;同键不同订单/版本/原因拒绝。
新命令仅在当前版本及取消状态门禁通过后执行,重复取消新命令返回 A0409。
取消、根履约聚合、历史、事件与幂等结果同事务完成;不接受缺版本的旧原因请求。
parameters:
- $ref: '#/components/parameters/OrderId'
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/MerchantOrderCancelRequest'}
responses:
'200': {description: 成功或业务失败,按响应体 code 判断}
'403': {description: 需要 merchant:order:cancel 权限和本客户订单归属}
/api/merchant/v1/orders/first-leg/{id}/submit:
post:
summary: 提交订单
@@ -1241,6 +1279,18 @@ components:
type: string
enum: [TAX_INCLUDED, TAX_EXCLUDED]
description: 报价包税维度;独立于订单 taxMode 的买单/退税语义
MerchantDraftUpdateRequest:
type: object
required: [expectedVersion, draft]
properties:
expectedVersion: {type: integer, minimum: 0}
draft: {$ref: '#/components/schemas/FirstLegOrderDraft'}
MerchantOrderCancelRequest:
type: object
required: [expectedVersion, reason]
properties:
expectedVersion: {type: integer, minimum: 0}
reason: {type: string, minLength: 1, maxLength: 500}
FirstLegOrderDraft:
type: object
required: [customerName, transportMode, cargoes, pickupMethod, receivingWarehouseId, receivingWarehouseName, channelCode, channelName, taxMode, pricingTaxMode, destinationType]