Преглед на файлове

update expiry lot handle in jo/do and show qty will submit and search invenotry by itemid and uomid

master
CANCERYS\kw093 преди 1 ден
родител
ревизия
4f7dc6687d
променени са 5 файла, в които са добавени 56 реда и са изтрити 32 реда
  1. +22
    -1
      src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt
  2. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  3. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt
  4. +27
    -30
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt
  5. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt

+ 22
- 1
src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt Целия файл

@@ -12,13 +12,17 @@ import kotlin.jvm.optionals.getOrNull
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.stock.entity.Inventory
import com.ffii.fpsms.modules.stock.entity.InventoryRepository

@Service
open class ItemUomService(

val uomConversionService: UomConversionService,
val itemUomRespository: ItemUomRespository,
val currencyService: CurrencyService,
val itemsRepository: ItemsRepository
val itemsRepository: ItemsRepository,
private val inventoryRepository: InventoryRepository,
) {
val logger = org.slf4j.LoggerFactory.getLogger(this::class.java)

@@ -65,6 +69,23 @@ open class ItemUomService(
return itemUomRespository.findByItemIdAndBaseUnitIsTrueAndDeletedIsFalse(itemId)
}

/**
* 同一 [itemId] 可能對應多筆 [Inventory](不同 uom)。
* 優先取 `inventory.uomId` 與 `item_uom` 中 `baseUnit = true` 之列的 `uomId` 一致的那一筆;
* 若無 base 設定或無匹配列,則回退為 `findFirstByItemIdAndDeletedIsFalseOrderByIdAsc`。
*/
open fun findInventoryForItemBaseUom(itemId: Long): Inventory? {
val base = findBaseUnitByItemId(itemId)
val uomId = base?.uom?.id
if (uomId != null) {
val match = inventoryRepository.findFirstByItemIdAndUomIdAndDeletedIsFalseOrderByIdAsc(itemId, uomId)
if (match != null) {
return match
}
}
return inventoryRepository.findFirstByItemIdAndDeletedIsFalseOrderByIdAsc(itemId)
}

open fun findPickingUnitByItemId(itemId: Long): ItemUom? {
return itemUomRespository.findByItemIdAndPickingUnitIsTrueAndDeletedIsFalse(itemId)
}


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt Целия файл

@@ -73,6 +73,9 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long
@Query("SELECT ill FROM InventoryLotLine ill WHERE ill.warehouse.id IN :warehouseIds AND ill.deleted = false")
fun findAllByWarehouseIdInAndDeletedIsFalse(@Param("warehouseIds") warehouseIds: List<Long>): List<InventoryLotLine>

@Query("SELECT ill FROM InventoryLotLine ill WHERE ill.warehouse.id IN :warehouseIds AND ill.deleted = false AND ill.status = 'available'")
fun findAllByWarehouseIdInAndDeletedIsFalseAndStatusIsAvailable(@Param("warehouseIds") warehouseIds: List<Long>): List<InventoryLotLine>

@Query("""
SELECT ill FROM InventoryLotLine ill
WHERE ill.deleted = false


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt Целия файл

@@ -45,4 +45,7 @@ interface InventoryRepository: AbstractRepository<Inventory, Long> {
fun findAllByItemIdAndDeletedIsFalse(itemId: Long): List<Inventory>

fun findFirstByItemIdAndDeletedIsFalseOrderByIdAsc(itemId: Long): Inventory?

/** 與 item_uom 中 baseUnit=true 之 uomId 對齊時使用,避免同一 item 多筆 inventory 時 NonUniqueResult */
fun findFirstByItemIdAndUomIdAndDeletedIsFalseOrderByIdAsc(itemId: Long, uomId: Long): Inventory?
}

+ 27
- 30
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt Целия файл

@@ -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!!,


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt Целия файл

@@ -615,7 +615,7 @@ class StockTakeRecordService(
val warehouseIds = warehouses.mapNotNull { it.id }
println("Found ${warehouses.size} warehouses for section $stockTakeSection")

val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds)
val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalseAndStatusIsAvailable(warehouseIds)
println("Found ${inventoryLotLines.size} inventory lot lines")

val stockTakeRecordsMap = if (stockTakeId != null) {


Зареждане…
Отказ
Запис