소스 검색

update

master
CANCERYS\kw093 3 달 전
부모
커밋
dfbba5b48d
6개의 변경된 파일434개의 추가작업 그리고 173개의 파일을 삭제
  1. +1
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  2. +62
    -31
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt
  3. +0
    -3
      src/main/java/com/ffii/fpsms/modules/stock/service/PickOrderService.kt
  4. +124
    -33
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt
  5. +233
    -105
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt
  6. +14
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt 파일 보기

@@ -33,4 +33,5 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long
fun findAllByIdIn(ids: List<Serializable>): List<InventoryLotLine>

fun findAllByInventoryLotId(id: Serializable): List<InventoryLotLine>
fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: String): List<InventoryLotLine>
}

+ 62
- 31
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<InventoryLotLine> {
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)


+ 0
- 3
src/main/java/com/ffii/fpsms/modules/stock/service/PickOrderService.kt 파일 보기

@@ -1,3 +0,0 @@




+ 124
- 33
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<StockOutLine, Long, StockOutLIneRepository>(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()
}
}
}
}

+ 233
- 105
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<SuggestedPickLot> {
@@ -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<SuggestedPickLot>()
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<InventoryLotLine>()
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<Long?, BigDecimal>
): List<SuggestedPickLot> {
val suggestions = mutableListOf<SuggestedPickLot>()
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
})


+ 14
- 1
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")


불러오는 중...
취소
저장