From dfbba5b48d7d4e05c684cd5cfb4bd013345040a9 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Sat, 6 Sep 2025 15:18:30 +0800 Subject: [PATCH] update --- .../entity/InventoryLotLineRepository.kt | 1 + .../stock/service/InventoryLotLineService.kt | 93 +++-- .../modules/stock/service/PickOrderService.kt | 3 - .../stock/service/StockOutLineService.kt | 157 ++++++-- .../stock/service/SuggestedPickLotService.kt | 338 ++++++++++++------ .../stock/web/InventoryLotLineController.kt | 15 +- 6 files changed, 434 insertions(+), 173 deletions(-) delete mode 100644 src/main/java/com/ffii/fpsms/modules/stock/service/PickOrderService.kt diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt index 9c07cbf..1d0599b 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt @@ -33,4 +33,5 @@ interface InventoryLotLineRepository : AbstractRepository): List fun findAllByInventoryLotId(id: Serializable): List + fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: String): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt index 778ef60..aab25dd 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt @@ -33,13 +33,15 @@ import java.util.Optional import kotlin.jvm.optionals.getOrNull import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineQuantitiesRequest +import com.ffii.fpsms.modules.stock.entity.InventoryRepository @Service open class InventoryLotLineService( private val inventoryLotLineRepository: InventoryLotLineRepository, private val inventoryLotRepository: InventoryLotRepository, private val warehouseRepository: WarehouseRepository, private val itemUomRespository: ItemUomRespository, - private val stockInLineRepository: StockInLineRepository + private val stockInLineRepository: StockInLineRepository, + private val inventoryRepository: InventoryRepository ) { open fun findById(id: Long): Optional { return inventoryLotLineRepository.findById(id) @@ -95,41 +97,70 @@ open class InventoryLotLineService( } @Transactional open fun updateInventoryLotLineStatus(request: UpdateInventoryLotLineStatusRequest): MessageResponse { + // Get the existing inventory lot line + val existingLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() + + // Create update request with existing data and new status + val updateRequest = SaveInventoryLotLineRequest( + id = existingLotLine.id, + inventoryLotId = existingLotLine.inventoryLot?.id, + warehouseId = existingLotLine.warehouse?.id, + inQty = existingLotLine.inQty, + outQty = existingLotLine.outQty, + + holdQty = existingLotLine.holdQty, + stockUomId = existingLotLine.stockUom?.id, + status = request.status, + remarks = existingLotLine.remarks + ) + + val updatedLotLine = saveInventoryLotLine(updateRequest) + + // ✅ ADD THIS: Update inventory table after lot line status change + updateInventoryTable(updatedLotLine) + + return MessageResponse( + id = updatedLotLine.id, + name = updatedLotLine.id.toString(), + code = "SUCCESS", + type = "inventory_lot_line", + message = "Inventory lot line status updated successfully", + errorPosition = null + ) + } + + // ✅ ADD THIS: New method to update inventory table + private fun updateInventoryTable(inventoryLotLine: InventoryLotLine) { try { - val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() + // Get the item ID from the inventory lot + val itemId = inventoryLotLine.inventoryLot?.item?.id + if (itemId == null) { + println("Cannot update inventory table: itemId is null for lot line ${inventoryLotLine.id}") + return + } - // 使用现有的 saveInventoryLotLine 逻辑 - val updateRequest = SaveInventoryLotLineRequest( - id = inventoryLotLine.id, - inventoryLotId = inventoryLotLine.inventoryLot?.id, - warehouseId = inventoryLotLine.warehouse?.id, - stockUomId = inventoryLotLine.stockUom?.id, - inQty = inventoryLotLine.inQty, - outQty = inventoryLotLine.outQty, - holdQty = inventoryLotLine.holdQty, - status = request.status, // 新的状态 - remarks = inventoryLotLine.remarks - ) + // Calculate onHoldQty (sum of holdQty from available lots only) + val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, "available") + .sumOf { it.holdQty ?: BigDecimal.ZERO } - val updatedInventoryLotLine = saveInventoryLotLine(updateRequest) + // Calculate unavailableQty (sum of inQty from unavailable lots only) + val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, "unavailable") + .sumOf { it.inQty ?: BigDecimal.ZERO } - return MessageResponse( - id = updatedInventoryLotLine.id, - name = "Inventory lot line status updated", - code = "SUCCESS", - type = "inventory_lot_line", - message = "Status updated to ${request.status}", - errorPosition = null - ) + // Update the inventory table + val inventory = inventoryRepository.findByItemId(itemId).orElse(null) + if (inventory != null) { + inventory.onHoldQty = onHoldQty + inventory.unavailableQty = unavailableQty + inventoryRepository.save(inventory) + + println("Updated inventory for item $itemId: onHoldQty=$onHoldQty, unavailableQty=$unavailableQty") + } else { + println("Inventory not found for item $itemId") + } } catch (e: Exception) { - return MessageResponse( - id = null, - name = "Failed to update inventory lot line status", - code = "ERROR", - type = "inventory_lot_line", - message = "Error: ${e.message}", - errorPosition = null - ) + println("Error updating inventory table for lot line ${inventoryLotLine.id}: ${e.message}") + e.printStackTrace() } } @Throws(IOException::class) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/PickOrderService.kt deleted file mode 100644 index b28b04f..0000000 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/PickOrderService.kt +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt index 6da7204..b535700 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt @@ -17,6 +17,8 @@ import java.io.IOException import java.math.BigDecimal import java.time.LocalDate import java.time.LocalDateTime +import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService +import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus @Service open class StockOutLineService( @@ -27,7 +29,8 @@ open class StockOutLineService( private val itemRepository: ItemsRepository, private val inventoryRepository: InventoryRepository, private val itemUomRespository: ItemUomRespository, - private val inventoryLotLineRepository: InventoryLotLineRepository + private val inventoryLotLineRepository: InventoryLotLineRepository, + private val suggestedPickLotService: SuggestedPickLotService ): AbstractBaseEntityService(jdbcDao, stockOutLineRepository) { @Throws(IOException::class) @Transactional @@ -361,44 +364,132 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { } @Transactional - open fun updateStatus(request: UpdateStockOutLineStatusRequest): MessageResponse { - try { - val stockOutLine = stockOutLineRepository.findById(request.id).orElseThrow { - IllegalArgumentException("StockOutLine not found with ID: ${request.id}") - } +open fun updateStatus(request: UpdateStockOutLineStatusRequest): MessageResponse { + try { + val stockOutLine = stockOutLineRepository.findById(request.id).orElseThrow { + IllegalArgumentException("StockOutLine not found with ID: ${request.id}") + } + + println("Updating StockOutLine ID: ${request.id}") + println("Current status: ${stockOutLine.status}") + println("New status: ${request.status}") + + // Update status + stockOutLine.status = request.status + + // Update quantity if provided + if (request.qty != null) { + val currentQty = stockOutLine.qty?.toDouble() ?: 0.0 + val newQty = currentQty + request.qty + stockOutLine.qty = (newQty) + } + + val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) + println("Updated StockOutLine: ${savedStockOutLine.id} with status: ${savedStockOutLine.status}") + + // ✅ FIX: Only call once and add debugging + if (request.status == "rejected" || request.status == "REJECTED") { + println("=== TRIGGERING LOT REJECTION LOGIC ===") + handleLotRejectionFromStockOutLine(savedStockOutLine) + } + + val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) + + return MessageResponse( + id = savedStockOutLine.id, + name = savedStockOutLine.inventoryLotLine!!.inventoryLot!!.lotNo, + code = savedStockOutLine.stockOut!!.consoPickOrderCode, + type = savedStockOutLine.status, + message = "Stock out line status updated successfully", + errorPosition = null, + entity = mappedSavedStockOutLine, + ) + } catch (e: Exception) { + println("Error updating stock out line status: ${e.message}") + e.printStackTrace() + throw e + } +} + // ✅ ADD THIS: Handle lot rejection when stock out line is rejected + private fun handleLotRejectionFromStockOutLine(stockOutLine: StockOutLine) { + try { + println("=== HANDLING LOT REJECTION FROM STOCK OUT LINE ===") + println("StockOutLine ID: ${stockOutLine.id}") + println("StockOutLine Status: ${stockOutLine.status}") + + val inventoryLotLine = stockOutLine.inventoryLotLine + if (inventoryLotLine != null) { + println("InventoryLotLine ID: ${inventoryLotLine.id}") + println("Current lot status: ${inventoryLotLine.status}") + println("Current holdQty: ${inventoryLotLine.holdQty}") + + // Step 2: Update inventory lot line + inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE + inventoryLotLine.holdQty = BigDecimal.ZERO - println("Updating StockOutLine ID: ${request.id}") - println("Current status: ${stockOutLine.status}") - println("New status: ${request.status}") + println("Setting lot status to UNAVAILABLE and holdQty to 0") + val savedLotLine = inventoryLotLineRepository.save(inventoryLotLine) + println("Saved lot line: ${savedLotLine.id}, status: ${savedLotLine.status}, holdQty: ${savedLotLine.holdQty}") - // Update status - stockOutLine.status = request.status + // Step 3: Update inventory table + println("Updating inventory table...") + updateInventoryTableAfterLotRejection(inventoryLotLine) - // Update quantity if provided - if (request.qty != null) { - val currentQty = stockOutLine.qty?.toDouble() ?: 0.0 - val newQty = currentQty + request.qty - stockOutLine.qty = (newQty) + // Step 4: Trigger resuggest + val pickOrderLine = stockOutLine.pickOrderLine + if (pickOrderLine?.pickOrder?.id != null) { + println("Triggering resuggest for pick order ID: ${pickOrderLine.pickOrder!!.id}") + suggestedPickLotService.resuggestPickOrder(pickOrderLine.pickOrder!!.id!!) + println("Resuggest completed") + } else { + println("No pick order found for resuggest") } + } else { + println("No inventory lot line found in stock out line") + } + } catch (e: Exception) { + println("Error handling lot rejection from stock out line: ${e.message}") + e.printStackTrace() + } +} - val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) - println("Updated StockOutLine: ${savedStockOutLine.id} with status: ${savedStockOutLine.status}") +// ✅ ADD THIS: Update inventory table after lot rejection +private fun updateInventoryTableAfterLotRejection(inventoryLotLine: InventoryLotLine) { + try { + println("=== UPDATING INVENTORY TABLE ===") + val itemId = inventoryLotLine.inventoryLot?.item?.id + println("Item ID: $itemId") + + if (itemId != null) { + // Calculate onHoldQty (sum of holdQty from available lots only) + val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE.value) + .sumOf { it.holdQty ?: BigDecimal.ZERO } - val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) + // Calculate unavailableQty (sum of inQty from unavailable lots only) + val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId,InventoryLotLineStatus.UNAVAILABLE.value) + .sumOf { it.inQty ?: BigDecimal.ZERO } - return MessageResponse( - id = savedStockOutLine.id, - name = savedStockOutLine.inventoryLotLine!!.inventoryLot!!.lotNo, - code = savedStockOutLine.stockOut!!.consoPickOrderCode, - type = savedStockOutLine.status, - message = "Stock out line status updated successfully", - errorPosition = null, - entity = mappedSavedStockOutLine, - ) - } catch (e: Exception) { - println("Error updating stock out line status: ${e.message}") - e.printStackTrace() - throw e + println("Calculated onHoldQty: $onHoldQty") + println("Calculated unavailableQty: $unavailableQty") + + // Update the inventory table + val inventory = inventoryRepository.findByItemId(itemId).orElse(null) + if (inventory != null) { + println("Found inventory record: ${inventory.id}") + println("Current inventory - onHoldQty: ${inventory.onHoldQty}, unavailableQty: ${inventory.unavailableQty}") + + inventory.onHoldQty = onHoldQty + inventory.unavailableQty = unavailableQty + val savedInventory = inventoryRepository.save(inventory) + + println("Updated inventory - onHoldQty: ${savedInventory.onHoldQty}, unavailableQty: ${savedInventory.unavailableQty}") + } else { + println("No inventory record found for item $itemId") + } } + } catch (e: Exception) { + println("Error updating inventory table after lot rejection: ${e.message}") + e.printStackTrace() } -} \ No newline at end of file +} + } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt index aa7ada5..dc2e993 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt @@ -21,6 +21,8 @@ import org.springframework.transaction.annotation.Transactional import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository import java.math.RoundingMode +import com.ffii.fpsms.modules.stock.entity.InventoryRepository + @Service open class SuggestedPickLotService( val suggestedPickLotRepository: SuggestPickLotRepository, @@ -29,7 +31,8 @@ open class SuggestedPickLotService( val pickOrderLineRepository: PickOrderLineRepository, val inventoryLotLineService: InventoryLotLineService, val itemUomService: ItemUomService, - val pickOrderRepository: PickOrderRepository + val pickOrderRepository: PickOrderRepository, + val inventoryRepository: InventoryRepository ) { // Calculation Available Qty / Remaining Qty open fun calculateRemainingQtyForInfo(inventoryLotLine: InventoryLotLineInfo?): BigDecimal { @@ -198,100 +201,114 @@ open class SuggestedPickLotService( return suggestedPickLotRepository.saveAllAndFlush(request) } @Transactional(rollbackFor = [Exception::class]) - open fun resuggestPickOrder(pickOrderId: Long): MessageResponse { - try { - val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow() - - // Step 1: Find ALL suggestions for this pick order (not just problematic ones) - val allSuggestions = findAllSuggestionsForPickOrder(pickOrderId) - - if (allSuggestions.isEmpty()) { - return MessageResponse( - id = pickOrderId, - name = "No suggestions found", - code = "SUCCESS", - type = "resuggest", - message = "No suggestions to resuggest", - errorPosition = null - ) - } - - // Step 2: Calculate total excess quantity from problematic suggestions - val problematicSuggestions = findProblematicSuggestions(pickOrderId) - val totalExcessQty = problematicSuggestions.sumOf { suggestion -> - val pickOrderLine = pickOrder.pickOrderLines.find { it.id == suggestion.pickOrderLine?.id } - val inventoryLotLine = suggestion.suggestedLotLine - - if (pickOrderLine == null || inventoryLotLine == null) { - BigDecimal.ZERO - } else { - val salesUnit = pickOrderLine.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } - val ratio = (salesUnit?.ratioN ?: BigDecimal.ONE).divide(salesUnit?.ratioD ?: BigDecimal.ONE, 10, RoundingMode.HALF_UP) - - val availableQtyInBaseUnits = (inventoryLotLine.inQty ?: BigDecimal.ZERO) - .minus(inventoryLotLine.outQty ?: BigDecimal.ZERO) - .minus(inventoryLotLine.holdQty ?: BigDecimal.ZERO) - - val suggestionQtyInBaseUnits = (suggestion.qty ?: BigDecimal.ZERO).multiply(ratio) - val excessQtyInBaseUnits = suggestionQtyInBaseUnits.minus(availableQtyInBaseUnits) - - if (excessQtyInBaseUnits > BigDecimal.ZERO) { - excessQtyInBaseUnits - } else { - BigDecimal.ZERO - } - } - } - - // Step 3: Remove ALL existing suggestions and reset holdQty - val affectedLotLineIds = allSuggestions.mapNotNull { it.suggestedLotLine?.id }.distinct() - - val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(affectedLotLineIds) - inventoryLotLines.forEach { lotLine -> - // ✅ FIX: Reset holdQty to 0 for lots being resuggested - lotLine.holdQty = BigDecimal.ZERO - } - inventoryLotLineRepository.saveAll(inventoryLotLines) - - // Delete ALL suggestions for this pick order - suggestedPickLotRepository.deleteAllById(allSuggestions.mapNotNull { it.id }) - - // Step 4: Generate completely new suggestions following FEFO - val newSuggestions = generateCorrectSuggestionsWithExistingHolds(pickOrder) - - // Step 5: Save new suggestions and update holdQty - val savedSuggestions = suggestedPickLotRepository.saveAll(newSuggestions) - - val newInventoryLotLines = inventoryLotLineRepository.findAllByIdIn( - savedSuggestions.mapNotNull { it.suggestedLotLine?.id } - ) - /* - savedSuggestions.forEach { suggestion -> - val lotLine = newInventoryLotLines.find { it.id == suggestion.suggestedLotLine?.id } - lotLine?.let { - it.holdQty = (it.holdQty ?: BigDecimal.ZERO).plus(suggestion.qty ?: BigDecimal.ZERO) - } - } - inventoryLotLineRepository.saveAll(newInventoryLotLines) - */ +open fun resuggestPickOrder(pickOrderId: Long): MessageResponse { + try { + val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow() + + // Step 1: Find ALL suggestions for this pick order + val allSuggestions = findAllSuggestionsForPickOrder(pickOrderId) + + if (allSuggestions.isEmpty()) { return MessageResponse( id = pickOrderId, - name = "Pick order resuggested successfully", + name = "No suggestions found", code = "SUCCESS", type = "resuggest", - message = "Redistributed ${allSuggestions.size} suggestions following FEFO order", + message = "No suggestions to resuggest", errorPosition = null ) + } + + // Step 2: Calculate total excess quantity from problematic suggestions + val problematicSuggestions = findProblematicSuggestions(pickOrderId) + + // Step 3: Store original holdQty before resetting + val affectedLotLineIds = allSuggestions.mapNotNull { it.suggestedLotLine?.id }.distinct() + val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(affectedLotLineIds) + + // ✅ FIX: Store original holdQty values before resetting + val originalHoldQtyMap = inventoryLotLines.associate { it.id to (it.holdQty ?: BigDecimal.ZERO) } + + // Reset holdQty to 0 for all affected lots + inventoryLotLines.forEach { lotLine -> + lotLine.holdQty = BigDecimal.ZERO + } + inventoryLotLineRepository.saveAll(inventoryLotLines) + + // Delete ALL suggestions for this pick order + suggestedPickLotRepository.deleteAllById(allSuggestions.mapNotNull { it.id }) + + // Step 4: Generate completely new suggestions with original holdQty info + val newSuggestions = generateCorrectSuggestionsWithOriginalHolds(pickOrder, originalHoldQtyMap) + + // Step 5: Save new suggestions and update holdQty + val savedSuggestions = suggestedPickLotRepository.saveAll(newSuggestions) + + val newInventoryLotLines = inventoryLotLineRepository.findAllByIdIn( + savedSuggestions.mapNotNull { it.suggestedLotLine?.id } + ) + savedSuggestions.forEach { suggestion -> + val lotLine = newInventoryLotLines.find { it.id == suggestion.suggestedLotLine?.id } + lotLine?.let { + val salesUnit = suggestion.pickOrderLine?.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } + val ratio = BigDecimal.ONE + val suggestionQtyInBaseUnits = (suggestion.qty ?: BigDecimal.ZERO).multiply(ratio) + + it.holdQty = suggestionQtyInBaseUnits + println("Setting holdQty for lot ${it.id}: suggestion.qty=${suggestion.qty}, ratio=$ratio, holdQty=$suggestionQtyInBaseUnits") + } + } + inventoryLotLineRepository.saveAll(newInventoryLotLines) + updateInventoryTableAfterResuggest(pickOrder) + + return MessageResponse( + id = pickOrderId, + name = "Pick order resuggested successfully", + code = "SUCCESS", + type = "resuggest", + message = "Redistributed ${allSuggestions.size} suggestions following FEFO order", + errorPosition = null + ) + + } catch (e: Exception) { + return MessageResponse( + id = pickOrderId, + name = "Failed to resuggest pick order", + code = "ERROR", + type = "resuggest", + message = "Error: ${e.message}", + errorPosition = null + ) + } +} + + private fun updateInventoryTableAfterResuggest(pickOrder: PickOrder) { + try { + // Get all item IDs from the pick order + val itemIds = pickOrder.pickOrderLines.mapNotNull { it.item?.id }.distinct() + itemIds.forEach { itemId -> + // Calculate onHoldQty (sum of holdQty from available lots only) + val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, "available") + .sumOf { it.holdQty ?: BigDecimal.ZERO } + + // Calculate unavailableQty (sum of inQty from unavailable lots only) + val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, "unavailable") + .sumOf { it.inQty ?: BigDecimal.ZERO } + + // Update the inventory table + val inventory = inventoryRepository.findByItemId(itemId).orElse(null) + if (inventory != null) { + inventory.onHoldQty = onHoldQty + inventory.unavailableQty = unavailableQty + inventoryRepository.save(inventory) + + println("Updated inventory for item $itemId after resuggest: onHoldQty=$onHoldQty, unavailableQty=$unavailableQty") + } + } } catch (e: Exception) { - return MessageResponse( - id = pickOrderId, - name = "Failed to resuggest pick order", - code = "ERROR", - type = "resuggest", - message = "Error: ${e.message}", - errorPosition = null - ) + println("Error updating inventory table after resuggest: ${e.message}") + e.printStackTrace() } } private fun findAllSuggestionsForPickOrder(pickOrderId: Long): List { @@ -313,7 +330,7 @@ open class SuggestedPickLotService( // Get sales unit conversion ratio val salesUnit = itemUomService.findSalesUnitByItemId(itemId) - val ratio = (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) + val ratio = one // Convert excess quantity to sales units val excessQtyInSalesUnits = excessQtyInBaseUnits.divide(ratio, 0, RoundingMode.HALF_UP) @@ -424,7 +441,8 @@ private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): L val suggestions = mutableListOf() val zero = BigDecimal.ZERO val one = BigDecimal.ONE - + val today = LocalDate.now() + pickOrder.pickOrderLines.forEach { orderLine -> val itemId = orderLine.item?.id ?: return@forEach val requiredQty = orderLine.qty ?: zero // This is in sales units @@ -434,60 +452,170 @@ private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): L // Get sales unit conversion ratio val salesUnit = itemUomService.findSalesUnitByItemId(itemId) - val ratio = (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) + val ratio = one + + // ✅ FIX: Get ALL inventory lots (both available and unavailable) + val allLots = inventoryLotLineService + .allInventoryLotLinesByItemIdIn(listOf(itemId)) + .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } + .sortedBy { it.expiryDate } + + // ✅ FIX: Separate available and unavailable lots + val availableLots = allLots.filter { it.status == InventoryLotLineStatus.AVAILABLE.value } + val unavailableLots = allLots.filter { it.status == InventoryLotLineStatus.UNAVAILABLE.value } + + // ✅ FIX: Calculate total quantity that was previously held by unavailable lots + var totalUnavailableHoldQty = BigDecimal.ZERO + val modifiedUnavailableLots = mutableListOf() + + unavailableLots.forEach { lotInfo -> + val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } + lot?.let { + totalUnavailableHoldQty = totalUnavailableHoldQty.plus(it.holdQty ?: zero) + // ✅ Reset holdQty for unavailable lots + it.holdQty = BigDecimal.ZERO + modifiedUnavailableLots.add(it) // ✅ Keep reference to modified entity + } + } + + // ✅ FIX: Save the modified entities (not fresh ones from database) + if (modifiedUnavailableLots.isNotEmpty()) { + inventoryLotLineRepository.saveAll(modifiedUnavailableLots) + println("Reset holdQty for ${modifiedUnavailableLots.size} unavailable lots") + } + + // ✅ FIX: Add the unavailable hold quantity to the required quantity + //val totalRequiredQty = requiredQty.plus(totalUnavailableHoldQty.divide(ratio, 2, RoundingMode.HALF_UP)) + val totalRequiredQty = requiredQty + var remainingQtyInSalesUnits = totalRequiredQty + + println("Total required qty (including unavailable): $totalRequiredQty") + println("Available lots: ${availableLots.size}, Unavailable lots: ${unavailableLots.size}") + println("Total unavailable hold qty to redistribute: $totalUnavailableHoldQty") + + // Generate suggestions from available lots ONLY + availableLots.forEach { lotInfo -> + if (remainingQtyInSalesUnits <= zero) return@forEach + + val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } + ?: return@forEach + + val totalQty = lot.inQty ?: zero + val outQty = lot.outQty ?: zero + val holdQty = lot.holdQty ?: zero + val availableQtyInBaseUnits = totalQty.minus(outQty).minus(holdQty) + + val availableQtyInSalesUnits = availableQtyInBaseUnits.divide(ratio, 2, RoundingMode.HALF_UP) + + println("Available lot ${lot.id}: availableQtyInBaseUnits=$availableQtyInBaseUnits, availableQtyInSalesUnits=$availableQtyInSalesUnits") + + if (availableQtyInSalesUnits <= zero) return@forEach + + val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQtyInSalesUnits) + remainingQtyInSalesUnits = remainingQtyInSalesUnits.minus(assignQtyInSalesUnits) + + println("Assigning $assignQtyInSalesUnits to lot ${lot.id}, remaining: $remainingQtyInSalesUnits") + + if (assignQtyInSalesUnits > zero) { + suggestions.add(SuggestedPickLot().apply { + type = SuggestedPickLotType.PICK_ORDER + suggestedLotLine = lot + pickOrderLine = orderLine + qty = assignQtyInSalesUnits + }) + } + } - println("Ratio: $ratio") + // If we still have remaining qty, create a suggestion with null lot (insufficient stock) + if (remainingQtyInSalesUnits > zero) { + println("Creating insufficient stock suggestion for remaining qty: $remainingQtyInSalesUnits") + suggestions.add(SuggestedPickLot().apply { + type = SuggestedPickLotType.PICK_ORDER + suggestedLotLine = null + pickOrderLine = orderLine + qty = remainingQtyInSalesUnits + }) + } + } + + return suggestions +} +private fun generateCorrectSuggestionsWithOriginalHolds( + pickOrder: PickOrder, + originalHoldQtyMap: Map +): List { + val suggestions = mutableListOf() + val zero = BigDecimal.ZERO + val one = BigDecimal.ONE + val today = LocalDate.now() + + pickOrder.pickOrderLines.forEach { orderLine -> + val itemId = orderLine.item?.id ?: return@forEach + val requiredQty = orderLine.qty ?: zero + + println("=== DEBUG: generateCorrectSuggestionsWithOriginalHolds ===") + println("Item ID: $itemId, Required Qty: $requiredQty") + + val salesUnit = itemUomService.findSalesUnitByItemId(itemId) + val ratio = one // Get available inventory lots for this item (FEFO order) val availableLots = inventoryLotLineService .allInventoryLotLinesByItemIdIn(listOf(itemId)) .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } + .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } .sortedBy { it.expiryDate } - - var remainingQtyInSalesUnits = requiredQty - + + // ✅ Calculate total quantity that needs to be redistributed + var totalRedistributeQty = requiredQty + originalHoldQtyMap.forEach { (lotId, originalHoldQty) -> + if (originalHoldQty > zero) { + // Check if this lot is now unavailable + val lot = lotId?.let { inventoryLotLineRepository.findById(it).orElse(null) } + if (lot?.status == InventoryLotLineStatus.UNAVAILABLE) { + totalRedistributeQty = totalRedistributeQty.plus(originalHoldQty.divide(ratio, 2, RoundingMode.HALF_UP)) + println("Adding ${originalHoldQty.divide(ratio, 2, RoundingMode.HALF_UP)} from unavailable lot $lotId") + } + } + } + + var remainingQtyInSalesUnits = totalRedistributeQty + println("Total qty to redistribute: $totalRedistributeQty") + + // Generate suggestions from available lots availableLots.forEach { lotInfo -> if (remainingQtyInSalesUnits <= zero) return@forEach - // Get the actual InventoryLotLine entity val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } ?: return@forEach - // ✅ FIX: Calculate available qty directly in sales units val totalQty = lot.inQty ?: zero val outQty = lot.outQty ?: zero val holdQty = lot.holdQty ?: zero val availableQtyInBaseUnits = totalQty.minus(outQty).minus(holdQty) - // ✅ FIX: Convert to sales units with proper rounding val availableQtyInSalesUnits = availableQtyInBaseUnits.divide(ratio, 2, RoundingMode.HALF_UP) - println("Lot ${lot.id}: availableQtyInBaseUnits=$availableQtyInBaseUnits, availableQtyInSalesUnits=$availableQtyInSalesUnits") - if (availableQtyInSalesUnits <= zero) return@forEach - // ✅ FIX: Take what we can from this lot (constrained by available AND remaining) val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQtyInSalesUnits) remainingQtyInSalesUnits = remainingQtyInSalesUnits.minus(assignQtyInSalesUnits) - println("Lot ${lot.id}: assignQtyInSalesUnits=$assignQtyInSalesUnits, remainingQtyInSalesUnits=$remainingQtyInSalesUnits") - if (assignQtyInSalesUnits > zero) { suggestions.add(SuggestedPickLot().apply { type = SuggestedPickLotType.PICK_ORDER suggestedLotLine = lot pickOrderLine = orderLine - qty = assignQtyInSalesUnits // Store in sales units + qty = assignQtyInSalesUnits }) } } // If we still have remaining qty, create a suggestion with null lot (insufficient stock) if (remainingQtyInSalesUnits > zero) { - println("Remaining Qty in Sales Units: $remainingQtyInSalesUnits") suggestions.add(SuggestedPickLot().apply { type = SuggestedPickLotType.PICK_ORDER - suggestedLotLine = null // No lot available + suggestedLotLine = null pickOrderLine = orderLine qty = remainingQtyInSalesUnits }) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt index 712149b..93e5d20 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt @@ -80,7 +80,20 @@ class InventoryLotLineController ( } @PostMapping("/updateStatus") fun updateInventoryLotLineStatus(@RequestBody request: UpdateInventoryLotLineStatusRequest): MessageResponse { - return inventoryLotLineService.updateInventoryLotLineStatus(request) + println("=== DEBUG: updateInventoryLotLineStatus Controller ===") + println("Request received: $request") + println("inventoryLotLineId: ${request.inventoryLotLineId}") + println("status: ${request.status}") + + try { + val result = inventoryLotLineService.updateInventoryLotLineStatus(request) + println("✅ Controller: Update successful - $result") + return result + } catch (e: Exception) { + println("❌ Controller: Update failed - ${e.message}") + e.printStackTrace() + throw e + } } @PostMapping("/updateQuantities")