feat(appointment): integrate work calendar and stable cargo batches
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
openapi: 3.0.3
|
||||
info: {title: Firstleg appointment work authoritative sources, version: 1.0.0}
|
||||
security: [{InternalServiceToken: []}]
|
||||
paths:
|
||||
/internal/firstleg/appointment-work/containers:
|
||||
get:
|
||||
summary: Complete actual-sailed container scope
|
||||
description: Includes missing warehouses, private destinations and stopped tracking tasks. An actual departure or later maritime milestone is required. GET never creates work. Scope-versioned cursors reject changes between pages with 409; consumers restart a full scan. Original local times without a source timezone are never converted to UTC.
|
||||
parameters:
|
||||
- {name: after, in: query, schema: {type: string, maxLength: 128}}
|
||||
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 200}}
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data: {$ref: '#/components/schemas/SourcePage'}
|
||||
'401': {description: Invalid internal service identity}
|
||||
'409': {description: Scope changed; restart from the first page}
|
||||
/internal/firstleg/appointment-work/containers/{id}:
|
||||
get:
|
||||
summary: Read one authoritative sailed container and physical cargo lineage
|
||||
parameters: [{name: id, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}]
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties: {data: {$ref: '#/components/schemas/ContainerSource'}}
|
||||
'401': {description: Invalid internal service identity}
|
||||
'404': {description: No active actual-sailed source}
|
||||
/internal/firstleg/appointment-work/facts:
|
||||
post:
|
||||
summary: Read pickup, direct unloading and empty-return facts; no writes
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {type: array, minItems: 1, maxItems: 200, items: {$ref: '#/components/schemas/FactRequest'}}
|
||||
responses:
|
||||
'200':
|
||||
description: Exactly one result per requested work key, never a partial success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties: {data: {type: array, items: {$ref: '#/components/schemas/Fact'}}}
|
||||
'400': {description: Invalid type or duplicate identity}
|
||||
'401': {description: Invalid internal service identity}
|
||||
'409': {description: Direct requirement does not match physical cargo}
|
||||
components:
|
||||
securitySchemes:
|
||||
InternalServiceToken: {type: apiKey, in: header, name: X-Internal-Service-Token}
|
||||
schemas:
|
||||
Reference:
|
||||
type: object
|
||||
required: [kind, key, label]
|
||||
properties:
|
||||
kind: {type: string}
|
||||
key: {type: string, description: Internal association only}
|
||||
businessNo: {type: string, nullable: true}
|
||||
label: {type: string}
|
||||
SourceReference:
|
||||
type: object
|
||||
properties:
|
||||
customerId: {type: string}
|
||||
sourceKey: {type: string}
|
||||
destinationKey: {type: string, nullable: true}
|
||||
Requirement:
|
||||
type: object
|
||||
required: [requirementKey, references, replacesRequirementKeys, sources]
|
||||
properties:
|
||||
requirementKey: {type: string, description: 'Immutable INBOUND:<physical inbound id>'}
|
||||
batchKey: {type: string, nullable: true}
|
||||
batchNo: {type: string, nullable: true}
|
||||
destinationKind: {type: string, nullable: true}
|
||||
originName: {type: string, nullable: true}
|
||||
destinationName: {type: string, nullable: true}
|
||||
timezone: {type: string, nullable: true}
|
||||
references: {type: array, items: {$ref: '#/components/schemas/Reference'}}
|
||||
replacesRequirementKeys: {type: array, items: {type: string}}
|
||||
sources: {type: array, items: {$ref: '#/components/schemas/SourceReference'}, description: Empty until Lastleg resolves its canonical source; domestic ORDER references are not tail-leg ORDER keys}
|
||||
ContainerSource:
|
||||
type: object
|
||||
required: [containerBookingId, sourceVersion, establishedFlow, requirements]
|
||||
properties:
|
||||
containerBookingId: {type: string}
|
||||
containerNo: {type: string, nullable: true}
|
||||
departedAt: {type: string, format: date-time, nullable: true}
|
||||
departedLocalAt: {type: string, nullable: true, description: Exact actual departure only; source local timestamp without offset}
|
||||
sailedEvidenceLocalAt: {type: string, nullable: true, description: Actual departure-or-later node proving sailed eligibility; never presented as an actual departure time}
|
||||
sourceVersion: {type: string}
|
||||
establishedFlow: {type: string, enum: [UNKNOWN, DIRECT, TRANSFER]}
|
||||
timezone: {type: string, nullable: true}
|
||||
requirements: {type: array, items: {$ref: '#/components/schemas/Requirement'}}
|
||||
SourcePage:
|
||||
type: object
|
||||
required: [containers, complete, scopeVersion]
|
||||
properties:
|
||||
containers: {type: array, items: {$ref: '#/components/schemas/ContainerSource'}}
|
||||
nextCursor: {type: string, nullable: true}
|
||||
complete: {type: boolean}
|
||||
scopeVersion: {type: string}
|
||||
ContainerReference:
|
||||
type: object
|
||||
properties:
|
||||
containerBookingId: {type: string}
|
||||
bookingNo: {type: string, nullable: true}
|
||||
containerNo: {type: string, nullable: true}
|
||||
FactRequest:
|
||||
type: object
|
||||
required: [workItemKey, type, flow, containerBookingId]
|
||||
properties:
|
||||
workItemKey: {type: string}
|
||||
type: {type: string, enum: [PICKUP, EMPTY_RETURN, DELIVERY]}
|
||||
flow: {type: string, enum: [TRANSFER, DIRECT, UNKNOWN]}
|
||||
containerBookingId: {type: string}
|
||||
requirementKey: {type: string, nullable: true}
|
||||
batchKey: {type: string, nullable: true}
|
||||
tripKey: {type: string, nullable: true}
|
||||
batchKeys: {type: array, items: {type: string}}
|
||||
containers: {type: array, items: {$ref: '#/components/schemas/ContainerReference'}}
|
||||
sources: {type: array, items: {$ref: '#/components/schemas/SourceReference'}}
|
||||
Slot:
|
||||
type: object
|
||||
properties:
|
||||
date: {type: string, format: date}
|
||||
startTime: {type: string, nullable: true}
|
||||
endDate: {type: string, format: date, nullable: true}
|
||||
endTime: {type: string, nullable: true}
|
||||
timezone: {type: string, nullable: true, description: Null for unzoned original plans; source-local date is retained}
|
||||
Actual:
|
||||
type: object
|
||||
description: At least one time is required. Unzoned source facts contain localOccurredAt only.
|
||||
properties:
|
||||
occurredAt: {type: string, format: date-time, nullable: true}
|
||||
localOccurredAt: {type: string, nullable: true}
|
||||
timezone: {type: string, nullable: true}
|
||||
Fact:
|
||||
type: object
|
||||
required: [workItemKey, fingerprint, impactToken, planExists, planStatus, executionStatus, readPermissions, actionPermissions, blockReasons, missingFields, references]
|
||||
properties:
|
||||
workItemKey: {type: string}
|
||||
fingerprint: {type: string}
|
||||
impactToken: {type: string, description: First scheduling and first carrier assignment do not increase semantic impact}
|
||||
planExists: {type: boolean}
|
||||
planStatus: {type: string, enum: [UNPLANNED, TARGET_SET, AWAITING_REPLY, CONFIRMED, NOT_REQUIRED]}
|
||||
executionStatus: {type: string, enum: [PENDING, IN_PROGRESS, COMPLETED]}
|
||||
target: {$ref: '#/components/schemas/Slot'}
|
||||
confirmed: {$ref: '#/components/schemas/Slot'}
|
||||
actual: {$ref: '#/components/schemas/Actual'}
|
||||
timezone: {type: string, nullable: true}
|
||||
businessNo: {type: string, nullable: true}
|
||||
tripKey: {type: string, nullable: true}
|
||||
readPermissions: {type: array, items: {type: string}}
|
||||
actionPermissions: {type: array, items: {type: string}}
|
||||
blockReasons: {type: array, items: {type: string}}
|
||||
missingFields: {type: array, items: {type: string}}
|
||||
references: {type: array, items: {$ref: '#/components/schemas/Reference'}}
|
||||
responsibility:
|
||||
type: object
|
||||
nullable: true
|
||||
properties: {kind: {type: string}, id: {type: string, nullable: true}, name: {type: string}}
|
||||
action:
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
code: {type: string}
|
||||
label: {type: string}
|
||||
targetWorkItemKey: {type: string, nullable: true}
|
||||
target:
|
||||
type: object
|
||||
properties: {kind: {type: string, enum: [FIRSTLEG_CONTAINER]}, containerBookingId: {type: string}}
|
||||
@@ -0,0 +1,70 @@
|
||||
openapi: 3.0.3
|
||||
info: {title: Direct destination unloading evidence, version: 1.0.0}
|
||||
paths:
|
||||
/api/platform/v1/terminal-drayages/{containerBookingId}/direct-deliveries:
|
||||
get:
|
||||
summary: Physical loaded inbound sources and immutable direct unloading evidence
|
||||
x-permission: platform:firstleg:terminal-drayage:list
|
||||
parameters: [{$ref: '#/components/parameters/Container'}]
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties: {data: {type: array, items: {$ref: '#/components/schemas/View'}}}
|
||||
'403': {description: Requires platform identity and list permission}
|
||||
/api/platform/v1/terminal-drayages/{containerBookingId}/direct-deliveries/{inboundId}:
|
||||
post:
|
||||
summary: Record actual unloading for one complete physically loaded source
|
||||
description: Locks the physical container. Requires Lastleg's persisted confirmed DIRECT route, actual pickup, a real recipient and receipt reference, an unambiguous IANA local time, and no warehouse unloading or empty-return fact. Route source failure returns 503. Exact retries replay; changed retries conflict. Does not create warehouse stock. Every loaded source must complete before manual empty return. Unzoned pickup/return timestamps are not compared to destination wall-clock times; cross-location chronology cannot be proven without the source zone. Direct completion and its 72-hour reminder use the latest evidence Instant.
|
||||
x-permission: platform:firstleg:terminal-drayage:direct-unload:confirm
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/Container'}
|
||||
- {name: inboundId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
- {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}}
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [expectedVersion, actualAt, timezone, receivedBy, evidenceNo]
|
||||
properties:
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
actualAt: {type: string, description: Local YYYY-MM-DDTHH:mm:ss without offset}
|
||||
timezone: {type: string, description: Explicit destination IANA zone}
|
||||
receivedBy: {type: string, minLength: 1, maxLength: 128}
|
||||
evidenceNo: {type: string, minLength: 1, maxLength: 128}
|
||||
remark: {type: string, maxLength: 1000, nullable: true}
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties: {data: {$ref: '#/components/schemas/View'}}
|
||||
'400': {description: Invalid source, time or evidence}
|
||||
'403': {description: Requires platform identity and independent unloading permission}
|
||||
'409': {description: Invalid business prerequisites, version or replay content}
|
||||
'503': {description: Cannot verify authoritative route}
|
||||
components:
|
||||
parameters:
|
||||
Container: {name: containerBookingId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
schemas:
|
||||
View:
|
||||
type: object
|
||||
properties:
|
||||
requirementKey: {type: string, description: 'INBOUND:<id>, association only, not a visible business number'}
|
||||
inboundCode: {type: string, nullable: true}
|
||||
orderNo: {type: string, nullable: true}
|
||||
destinationName: {type: string, nullable: true}
|
||||
packageCount: {type: integer}
|
||||
version: {type: integer, description: Zero until first actual unloading}
|
||||
actualAt: {type: string, nullable: true}
|
||||
timezone: {type: string, nullable: true}
|
||||
receivedBy: {type: string, nullable: true}
|
||||
evidenceNo: {type: string, nullable: true}
|
||||
remark: {type: string, nullable: true}
|
||||
@@ -4,7 +4,7 @@ paths:
|
||||
/api/platform/v1/terminal-drayages/calendar:
|
||||
get:
|
||||
summary: 美国目的仓相关的提柜预约日历
|
||||
description: 平台身份,同时要求预约列表和码头拖车列表权限。按原业务预约提柜日期查询,不将实际提柜或预计到仓替代预约时间。美国范围按启用目的仓国家确认,不代表已确认港口所在国。原字段未提供港口时区,timeBasis明确待核实,禁止当作UTC或套用收货仓时区。最多42天、读取1001条以检测截断;返回最多1000条中的美国范围记录,未知目的仓计入unclassifiedCount,不能将缺失数据当零。
|
||||
description: 平台身份,同时要求预约列表和码头拖车列表权限。已提柜按船节点权威实际提柜日期、尚未提柜按原预约日期查询,改期不移动已执行事实;原预约与实际时间分别返回,不以预计到仓替代。美国范围按启用目的仓国家确认,不代表已确认港口所在国。原字段未提供港口时区,timeBasis明确待核实,禁止当作UTC或套用收货仓时区。最多42天,原预约和船节点实际日期候选分别读取1001条检测截断,经各自所属服务读取并按柜去重;返回最多1000条美国范围记录,未知目的仓计入unclassifiedCount,不能将缺失数据当零。
|
||||
x-permissions: [platform:appointment:list, 'platform:firstleg:terminal-drayage:list']
|
||||
parameters:
|
||||
- {name: from, in: query, required: true, schema: {type: string, format: date}, description: 包含的原业务当地日期}
|
||||
@@ -38,7 +38,7 @@ paths:
|
||||
destinationPortName: {type: string, nullable: true}
|
||||
destinationWarehouseCode: {type: string, nullable: true}
|
||||
fleetSupplierName: {type: string, nullable: true}
|
||||
pickupAppointmentAt: {type: string, description: 原业务当地时间,无时区偏移,不是UTC时间点}
|
||||
pickupAppointmentAt: {type: string, nullable: true, description: 原业务当地预约时间,无时区偏移;仅有实际提柜事实时可为空}
|
||||
actualPickupAt: {type: string, nullable: true}
|
||||
'400': {description: 日期范围非法}
|
||||
'403': {description: 非平台身份或权限不足}
|
||||
@@ -82,8 +82,8 @@ paths:
|
||||
responses: {'200': {$ref: '#/components/responses/ViewResponse'}}
|
||||
/api/platform/v1/terminal-drayages/{containerBookingId}/empty-return:
|
||||
post:
|
||||
summary: 海外卸柜收货完成后确认实际还柜并推进EMPTY_RETURNED节点
|
||||
description: 以海外卸柜完成事实开放还柜;无需已有车队、预约或实际提柜记录。无拖车记录时提交 expectedVersion=0,由命令创建记录,不补造提柜时间。实际还柜时间不得早于卸柜完成时间及已有实际提柜时间。
|
||||
summary: 海外仓卸柜或全部直送目的仓实际卸货完成后确认实际还柜并推进EMPTY_RETURNED节点
|
||||
description: 海外仓卸柜或全部直送目的仓实际卸货完成后开放还柜。无需已有车队或预约;海外仓卸柜可无实际提柜记录。无拖车记录时提交 expectedVersion=0,不补造提柜时间。保留原海外仓卸柜及已有船节点实际提柜的当地时间先后校验;直送目的仓时区不能套用于缺少时区的还柜时间,不比较不同地点的当地钟面值。
|
||||
x-permission: platform:firstleg:terminal-drayage:empty-return:confirm
|
||||
parameters: [{$ref: '#/components/parameters/ContainerBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActualTimeRequest'}}}}
|
||||
@@ -173,7 +173,7 @@ components:
|
||||
required: [status, overdueHours, overdueDays]
|
||||
properties:
|
||||
status: {type: string, enum: [NOT_APPLICABLE, PENDING, OVERDUE, RESOLVED]}
|
||||
deadlineAt: {type: string, format: date-time, nullable: true, description: 卸柜完成时间加72小时}
|
||||
deadlineAt: {type: string, format: date-time, nullable: true, description: 原海外仓卸柜时间加72小时;直送按最晚真实卸货Instant加72小时后保留该目的地当地时间}
|
||||
overdueHours: {type: integer, format: int64, minimum: 0}
|
||||
overdueDays: {type: integer, format: int64, minimum: 0}
|
||||
TerminalDrayageView:
|
||||
@@ -189,7 +189,7 @@ components:
|
||||
fleetSupplierName: {type: string, nullable: true}
|
||||
pickupAppointmentAt: {type: string, format: date-time, nullable: true}
|
||||
actualPickupAt: {type: string, format: date-time, nullable: true, description: 优先使用实际船节点提柜时间;缺失时在海外PDA确认整柜拆柜完成后使用卸柜完成时间}
|
||||
estimatedEmptyReturnAt: {type: string, format: date-time, nullable: true, description: 服务端权威值,卸柜完成时间加72小时}
|
||||
estimatedEmptyReturnAt: {type: string, format: date-time, nullable: true, description: 服务端权威值;直送72小时按绝对时长计算,时区不明的原海外仓事实保留原规则}
|
||||
actualEmptyReturnAt: {type: string, format: date-time, nullable: true}
|
||||
unloadingCompletedAt: {type: string, format: date-time, nullable: true}
|
||||
emptyReturnWarning: {$ref: '#/components/schemas/Warning'}
|
||||
|
||||
Reference in New Issue
Block a user