优化
This commit is contained in:
@@ -5216,7 +5216,7 @@ BEGIN
|
||||
OR (column_name = 'container_booking_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
|
||||
OR (column_name = 'cabinet_no' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'NO')
|
||||
OR (column_name = 'container_no' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
|
||||
OR (column_name = 'task_status' AND LOWER(column_type) = 'varchar(16)' AND is_nullable = 'NO' AND column_default = 'LOADING')
|
||||
OR (column_name = 'task_status' AND LOWER(column_type) = 'varchar(24)' AND is_nullable = 'NO' AND column_default = 'LOADING')
|
||||
OR (column_name = 'started_by' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
|
||||
OR (column_name = 'started_by_name' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
|
||||
OR (column_name = 'started_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'NO' AND LOWER(column_default) LIKE 'current_timestamp%')
|
||||
@@ -15471,20 +15471,6 @@ WHERE tenant_id=0 AND app_code='PLATFORM'
|
||||
CALL assert_loading_pick_task_shapes(1);
|
||||
DROP PROCEDURE IF EXISTS assert_loading_pick_task_shapes;
|
||||
|
||||
-- 修复旧版本“推送预装即拣货中”的状态污染;从未实际拣货的计划回收到预装柜中。
|
||||
UPDATE fl_loading_plan plan
|
||||
SET plan_status = 'EDITING',
|
||||
updated_at = CURRENT_TIMESTAMP(3),
|
||||
version = version + 1
|
||||
WHERE plan.plan_status = 'LOADING'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM fl_loading_pick_item pick_item
|
||||
JOIN fl_loading_pick_task pick_task ON pick_task.id = pick_item.pick_task_id
|
||||
WHERE pick_task.loading_plan_id = plan.id
|
||||
AND pick_item.picked_package_count > 0
|
||||
);
|
||||
|
||||
/* Phase 3 channel-owned containers and stable PDA identity. Source: firstleg phase3-channel-container-migration.sql */
|
||||
DROP PROCEDURE IF EXISTS migrate_phase3_channel_containers;
|
||||
DELIMITER $$
|
||||
@@ -15648,6 +15634,42 @@ CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_container_transport_history (
|
||||
KEY idx_container_transport_history(container_booking_id,created_at,id)
|
||||
) ENGINE=InnoDB COMMENT='柜次真实运输字段不可变变更审计';
|
||||
|
||||
DROP PROCEDURE IF EXISTS baiyue_firstleg.ensure_container_transport_history_schema;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE baiyue_firstleg.ensure_container_transport_history_schema()
|
||||
BEGIN
|
||||
IF (NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg'
|
||||
AND table_name='fl_container_transport_history' AND column_name='operation_id')
|
||||
OR NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg'
|
||||
AND table_name='fl_container_transport_history' AND column_name='request_hash'))
|
||||
AND EXISTS (SELECT 1 FROM baiyue_firstleg.fl_container_transport_history LIMIT 1) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='运输历史升级失败:旧表已有数据且缺少幂等字段';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg'
|
||||
AND table_name='fl_container_transport_history' AND column_name='operation_id') THEN
|
||||
ALTER TABLE baiyue_firstleg.fl_container_transport_history
|
||||
ADD COLUMN operation_id VARCHAR(64) NOT NULL COMMENT '运输字段操作幂等ID' AFTER after_voyage_no;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg'
|
||||
AND table_name='fl_container_transport_history' AND column_name='request_hash') THEN
|
||||
ALTER TABLE baiyue_firstleg.fl_container_transport_history
|
||||
ADD COLUMN request_hash CHAR(64) NOT NULL COMMENT '运输字段请求哈希' AFTER operation_id;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg'
|
||||
AND table_name='fl_container_transport_history' AND index_name='uk_container_transport_history_operation') THEN
|
||||
ALTER TABLE baiyue_firstleg.fl_container_transport_history
|
||||
ADD UNIQUE KEY uk_container_transport_history_operation (container_booking_id,operator_id,operation_id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg'
|
||||
AND table_name='fl_container_transport_history' AND index_name='idx_container_transport_history') THEN
|
||||
ALTER TABLE baiyue_firstleg.fl_container_transport_history
|
||||
ADD KEY idx_container_transport_history (container_booking_id,created_at,id);
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL baiyue_firstleg.ensure_container_transport_history_schema();
|
||||
DROP PROCEDURE baiyue_firstleg.ensure_container_transport_history_schema;
|
||||
|
||||
ALTER TABLE baiyue_firstleg.fl_container_transport_history
|
||||
MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '运输字段变更历史ID',
|
||||
MODIFY COLUMN container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '稳定柜次ID',
|
||||
|
||||
@@ -706,9 +706,20 @@ public class LoadingPickService {
|
||||
/** 进入PDA拣货任务即将预装柜计划推进为拣货中;状态推进和历史写入保持幂等。 */
|
||||
private void startPickingIfNeeded(LoadingPickTaskEntity task, long operatorId, String operatorName) {
|
||||
if (smartLoadingRepository.startPicking(task.getLoadingPlanId(), operatorId, operatorName) == 1) {
|
||||
String operationId = "PDA-START-" + task.getId();
|
||||
LoadingPlanHistoryEntity existing = smartLoadingRepository.operation(operationId);
|
||||
if (existing != null) {
|
||||
if (!Objects.equals(existing.getLoadingPlanId(), task.getLoadingPlanId())
|
||||
|| !"START_LOADING".equals(existing.getActionType())
|
||||
|| !"EDITING".equals(existing.getFromStatus())
|
||||
|| !"LOADING".equals(existing.getToStatus())) {
|
||||
throw conflict("PDA开始装柜历史与当前任务不一致");
|
||||
}
|
||||
return;
|
||||
}
|
||||
LoadingPlanHistoryEntity history = new LoadingPlanHistoryEntity();
|
||||
history.setLoadingPlanId(task.getLoadingPlanId());
|
||||
history.setOperationId("PDA-START-" + task.getId());
|
||||
history.setOperationId(operationId);
|
||||
history.setActionType("START_LOADING");
|
||||
history.setFromStatus("EDITING");
|
||||
history.setToStatus("LOADING");
|
||||
|
||||
@@ -175,3 +175,39 @@ CREATE TABLE IF NOT EXISTS fl_container_transport_history (
|
||||
UNIQUE KEY uk_container_transport_history_operation (container_booking_id,operator_id,operation_id),
|
||||
KEY idx_container_transport_history (container_booking_id,created_at,id)
|
||||
) ENGINE=InnoDB COMMENT='柜次真实运输字段不可变变更审计';
|
||||
|
||||
DROP PROCEDURE IF EXISTS ensure_container_transport_history_schema;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE ensure_container_transport_history_schema()
|
||||
BEGIN
|
||||
IF (NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_container_transport_history' AND column_name='operation_id')
|
||||
OR NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_container_transport_history' AND column_name='request_hash'))
|
||||
AND EXISTS (SELECT 1 FROM fl_container_transport_history LIMIT 1) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='运输历史升级失败:旧表已有数据且缺少幂等字段';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_container_transport_history' AND column_name='operation_id') THEN
|
||||
ALTER TABLE fl_container_transport_history
|
||||
ADD COLUMN operation_id VARCHAR(64) NOT NULL COMMENT '运输字段操作幂等ID' AFTER after_voyage_no;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_container_transport_history' AND column_name='request_hash') THEN
|
||||
ALTER TABLE fl_container_transport_history
|
||||
ADD COLUMN request_hash CHAR(64) NOT NULL COMMENT '运输字段请求哈希' AFTER operation_id;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_container_transport_history' AND index_name='uk_container_transport_history_operation') THEN
|
||||
ALTER TABLE fl_container_transport_history
|
||||
ADD UNIQUE KEY uk_container_transport_history_operation (container_booking_id,operator_id,operation_id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_container_transport_history' AND index_name='idx_container_transport_history') THEN
|
||||
ALTER TABLE fl_container_transport_history
|
||||
ADD KEY idx_container_transport_history (container_booking_id,created_at,id);
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL ensure_container_transport_history_schema();
|
||||
DROP PROCEDURE ensure_container_transport_history_schema;
|
||||
|
||||
@@ -902,20 +902,6 @@ BEGIN
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
-- 旧版本在推送预装时即写入 LOADING。仅回收从未发生实际拣货的计划;有实际拣货记录的数据不回退。
|
||||
UPDATE fl_loading_plan plan
|
||||
SET plan_status = 'EDITING',
|
||||
updated_at = CURRENT_TIMESTAMP(3),
|
||||
version = version + 1
|
||||
WHERE plan.plan_status = 'LOADING'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM fl_loading_pick_item pick_item
|
||||
JOIN fl_loading_pick_task pick_task ON pick_task.id = pick_item.pick_task_id
|
||||
WHERE pick_task.loading_plan_id = plan.id
|
||||
AND pick_item.picked_package_count > 0
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_loading_item_before_insert;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_loading_item_before_insert
|
||||
|
||||
+4
@@ -63,6 +63,10 @@ class Phase3ChannelContainerMigrationContractTest {
|
||||
.contains("current_plan_status IN ('LOADING', 'COMPLETED')")
|
||||
.contains("装柜作业开始后仅允许修改集装箱号与放舱运输数据")
|
||||
.contains("已完成PDA拣货任务不可修改")
|
||||
.contains("ensure_container_transport_history_schema")
|
||||
.contains("ADD COLUMN operation_id VARCHAR(64) NOT NULL")
|
||||
.contains("ADD COLUMN request_hash CHAR(64) NOT NULL")
|
||||
.contains("旧表已有数据且缺少幂等字段")
|
||||
.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)")
|
||||
|
||||
+3
-1
@@ -204,7 +204,8 @@ class SmartLoadingSqlContractTest {
|
||||
.contains("OLD.plan_status = 'EDITING' AND NEW.plan_status IN ('EDITING', 'LOADING')")
|
||||
.contains("NEW.plan_status IN ('LOADING', 'COMPLETED')")
|
||||
.contains("OLD.plan_status = 'LOADING' AND NEW.plan_status = 'EDITING'")
|
||||
.contains("旧版本在推送预装时即写入 LOADING")
|
||||
.doesNotContain("旧版本在推送预装时即写入 LOADING")
|
||||
.doesNotContain("修复旧版本“推送预装即拣货中”的状态污染")
|
||||
.contains("SET MESSAGE_TEXT = '装柜状态只允许单向推进'")
|
||||
.contains("IF OLD.plan_status = 'EDITING' AND NEW.plan_status = 'LOADING' THEN")
|
||||
.contains("ELSEIF OLD.plan_status = 'LOADING' AND NEW.plan_status = 'COMPLETED' THEN")
|
||||
@@ -314,6 +315,7 @@ class SmartLoadingSqlContractTest {
|
||||
.contains("cabinet_no VARCHAR(64) NOT NULL")
|
||||
.contains("container_no VARCHAR(64) NULL")
|
||||
.contains("task_status VARCHAR(24) NOT NULL DEFAULT 'LOADING'")
|
||||
.contains("column_name = 'task_status' AND LOWER(column_type) = 'varchar(24)'")
|
||||
.contains("UNIQUE KEY uk_loading_pick_task_plan (loading_plan_id)")
|
||||
.contains("UNIQUE KEY uk_loading_pick_task_booking (container_booking_id)")
|
||||
.contains("KEY idx_loading_pick_container (container_no, task_status, started_at)")
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickOperationEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
|
||||
@@ -902,6 +903,43 @@ class LoadingPickServiceTest {
|
||||
verify(repository, times(1)).syncInboundProjections(20L, 9L, "operator");
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchRecoversPlanResetToEditingWithoutDuplicatingExistingStartHistory() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
LoadingPickItemEntity item = item();
|
||||
item.setPlannedPackageCount(100);
|
||||
LoadingPlanHistoryEntity existingStart = new LoadingPlanHistoryEntity();
|
||||
existingStart.setLoadingPlanId(10L);
|
||||
existingStart.setOperationId("PDA-START-20");
|
||||
existingStart.setActionType("START_LOADING");
|
||||
existingStart.setFromStatus("EDITING");
|
||||
existingStart.setToStatus("LOADING");
|
||||
when(repository.history(any(String.class))).thenReturn(null);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.items(20L)).thenReturn(List.of(item));
|
||||
when(repository.items(20L, 101L)).thenReturn(List.of(item));
|
||||
when(repository.pick(30L, 100, "AUTO", "[]", 0, 101L)).thenReturn(1);
|
||||
when(repository.allTaskItemsResolved(20L)).thenReturn(true);
|
||||
when(repository.touchTask(20L, 0)).thenReturn(1);
|
||||
when(smartLoadingRepository.startPicking(10L, 9L, "operator")).thenReturn(1);
|
||||
when(smartLoadingRepository.operation("PDA-START-20")).thenReturn(existingStart);
|
||||
when(warehouse.occupiedResources(any(), any(Long.class), any(), any(Long.class))).thenReturn(
|
||||
ApiResponse.success(List.of(new WarehousePalletLocationClient.OccupiedResource(
|
||||
71L, 72L, 0, 101L, 73L, 74L, "A-01", false, 100))));
|
||||
String snapshotToken = service.inventorySnapshot(20L, 101L).snapshotToken();
|
||||
BatchRequest request = new BatchRequest(101L, 10L, 99L, 0, snapshotToken, List.of(
|
||||
new BatchItemRequest(30L, "PICK", 100, null, "AUTO", List.of(), 0, null)));
|
||||
|
||||
service.batch(20L, "batch-reset-plan", request, 9L, "operator");
|
||||
|
||||
verify(repository).pick(30L, 100, "AUTO", "[]", 0, 101L);
|
||||
verify(smartLoadingRepository).startPicking(10L, 9L, "operator");
|
||||
verify(smartLoadingRepository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
||||
verify(repository).touchTask(20L, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsWholeBatchBeforeWritingWhenAnyLineIsInvalid() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
|
||||
@@ -163,6 +163,10 @@ public class PlatformUserManagementService {
|
||||
call(() -> authClient.revokeSessions(internalToken,
|
||||
new AuthAccountClient.RevokeAccountSessionsRequest(Set.of(user.getAuthUserId()))));
|
||||
}
|
||||
if (rolesChanged || warehouseChanged || scopeChanged) {
|
||||
securityEvents.permissionChanged(user.getAuthUserId(), newTenantId,
|
||||
appCode(newTenantId), "PLATFORM_USER_AUTHORIZATION_CHANGED");
|
||||
}
|
||||
} catch (RuntimeException localFailure) {
|
||||
compensateUpdate(user.getAuthUserId(), membershipId, oldTenantId, oldAccount,
|
||||
profileChanged, scopeChanged, localFailure);
|
||||
|
||||
@@ -159,6 +159,8 @@ class PlatformUserManagementServiceTest {
|
||||
assertEquals(900L, membership.getOverseasWarehouseId());
|
||||
assertEquals(900L, response.overseasWarehouseId());
|
||||
verify(auth).revokeSessions(eq("internal-token"), any());
|
||||
verify(securityEvents).permissionChanged(10L, 0L, "PLATFORM",
|
||||
"PLATFORM_USER_AUTHORIZATION_CHANGED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -195,6 +197,8 @@ class PlatformUserManagementServiceTest {
|
||||
assertEquals(901L, response.overseasWarehouseId());
|
||||
verify(auth).revokeSessions(eq("internal-token"),
|
||||
eq(new AuthAccountClient.RevokeAccountSessionsRequest(Set.of(10L))));
|
||||
verify(securityEvents).permissionChanged(10L, 0L, "PLATFORM",
|
||||
"PLATFORM_USER_AUTHORIZATION_CHANGED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,6 +213,8 @@ class PlatformUserManagementServiceTest {
|
||||
assertNull(response.overseasWarehouseId());
|
||||
verify(auth).revokeSessions(eq("internal-token"),
|
||||
eq(new AuthAccountClient.RevokeAccountSessionsRequest(Set.of(10L))));
|
||||
verify(securityEvents).permissionChanged(10L, 0L, "PLATFORM",
|
||||
"PLATFORM_USER_AUTHORIZATION_CHANGED");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user