diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt index fbae53d..04d8bfc 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt @@ -78,7 +78,7 @@ open class ItemsService( pol.qty as requiredQty, COALESCE(inv.availableQty, 0) as currentStock, COALESCE(uc.udfudesc, 'N/A') as unit, - po.targetDate, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate, po.status, po.consoCode, po.assignTo, @@ -133,7 +133,7 @@ open class ItemsService( val result = jdbcDao.queryForList(finalSql, args) println("Query result size: ${result.size}") - + result.forEach { row -> println("Result row: $row") } return result } catch (e: Exception) { println("Error in getPickOrderItemsByPage: ${e.message}") diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index 0d1562f..03cd3e0 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -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> { + 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 +} } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt index 18494e6..304dcfa 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt @@ -203,16 +203,18 @@ class PickOrderController( return ResponseEntity.ok(response) - } + @GetMapping("/groups/latest") fun getLatestGroupName(): MessageResponse { return pickOrderService.getLatestGroupNameAndCreate() } + @GetMapping("/groups/list") fun getAllGroups(): List { return pickOrderService.allPickOrdersGroup() } + @PostMapping("/groups/create") fun createNewGroups(@Valid @RequestBody request: SavePickOrderGroupRequest): MessageResponse { return pickOrderService.createNewGroups(request) @@ -223,28 +225,39 @@ class PickOrderController( fun getPickOrderDetailsOptimizedByUser(@PathVariable userId: Long): GetPickOrderInfoResponse { return pickOrderService.getPickOrderDetailsOptimizedByUser(userId) } + @PostMapping("/auto-assign-release/{userId}") fun autoAssignAndReleasePickOrder(@PathVariable userId: Long): MessageResponse { return pickOrderService.autoAssignAndReleasePickOrder(userId) } + @GetMapping("/check-pick-completion/{userId}") fun checkPickOrderCompletion(@PathVariable userId: Long): MessageResponse { return pickOrderService.checkPickOrderCompletion(userId) } + @PostMapping("/check-complete/{consoCode}") fun checkAndCompletePickOrderByConsoCode(@PathVariable consoCode: String): MessageResponse { return pickOrderService.checkAndCompletePickOrderByConsoCode(consoCode) } + @GetMapping("/all-lots-with-details/{userId}") fun getAllPickOrderLotsWithDetails(@PathVariable userId: Long): List> { return pickOrderService.getAllPickOrderLotsWithDetailsWithAutoAssign(userId) } + @GetMapping("/fg-pick-orders") fun getFgPickOrders(): List> { return pickOrderService.getFgPickOrders() } + @GetMapping("/fg-pick-orders/{pickOrderId}") fun getFgPickOrdersByPickOrderId(@PathVariable pickOrderId: Long): List> { return pickOrderService.getFgPickOrdersByPickOrderId(pickOrderId) } - } \ No newline at end of file + + @GetMapping("/all-lots-with-details-no-auto-assign/{userId}") + fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(@PathVariable userId: Long): List> { + return pickOrderService.getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt index 08d00da..db197f4 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt @@ -20,4 +20,5 @@ interface StockOutLIneRepository: AbstractRepository { pickOrderLineId: Long, inventoryLotLineId: Long ): List + fun existsByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(pickOrderLineId: Long, inventoryLotLineId: Long): Boolean }