Selaa lähdekoodia

建議批號 UOM 檢核/不符則擋、標籤列印只顯示同 UOM

production
CANCERYS\kw093 9 tuntia sitten
vanhempi
commit
3ebf46e304
15 muutettua tiedostoa jossa 268 lisäystä ja 64 poistoa
  1. +124
    -36
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  2. +9
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/ItemsController.kt
  3. +2
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/HierarchicalFgPayloadAssembler.kt
  4. +1
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderWorkbenchService.kt
  5. +2
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt
  6. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryLotLineInfo.kt
  7. +16
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt
  8. +2
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt
  9. +5
    -4
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  10. +12
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineWorkbenchService.kt
  11. +23
    -6
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt
  12. +17
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotWorkbenchService.kt
  13. +4
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/InventoryController.kt
  14. +37
    -11
      src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt
  15. +11
    -3
      src/main/java/com/ffii/fpsms/modules/stock/web/model/LotLineInfo.kt

+ 124
- 36
src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt Näytä tiedosto

@@ -266,6 +266,7 @@ open class ItemsService(
return jdbcDao.queryForList(sql.toString(), args); return jdbcDao.queryForList(sql.toString(), args);
} }


/** FP-MTMS Version Checklist | Functions Ref. No. 35 | v1.0.1 | 2026-07-22 */
open fun getPickOrderItemsByPage(args: Map<String, Any>): List<Map<String, Any>> { open fun getPickOrderItemsByPage(args: Map<String, Any>): List<Map<String, Any>> {
try { try {
println("=== Debug: getPickOrderItemsByPage in ItemsService ===") println("=== Debug: getPickOrderItemsByPage in ItemsService ===")
@@ -274,10 +275,12 @@ open class ItemsService(
val sql = StringBuilder( val sql = StringBuilder(
""" """
SELECT SELECT
CONCAT(po.id, '-', pol.itemId) as id,
CONCAT(po.id, '-', pol.id) as id,
po.id as pickOrderId, po.id as pickOrderId,
po.code as pickOrderCode, po.code as pickOrderCode,
pol.id as pickOrderLineId,
pol.itemId, pol.itemId,
pol.uomId,
i.code as itemCode, i.code as itemCode,
i.name as itemName, i.name as itemName,
pol.qty as requiredQty, pol.qty as requiredQty,
@@ -296,15 +299,16 @@ open class ItemsService(
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
il.itemId, il.itemId,
iu.uomId as uomId,
SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty
FROM inventory_lot_line ill FROM inventory_lot_line ill
JOIN inventory_lot il ON il.id = ill.inventoryLotId JOIN inventory_lot il ON il.id = ill.inventoryLotId
LEFT JOIN item_uom iu_ratio ON iu_ratio.itemId = il.itemId AND iu_ratio.deleted = false AND iu_ratio.salesUnit = true
JOIN item_uom iu ON iu.id = ill.stockItemUomId AND iu.deleted = false
WHERE ill.status = 'available' WHERE ill.status = 'available'
AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0 AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0
AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE()) AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE())
GROUP BY il.itemId
) inv ON inv.itemId = pol.itemId
GROUP BY il.itemId, iu.uomId
) inv ON inv.itemId = pol.itemId AND inv.uomId = pol.uomId
WHERE po.deleted = false WHERE po.deleted = false
AND po.consoCode IS NULL AND po.consoCode IS NULL
""" """
@@ -752,56 +756,66 @@ open class ItemsService(
) )
} }


/**
* FP-MTMS Version Checklist | Functions Ref. No. 35 | v1.0.1 | 2026-07-22
* Pick-order create search: one row per item + available lot UOM
* (available qty summed per UOM). Items with no available stock are omitted.
*/
open fun getItemsWithDetailsByPage(args: Map<String, Any>): List<Map<String, Any>> { open fun getItemsWithDetailsByPage(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder( val sql = StringBuilder(
"select"
+ " i.id, "
+ " i.code, "
+ " i.name, "
+ " i.type, "
+ " i.description, "
+ " uc.id as uomId, "
+ " uc.code as uom, "
+ " uc.udfudesc as uomDesc, "
+ " COALESCE(inv.availableQty, 0) as currentStockBalance "
+ " from items i "
+ " left join item_uom iu on iu.itemId = i.id and iu.deleted = false and iu.salesUnit = true "
+ " left join uom_conversion uc on uc.id = iu.uomId "
+ " left join ("
+ " SELECT "
+ " il.itemId, "
+ " SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty "
+ " FROM inventory_lot_line ill "
+ " JOIN inventory_lot il ON il.id = ill.inventoryLotId "
+ " LEFT JOIN item_uom iu_ratio ON iu_ratio.itemId = il.itemId AND iu_ratio.deleted = false AND iu_ratio.salesUnit = true "
+ " WHERE ill.status = 'available' "
+ " AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0 "
+ " AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE()) "
+ " GROUP BY il.itemId "
+ " ) inv ON inv.itemId = i.id "
+ " where i.deleted = false "
"""
SELECT
i.id,
i.code,
i.name,
i.type,
i.description,
uc.id AS uomId,
uc.code AS uom,
uc.udfudesc AS uomDesc,
SUM(
COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)
) AS currentStockBalance
FROM items i
JOIN inventory_lot il
ON il.itemId = i.id
AND il.deleted = false
JOIN inventory_lot_line ill
ON ill.inventoryLotId = il.id
AND ill.deleted = false
JOIN item_uom iu
ON iu.id = ill.stockItemUomId
AND iu.deleted = false
JOIN uom_conversion uc
ON uc.id = iu.uomId
WHERE i.deleted = false
AND ill.status = 'available'
AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0
AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE())
""".trimIndent()
) )


// Add search conditions
if (args.containsKey("name")) { if (args.containsKey("name")) {
sql.append(" AND i.name LIKE :name ") sql.append(" AND i.name LIKE :name ")
} }

if (args.containsKey("code")) { if (args.containsKey("code")) {
sql.append(" AND i.code LIKE :code ") sql.append(" AND i.code LIKE :code ")
} }

if (args.containsKey("description")) { if (args.containsKey("description")) {
sql.append(" AND i.description LIKE :description ") sql.append(" AND i.description LIKE :description ")
} }

if (args.containsKey("type")) { if (args.containsKey("type")) {
sql.append(" AND i.type = :type ") sql.append(" AND i.type = :type ")
} }


sql.append(" ORDER BY i.name ASC ")
sql.append(
"""
GROUP BY i.id, i.code, i.name, i.type, i.description, uc.id, uc.code, uc.udfudesc
ORDER BY i.name ASC, uc.udfudesc ASC
""".trimIndent()
)


return jdbcDao.queryForList(sql.toString(), args);
return jdbcDao.queryForList(sql.toString(), args)
} }


open fun getItemOrderOrderedItems(search: String?, type: String? = null): List<Map<String, Any>> { open fun getItemOrderOrderedItems(search: String?, type: String? = null): List<Map<String, Any>> {
@@ -971,4 +985,78 @@ open class ItemsService(
}?.takeIf { it.isNotBlank() } }?.takeIf { it.isNotBlank() }
} }


/**
* FP-MTMS Version Checklist | Functions Ref. No. 35 | v1.0.1 | 2026-07-22
* Pick-order create: stock UOM (default) + distinct UOMs from currently available lots.
* Lot UOM = inventory_lot_line.stockItemUom → item_uom.uom (uom_conversion id).
*/
open fun getAvailablePickUoms(itemId: Long): Map<String, Any?> {
val args = mapOf("itemId" to itemId)

val stockRows = jdbcDao.queryForList(
"""
SELECT
uc.id AS uomId,
uc.code AS uomCode,
uc.udfudesc AS uomDesc
FROM item_uom iu
JOIN uom_conversion uc ON uc.id = iu.uomId
WHERE iu.itemId = :itemId
AND iu.deleted = false
AND iu.stockUnit = true
LIMIT 1
""".trimIndent(),
args,
)
val stockUom = stockRows.firstOrNull()?.let { row ->
mapOf(
"uomId" to row["uomId"],
"uomCode" to row["uomCode"],
"uomDesc" to row["uomDesc"],
)
}

val lotRows = jdbcDao.queryForList(
"""
SELECT DISTINCT
uc.id AS uomId,
uc.code AS uomCode,
uc.udfudesc AS uomDesc
FROM inventory_lot_line ill
JOIN inventory_lot il ON il.id = ill.inventoryLotId
JOIN item_uom iu ON iu.id = ill.stockItemUomId AND iu.deleted = false
JOIN uom_conversion uc ON uc.id = iu.uomId
WHERE il.itemId = :itemId
AND ill.deleted = false
AND ill.status = 'available'
AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0)) > 0
AND (il.expiryDate IS NULL OR il.expiryDate >= CURDATE())
ORDER BY uc.udfudesc ASC
""".trimIndent(),
args,
)

val byId = linkedMapOf<Long, Map<String, Any?>>()
fun putUom(row: Map<String, Any?>?) {
if (row == null) return
val id = (row["uomId"] as? Number)?.toLong() ?: return
if (id <= 0L) return
if (!byId.containsKey(id)) {
byId[id] = mapOf(
"uomId" to id,
"uomCode" to row["uomCode"],
"uomDesc" to row["uomDesc"],
)
}
}
putUom(stockUom)
lotRows.forEach { putUom(it) }

return mapOf(
"itemId" to itemId,
"stockUom" to stockUom,
"options" to byId.values.toList(),
)
}

} }

+ 9
- 0
src/main/java/com/ffii/fpsms/modules/master/web/ItemsController.kt Näytä tiedosto

@@ -45,6 +45,15 @@ class ItemsController(
fun allConsumables(): List<Map<String, Any>> { fun allConsumables(): List<Map<String, Any>> {
return itemsService.allConsumables() return itemsService.allConsumables()
} }

/**
* FP-MTMS Version Checklist | Functions Ref. No. 35 | v1.0.1 | 2026-07-22
* Stock UOM + distinct available-lot UOMs for pick-order create dropdown.
*/
@GetMapping("/{itemId}/available-pick-uoms")
fun getAvailablePickUoms(@PathVariable itemId: Long): Map<String, Any?> {
return itemsService.getAvailablePickUoms(itemId)
}
// @GetMapping("/getRecordByPage") // @GetMapping("/getRecordByPage")
// fun getAllItemsByPage(@RequestBody filterRequest: HttpServletRequest): RecordsRes<Map<String, Any>> { // fun getAllItemsByPage(@RequestBody filterRequest: HttpServletRequest): RecordsRes<Map<String, Any>> {
// val pageSize = filterRequest.getParameter("pageSize").toString().toInt(); // Default to 10 if not provided // val pageSize = filterRequest.getParameter("pageSize").toString().toInt(); // Default to 10 if not provided


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/service/HierarchicalFgPayloadAssembler.kt Näytä tiedosto

@@ -96,6 +96,7 @@ SELECT
i.code as itemCode, i.code as itemCode,
i.name as itemName, i.name as itemName,
i.item_Order as itemOrder, i.item_Order as itemOrder,
pol.uomId as uomId,
uc.code as uomCode, uc.code as uomCode,
uc.udfudesc as uomDesc, uc.udfudesc as uomDesc,
uc.udfShortDesc as uomShortDesc, uc.udfShortDesc as uomShortDesc,
@@ -261,6 +262,7 @@ ORDER BY
"id" to firstLineRow["itemId"], "id" to firstLineRow["itemId"],
"code" to firstLineRow["itemCode"], "code" to firstLineRow["itemCode"],
"name" to firstLineRow["itemName"], "name" to firstLineRow["itemName"],
"uomId" to firstLineRow["uomId"],
"uomCode" to firstLineRow["uomCode"], "uomCode" to firstLineRow["uomCode"],
"uomDesc" to firstLineRow["uomDesc"], "uomDesc" to firstLineRow["uomDesc"],
"uomShortDesc" to firstLineRow["uomShortDesc"], "uomShortDesc" to firstLineRow["uomShortDesc"],


+ 1
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderWorkbenchService.kt Näytä tiedosto

@@ -251,6 +251,7 @@ open class PickOrderWorkbenchService(
"id" to pol.item?.id, "id" to pol.item?.id,
"code" to pol.item?.code, "code" to pol.item?.code,
"name" to pol.item?.name, "name" to pol.item?.name,
"uomId" to pol.uom?.id,
"uomCode" to pol.uom?.code, "uomCode" to pol.uom?.code,
"uomDesc" to pol.uom?.udfudesc, "uomDesc" to pol.uom?.udfudesc,
), ),


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt Näytä tiedosto

@@ -47,6 +47,7 @@ interface InventoryRepository: AbstractRepository<Inventory, Long> {
): Page<InventoryInfo> ): Page<InventoryInfo>


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.0 | 2026-07-17
* Inventory search page only: no baseUnit/uomId match required; * Inventory search page only: no baseUnit/uomId match required;
* one row per item = latest inventory id (no larger id for same item). * one row per item = latest inventory id (no larger id for same item).
*/ */
@@ -72,6 +73,7 @@ interface InventoryRepository: AbstractRepository<Inventory, Long> {
pageable: Pageable, pageable: Pageable,
): Page<InventoryInfo> ): Page<InventoryInfo>


/** FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.0 | 2026-07-17 */
@Query( @Query(
""" """
SELECT i FROM Inventory i SELECT i FROM Inventory i


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryLotLineInfo.kt Näytä tiedosto

@@ -50,6 +50,9 @@ interface InventoryLotLineInfo {
@get:Value("#{target.stockUom?.uom?.udfudesc}") @get:Value("#{target.stockUom?.uom?.udfudesc}")
val uom: String? val uom: String?


@get:Value("#{target.stockUom?.uom?.id}")
val uomId: Long?

@get:Value("#{target.inventoryLot.expiryDate}") @get:Value("#{target.inventoryLot.expiryDate}")
val expiryDate: LocalDate val expiryDate: LocalDate
} }


+ 16
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt Näytä tiedosto

@@ -454,6 +454,7 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 34 | v1.0.1 | 2026-07-22
* DO workbench label modal: pickable qty = in - out (aligned with workbench scan-pick). * DO workbench label modal: pickable qty = in - out (aligned with workbench scan-pick).
*/ */
open fun analyzeQrCodeWorkbench(request: QrCodeAnalysisRequest): QrCodeAnalysisResponse { open fun analyzeQrCodeWorkbench(request: QrCodeAnalysisRequest): QrCodeAnalysisResponse {
@@ -461,16 +462,22 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 34 | v1.0.1 | 2026-07-22
* DO workbench label modal fallback (no stockInLineId/no-lot row): * DO workbench label modal fallback (no stockInLineId/no-lot row):
* list available lots by item only, using pickable qty = in - out. * list available lots by item only, using pickable qty = in - out.
* When [uomId] is set, only lots whose stockUom.uom.id matches are returned.
*/ */
open fun listWorkbenchAvailableLotsByItem(itemId: Long): WorkbenchItemLotsResponse {
open fun listWorkbenchAvailableLotsByItem(itemId: Long, uomId: Long? = null): WorkbenchItemLotsResponse {
val source = inventoryLotLineRepository val source = inventoryLotLineRepository
.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE) .findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE)
.asSequence() .asSequence()
.filter { !it.deleted && it.inventoryLot?.item != null } .filter { !it.deleted && it.inventoryLot?.item != null }
.filter { uomId == null || it.stockUom?.uom?.id == uomId }
.toList() .toList()
val item = source.firstOrNull()?.inventoryLot?.item val item = source.firstOrNull()?.inventoryLot?.item
?: inventoryLotLineRepository
.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE)
.firstOrNull { !it.deleted }?.inventoryLot?.item
if (item == null) { if (item == null) {
return WorkbenchItemLotsResponse( return WorkbenchItemLotsResponse(
itemId = itemId, itemId = itemId,
@@ -484,6 +491,7 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit
val lot = lotLine.inventoryLot ?: return@mapNotNull null val lot = lotLine.inventoryLot ?: return@mapNotNull null
val lotNo = lot.stockInLine?.lotNo ?: return@mapNotNull null val lotNo = lot.stockInLine?.lotNo ?: return@mapNotNull null
val uomDesc = lotLine.stockUom?.uom?.udfudesc ?: return@mapNotNull null val uomDesc = lotLine.stockUom?.uom?.udfudesc ?: return@mapNotNull null
val lotUomId = lotLine.stockUom?.uom?.id
val inQty = lotLine.inQty ?: BigDecimal.ZERO val inQty = lotLine.inQty ?: BigDecimal.ZERO
val outQty = lotLine.outQty ?: BigDecimal.ZERO val outQty = lotLine.outQty ?: BigDecimal.ZERO
val remainingQty = inQty.minus(outQty) val remainingQty = inQty.minus(outQty)
@@ -494,6 +502,7 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit
stockInLineId = lot.stockInLine?.id, stockInLineId = lot.stockInLine?.id,
availableQty = remainingQty, availableQty = remainingQty,
uom = uomDesc, uom = uomDesc,
uomId = lotUomId,
warehouseCode = lotLine.warehouse?.code, warehouseCode = lotLine.warehouse?.code,
warehouseName = lotLine.warehouse?.name, warehouseName = lotLine.warehouse?.name,
) )
@@ -521,14 +530,17 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit
val item = scannedInventoryLotLine.inventoryLot?.item val item = scannedInventoryLotLine.inventoryLot?.item
?: throw IllegalStateException("Item not found for lot line id=${scannedInventoryLotLine.id}") ?: throw IllegalStateException("Item not found for lot line id=${scannedInventoryLotLine.id}")


val filterUomId = request.uomId
val sameItemLots = inventoryLotLineRepository val sameItemLots = inventoryLotLineRepository
.findAllByInventoryLotItemIdAndStatus(request.itemId, InventoryLotLineStatus.AVAILABLE) .findAllByInventoryLotItemIdAndStatus(request.itemId, InventoryLotLineStatus.AVAILABLE)
.asSequence() .asSequence()
.filter { it.id != scannedInventoryLotLine.id } .filter { it.id != scannedInventoryLotLine.id }
.filter { filterUomId == null || it.stockUom?.uom?.id == filterUomId }
.mapNotNull { lotLine -> .mapNotNull { lotLine ->
val lot = lotLine.inventoryLot ?: return@mapNotNull null val lot = lotLine.inventoryLot ?: return@mapNotNull null
val lotNo = lot.stockInLine?.lotNo ?: return@mapNotNull null val lotNo = lot.stockInLine?.lotNo ?: return@mapNotNull null
val uomDesc = lotLine.stockUom?.uom?.udfudesc ?: return@mapNotNull null val uomDesc = lotLine.stockUom?.uom?.udfudesc ?: return@mapNotNull null
val lotUomId = lotLine.stockUom?.uom?.id
val whCode = lotLine.warehouse?.code val whCode = lotLine.warehouse?.code
val whName = lotLine.warehouse?.name val whName = lotLine.warehouse?.name


@@ -545,6 +557,7 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit
stockInLineId = lot.stockInLine?.id, stockInLineId = lot.stockInLine?.id,
availableQty = remainingQty, availableQty = remainingQty,
uom = uomDesc, uom = uomDesc,
uomId = lotUomId,
warehouseCode = whCode, warehouseCode = whCode,
warehouseName = whName, warehouseName = whName,
) )
@@ -569,6 +582,8 @@ open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantit
?: throw IllegalStateException("inventoryLotLineId missing on scanned lot line"), ?: throw IllegalStateException("inventoryLotLineId missing on scanned lot line"),
warehouseCode = scannedInventoryLotLine.warehouse?.code, warehouseCode = scannedInventoryLotLine.warehouse?.code,
warehouseName = scannedInventoryLotLine.warehouse?.name, warehouseName = scannedInventoryLotLine.warehouse?.name,
uom = scannedInventoryLotLine.stockUom?.uom?.udfudesc,
uomId = scannedInventoryLotLine.stockUom?.uom?.id,
), ),
sameItemLots = sameItemLots, sameItemLots = sameItemLots,
) )


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt Näytä tiedosto

@@ -89,8 +89,9 @@ open class InventoryService(
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.0 | 2026-07-17
* Inventory search page only: no baseUnit/uomId filter; * Inventory search page only: no baseUnit/uomId filter;
* one inventory row per item (MAX id). Shared getRecordByPage is unchanged.
* one inventory row per item (latest id). Shared getRecordByPage is unchanged.
*/ */
open fun searchInventoriesByLatestInventory( open fun searchInventoriesByLatestInventory(
request: SearchInventoryRequest, request: SearchInventoryRequest,


+ 5
- 4
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt Näytä tiedosto

@@ -1034,9 +1034,6 @@ open class StockInLineService(
if (request.status == StockInLineStatus.RECEIVED.status) { if (request.status == StockInLineStatus.RECEIVED.status) {
val _tPutAwayTotal = System.nanoTime() val _tPutAwayTotal = System.nanoTime()
// Putaway // Putaway
var savedInventoryLotLine: InventoryLotLine? = null

// savedInventoryLotLine = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine)
val _tSaveLotLines = System.nanoTime() val _tSaveLotLines = System.nanoTime()
val savedInventoryLotLines = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine) val savedInventoryLotLines = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine)
_logStep("saveInventoryLotLineWhenStockIn", _tSaveLotLines) _logStep("saveInventoryLotLineWhenStockIn", _tSaveLotLines)
@@ -1045,6 +1042,11 @@ open class StockInLineService(
val inventoryLotLines = stockInLine.inventoryLot?.let { it.id?.let { _id -> inventoryLotLineRepository.findAllByInventoryLotId(_id) } } ?: listOf() val inventoryLotLines = stockInLine.inventoryLot?.let { it.id?.let { _id -> inventoryLotLineRepository.findAllByInventoryLotId(_id) } } ?: listOf()
_logStep("inventoryLotLineRepository.findAllByInventoryLotId", _tFindAllLotLines) _logStep("inventoryLotLineRepository.findAllByInventoryLotId", _tFindAllLotLines)


// NOR putaway can create multiple ILL (per warehouse); SIL only has one FK — keep latest from this putaway.
(savedInventoryLotLines.lastOrNull() ?: inventoryLotLines.lastOrNull())?.let { primaryLotLine ->
stockInLine.inventoryLotLine = primaryLotLine
}

// ✅ 每次上架都寫一筆 stock_ledger(inQty = 本次上架的庫存數量) // ✅ 每次上架都寫一筆 stock_ledger(inQty = 本次上架的庫存數量)
val _tDeltaCompute = System.nanoTime() val _tDeltaCompute = System.nanoTime()
val putAwayDeltaStockQty = (request.inventoryLotLines ?: listOf()).sumOf { line -> val putAwayDeltaStockQty = (request.inventoryLotLines ?: listOf()).sumOf { line ->
@@ -1102,7 +1104,6 @@ open class StockInLineService(
else else
StockInLineStatus.PARTIALLY_COMPLETE.status StockInLineStatus.PARTIALLY_COMPLETE.status
} }
// this.inventoryLotLine = savedInventoryLotLine
} }
// Update JO Status // Update JO Status
if (stockInLine.jobOrder != null) { //TODO Improve if (stockInLine.jobOrder != null) { //TODO Improve


+ 12
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineWorkbenchService.kt Näytä tiedosto

@@ -197,6 +197,9 @@ open class StockOutLineWorkbenchService(
) )
} }


/**
* FP-MTMS Version Checklist | Functions Ref. No. 33 | v1.0.1 | 2026-07-22
*/
@Transactional(rollbackFor = [Exception::class]) @Transactional(rollbackFor = [Exception::class])
open fun scanPick(request: WorkbenchScanPickRequest): MessageResponse { open fun scanPick(request: WorkbenchScanPickRequest): MessageResponse {
val lotNo = request.lotNo.trim() val lotNo = request.lotNo.trim()
@@ -315,6 +318,15 @@ open class StockOutLineWorkbenchService(
) )
} }


val polUomId = pol.uom?.id
val lotUomId = scannedIll.stockUom?.uom?.id
if (polUomId != null && lotUomId != null && polUomId != lotUomId) {
return MessageResponse(
id = sol.id, name = "scan_pick", code = "INVALID", type = "workbench_scan_pick",
message = "Scanned lot UOM does not match pick line UOM", errorPosition = null, entity = null
)
}

sol.inventoryLotLine = scannedIll sol.inventoryLotLine = scannedIll


val required = pol.qty ?: BigDecimal.ZERO val required = pol.qty ?: BigDecimal.ZERO


+ 23
- 6
src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt Näytä tiedosto

@@ -96,6 +96,7 @@ open class SuggestedPickLotService(
return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList)
} }


/** FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22 */
open fun suggestionForPickOrderLines(request: SuggestedPickLotForPolRequest): SuggestedPickLotResponse { open fun suggestionForPickOrderLines(request: SuggestedPickLotForPolRequest): SuggestedPickLotResponse {
val pols = request.pickOrderLines val pols = request.pickOrderLines
val itemIds = pols.mapNotNull { it.item?.id } val itemIds = pols.mapNotNull { it.item?.id }
@@ -123,7 +124,9 @@ open class SuggestedPickLotService(
pols.forEach { line -> pols.forEach { line ->
val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) }
val polUomId = line.uom?.id
val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() val lotLines = availableInventoryLotLines[line.item?.id].orEmpty()
.filter { polUomId == null || it.uomId == polUomId }
val ratio = one // (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) val ratio = one // (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP)
val pickOrder = line.pickOrder val pickOrder = line.pickOrder
val isDoPickOrder = pickOrder?.type?.value == "do" || pickOrder?.type?.value == "delivery_order" val isDoPickOrder = pickOrder?.type?.value == "do" || pickOrder?.type?.value == "delivery_order"
@@ -275,6 +278,7 @@ open class SuggestedPickLotService(
} }
return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList)
} }
/** FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22 */
open fun suggestionForPickOrderLinesForJobOrder(request: SuggestedPickLotForPolRequest): SuggestedPickLotResponse { open fun suggestionForPickOrderLinesForJobOrder(request: SuggestedPickLotForPolRequest): SuggestedPickLotResponse {
val pols = request.pickOrderLines val pols = request.pickOrderLines
val itemIds = pols.mapNotNull { it.item?.id } val itemIds = pols.mapNotNull { it.item?.id }
@@ -302,8 +306,10 @@ open class SuggestedPickLotService(
pols.forEach { line -> pols.forEach { line ->
val itemId = line.item?.id val itemId = line.item?.id
val isWipItem = itemId != null && wipItemIdSet.contains(itemId) val isWipItem = itemId != null && wipItemIdSet.contains(itemId)
val polUomId = line.uom?.id

val lotInfos = availableInventoryLotLines[itemId].orEmpty() val lotInfos = availableInventoryLotLines[itemId].orEmpty()
.filter { polUomId == null || it.uomId == polUomId }
val lotIds = lotInfos.mapNotNull { it.id } val lotIds = lotInfos.mapNotNull { it.id }
// 仅一次查询,避免后续重复 findById // 仅一次查询,避免后续重复 findById
val lotEntities = if (lotIds.isNotEmpty()) inventoryLotLineRepository.findAllById(lotIds) else emptyList() val lotEntities = if (lotIds.isNotEmpty()) inventoryLotLineRepository.findAllById(lotIds) else emptyList()
@@ -580,6 +586,7 @@ open class SuggestedPickLotService(
return null return null
} }
} }
/** FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22 */
@Transactional(rollbackFor = [Exception::class]) @Transactional(rollbackFor = [Exception::class])
open fun resuggestPickOrder(pickOrderId: Long): MessageResponse { open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
try { try {
@@ -1290,11 +1297,15 @@ private fun generateOptimalSuggestionsForAllPickOrders(
var lotRemainingQty = availableQty var lotRemainingQty = availableQty
// Allocate this lot to pick order lines in order
// Allocate this lot to pick order lines in order (same UOM only)
remainingPickOrderLines.removeAll { pickOrderLine -> remainingPickOrderLines.removeAll { pickOrderLine ->
val lineId = pickOrderLine.id val lineId = pickOrderLine.id
val lineRemainingQty = remainingQtyPerLine[lineId] ?: zero val lineRemainingQty = remainingQtyPerLine[lineId] ?: zero
val polUomId = pickOrderLine.uom?.id
if (polUomId != null && lot.stockUom?.uom?.id != polUomId) {
return@removeAll false
}

if (lineRemainingQty <= zero || lotRemainingQty <= zero) return@removeAll false if (lineRemainingQty <= zero || lotRemainingQty <= zero) return@removeAll false
val assignQty = minOf(lotRemainingQty, lineRemainingQty) val assignQty = minOf(lotRemainingQty, lineRemainingQty)
@@ -1394,10 +1405,12 @@ private fun updateInventoryTableAfterResuggest(pickOrder: PickOrder) {
if (excessQtyInSalesUnits <= zero) return@forEach if (excessQtyInSalesUnits <= zero) return@forEach
// Get available inventory lots for this item (FEFO order)
val polUomId = orderLine.uom?.id
// Get available inventory lots for this item (FEFO order, same UOM)
val availableLots = inventoryLotLineService val availableLots = inventoryLotLineService
.allInventoryLotLinesByItemIdIn(listOf(itemId)) .allInventoryLotLinesByItemIdIn(listOf(itemId))
.filter { it.status == InventoryLotLineStatus.AVAILABLE.value } .filter { it.status == InventoryLotLineStatus.AVAILABLE.value }
.filter { polUomId == null || it.uomId == polUomId }
.sortedBy { it.expiryDate } .sortedBy { it.expiryDate }
var remainingQtyInBaseUnits = excessQtyInBaseUnits var remainingQtyInBaseUnits = excessQtyInBaseUnits
@@ -1550,9 +1563,11 @@ private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): L
println("Available lots: ${availableLots.size}, Unavailable lots: ${unavailableLots.size}") println("Available lots: ${availableLots.size}, Unavailable lots: ${unavailableLots.size}")
println("Total unavailable hold qty to redistribute: $totalUnavailableHoldQty") println("Total unavailable hold qty to redistribute: $totalUnavailableHoldQty")
// Generate suggestions from available lots ONLY
val polUomId = orderLine.uom?.id
// Generate suggestions from available lots ONLY (same UOM as POL)
availableLots.forEach { lotInfo -> availableLots.forEach { lotInfo ->
if (remainingQtyInSalesUnits <= zero) return@forEach if (remainingQtyInSalesUnits <= zero) return@forEach
if (polUomId != null && lotInfo.uomId != polUomId) return@forEach


val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) }
?: return@forEach ?: return@forEach
@@ -1620,12 +1635,14 @@ private fun generateCorrectSuggestionsWithOriginalHolds(
val salesUnit = itemUomService.findSalesUnitByItemId(itemId) val salesUnit = itemUomService.findSalesUnitByItemId(itemId)
val ratio = one val ratio = one
val polUomId = orderLine.uom?.id
// Get available inventory lots for this item (FEFO order)
// Get available inventory lots for this item (FEFO order, same UOM)
val availableLots = inventoryLotLineService val availableLots = inventoryLotLineService
.allInventoryLotLinesByItemIdIn(listOf(itemId)) .allInventoryLotLinesByItemIdIn(listOf(itemId))
.filter { it.status == InventoryLotLineStatus.AVAILABLE.value } .filter { it.status == InventoryLotLineStatus.AVAILABLE.value }
.filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) }
.filter { polUomId == null || it.uomId == polUomId }
.sortedBy { it.expiryDate } .sortedBy { it.expiryDate }
// Calculate total quantity that needs to be redistributed // Calculate total quantity that needs to be redistributed


+ 17
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotWorkbenchService.kt Näytä tiedosto

@@ -150,6 +150,16 @@ open class SuggestedPickLotWorkbenchService(
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22
* Lot stock UOM must match pick_order_line.uom (UomConversion id).
*/
private fun matchesPolUom(lot: InventoryLotLine, line: PickOrderLine): Boolean {
val polUomId = line.uom?.id ?: return false
return lot.stockUom?.uom?.id == polUomId
}

/**
* FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22
* Build suggestions with no-hold rule: * Build suggestions with no-hold rule:
* available = inQty - outQty (ignore holdQty). * available = inQty - outQty (ignore holdQty).
* When [desired] is a single segment, reuses one SPL row (max id) and repoints lot + qty (switch-lot without new id). * When [desired] is a single segment, reuses one SPL row (max id) and repoints lot + qty (switch-lot without new id).
@@ -207,7 +217,7 @@ open class SuggestedPickLotWorkbenchService(
return@forEach return@forEach
} }


val lots = lotsByItem[line.item?.id].orEmpty()
val lots = lotsByItem[line.item?.id].orEmpty().filter { matchesPolUom(it, line) }
val desired = buildDesiredAllocations(lots, remaining, lineId) val desired = buildDesiredAllocations(lots, remaining, lineId)
created += syncSuggestedPickLotsForLine(line, desired) created += syncSuggestedPickLotsForLine(line, desired)
} }
@@ -282,6 +292,7 @@ open class SuggestedPickLotWorkbenchService(
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22
* Same no-hold allocation as [rebuildNoHoldSuggestionsForPickOrder], but only for one [pickOrderLineId]. * Same no-hold allocation as [rebuildNoHoldSuggestionsForPickOrder], but only for one [pickOrderLineId].
* Used by workbench scan-pick to avoid rebuilding suggestions for every line on the pick order. * Used by workbench scan-pick to avoid rebuilding suggestions for every line on the pick order.
*/ */
@@ -340,6 +351,7 @@ open class SuggestedPickLotWorkbenchService(
.filter { it.status == InventoryLotLineStatus.AVAILABLE } .filter { it.status == InventoryLotLineStatus.AVAILABLE }
.filter { it.inventoryLot?.expiryDate?.isBefore(today) != true } .filter { it.inventoryLot?.expiryDate?.isBefore(today) != true }
.filter { !isWarehouseExcluded(it, excludeSet) } .filter { !isWarehouseExcluded(it, excludeSet) }
.filter { matchesPolUom(it, lineFresh) }
.filter { lot -> .filter { lot ->
if (storeKey == null) true if (storeKey == null) true
else normalizeStoreId(lot.warehouse?.store_id) == storeKey else normalizeStoreId(lot.warehouse?.store_id) == storeKey
@@ -372,6 +384,7 @@ open class SuggestedPickLotWorkbenchService(
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22
* Workbench explicit-qty remainder support (single-next-lot): * Workbench explicit-qty remainder support (single-next-lot):
* Create exactly ONE suggested lot row for this POL with [targetQty] pointing to the next available lot. * Create exactly ONE suggested lot row for this POL with [targetQty] pointing to the next available lot.
* When [storeId] is provided, the next lot must be within the same store (warehouse.store_id). * When [storeId] is provided, the next lot must be within the same store (warehouse.store_id).
@@ -411,6 +424,7 @@ open class SuggestedPickLotWorkbenchService(
.filter { it.status == InventoryLotLineStatus.AVAILABLE } .filter { it.status == InventoryLotLineStatus.AVAILABLE }
.filter { it.inventoryLot?.expiryDate?.isBefore(today) != true } .filter { it.inventoryLot?.expiryDate?.isBefore(today) != true }
.filter { !isWarehouseExcluded(it, excludeSet) } .filter { !isWarehouseExcluded(it, excludeSet) }
.filter { matchesPolUom(it, line) }
.filter { lot -> .filter { lot ->
if (excludeInventoryLotLineId == null) true else lot.id != excludeInventoryLotLineId if (excludeInventoryLotLineId == null) true else lot.id != excludeInventoryLotLineId
} }
@@ -431,6 +445,7 @@ open class SuggestedPickLotWorkbenchService(
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 32 | v1.0.1 | 2026-07-22
* Workbench explicit-qty split support: * Workbench explicit-qty split support:
* Set suggestions for this POL to an exact [targetQty] (independent of POL.qty). * Set suggestions for this POL to an exact [targetQty] (independent of POL.qty).
* - If [targetQty] <= 0: soft-delete all active suggestions for the line. * - If [targetQty] <= 0: soft-delete all active suggestions for the line.
@@ -466,6 +481,7 @@ open class SuggestedPickLotWorkbenchService(
.filter { it.status == InventoryLotLineStatus.AVAILABLE } .filter { it.status == InventoryLotLineStatus.AVAILABLE }
.filter { it.inventoryLot?.expiryDate?.isBefore(today) != true } .filter { it.inventoryLot?.expiryDate?.isBefore(today) != true }
.filter { !isWarehouseExcluded(it, excludeSet) } .filter { !isWarehouseExcluded(it, excludeSet) }
.filter { matchesPolUom(it, line) }
.sortedBy { it.inventoryLot?.expiryDate } .sortedBy { it.inventoryLot?.expiryDate }


val desired = buildDesiredAllocations(lots, targetQty, pickOrderLineId) val desired = buildDesiredAllocations(lots, targetQty, pickOrderLineId)


+ 4
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/InventoryController.kt Näytä tiedosto

@@ -21,7 +21,10 @@ class InventoryController(
return inventoryService.allInventoriesByPage(request); return inventoryService.allInventoriesByPage(request);
} }


/** Inventory search page only: latest inventory row per item, no baseUnit/uomId filter. */
/**
* FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.0 | 2026-07-17
* Inventory search page only: latest inventory row per item, no baseUnit/uomId filter.
*/
@GetMapping("/searchLatest/getRecordByPage") @GetMapping("/searchLatest/getRecordByPage")
fun searchInventoriesByLatestInventory( fun searchInventoriesByLatestInventory(
@ModelAttribute request: SearchInventoryRequest, @ModelAttribute request: SearchInventoryRequest,


+ 37
- 11
src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt Näytä tiedosto

@@ -72,18 +72,35 @@ class InventoryLotLineController (
val stockInLine = stockInLineRepository.findById(stockInLineId).orElseThrow { val stockInLine = stockInLineRepository.findById(stockInLineId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "stockInLineId $stockInLineId not found") ResponseStatusException(HttpStatus.NOT_FOUND, "stockInLineId $stockInLineId not found")
} }
val inventoryLotLine = stockInLine.inventoryLotLine!!
// NOR putaway historically may leave inventoryLotLineId null; fall back via inventoryLot.
val inventoryLotLine = stockInLine.inventoryLotLine
?: stockInLine.inventoryLot?.inventoryLotLines?.firstOrNull()
?: throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"No inventory lot line for stockInLineId $stockInLineId",
)
val zero = BigDecimal.ZERO val zero = BigDecimal.ZERO
val item = inventoryLotLine.inventoryLot?.item
?: throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"Item not found for stockInLineId $stockInLineId",
)
val uom = inventoryLotLine.stockUom?.uom
return LotLineInfo( return LotLineInfo(
inventoryLotLineId = inventoryLotLine.id!!,
itemId = inventoryLotLine.inventoryLot!!.item!!.id!!,
itemNo = inventoryLotLine.inventoryLot!!.item!!.code!!,
itemName = inventoryLotLine.inventoryLot!!.item!!.name!!,
lotNo = stockInLine.lotNo!!,
inventoryLotLineId = inventoryLotLine.id
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "inventoryLotLine id missing"),
itemId = item.id
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "item id missing"),
itemNo = item.code ?: "",
itemName = item.name ?: "",
lotNo = stockInLine.lotNo
?: inventoryLotLine.inventoryLot?.lotNo
?: "",
remainingQty = (inventoryLotLine.inQty ?: zero) remainingQty = (inventoryLotLine.inQty ?: zero)
.minus(inventoryLotLine.outQty ?: zero) .minus(inventoryLotLine.outQty ?: zero)
.minus(inventoryLotLine.holdQty ?: zero), .minus(inventoryLotLine.holdQty ?: zero),
uom = inventoryLotLine.stockUom!!.uom!!.udfudesc!!
uom = uom?.udfudesc ?: uom?.code ?: "",
uomId = uom?.id,
) )
} }


@@ -182,15 +199,24 @@ class InventoryLotLineController (
return inventoryLotLineService.analyzeQrCode(request) return inventoryLotLineService.analyzeQrCode(request)
} }


/** DO workbench label modal: same-item lots use pickable qty inQty - outQty (no hold). */
/**
* FP-MTMS Version Checklist | Functions Ref. No. 34 | v1.0.1 | 2026-07-22
* DO workbench label modal: same-item lots use pickable qty inQty - outQty (no hold).
*/
@PostMapping("/workbench/analyze-qr-code") @PostMapping("/workbench/analyze-qr-code")
fun analyzeQrCodeWorkbench(@RequestBody request: QrCodeAnalysisRequest): QrCodeAnalysisResponse { fun analyzeQrCodeWorkbench(@RequestBody request: QrCodeAnalysisRequest): QrCodeAnalysisResponse {
return inventoryLotLineService.analyzeQrCodeWorkbench(request) return inventoryLotLineService.analyzeQrCodeWorkbench(request)
} }


/** Workbench label modal fallback: load same-item available lots without stockInLineId. */
/**
* FP-MTMS Version Checklist | Functions Ref. No. 34 | v1.0.1 | 2026-07-22
* Workbench label modal fallback: load same-item available lots without stockInLineId.
*/
@GetMapping("/workbench/available-lots-by-item/{itemId}") @GetMapping("/workbench/available-lots-by-item/{itemId}")
fun listWorkbenchAvailableLotsByItem(@PathVariable itemId: Long): WorkbenchItemLotsResponse {
return inventoryLotLineService.listWorkbenchAvailableLotsByItem(itemId)
fun listWorkbenchAvailableLotsByItem(
@PathVariable itemId: Long,
@RequestParam(required = false) uomId: Long?,
): WorkbenchItemLotsResponse {
return inventoryLotLineService.listWorkbenchAvailableLotsByItem(itemId, uomId)
} }
} }

+ 11
- 3
src/main/java/com/ffii/fpsms/modules/stock/web/model/LotLineInfo.kt Näytä tiedosto

@@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.stock.web.model


import java.math.BigDecimal import java.math.BigDecimal



data class LotLineInfo( data class LotLineInfo(
val inventoryLotLineId: Long, val inventoryLotLineId: Long,
val itemId: Long, val itemId: Long,
@@ -9,7 +10,9 @@ data class LotLineInfo(
val itemName: String, val itemName: String,
val lotNo: String, val lotNo: String,
val remainingQty: BigDecimal, val remainingQty: BigDecimal,
val uom: String
val uom: String,
/** UomConversion id from inventory_lot_line.stockItemUom → item_uom.uom */
val uomId: Long? = null,
) )
data class UpdateInventoryLotLineStatusRequest( data class UpdateInventoryLotLineStatusRequest(
val inventoryLotLineId: Long, val inventoryLotLineId: Long,
@@ -18,6 +21,8 @@ data class UpdateInventoryLotLineStatusRequest(
data class QrCodeAnalysisRequest( data class QrCodeAnalysisRequest(
val itemId: Long, val itemId: Long,
val stockInLineId: Long, val stockInLineId: Long,
/** When set, sameItemLots only include lots with this UomConversion id. */
val uomId: Long? = null,
) )


data class ScannedLotInfo( data class ScannedLotInfo(
@@ -25,7 +30,9 @@ data class ScannedLotInfo(
val lotNo: String, val lotNo: String,
val inventoryLotLineId: Long, val inventoryLotLineId: Long,
val warehouseCode: String? = null, val warehouseCode: String? = null,
val warehouseName: String? = null
val warehouseName: String? = null,
val uom: String? = null,
val uomId: Long? = null,
) )


data class SameItemLotInfo( data class SameItemLotInfo(
@@ -34,6 +41,7 @@ data class SameItemLotInfo(
val stockInLineId: Long? = null, val stockInLineId: Long? = null,
val availableQty: BigDecimal, val availableQty: BigDecimal,
val uom: String, val uom: String,
val uomId: Long? = null,
val warehouseCode: String? = null, val warehouseCode: String? = null,
val warehouseName: String? = null val warehouseName: String? = null
) )
@@ -51,4 +59,4 @@ data class WorkbenchItemLotsResponse(
val itemCode: String, val itemCode: String,
val itemName: String, val itemName: String,
val sameItemLots: List<SameItemLotInfo> val sameItemLots: List<SameItemLotInfo>
)
)

Ladataan…
Peruuta
Tallenna