This commit is contained in:
2026-08-08 11:33:18 +08:00
parent 2ee78aafc2
commit 92b3b9b1fb
37 changed files with 17226 additions and 39 deletions
@@ -10,8 +10,8 @@ import java.time.LocalDateTime;
@Getter
@Setter
@TableName("fl_loading_pick_task")
public class LoadingPickTaskEntity {
@TableName("fl_inbound_loading_pick_task")
public class InboundLoadingPickTaskEntity {
@TableId(type = IdType.ASSIGN_ID) private Long id;
private Long tenantId;
private Long inboundId;
@@ -1,14 +1,14 @@
package com.baiyue.firstleg.loading.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baiyue.firstleg.loading.entity.LoadingPickTaskEntity;
import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface InboundLoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity> {
@Update("UPDATE fl_loading_pick_task SET status=#{toStatus}, updated_by=#{operatorId}, updated_by_name=#{operatorName}, "
public interface InboundLoadingPickTaskMapper extends BaseMapper<InboundLoadingPickTaskEntity> {
@Update("UPDATE fl_inbound_loading_pick_task SET status=#{toStatus}, updated_by=#{operatorId}, updated_by_name=#{operatorName}, "
+ "remark=COALESCE(#{remark}, remark), expected_count=COALESCE(#{expectedCount}, expected_count), "
+ "picked_count=COALESCE(#{pickedCount}, picked_count), version=version+1 WHERE tenant_id=#{tenantId} AND inbound_id=#{inboundId} "
+ "AND status=#{fromStatus} AND version=#{expectedVersion}")
@@ -8,7 +8,7 @@ import com.baiyue.firstleg.loading.mapper.LoadingEvidenceMapper;
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper;
import com.baiyue.firstleg.loading.mapper.LoadingOperationMapper;
import com.baiyue.firstleg.loading.mapper.InboundLoadingPickTaskMapper;
import com.baiyue.firstleg.loading.entity.LoadingPickTaskEntity;
import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Repository;
@@ -75,11 +75,11 @@ public class LoadingRepository {
operations.complete(tenantId, inboundId, operatorId, operation, key, resultJson);
}
public LoadingPickTaskEntity findPickTask(long tenantId, long inboundId) {
return picks.selectOne(new LambdaQueryWrapper<LoadingPickTaskEntity>()
.eq(LoadingPickTaskEntity::getTenantId, tenantId).eq(LoadingPickTaskEntity::getInboundId, inboundId));
public InboundLoadingPickTaskEntity findPickTask(long tenantId, long inboundId) {
return picks.selectOne(new LambdaQueryWrapper<InboundLoadingPickTaskEntity>()
.eq(InboundLoadingPickTaskEntity::getTenantId, tenantId).eq(InboundLoadingPickTaskEntity::getInboundId, inboundId));
}
public void createPickTask(LoadingPickTaskEntity task) { picks.insert(task); }
public void createPickTask(InboundLoadingPickTaskEntity task) { picks.insert(task); }
public int changePickStatus(long tenantId, long inboundId, String from, String to, long operatorId,
String operatorName, String remark, Integer expectedCount,
Integer pickedCount, int version) {
@@ -10,7 +10,7 @@ import com.baiyue.firstleg.loading.dto.LoadingRequests.*;
import com.baiyue.firstleg.loading.dto.LoadingResponses.*;
import com.baiyue.firstleg.loading.entity.LoadingEvidenceEntity;
import com.baiyue.firstleg.loading.entity.LoadingOperationEntity;
import com.baiyue.firstleg.loading.entity.LoadingPickTaskEntity;
import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
import com.baiyue.firstleg.loading.domain.PickingStatus;
import com.baiyue.firstleg.loading.dto.LoadingResponses.PickingResponse;
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper.Snapshot;
@@ -120,10 +120,10 @@ public class LoadingDeliveryService {
LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, "START_PICKING", key, hash);
if (replay != null) return pickResponse(tenantId, inboundId);
requireLoadingStage(tenantId, inboundId);
LoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
InboundLoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
if (task == null) {
if (request.expectedVersion() != 0) throw concurrent();
task = new LoadingPickTaskEntity(); task.setTenantId(tenantId); task.setInboundId(inboundId);
task = new InboundLoadingPickTaskEntity(); task.setTenantId(tenantId); task.setInboundId(inboundId);
task.setStatus(PickingStatus.IN_PROGRESS); task.setVersion(0); task.setExpectedCount(request.expectedCount());
task.setPickedCount(request.pickedCount());
task.setRemark(request.remark()); task.setUpdatedBy(operatorId); task.setUpdatedByName(operatorName);
@@ -143,9 +143,9 @@ public class LoadingDeliveryService {
LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, "COMPLETE_PICKING", key, hash);
if (replay != null) return pickResponse(tenantId, inboundId);
requireLoadingStage(tenantId, inboundId);
LoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
InboundLoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
if (task == null) {
task = new LoadingPickTaskEntity(); task.setTenantId(tenantId); task.setInboundId(inboundId);
task = new InboundLoadingPickTaskEntity(); task.setTenantId(tenantId); task.setInboundId(inboundId);
task.setStatus(PickingStatus.COMPLETED); task.setVersion(0); task.setExpectedCount(request.expectedCount());
task.setPickedCount(request.pickedCount());
task.setRemark(request.remark()); task.setUpdatedBy(operatorId); task.setUpdatedByName(operatorName);
@@ -164,7 +164,7 @@ public class LoadingDeliveryService {
}
private PickingResponse pickResponse(long tenantId, long inboundId) {
LoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
InboundLoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
if (task == null) return null;
return new PickingResponse(task.getId(), task.getInboundId(), task.getStatus(), task.getVersion(),
task.getExpectedCount(), task.getPickedCount(), task.getRemark());
@@ -2,7 +2,7 @@ package com.baiyue.firstleg.repository;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baiyue.firstleg.loading.entity.LoadingEvidenceEntity;
import com.baiyue.firstleg.loading.entity.LoadingPickTaskEntity;
import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
import com.baiyue.firstleg.loading.mapper.LoadingEvidenceMapper;
import com.baiyue.firstleg.loading.mapper.InboundLoadingPickTaskMapper;
import com.baiyue.firstleg.split.entity.PreloadPlanEntity;
@@ -34,10 +34,10 @@ public class UnifiedInboundReadRepository {
.eq(PreloadPlanEntity::getInboundId, inboundId));
}
public LoadingPickTaskEntity findPick(long tenantId, long inboundId) {
return picks.selectOne(new LambdaQueryWrapper<LoadingPickTaskEntity>()
.eq(LoadingPickTaskEntity::getTenantId, tenantId)
.eq(LoadingPickTaskEntity::getInboundId, inboundId));
public InboundLoadingPickTaskEntity findPick(long tenantId, long inboundId) {
return picks.selectOne(new LambdaQueryWrapper<InboundLoadingPickTaskEntity>()
.eq(InboundLoadingPickTaskEntity::getTenantId, tenantId)
.eq(InboundLoadingPickTaskEntity::getInboundId, inboundId));
}
public List<LoadingEvidenceEntity> findEvidence(long tenantId, long inboundId) {
@@ -55,12 +55,12 @@ public class UnifiedInboundReadRepository {
.collect(Collectors.toMap(PreloadPlanEntity::getInboundId, Function.identity(), (left, right) -> left));
}
public Map<Long, LoadingPickTaskEntity> findPicks(Map<Long, Long> tenantByInbound) {
public Map<Long, InboundLoadingPickTaskEntity> findPicks(Map<Long, Long> tenantByInbound) {
if (tenantByInbound.isEmpty()) return Map.of();
return picks.selectList(new LambdaQueryWrapper<LoadingPickTaskEntity>()
.in(LoadingPickTaskEntity::getInboundId, tenantByInbound.keySet()))
return picks.selectList(new LambdaQueryWrapper<InboundLoadingPickTaskEntity>()
.in(InboundLoadingPickTaskEntity::getInboundId, tenantByInbound.keySet()))
.stream().filter(item -> owns(tenantByInbound, item.getInboundId(), item.getTenantId()))
.collect(Collectors.toMap(LoadingPickTaskEntity::getInboundId, Function.identity(), (left, right) -> left));
.collect(Collectors.toMap(InboundLoadingPickTaskEntity::getInboundId, Function.identity(), (left, right) -> left));
}
public Map<Long, List<LoadingEvidenceEntity>> findEvidence(Map<Long, Long> tenantByInbound) {
@@ -163,7 +163,8 @@ public class MeasurementBatchService {
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
item.setCalculationRuleVersion(weighted.calculationRuleVersion());
item.setSourceImageFileId(source.intelligentImageFileId());
item.setOverLimitWarning(source.overLimitWarning());
item.setOverLimitWarning(weighted.warning() == null
? source.overLimitWarning() : weighted.warning());
item.setOuterLengthCm(weighted.outerLengthCm());
item.setOuterWidthCm(weighted.outerWidthCm());
item.setOuterHeightCm(weighted.outerHeightCm());
@@ -84,7 +84,7 @@ public class UnifiedInboundQueryService {
private UnifiedInboundSummary summary(InboundOrderEntity inbound, InboundOrderDetail detail,
com.baiyue.firstleg.split.entity.PreloadPlanEntity preload,
com.baiyue.firstleg.loading.entity.LoadingPickTaskEntity pick,
com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity pick,
List<LoadingEvidenceEntity> evidence, LocalDateTime deliveryAt,
InboundTransportSnapshotEntity transport) {
String mainStatus = mainStatus(inbound.getProcessStatus());
@@ -217,7 +217,7 @@ CREATE TABLE IF NOT EXISTS fl_inbound_measurement_item (
total_weight_kg DECIMAL(18,3) NULL COMMENT '本箱规总重量kg',
total_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本箱规总体积m3',
volume_weight_kg DECIMAL(18,3) NOT NULL COMMENT '本箱规材积重kg',
calculation_rule_version VARCHAR(32) NOT NULL COMMENT '体积计算和舍入规则版本',
calculation_rule_version VARCHAR(64) NOT NULL COMMENT '体积计算和舍入规则版本',
source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID',
machine_no VARCHAR(64) NULL COMMENT '本次测量设备编号',
device_measured_at DATETIME(3) NULL COMMENT '本次设备测量时间',
@@ -228,6 +228,92 @@ CREATE TABLE IF NOT EXISTS fl_loading_plan_history (
REFERENCES fl_loading_plan_item (id)
) ENGINE=InnoDB COMMENT='智能装柜追加式不可变操作历史';
-- fl_loading_pick_task 曾被另一套入库汇总模型复用。先按字段签名识别并迁走,
-- 再创建智能装柜任务表;未知或混合结构必须人工处理,禁止静默套用错误表型。
DROP PROCEDURE IF EXISTS migrate_legacy_loading_pick_task;
DELIMITER $$
CREATE PROCEDURE migrate_legacy_loading_pick_task()
BEGIN
DECLARE source_exists INT DEFAULT 0;
DECLARE source_smart_columns INT DEFAULT 0;
DECLARE source_inbound_columns INT DEFAULT 0;
DECLARE target_exists INT DEFAULT 0;
DECLARE target_inbound_columns INT DEFAULT 0;
SELECT COUNT(*) INTO source_exists
FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task';
IF source_exists = 1 THEN
SELECT COUNT(*) INTO source_smart_columns
FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task'
AND column_name IN ('loading_plan_id', 'container_no', 'task_status');
SELECT COUNT(*) INTO source_inbound_columns
FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task'
AND column_name IN ('tenant_id', 'inbound_id', 'status', 'expected_count', 'picked_count');
IF source_smart_columns = 3 AND source_inbound_columns = 0 THEN
SET source_exists = 1;
ELSEIF source_smart_columns = 0 AND source_inbound_columns = 5 THEN
SELECT COUNT(*) INTO target_exists
FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_loading_pick_task';
IF target_exists = 0 THEN
RENAME TABLE fl_loading_pick_task TO fl_inbound_loading_pick_task;
ELSE
SELECT COUNT(*) INTO target_inbound_columns
FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_loading_pick_task'
AND column_name IN ('tenant_id', 'inbound_id', 'status', 'expected_count', 'picked_count');
IF target_inbound_columns <> 5 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '拣货任务迁移失败:fl_inbound_loading_pick_task 不是入库汇总结构';
END IF;
INSERT INTO fl_inbound_loading_pick_task
(id, tenant_id, inbound_id, status, version, expected_count, picked_count,
remark, updated_by, updated_by_name, created_at, updated_at)
SELECT source.id, source.tenant_id, source.inbound_id, source.status, source.version,
source.expected_count, source.picked_count, source.remark, source.updated_by,
source.updated_by_name, source.created_at, source.updated_at
FROM fl_loading_pick_task source
LEFT JOIN fl_inbound_loading_pick_task target
ON target.id = source.id
WHERE target.id IS NULL;
IF EXISTS (
SELECT 1
FROM fl_loading_pick_task source
LEFT JOIN fl_inbound_loading_pick_task target ON target.id = source.id
WHERE target.id IS NULL
OR NOT (target.tenant_id <=> source.tenant_id)
OR NOT (target.inbound_id <=> source.inbound_id)
OR NOT (target.status <=> source.status)
OR NOT (target.version <=> source.version)
OR NOT (target.expected_count <=> source.expected_count)
OR NOT (target.picked_count <=> source.picked_count)
OR NOT (target.remark <=> source.remark)
OR NOT (target.updated_by <=> source.updated_by)
OR NOT (target.updated_by_name <=> source.updated_by_name)
OR NOT (target.created_at <=> source.created_at)
OR NOT (target.updated_at <=> source.updated_at)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '拣货任务迁移失败:新旧入库汇总任务存在主键冲突';
END IF;
DROP TABLE fl_loading_pick_task;
END IF;
ELSE
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '拣货任务迁移失败:fl_loading_pick_task 字段签名未知或混合';
END IF;
END IF;
END$$
DELIMITER ;
CALL migrate_legacy_loading_pick_task();
DROP PROCEDURE migrate_legacy_loading_pick_task;
CREATE TABLE IF NOT EXISTS fl_loading_pick_task (
id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货任务ID',
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID',
@@ -103,6 +103,19 @@ class CustomerDimensionConfigSqlContractTest {
.contains("客户尺寸计算配置权限码、固定ID或父节点不正确");
}
@Test
void measurementRuleVersionFitsAllBoxTypeNamesAndUpgradesExistingTables() throws Exception {
String schema = resource("db/phase2-inbound-schema.sql");
String unified = Files.readString(
Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
StandardCharsets.UTF_8);
assertThat(schema).contains("calculation_rule_version VARCHAR(64) NOT NULL");
assertThat(unified)
.contains("calculation_rule_version VARCHAR(64) NOT NULL")
.contains("MODIFY COLUMN calculation_rule_version VARCHAR(64) NOT NULL");
}
@Test
void unifiedMigrationUpgradesOnlyRecognizedLegacyTableAndBackfillsIdempotently() throws Exception {
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
@@ -0,0 +1,37 @@
package com.baiyue.firstleg.repository;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
import com.baiyue.firstleg.loading.mapper.InboundLoadingPickTaskMapper;
import org.apache.ibatis.annotations.Update;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
class LoadingPickTaskMappingContractTest {
@Test
void detailedAndInboundPickEntitiesUseDistinctTables() {
assertThat(LoadingPickTaskEntity.class.getAnnotation(TableName.class).value())
.isEqualTo("fl_loading_pick_task");
assertThat(InboundLoadingPickTaskEntity.class.getAnnotation(TableName.class).value())
.isEqualTo("fl_inbound_loading_pick_task")
.isNotEqualTo(LoadingPickTaskEntity.class.getAnnotation(TableName.class).value());
}
@Test
void inboundMapperUsesInboundTaskTableAndFields() throws Exception {
Method method = InboundLoadingPickTaskMapper.class.getDeclaredMethod(
"changeStatus", long.class, long.class, String.class, String.class,
long.class, String.class, String.class, Integer.class, Integer.class, int.class);
String sql = method.getAnnotation(Update.class).value()[0];
assertThat(sql)
.contains("UPDATE fl_inbound_loading_pick_task")
.contains("tenant_id=#{tenantId}")
.contains("inbound_id=#{inboundId}")
.doesNotContain("fl_loading_pick_task");
}
}
@@ -112,6 +112,30 @@ class CartonMeasurementServiceTest {
verify(images).delete("internal-token", 90001L, 10001L, 70001L, 88001L);
}
@Test
void oversizeScanKeepsImageAndActualDimensionsForManualReview() {
InboundMeasurementEntity measurement = measurement(30001L, "RECEIVED", 0);
when(repository.findItemByBoxCode(10001L, "RK-001#5-1")).thenReturn(null);
when(repository.findLatestEditableMeasurement(10001L, 70001L)).thenReturn(measurement);
when(repository.listItems(10001L, 30001L)).thenAnswer(invocation -> List.of(insertedItem.get()));
when(repository.updateMeasurement(measurement)).thenReturn(true);
CartonMeasurementCommand oversize = new CartonMeasurementCommand(
"RK-001#5-1", new BigDecimal("60.100"), new BigDecimal("50.100"),
new BigDecimal("50.100"), new BigDecimal("12.500"), "MJ-01",
LocalDateTime.parse("2026-08-07T10:30:00"),
new MockMultipartFile("image", "measure.jpg", "image/jpeg", "image".getBytes()));
CartonMeasurementResult result = service.upsert(10001L, 90001L, oversize);
assertThat(result.created()).isTrue();
assertThat(insertedItem.get().getSourceImageFileId()).isEqualTo(99001L);
assertThat(insertedItem.get().getLengthCm()).isEqualByComparingTo("60.100");
assertThat(insertedItem.get().getCalculationRuleVersion())
.isEqualTo("ACTUAL_DIMENSION_OVERSIZE_V1");
assertThat(insertedItem.get().getOverLimitWarning()).contains("人工复核");
verify(images, never()).delete(any(), any(Long.TYPE), any(Long.TYPE), any(Long.TYPE), any(Long.TYPE));
}
@Test
void submittedInboundRejectsDeviceBeforeUploadingImage() {
when(repository.findInbound(10001L, 70001L)).thenReturn(inbound("MEASURE_PENDING"));