feat: complete finance and overseas receiving workflow
This commit is contained in:
+59
-27
@@ -60,10 +60,22 @@ public interface DropshipPalletizationMapper {
|
||||
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE pallet_no=#{palletNo}") PalletRow palletByNo(@Param("palletNo") String palletNo);
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE seal_operation_id=#{operationId}") PalletRow palletBySealOperation(@Param("operationId") String operationId);
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE id=#{palletId} AND task_id=#{taskId} AND warehouse_id=#{warehouseId}")
|
||||
@Select("""
|
||||
SELECT p.* FROM fl_dropship_pallet p
|
||||
WHERE p.id=#{palletId} AND p.warehouse_id=#{warehouseId}
|
||||
AND (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}))
|
||||
""")
|
||||
PalletRow pallet(@Param("taskId") long taskId,@Param("palletId") long palletId,@Param("warehouseId") long warehouseId);
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE task_id=#{taskId} ORDER BY id") List<PalletRow> pallets(@Param("taskId") long taskId);
|
||||
@Select("SELECT p.* FROM fl_dropship_pallet p JOIN fl_dropship_pallet_task t ON t.id=p.task_id WHERE t.tenant_id=#{tenantId} AND t.inbound_id=#{inboundId} ORDER BY p.id")
|
||||
@Select("""
|
||||
SELECT DISTINCT p.* FROM fl_dropship_pallet p
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
WHERE c.task_id=#{taskId} ORDER BY p.id
|
||||
""") List<PalletRow> pallets(@Param("taskId") long taskId);
|
||||
@Select("""
|
||||
SELECT DISTINCT p.* FROM fl_dropship_pallet p
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
WHERE 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")
|
||||
String inboundCode(@Param("tenantId") long tenantId,@Param("inboundId") long inboundId);
|
||||
@@ -123,46 +135,66 @@ public interface DropshipPalletizationMapper {
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet SET pallet_status='SEALED',seal_operation_id=#{operationId},completed_by=#{operatorId},
|
||||
completed_by_name=#{operatorName},completed_at=CURRENT_TIMESTAMP(3),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{palletId} AND task_id=#{taskId} AND pallet_status='OPEN' AND version=#{expectedVersion}
|
||||
WHERE id=#{palletId} AND warehouse_id=#{warehouseId} AND pallet_status='OPEN' AND version=#{expectedVersion}
|
||||
AND EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.pallet_id=#{palletId})
|
||||
AND NOT EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.task_id=#{taskId} AND c.pallet_id IS NULL)""")
|
||||
AND EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.pallet_id=#{palletId} AND c.task_id=#{taskId})
|
||||
AND NOT EXISTS(
|
||||
SELECT 1 FROM fl_dropship_pallet_task t
|
||||
WHERE EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.pallet_id=#{palletId} AND c.task_id=t.id)
|
||||
AND (t.counted_cartons<>t.total_cartons OR t.allocated_cartons<>t.total_cartons
|
||||
OR EXISTS(SELECT 1 FROM fl_dropship_pallet_carton pending WHERE pending.task_id=t.id AND pending.pallet_id IS NULL))
|
||||
)""")
|
||||
int seal(@Param("taskId") long taskId,@Param("palletId") long palletId,@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
@Select("SELECT COUNT(*) FROM fl_dropship_pallet WHERE task_id=#{taskId} AND pallet_status='OPEN'") int openPalletCount(@Param("taskId") long taskId);
|
||||
@Param("warehouseId") long warehouseId,@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
@Select("""
|
||||
SELECT COUNT(DISTINCT p.id) FROM fl_dropship_pallet p
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.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(*) FROM fl_dropship_pallet p
|
||||
WHERE p.inbound_id=o.id AND p.pallet_status='SEALED'),o.updated_by=#{operatorId},o.version=o.version+1
|
||||
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
|
||||
WHERE c.tenant_id=o.tenant_id AND c.inbound_id=o.id AND p.pallet_status='SEALED'),o.updated_by=#{operatorId},o.version=o.version+1
|
||||
WHERE o.id=#{inboundId} AND o.tenant_id=#{tenantId} AND o.process_status='WAIT_PALLETIZE'
|
||||
AND EXISTS(SELECT 1 FROM fl_dropship_pallet_task t WHERE t.id=#{taskId} AND t.task_status='COMPLETED')""")
|
||||
int completeInbound(@Param("taskId") long taskId,@Param("tenantId") long tenantId,@Param("inboundId") long inboundId,@Param("operatorId") long operatorId);
|
||||
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.pallet_id=#{palletId} ORDER BY c.sku_code,c.inbound_detail_id,c.id") List<CartonRow> cartons(@Param("palletId") long palletId);
|
||||
@Select("""
|
||||
SELECT c.*,p.pallet_no,o.inbound_code FROM fl_dropship_pallet_carton c
|
||||
LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id
|
||||
JOIN fl_inbound_order o ON o.id=c.inbound_id AND o.tenant_id=c.tenant_id
|
||||
WHERE c.pallet_id=#{palletId} ORDER BY o.inbound_code,c.sku_code,c.inbound_detail_id,c.id
|
||||
""") List<CartonRow> cartons(@Param("palletId") long palletId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.task_id=#{taskId} AND c.pallet_id IS NULL ORDER BY c.counted_at,c.id") List<CartonRow> unboundCartons(@Param("taskId") long taskId);
|
||||
@Select("""
|
||||
SELECT COALESCE(o.actual_gross_weight_kg*COUNT(c.id)/NULLIF(o.total_package_count,0),0) weight_kg,
|
||||
COALESCE(o.actual_volume_cbm*COUNT(c.id)/NULLIF(o.total_package_count,0),0) volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_inbound_order o ON o.id=p.inbound_id AND o.tenant_id=p.tenant_id
|
||||
LEFT JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id WHERE p.id=#{palletId}
|
||||
GROUP BY p.id,o.actual_gross_weight_kg,o.actual_volume_cbm,o.total_package_count""")
|
||||
SELECT COALESCE(SUM(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0)),0) weight_kg,
|
||||
COALESCE(SUM(o.actual_volume_cbm/NULLIF(o.total_package_count,0)),0) volume_cbm
|
||||
FROM fl_dropship_pallet_carton c
|
||||
JOIN fl_inbound_order o ON o.id=c.inbound_id AND o.tenant_id=c.tenant_id
|
||||
WHERE c.pallet_id=#{palletId}""")
|
||||
PalletMetrics metrics(@Param("palletId") long palletId);
|
||||
@Select("""
|
||||
SELECT p.id pallet_id,p.pallet_no,'SEALED_PALLET' unit_type,TRUE indivisible,COUNT(c.id) carton_count,
|
||||
COUNT(DISTINCT c.sku_code) sku_count,
|
||||
COALESCE(o.actual_gross_weight_kg*COUNT(c.id)/NULLIF(o.total_package_count,0),0) weight_kg,
|
||||
COALESCE(o.actual_volume_cbm*COUNT(c.id)/NULLIF(o.total_package_count,0),0) volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_inbound_order o ON o.id=p.inbound_id AND o.tenant_id=p.tenant_id
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
WHERE p.inbound_id=#{inboundId} AND p.pallet_status='SEALED'
|
||||
COALESCE(SUM(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0)),0) weight_kg,
|
||||
COALESCE(SUM(o.actual_volume_cbm/NULLIF(o.total_package_count,0)),0) volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
JOIN fl_inbound_order o ON o.id=c.inbound_id AND o.tenant_id=c.tenant_id
|
||||
WHERE EXISTS(SELECT 1 FROM fl_dropship_pallet_carton own WHERE own.pallet_id=p.id AND own.inbound_id=#{inboundId})
|
||||
AND p.pallet_status='SEALED'
|
||||
AND NOT EXISTS(SELECT 1 FROM fl_loading_plan_item i WHERE i.pallet_id=p.id AND i.deleted=0)
|
||||
GROUP BY p.id,p.pallet_no,o.actual_gross_weight_kg,o.actual_volume_cbm,o.total_package_count ORDER BY p.id
|
||||
GROUP BY p.id,p.pallet_no ORDER BY p.id
|
||||
""") List<PalletCandidate> palletCandidates(@Param("inboundId") long inboundId);
|
||||
|
||||
@Select("""
|
||||
SELECT p.id pallet_id,p.pallet_no,p.pallet_status,p.tenant_id account_id,p.inbound_id,o.order_id,o.order_no,
|
||||
SELECT p.id pallet_id,p.pallet_no,p.pallet_status,c.tenant_id account_id,c.inbound_id,o.order_id,o.order_no,
|
||||
o.inbound_code,o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,
|
||||
o.destination_code destination_warehouse_code,o.destination_name destination_warehouse_name,o.customer_name,
|
||||
c.inbound_detail_id,c.sku_code,MAX(c.cargo_name) cargo_name,COUNT(c.id) carton_count,
|
||||
@@ -170,9 +202,9 @@ public interface DropshipPalletizationMapper {
|
||||
COALESCE(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0),0) unit_gross_weight_kg,
|
||||
COALESCE(o.actual_net_weight_kg/NULLIF(o.total_package_count,0),0) unit_net_weight_kg,
|
||||
COALESCE(o.actual_volume_cbm/NULLIF(o.total_package_count,0),0) unit_volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_inbound_order o ON o.id=p.inbound_id AND o.tenant_id=p.tenant_id
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id WHERE p.id=#{palletId}
|
||||
GROUP BY p.id,p.pallet_no,p.pallet_status,p.tenant_id,p.inbound_id,o.order_id,o.order_no,o.inbound_code,
|
||||
FROM fl_dropship_pallet p JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
JOIN fl_inbound_order o ON o.id=c.inbound_id AND o.tenant_id=c.tenant_id WHERE p.id=#{palletId}
|
||||
GROUP BY p.id,p.pallet_no,p.pallet_status,c.tenant_id,c.inbound_id,o.order_id,o.order_no,o.inbound_code,
|
||||
o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,o.destination_code,o.destination_name,
|
||||
o.customer_name,c.inbound_detail_id,c.sku_code,o.actual_gross_weight_kg,o.actual_net_weight_kg,o.actual_volume_cbm,o.total_package_count
|
||||
ORDER BY c.inbound_detail_id
|
||||
@@ -181,7 +213,7 @@ public interface DropshipPalletizationMapper {
|
||||
@Getter @Setter class TaskRow { private Long id,tenantId,inboundId,warehouseId,assigneeId; private String inboundCode,customerName,warehouseName,taskStatus,assigneeName,processStatus,fulfillmentType; private Integer totalCartons,countedCartons,allocatedCartons,discrepancyCartons,version; }
|
||||
@Getter @Setter class PalletRow { private Long id,taskId,tenantId,inboundId,warehouseId; private String palletNo,palletStatus,sealOperationId; private Integer version; private LocalDateTime completedAt; }
|
||||
@Getter @Setter class LabelRow { private Long cartonLabelId,inboundDetailId; private String skuCode,cargoName,inboundCode,boxCode; private Integer unitsPerCarton; }
|
||||
@Getter @Setter class CartonRow { private Long id,taskId,palletId,inboundId,inboundDetailId; private String palletNo,skuCode,cargoName,boxCode,discrepancyStatus,discrepancyRemark; private Integer expectedSkuQuantity,actualSkuQuantity; }
|
||||
@Getter @Setter class CartonRow { private Long id,taskId,palletId,inboundId,inboundDetailId; private String palletNo,inboundCode,skuCode,cargoName,boxCode,discrepancyStatus,discrepancyRemark; private Integer expectedSkuQuantity,actualSkuQuantity; }
|
||||
record PalletMetrics(BigDecimal weightKg,BigDecimal volumeCbm) {}
|
||||
@Getter @Setter class PalletLine { private Long palletId,tenantId,accountId,inboundId,orderId,warehouseId,destinationWarehouseId,inboundDetailId; private String palletNo,palletStatus,orderNo,inboundCode,channelCode,channelName,destinationWarehouseCode,destinationWarehouseName,customerName,skuCode,cargoName; private Integer cartonCount,actualSkuQuantity; private BigDecimal unitGrossWeightKg,unitNetWeightKg,unitVolumeCbm; }
|
||||
}
|
||||
|
||||
@@ -85,7 +85,8 @@ public interface LoadingCompletedEventMapper {
|
||||
pallet.pallet_no AS pallet_code,NULL AS allocation_no,
|
||||
COUNT(*) OVER (PARTITION BY pallet.id,carton.tenant_id,carton.inbound_id) AS allocated_package_count,
|
||||
carton.inbound_detail_id AS source_inbound_detail_id,carton.sku_code,
|
||||
item.product_name_zh,item.package_count,item.product_quantity,carton.box_code
|
||||
item.product_name_zh,item.package_count,item.product_quantity,carton.box_code,
|
||||
carton.actual_sku_quantity
|
||||
FROM fl_dropship_pallet pallet
|
||||
JOIN fl_dropship_pallet_carton carton ON carton.pallet_id=pallet.id
|
||||
JOIN fl_loading_plan_item item ON item.loading_plan_id=#{planId}
|
||||
@@ -98,15 +99,17 @@ public interface LoadingCompletedEventMapper {
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(DISTINCT p.id) FROM fl_dropship_pallet p
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id=#{planId} AND i.account_id=p.tenant_id
|
||||
AND i.inbound_id=p.inbound_id AND i.deleted=0 WHERE p.pallet_status='SEALED'
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id=#{planId} AND i.account_id=c.tenant_id
|
||||
AND i.inbound_id=c.inbound_id AND i.deleted=0 WHERE p.pallet_status='SEALED'
|
||||
""")
|
||||
int workPalletCount(@Param("planId") long planId);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(DISTINCT p.id) FROM fl_dropship_pallet p
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id=#{planId} AND i.account_id=p.tenant_id
|
||||
AND i.inbound_id=p.inbound_id AND i.deleted=0 WHERE p.pallet_status='OPEN'
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id=#{planId} AND i.account_id=c.tenant_id
|
||||
AND i.inbound_id=c.inbound_id AND i.deleted=0 WHERE p.pallet_status='OPEN'
|
||||
""")
|
||||
int openWorkPalletCount(@Param("planId") long planId);
|
||||
|
||||
@@ -138,6 +141,6 @@ public interface LoadingCompletedEventMapper {
|
||||
private Boolean actualBinding;
|
||||
private Integer allocationNo; private Integer allocatedPackageCount;
|
||||
private Long sourceInboundDetailId; private String skuCode; private String productNameZh;
|
||||
private Integer packageCount; private Integer productQuantity; private String boxCode;
|
||||
private Integer packageCount; private Integer productQuantity; private Integer actualSkuQuantity; private String boxCode;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-12
@@ -143,16 +143,18 @@ public class DropshipPalletizationService {
|
||||
public Task seal(long taskId,long palletId,SealPalletRequest request,long operatorId,String operatorName){
|
||||
TaskRow task=requireWorkingTask(taskId,request.warehouseId(),operatorId);
|
||||
PalletRow replay=mapper.palletBySealOperation(operation(request.operationId()));
|
||||
if(replay!=null){ if(!Objects.equals(replay.getTaskId(),taskId)||!Objects.equals(replay.getId(),palletId)) throw conflict("封托幂等号已用于其他托盘"); return view(requireTask(taskId,request.warehouseId())); }
|
||||
if(replay!=null){ if(!Objects.equals(replay.getId(),palletId)||!Objects.equals(replay.getWarehouseId(),request.warehouseId())) throw conflict("封托幂等号已用于其他托盘"); return view(requireTask(taskId,request.warehouseId())); }
|
||||
requireOpenPallet(taskId,palletId,request.warehouseId());
|
||||
if(mapper.unboundCount(taskId)>0) throw conflict("仍有已清点待绑托箱,不能封托");
|
||||
if(mapper.seal(taskId,palletId,request.expectedVersion(),operation(request.operationId()),operatorId,operatorName)!=1)
|
||||
if(mapper.seal(taskId,palletId,request.expectedVersion(),request.warehouseId(),operation(request.operationId()),operatorId,operatorName)!=1)
|
||||
throw conflict("空托盘、托盘状态或版本已变化,请刷新后重试");
|
||||
TaskRow refreshed=requireTask(taskId,request.warehouseId());
|
||||
if(Objects.equals(refreshed.getCountedCartons(),refreshed.getTotalCartons()) &&
|
||||
Objects.equals(refreshed.getAllocatedCartons(),refreshed.getTotalCartons()) && mapper.openPalletCount(taskId)==0){
|
||||
if(mapper.completeTask(taskId)!=1 || mapper.completeInbound(taskId,refreshed.getTenantId(),refreshed.getInboundId(),operatorId)!=1)
|
||||
throw conflict("组托完成状态已变化,请刷新后重试");
|
||||
for(Long participatingTaskId:mapper.taskIdsByPallet(palletId)){
|
||||
TaskRow participating=requireTask(participatingTaskId,request.warehouseId());
|
||||
if(Objects.equals(participating.getCountedCartons(),participating.getTotalCartons()) &&
|
||||
Objects.equals(participating.getAllocatedCartons(),participating.getTotalCartons()) && mapper.openPalletCount(participatingTaskId)==0){
|
||||
if(mapper.completeTask(participatingTaskId)!=1 || mapper.completeInbound(participatingTaskId,participating.getTenantId(),participating.getInboundId(),operatorId)!=1)
|
||||
throw conflict("组托完成状态已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
return view(requireTask(taskId,request.warehouseId()));
|
||||
}
|
||||
@@ -166,14 +168,14 @@ public class DropshipPalletizationService {
|
||||
private PalletRow getOrCreatePallet(TaskRow task,String palletNo,String operation,long operatorId,String operatorName){
|
||||
PalletRow existing=mapper.palletByNo(palletNo);
|
||||
if(existing!=null){
|
||||
if(!Objects.equals(existing.getTaskId(),task.getId()) || !Objects.equals(existing.getTenantId(),task.getTenantId())) throw conflict("托盘码已被其他客户或入仓任务占用");
|
||||
if(!Objects.equals(existing.getWarehouseId(),task.getWarehouseId())) throw conflict("托盘码属于其他仓库");
|
||||
if(!"OPEN".equals(existing.getPalletStatus())) throw conflict("托盘已封托,不能继续绑定箱");
|
||||
return existing;
|
||||
}
|
||||
try{ mapper.insertPallet(IdWorker.getId(),task.getId(),task.getTenantId(),task.getInboundId(),task.getWarehouseId(),palletNo,operation,operatorId,operatorName); }
|
||||
catch(DuplicateKeyException e){ existing=mapper.palletByNo(palletNo); if(existing==null) throw conflict("托盘码已被并发占用"); }
|
||||
existing=mapper.palletByNo(palletNo);
|
||||
if(existing==null || !Objects.equals(existing.getTaskId(),task.getId())) throw conflict("托盘码已被其他任务占用");
|
||||
if(existing==null || !Objects.equals(existing.getWarehouseId(),task.getWarehouseId()) || !"OPEN".equals(existing.getPalletStatus())) throw conflict("托盘码已被其他仓库占用或已经封托");
|
||||
return existing;
|
||||
}
|
||||
|
||||
@@ -192,15 +194,15 @@ public class DropshipPalletizationService {
|
||||
|
||||
private Pallet palletView(PalletRow row,String inboundCode){
|
||||
List<CartonRow> cartons=mapper.cartons(row.getId()); Map<String,List<CartonRow>> groups=new LinkedHashMap<>();
|
||||
for(CartonRow c:cartons) groups.computeIfAbsent(c.getInboundDetailId()+"|"+c.getSkuCode(),ignored->new ArrayList<>()).add(c);
|
||||
List<SkuItem> items=groups.values().stream().map(values->{CartonRow first=values.get(0);return new SkuItem(first.getInboundId(),inboundCode,
|
||||
for(CartonRow c:cartons) groups.computeIfAbsent(c.getInboundId()+"|"+c.getInboundDetailId()+"|"+c.getSkuCode(),ignored->new ArrayList<>()).add(c);
|
||||
List<SkuItem> items=groups.values().stream().map(values->{CartonRow first=values.get(0);return new SkuItem(first.getInboundId(),first.getInboundCode(),
|
||||
first.getInboundDetailId(),first.getSkuCode(),first.getCargoName(),values.size(),values.stream().mapToInt(v->v.getActualSkuQuantity()==null?0:v.getActualSkuQuantity()).sum(),
|
||||
values.stream().map(CartonRow::getBoxCode).toList());}).toList();
|
||||
int discrepancies=(int)cartons.stream().filter(c->"ACCEPTED_VARIANCE".equals(c.getDiscrepancyStatus())).count();
|
||||
PalletMetrics metrics=mapper.metrics(row.getId());
|
||||
return new Pallet(row.getId(),row.getPalletNo(),row.getPalletStatus(),cartons.size(),items.size(),discrepancies,
|
||||
metrics==null?BigDecimal.ZERO:metrics.weightKg(),metrics==null?BigDecimal.ZERO:metrics.volumeCbm(),row.getVersion(),items,
|
||||
cartons.stream().map(c->cartonView(c,inboundCode,false,null)).toList(),row.getCompletedAt());
|
||||
cartons.stream().map(c->cartonView(c,c.getInboundCode(),false,null)).toList(),row.getCompletedAt());
|
||||
}
|
||||
|
||||
private Carton cartonView(CartonRow row,String inboundCode,boolean canCount,String blockReason){
|
||||
|
||||
+41
-12
@@ -67,12 +67,14 @@ public class LoadingCompletedOutboxService {
|
||||
palletLabelService.prepare(planId, rows.get(0).getWarehouseId());
|
||||
Map<Long, List<String>> cartonMarks = cartonLabelPools(rows);
|
||||
List<PalletRow> palletRows = palletRows(planId);
|
||||
Map<String, PalletManifest> palletManifests = palletManifests(palletRows);
|
||||
List<com.baiyue.firstleg.mapper.LoadingPalletLabelMapper.LabelRow> standardLabels = palletLabelMapper.labels(planId);
|
||||
return rows.stream().collect(java.util.stream.Collectors.groupingBy(Row::getAccountId,
|
||||
java.util.TreeMap::new, java.util.stream.Collectors.toList()))
|
||||
.entrySet().stream().map(entry -> envelope(entry.getKey(), entry.getValue(),
|
||||
palletRows.stream().filter(item -> entry.getKey().equals(item.getAccountId())).toList(),
|
||||
standardLabels.stream().filter(item -> entry.getKey().equals(item.getAccountId())).toList(), cartonMarks))
|
||||
standardLabels.stream().filter(item -> entry.getKey().equals(item.getAccountId())).toList(), cartonMarks,
|
||||
palletManifests))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -98,14 +100,16 @@ public class LoadingCompletedOutboxService {
|
||||
|
||||
private EventEnvelope envelope(long accountId, List<Row> rows, List<PalletRow> palletRows,
|
||||
List<com.baiyue.firstleg.mapper.LoadingPalletLabelMapper.LabelRow> standardLabels,
|
||||
Map<Long, List<String>> cartonMarks) {
|
||||
Map<Long, List<String>> cartonMarks,
|
||||
Map<String, PalletManifest> palletManifests) {
|
||||
Row first = rows.get(0);
|
||||
Map<Long, List<Row>> byInbound = rows.stream().collect(java.util.stream.Collectors.groupingBy(
|
||||
Row::getSourceInboundId, java.util.TreeMap::new, java.util.stream.Collectors.toList()));
|
||||
List<FirstLegLoadingPlanCompletedV2.Order> orders = byInbound.values().stream()
|
||||
.map(value -> order(value, palletRows.stream()
|
||||
.filter(item -> Objects.equals(item.getSourceInboundId(), value.get(0).getSourceInboundId())).toList(),
|
||||
standardLabels.stream().filter(item -> Objects.equals(item.getInboundId(), value.get(0).getSourceInboundId())).toList(), cartonMarks)).toList();
|
||||
standardLabels.stream().filter(item -> Objects.equals(item.getInboundId(), value.get(0).getSourceInboundId())).toList(),
|
||||
cartonMarks, palletManifests)).toList();
|
||||
var payload = new FirstLegLoadingPlanCompletedV2(String.valueOf(first.getLoadingPlanId()),
|
||||
first.getPlanNo(), string(first.getContainerBookingId()),
|
||||
ContainerNumbers.requireCanonical(first.getContainerNo()), first.getCabinetNo(),
|
||||
@@ -123,7 +127,8 @@ public class LoadingCompletedOutboxService {
|
||||
|
||||
private FirstLegLoadingPlanCompletedV2.Order order(List<Row> values, List<PalletRow> palletRows,
|
||||
List<com.baiyue.firstleg.mapper.LoadingPalletLabelMapper.LabelRow> standardLabels,
|
||||
Map<Long, List<String>> cartonMarks) {
|
||||
Map<Long, List<String>> cartonMarks,
|
||||
Map<String, PalletManifest> palletManifests) {
|
||||
List<Row> sorted = values.stream().sorted(Comparator.comparing(Row::getSourceInboundDetailId)).toList();
|
||||
Row first = sorted.get(0);
|
||||
List<FirstLegLoadingPlanCompletedV2.Item> items = sorted.stream().map(row ->
|
||||
@@ -138,7 +143,7 @@ public class LoadingCompletedOutboxService {
|
||||
String fulfillmentType = fulfillmentType(sorted);
|
||||
String orderType = dropship(fulfillmentType) ? "ONE_CLICK_DROPSHIP" : "FIRST_LEG";
|
||||
List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> pallets = dropship(fulfillmentType)
|
||||
? palletSnapshots(sorted, palletRows) : standardLabels.stream()
|
||||
? palletSnapshots(sorted, palletRows, palletManifests) : standardLabels.stream()
|
||||
.map(label -> new FirstLegLoadingPlanCompletedV2.PalletSnapshot(label.getPalletCode(), List.of())).toList();
|
||||
if ("ONE_CLICK_DROPSHIP".equals(orderType) && pallets.isEmpty()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "一键代发入仓单缺少托盘冻结快照");
|
||||
@@ -257,10 +262,11 @@ public class LoadingCompletedOutboxService {
|
||||
private record LabelPoolKey(long accountId, long inboundId) { }
|
||||
private record InboundPalletKey(long accountId, long inboundId) { }
|
||||
|
||||
private List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> palletSnapshots(List<Row> values, List<PalletRow> source) {
|
||||
private List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> palletSnapshots(List<Row> values, List<PalletRow> source,
|
||||
Map<String, PalletManifest> manifests) {
|
||||
if (source.isEmpty()) return List.of();
|
||||
if (source.stream().anyMatch(item -> Boolean.TRUE.equals(item.getActualBinding()))) {
|
||||
return actualPalletSnapshots(values, source);
|
||||
return actualPalletSnapshots(values, source, manifests);
|
||||
}
|
||||
Map<String, List<PalletRow>> byPallet = source.stream().collect(java.util.stream.Collectors.groupingBy(
|
||||
p -> (p.getAllocationNo() == null ? "" : p.getAllocationNo()) + "|" + p.getPalletCode(),
|
||||
@@ -314,7 +320,7 @@ public class LoadingCompletedOutboxService {
|
||||
|
||||
/** 现场逐箱扫描已给出精确箱到托关系,不再使用旧的按托盘容量顺序推算。 */
|
||||
private List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> actualPalletSnapshots(
|
||||
List<Row> values, List<PalletRow> source) {
|
||||
List<Row> values, List<PalletRow> source, Map<String, PalletManifest> manifests) {
|
||||
Map<Long, Row> details = values.stream().collect(java.util.stream.Collectors.toMap(
|
||||
Row::getSourceInboundDetailId, value -> value, (left, right) -> left, LinkedHashMap::new));
|
||||
Map<String, List<PalletRow>> byPallet = source.stream().collect(java.util.stream.Collectors.groupingBy(
|
||||
@@ -334,14 +340,17 @@ public class LoadingCompletedOutboxService {
|
||||
if (marks.size() != detailRows.getValue().size()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "现场托盘包含空箱唛或重复箱唛");
|
||||
}
|
||||
int quantity = detail.getProductQuantity() == null ? marks.size()
|
||||
: (int) Math.round((double) detail.getProductQuantity() * marks.size()
|
||||
/ Math.max(1, detail.getPackageCount()));
|
||||
int quantity = detailRows.getValue().stream().map(PalletRow::getActualSkuQuantity)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
||||
if (quantity <= 0) throw new BizException(ResultCode.CONFLICT, "一件代发现场托盘缺少实点 SKU 数量");
|
||||
skus.add(new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(detail.getSourceInboundDetailId().toString(), detail.getSkuCode(),
|
||||
detail.getProductNameZh(), marks.size(), quantity, marks));
|
||||
actualPackages += marks.size();
|
||||
}
|
||||
result.add(new FirstLegLoadingPlanCompletedV2.PalletSnapshot(pallet.getKey(), skus));
|
||||
PalletManifest manifest = manifests.get(pallet.getKey());
|
||||
if (manifest == null) throw new BizException(ResultCode.CONFLICT, "一件代发托盘缺少完整清单摘要");
|
||||
result.add(new FirstLegLoadingPlanCompletedV2.PalletSnapshot(pallet.getKey(), skus,
|
||||
manifest.version(), manifest.packageCount(), manifest.fragmentCount(), manifest.hash()));
|
||||
}
|
||||
if (actualPackages != sumInt(values, Row::getPackageCount)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "一件代发现场托盘箱唛数与实装箱数不一致");
|
||||
@@ -349,12 +358,32 @@ public class LoadingCompletedOutboxService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, PalletManifest> palletManifests(List<PalletRow> rows) {
|
||||
Map<String, List<PalletRow>> grouped = rows.stream().filter(row -> Boolean.TRUE.equals(row.getActualBinding()))
|
||||
.collect(java.util.stream.Collectors.groupingBy(PalletRow::getPalletCode, LinkedHashMap::new,
|
||||
java.util.stream.Collectors.toList()));
|
||||
Map<String, PalletManifest> result = new LinkedHashMap<>();
|
||||
grouped.forEach((palletNo, values) -> {
|
||||
List<PalletRow> sorted = values.stream().sorted(Comparator
|
||||
.comparing(PalletRow::getAccountId).thenComparing(PalletRow::getSourceInboundId)
|
||||
.thenComparing(PalletRow::getSourceInboundDetailId).thenComparing(PalletRow::getBoxCode)).toList();
|
||||
String canonical = sorted.stream().map(value -> value.getAccountId() + "|" + value.getSourceInboundId()
|
||||
+ "|" + value.getSourceInboundDetailId() + "|" + value.getSkuCode() + "|" + value.getBoxCode()
|
||||
+ "|" + value.getActualSkuQuantity()).collect(java.util.stream.Collectors.joining("\n"));
|
||||
int fragments = (int) sorted.stream().map(PalletRow::getAccountId).distinct().count();
|
||||
result.put(palletNo, new PalletManifest(1, sorted.size(), fragments,
|
||||
EventPayloadHash.sha256(canonical.getBytes(StandardCharsets.UTF_8))));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean dropship(String value) {
|
||||
return "ONE_CLICK_DROPSHIP".equals(value);
|
||||
}
|
||||
|
||||
private record DetailSnapshot(Long detailId, String skuCode, String productName,
|
||||
Integer packageCount, Integer productQuantity, List<String> marks) {}
|
||||
private record PalletManifest(int version, int packageCount, int fragmentCount, String hash) {}
|
||||
|
||||
private void insert(EventEnvelope envelope) {
|
||||
InboundOutboxEntity value = new InboundOutboxEntity();
|
||||
|
||||
Reference in New Issue
Block a user