|
|
|
@@ -38,6 +38,7 @@ import com.ffii.fpsms.modules.common.CodeGenerator |
|
|
|
import org.springframework.context.annotation.Lazy |
|
|
|
import com.ffii.fpsms.modules.bag.service.BagService |
|
|
|
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService |
|
|
|
import com.ffii.fpsms.modules.master.service.ItemUomService |
|
|
|
import com.ffii.fpsms.modules.common.SecurityUtils |
|
|
|
import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository |
|
|
|
import com.ffii.fpsms.modules.stock.entity.InventoryRepository |
|
|
|
@@ -71,7 +72,8 @@ private val inventoryLotLineService: InventoryLotLineService, |
|
|
|
private val pickOrderService: PickOrderService, |
|
|
|
private val stockLedgerRepository: StockLedgerRepository, |
|
|
|
private val inventoryRepository: InventoryRepository, |
|
|
|
private val pickExecutionIssueRepository: PickExecutionIssueRepository |
|
|
|
private val pickExecutionIssueRepository: PickExecutionIssueRepository, |
|
|
|
private val itemUomService: ItemUomService, |
|
|
|
): AbstractBaseEntityService<StockOutLine, Long, StockOutLIneRepository>(jdbcDao, stockOutLineRepository) { |
|
|
|
private fun isEndStatus(status: String?): Boolean { |
|
|
|
val s = status?.trim()?.lowercase() ?: return false |
|
|
|
@@ -1190,7 +1192,10 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { |
|
|
|
val processedIds = mutableListOf<Long>() |
|
|
|
|
|
|
|
try { |
|
|
|
// 1) Bulk load all lot lines and inventories |
|
|
|
// 1) Bulk load lot lines(下方會用 lotLines 取批次/袋裝等) |
|
|
|
// 注意:勿呼叫 inventoryRepository.findByItemId —— 若歷史資料同一 item 有多筆 inventory, |
|
|
|
// Spring Data 的 Optional 單筆查詢會拋 NonUniqueResultException(「unique result: 2」)。 |
|
|
|
// 先前預載的 inventories 從未使用(save 已註解),已移除以避免批量提交無故失敗。 |
|
|
|
val lotLineIds = request.lines.mapNotNull { it.inventoryLotLineId } |
|
|
|
println("Loading ${lotLineIds.size} lot lines...") |
|
|
|
val lotLines = if (lotLineIds.isNotEmpty()) { |
|
|
|
@@ -1199,12 +1204,6 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { |
|
|
|
emptyMap() |
|
|
|
} |
|
|
|
|
|
|
|
val itemIds = lotLines.values.mapNotNull { it.inventoryLot?.item?.id } |
|
|
|
println("Loading ${itemIds.size} inventories...") |
|
|
|
val inventories = itemIds.mapNotNull { itemId -> |
|
|
|
inventoryRepository.findByItemId(itemId).orElse(null) |
|
|
|
}.associateBy { it.item?.id } |
|
|
|
|
|
|
|
// 2) Bulk load all stock out lines to get current quantities |
|
|
|
val stockOutLineIds = request.lines.map { it.stockOutLineId } |
|
|
|
println("Loading ${stockOutLineIds.size} stock out lines...") |
|
|
|
@@ -1292,8 +1291,8 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) { |
|
|
|
|
|
|
|
// ✅ 修复:在更新 inventory_lot_line 之前获取 inventory 的当前 onHandQty |
|
|
|
val item = stockOutLine.item |
|
|
|
val inventoryBeforeUpdate = item?.let { |
|
|
|
inventoryRepository.findByItemId(it.id!!).orElse(null) |
|
|
|
val inventoryBeforeUpdate = item?.id?.let { itemId -> |
|
|
|
itemUomService.findInventoryForItemBaseUom(itemId) |
|
|
|
} |
|
|
|
val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble() |
|
|
|
|
|
|
|
@@ -1314,8 +1313,8 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) { |
|
|
|
} else if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId == null) { |
|
|
|
// ✅ 修复:即使没有 inventoryLotLineId,也应该获取 inventory.onHandQty |
|
|
|
val item = stockOutLine.item |
|
|
|
val inventoryBeforeUpdate = item?.let { |
|
|
|
inventoryRepository.findByItemId(it.id!!).orElse(null) |
|
|
|
val inventoryBeforeUpdate = item?.id?.let { itemId -> |
|
|
|
itemUomService.findInventoryForItemBaseUom(itemId) |
|
|
|
} |
|
|
|
val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble() |
|
|
|
|
|
|
|
@@ -1370,9 +1369,8 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 4) 移除:不需要保存 lotLines 和 inventories,因为它们没有被修改 |
|
|
|
// 4) 移除:不需要保存 lotLines,因为它们没有被修改 |
|
|
|
// inventoryLotLineRepository.saveAll(lotLines.values.toList()) |
|
|
|
// inventoryRepository.saveAll(inventories.values.toList()) |
|
|
|
|
|
|
|
// ✅ 修复:批处理完成后,检查所有受影响的 pick order lines 是否应该标记为完成 |
|
|
|
val affectedPickOrderIds = request.lines |
|
|
|
@@ -1394,7 +1392,7 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) { |
|
|
|
try { |
|
|
|
checkIsStockOutLineCompleted(pickOrderLineId) |
|
|
|
} catch (e: Exception) { |
|
|
|
println(" ✗ Error checking pick order line $pickOrderLineId: ${e.message}") |
|
|
|
println("Error checking pick order line $pickOrderLineId: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
val affectedConsoCodes = affectedPickOrderIds |
|
|
|
@@ -1405,16 +1403,16 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) { |
|
|
|
.filter { !it.isNullOrBlank() } |
|
|
|
.distinct() |
|
|
|
|
|
|
|
println("=== Checking completion by consoCode for ${affectedConsoCodes.size} affected consoCodes after batch submit ===") |
|
|
|
affectedConsoCodes.forEach { consoCode -> |
|
|
|
try { |
|
|
|
val result = pickOrderService.checkAndCompletePickOrderByConsoCode(consoCode) |
|
|
|
println(" -> checkAndCompletePickOrderByConsoCode($consoCode) result: code=${result.code}, message=${result.message}") |
|
|
|
} catch (e: Exception) { |
|
|
|
println(" ✗ Error checking completion for consoCode=$consoCode: ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
} |
|
|
|
} |
|
|
|
println("=== Checking completion by consoCode for ${affectedConsoCodes.size} affected consoCodes after batch submit ===") |
|
|
|
affectedConsoCodes.forEach { consoCode -> |
|
|
|
try { |
|
|
|
val result = pickOrderService.checkAndCompletePickOrderByConsoCode(consoCode) |
|
|
|
println(" -> checkAndCompletePickOrderByConsoCode($consoCode) result: code=${result.code}, message=${result.message}") |
|
|
|
} catch (e: Exception) { |
|
|
|
println("Error checking completion for consoCode=$consoCode: ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
} |
|
|
|
} |
|
|
|
val msg = if (errors.isEmpty()) { |
|
|
|
"Batch submit success (${processedIds.size} lines)." |
|
|
|
} else { |
|
|
|
@@ -1461,7 +1459,7 @@ affectedConsoCodes.forEach { consoCode -> |
|
|
|
@Transactional |
|
|
|
private fun createStockLedgerForStockOut(stockOutLine: StockOutLine) { |
|
|
|
val item = stockOutLine.item ?: return |
|
|
|
val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return |
|
|
|
val inventory = itemUomService.findInventoryForItemBaseUom(item.id!!) ?: return |
|
|
|
|
|
|
|
val outQty = stockOutLine.qty?.toDouble() ?: 0.0 |
|
|
|
// Use latest ledger balance (same pattern as createStockLedgerForStockIn) so balance is correct when multiple actions run in one transaction |
|
|
|
@@ -1560,8 +1558,7 @@ affectedConsoCodes.forEach { consoCode -> |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
val inventory = inventoryRepository.findAllByItemIdAndDeletedIsFalse(item.id!!) |
|
|
|
.firstOrNull() |
|
|
|
val inventory = itemUomService.findInventoryForItemBaseUom(item.id!!) |
|
|
|
if (inventory == null) { |
|
|
|
println("${tracePrefix}Skip ledger creation: inventory not found by itemId=${item.id}") |
|
|
|
return |
|
|
|
@@ -1948,7 +1945,7 @@ fun applyStockOutLineDelta( |
|
|
|
|
|
|
|
val itemId = savedSol.item?.id |
|
|
|
if (itemId != null) { |
|
|
|
val inv = inventoryRepository.findByItemId(itemId).orElse(null) |
|
|
|
val inv = itemUomService.findInventoryForItemBaseUom(itemId) |
|
|
|
if (inv != null) { |
|
|
|
val zero = BigDecimal.ZERO |
|
|
|
inv.onHandQty = (inv.onHandQty ?: zero).minus(deltaQty) |
|
|
|
@@ -1963,7 +1960,7 @@ fun applyStockOutLineDelta( |
|
|
|
// 3) stock_ledger (shared source with createStockOut/createStockLedgerForStockOut style) |
|
|
|
if (!skipLedgerWrite) { |
|
|
|
val item = savedSol.item ?: return savedSol |
|
|
|
val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return savedSol |
|
|
|
val inventory = itemUomService.findInventoryForItemBaseUom(item.id!!) ?: return savedSol |
|
|
|
|
|
|
|
val previousBalance = resolvePreviousBalance( |
|
|
|
itemId = item.id!!, |
|
|
|
|