diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index 07b97973b..47161a1ca 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -18131,8 +18131,7 @@ CREATE TABLE IF NOT EXISTS fl_terminal_drayage ( KEY idx_terminal_drayage_warning(actual_empty_return_at,estimated_empty_return_at), CONSTRAINT fk_terminal_drayage_container FOREIGN KEY(container_booking_id) REFERENCES fl_container_booking(id), CONSTRAINT chk_terminal_drayage_times CHECK((actual_pickup_at IS NULL OR pickup_appointment_at IS NOT NULL) - AND (actual_empty_return_at IS NULL OR actual_pickup_at IS NOT NULL) - AND (actual_empty_return_at IS NULL OR actual_empty_return_at >= actual_pickup_at)) + AND (actual_empty_return_at IS NULL OR actual_pickup_at IS NULL OR actual_empty_return_at >= actual_pickup_at)) ) ENGINE=InnoDB COMMENT='目的港货柜级码头拖车与还空事实'; CREATE TABLE IF NOT EXISTS fl_terminal_drayage_history ( id BIGINT UNSIGNED NOT NULL COMMENT '历史ID', drayage_id BIGINT UNSIGNED NOT NULL COMMENT '码头拖车聚合ID', @@ -18158,6 +18157,10 @@ CREATE TABLE IF NOT EXISTS fl_terminal_drayage_operation ( -- MySQL DDL 会隐式提交;如果前次执行在末段校验失败,重跑时需把已创建表的列注释收敛到当前基线。 ALTER TABLE fl_terminal_drayage + DROP CHECK chk_terminal_drayage_times, + ADD CONSTRAINT chk_terminal_drayage_times CHECK( + (actual_pickup_at IS NULL OR pickup_appointment_at IS NOT NULL) AND + (actual_empty_return_at IS NULL OR actual_pickup_at IS NULL OR actual_empty_return_at >= actual_pickup_at)), MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '目的港码头拖车聚合ID', MODIFY COLUMN container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '唯一归属物理货柜ID', MODIFY COLUMN fleet_supplier_id BIGINT UNSIGNED NULL COMMENT 'Warehouse ACTIVE/TRUCKING供应商ID快照', diff --git a/firstleg/pom.xml b/firstleg/pom.xml index 1a35216ef..98dc43916 100644 --- a/firstleg/pom.xml +++ b/firstleg/pom.xml @@ -5,6 +5,7 @@ com.baiyue.firstlegbaiyue-firstleg-platform1.0.0-SNAPSHOT../pom.xml firstleg-service + org.springframework.bootspring-boot-starter-mail com.baiyue.sharedbaiyue-common-core${project.version} com.baiyue.sharedcommon-event-contract${project.version} com.baiyue.sharedbaiyue-common-audit${project.version} diff --git a/firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseSupplierClient.java b/firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseSupplierClient.java index 7559f7e14..a0874578a 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseSupplierClient.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseSupplierClient.java @@ -24,4 +24,10 @@ public interface WarehouseSupplierClient { record Snapshot(long supplierId, String name, String category, String status, List carrierCodes) {} + + @GetMapping("/{supplierId}/trucking-contact") + ApiResponse truckingContact(@RequestHeader("X-Internal-Service-Token") String token, + @PathVariable long supplierId, @RequestParam boolean requireActive); + + record TruckingContact(long supplierId, String name, String email) {} } diff --git a/firstleg/src/main/resources/db/phase3-terminal-drayage-schema.sql b/firstleg/src/main/resources/db/phase3-terminal-drayage-schema.sql index f68365ccd..4b779b7fa 100644 --- a/firstleg/src/main/resources/db/phase3-terminal-drayage-schema.sql +++ b/firstleg/src/main/resources/db/phase3-terminal-drayage-schema.sql @@ -23,8 +23,7 @@ CREATE TABLE IF NOT EXISTS fl_terminal_drayage ( CONSTRAINT fk_terminal_drayage_container FOREIGN KEY(container_booking_id) REFERENCES fl_container_booking(id), CONSTRAINT chk_terminal_drayage_times CHECK( (actual_pickup_at IS NULL OR pickup_appointment_at IS NOT NULL) AND - (actual_empty_return_at IS NULL OR actual_pickup_at IS NOT NULL) AND - (actual_empty_return_at IS NULL OR actual_empty_return_at >= actual_pickup_at)) + (actual_empty_return_at IS NULL OR actual_pickup_at IS NULL OR actual_empty_return_at >= actual_pickup_at)) ) ENGINE=InnoDB COMMENT='目的港货柜级码头拖车与还空事实'; CREATE TABLE IF NOT EXISTS fl_terminal_drayage_history ( diff --git a/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java index fd6635a2b..cac6e7aaf 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java @@ -8,6 +8,20 @@ import java.nio.file.Path; import static org.assertj.core.api.Assertions.assertThat; class TerminalDrayageSqlContractTest { + @Test + void bothSqlEntrypointsAllowReturnWithoutPickupAndRetainKnownTimeOrdering() throws Exception { + for (Path path : java.util.List.of( + Path.of("src/main/resources/db/phase3-terminal-drayage-schema.sql"), + Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql"))) { + String sql = Files.readString(path); + assertThat(sql).contains("actual_empty_return_at IS NULL OR actual_pickup_at IS NULL OR actual_empty_return_at >= actual_pickup_at") + .doesNotContain("actual_empty_return_at IS NULL OR actual_pickup_at IS NOT NULL"); + } + String release = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")); + assertThat(release).contains("DROP CHECK chk_terminal_drayage_times", + "ADD CONSTRAINT chk_terminal_drayage_times CHECK("); + } + @Test void schemaKeepsOneAggregatePerContainerAndImmutableAudit() throws Exception { String sql = Files.readString(Path.of("src/main/resources/db/phase3-terminal-drayage-schema.sql")); diff --git a/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalSupplierController.java b/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalSupplierController.java index be193cc9c..78cfb51e8 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalSupplierController.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalSupplierController.java @@ -37,5 +37,17 @@ public class InternalSupplierController { return ApiResponse.success(service.internalBookingCandidates(carrierCode)); } + public record TruckingContact(long supplierId, String name, String email) {} + + @GetMapping("/{supplierId}/trucking-contact") + public ApiResponse truckingContact( + @RequestHeader(value="X-Internal-Service-Token", required=false) String token, + @PathVariable long supplierId, @RequestParam(defaultValue="true") boolean requireActive) { + requireToken(token); + service.internalSnapshot(supplierId, requireActive, "TRUCKING"); + var supplier = service.detail(supplierId); + return ApiResponse.success(new TruckingContact(supplierId, supplier.name(), supplier.email())); + } + private void requireToken(String token) { if (token == null || !MessageDigest.isEqual(expected, token.getBytes(StandardCharsets.UTF_8))) throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效"); } }