修复装柜清单托盘数快照

This commit is contained in:
2026-08-24 17:03:29 +08:00
parent c88d99a593
commit 8708904dab
7 changed files with 50 additions and 4 deletions
@@ -218,7 +218,9 @@ public interface InboundSplitSettlementMapper {
@Update(""" @Update("""
UPDATE fl_loading_plan_item plan_item UPDATE fl_loading_plan_item plan_item
JOIN fl_loading_pick_item pick_item ON pick_item.loading_plan_item_id = plan_item.id JOIN fl_loading_pick_item pick_item ON pick_item.loading_plan_item_id = plan_item.id
SET plan_item.package_count = pick_item.picked_package_count, SET plan_item.pallet_count = CEIL(COALESCE(plan_item.pallet_count,0)
* pick_item.picked_package_count / NULLIF(plan_item.package_count,0)),
plan_item.package_count = pick_item.picked_package_count,
plan_item.updated_by = #{operatorId}, plan_item.updated_by_name = #{operatorName}, plan_item.updated_by = #{operatorId}, plan_item.updated_by_name = #{operatorName},
plan_item.updated_at = CURRENT_TIMESTAMP(3), plan_item.version = plan_item.version + 1 plan_item.updated_at = CURRENT_TIMESTAMP(3), plan_item.version = plan_item.version + 1
WHERE plan_item.id = #{planItemId} AND plan_item.deleted = 0 WHERE plan_item.id = #{planItemId} AND plan_item.deleted = 0
@@ -174,7 +174,13 @@ public interface LoadingListQueryMapper {
SELECT i.id AS item_id, i.account_id, i.customer_name, i.order_id, i.order_no, SELECT i.id AS item_id, i.account_id, i.customer_name, i.order_id, i.order_no,
i.inbound_id, i.inbound_detail_id, i.inbound_code, b.container_no, i.inbound_id, i.inbound_detail_id, i.inbound_code, b.container_no,
i.sku_code, i.destination_warehouse_name, i.product_name_zh, i.sku_code, i.destination_warehouse_name, i.product_name_zh,
product_name_en, product_image_url, package_count, pallet_count, product_name_en, product_image_url, i.package_count,
COALESCE(NULLIF(i.pallet_count,0),
CEIL(COALESCE(NULLIF(inbound.pallet_count,0),
(SELECT COUNT(*) FROM fl_inbound_weight_result weight
WHERE weight.tenant_id=i.account_id AND weight.inbound_id=i.inbound_id
AND weight.deleted=0 AND weight.recognition_status='CONFIRMED'),0)
* i.package_count / NULLIF(inbound.total_package_count,0)),0) pallet_count,
gross_weight_kg, net_weight_kg, volume_cbm, dimensional_weight_kg, gross_weight_kg, net_weight_kg, volume_cbm, dimensional_weight_kg,
product_quantity, units_per_carton, fba_tracking_no, shipment_no, product_quantity, units_per_carton, fba_tracking_no, shipment_no,
shipping_mark, internal_remark, customer_remark, warehouse_remark, shipping_mark, internal_remark, customer_remark, warehouse_remark,
@@ -182,6 +188,7 @@ public interface LoadingListQueryMapper {
FROM fl_loading_plan_item i FROM fl_loading_plan_item i
JOIN fl_loading_plan p ON p.id=i.loading_plan_id JOIN fl_loading_plan p ON p.id=i.loading_plan_id
JOIN fl_container_booking b ON b.id=p.container_booking_id AND b.record_status='ACTIVE' JOIN fl_container_booking b ON b.id=p.container_booking_id AND b.record_status='ACTIVE'
LEFT JOIN fl_inbound_order inbound ON inbound.id=i.inbound_id AND inbound.tenant_id=i.account_id
WHERE i.loading_plan_id = #{planId} AND i.deleted = 0 WHERE i.loading_plan_id = #{planId} AND i.deleted = 0
ORDER BY i.load_sequence, i.id ORDER BY i.load_sequence, i.id
""") """)
@@ -6,11 +6,13 @@ import com.baiyue.firstleg.entity.ContainerBookingEntity;
import com.baiyue.firstleg.entity.LoadingPlanEntity; import com.baiyue.firstleg.entity.LoadingPlanEntity;
import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity; import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity;
import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.mapper.ContainerBookingMapper; import com.baiyue.firstleg.mapper.ContainerBookingMapper;
import com.baiyue.firstleg.mapper.LoadingPlanHistoryMapper; import com.baiyue.firstleg.mapper.LoadingPlanHistoryMapper;
import com.baiyue.firstleg.mapper.LoadingPlanItemMapper; import com.baiyue.firstleg.mapper.LoadingPlanItemMapper;
import com.baiyue.firstleg.mapper.LoadingPlanMapper; import com.baiyue.firstleg.mapper.LoadingPlanMapper;
import com.baiyue.firstleg.mapper.SmartLoadingCandidateMapper; import com.baiyue.firstleg.mapper.SmartLoadingCandidateMapper;
import com.baiyue.firstleg.mapper.InboundOrderMapper;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -26,11 +28,17 @@ public class SmartLoadingRepository {
@Resource private LoadingPlanHistoryMapper histories; @Resource private LoadingPlanHistoryMapper histories;
@Resource private ContainerBookingMapper bookings; @Resource private ContainerBookingMapper bookings;
@Resource private SmartLoadingCandidateMapper candidates; @Resource private SmartLoadingCandidateMapper candidates;
@Resource private InboundOrderMapper inbounds;
public List<Candidate> candidates(long warehouseId) { return candidates.candidates(warehouseId); } public List<Candidate> candidates(long warehouseId) { return candidates.candidates(warehouseId); }
public Candidate candidate(long warehouseId, long inboundId, long detailId) { public Candidate candidate(long warehouseId, long inboundId, long detailId) {
return candidates.candidate(warehouseId, inboundId, detailId); return candidates.candidate(warehouseId, inboundId, detailId);
} }
public InboundOrderEntity inbound(long accountId, long inboundId) {
return inbounds.selectOne(new LambdaQueryWrapper<InboundOrderEntity>()
.eq(InboundOrderEntity::getTenantId, accountId)
.eq(InboundOrderEntity::getId, inboundId));
}
public List<LoadingPlanItemEntity> activeItemsByInbound(long planId, long accountId, long inboundId) { public List<LoadingPlanItemEntity> activeItemsByInbound(long planId, long accountId, long inboundId) {
return items.selectList(new LambdaQueryWrapper<LoadingPlanItemEntity>() return items.selectList(new LambdaQueryWrapper<LoadingPlanItemEntity>()
.eq(LoadingPlanItemEntity::getLoadingPlanId, planId) .eq(LoadingPlanItemEntity::getLoadingPlanId, planId)
@@ -156,7 +156,8 @@ public class SmartLoadingService {
} else { } else {
existing.setPackageCount(total); existing.setGrossWeightKg(unit(candidate.unitGrossWeightKg()).multiply(BigDecimal.valueOf(total))); existing.setPackageCount(total); existing.setGrossWeightKg(unit(candidate.unitGrossWeightKg()).multiply(BigDecimal.valueOf(total)));
existing.setNetWeightKg(unit(candidate.unitNetWeightKg()).multiply(BigDecimal.valueOf(total))); existing.setNetWeightKg(unit(candidate.unitNetWeightKg()).multiply(BigDecimal.valueOf(total)));
existing.setVolumeCbm(unit(candidate.unitVolumeCbm()).multiply(BigDecimal.valueOf(total))); applyCargo(existing, cargo, total); existing.setVolumeCbm(unit(candidate.unitVolumeCbm()).multiply(BigDecimal.valueOf(total)));
existing.setPalletCount(estimatedPalletCount(candidate, total)); applyCargo(existing, cargo, total);
existing.setUpdatedBy(operatorId); existing.setUpdatedByName(operatorName); existing.setUpdatedBy(operatorId); existing.setUpdatedByName(operatorName);
if (repository.update(existing) != 1) throw conflict("装柜草稿已被并发修改"); if (repository.update(existing) != 1) throw conflict("装柜草稿已被并发修改");
changed = existing; changed = existing;
@@ -306,7 +307,8 @@ public class SmartLoadingService {
item.setPackageCount(request.packageCount()); item.setPackageCount(request.packageCount());
item.setGrossWeightKg(unit(candidate.unitGrossWeightKg()).multiply(BigDecimal.valueOf(request.packageCount()))); item.setGrossWeightKg(unit(candidate.unitGrossWeightKg()).multiply(BigDecimal.valueOf(request.packageCount())));
item.setNetWeightKg(unit(candidate.unitNetWeightKg()).multiply(BigDecimal.valueOf(request.packageCount()))); item.setNetWeightKg(unit(candidate.unitNetWeightKg()).multiply(BigDecimal.valueOf(request.packageCount())));
item.setVolumeCbm(unit(candidate.unitVolumeCbm()).multiply(BigDecimal.valueOf(request.packageCount()))); applyCargo(item, cargo, request.packageCount()); item.setVolumeCbm(unit(candidate.unitVolumeCbm()).multiply(BigDecimal.valueOf(request.packageCount())));
item.setPalletCount(estimatedPalletCount(candidate, request.packageCount())); applyCargo(item, cargo, request.packageCount());
item.setUpdatedBy(operatorId); item.setUpdatedByName(operatorName); item.setUpdatedBy(operatorId); item.setUpdatedByName(operatorName);
if (repository.update(item) != 1) throw conflict("装柜明细已被并发修改"); if (repository.update(item) != 1) throw conflict("装柜明细已被并发修改");
history(planId, itemId, "ADJUST_ITEM", plan.getPlanStatus(), plan.getPlanStatus(), operation, history(planId, itemId, "ADJUST_ITEM", plan.getPlanStatus(), plan.getPlanStatus(), operation,
@@ -437,8 +439,16 @@ public class SmartLoadingService {
i.setGrossWeightKg(unit(c.unitGrossWeightKg()).multiply(BigDecimal.valueOf(count))); i.setGrossWeightKg(unit(c.unitGrossWeightKg()).multiply(BigDecimal.valueOf(count)));
i.setNetWeightKg(unit(c.unitNetWeightKg()).multiply(BigDecimal.valueOf(count))); i.setNetWeightKg(unit(c.unitNetWeightKg()).multiply(BigDecimal.valueOf(count)));
i.setVolumeCbm(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count))); i.setVolumeCbm(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count)));
i.setPalletCount(estimatedPalletCount(c, count));
applyCargo(i, cargo, count); i.setLoadSequence(seq); i.setDeleted(false); i.setCreatedBy(op); i.setCreatedByName(name); i.setUpdatedBy(op); i.setUpdatedByName(name); i.setVersion(0); return i; applyCargo(i, cargo, count); i.setLoadSequence(seq); i.setDeleted(false); i.setCreatedBy(op); i.setCreatedByName(name); i.setUpdatedBy(op); i.setUpdatedByName(name); i.setVersion(0); return i;
} }
private int estimatedPalletCount(Candidate candidate, int packageCount) {
InboundOrderEntity inbound = repository.inbound(candidate.accountId(), candidate.inboundId());
if (inbound == null || inbound.getPalletCount() == null || inbound.getPalletCount() <= 0
|| inbound.getTotalPackageCount() == null || inbound.getTotalPackageCount() <= 0) return 0;
return BigDecimal.valueOf(inbound.getPalletCount()).multiply(BigDecimal.valueOf(packageCount))
.divide(BigDecimal.valueOf(inbound.getTotalPackageCount()), 0, RoundingMode.CEILING).intValueExact();
}
private void applyCargo(LoadingPlanItemEntity item, OrderCargoClient.CargoBinding cargo, int boxes) { private void applyCargo(LoadingPlanItemEntity item, OrderCargoClient.CargoBinding cargo, int boxes) {
item.setSkuCode(cargo.skuNo()); item.setSkuCode(cargo.skuNo());
item.setProductNameEn(cargo.cargoNameEn()); item.setProductNameEn(cargo.cargoNameEn());
@@ -60,6 +60,9 @@ class LoadingListQueryMapperContractTest {
assertThat(items) assertThat(items)
.contains("i.order_id", "i.order_no", "b.container_no") .contains("i.order_id", "i.order_no", "b.container_no")
.contains("b.record_status='ACTIVE'") .contains("b.record_status='ACTIVE'")
.contains("NULLIF(i.pallet_count,0)")
.contains("fl_inbound_weight_result weight")
.contains("weight.recognition_status='CONFIRMED'")
.contains("i.loading_plan_id = #{planId}") .contains("i.loading_plan_id = #{planId}")
.contains("i.deleted = 0"); .contains("i.deleted = 0");
} }
@@ -101,6 +101,17 @@ class PdaSplitSettlementSqlContractTest {
.contains("split_status='FULLY_SPLIT'"); .contains("split_status='FULLY_SPLIT'");
} }
@Test
void partialPickingConvergesPalletSnapshotWithActualCartons() throws Exception {
String converge = InboundSplitSettlementMapper.class.getMethod("convergePartialPlanItem",
long.class, long.class, String.class)
.getAnnotation(Update.class).value()[0];
assertThat(converge)
.contains("plan_item.pallet_count = CEIL")
.contains("pick_item.picked_package_count / NULLIF(plan_item.package_count,0)")
.contains("plan_item.package_count = pick_item.picked_package_count");
}
@Test @Test
void keepsAllocationConfirmedUntilReleaseStep() throws Exception { void keepsAllocationConfirmedUntilReleaseStep() throws Exception {
String consume = InboundPalletAllocationMapper.class.getMethod("consume", String consume = InboundPalletAllocationMapper.class.getMethod("consume",
@@ -16,6 +16,7 @@ import com.baiyue.firstleg.entity.ContainerBookingEntity;
import com.baiyue.firstleg.entity.LoadingPlanEntity; import com.baiyue.firstleg.entity.LoadingPlanEntity;
import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity; import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity;
import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.repository.SmartLoadingRepository; import com.baiyue.firstleg.repository.SmartLoadingRepository;
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService; import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
import com.baiyue.firstleg.split.service.SplitPreloadService; import com.baiyue.firstleg.split.service.SplitPreloadService;
@@ -299,6 +300,9 @@ class SmartLoadingServiceTest {
@Test @Test
void snapshotsCargoFields() { void snapshotsCargoFields() {
stubAssignment(); stubAssignment();
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setId(10L); inbound.setTenantId(9L); inbound.setTotalPackageCount(10); inbound.setPalletCount(4);
when(repository.inbound(9L, 10L)).thenReturn(inbound);
when(cargoClient.detail(anyString(), eq(9L), eq(30L), eq(20L))).thenReturn(ApiResponse.success( when(cargoClient.detail(anyString(), eq(9L), eq(30L), eq(20L))).thenReturn(ApiResponse.success(
new OrderCargoClient.CargoBinding(20L, 30L, 1, "中文品名", 10, new OrderCargoClient.CargoBinding(20L, 30L, 1, "中文品名", 10,
new BigDecimal("20"), new BigDecimal("2"), "SKU-1", "English name", new BigDecimal("20"), new BigDecimal("2"), "SKU-1", "English name",
@@ -319,6 +323,7 @@ class SmartLoadingServiceTest {
assertThat(saved.getProductNameEn()).isEqualTo("English name"); assertThat(saved.getProductNameEn()).isEqualTo("English name");
assertThat(saved.getUnitsPerCarton()).isEqualTo(4); assertThat(saved.getUnitsPerCarton()).isEqualTo(4);
assertThat(saved.getProductQuantity()).isEqualTo(20); assertThat(saved.getProductQuantity()).isEqualTo(20);
assertThat(saved.getPalletCount()).isEqualTo(2);
assertThat(saved.getNetWeightKg()).isEqualByComparingTo("7.5"); assertThat(saved.getNetWeightKg()).isEqualByComparingTo("7.5");
assertThat(saved.getCustomerRemark()).isEqualTo("客户备注"); assertThat(saved.getCustomerRemark()).isEqualTo("客户备注");
assertThat(saved.getInternalRemark()).isEqualTo("最新内部备注"); assertThat(saved.getInternalRemark()).isEqualTo("最新内部备注");