diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index 948e216fe..c00516ef8 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -9197,6 +9197,17 @@ CREATE TABLE IF NOT EXISTS fl_ocean_booking_container_allocation ( CONSTRAINT fk_ocean_allocation_container FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id) ) ENGINE=InnoDB COMMENT='订舱需求与现有柜次的唯一分配关系'; +CREATE TABLE IF NOT EXISTS fl_ocean_booking_document ( + id BIGINT UNSIGNED NOT NULL COMMENT '订舱文件关联ID', ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '所属海运订舱ID', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件逻辑ID', + document_type VARCHAR(32) NOT NULL COMMENT '文件类型:BOOKING_CONFIRMATION订舱确认、BILL_OF_LADING提单', file_name VARCHAR(255) NOT NULL COMMENT 'File返回的原始文件名快照', file_hash CHAR(64) NULL COMMENT 'File返回的SHA-256快照', + size_bytes BIGINT UNSIGNED NULL COMMENT 'File返回的文件大小快照', content_type VARCHAR(128) NULL COMMENT 'File返回的内容类型快照', valid TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否为有效文件关联', + uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传或关联操作人ID', uploaded_by_name VARCHAR(64) NULL COMMENT '上传或关联操作人姓名快照', uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上传或关联时间', + invalidated_by BIGINT UNSIGNED NULL COMMENT '作废操作人ID', invalidated_by_name VARCHAR(64) NULL COMMENT '作废操作人姓名快照', invalidated_at DATETIME(3) NULL COMMENT '作废时间', + PRIMARY KEY(id), KEY idx_ocean_document_booking(ocean_booking_id,valid,uploaded_at,id), KEY idx_ocean_document_file(file_id), + CONSTRAINT fk_ocean_document_booking FOREIGN KEY(ocean_booking_id) REFERENCES fl_ocean_booking(id) ON DELETE CASCADE, + CONSTRAINT chk_ocean_document_type CHECK(document_type IN ('BOOKING_CONFIRMATION','BILL_OF_LADING')), CONSTRAINT chk_ocean_document_valid CHECK(valid IN (0,1)) +) ENGINE=InnoDB COMMENT='订舱聚合拥有的订舱确认与提单文件关联'; + CREATE TABLE IF NOT EXISTS fl_ocean_booking_status_history ( id BIGINT UNSIGNED NOT NULL COMMENT '订舱状态历史ID', ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '所属海运订舱ID', @@ -9292,6 +9303,21 @@ CREATE TRIGGER trg_ocean_history_before_delete BEFORE DELETE ON fl_ocean_booking BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱状态历史不可删除'; END$$ DELIMITER ; +DROP TRIGGER IF EXISTS trg_ocean_document_before_insert; +DROP TRIGGER IF EXISTS trg_ocean_document_before_update; +DROP TRIGGER IF EXISTS trg_ocean_document_before_delete; +DELIMITER $$ +CREATE TRIGGER trg_ocean_document_before_insert BEFORE INSERT ON fl_ocean_booking_document FOR EACH ROW +BEGIN IF (SELECT status FROM fl_ocean_booking WHERE id=NEW.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='已放舱订舱文件永久只读'; END IF; END$$ +CREATE TRIGGER trg_ocean_document_before_update BEFORE UPDATE ON fl_ocean_booking_document FOR EACH ROW +BEGIN + IF (SELECT status FROM fl_ocean_booking WHERE id=OLD.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='已放舱订舱文件永久只读'; END IF; + IF NEW.ocean_booking_id <> OLD.ocean_booking_id OR NEW.file_id <> OLD.file_id OR NEW.document_type <> OLD.document_type OR NEW.uploaded_by <> OLD.uploaded_by OR NEW.uploaded_at <> OLD.uploaded_at OR NEW.valid > OLD.valid THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱文件只允许在草稿期作废'; END IF; +END$$ +CREATE TRIGGER trg_ocean_document_before_delete BEFORE DELETE ON fl_ocean_booking_document FOR EACH ROW +BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱文件关联不可物理删除'; END$$ +DELIMITER ; + USE baiyue_iam; DELETE rp FROM iam_role_permission rp WHERE rp.permission_id IN (5102141,5102143,5102144,5102145); DELETE FROM iam_permission WHERE id IN (5102141,5102143,5102144,5102145); @@ -9364,9 +9390,9 @@ CREATE TABLE IF NOT EXISTS fl_truck_appointment_history ( operator_name VARCHAR(64) NULL COMMENT '操作人姓名快照', request_id VARCHAR(128) NULL COMMENT '请求链路中的请求ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '历史记录创建时间', PRIMARY KEY(id), KEY idx_truck_history(appointment_id,revision) ) 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 '附件类型:BILL_OF_LADING提单、DISPATCH_ORDER派车单、DRIVER_DOCUMENT司机证件、VEHICLE_DOCUMENT车辆证件、OTHER其他', + 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', - 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 ('BILL_OF_LADING','DISPATCH_ORDER','DRIVER_DOCUMENT','VEHICLE_DOCUMENT','OTHER')) + 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 ( id BIGINT UNSIGNED NOT NULL COMMENT '拖车写操作幂等记录ID', appointment_id BIGINT UNSIGNED NOT NULL COMMENT '所属拖车预约ID', idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端写操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '规范化请求内容SHA-256摘要;用于识别同键不同请求', diff --git a/firstleg/src/main/java/com/baiyue/firstleg/client/OceanBookingFileClient.java b/firstleg/src/main/java/com/baiyue/firstleg/client/OceanBookingFileClient.java new file mode 100644 index 000000000..6ffd2ab4c --- /dev/null +++ b/firstleg/src/main/java/com/baiyue/firstleg/client/OceanBookingFileClient.java @@ -0,0 +1,40 @@ +package com.baiyue.firstleg.client; + +import com.baiyue.common.core.api.ApiResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.http.ResponseEntity; + +@FeignClient(name = "file-service", contextId = "oceanBookingFileClient", + url = "${baiyue.clients.file.url:}", path = "/internal/file/v1/business-files") +public interface OceanBookingFileClient { + @PostMapping(value = "/{tenantId}/{businessType}/{businessId}", consumes = "multipart/form-data") + ApiResponse upload(@RequestHeader("X-Internal-Service-Token") String token, + @RequestHeader("X-Operator-Id") long operatorId, + @PathVariable long tenantId, + @PathVariable String businessType, + @PathVariable String businessId, + @RequestPart("file") MultipartFile file); + + @GetMapping("/{tenantId}/{businessType}/{businessId}/{fileId}") + ApiResponse metadata(@RequestHeader("X-Internal-Service-Token") String token, + @PathVariable long tenantId, + @PathVariable String businessType, + @PathVariable String businessId, + @PathVariable long fileId); + + @GetMapping("/{tenantId}/{businessType}/{businessId}/{fileId}/content") + ResponseEntity download(@RequestHeader("X-Internal-Service-Token") String token, + @PathVariable long tenantId, + @PathVariable String businessType, + @PathVariable String businessId, + @PathVariable long fileId); + + record Metadata(Long fileId, Long tenantId, String businessType, String businessId, + String fileName, String sha256, String contentType, Long sizeBytes) {} +} diff --git a/firstleg/src/main/java/com/baiyue/firstleg/client/PrintPdfClient.java b/firstleg/src/main/java/com/baiyue/firstleg/client/PrintPdfClient.java index c15672df4..832b154ba 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/client/PrintPdfClient.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/client/PrintPdfClient.java @@ -8,8 +8,9 @@ import org.springframework.web.bind.annotation.RequestHeader; import java.math.BigDecimal; import java.util.List; +import java.util.Map; -/** Print Service contract for stateless inbound carton label rendering. */ +/** Print Service contract for stateless Firstleg PDF rendering. */ @FeignClient(name = "print-service", contextId = "firstlegPrintPdfClient", url = "${baiyue.clients.print.url:}", path = "/internal/print/v1/pdfs") public interface PrintPdfClient { @@ -19,6 +20,12 @@ public interface PrintPdfClient { @RequestHeader(value = "X-Request-Id", required = false) String requestId, @RequestBody InboundMarkPdfRequest request); + @PostMapping(value = "/freight-a4-documents", consumes = "application/json") + ResponseEntity generateFreightA4Documents( + @RequestHeader("X-Internal-Service-Token") String token, + @RequestHeader(value = "X-Request-Id", required = false) String requestId, + @RequestBody FreightA4PdfRequest request); + record InboundMarkPdfRequest(String templateCode, PageSpec pageSpec, String downloadFileName, List