|
|
|
@@ -1041,19 +1041,41 @@ open class PickOrderService( |
|
|
|
val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( |
|
|
|
saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } |
|
|
|
) |
|
|
|
|
|
|
|
saveSuggestedPickLots.forEach { lot -> |
|
|
|
if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) { |
|
|
|
val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine) |
|
|
|
if (lineIndex >= 0) { |
|
|
|
inventoryLotLines[lineIndex].holdQty = |
|
|
|
(inventoryLotLines[lineIndex].holdQty ?: zero).plus(lot.qty ?: zero) |
|
|
|
|
|
|
|
logger.info("saveSuggestedPickLots: $saveSuggestedPickLots") |
|
|
|
inventoryLotLineRepository.saveAll(inventoryLotLines) |
|
|
|
var precreated = 0 |
|
|
|
saveSuggestedPickLots.forEach { lot -> |
|
|
|
val polId = lot.pickOrderLine?.id |
|
|
|
val illId = lot.suggestedLotLine?.id |
|
|
|
if (polId != null && illId != null) { |
|
|
|
try { |
|
|
|
val existingLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) |
|
|
|
if (existingLines.isEmpty()) { |
|
|
|
val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null) |
|
|
|
val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null) |
|
|
|
|
|
|
|
if (pickOrderLine != null && inventoryLotLine != null) { |
|
|
|
val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply { |
|
|
|
this.stockOut = savedStockOut |
|
|
|
this.pickOrderLine = pickOrderLine |
|
|
|
this.inventoryLotLine = inventoryLotLine |
|
|
|
this.item = pickOrderLine.item // ✅ Add the required item field |
|
|
|
this.status = com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status |
|
|
|
this.qty = 0.0 |
|
|
|
} |
|
|
|
stockOutLIneRepository.save(line) |
|
|
|
precreated++ |
|
|
|
} else { |
|
|
|
logger.warn("Could not find pickOrderLine (id=$polId) or inventoryLotLine (id=$illId)") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
inventoryLotLineRepository.saveAll(inventoryLotLines) |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
logger.warn("Failed to create stock out line for pickOrderLineId=$polId, inventoryLotLineId=$illId: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
logger.info("Precreated $precreated stock out lines for suggested lots on release.") |
|
|
|
return MessageResponse( |
|
|
|
id = null, |
|
|
|
name = "Pick orders released successfully with inventory management", |
|
|
|
@@ -2454,4 +2476,234 @@ open class PickOrderService( |
|
|
|
|
|
|
|
return addressParts.joinToString(", ") |
|
|
|
} |
|
|
|
|
|
|
|
// Add this new method that doesn't auto-assign |
|
|
|
open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map<String, Any>> { |
|
|
|
println("=== Debug: getAllPickOrderLotsWithDetailsWithoutAutoAssign ===") |
|
|
|
println("today: ${LocalDate.now()}") |
|
|
|
println("userId filter: $userId") |
|
|
|
|
|
|
|
// Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId) |
|
|
|
val user = userService.find(userId).orElse(null) |
|
|
|
if (user == null) { |
|
|
|
println("❌ User not found: $userId") |
|
|
|
return emptyList() |
|
|
|
} |
|
|
|
|
|
|
|
// Get all pick orders assigned to user with PENDING or RELEASED status that have doId |
|
|
|
val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( |
|
|
|
user, |
|
|
|
listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) |
|
|
|
).filter { it.deliveryOrder != null } // Only pick orders with doId |
|
|
|
|
|
|
|
val pickOrderIds = allAssignedPickOrders.map { it.id!! } |
|
|
|
|
|
|
|
println(" Pick order IDs to fetch: $pickOrderIds") |
|
|
|
|
|
|
|
if (pickOrderIds.isEmpty()) { |
|
|
|
return emptyList() |
|
|
|
} |
|
|
|
|
|
|
|
// Use the same SQL query as the auto-assign version but without the auto-assignment |
|
|
|
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, |
|
|
|
|
|
|
|
-- Lot Information (similar to lot-details endpoint) |
|
|
|
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, |
|
|
|
|
|
|
|
-- ✅ 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 from lot-details |
|
|
|
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, |
|
|
|
|
|
|
|
-- Add detailed debug fields for lotAvailability calculation |
|
|
|
ill.status as debug_ill_status, |
|
|
|
(il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) as debug_is_expired, |
|
|
|
sol.status as debug_sol_status, |
|
|
|
(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as debug_remaining_stock, |
|
|
|
(COALESCE(spl.qty, 0) - COALESCE(sol.qty, 0)) as debug_required_after_picked, |
|
|
|
|
|
|
|
-- Lot availability status (same logic as lot-details) |
|
|
|
CASE |
|
|
|
-- Check if lot is expired |
|
|
|
WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' |
|
|
|
-- Check if lot has rejected stock out line for this pick order |
|
|
|
WHEN sol.status = 'rejected' THEN 'rejected' |
|
|
|
-- Check if lot is unavailable due to insufficient stock |
|
|
|
WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' |
|
|
|
-- Check if lot status is unavailable |
|
|
|
WHEN ill.status = 'unavailable' THEN 'status_unavailable' |
|
|
|
-- Default to available |
|
|
|
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.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') |
|
|
|
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, -- Show rejected lots first |
|
|
|
po.code ASC, |
|
|
|
i.code ASC, |
|
|
|
il.expiryDate ASC, |
|
|
|
il.lotNo ASC |
|
|
|
""".trimIndent() |
|
|
|
|
|
|
|
println("🔍 Executing SQL for all pick order lots with details (no auto-assign): $sql") |
|
|
|
println(" With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") |
|
|
|
|
|
|
|
val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) |
|
|
|
println("✅ Total result count: ${results.size}") |
|
|
|
|
|
|
|
// Filter out lots with null availableQty (rejected lots) |
|
|
|
val filteredResults = results.filter { row -> |
|
|
|
val availableQty = row["availableQty"] |
|
|
|
availableQty != null |
|
|
|
} |
|
|
|
|
|
|
|
println("✅ Filtered result count: ${filteredResults.size}") |
|
|
|
|
|
|
|
// ✅ Add router information for each lot |
|
|
|
val enrichedResults = filteredResults.map { row -> |
|
|
|
val inventoryLotId = row["debugInventoryLotId"] as? Number |
|
|
|
|
|
|
|
// Get router information for this inventory lot |
|
|
|
val routerInfo = if (inventoryLotId != null) { |
|
|
|
try { |
|
|
|
val router = routerRepository.findFirstByInventoryLotIdAndDeletedFalse(inventoryLotId.toInt()) |
|
|
|
if (router != null) { |
|
|
|
mapOf( |
|
|
|
"routerIndex" to (router.index ?: 0), |
|
|
|
"routerRoute" to (router.route ?: ""), |
|
|
|
"routerArea" to (router.area ?: ""), |
|
|
|
"routerItemCode" to (router.itemCode ?: 0), |
|
|
|
"routerItemName" to (router.itemName ?: ""), |
|
|
|
"routerUomId" to (router.uomId ?: 0), |
|
|
|
"routerNoofCarton" to (router.noofCarton ?: 0) |
|
|
|
) |
|
|
|
} else { |
|
|
|
mapOf( |
|
|
|
"routerIndex" to 0, |
|
|
|
"routerRoute" to "", |
|
|
|
"routerArea" to "", |
|
|
|
"routerItemCode" to 0, |
|
|
|
"routerItemName" to "", |
|
|
|
"routerUomId" to 0, |
|
|
|
"routerNoofCarton" to 0 |
|
|
|
) |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
println("⚠️ Error getting router info for inventoryLotId $inventoryLotId: ${e.message}") |
|
|
|
mapOf( |
|
|
|
"routerIndex" to 0, |
|
|
|
"routerRoute" to "", |
|
|
|
"routerArea" to "", |
|
|
|
"routerItemCode" to 0, |
|
|
|
"routerItemName" to "", |
|
|
|
"routerUomId" to 0, |
|
|
|
"routerNoofCarton" to 0 |
|
|
|
) |
|
|
|
} |
|
|
|
} else { |
|
|
|
mapOf( |
|
|
|
"routerIndex" to 0, |
|
|
|
"routerRoute" to "", |
|
|
|
"routerArea" to "", |
|
|
|
"routerItemCode" to 0, |
|
|
|
"routerItemName" to "", |
|
|
|
"routerUomId" to 0, |
|
|
|
"routerNoofCarton" to 0 |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// Combine original row with router information |
|
|
|
row.toMutableMap().apply { |
|
|
|
putAll(routerInfo) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return enrichedResults |
|
|
|
} |
|
|
|
} |