| @@ -3340,125 +3340,142 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||
| val pickOrderIdsStr = pickOrderIds.joinToString(",") | |||
| val sql = """ | |||
| SELECT | |||
| -- Pick Order Information | |||
| 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, | |||
| -- Pick Order Line Information | |||
| pol.id as pickOrderLineId, | |||
| pol.qty as pickOrderLineRequiredQty, | |||
| pol.status as pickOrderLineStatus, | |||
| -- Item Information | |||
| i.id as itemId, | |||
| i.code as itemCode, | |||
| i.name as itemName, | |||
| uc.code as uomCode, | |||
| uc.udfudesc as uomDesc, | |||
| uc.udfShortDesc as uomShortDesc, | |||
| -- Lot Information | |||
| 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, | |||
| -- Router Information | |||
| r.id as routerId, | |||
| r.index as routerIndex, | |||
| r.route as routerRoute, | |||
| r.area as routerArea, | |||
| -- ✅ FIXED: Set quantities to NULL for rejected lots | |||
| CASE | |||
| WHEN sol.status = 'rejected' THEN NULL | |||
| ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) | |||
| END as availableQty, | |||
| -- Required quantity for this lot | |||
| COALESCE(spl.qty, 0) as requiredQty, | |||
| -- Actual picked quantity | |||
| COALESCE(sol.qty, 0) as actualPickQty, | |||
| -- Suggested pick lot information | |||
| spl.id as suggestedPickLotId, | |||
| ill.status as lotStatus, | |||
| -- Stock out line information | |||
| sol.id as stockOutLineId, | |||
| sol.status as stockOutLineStatus, | |||
| COALESCE(sol.qty, 0) as stockOutLineQty, | |||
| -- Additional detailed fields | |||
| COALESCE(ill.inQty, 0) as inQty, | |||
| COALESCE(ill.outQty, 0) as outQty, | |||
| COALESCE(ill.holdQty, 0) as holdQty, | |||
| COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId, | |||
| ill.inventoryLotId as debugInventoryLotId, | |||
| -- Calculate total picked quantity by ALL pick orders for this lot | |||
| COALESCE(( | |||
| SELECT SUM(sol_all.qty) | |||
| FROM fpsmsdb.stock_out_line sol_all | |||
| WHERE sol_all.inventoryLotLineId = ill.id | |||
| AND sol_all.deleted = false | |||
| AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed') | |||
| ), 0) as totalPickedByAllPickOrders, | |||
| -- Calculate remaining available quantity correctly | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, | |||
| -- Lot availability status | |||
| 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, | |||
| -- Processing status | |||
| 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 | |||
| JOIN fpsmsdb.items i ON i.id = pol.itemId | |||
| LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId | |||
| LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId | |||
| LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id | |||
| LEFT JOIN fpsmsdb.router r ON r.inventoryLotId = ill.id AND r.deleted = false | |||
| LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false | |||
| WHERE po.deleted = false | |||
| AND po.id IN ($pickOrderIdsStr) | |||
| AND pol.deleted = false | |||
| AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED') | |||
| AND po.assignTo = :userId | |||
| AND ill.deleted = false | |||
| AND il.deleted = false | |||
| AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) | |||
| ORDER BY | |||
| CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, | |||
| COALESCE(r.index, 0) ASC, | |||
| po.code ASC, | |||
| i.code ASC, | |||
| il.expiryDate ASC, | |||
| il.lotNo ASC | |||
| """.trimIndent() | |||
| SELECT | |||
| -- Pick Order Information | |||
| 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, | |||
| -- Pick Order Line Information | |||
| pol.id as pickOrderLineId, | |||
| pol.qty as pickOrderLineRequiredQty, | |||
| pol.status as pickOrderLineStatus, | |||
| -- Item Information | |||
| i.id as itemId, | |||
| i.code as itemCode, | |||
| i.name as itemName, | |||
| uc.code as uomCode, | |||
| uc.udfudesc as uomDesc, | |||
| uc.udfShortDesc as uomShortDesc, | |||
| -- Lot Information | |||
| 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, | |||
| -- Router Information | |||
| r.id as routerId, | |||
| r.index as routerIndex, | |||
| r.route as routerRoute, | |||
| r.area as routerArea, | |||
| -- ✅ FIXED: Set quantities to NULL for rejected lots | |||
| CASE | |||
| WHEN sol.status = 'rejected' THEN NULL | |||
| ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) | |||
| END as availableQty, | |||
| -- Required quantity for this lot | |||
| COALESCE(spl.qty, 0) as requiredQty, | |||
| -- Actual picked quantity | |||
| COALESCE(sol.qty, 0) as actualPickQty, | |||
| -- Suggested pick lot information | |||
| spl.id as suggestedPickLotId, | |||
| ill.status as lotStatus, | |||
| -- Stock out line information | |||
| sol.id as stockOutLineId, | |||
| sol.status as stockOutLineStatus, | |||
| COALESCE(sol.qty, 0) as stockOutLineQty, | |||
| -- Additional detailed fields | |||
| COALESCE(ill.inQty, 0) as inQty, | |||
| COALESCE(ill.outQty, 0) as outQty, | |||
| COALESCE(ill.holdQty, 0) as holdQty, | |||
| COALESCE(spl.suggestedLotLineId, sol.inventoryLotLineId) as debugSuggestedLotLineId, | |||
| ill.inventoryLotId as debugInventoryLotId, | |||
| -- Calculate total picked quantity by ALL pick orders for this lot | |||
| COALESCE(( | |||
| SELECT SUM(sol_all.qty) | |||
| FROM fpsmsdb.stock_out_line sol_all | |||
| WHERE sol_all.inventoryLotLineId = ill.id | |||
| AND sol_all.deleted = false | |||
| AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed') | |||
| ), 0) as totalPickedByAllPickOrders, | |||
| -- Calculate remaining available quantity correctly | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, | |||
| -- Lot availability status | |||
| 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, | |||
| -- Processing status | |||
| 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 | |||
| JOIN fpsmsdb.items i ON i.id = pol.itemId | |||
| LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId | |||
| -- Base lot links: all lot lines referenced by either suggestions or stock out lines for this POL | |||
| LEFT JOIN ( | |||
| SELECT spl.pickOrderLineId AS 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 | |||
| -- Re-bind spl/sol strictly on the same lot line | |||
| 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 | |||
| LEFT JOIN fpsmsdb.router r ON r.inventoryLotId = ill.id AND r.deleted = false | |||
| LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| WHERE po.deleted = false | |||
| AND po.id IN ($pickOrderIdsStr) | |||
| AND pol.deleted = false | |||
| AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED') | |||
| AND po.assignTo = :userId | |||
| AND ill.deleted = false | |||
| AND il.deleted = false | |||
| AND ll.lotLineId IS NOT NULL | |||
| AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) | |||
| ORDER BY | |||
| CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, | |||
| COALESCE(r.index, 0) ASC, | |||
| po.code ASC, | |||
| i.code ASC, | |||
| il.expiryDate ASC, | |||
| il.lotNo ASC | |||
| """.trimIndent() | |||
| println("�� Executing SQL for hierarchical structure: $sql") | |||
| println("�� With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") | |||