fix(dispatch): calculate received pallet cargo from current stock
This commit is contained in:
+57
-6
@@ -40,6 +40,55 @@ public interface DispatchCandidateMapper {
|
||||
"COALESCE((SELECT CASE WHEN COUNT(*)>0 AND COUNT(*)=COUNT(d.gross_weight_kg) AND MIN(d.gross_weight_kg)>0 " +
|
||||
"THEN SUM(d.gross_weight_kg) END FROM ll_order_detail d WHERE d.customer_id=ip.customer_id AND d.order_id=l.order_id)," +
|
||||
"(SELECT inbound.gross_weight_kg FROM ll_inbound_order inbound WHERE inbound.id=l.order_id AND inbound.customer_id=ip.customer_id LIMIT 1))";
|
||||
/** 收货毛重按逐票确认箱数分摊;零贡献不占重量,任一有效来源缺失毛重时整托保持未知。 */
|
||||
String RECEIVED_GROSS_WEIGHT =
|
||||
"CASE WHEN MIN(CASE WHEN a.counted_package_count=0 THEN 1 WHEN " + SOURCE_GROSS_WEIGHT +
|
||||
" >0 AND l.required_package_count>0 AND a.counted_package_count>0 THEN 1 ELSE 0 END)=1 THEN " +
|
||||
"ROUND(SUM(CASE WHEN a.counted_package_count=0 THEN 0 ELSE " + SOURCE_GROSS_WEIGHT +
|
||||
" * a.counted_package_count / NULLIF(l.required_package_count,0) END),3) END";
|
||||
/** 体积采用收货端记录的逐来源体积,不将计划均分值或部分来源合计冒充完整收货量。 */
|
||||
String RECEIVED_VOLUME =
|
||||
"CASE WHEN MIN(CASE WHEN a.counted_package_count=0 OR a.actual_volume_cbm>0 THEN 1 ELSE 0 END)=1 " +
|
||||
"THEN NULLIF(SUM(CASE WHEN a.counted_package_count=0 THEN 0 ELSE a.actual_volume_cbm END),0) END";
|
||||
/** 已收货候选与箱数共用当前库存范围;入仓身份沿余额关联,不能把尾程订单ID当转运入仓ID。 */
|
||||
String CURRENT_STOCK_FROM =
|
||||
" FROM ll_inventory_position stock LEFT JOIN ll_inventory_balance balance " +
|
||||
"ON balance.id=stock.balance_id AND balance.customer_id=stock.customer_id " +
|
||||
"LEFT JOIN ll_inbound_order inbound ON inbound.id=balance.inbound_order_id AND inbound.customer_id=stock.customer_id " +
|
||||
"WHERE stock.customer_id=ip.customer_id AND stock.pallet_id=p.warehouse_pallet_id AND stock.available_packages>0";
|
||||
String CURRENT_SOURCE_WEIGHT =
|
||||
"COALESCE((SELECT CASE WHEN COUNT(*)>0 AND COUNT(*)=COUNT(d.gross_weight_kg) AND MIN(d.gross_weight_kg)>0 " +
|
||||
"THEN SUM(d.gross_weight_kg) END FROM ll_order_detail d WHERE d.customer_id=stock.customer_id AND d.order_id=inbound.id),inbound.gross_weight_kg)";
|
||||
/** 拆合托和部分出库后的估算按各真实入仓来源剩余箱数汇总,任一来源不明时不返回部分合计。 */
|
||||
String CURRENT_STOCK_WEIGHT =
|
||||
"(SELECT CASE WHEN MIN(CASE WHEN inbound.package_count>0 AND " + CURRENT_SOURCE_WEIGHT +
|
||||
" >0 THEN 1 ELSE 0 END)=1 THEN ROUND(SUM(" + CURRENT_SOURCE_WEIGHT +
|
||||
" * stock.available_packages / NULLIF(inbound.package_count,0)),3) END" + CURRENT_STOCK_FROM + ")";
|
||||
String CURRENT_STOCK_VOLUME =
|
||||
"(SELECT CASE WHEN MIN(CASE WHEN inbound.package_count>0 AND inbound.volume_cbm>0 THEN 1 ELSE 0 END)=1 " +
|
||||
"THEN ROUND(SUM(inbound.volume_cbm * stock.available_packages / NULLIF(inbound.package_count,0)),4) END" + CURRENT_STOCK_FROM + ")";
|
||||
/** 仅当前整托仍为同一转运来源、全量可用且未拆合换绑时,保留该托实测;不按订单总重均摊已有实测。 */
|
||||
String CURRENT_MEASUREMENT_FROM =
|
||||
" FROM ll_warehouse_transfer_pallet measurement JOIN ll_warehouse_transfer_order transfer " +
|
||||
"ON transfer.id=measurement.transfer_order_id AND transfer.customer_id=measurement.customer_id " +
|
||||
"WHERE measurement.customer_id=ip.customer_id AND measurement.warehouse_pallet_id=p.warehouse_pallet_id " +
|
||||
"AND transfer.lastleg_order_id=ip.source_order_id AND measurement.status='COMPLETED' AND measurement.completed_at IS NOT NULL " +
|
||||
"AND measurement.carton_count>0 AND measurement.carton_count=(SELECT SUM(stock.available_packages) FROM ll_inventory_position stock " +
|
||||
"WHERE stock.customer_id=ip.customer_id AND stock.pallet_id=p.warehouse_pallet_id) " +
|
||||
"AND NOT EXISTS(SELECT 1 FROM ll_inventory_position stock LEFT JOIN ll_inventory_balance balance " +
|
||||
"ON balance.id=stock.balance_id AND balance.customer_id=stock.customer_id " +
|
||||
"WHERE stock.pallet_id=p.warehouse_pallet_id AND (stock.available_packages+stock.hold_packages+stock.allocated_packages+stock.picked_packages+stock.receiving_packages)>0 " +
|
||||
"AND (stock.customer_id!=ip.customer_id OR balance.inbound_order_id IS NULL OR balance.inbound_order_id!=transfer.inbound_order_id " +
|
||||
"OR stock.overseas_warehouse_id!=transfer.operating_warehouse_id OR stock.hold_packages+stock.allocated_packages+stock.picked_packages+stock.receiving_packages>0)) " +
|
||||
"AND NOT EXISTS(SELECT 1 FROM ll_inventory_ledger movement WHERE movement.pallet_id=p.warehouse_pallet_id " +
|
||||
"AND movement.created_at>=measurement.completed_at AND movement.business_type IN " +
|
||||
"('RELABEL_PALLET_DISASSEMBLE','PALLET_ASSEMBLE','PALLET_DISASSEMBLE','PALLET_SWAP','PALLET_UNBIND'))";
|
||||
String CURRENT_GROSS_WEIGHT =
|
||||
"COALESCE((SELECT CASE WHEN COUNT(*)=1 AND MAX(measurement.gross_weight_kg)>0 THEN MAX(measurement.gross_weight_kg) END" +
|
||||
CURRENT_MEASUREMENT_FROM + ")," + CURRENT_STOCK_WEIGHT + ")";
|
||||
String CURRENT_VOLUME =
|
||||
"COALESCE((SELECT CASE WHEN COUNT(*)=1 AND MAX(measurement.volume_cbm)>0 THEN MAX(measurement.volume_cbm) END" +
|
||||
CURRENT_MEASUREMENT_FROM + ")," + CURRENT_STOCK_VOLUME + ")";
|
||||
@Select("<script>" +
|
||||
"SELECT DISTINCT customer_id FROM ll_inbound_plan WHERE id IN " +
|
||||
"<foreach collection='planIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
|
||||
@@ -60,11 +109,11 @@ public interface DispatchCandidateMapper {
|
||||
"MIN(l.order_id) AS orderId," +
|
||||
"MIN(ip.customer_id) AS customerId,MIN(o.customer_name) AS customerName," +
|
||||
"MIN(ip.plan_no) AS planNo,MIN(o.order_type) AS orderType,MIN(o.volume_weight_kg) AS volumeWeightKg," +
|
||||
"CASE WHEN MIN(CASE WHEN " + SOURCE_GROSS_WEIGHT +
|
||||
"CASE WHEN NOT " + PLAN_ONLY + " THEN " + CURRENT_GROSS_WEIGHT + " ELSE CASE WHEN MIN(CASE WHEN " + SOURCE_GROSS_WEIGHT +
|
||||
" >0 AND (COALESCE(l.volume_cbm,0)<=0 OR COALESCE(a.allocated_volume_cbm,0)>0) THEN 1 ELSE 0 END)=1 THEN " +
|
||||
"NULLIF(SUM(CASE WHEN COALESCE(l.volume_cbm,0)>0 THEN " + SOURCE_GROSS_WEIGHT +
|
||||
" * a.allocated_volume_cbm / l.volume_cbm ELSE " + SOURCE_GROSS_WEIGHT +
|
||||
" / NULLIF((SELECT COUNT(*) FROM ll_inbound_plan_pallet_allocation a2 WHERE a2.line_id=a.line_id),0) END),0) ELSE NULL END AS grossWeightKg," +
|
||||
" / NULLIF((SELECT COUNT(*) FROM ll_inbound_plan_pallet_allocation a2 WHERE a2.line_id=a.line_id),0) END),0) ELSE NULL END END AS grossWeightKg," +
|
||||
"MIN(ip.container_no) AS containerNo," +
|
||||
CONFIRMED_APPOINTMENT_AT + " AS appointmentAt,NULL AS expectedLoadingAt," +
|
||||
// 仓ID文本统一保留128字符,避免混合CASE的NONE排序规则及无长度CHAR截断;来源优先级与身份等值不变。
|
||||
@@ -88,7 +137,8 @@ public interface DispatchCandidateMapper {
|
||||
"CASE WHEN NOT " + PLAN_ONLY + " THEN COALESCE((SELECT SUM(pos.available_packages) " +
|
||||
"FROM ll_inventory_position pos WHERE pos.customer_id=ip.customer_id AND pos.pallet_id=p.warehouse_pallet_id),0) " +
|
||||
"ELSE COALESCE(SUM(CASE WHEN a.picked_package_count > 0 THEN a.picked_package_count " +
|
||||
"ELSE a.planned_package_count END),0) END AS packageCount,p.allocated_volume_cbm AS volumeCbm," +
|
||||
"ELSE a.planned_package_count END),0) END AS packageCount,CASE WHEN " + PLAN_ONLY +
|
||||
" THEN p.allocated_volume_cbm ELSE " + CURRENT_VOLUME + " END AS volumeCbm," +
|
||||
"CASE WHEN " + PLAN_ONLY + " THEN 'PLAN' ELSE 'RECEIVED' END AS sourceStage," +
|
||||
"(SELECT GROUP_CONCAT(DISTINCT c.carton_mark ORDER BY c.carton_mark SEPARATOR ',') " +
|
||||
"FROM ll_inbound_plan_carton c WHERE c.plan_pallet_id=p.id AND NOT EXISTS (" +
|
||||
@@ -170,11 +220,11 @@ public interface DispatchCandidateMapper {
|
||||
"MIN(l.order_id) AS orderId," +
|
||||
"MIN(ip.customer_id) AS customerId,MIN(o.customer_name) AS customerName," +
|
||||
"MIN(ip.plan_no) AS planNo,MIN(o.order_type) AS orderType,MIN(o.volume_weight_kg) AS volumeWeightKg," +
|
||||
"CASE WHEN MIN(CASE WHEN " + SOURCE_GROSS_WEIGHT +
|
||||
"CASE WHEN NOT " + PLAN_ONLY + " THEN " + CURRENT_GROSS_WEIGHT + " ELSE CASE WHEN MIN(CASE WHEN " + SOURCE_GROSS_WEIGHT +
|
||||
" >0 AND (COALESCE(l.volume_cbm,0)<=0 OR COALESCE(a.allocated_volume_cbm,0)>0) THEN 1 ELSE 0 END)=1 THEN " +
|
||||
"NULLIF(SUM(CASE WHEN COALESCE(l.volume_cbm,0)>0 THEN " + SOURCE_GROSS_WEIGHT +
|
||||
" * a.allocated_volume_cbm / l.volume_cbm ELSE " + SOURCE_GROSS_WEIGHT +
|
||||
" / NULLIF((SELECT COUNT(*) FROM ll_inbound_plan_pallet_allocation a2 WHERE a2.line_id=a.line_id),0) END),0) ELSE NULL END AS grossWeightKg," +
|
||||
" / NULLIF((SELECT COUNT(*) FROM ll_inbound_plan_pallet_allocation a2 WHERE a2.line_id=a.line_id),0) END),0) ELSE NULL END END AS grossWeightKg," +
|
||||
"MIN(ip.container_no) AS containerNo," +
|
||||
CONFIRMED_APPOINTMENT_AT + " AS appointmentAt,NULL AS expectedLoadingAt," +
|
||||
"CASE WHEN NOT " + PLAN_ONLY + " THEN (SELECT MIN(pos.overseas_warehouse_id) " +
|
||||
@@ -197,7 +247,8 @@ public interface DispatchCandidateMapper {
|
||||
"CASE WHEN NOT " + PLAN_ONLY + " THEN COALESCE((SELECT SUM(pos.available_packages) " +
|
||||
"FROM ll_inventory_position pos WHERE pos.customer_id=#{customerId} AND pos.pallet_id=p.warehouse_pallet_id),0) " +
|
||||
"ELSE COALESCE(SUM(CASE WHEN a.picked_package_count > 0 THEN a.picked_package_count " +
|
||||
"ELSE a.planned_package_count END),0) END AS packageCount,p.allocated_volume_cbm AS volumeCbm," +
|
||||
"ELSE a.planned_package_count END),0) END AS packageCount,CASE WHEN " + PLAN_ONLY +
|
||||
" THEN p.allocated_volume_cbm ELSE " + CURRENT_VOLUME + " END AS volumeCbm," +
|
||||
"CASE WHEN " + PLAN_ONLY + " THEN 'PLAN' ELSE 'RECEIVED' END AS sourceStage," +
|
||||
"(SELECT GROUP_CONCAT(DISTINCT c.carton_mark ORDER BY c.carton_mark SEPARATOR ',') " +
|
||||
"FROM ll_inbound_plan_carton c WHERE c.plan_pallet_id=p.id AND NOT EXISTS (" +
|
||||
|
||||
+6
@@ -21,6 +21,12 @@ public interface DispatchItemMapper extends BaseMapper<DispatchItem> {
|
||||
|
||||
record ReceiptDispatch(String dispatchNo, Long tripId, Long id) { }
|
||||
|
||||
/** 逐托交接数量与收货货量;显式写入NULL,避免未知毛重/体积继续保留旧计划值。 */
|
||||
@Update("UPDATE ll_dispatch_item SET warehouse_pallet_id=#{warehousePalletId},pallet_no=#{palletNo},"
|
||||
+ "source_stage=#{sourceStage},package_count=#{packageCount},gross_weight_kg=#{grossWeightKg},volume_cbm=#{volumeCbm} "
|
||||
+ "WHERE id=#{id} AND customer_id=#{customerId} AND dispatch_order_id=#{dispatchOrderId} AND warehouse_pallet_id IS NULL")
|
||||
int confirmReceipt(DispatchItem item);
|
||||
|
||||
/** 平台整车读取全客户明细,后续库存与费用仍按每条真实客户执行。 */
|
||||
@Select("SELECT * FROM ll_dispatch_item WHERE dispatch_order_id=#{dispatchOrderId} ORDER BY customer_id,id")
|
||||
List<DispatchItem> findWholeDispatch(@Param("dispatchOrderId") Long dispatchOrderId);
|
||||
|
||||
+5
-3
@@ -22,12 +22,14 @@ public interface DispatchSourceMapper {
|
||||
record ItemPicked(Long itemId,long packages) { }
|
||||
|
||||
/** 收货交接只读已确认逐票实收,不以可用库存、出货资格或计划均分反推箱数。 */
|
||||
@Select("""
|
||||
@Select("<script>" + """
|
||||
SELECT p.id AS planPalletId,p.plan_id AS planId,p.customer_id AS customerId,
|
||||
p.warehouse_pallet_id AS warehousePalletId,p.pallet_no AS palletNo,
|
||||
CAST(ip.overseas_warehouse_id AS CHAR(128)) AS overseasWarehouseId,
|
||||
MIN(l.destination_type) AS destinationType,MIN(l.fba_warehouse_code) AS fbaWarehouseCode,
|
||||
p.delivery_address AS deliveryAddress,SUM(a.counted_package_count) AS packageCount,'RECEIVED' AS sourceStage
|
||||
p.delivery_address AS deliveryAddress,SUM(a.counted_package_count) AS packageCount,'RECEIVED' AS sourceStage,
|
||||
""" + DispatchCandidateMapper.RECEIVED_GROSS_WEIGHT + " AS grossWeightKg,"
|
||||
+ DispatchCandidateMapper.RECEIVED_VOLUME + " AS volumeCbm " + """
|
||||
FROM ll_inbound_plan_pallet p JOIN ll_inbound_plan ip ON ip.id=p.plan_id AND ip.customer_id=p.customer_id
|
||||
JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=p.id AND a.customer_id=p.customer_id
|
||||
JOIN ll_inbound_plan_line l ON l.id=a.line_id AND l.customer_id=a.customer_id
|
||||
@@ -35,7 +37,7 @@ public interface DispatchSourceMapper {
|
||||
GROUP BY p.id,p.plan_id,p.customer_id,p.warehouse_pallet_id,p.pallet_no,ip.overseas_warehouse_id,p.delivery_address
|
||||
HAVING COUNT(*)=COUNT(a.counted_package_count) AND COUNT(DISTINCT l.destination_type)=1
|
||||
AND COUNT(DISTINCT COALESCE(l.fba_warehouse_code,''))=1
|
||||
""")
|
||||
""" + "</script>")
|
||||
@Options(useCache=false,flushCache=Options.FlushCachePolicy.TRUE)
|
||||
List<com.baiyue.lastleg.dispatch.model.DispatchPalletCandidate> confirmedPlan(@Param("customer") String customer,@Param("id") long id);
|
||||
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ public class DispatchPickingRepository {
|
||||
public int pick(DispatchPdaPick pick){return picks.insert(pick);}
|
||||
/** 本明细实际已扫码数量。 */
|
||||
public long itemPicked(String customer,long task,long item){return picks.totalItem(customer,task,item);}
|
||||
/** 自然收货后保存已经核实的同一计划来源实托身份。 */
|
||||
public int materialize(DispatchItem item){return items.updateById(item);}
|
||||
/** 自然收货后保存同一来源实托、箱数和货量,允许未知货量清除计划估算。 */
|
||||
public int materialize(DispatchItem item){return items.confirmReceipt(item);}
|
||||
/** 按真实客户取本车扫码,不借主单客户头。 */
|
||||
public List<DispatchPdaPick> picks(String customer,long dispatch){return picks.wholeDispatch(customer,dispatch);}
|
||||
}
|
||||
|
||||
+3
@@ -75,6 +75,9 @@ public class DispatchInboundReceiptService implements InboundDispatchReceiptGate
|
||||
for(var entry:received.entrySet()) {
|
||||
entry.getKey().setWarehousePalletId(entry.getValue().warehousePalletId());
|
||||
entry.getKey().setPackageCount(entry.getValue().packageCount());
|
||||
// 原预排的均分重量体积在确认入库时一并交接;未知值也必须覆盖旧计划快照。
|
||||
entry.getKey().setGrossWeightKg(entry.getValue().grossWeightKg());
|
||||
entry.getKey().setVolumeCbm(entry.getValue().volumeCbm());
|
||||
}
|
||||
for(var item:pending.stream().sorted(Comparator.comparing(DispatchItem::getPlanPalletId)).toList()) {
|
||||
if(sources.lockPlan(item.getCustomerId(),item.getPlanPalletId())==null)throw conflict("原计划来源已变化");
|
||||
|
||||
Reference in New Issue
Block a user