소스 검색

inventory search fix

fix負數倉
CANCERYS\kw093 4 일 전
부모
커밋
c26f2c6a06
11개의 변경된 파일403개의 추가작업 그리고 56개의 파일을 삭제
  1. +5
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  2. +4
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  3. +28
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfoDto.kt
  4. +2
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryStockUomSearchRow.kt
  5. +28
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryStockUomSearchRowDto.kt
  6. +311
    -42
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt
  7. +3
    -2
      src/main/java/com/ffii/fpsms/modules/stock/service/StockAdjustmentService.kt
  8. +16
    -6
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  9. +3
    -3
      src/main/java/com/ffii/fpsms/modules/stock/web/InventoryController.kt
  10. +1
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/StockAdjustmentRequest.kt
  11. +2
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/model/StockInRequest.kt

+ 5
- 0
src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt 파일 보기

@@ -410,8 +410,13 @@ open class ItemsService(
"i.AverageUnitPrice as averageUnitPrice, " +
"i.LatestMarketUnitPrice as latestMarketUnitPrice, " +
"DATE_FORMAT(i.latestMupUpdatedDate, '%Y-%m-%d %H:%i') as latestMupUpdatedDate, " +
"uc_s.id as uomId, " +
"uc_s.code as uom, " +
"uc_s.udfudesc as uomDesc, " +
"uc_p.udfudesc as purchaseUnit " +
"FROM items i " +
"LEFT JOIN item_uom iu_s ON iu_s.itemId = i.id AND iu_s.deleted = false AND iu_s.stockUnit = true " +
"LEFT JOIN uom_conversion uc_s ON uc_s.id = iu_s.uomId " +
"LEFT JOIN item_uom iu_p ON iu_p.itemId = i.id AND iu_p.deleted = false AND iu_p.purchaseUnit = true " +
"LEFT JOIN uom_conversion uc_p ON uc_p.id = iu_p.uomId " +
"WHERE i.deleted = false"


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

@@ -156,7 +156,10 @@ WHERE ill.id = :id
AND (:lotNo IS NULL OR :lotNo = '' OR il.lotNo = :lotNo)
""",
)
/** FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.1 | 2026-09-10 */
/**
* FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.1 | 2026-09-10
* Lot UOMs only. Location Search uses [com.ffii.fpsms.modules.stock.service.InventoryService.searchInventoriesByLatestInventory].
*/
fun searchInventoryGroupedByStockUom(
@Param("code") code: String,
@Param("name") name: String,


+ 28
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfoDto.kt 파일 보기

@@ -0,0 +1,28 @@
package com.ffii.fpsms.modules.stock.entity.projection

import java.math.BigDecimal
import java.time.LocalDateTime

/** JDBC row for Item Search: inventory buckets plus current master stock UOM when missing. */
data class InventoryInfoDto(
override val id: Long? = null,
override val itemId: Long? = null,
override val itemCode: String? = null,
override val itemName: String? = null,
override val itemType: String? = null,
override val onHandQty: BigDecimal? = null,
override val onHoldQty: BigDecimal? = null,
override val unavailableQty: BigDecimal? = null,
override val availableQty: BigDecimal? = null,
override val stockUomId: Long? = null,
override val uomCode: String? = null,
override val uomUdfudesc: String? = null,
override val uomShortDesc: String? = null,
override val qtyPerSmallestUnit: BigDecimal? = null,
override val baseUom: String? = null,
override val price: BigDecimal? = null,
override val currencyName: String? = null,
override val status: String? = null,
override val latestMarketUnitPrice: Double? = null,
override val latestMupUpdatedDate: LocalDateTime? = null,
) : InventoryInfo

+ 2
- 1
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryStockUomSearchRow.kt 파일 보기

@@ -4,7 +4,8 @@ import java.math.BigDecimal
import java.time.LocalDateTime

/**
* Inventory search page: one row per item + lot-line stock UoM.
* Inventory search page: one row per item + UoM
* (lot-line / bucket UOMs, plus current master stockUnit when it has no rows).
*/
interface InventoryStockUomSearchRow {
val id: Long?


+ 28
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryStockUomSearchRowDto.kt 파일 보기

@@ -0,0 +1,28 @@
package com.ffii.fpsms.modules.stock.entity.projection

import java.math.BigDecimal
import java.time.LocalDateTime

/** JDBC row for inventory search: lot UOMs plus current master stock UOM when missing. */
data class InventoryStockUomSearchRowDto(
override val id: Long? = null,
override val itemId: Long? = null,
override val itemCode: String? = null,
override val itemName: String? = null,
override val itemType: String? = null,
override val onHandQty: BigDecimal? = null,
override val onHoldQty: BigDecimal? = null,
override val unavailableQty: BigDecimal? = null,
override val availableQty: BigDecimal? = null,
override val uomId: Long? = null,
override val uomCode: String? = null,
override val uomUdfudesc: String? = null,
override val uomShortDesc: String? = null,
override val qtyPerSmallestUnit: BigDecimal? = null,
override val baseUom: String? = null,
override val price: BigDecimal? = null,
override val currencyName: String? = null,
override val status: String? = null,
override val latestMarketUnitPrice: Double? = null,
override val latestMupUpdatedDate: LocalDateTime? = null,
) : InventoryStockUomSearchRow

+ 311
- 42
src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt 파일 보기

@@ -17,15 +17,17 @@ import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderType
import com.ffii.fpsms.modules.stock.entity.*
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo
import com.ffii.fpsms.modules.stock.entity.projection.InventoryInfoDto
import com.ffii.fpsms.modules.stock.entity.projection.InventoryStockUomSearchRow
import com.ffii.fpsms.modules.stock.entity.projection.InventoryStockUomSearchRowDto
import com.ffii.fpsms.modules.stock.web.model.SearchInventoryRequest
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.sql.Timestamp
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
@@ -58,67 +60,80 @@ open class InventoryService(
return inventoryRepository.findInventoryInfoByDeletedIsFalse();
}

open fun allInventoriesByPage(request: SearchInventoryRequest): RecordsRes<InventoryInfo>{
val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10)
/**
* Item Search: one row per (itemId, stockUomId) bucket, plus current master stockUnit
* as a 0-qty row when that UoM has no inventory bucket.
*/
open fun allInventoriesByPage(request: SearchInventoryRequest): RecordsRes<InventoryInfo> {
val pageSize = (request.pageSize ?: 10).coerceAtLeast(1)
val pageNum = (request.pageNum ?: 0).coerceAtLeast(0)
val code = request.code?.trim().orEmpty()
val name = request.name?.trim().orEmpty()
val type = request.type?.trim().orEmpty()
val lotNo = request.lotNo?.trim()?.takeIf { it.isNotEmpty() }

val response = if (lotNo != null) {
val itemIds = inventoryLotLineRepository.findDistinctItemIdsByLotNo(lotNo)
if (itemIds.isEmpty()) {
return RecordsRes(emptyList(), 0)
}
inventoryRepository.findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndItemIdInAndDeletedIsFalse(
code = request.code,
name = request.name,
type = request.type,
itemIds = itemIds,
pageable = pageable
)
val itemIds = if (lotNo != null) {
inventoryLotLineRepository.findDistinctItemIdsByLotNo(lotNo)
} else {
inventoryRepository.findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndDeletedIsFalse(
code = request.code,
name = request.name,
type = request.type,
pageable = pageable
)
emptyList()
}
if (lotNo != null && itemIds.isEmpty()) {
return RecordsRes(emptyList(), 0)
}

val records = response.content
val total = response.totalElements

return RecordsRes<InventoryInfo>(records, total.toInt());
val args = mutableMapOf<String, Any>(
"code" to code,
"name" to name,
"type" to type,
"hasItemIdFilter" to if (lotNo != null) 1 else 0,
"itemIds" to itemIds.ifEmpty { listOf(-1L) },
)
val unionSql = buildItemSearchUnionSql()
val total = jdbcDao.queryForInt("SELECT COUNT(*) FROM ($unionSql) t", args)
args["limit"] = pageSize
args["offset"] = pageNum * pageSize
val rows = jdbcDao.queryForList(
"$unionSql ORDER BY itemCode ASC, uomUdfudesc ASC LIMIT :limit OFFSET :offset",
args,
)
return RecordsRes(rows.map { mapItemSearchRow(it) }, total)
}

/**
* Location Search: one row per item + lot-line stock UoM, optionally filtered by warehouse.
* Item Search uses [allInventoriesByPage] (inventory buckets, no location).
* Location Search: one row per item + lot-line UoM, plus current master stockUnit
* as a 0-qty row when that UoM has no matching lots.
*/
open fun searchInventoriesByLatestInventory(
request: SearchInventoryRequest,
): RecordsRes<InventoryStockUomSearchRow> {
val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10)
val pageSize = (request.pageSize ?: 10).coerceAtLeast(1)
val pageNum = (request.pageNum ?: 0).coerceAtLeast(0)
val code = request.code?.trim().orEmpty()
val name = request.name?.trim().orEmpty()
val type = request.type?.trim().orEmpty()
val lotNo = request.lotNo?.trim()?.takeIf { it.isNotEmpty() }
val lotNo = request.lotNo?.trim().orEmpty()
val warehouseIds = resolveWarehouseIds(request.storeId, request.warehouse, request.area)
if (warehouseIds != null && warehouseIds.isEmpty()) {
return RecordsRes(emptyList(), 0)
}

val hasWarehouseFilter = warehouseIds != null
val response = inventoryLotLineRepository.searchInventoryGroupedByStockUom(
code = code,
name = name,
type = type,
status = InventoryLotLineStatus.AVAILABLE,
hasWarehouseFilter = hasWarehouseFilter,
warehouseIds = warehouseIds ?: listOf(-1L),
lotNo = lotNo,
pageable = pageable,
val args = mutableMapOf<String, Any>(
"code" to code,
"name" to name,
"type" to type,
"lotNo" to lotNo,
"status" to InventoryLotLineStatus.AVAILABLE.value,
"hasWarehouseFilter" to if (warehouseIds != null) 1 else 0,
"warehouseIds" to (warehouseIds ?: listOf(-1L)),
)

return RecordsRes(response.content, response.totalElements.toInt())
val unionSql = buildLocationSearchUnionSql()
val total = jdbcDao.queryForInt("SELECT COUNT(*) FROM ($unionSql) t", args)
args["limit"] = pageSize
args["offset"] = pageNum * pageSize
val rows = jdbcDao.queryForList(
"$unionSql ORDER BY itemCode ASC, uomUdfudesc ASC LIMIT :limit OFFSET :offset",
args,
)
return RecordsRes(rows.map { mapLocationSearchRow(it) }, total)
}

/** FP-MTMS Version Checklist | Functions Ref. No. 19 | v1.0.1 | 2026-09-10 */
@@ -140,6 +155,260 @@ open class InventoryService(
).mapNotNull { it.id }
}

private fun buildItemSearchUnionSql(): String {
val itemFilter = """
AND (:code = '' OR item.code LIKE CONCAT('%', :code, '%'))
AND (:name = '' OR item.name LIKE CONCAT('%', :name, '%'))
AND (:type = '' OR item.type = :type)
AND (:hasItemIdFilter = 0 OR item.id IN (:itemIds))
""".trimIndent()
return """
SELECT
inv.id AS id,
item.id AS itemId,
item.code AS itemCode,
item.name AS itemName,
item.type AS itemType,
COALESCE(inv.onHandQty, 0) AS onHandQty,
COALESCE(inv.onHoldQty, 0) AS onHoldQty,
COALESCE(inv.unavailableQty, 0) AS unavailableQty,
(COALESCE(inv.onHandQty, 0) - COALESCE(inv.unavailableQty, 0)) AS availableQty,
uc.id AS stockUomId,
uc.code AS uomCode,
uc.udfudesc AS uomUdfudesc,
uc.udfShortDesc AS uomShortDesc,
(COALESCE(inv.onHandQty, 0) - COALESCE(inv.unavailableQty, 0)) AS qtyPerSmallestUnit,
uc_base.udfudesc AS baseUom,
inv.price AS price,
cur.name AS currencyName,
inv.status AS status,
item.latestMarketUnitPrice AS latestMarketUnitPrice,
item.latestMupUpdatedDate AS latestMupUpdatedDate
FROM inventory inv
INNER JOIN items item ON item.id = inv.itemId AND item.deleted = 0
LEFT JOIN uom_conversion uc ON uc.id = inv.stockUomId
LEFT JOIN uom_conversion uc_base ON uc_base.id = inv.uomId
LEFT JOIN currency cur ON cur.id = inv.currencyId
WHERE inv.deleted = 0
$itemFilter

UNION ALL

SELECT
0 AS id,
item.id AS itemId,
item.code AS itemCode,
item.name AS itemName,
item.type AS itemType,
0 AS onHandQty,
0 AS onHoldQty,
0 AS unavailableQty,
0 AS availableQty,
uc.id AS stockUomId,
uc.code AS uomCode,
uc.udfudesc AS uomUdfudesc,
uc.udfShortDesc AS uomShortDesc,
0 AS qtyPerSmallestUnit,
uc.udfudesc AS baseUom,
NULL AS price,
NULL AS currencyName,
'active' AS status,
item.latestMarketUnitPrice AS latestMarketUnitPrice,
item.latestMupUpdatedDate AS latestMupUpdatedDate
FROM items item
INNER JOIN item_uom iu
ON iu.itemId = item.id
AND iu.stockUnit = 1
AND iu.deleted = 0
AND iu.id = (
SELECT MIN(iu2.id)
FROM item_uom iu2
WHERE iu2.itemId = item.id
AND iu2.stockUnit = 1
AND iu2.deleted = 0
)
INNER JOIN uom_conversion uc ON uc.id = iu.uomId
WHERE item.deleted = 0
$itemFilter
AND EXISTS (
SELECT 1
FROM inventory inv2
WHERE inv2.itemId = item.id
AND inv2.deleted = 0
)
AND NOT EXISTS (
SELECT 1
FROM inventory inv2
WHERE inv2.itemId = item.id
AND inv2.deleted = 0
AND inv2.stockUomId = uc.id
)
""".trimIndent()
}

private fun buildLocationSearchUnionSql(): String {
val itemFilter = """
AND (:code = '' OR LOWER(item.code) LIKE LOWER(CONCAT('%', :code, '%')))
AND (:name = '' OR LOWER(item.name) LIKE LOWER(CONCAT('%', :name, '%')))
AND (:type = '' OR item.type = :type)
""".trimIndent()
val lotFilter = """
ill.deleted = 0
AND il.deleted = 0
AND item.deleted = 0
AND ill.status = :status
AND (:hasWarehouseFilter = 0 OR ill.warehouseId IN (:warehouseIds))
AND (:lotNo = '' OR il.lotNo = :lotNo)
""".trimIndent()
val lotExistsFilter = """
ill2.deleted = 0
AND il2.deleted = 0
AND ill2.status = :status
AND (:hasWarehouseFilter = 0 OR ill2.warehouseId IN (:warehouseIds))
AND (:lotNo = '' OR il2.lotNo = :lotNo)
""".trimIndent()
return """
SELECT
MAX(ill.id) AS id,
item.id AS itemId,
item.code AS itemCode,
item.name AS itemName,
item.type AS itemType,
SUM(COALESCE(ill.inQty, 0)) AS onHandQty,
SUM(COALESCE(ill.holdQty, 0)) AS onHoldQty,
SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) AS availableQty,
uom.id AS uomId,
uom.code AS uomCode,
uom.udfudesc AS uomUdfudesc,
uom.udfShortDesc AS uomShortDesc,
SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) AS qtyPerSmallestUnit,
MAX(item.latestMarketUnitPrice) AS latestMarketUnitPrice,
MAX(item.latestMupUpdatedDate) AS latestMupUpdatedDate
FROM inventory_lot_line ill
INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId
INNER JOIN items item ON item.id = il.itemId
INNER JOIN item_uom su ON su.id = ill.stockItemUomId
INNER JOIN uom_conversion uom ON uom.id = su.uomId
WHERE $lotFilter
$itemFilter
GROUP BY item.id, item.code, item.name, item.type, uom.id, uom.code, uom.udfudesc, uom.udfShortDesc

UNION ALL

SELECT
0 AS id,
item.id AS itemId,
item.code AS itemCode,
item.name AS itemName,
item.type AS itemType,
0 AS onHandQty,
0 AS onHoldQty,
0 AS availableQty,
uom.id AS uomId,
uom.code AS uomCode,
uom.udfudesc AS uomUdfudesc,
uom.udfShortDesc AS uomShortDesc,
0 AS qtyPerSmallestUnit,
item.latestMarketUnitPrice AS latestMarketUnitPrice,
item.latestMupUpdatedDate AS latestMupUpdatedDate
FROM items item
INNER JOIN item_uom iu
ON iu.itemId = item.id
AND iu.stockUnit = 1
AND iu.deleted = 0
AND iu.id = (
SELECT MIN(iu2.id)
FROM item_uom iu2
WHERE iu2.itemId = item.id
AND iu2.stockUnit = 1
AND iu2.deleted = 0
)
INNER JOIN uom_conversion uom ON uom.id = iu.uomId
WHERE item.deleted = 0
$itemFilter
AND EXISTS (
SELECT 1
FROM inventory_lot_line ill2
INNER JOIN inventory_lot il2 ON il2.id = ill2.inventoryLotId
WHERE il2.itemId = item.id
AND $lotExistsFilter
)
AND NOT EXISTS (
SELECT 1
FROM inventory_lot_line ill2
INNER JOIN inventory_lot il2 ON il2.id = ill2.inventoryLotId
INNER JOIN item_uom su2 ON su2.id = ill2.stockItemUomId
WHERE il2.itemId = item.id
AND su2.uomId = uom.id
AND $lotExistsFilter
)
""".trimIndent()
}

private fun mapItemSearchRow(row: Map<String, Any>): InventoryInfoDto {
val available = toBigDecimal(row["availableQty"])
return InventoryInfoDto(
id = toLong(row["id"]),
itemId = toLong(row["itemId"]),
itemCode = row["itemCode"]?.toString(),
itemName = row["itemName"]?.toString(),
itemType = row["itemType"]?.toString(),
onHandQty = toBigDecimal(row["onHandQty"]),
onHoldQty = toBigDecimal(row["onHoldQty"]),
unavailableQty = toBigDecimal(row["unavailableQty"]),
availableQty = available,
stockUomId = toLong(row["stockUomId"]),
uomCode = row["uomCode"]?.toString(),
uomUdfudesc = row["uomUdfudesc"]?.toString(),
uomShortDesc = row["uomShortDesc"]?.toString(),
qtyPerSmallestUnit = available,
baseUom = row["baseUom"]?.toString(),
price = toBigDecimal(row["price"]),
currencyName = row["currencyName"]?.toString(),
status = row["status"]?.toString(),
latestMarketUnitPrice = (row["latestMarketUnitPrice"] as? Number)?.toDouble(),
latestMupUpdatedDate = toLocalDateTime(row["latestMupUpdatedDate"]),
)
}

private fun mapLocationSearchRow(row: Map<String, Any>): InventoryStockUomSearchRowDto {
val available = toBigDecimal(row["availableQty"])
return InventoryStockUomSearchRowDto(
id = toLong(row["id"]),
itemId = toLong(row["itemId"]),
itemCode = row["itemCode"]?.toString(),
itemName = row["itemName"]?.toString(),
itemType = row["itemType"]?.toString(),
onHandQty = toBigDecimal(row["onHandQty"]),
onHoldQty = toBigDecimal(row["onHoldQty"]),
unavailableQty = BigDecimal.ZERO,
availableQty = available,
uomId = toLong(row["uomId"]),
uomCode = row["uomCode"]?.toString(),
uomUdfudesc = row["uomUdfudesc"]?.toString(),
uomShortDesc = row["uomShortDesc"]?.toString(),
qtyPerSmallestUnit = available,
latestMarketUnitPrice = (row["latestMarketUnitPrice"] as? Number)?.toDouble(),
latestMupUpdatedDate = toLocalDateTime(row["latestMupUpdatedDate"]),
)
}

private fun toLong(value: Any?): Long? = (value as? Number)?.toLong()

private fun toBigDecimal(value: Any?): BigDecimal? = when (value) {
null -> null
is BigDecimal -> value
is Number -> BigDecimal(value.toString())
else -> null
}

private fun toLocalDateTime(value: Any?): LocalDateTime? = when (value) {
is LocalDateTime -> value
is Timestamp -> value.toLocalDateTime()
is java.sql.Date -> value.toLocalDate().atStartOfDay()
else -> null
}

open fun allInventoriesByItems(items: List<Items>): List<InventoryInfo>{
return inventoryRepository.findInventoryInfoByItemInAndDeletedIsFalse(items);
}


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

@@ -25,7 +25,7 @@ open class StockAdjustmentService(
private val stockAdjustmentRecordRepository: StockAdjustmentRecordRepository
) {

/** FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.7 | 2026-09-08 */
/** FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.8 | 2026-09-16 */
@Transactional
open fun submit(request: StockAdjustmentRequest): MessageResponse {
val originalById = request.originalLines.filter { it.id > 0 }.associateBy { it.id }
@@ -55,7 +55,8 @@ open class StockAdjustmentService(
productLotNo = current.productlotNo?.takeIf { it.isNotBlank() },
dnNo = current.dnNo?.takeIf { it.isNotBlank() },
type = stockType,
warehouseId = current.warehouseId
warehouseId = current.warehouseId,
uomId = current.uomId,
)
)
saveAdjustmentRecordForStockIn(stockInLine, current.remarks)


+ 16
- 6
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt 파일 보기

@@ -28,6 +28,7 @@ import com.ffii.core.utils.PdfUtils;
import com.ffii.core.utils.ZebraPrinterUtil
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.master.entity.ItemUom
import com.ffii.fpsms.modules.master.entity.ItemUomRespository
import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.master.service.ItemUomService
@@ -1496,12 +1497,7 @@ open class StockInLineService(
savedStockInLine.inventoryLot = savedInventoryLot

// Step 4: Create a row in inventory_lot_line table
val stockItemUom = itemUomRepository.findFirstByItemIdAndStockUnitIsTrueAndDeletedIsFalseOrderByIdAsc(
request.itemId
) ?: throw IllegalArgumentException(
"Stock UOM not found for item: id=${request.itemId}, itemNo=${request.itemNo}. " +
"Ensure at least one item_uom row has stockUnit=1, deleted=0 for this item."
)
val stockItemUom = resolveItemUomForNewLot(request.itemId, request.itemNo, request.uomId)

val warehouse = if (request.warehouseId != null) {
warehouseRepository.findById(request.warehouseId).orElseThrow()
@@ -1531,6 +1527,20 @@ open class StockInLineService(
return savedStockInLine
}

private fun resolveItemUomForNewLot(itemId: Long, itemNo: String, uomId: Long?): ItemUom {
if (uomId != null) {
return itemUomRepository.findFirstByItemIdAndUomIdAndDeletedIsFalse(itemId, uomId)
?: throw IllegalArgumentException(
"Item UOM not found for item: id=$itemId, itemNo=$itemNo, uomId=$uomId."
)
}
return itemUomRepository.findFirstByItemIdAndStockUnitIsTrueAndDeletedIsFalseOrderByIdAsc(itemId)
?: throw IllegalArgumentException(
"Stock UOM not found for item: id=$itemId, itemNo=$itemNo. " +
"Ensure at least one item_uom row has stockUnit=1, deleted=0 for this item."
)
}

@Transactional
open fun createStockInForExistingInventoryLotLine(
request: StockInRequest,


+ 3
- 3
src/main/java/com/ffii/fpsms/modules/stock/web/InventoryController.kt 파일 보기

@@ -17,15 +17,15 @@ class InventoryController(
return inventoryService.allInventories()
}

/** Full inventory search: one row per (itemId, stockUomId) bucket. */
/** Full inventory search: one row per (itemId, stockUomId) bucket, plus current stockUnit when missing. */
@GetMapping("/getRecordByPage")
fun allInventoriesByPage(@ModelAttribute request: SearchInventoryRequest): RecordsRes<InventoryInfo> {
return inventoryService.allInventoriesByPage(request)
}

/**
* Location Search: one row per item + lot-line stock UoM, with optional location filters.
* Item Search uses GET /inventory/getRecordByPage (inventory buckets).
* Location Search: one row per item + lot-line stock UoM, plus current master stockUnit when missing.
* Item Search uses GET /inventory/getRecordByPage (inventory buckets + current stockUnit).
*/
@GetMapping("/searchLatest/getRecordByPage")
fun searchInventoriesByLatestInventory(


+ 1
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/StockAdjustmentRequest.kt 파일 보기

@@ -15,6 +15,7 @@ data class StockAdjustmentLineRequest(
val expiryDate: String,
val warehouseId: Long,
val uom: String? = null,
val uomId: Long? = null,
val remarks: String? = null
)



+ 2
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/model/StockInRequest.kt 파일 보기

@@ -13,5 +13,6 @@ data class StockInRequest(
val productLotNo: String? = null,
val dnNo: String? = null,
val type: String? = null,
val warehouseId: Long? = null
val warehouseId: Long? = null,
val uomId: Long? = null,
)

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