fix(firstleg): scope truck appointments by booking
This commit is contained in:
@@ -4,8 +4,8 @@ CREATE TABLE IF NOT EXISTS fl_truck_appointment (
|
||||
appointment_no VARCHAR(40) NOT NULL COMMENT '平台拖车预约编号',
|
||||
outbound_order_id BIGINT UNSIGNED NULL COMMENT '关联头程订单逻辑ID;不跨库建外键',
|
||||
ocean_booking_id BIGINT UNSIGNED NULL COMMENT '关联海运订舱逻辑ID',
|
||||
container_booking_id BIGINT UNSIGNED NULL COMMENT '关联柜次逻辑ID;拖车完成供装柜门禁校验',
|
||||
loading_plan_id BIGINT UNSIGNED NULL COMMENT '关联装柜计划逻辑ID',
|
||||
container_booking_id BIGINT UNSIGNED NULL COMMENT '停用保留列;订舱级拖车单不写入、不读取',
|
||||
loading_plan_id BIGINT UNSIGNED NULL COMMENT '停用保留列;订舱级拖车单不写入、不读取',
|
||||
warehouse_id BIGINT UNSIGNED NULL COMMENT '预约到仓的国内收货仓逻辑ID',
|
||||
carrier_id BIGINT UNSIGNED NULL COMMENT '拖车服务商逻辑ID',
|
||||
carrier_name VARCHAR(128) NULL COMMENT '拖车服务商名称快照',
|
||||
@@ -35,14 +35,14 @@ CREATE TABLE IF NOT EXISTS fl_truck_appointment (
|
||||
payable_amount DECIMAL(18,2) NULL COMMENT '应付拖车服务商金额;币种由currency指定',
|
||||
currency VARCHAR(8) NOT NULL DEFAULT 'RMB' COMMENT '应付金额币种;仅RMB或USD',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '拖车预约状态:DRAFT草稿、PENDING_DISPATCH待派车、DISPATCHED已派车、PICKUP_IN_PROGRESS提货中、ARRIVED_WAREHOUSE已到仓、LOADING装柜中、DEPARTED已离仓、COMPLETED已完成、RESCHEDULED已改约、EXCEPTION异常、CANCELLED已取消',
|
||||
active_container_booking_id BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN status NOT IN ('COMPLETED','CANCELLED','EXCEPTION') THEN container_booking_id ELSE NULL END) STORED COMMENT '非终态预约的柜次唯一键',
|
||||
active_ocean_booking_id BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN status NOT IN ('COMPLETED','CANCELLED','EXCEPTION') THEN ocean_booking_id ELSE NULL END) STORED COMMENT '非终态预约的订舱唯一键',
|
||||
revision INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '拖车预约业务修订号;每次状态或改约递增',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
remark VARCHAR(1000) NULL COMMENT '平台内部备注或异常说明',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '拖车预约创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '拖车预约最后更新时间',
|
||||
PRIMARY KEY(id), UNIQUE KEY uk_truck_appointment_no(appointment_no), UNIQUE KEY uk_truck_active_container(active_container_booking_id),
|
||||
KEY idx_truck_appointment_status(status,updated_at), KEY idx_truck_appointment_container(container_booking_id),
|
||||
PRIMARY KEY(id), UNIQUE KEY uk_truck_appointment_no(appointment_no), UNIQUE KEY uk_truck_active_ocean_booking(active_ocean_booking_id),
|
||||
KEY idx_truck_appointment_status(status,updated_at), KEY idx_truck_appointment_ocean(ocean_booking_id),
|
||||
CONSTRAINT chk_truck_appointment_status CHECK(status IN ('DRAFT','PENDING_DISPATCH','DISPATCHED','PICKUP_IN_PROGRESS','ARRIVED_WAREHOUSE','LOADING','DEPARTED','COMPLETED','RESCHEDULED','EXCEPTION','CANCELLED')),
|
||||
CONSTRAINT chk_truck_appointment_currency CHECK(currency IN ('RMB','USD'))
|
||||
) ENGINE=InnoDB COMMENT='三期拖车预约派车聚合';
|
||||
@@ -100,16 +100,26 @@ CALL phase3_truck_add_column(
|
||||
'DATETIME(3) NULL COMMENT ''车辆实际离仓时间;DEPARTED后供LOADED门禁使用'' AFTER actual_loading_at'
|
||||
);
|
||||
CALL phase3_truck_add_column(
|
||||
'active_container_booking_id',
|
||||
'BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN status NOT IN (''COMPLETED'',''CANCELLED'',''EXCEPTION'') THEN container_booking_id ELSE NULL END) STORED COMMENT ''非终态预约的柜次唯一键'' AFTER status'
|
||||
'active_ocean_booking_id',
|
||||
'BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN status NOT IN (''COMPLETED'',''CANCELLED'',''EXCEPTION'') THEN ocean_booking_id ELSE NULL END) STORED COMMENT ''非终态预约的订舱唯一键'' AFTER status'
|
||||
);
|
||||
DROP PROCEDURE IF EXISTS phase3_truck_add_column;
|
||||
|
||||
SET @phase3_truck_active_index = IF(
|
||||
SET @phase3_truck_drop_old_active_index = IF(
|
||||
EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_truck_appointment' AND index_name='uk_truck_active_container' AND non_unique=0),
|
||||
'ALTER TABLE fl_truck_appointment DROP INDEX uk_truck_active_container',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE phase3_truck_active_stmt FROM @phase3_truck_drop_old_active_index;
|
||||
EXECUTE phase3_truck_active_stmt;
|
||||
DEALLOCATE PREPARE phase3_truck_active_stmt;
|
||||
|
||||
SET @phase3_truck_active_index = IF(
|
||||
EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_truck_appointment' AND index_name='uk_truck_active_ocean_booking' AND non_unique=0),
|
||||
'SELECT 1',
|
||||
'ALTER TABLE fl_truck_appointment ADD UNIQUE KEY uk_truck_active_container(active_container_booking_id)'
|
||||
'ALTER TABLE fl_truck_appointment ADD UNIQUE KEY uk_truck_active_ocean_booking(active_ocean_booking_id)'
|
||||
);
|
||||
PREPARE phase3_truck_active_stmt FROM @phase3_truck_active_index;
|
||||
EXECUTE phase3_truck_active_stmt;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Truck Appointment API
|
||||
version: 1.3.0
|
||||
description: Every truck appointment must select one RELEASED ocean booking and its allocated ACTIVE internal container identity. A real ISO 6346 container number is not required for create, edit, submit or dispatch. PICKUP may omit it and still advances normally; when supplied, PICKUP atomically records it if still missing or reuses the existing valid number. The bill-of-lading number is frozen from the booking, and the booking must own at least one active BOOKING_CONFIRMATION or BILL_OF_LADING file under OCEAN_BOOKING_DOCUMENT. Booking and bill files are not owned by truck appointments. After a PDA loading transaction commits, the server best-effort completes the unique active appointment explicitly linked to that container in an isolated transaction; absence, duplicates or concurrent truck edits never roll back loading completion.
|
||||
version: 2.0.0
|
||||
description: A truck appointment belongs to one RELEASED ocean booking, independently of when or how that booking's internal containers are linked. Each booking may have at most one non-terminal appointment. The bill-of-lading number and warehouse are frozen from the booking, which must own at least one active BOOKING_CONFIRMATION or BILL_OF_LADING file. Container identities and real container numbers remain in the container/loading modules and are not part of this API. PDA loading completion does not change a booking-level truck appointment.
|
||||
paths:
|
||||
/api/platform/v1/truck-appointments:
|
||||
get:
|
||||
summary: Query truck appointments
|
||||
responses:
|
||||
'200':
|
||||
description: Paged truck appointments with stable booking/container snapshots and attachment count
|
||||
description: Paged booking-level truck appointments with stable booking snapshots and attachment count
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -20,8 +20,8 @@ paths:
|
||||
properties:
|
||||
records: {type: array, items: {$ref: '#/components/schemas/DetailStableReferences'}}
|
||||
post:
|
||||
summary: Create a truck appointment for a released booking and its allocated internal container
|
||||
description: If the real container already has a non-terminal appointment, the server returns A0409 with the existing appointment number and instructs the operator to continue editing. This duplicate guard runs before booking-document validation; the database unique key provides the same response for concurrent creates.
|
||||
summary: Create a truck appointment for a released booking
|
||||
description: If the booking already has a non-terminal appointment, the server returns A0409 with the existing appointment number and instructs the operator to continue editing. The database booking-level unique key provides the same guarantee for concurrent creates.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -29,17 +29,17 @@ paths:
|
||||
schema: {$ref: '#/components/schemas/SaveRequest'}
|
||||
responses:
|
||||
'200': {description: 'Created appointment', content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}}
|
||||
'409': {description: 'A0409 - this container already has a non-terminal appointment; message includes its appointment number and asks the operator to continue editing'}
|
||||
'409': {description: 'A0409 - this booking already has a non-terminal appointment; message includes its appointment number and asks the operator to continue editing'}
|
||||
/api/platform/v1/truck-appointments/candidates:
|
||||
get:
|
||||
summary: Query authoritative create-time ocean-booking and internal-container candidates
|
||||
description: Requires platform:firstleg:truck-appointment:list. Returns only RELEASED bookings with an active booking-owned confirmation or bill file and allocated ACTIVE internal containers that have no non-terminal truck appointment and whose loading plan is absent or not COMPLETED. A missing containerNo remains eligible. Completed/LOADED containers, occupied containers and bookings without any remaining eligible allocated container are absent. Edit pages must display their current binding from appointment detail rather than injecting it into this create-only candidate result.
|
||||
summary: Query authoritative create-time ocean-booking candidates
|
||||
description: Requires platform:firstleg:truck-appointment:list. Returns one row per RELEASED booking with a bill of lading and an active booking-owned confirmation or bill file, excluding bookings that already have a non-terminal truck appointment. Container allocation and loading status do not affect eligibility. Edit pages display their current booking from appointment detail rather than injecting it into this create-only result.
|
||||
parameters:
|
||||
- {name: keyword, in: query, required: false, schema: {type: string, maxLength: 64}, description: Matches booking number, bill of lading, cabinet number or real container number}
|
||||
- {name: keyword, in: query, required: false, schema: {type: string, maxLength: 64}, description: Matches booking number or bill of lading}
|
||||
- {name: page, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, default: 1}}
|
||||
- {name: size, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}}
|
||||
responses:
|
||||
'200': {description: Paged flat booking-container candidates, content: {application/json: {schema: {$ref: '#/components/schemas/CandidatePageEnvelope'}}}}
|
||||
'200': {description: Paged booking candidates, content: {application/json: {schema: {$ref: '#/components/schemas/CandidatePageEnvelope'}}}}
|
||||
/api/platform/v1/truck-appointments/{id}:
|
||||
parameters: [{$ref: '#/components/parameters/AppointmentId'}]
|
||||
get:
|
||||
@@ -63,7 +63,7 @@ paths:
|
||||
- {$ref: '#/components/parameters/RequestHash'}
|
||||
post:
|
||||
summary: Apply an idempotent appointment lifecycle action
|
||||
description: PICKUP accepts optional containerNo. Omitting it never blocks the transition to PICKUP_IN_PROGRESS; when supplied, the server atomically records it on the linked internal container if missing. An existing valid ISO 6346 number may be omitted or resubmitted unchanged; a different value is rejected and must use the preload correction flow. Other actions reject containerNo. ARRIVE, LOAD and DEPART persist their actual event timestamps atomically with the status transition.
|
||||
description: All actions transition the booking-level appointment as one aggregate. PICKUP does not accept or write a container number. ARRIVE, LOAD and DEPART persist their actual event timestamps atomically with the status transition.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -168,7 +168,7 @@ components:
|
||||
timestamp: {type: string, format: date-time}
|
||||
Candidate:
|
||||
type: object
|
||||
required: [oceanBookingId, oceanBookingNo, warehouseId, requirementId, containerType, requirementQuantity, containerBookingId, cabinetNo, bookingDocumentReady]
|
||||
required: [oceanBookingId, oceanBookingNo, warehouseId, bookingDocumentReady]
|
||||
properties:
|
||||
oceanBookingId: {type: integer, format: int64}
|
||||
oceanBookingNo: {type: string}
|
||||
@@ -177,15 +177,6 @@ components:
|
||||
warehouseName: {type: string, nullable: true}
|
||||
channelCode: {type: string, nullable: true}
|
||||
channelName: {type: string, nullable: true}
|
||||
requirementId: {type: integer, format: int64}
|
||||
containerType: {type: string}
|
||||
requirementQuantity: {type: integer, minimum: 1}
|
||||
containerBookingId: {type: integer, format: int64}
|
||||
cabinetNo: {type: string}
|
||||
containerNo: {type: string, nullable: true, description: Optional until PICKUP or preload correction; absence does not make the candidate unavailable}
|
||||
loadingPlanId: {type: integer, format: int64, nullable: true}
|
||||
loadingPlanNo: {type: string, nullable: true}
|
||||
loadingPlanStatus: {type: string, nullable: true}
|
||||
bookingDocumentReady: {type: boolean, enum: [true]}
|
||||
DetailEnvelope:
|
||||
type: object
|
||||
@@ -225,16 +216,13 @@ components:
|
||||
properties:
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
reason: {type: string, maxLength: 500}
|
||||
containerNo: {type: string, maxLength: 32, nullable: true, description: Optional and only valid for PICKUP. Omission does not block PICKUP; when supplied it is normalized and validated as unique ISO 6346 before being written atomically. A different value from an existing number is rejected and must use preload correction.}
|
||||
SaveRequest:
|
||||
type: object
|
||||
required: [oceanBookingId, containerBookingId, carrierId, plannedLoadingAt, expectedVersion]
|
||||
required: [oceanBookingId, carrierId, plannedLoadingAt, expectedVersion]
|
||||
properties:
|
||||
outboundOrderId: {type: integer, format: int64, minimum: 1, nullable: true}
|
||||
oceanBookingId: {type: integer, format: int64, minimum: 1, description: Required RELEASED booking; must own an active BOOKING_CONFIRMATION or BILL_OF_LADING file.}
|
||||
containerBookingId: {type: integer, format: int64, minimum: 1, description: Required ACTIVE internal container already allocated to oceanBookingId. Its real ISO 6346 number may remain empty until PICKUP.}
|
||||
loadingPlanId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional loading-plan association. It may be supplied only with containerBookingId and must be that container's current plan.}
|
||||
warehouseId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional warehouse scope. Linked booking/container warehouse facts must agree; when omitted the linked fact is frozen if available.}
|
||||
oceanBookingId: {type: integer, format: int64, minimum: 1, description: Required RELEASED booking; must have a bill of lading and own an active BOOKING_CONFIRMATION or BILL_OF_LADING file.}
|
||||
warehouseId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional consistency hint. The persisted warehouse is frozen from the selected booking.}
|
||||
carrierId: {type: integer, format: int64, minimum: 1, description: Required ACTIVE Warehouse supplier ID validated as TRUCKING.}
|
||||
carrierName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint only; the persisted snapshot comes from Warehouse supplier master data.}
|
||||
billOfLadingNo: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint; the persisted snapshot is always frozen from the selected RELEASED booking.}
|
||||
@@ -245,15 +233,11 @@ components:
|
||||
DetailStableReferences:
|
||||
type: object
|
||||
required: [appointmentNo, carrierId, carrierName, attachmentCount]
|
||||
description: Stable IDs plus server-resolved saved resource names returned by appointment detail. Ocean booking and container associations are always present.
|
||||
description: Stable booking and supplier references returned by appointment detail. Container identities are intentionally absent.
|
||||
properties:
|
||||
appointmentNo: {type: string, pattern: '^TA-[0-9]{6}-[0-9]{4}$', example: TA-260818-0001, description: 派车单号;按北京时间创建日逐日编号}
|
||||
oceanBookingId: {type: integer, format: int64, nullable: true}
|
||||
oceanBookingNo: {type: string, nullable: true, pattern: '^OB-[0-9]{6}-[0-9]{4}$', example: OB-260818-0001}
|
||||
containerBookingId: {type: integer, format: int64, nullable: true}
|
||||
cabinetNo: {type: string, nullable: true, pattern: '^CAB-[0-9]{6}-[0-9]{4}$', example: CAB-260818-0001}
|
||||
containerNo: {type: string, nullable: true}
|
||||
loadingPlanId: {type: integer, format: int64, nullable: true}
|
||||
warehouseId: {type: integer, format: int64, nullable: true}
|
||||
warehouseName: {type: string, nullable: true}
|
||||
shippingCompanyId: {type: integer, format: int64, nullable: true}
|
||||
|
||||
Reference in New Issue
Block a user