feat(appointments): unify pre-inbound planning and confirmed FBA bookings

This commit is contained in:
2026-10-10 10:38:06 +08:00
parent 24f5ccf7ff
commit c4c1bddeec
54 changed files with 1623 additions and 744 deletions
@@ -1,15 +1,15 @@
openapi: 3.0.3
info:
title: 跨客户 FBA 预约与日历
version: 1.0.0
description: 按同一目的FBA仓合并多个客户的货物,预约号全局唯一。货物明细保留客户归属,只有平台可读取或维护完整预约;不改变库存与客户订单归属。
version: 2.0.0
description: 同一美国起运仓和目的FBA仓的跨客户安排。系统单号与外部约号分开;入仓前后共用稳定货源,只有平台可维护完整预约,不推进库存或现场作业。
security:
- bearerAuth: []
paths:
/api/platform/v1/last-leg/inbound-plans/fba-appointments/batch:
post:
summary: 为同一目的仓的跨客户入仓明细创建预约
description: '权限:platform:overseas-warehouse:inbound-order:fba-appointment。平台身份限定。客户归属由服务端入仓计划确定,不接受预约主单customerId;同号同内容重放返回原预约,异内容拒绝。已有有效预约的货物不能重复建约。'
description: '权限:platform:overseas-warehouse:inbound-order:fba-appointment。平台身份限定。入仓明细仅作选择入口,服务端转成原ORDER/BOOKING整票来源,不另建别名。系统生成appointmentNo,不接受主单客户或外部号冒充系统单号。同键、同操作者、同内容返回原命令快照;异内容拒绝。与入仓前货源入口共用同一主表及唯一占用约束。'
requestBody:
required: true
content:
@@ -18,10 +18,11 @@ paths:
allOf:
- {$ref: '#/components/schemas/AppointmentWrite'}
- type: object
required: [lineIds, appointmentNo]
required: [lineIds, originWarehouseId, fbaWarehouseCode]
properties:
lineIds: {type: array, minItems: 1, maxItems: 500, items: {type: string}}
appointmentNo: {type: string, maxLength: 64}
originWarehouseId: {type: string, minLength: 1, description: 选择时的接仓点,保存时核对}
fbaWarehouseCode: {type: string, minLength: 1, maxLength: 64, description: 选择时的目的仓,变化则拒绝}
lineIds: {type: array, minItems: 1, maxItems: 100, items: {type: string}}
responses:
'200': {$ref: '#/components/responses/AppointmentResult'}
'403': {description: 非平台身份或无写权限}
@@ -32,8 +33,8 @@ paths:
required: true
schema: {type: string}
delete:
summary: 删除未关联有效排车的预约
description: '平台专用,权限platform:appointment:delete。校验当前版本后原子删除预约及预约明细,不删除客户货物。已有关联有效排车时拒绝。'
summary: 取消未关联有效排车的预约并释放占用
description: '平台专用,权限platform:appointment:delete。校验版本后保留主单、确认事实及明细历史,设置CANCELLED并释放货源占用;不删除客户货物或回退库存。已有关联有效排车时拒绝。取消命令按系统单号、版本和操作者幂等。'
parameters:
- {name: version, in: query, required: true, schema: {type: integer, minimum: 0}}
responses:
@@ -47,7 +48,7 @@ paths:
'403': {description: 无读取权限}
put:
summary: 按版本更新预约
description: '权限:platform:overseas-warehouse:inbound-order:fba-appointment。平台限定,版本冲突拒绝;取消不删除货物。恢复时重新校验货物是否已被其他有效预约占用。'
description: '权限:platform:overseas-warehouse:inbound-order:fba-appointment。平台限定,版本及持久化幂等校验;已有有效排车时先处理运输安排。取消/失败必须写原因并保留原确认时间。恢复时重新核对来源、目的仓和占用;不能直接将SUCCESS退回PENDING。'
requestBody:
required: true
content:
@@ -66,7 +67,7 @@ paths:
get:
operationId: fbaAppointmentCalendar
summary: 按日期范围查询 FBA 预约
description: '权限:platform:appointment:list。仅平台身份可读取跨客户预约。固定批量查询预约及关联明细,不逐预约查详情。'
description: '权限:platform:appointment:list。按确认当地时间、计划时段或仅计划日期选取,排除取消/失败。仅有日期时不补造零点时段;无日期记录走未定时分页入口。固定批量查询预约及明细,不逐预约查详情。跨时区总览仍需按地点标注,不将当地值当成浏览器时区。'
parameters:
- name: from
in: query
@@ -110,22 +111,40 @@ components:
schemas:
AppointmentWrite:
type: object
required: [appointmentAt, expectedLoadingAt, appointmentStatus]
required: [details, idempotencyKey]
properties:
appointmentAt: {type: string, description: 预约开始日期时间}
details: {$ref: '#/components/schemas/AppointmentDetails'}
idempotencyKey: {type: string, minLength: 1, maxLength: 64}
AppointmentDetails:
type: object
required: [appointmentStatus]
properties:
plannedDate: {type: string, format: date, nullable: true, description: 计划当地日期,允许未定}
plannedAt: {type: string, nullable: true, description: 已知的计划开始当地时段,日期必须与plannedDate一致}
plannedEndAt: {type: string, nullable: true}
confirmationNo: {type: string, maxLength: 128, nullable: true, description: 仅SUCCESS登记收货方的外部约号;同一FBA仓不得重复}
appointmentAt: {type: string, nullable: true, description: 对方确认开始当地时间;SUCCESS必填,PENDING不能当作计划填写}
appointmentEndAt: {type: string, nullable: true, description: 明确结束时间时必须晚于开始时间;未知留空,不推算}
expectedLoadingAt: {type: string}
appointmentStatus: {type: string, enum: [PENDING, SUCCESS, FAILED, CANCELLED]}
appointmentRemark: {type: string, maxLength: 1000, nullable: true}
startOffset: {type: string, maxLength: 6, nullable: true, description: 当地重叠时刻的明确时差;必须符合目的仓IANA时区}
endOffset: {type: string, maxLength: 6, nullable: true}
Appointment:
type: object
properties:
id: {type: string, description: 内部标识,不作为展示编号}
appointmentNo: {type: string}
appointmentAt: {type: string, description: 业务日期时间,不含时区偏移}
appointmentNo: {type: string, description: 系统生成AP业务单号}
confirmationNo: {type: string, nullable: true, description: 对方正式外部确认号}
plannedDate: {type: string, format: date, nullable: true}
plannedAt: {type: string, nullable: true}
plannedEndAt: {type: string, nullable: true}
appointmentAt: {type: string, nullable: true, description: 对方确认的地点当地时间}
appointmentEndAt: {type: string, nullable: true}
version: {type: integer, minimum: 0}
expectedLoadingAt: {type: string}
appointmentStartUtc: {type: string, format: date-time, nullable: true}
appointmentEndUtc: {type: string, format: date-time, nullable: true}
timezone: {type: string, nullable: true, description: 来自美国FBA仓主档的IANA时区;缺失不得正式确认}
originWarehouseId: {type: string}
appointmentStatus: {type: string, enum: [PENDING, SUCCESS, FAILED, CANCELLED]}
appointmentRemark: {type: string, nullable: true}
fbaWarehouseCode: {type: string}
@@ -140,8 +159,9 @@ components:
properties:
id: {type: string}
customerId: {type: string, description: 本票货物客户归属,不在预约主单指定客户}
planId: {type: string}
lineId: {type: string}
orderId: {type: string}
inboundCode: {type: string}
sourceKey: {type: string}
sourceVersion: {type: string, nullable: true}
orderId: {type: string, nullable: true}
inboundCode: {type: string, nullable: true}
packageCount: {type: integer, nullable: true}
fbaWarehouseCode: {type: string}