This commit is contained in:
2026-08-14 10:57:05 +08:00
parent 66089f9b99
commit 91e781b3ff
16 changed files with 219 additions and 119 deletions
@@ -10,7 +10,9 @@ import java.util.List;
@Insert("INSERT INTO fl_container_exception_history(id,container_booking_id,action,reason,operator_id,operator_name,operation_id,request_hash,occurred_at) VALUES(#{id},#{bookingId},#{action},#{reason},#{operatorId},#{operatorName},#{operationId},#{requestHash},CURRENT_TIMESTAMP(3))") int insertException(@Param("id") long id,@Param("bookingId") long bookingId,@Param("action") String action,@Param("reason") String reason,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName,@Param("operationId") String operationId,@Param("requestHash") String requestHash);
@Select("SELECT action,reason,operator_id operatorId,operator_name operatorName,occurred_at occurredAt FROM fl_container_exception_history WHERE container_booking_id=#{id} ORDER BY id DESC LIMIT 1") ExceptionRow latestException(@Param("id") long id);
@Select("SELECT id,action,reason,operator_id operatorId,operator_name operatorName,occurred_at occurredAt,request_hash requestHash FROM fl_container_exception_history WHERE container_booking_id=#{bookingId} AND operator_id=#{operatorId} AND operation_id=#{operationId} LIMIT 1") ExceptionRow exceptionOperation(@Param("bookingId") long bookingId,@Param("operatorId") long operatorId,@Param("operationId") String operationId);
@Select("SELECT * FROM fl_loading_pick_task WHERE container_booking_id=#{id} ORDER BY id DESC LIMIT 1") PickTaskRow pickTask(@Param("id") long id);
@Select("SELECT id,loading_plan_id AS loadingPlanId,container_booking_id AS containerBookingId,"
+ "task_status AS taskStatus,version FROM fl_loading_pick_task "
+ "WHERE container_booking_id=#{id} ORDER BY id DESC LIMIT 1") PickTaskRow pickTask(@Param("id") long id);
@Select("SELECT COUNT(*) FROM fl_loading_pick_item WHERE pick_task_id=#{id}") int itemCount(@Param("id") long id);
@Select("SELECT COUNT(*) FROM fl_loading_pick_item WHERE pick_task_id=#{id} AND item_status IN ('PICKED','PARTIAL','SKIPPED')") int completedItemCount(@Param("id") long id);
@Select("SELECT COUNT(*) FROM fl_loading_pick_item WHERE pick_task_id=#{id} AND item_status NOT IN ('PICKED','PARTIAL','SKIPPED')") int unresolvedItemCount(@Param("id") long id);
@@ -90,17 +90,13 @@ BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status
FROM fl_loading_plan WHERE container_booking_id = OLD.id;
IF current_plan_status = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜全部只读,不允许修改';
END IF;
IF current_plan_status = 'LOADING' AND (
IF current_plan_status IN ('LOADING', 'COMPLETED') AND (
NOT (OLD.id <=> NEW.id) OR NOT (OLD.booking_no <=> NEW.booking_no)
OR NOT (OLD.cabinet_no <=> NEW.cabinet_no)
OR NOT (OLD.channel_code <=> NEW.channel_code) OR NOT (OLD.channel_name <=> NEW.channel_name)
OR NOT (OLD.ocean_booking_id <=> NEW.ocean_booking_id)
OR NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id)
OR NOT (OLD.warehouse_id <=> NEW.warehouse_id) OR NOT (OLD.warehouse_name <=> NEW.warehouse_name)
OR NOT (OLD.bill_of_lading_no <=> NEW.bill_of_lading_no) OR NOT (OLD.vessel_name <=> NEW.vessel_name)
OR NOT (OLD.container_type <=> NEW.container_type)
OR NOT (OLD.shipping_company_id <=> NEW.shipping_company_id) OR NOT (OLD.shipping_company_name <=> NEW.shipping_company_name)
OR NOT (OLD.departure_port_code <=> NEW.departure_port_code) OR NOT (OLD.departure_port_name <=> NEW.departure_port_name)
@@ -108,13 +104,13 @@ BEGIN
OR NOT (OLD.destination_warehouse_code <=> NEW.destination_warehouse_code) OR NOT (OLD.destination_warehouse_name <=> NEW.destination_warehouse_name)
OR NOT (OLD.capacity_cbm <=> NEW.capacity_cbm) OR NOT (OLD.max_weight_kg <=> NEW.max_weight_kg)
OR NOT (OLD.terminal_trucking_company <=> NEW.terminal_trucking_company) OR NOT (OLD.loading_at <=> NEW.loading_at)
OR NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_at) OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at)
OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at)
OR NOT (OLD.estimated_port_arrival_at <=> NEW.estimated_port_arrival_at) OR NOT (OLD.actual_port_arrival_at <=> NEW.actual_port_arrival_at)
OR NOT (OLD.estimated_warehouse_arrival_at <=> NEW.estimated_warehouse_arrival_at) OR NOT (OLD.actual_warehouse_arrival_at <=> NEW.actual_warehouse_arrival_at)
OR NOT (OLD.created_by <=> NEW.created_by) OR NOT (OLD.created_by_name <=> NEW.created_by_name)
OR NOT (OLD.created_at <=> NEW.created_at)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜中仅允许修改集装箱号与航次';
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜作业开始后仅允许修改集装箱号与放舱运输数据';
END IF;
END$$
DELIMITER ;
@@ -28,23 +28,19 @@ PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
ALTER TABLE fl_container_document ADD CONSTRAINT chk_container_document_type
CHECK (document_type IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER'));
-- Keep the container master immutable after loading; only the two document rows remain operable.
-- Keep container identity/loading facts immutable while allowing release transport data to arrive later.
DROP TRIGGER IF EXISTS trg_container_booking_before_update;
DELIMITER $$
CREATE TRIGGER trg_container_booking_before_update BEFORE UPDATE ON fl_container_booking FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan WHERE container_booking_id=OLD.id;
IF current_plan_status='COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜完成后货柜全部只读,不允许修改';
END IF;
IF current_plan_status='LOADING' AND (
IF current_plan_status IN ('LOADING','COMPLETED') AND (
NOT (OLD.id <=> NEW.id) OR NOT (OLD.booking_no <=> NEW.booking_no)
OR NOT (OLD.cabinet_no <=> NEW.cabinet_no) OR NOT (OLD.channel_code <=> NEW.channel_code)
OR NOT (OLD.channel_name <=> NEW.channel_name) OR NOT (OLD.ocean_booking_id <=> NEW.ocean_booking_id)
OR NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id)
OR NOT (OLD.warehouse_id <=> NEW.warehouse_id) OR NOT (OLD.warehouse_name <=> NEW.warehouse_name)
OR NOT (OLD.bill_of_lading_no <=> NEW.bill_of_lading_no) OR NOT (OLD.vessel_name <=> NEW.vessel_name)
OR NOT (OLD.container_type <=> NEW.container_type) OR NOT (OLD.shipping_company_id <=> NEW.shipping_company_id)
OR NOT (OLD.shipping_company_name <=> NEW.shipping_company_name)
OR NOT (OLD.departure_port_code <=> NEW.departure_port_code) OR NOT (OLD.departure_port_name <=> NEW.departure_port_name)
@@ -53,14 +49,14 @@ BEGIN
OR NOT (OLD.destination_warehouse_name <=> NEW.destination_warehouse_name)
OR NOT (OLD.capacity_cbm <=> NEW.capacity_cbm) OR NOT (OLD.max_weight_kg <=> NEW.max_weight_kg)
OR NOT (OLD.terminal_trucking_company <=> NEW.terminal_trucking_company) OR NOT (OLD.loading_at <=> NEW.loading_at)
OR NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_at) OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at)
OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at)
OR NOT (OLD.estimated_port_arrival_at <=> NEW.estimated_port_arrival_at)
OR NOT (OLD.actual_port_arrival_at <=> NEW.actual_port_arrival_at)
OR NOT (OLD.estimated_warehouse_arrival_at <=> NEW.estimated_warehouse_arrival_at)
OR NOT (OLD.actual_warehouse_arrival_at <=> NEW.actual_warehouse_arrival_at)
OR NOT (OLD.created_by <=> NEW.created_by) OR NOT (OLD.created_by_name <=> NEW.created_by_name)
OR NOT (OLD.created_at <=> NEW.created_at)) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜中仅允许修改集装箱号与航次';
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜作业开始后仅允许修改集装箱号与放舱运输数据';
END IF;
END$$
DELIMITER ;
@@ -584,10 +584,7 @@ BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status
FROM fl_loading_plan WHERE container_booking_id = OLD.id;
IF current_plan_status = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜全部只读,不允许修改';
END IF;
IF current_plan_status = 'LOADING' AND (
IF current_plan_status IN ('LOADING', 'COMPLETED') AND (
NOT (OLD.id <=> NEW.id) OR NOT (OLD.booking_no <=> NEW.booking_no)
OR NOT (OLD.cabinet_no <=> NEW.cabinet_no)
OR NOT (OLD.channel_code <=> NEW.channel_code)
@@ -596,8 +593,6 @@ BEGIN
OR NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id)
OR NOT (OLD.warehouse_id <=> NEW.warehouse_id)
OR NOT (OLD.warehouse_name <=> NEW.warehouse_name)
OR NOT (OLD.bill_of_lading_no <=> NEW.bill_of_lading_no)
OR NOT (OLD.vessel_name <=> NEW.vessel_name)
OR NOT (OLD.container_type <=> NEW.container_type)
OR NOT (OLD.shipping_company_id <=> NEW.shipping_company_id)
OR NOT (OLD.shipping_company_name <=> NEW.shipping_company_name)
@@ -611,7 +606,6 @@ BEGIN
OR NOT (OLD.max_weight_kg <=> NEW.max_weight_kg)
OR NOT (OLD.terminal_trucking_company <=> NEW.terminal_trucking_company)
OR NOT (OLD.loading_at <=> NEW.loading_at)
OR NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_at)
OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at)
OR NOT (OLD.estimated_port_arrival_at <=> NEW.estimated_port_arrival_at)
OR NOT (OLD.actual_port_arrival_at <=> NEW.actual_port_arrival_at)
@@ -622,7 +616,7 @@ BEGIN
OR NOT (OLD.created_at <=> NEW.created_at)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '装柜中仅允许修改集装箱号与航次';
SET MESSAGE_TEXT = '装柜作业开始后仅允许修改集装箱号与放舱运输数据';
END IF;
END$$
DELIMITER ;
@@ -1,6 +1,8 @@
package com.baiyue.firstleg.repository;
import org.junit.jupiter.api.Test;
import org.apache.ibatis.annotations.Select;
import com.baiyue.firstleg.mapper.ContainerOverviewMapper;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
@@ -10,6 +12,14 @@ import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class ContainerOverviewSqlContractTest {
@Test void pdaSummarySelectsRecordFieldsExplicitlyIncludingTaskVersion() throws Exception {
String sql = String.join(" ", ContainerOverviewMapper.class.getDeclaredMethod("pickTask", long.class)
.getAnnotation(Select.class).value());
assertThat(sql).contains("loading_plan_id AS loadingPlanId",
"container_booking_id AS containerBookingId", "task_status AS taskStatus", "version")
.doesNotContain("SELECT *");
}
@Test void developmentAndUnifiedSchemasContainImmutableIdempotentExceptionHistory() throws Exception {
String development = new ClassPathResource("db/phase3-smart-loading-schema.sql")
.getContentAsString(StandardCharsets.UTF_8);
@@ -60,12 +60,16 @@ class Phase3ChannelContainerMigrationContractTest {
.contains("@phase3_channel_message = MESSAGE_TEXT")
.contains("渠道柜迁移失败:货柜只读触发器未恢复")
.contains("渠道柜迁移失败:PDA任务只读触发器未恢复")
.contains("装柜完成后货柜全部只读,不允许修改")
.contains("current_plan_status IN ('LOADING', 'COMPLETED')")
.contains("装柜作业开始后仅允许修改集装箱号与放舱运输数据")
.contains("已完成PDA拣货任务不可修改")
.contains("NOT (OLD.channel_code <=> NEW.channel_code)")
.contains("NOT (OLD.channel_name <=> NEW.channel_name)")
.contains("NOT (OLD.ocean_booking_id <=> NEW.ocean_booking_id)")
.contains("NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id)")
.doesNotContain("NOT (OLD.bill_of_lading_no <=> NEW.bill_of_lading_no)")
.doesNotContain("NOT (OLD.vessel_name <=> NEW.vessel_name)")
.doesNotContain("NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_at)")
.doesNotContain("p.plan_status='COMPLETED'");
assertThat(call).isGreaterThan(0);
assertThat(containerRestore).isGreaterThan(call);
@@ -81,7 +81,8 @@ class SmartLoadingSqlContractTest {
assertThat(Files.readString(Path.of("src/main/resources/db/phase3-simple-customs-document-migration.sql"),
StandardCharsets.UTF_8))
.contains("CREATE TRIGGER trg_container_booking_before_update")
.contains("IF current_plan_status='COMPLETED' THEN")
.contains("current_plan_status IN ('LOADING','COMPLETED')")
.contains("装柜作业开始后仅允许修改集装箱号与放舱运输数据")
.contains("NEW.document_type NOT IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION')")
.contains("OLD.document_type NOT IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION')");
}
@@ -145,8 +146,11 @@ class SmartLoadingSqlContractTest {
.contains("SET frozen_at = COALESCE(frozen_at, NEW.confirmed_at, CURRENT_TIMESTAMP(3))")
.contains("已有PDA拣货记录的装柜明细仅允许收敛部分拣货实际箱数")
.contains("装柜完成后的明细永久冻结")
.contains("装柜中仅允许修改集装箱号与航次")
.contains("装柜完成后货柜全部只读,不允许修改")
.contains("current_plan_status IN ('LOADING', 'COMPLETED')")
.contains("装柜作业开始后仅允许修改集装箱号与放舱运输数据")
.doesNotContain("NOT (OLD.bill_of_lading_no <=> NEW.bill_of_lading_no)")
.doesNotContain("NOT (OLD.vessel_name <=> NEW.vessel_name)")
.doesNotContain("NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_at)")
.doesNotContain("preload_status")
.doesNotContain("PRELOAD_PUSHED")
.doesNotContain("PRELOADING")