|
|
|
@@ -3642,98 +3642,128 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A |
|
|
|
|
|
|
|
// ✅ 查询该 pick order 的所有 lines 和 lots |
|
|
|
val linesSql = """ |
|
|
|
SELECT |
|
|
|
po.id as pickOrderId, |
|
|
|
po.code as pickOrderCode, |
|
|
|
po.consoCode as pickOrderConsoCode, |
|
|
|
DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, |
|
|
|
po.type as pickOrderType, |
|
|
|
po.status as pickOrderStatus, |
|
|
|
po.assignTo as pickOrderAssignTo, |
|
|
|
|
|
|
|
pol.id as pickOrderLineId, |
|
|
|
pol.qty as pickOrderLineRequiredQty, |
|
|
|
pol.status as pickOrderLineStatus, |
|
|
|
|
|
|
|
i.id as itemId, |
|
|
|
i.code as itemCode, |
|
|
|
i.name as itemName, |
|
|
|
uc.code as uomCode, |
|
|
|
uc.udfudesc as uomDesc, |
|
|
|
uc.udfShortDesc as uomShortDesc, |
|
|
|
|
|
|
|
ill.id as lotId, |
|
|
|
il.lotNo, |
|
|
|
DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, |
|
|
|
w.name as location, |
|
|
|
COALESCE(uc.udfudesc, 'N/A') as stockUnit, |
|
|
|
w.`order` as routerIndex, |
|
|
|
w.code as routerRoute, |
|
|
|
|
|
|
|
CASE |
|
|
|
WHEN sol.status = 'rejected' THEN NULL |
|
|
|
ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) |
|
|
|
END as availableQty, |
|
|
|
|
|
|
|
COALESCE(spl.qty, 0) as requiredQty, |
|
|
|
COALESCE(sol.qty, 0) as actualPickQty, |
|
|
|
spl.id as suggestedPickLotId, |
|
|
|
ill.status as lotStatus, |
|
|
|
sol.id as stockOutLineId, |
|
|
|
sol.status as stockOutLineStatus, |
|
|
|
COALESCE(sol.qty, 0) as stockOutLineQty, |
|
|
|
COALESCE(ill.inQty, 0) as inQty, |
|
|
|
COALESCE(ill.outQty, 0) as outQty, |
|
|
|
COALESCE(ill.holdQty, 0) as holdQty, |
|
|
|
|
|
|
|
CASE |
|
|
|
WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' |
|
|
|
WHEN sol.status = 'rejected' THEN 'rejected' |
|
|
|
WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' |
|
|
|
WHEN ill.status = 'unavailable' THEN 'status_unavailable' |
|
|
|
ELSE 'available' |
|
|
|
END as lotAvailability, |
|
|
|
|
|
|
|
CASE |
|
|
|
WHEN sol.status = 'completed' THEN 'completed' |
|
|
|
WHEN sol.status = 'rejected' THEN 'rejected' |
|
|
|
WHEN sol.status = 'created' THEN 'pending' |
|
|
|
ELSE 'pending' |
|
|
|
END as processingStatus |
|
|
|
|
|
|
|
FROM fpsmsdb.pick_order po |
|
|
|
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false |
|
|
|
JOIN fpsmsdb.items i ON i.id = pol.itemId |
|
|
|
LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId |
|
|
|
SELECT |
|
|
|
po.id as pickOrderId, |
|
|
|
po.code as pickOrderCode, |
|
|
|
po.consoCode as pickOrderConsoCode, |
|
|
|
DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, |
|
|
|
po.type as pickOrderType, |
|
|
|
po.status as pickOrderStatus, |
|
|
|
po.assignTo as pickOrderAssignTo, |
|
|
|
|
|
|
|
LEFT JOIN ( |
|
|
|
SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId |
|
|
|
FROM fpsmsdb.suggested_pick_lot spl |
|
|
|
UNION |
|
|
|
SELECT sol.pickOrderLineId, sol.inventoryLotLineId |
|
|
|
FROM fpsmsdb.stock_out_line sol |
|
|
|
WHERE sol.deleted = false |
|
|
|
) ll ON ll.pickOrderLineId = pol.id |
|
|
|
pol.id as pickOrderLineId, |
|
|
|
pol.qty as pickOrderLineRequiredQty, |
|
|
|
pol.status as pickOrderLineStatus, |
|
|
|
|
|
|
|
LEFT JOIN fpsmsdb.suggested_pick_lot spl |
|
|
|
ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId |
|
|
|
LEFT JOIN fpsmsdb.stock_out_line sol |
|
|
|
ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ll.lotLineId AND sol.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId |
|
|
|
i.id as itemId, |
|
|
|
i.code as itemCode, |
|
|
|
i.name as itemName, |
|
|
|
uc.code as uomCode, |
|
|
|
uc.udfudesc as uomDesc, |
|
|
|
uc.udfShortDesc as uomShortDesc, |
|
|
|
|
|
|
|
WHERE po.id = :pickOrderId |
|
|
|
AND po.deleted = false |
|
|
|
ORDER BY |
|
|
|
COALESCE(w.`order`, 999999) ASC, |
|
|
|
pol.id ASC, |
|
|
|
il.lotNo ASC |
|
|
|
ill.id as lotId, |
|
|
|
il.lotNo, |
|
|
|
DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, |
|
|
|
w.name as location, |
|
|
|
COALESCE(uc.udfudesc, 'N/A') as stockUnit, |
|
|
|
w.`order` as routerIndex, |
|
|
|
w.code as routerRoute, |
|
|
|
|
|
|
|
CASE |
|
|
|
WHEN sol.status = 'rejected' THEN NULL |
|
|
|
ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) |
|
|
|
END as availableQty, |
|
|
|
|
|
|
|
COALESCE(spl.qty, 0) as requiredQty, |
|
|
|
COALESCE(sol.qty, 0) as actualPickQty, |
|
|
|
spl.id as suggestedPickLotId, |
|
|
|
ill.status as lotStatus, |
|
|
|
sol.id as stockOutLineId, |
|
|
|
sol.status as stockOutLineStatus, |
|
|
|
COALESCE(sol.qty, 0) as stockOutLineQty, |
|
|
|
COALESCE(ill.inQty, 0) as inQty, |
|
|
|
COALESCE(ill.outQty, 0) as outQty, |
|
|
|
COALESCE(ill.holdQty, 0) as holdQty, |
|
|
|
|
|
|
|
CASE |
|
|
|
WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' |
|
|
|
WHEN sol.status = 'rejected' THEN 'rejected' |
|
|
|
WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' |
|
|
|
WHEN ill.status = 'unavailable' THEN 'status_unavailable' |
|
|
|
ELSE 'available' |
|
|
|
END as lotAvailability, |
|
|
|
|
|
|
|
CASE |
|
|
|
WHEN sol.status = 'completed' THEN 'completed' |
|
|
|
WHEN sol.status = 'rejected' THEN 'rejected' |
|
|
|
WHEN sol.status = 'created' THEN 'pending' |
|
|
|
ELSE 'pending' |
|
|
|
END as processingStatus, |
|
|
|
|
|
|
|
-- 新增:stockouts 专用字段(不依赖 ll) |
|
|
|
sol_any.id as stockOutLineId_any, |
|
|
|
sol_any.status as stockOutLineStatus_any, |
|
|
|
COALESCE(sol_any.qty, 0) as stockOutLineQty_any, |
|
|
|
sol_any.inventoryLotLineId as lotId_any, |
|
|
|
il_any.lotNo as lotNo_any, |
|
|
|
w_any.name as location_any, |
|
|
|
-- 仅当有 lot 时可计算,没 lot 时为 NULL |
|
|
|
CASE |
|
|
|
WHEN ill_any.id IS NULL THEN NULL |
|
|
|
ELSE (COALESCE(ill_any.inQty,0) - COALESCE(ill_any.outQty,0) - COALESCE(ill_any.holdQty,0)) |
|
|
|
END as availableQty_any |
|
|
|
FROM fpsmsdb.pick_order po |
|
|
|
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false |
|
|
|
JOIN fpsmsdb.items i ON i.id = pol.itemId |
|
|
|
LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId |
|
|
|
|
|
|
|
LEFT JOIN ( |
|
|
|
SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId |
|
|
|
FROM fpsmsdb.suggested_pick_lot spl |
|
|
|
UNION |
|
|
|
SELECT sol.pickOrderLineId, sol.inventoryLotLineId |
|
|
|
FROM fpsmsdb.stock_out_line sol |
|
|
|
WHERE sol.deleted = false |
|
|
|
) ll ON ll.pickOrderLineId = pol.id |
|
|
|
|
|
|
|
LEFT JOIN fpsmsdb.suggested_pick_lot spl |
|
|
|
ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId |
|
|
|
|
|
|
|
-- lots 用(保留原逻辑) |
|
|
|
LEFT JOIN fpsmsdb.stock_out_line sol |
|
|
|
ON sol.pickOrderLineId = pol.id |
|
|
|
AND ( (sol.inventoryLotLineId = ll.lotLineId) |
|
|
|
OR (sol.inventoryLotLineId IS NULL AND ll.lotLineId IS NULL) ) |
|
|
|
AND sol.deleted = false |
|
|
|
|
|
|
|
LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId |
|
|
|
|
|
|
|
-- stockouts 用(不依赖 ll) |
|
|
|
LEFT JOIN fpsmsdb.stock_out_line sol_any |
|
|
|
ON sol_any.pickOrderLineId = pol.id |
|
|
|
AND sol_any.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.inventory_lot_line ill_any |
|
|
|
ON ill_any.id = sol_any.inventoryLotLineId AND ill_any.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.inventory_lot il_any |
|
|
|
ON il_any.id = ill_any.inventoryLotId AND il_any.deleted = false |
|
|
|
LEFT JOIN fpsmsdb.warehouse w_any |
|
|
|
ON w_any.id = ill_any.warehouseId |
|
|
|
|
|
|
|
WHERE po.id = :pickOrderId |
|
|
|
AND po.deleted = false |
|
|
|
ORDER BY |
|
|
|
COALESCE(w.`order`, 999999) ASC, |
|
|
|
pol.id ASC, |
|
|
|
il.lotNo ASC |
|
|
|
""".trimIndent() |
|
|
|
|
|
|
|
val linesResults = jdbcDao.queryForList(linesSql, mapOf("pickOrderId" to pickOrderId)) |
|
|
|
println("🔍 Pick order $pickOrderId has ${linesResults.size} line-lot records") |
|
|
|
|
|
|
|
println("DEBUG polIds in linesResults: " + linesResults.map { it["pickOrderLineId"] }.distinct()) |
|
|
|
println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["stockOutLineId"]?.let { _ -> it["pickOrderLineId"] } }.distinct()) |
|
|
|
// ✅ 按 pickOrderLineId 分组 |
|
|
|
val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() } |
|
|
|
|
|
|
|
@@ -3743,7 +3773,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A |
|
|
|
if (firstLineRow == null) { |
|
|
|
null |
|
|
|
} else { |
|
|
|
// ✅ 构建 lots 列表 |
|
|
|
// ✅ lots:仍然是“有批次”的明细(保留你当前的逻辑) |
|
|
|
val lots = if (lineRows.any { it["lotId"] != null }) { |
|
|
|
lineRows.filter { it["lotId"] != null }.map { lotRow -> |
|
|
|
mapOf( |
|
|
|
@@ -3780,7 +3810,27 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A |
|
|
|
} else { |
|
|
|
emptyList() |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:stockouts(包括 inventoryLotLineId 为 null 的出库行) |
|
|
|
// stockouts:包含所有出库行(即使 lot 为空) |
|
|
|
val stockouts = lineRows |
|
|
|
.filter { it["stockOutLineId_any"] != null } |
|
|
|
.map { row -> |
|
|
|
val noLot = (row["lotId_any"] == null) |
|
|
|
mapOf( |
|
|
|
"id" to row["stockOutLineId_any"], |
|
|
|
"status" to row["stockOutLineStatus_any"], |
|
|
|
"qty" to row["stockOutLineQty_any"], |
|
|
|
"lotId" to row["lotId_any"], // 可能为 null |
|
|
|
"lotNo" to (row["lotNo_any"] ?: ""), // 用 *_any |
|
|
|
"location" to (row["location_any"] ?: ""), // 用 *_any |
|
|
|
"availableQty" to row["availableQty_any"], // 用 *_any |
|
|
|
"noLot" to noLot |
|
|
|
) |
|
|
|
//println("DEBUG sol_any stockOutLineId: " + row["stockOutLineId_any"]) |
|
|
|
//println("DEBUG sol_any pickOrderLineId: " + row["pickOrderLineId"]) |
|
|
|
} |
|
|
|
|
|
|
|
mapOf( |
|
|
|
"id" to lineId, |
|
|
|
"requiredQty" to firstLineRow["pickOrderLineRequiredQty"], |
|
|
|
@@ -3793,7 +3843,9 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A |
|
|
|
"uomDesc" to firstLineRow["uomDesc"], |
|
|
|
"uomShortDesc" to firstLineRow["uomShortDesc"], |
|
|
|
), |
|
|
|
"lots" to lots |
|
|
|
"lots" to lots, |
|
|
|
// ✅ 新增字段 |
|
|
|
"stockouts" to stockouts |
|
|
|
) |
|
|
|
} |
|
|
|
}.filterNotNull() |
|
|
|
|