test(firstleg): cover phase3 container loading flow

This commit is contained in:
2026-08-08 21:34:39 +08:00
parent 2dae8b2029
commit da85e30f33
8 changed files with 644 additions and 15 deletions
@@ -314,7 +314,7 @@ public class SmartLoadingService {
|| !Objects.equals(history.getActionType(), action)
|| itemId != null && !Objects.equals(history.getLoadingPlanItemId(), itemId)
|| history.getAfterSnapshot() == null
|| !history.getAfterSnapshot().contains("\"requestHash\":\"" + requestHash + "\"")) {
|| !sameRequestHash(history.getAfterSnapshot(), requestHash)) {
throw conflict("operationId 已用于不同请求");
}
LoadingPlanEntity plan = repository.plan(planId);
@@ -322,6 +322,15 @@ public class SmartLoadingService {
return view(plan, requireBooking(plan.getContainerBookingId()));
}
private boolean sameRequestHash(String snapshot, String requestHash) {
try {
var value = objectMapper.readTree(snapshot);
return value != null && requestHash.equals(value.path("requestHash").asText());
} catch (JsonProcessingException exception) {
return false;
}
}
private void history(long planId, Long itemId, String action, String from, String to,
String operation, long operatorId, String operatorName) {
history(planId, itemId, action, from, to, operation, null, null, operatorId, operatorName);
@@ -461,6 +461,92 @@ CREATE TABLE IF NOT EXISTS fl_loading_pick_history (
CONSTRAINT fk_loading_pick_history_item FOREIGN KEY (pick_item_id) REFERENCES fl_loading_pick_item (id)
) ENGINE=InnoDB COMMENT='PDA拣货幂等和操作审计历史';
DROP PROCEDURE IF EXISTS repair_phase3_loading_pick_task_foreign_keys;
DELIMITER $$
CREATE PROCEDURE repair_phase3_loading_pick_task_foreign_keys()
BEGIN
DECLARE item_fk_count INT DEFAULT 0;
DECLARE item_target_count INT DEFAULT 0;
DECLARE history_fk_count INT DEFAULT 0;
DECLARE history_target_count INT DEFAULT 0;
DECLARE item_drop_clauses TEXT;
DECLARE history_drop_clauses TEXT;
IF EXISTS (
SELECT 1 FROM fl_loading_pick_item item
LEFT JOIN fl_loading_pick_task task ON task.id = item.pick_task_id
WHERE task.id IS NULL
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '柜次PDA明细存在无法迁移到智能装柜任务的引用';
END IF;
IF EXISTS (
SELECT 1 FROM fl_loading_pick_history history
LEFT JOIN fl_loading_pick_task task ON task.id = history.pick_task_id
WHERE task.id IS NULL
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '柜次PDA历史存在无法迁移到智能装柜任务的引用';
END IF;
SELECT COUNT(*),
SUM(k.referenced_table_name = 'fl_loading_pick_task'
AND k.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = k.constraint_schema
AND peer.table_name = k.table_name
AND peer.constraint_name = k.constraint_name) = 1),
GROUP_CONCAT(CONCAT('DROP FOREIGN KEY `', REPLACE(k.constraint_name, '`', '``'), '`')
ORDER BY k.constraint_name SEPARATOR ', ')
INTO item_fk_count, item_target_count, item_drop_clauses
FROM information_schema.key_column_usage k
WHERE k.constraint_schema = 'baiyue_firstleg'
AND k.table_name = 'fl_loading_pick_item'
AND k.column_name = 'pick_task_id'
AND k.referenced_table_name IS NOT NULL;
IF item_fk_count <> 1 OR item_target_count <> 1 THEN
IF item_drop_clauses IS NOT NULL THEN
SET @phase3_fk_ddl = CONCAT('ALTER TABLE fl_loading_pick_item ', item_drop_clauses);
PREPARE phase3_fk_statement FROM @phase3_fk_ddl;
EXECUTE phase3_fk_statement;
DEALLOCATE PREPARE phase3_fk_statement;
END IF;
ALTER TABLE fl_loading_pick_item
ADD CONSTRAINT fk_loading_pick_item_task
FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id);
END IF;
SELECT COUNT(*),
SUM(k.referenced_table_name = 'fl_loading_pick_task'
AND k.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = k.constraint_schema
AND peer.table_name = k.table_name
AND peer.constraint_name = k.constraint_name) = 1),
GROUP_CONCAT(CONCAT('DROP FOREIGN KEY `', REPLACE(k.constraint_name, '`', '``'), '`')
ORDER BY k.constraint_name SEPARATOR ', ')
INTO history_fk_count, history_target_count, history_drop_clauses
FROM information_schema.key_column_usage k
WHERE k.constraint_schema = 'baiyue_firstleg'
AND k.table_name = 'fl_loading_pick_history'
AND k.column_name = 'pick_task_id'
AND k.referenced_table_name IS NOT NULL;
IF history_fk_count <> 1 OR history_target_count <> 1 THEN
IF history_drop_clauses IS NOT NULL THEN
SET @phase3_fk_ddl = CONCAT('ALTER TABLE fl_loading_pick_history ', history_drop_clauses);
PREPARE phase3_fk_statement FROM @phase3_fk_ddl;
EXECUTE phase3_fk_statement;
DEALLOCATE PREPARE phase3_fk_statement;
END IF;
ALTER TABLE fl_loading_pick_history
ADD CONSTRAINT fk_loading_pick_history_task
FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id);
END IF;
END$$
DELIMITER ;
CALL repair_phase3_loading_pick_task_foreign_keys();
DROP PROCEDURE repair_phase3_loading_pick_task_foreign_keys;
DROP TRIGGER IF EXISTS trg_container_booking_before_update;
DELIMITER $$
CREATE TRIGGER trg_container_booking_before_update
@@ -1091,6 +1177,40 @@ BEGIN
'uk_loading_pick_history_operation')) <> 3 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:PDA唯一索引缺失';
END IF;
IF (SELECT COUNT(*) FROM information_schema.key_column_usage
WHERE constraint_schema = 'baiyue_firstleg'
AND table_name = 'fl_loading_pick_item'
AND column_name = 'pick_task_id'
AND referenced_table_name IS NOT NULL) <> 1
OR (SELECT COUNT(*) FROM information_schema.key_column_usage outer_fk
WHERE outer_fk.constraint_schema = 'baiyue_firstleg'
AND outer_fk.table_name = 'fl_loading_pick_item'
AND outer_fk.column_name = 'pick_task_id'
AND outer_fk.referenced_table_name = 'fl_loading_pick_task'
AND outer_fk.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = outer_fk.constraint_schema
AND peer.table_name = outer_fk.table_name
AND peer.constraint_name = outer_fk.constraint_name) = 1) <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:柜次PDA明细任务外键不唯一或指向错误';
END IF;
IF (SELECT COUNT(*) FROM information_schema.key_column_usage
WHERE constraint_schema = 'baiyue_firstleg'
AND table_name = 'fl_loading_pick_history'
AND column_name = 'pick_task_id'
AND referenced_table_name IS NOT NULL) <> 1
OR (SELECT COUNT(*) FROM information_schema.key_column_usage outer_fk
WHERE outer_fk.constraint_schema = 'baiyue_firstleg'
AND outer_fk.table_name = 'fl_loading_pick_history'
AND outer_fk.column_name = 'pick_task_id'
AND outer_fk.referenced_table_name = 'fl_loading_pick_task'
AND outer_fk.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = outer_fk.constraint_schema
AND peer.table_name = outer_fk.table_name
AND peer.constraint_name = outer_fk.constraint_name) = 1) <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:柜次PDA历史任务外键不唯一或指向错误';
END IF;
IF (SELECT COUNT(*) FROM information_schema.triggers
WHERE trigger_schema = 'baiyue_firstleg'
AND trigger_name IN ('trg_container_booking_before_update',
@@ -144,6 +144,16 @@ class SmartLoadingSqlContractTest {
.contains("UNIQUE KEY uk_loading_pick_history_operation (operation_id)")
.contains("上层拆分按业务规则向上取整")
.contains("CONSTRAINT chk_loading_pick_item_planned CHECK (planned_package_count > 0)")
.contains("CALL repair_phase3_loading_pick_task_foreign_keys()")
.contains("GROUP_CONCAT(CONCAT('DROP FOREIGN KEY `'")
.contains("item_fk_count <> 1 OR item_target_count <> 1")
.contains("history_fk_count <> 1 OR history_target_count <> 1")
.contains("FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id)")
.contains("referenced_column_name = 'id'")
.contains("柜次PDA明细任务外键不唯一或指向错误")
.contains("柜次PDA历史任务外键不唯一或指向错误")
.doesNotContain("DROP FOREIGN KEY fk_loading_pick_item_task")
.doesNotContain("DROP FOREIGN KEY fk_loading_pick_history_task")
.contains("trg_loading_pick_task_before_update")
.contains("trg_loading_pick_item_before_update")
.contains("trg_loading_pick_item_before_delete")
@@ -171,7 +181,8 @@ class SmartLoadingSqlContractTest {
.contains("tmp_phase3_inbound_role_grant")
.contains("DROP TEMPORARY TABLE IF EXISTS tmp_phase3_inbound_role_grant")
.contains("5102121, 0, 'PLATFORM'")
.contains("5102129, 0, 'PLATFORM'");
.contains("5102129, 0, 'PLATFORM'")
.contains("role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')");
}
@Test
@@ -311,7 +311,8 @@ class SmartLoadingServiceTest {
value.setLoadingPlanId(planId);
value.setLoadingPlanItemId(itemId);
value.setActionType(action);
value.setAfterSnapshot("{\"requestHash\":\"" + hash + "\"}");
// MySQL JSON columns normalize a space after the colon; replay must parse structurally.
value.setAfterSnapshot("{\"requestHash\": \"" + hash + "\"}");
return value;
}