feat: allow shared dropship pallets

This commit is contained in:
2026-09-11 17:12:03 +08:00
parent 1683977e0b
commit c8e8245853
5 changed files with 52 additions and 10 deletions
@@ -56,7 +56,7 @@ public interface DropshipPalletizationMapper {
@Param("inboundId") long inboundId,@Param("warehouseId") long warehouseId,@Param("palletNo") String palletNo,
@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
@Select("SELECT * FROM fl_dropship_pallet WHERE pallet_no=#{palletNo}") PalletRow palletByNo(@Param("palletNo") String palletNo);
@Select("SELECT * FROM fl_dropship_pallet WHERE pallet_no=#{palletNo} FOR UPDATE") PalletRow palletByNo(@Param("palletNo") String palletNo);
@Select("SELECT COUNT(*) FROM fl_dropship_pallet WHERE task_id=#{taskId}") int palletCount(@Param("taskId") long taskId);
@Select("SELECT * FROM fl_dropship_pallet WHERE seal_operation_id=#{operationId}") PalletRow palletBySealOperation(@Param("operationId") String operationId);
@Select("""
@@ -67,11 +67,18 @@ public interface DropshipPalletizationMapper {
PalletRow pallet(@Param("taskId") long taskId,@Param("palletId") long palletId,@Param("warehouseId") long warehouseId);
@Select("""
SELECT p.* FROM fl_dropship_pallet p
WHERE p.task_id=#{taskId} ORDER BY p.id
WHERE p.task_id=#{taskId}
OR EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.pallet_id=p.id AND c.task_id=#{taskId})
ORDER BY p.id
""") List<PalletRow> pallets(@Param("taskId") long taskId);
@Select("""
SELECT p.* FROM fl_dropship_pallet p
WHERE p.tenant_id=#{tenantId} AND p.inbound_id=#{inboundId} ORDER BY p.id
WHERE ((p.tenant_id=#{tenantId} AND p.inbound_id=#{inboundId})
AND EXISTS(SELECT 1 FROM fl_inbound_order own
WHERE own.id=#{inboundId} AND own.tenant_id=#{tenantId} AND own.process_status='WAIT_PALLETIZE'))
OR EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c
WHERE c.pallet_id=p.id AND c.tenant_id=#{tenantId} AND c.inbound_id=#{inboundId})
ORDER BY p.id
""")
List<PalletRow> palletsByInbound(@Param("tenantId") long tenantId,@Param("inboundId") long inboundId);
@Select("SELECT inbound_code FROM fl_inbound_order WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0")
@@ -162,15 +169,17 @@ public interface DropshipPalletizationMapper {
int seal(@Param("taskId") long taskId,@Param("palletId") long palletId,@Param("expectedVersion") int expectedVersion,
@Param("warehouseId") long warehouseId,@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
@Select("""
SELECT COUNT(*) FROM fl_dropship_pallet p
WHERE p.task_id=#{taskId} AND p.pallet_status='OPEN'
SELECT COUNT(DISTINCT p.id) FROM fl_dropship_pallet_carton c
JOIN fl_dropship_pallet p ON p.id=c.pallet_id
WHERE c.task_id=#{taskId} AND p.pallet_status='OPEN'
""") int openPalletCount(@Param("taskId") long taskId);
@Select("SELECT DISTINCT task_id FROM fl_dropship_pallet_carton WHERE pallet_id=#{palletId} ORDER BY task_id")
List<Long> taskIdsByPallet(@Param("palletId") long palletId);
@Update("""
UPDATE fl_dropship_pallet_task SET task_status='COMPLETED',completed_at=CURRENT_TIMESTAMP(3),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
WHERE id=#{taskId} AND task_status='CLAIMED' AND counted_cartons=total_cartons AND allocated_cartons=total_cartons
AND NOT EXISTS(SELECT 1 FROM fl_dropship_pallet p WHERE p.task_id=#{taskId} AND p.pallet_status='OPEN')""") int completeTask(@Param("taskId") long taskId);
AND NOT EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c JOIN fl_dropship_pallet p ON p.id=c.pallet_id
WHERE c.task_id=#{taskId} AND p.pallet_status='OPEN')""") int completeTask(@Param("taskId") long taskId);
@Update("""
UPDATE fl_inbound_order o SET o.process_status='WAIT_SPLIT',o.pallet_count=(SELECT COUNT(DISTINCT p.id)
FROM fl_dropship_pallet_carton c JOIN fl_dropship_pallet p ON p.id=c.pallet_id
@@ -158,8 +158,8 @@ public class DropshipPalletizationService {
private PalletRow requireAssignablePallet(TaskRow task,String palletNo){
PalletRow existing=mapper.palletByNo(palletNo);
if(existing==null) throw new BizException(ResultCode.NOT_FOUND,"托盘码不是本单测量审核后生成的托盘码");
if(!Objects.equals(existing.getTaskId(),task.getId())||!Objects.equals(existing.getWarehouseId(),task.getWarehouseId())) throw conflict("托盘码不属于当前任务");
if(existing==null) throw new BizException(ResultCode.NOT_FOUND,"托盘码不存在,请扫描测量审核后生成的 DP 托盘码");
if(!Objects.equals(existing.getWarehouseId(),task.getWarehouseId())) throw conflict("托盘码不属于当前作业仓库");
if(!"OPEN".equals(existing.getPalletStatus())) throw conflict("托盘已封托,不能继续绑定箱");
return existing;
}
@@ -173,7 +173,7 @@ public class DropshipPalletizationService {
private Task view(TaskRow row){
List<Pallet> pallets=mapper.pallets(row.getId()).stream().map(p->palletView(p,row.getInboundCode())).toList();
int expected=row.getExpectedPalletCount()==null?0:row.getExpectedPalletCount();
int generated=pallets.size();
int generated=mapper.palletCount(row.getId());
boolean palletSetupReady=expected>0 && generated==expected;
return new Task(row.getId(),row.getInboundId(),row.getInboundCode(),row.getTenantId(),row.getCustomerName(),row.getWarehouseId(),row.getWarehouseName(),
row.getTotalCartons(),row.getCountedCartons(),row.getAllocatedCartons(),row.getCountedCartons()-row.getAllocatedCartons(),row.getDiscrepancyCartons(),
@@ -61,4 +61,19 @@ class DropshipPalletizationSqlContractTest {
.contains("DROP INDEX uk_loading_item_plan_detail")
.contains("ADD UNIQUE KEY uk_loading_item_plan_assignment (loading_plan_id,active_assignment_key)");
}
@Test
void sharedPalletsAreVisibleAndOnlyParticipatingOpenPalletsBlockCompletion() throws Exception {
String pallets = DropshipPalletizationMapper.class.getDeclaredMethod("pallets", long.class)
.getAnnotation(Select.class).value()[0];
String open = DropshipPalletizationMapper.class.getDeclaredMethod("openPalletCount", long.class)
.getAnnotation(Select.class).value()[0];
String complete = DropshipPalletizationMapper.class.getDeclaredMethod("completeTask", long.class)
.getAnnotation(org.apache.ibatis.annotations.Update.class).value()[0];
assertThat(pallets).contains("c.pallet_id=p.id AND c.task_id=#{taskId}");
assertThat(open).contains("fl_dropship_pallet_carton c").contains("c.task_id=#{taskId}");
assertThat(complete).contains("fl_dropship_pallet_carton c JOIN fl_dropship_pallet p")
.doesNotContain("p.task_id=#{taskId}");
}
}
@@ -53,6 +53,24 @@ class DropshipPalletizationServiceTest {
verify(mapper).insertCartonSku(anyLong(),anyLong(),eq(40L),eq("SKU-1"),any(),eq(12),eq(12),eq("MATCH"),isNull(),eq(1));
}
@Test void cartonCanBindToAnOpenPalletGeneratedForAnotherTaskInTheSameWarehouse(){
TaskRow current=task(); PalletRow shared=pallet(); shared.setTaskId(99L); shared.setTenantId(2L); shared.setInboundId(88L);
when(mapper.task(10L,100L)).thenReturn(current); when(mapper.palletCount(10L)).thenReturn(2);
when(mapper.label(10L,"BOX-1")).thenReturn(label()); when(mapper.palletByNo("DP-260911-0001")).thenReturn(shared);
when(mapper.addCounted(10L,7L,1,0)).thenReturn(1); when(mapper.carton(eq(10L),anyLong())).thenReturn(carton());
when(mapper.pallet(10L,20L,100L)).thenReturn(shared); when(mapper.cartons(20L)).thenReturn(java.util.List.of());
service.countCarton(10L,countRequest("DP-260911-0001"),7L,"张三");
verify(mapper).insertCarton(anyLong(),eq(10L),eq(20L),eq(1L),eq(30L),anyLong(),anyLong(),anyString(),any(),eq("BOX-1"),anyInt(),anyInt(),eq("MATCH"),isNull(),eq("op-1"),eq(7L),eq("张三"));
}
@Test void palletFromAnotherWarehouseCannotBeMixed(){
PalletRow other=pallet(); other.setWarehouseId(101L);
when(mapper.task(10L,100L)).thenReturn(task()); when(mapper.palletCount(10L)).thenReturn(2);
when(mapper.label(10L,"BOX-1")).thenReturn(label()); when(mapper.palletByNo("DP-260911-0001")).thenReturn(other);
assertThatThrownBy(()->service.countCarton(10L,countRequest("DP-260911-0001"),7L,"张三"))
.isInstanceOf(BizException.class).hasMessageContaining("当前作业仓库");
}
@Test void unknownPalletCodeCannotCreateAnAdHocPallet(){
when(mapper.task(10L,100L)).thenReturn(task()); when(mapper.palletCount(10L)).thenReturn(2); when(mapper.label(10L,"BOX-1")).thenReturn(label());
var request=countRequest("DP-260911-9999");