refactor(firstleg): remove customer scope from trucking

This commit is contained in:
2026-09-01 09:51:59 +08:00
parent 936412f351
commit 249b182e7f
17 changed files with 357 additions and 463 deletions
@@ -7,8 +7,6 @@ CREATE TABLE IF NOT EXISTS fl_truck_appointment (
container_booking_id BIGINT UNSIGNED NULL COMMENT '关联柜次逻辑ID;拖车完成供装柜门禁校验',
loading_plan_id BIGINT UNSIGNED NULL COMMENT '关联装柜计划逻辑ID',
warehouse_id BIGINT UNSIGNED NULL COMMENT '预约到仓的国内收货仓逻辑ID',
customer_id BIGINT UNSIGNED NULL COMMENT '客户ID和数据隔离键',
customer_name VARCHAR(128) NULL COMMENT '客户名称快照',
carrier_id BIGINT UNSIGNED NULL COMMENT '拖车服务商逻辑ID',
carrier_name VARCHAR(128) NULL COMMENT '拖车服务商名称快照',
bill_of_lading_no VARCHAR(128) NULL COMMENT '派车提单号快照;关联RELEASED订舱时由订舱事实源冻结',
@@ -28,23 +26,24 @@ CREATE TABLE IF NOT EXISTS fl_truck_appointment (
gross_weight_kg DECIMAL(18,3) NULL COMMENT '预约货物毛重,单位千克',
volume_cbm DECIMAL(18,4) NULL COMMENT '预约货物体积,单位立方米',
pallet_count INT UNSIGNED NULL COMMENT '预约托盘数量',
po_no VARCHAR(255) NULL COMMENT '关联采购单号或客户PO号',
po_no VARCHAR(255) NULL COMMENT '关联采购单号',
planned_loading_at DATETIME(3) NULL COMMENT '预约到仓装柜时间',
actual_arrived_warehouse_at DATETIME(3) NULL COMMENT '车辆实际到达收货仓时间',
actual_loading_at DATETIME(3) NULL COMMENT '实际开始装柜时间',
actual_departed_at DATETIME(3) NULL COMMENT '车辆实际离仓时间;DEPARTED后供LOADED门禁使用',
transit_minutes INT UNSIGNED NULL COMMENT '从到仓到离仓的运输作业耗时,单位分钟',
payable_amount DECIMAL(18,2) NULL COMMENT '应付拖车服务商金额;币种由currency指定',
receivable_amount DECIMAL(18,2) NULL COMMENT '向客户收取的拖车金额;币种由currency指定',
receivable_amount DECIMAL(18,2) NULL COMMENT '拖车业务应收金额;币种由currency指定',
currency VARCHAR(8) NOT NULL DEFAULT 'RMB' COMMENT '金额币种;首期默认RMB',
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 '非终态预约的柜次唯一键',
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),
KEY idx_truck_appointment_status(status,updated_at), KEY idx_truck_appointment_container(container_booking_id), KEY idx_truck_appointment_customer(customer_id),
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),
CONSTRAINT chk_truck_appointment_status CHECK(status IN ('DRAFT','PENDING_DISPATCH','DISPATCHED','PICKUP_IN_PROGRESS','ARRIVED_WAREHOUSE','LOADING','DEPARTED','COMPLETED','RESCHEDULED','EXCEPTION','CANCELLED'))
) ENGINE=InnoDB COMMENT='三期拖车预约派车聚合';
CREATE TABLE IF NOT EXISTS fl_truck_appointment_history (
@@ -54,7 +53,7 @@ CREATE TABLE IF NOT EXISTS fl_truck_appointment_history (
) ENGINE=InnoDB COMMENT='拖车状态与改约历史';
CREATE TABLE IF NOT EXISTS fl_truck_appointment_attachment (
id BIGINT UNSIGNED NOT NULL COMMENT '拖车附件关系ID', appointment_id BIGINT UNSIGNED NOT NULL COMMENT '所属拖车预约ID', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件逻辑ID', attachment_type VARCHAR(32) NOT NULL COMMENT '附件类型:DISPATCH_ORDER派车单、DRIVER_DOCUMENT司机证件、VEHICLE_DOCUMENT车辆证件、OTHER其他;订舱确认与提单归订舱聚合',
file_name VARCHAR(255) NULL COMMENT '上传时文件名快照', file_hash CHAR(64) NULL COMMENT '文件内容SHA-256摘要', visibility VARCHAR(24) NOT NULL DEFAULT 'INTERNAL' COMMENT '可见范围:INTERNAL仅平台内部、CLIENT客户可见', uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID',
file_name VARCHAR(255) NULL COMMENT '上传时文件名快照', file_hash CHAR(64) NULL COMMENT '文件内容SHA-256摘要', visibility VARCHAR(24) NOT NULL DEFAULT 'PLATFORM' COMMENT '可见范围:PLATFORM仅平台内部', uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID',
uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '附件上传时间', PRIMARY KEY(id), UNIQUE KEY uk_truck_attachment(appointment_id,file_id), KEY idx_truck_attachment(appointment_id), CONSTRAINT chk_truck_attachment_type CHECK(attachment_type IN ('DISPATCH_ORDER','DRIVER_DOCUMENT','VEHICLE_DOCUMENT','OTHER'))
) ENGINE=InnoDB COMMENT='拖车附件文件引用';
CREATE TABLE IF NOT EXISTS fl_truck_appointment_operation (
@@ -100,8 +99,22 @@ CALL phase3_truck_add_column(
'actual_departed_at',
'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'
);
DROP PROCEDURE IF EXISTS phase3_truck_add_column;
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_container' AND non_unique=0),
'SELECT 1',
'ALTER TABLE fl_truck_appointment ADD UNIQUE KEY uk_truck_active_container(active_container_booking_id)'
);
PREPARE phase3_truck_active_stmt FROM @phase3_truck_active_index;
EXECUTE phase3_truck_active_stmt;
DEALLOCATE PREPARE phase3_truck_active_stmt;
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_truck_appointment_permission_ids_available;
DELIMITER $$
@@ -7,8 +7,6 @@ paths:
/api/platform/v1/truck-appointments:
get:
summary: Query truck appointments
parameters:
- {name: customerId, in: query, description: 'Filter by customer', schema: {type: string, pattern: '^[1-9][0-9]*$'}}
responses:
'200':
description: Paged truck appointments with stable booking/container snapshots and attachment count
@@ -93,8 +91,8 @@ paths:
responses:
'200': {description: 'Attachment references', content: {application/json: {schema: {type: array, items: {$ref: '#/components/schemas/Attachment'}}}}}
post:
summary: Reference a file already owned by the appointment customer and business scope
description: 'Requires platform:firstleg:truck-appointment:edit. The file must belong to customerId + TRUCK_APPOINTMENT_ATTACHMENT + appointmentId.'
summary: Reference a platform-owned file already scoped to this appointment
description: 'Requires platform:firstleg:truck-appointment:edit. The file must belong to platform scope + TRUCK_APPOINTMENT_ATTACHMENT + appointmentId.'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/AttachmentRequest'}}}}
responses:
'200': {description: 'Created attachment reference', content: {application/json: {schema: {$ref: '#/components/schemas/Attachment'}}}}
@@ -178,15 +176,13 @@ components:
reason: {type: string, maxLength: 500}
SaveRequest:
type: object
required: [oceanBookingId, containerBookingId, customerId, carrierId, plannedLoadingAt, expectedVersion]
required: [oceanBookingId, containerBookingId, 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 container already allocated to oceanBookingId and carrying a valid unique ISO 6346 container number.}
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.}
customerId: {type: integer, format: int64, minimum: 1, description: Required ACTIVE IAM customer used for isolation and attachment ownership.}
customerName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint only; persisted value is resolved from IAM customer master data.}
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.}
@@ -194,7 +190,7 @@ components:
expectedVersion: {type: integer, minimum: 0, description: Must be 0 on create; current version on edit.}
DetailStableReferences:
type: object
required: [appointmentNo, customerId, customerName, carrierId, carrierName, attachmentCount]
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.
properties:
appointmentNo: {type: string, pattern: '^TA-[0-9]{6}-[0-9]{4}$', example: TA-260818-0001, description: 派车单号;按北京时间创建日逐日编号}
@@ -206,8 +202,6 @@ components:
loadingPlanId: {type: integer, format: int64, nullable: true}
warehouseId: {type: integer, format: int64, nullable: true}
warehouseName: {type: string, nullable: true}
customerId: {type: integer, format: int64}
customerName: {type: string, description: Frozen from ACTIVE IAM customer master data; request input cannot override it}
shippingCompanyId: {type: integer, format: int64, nullable: true}
shippingCompanyName: {type: string, nullable: true}
bookingAgentId: {type: integer, format: int64, nullable: true}