Browse Source

update stock take import function, added column to items table, please reload backend

master
kelvin.yau 1 week ago
parent
commit
74d089a767
7 changed files with 330 additions and 83 deletions
  1. +3
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt
  2. +16
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt
  3. +1
    -1
      src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
  4. +291
    -82
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  5. +11
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt
  6. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt
  7. +5
    -0
      src/main/resources/db/changelog/changes/20251203_01_KelvinY/01_add_company_to_items.sql

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt View File

@@ -58,6 +58,9 @@ open class Items : BaseEntity<Long>() {
@Column(name = "LocationCode", nullable = true, length = 255)
open var LocationCode: String? = null

@Column(name = "company", nullable = true, length = 30)
open var company: String? = null

@Column(name = "maxQty")
open var maxQty: Double? = null



+ 16
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt View File

@@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.master.entity

import com.ffii.core.support.AbstractRepository
import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import java.io.Serializable

@@ -12,4 +13,19 @@ interface WarehouseRepository : AbstractRepository<Warehouse, Long> {
fun findByIdAndDeletedIsFalse(id: Serializable): Warehouse?;

fun findByCodeAndDeletedIsFalse(code: String): Warehouse?;

@Query("""
SELECT w FROM Warehouse w
WHERE w.code = :code
AND w.stockTakeTable = :stockTakeTable
AND w.company = :company
AND w.storeLocation = :storeLocation
AND w.deleted = false
""")
fun findByCodeAndStockTakeTableAndCompanyAndStoreLocationAndDeletedIsFalse(
code: String,
stockTakeTable: String,
company: String,
storeLocation: String
): Warehouse?
}

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt View File

@@ -513,7 +513,7 @@ open class BomService(
val resolver = PathMatchingResourcePatternResolver()
// val excels = resolver.getResources("bomImport/*.xlsx")
//val excels = resolver.getResources("file:C:/Users/Kelvin YAU/Downloads/bom/*.xlsx")
val excels = resolver.getResources("file:C:/Users/kw093/Downloads/bom/*.xlsx")
val excels = resolver.getResources("file:C:/Users/Kelvin YAU/Downloads/bom/*.xlsx")
// val excels = resolver.getResources("file:C:/Users/2Fi/Desktop/Third Wave of BOM Excel/*.xlsx")
println("size: ${excels.size}")
val logExcel = ClassPathResource("excelTemplate/bom_excel_issue_log.xlsx")


+ 291
- 82
src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt View File

@@ -142,7 +142,7 @@ open class ItemsService(
}

fun writeInventoryCreated(rowNum: Int, stockTakeCount: BigDecimal) {
logWriter.println(" INVENTORY: CREATED - onHandQty: $stockTakeCount, onHoldQty: 0")
logWriter.println(" INVENTORY: CREATED - onHandQty: $stockTakeCount")
}

fun writeInventoryError(rowNum: Int, message: String) {
@@ -160,6 +160,7 @@ open class ItemsService(
fun writeSummary(
successCount: Int,
errorCount: Int,
duplicateSkippedCount: Int,
inventoryCreatedCount: Int,
inventoryFailedCount: Int,
missingInventoryItems: List<String>,
@@ -173,6 +174,7 @@ open class ItemsService(
logWriter.println()
logWriter.println("Items Patched: $successCount")
logWriter.println("Items with Errors: $errorCount")
logWriter.println("Duplicate Items Skipped: $duplicateSkippedCount")
logWriter.println()
logWriter.println("Inventory Records Created: $inventoryCreatedCount")
logWriter.println("Inventory Records Failed: $inventoryFailedCount")
@@ -606,8 +608,9 @@ open class ItemsService(
val sheet: Sheet = workbook.getSheetAt(0)

// Column indices (0-indexed)
val COLUMN_INVENTORY_SHEET_INDEX = 4 // Column E
val COLUMN_CODE_INDEX = 7 // Column H
val COLUMN_COMPANY_INDEX = 1 // Column B
val COLUMN_INVENTORY_SHEET_INDEX = 4 // Column E
val COLUMN_CODE_INDEX = 7 // Column H
val COLUMN_TYPE_INDEX = 9 // Column J
val COLUMN_STORE_ID_INDEX = 13 // Column N
val COLUMN_STORE_LOCATION_INDEX = 14 // Column O
@@ -617,12 +620,13 @@ open class ItemsService(
val COLUMN_MTMS_PICK_ROUTING_ID_INDEX = 18 // Column S
val COLUMN_ON_HOLD_QTY_INDEX = 21 // Column V

val START_ROW_INDEX = 4 // Starting from row 4 (0-indexed)
val START_ROW_INDEX = 3 // Starting from row 4 (0-indexed)

var successCount = 0
var errorCount = 0
var inventoryCreatedCount = 0
var inventoryFailedCount = 0
var duplicateSkippedCount = 0
val errors = mutableListOf<String>()
val missingInventoryItems = mutableListOf<String>()

@@ -664,28 +668,12 @@ open class ItemsService(
continue
}

// Get item code - Debug
// Get item code
val itemCode = try {
val cell = row.getCell(COLUMN_CODE_INDEX)

logger.info("=== DEBUG Row ${i + 1} ===")
logger.info("Reading from Column H (index ${COLUMN_CODE_INDEX})")

if (cell == null) {
logger.warn("Row ${i + 1}: Cell is NULL")
} else {
logger.info("Row ${i + 1}: Cell type = ${cell.cellType}")
val cellI = row.getCell(COLUMN_CODE_INDEX + 1)
logger.info("Column I (index ${COLUMN_CODE_INDEX + 1}) = '${getCellStringValue(cellI)}'")
}

val extractedCode = getCellStringValue(cell)?.trim()
logger.info("Extracted code = '$extractedCode'")
logger.info("=== END DEBUG Row ${i + 1} ===")

extractedCode
getCellStringValue(cell)?.trim()
} catch (e: Exception) {
logger.error("Import Error (Row ${i + 1} - Code Error): ${e.message}")
logger.error("Row ${i + 1}: Failed to read code - ${e.message}")
logWriter.writeRowError(i, "Failed to read code - ${e.message}")
errorCount++
errors.add("Row ${i + 1}: Failed to read code - ${e.message}")
@@ -719,16 +707,18 @@ open class ItemsService(

if (processedItemIds.containsKey(item.id!!)) {
val previousRow = processedItemIds[item.id!!]!! + 1
logger.warn("Row ${i + 1}: Item '$itemCode' was already processed at row $previousRow - Creating duplicate StockTakeLine")
logWriter.writeRowError(i, "WARNING: Item '$itemCode' appears multiple times (first at row $previousRow)")
duplicateSkippedCount++
logger.warn("Row ${i + 1}: Item '$itemCode' was already processed at row $previousRow - Skipping duplicate")
logWriter.writeRowSkipped(i, "Item '$itemCode' already processed at row $previousRow - Skipped")
continue // Skip to next item
}
processedItemIds[item.id!!] = i // Track this item
processedItemIds[item.id!!] = i

// Create StockTakeLine (for each row/item)
// Read quantity from Column V (same as inventory)
val qty = try {
val cell = row.getCell(COLUMN_ON_HOLD_QTY_INDEX)
when {
val extractedValue = when {
cell == null -> BigDecimal.ZERO
cell.cellType == CellType.NUMERIC -> cell.numericCellValue.toBigDecimal()
cell.cellType == CellType.STRING -> {
@@ -738,6 +728,8 @@ open class ItemsService(
}
else -> BigDecimal.ZERO
}
// Add 500 to the extracted value
extractedValue + BigDecimal(500)
} catch (e: Exception) {
logger.warn("Row ${i + 1}: Failed to read quantity from column V - ${e.message}")
BigDecimal.ZERO
@@ -767,18 +759,149 @@ open class ItemsService(
}
logger.info("Row ${i + 1}: Created StockTakeLine (ID: ${savedStockTakeLine.id}) for item '$itemCode'")

try {
// Read Company (Column B)
val company = getCellStringValue(row.getCell(COLUMN_COMPANY_INDEX))?.trim()
if (!company.isNullOrBlank()) {
item.company = company
logger.info("Row ${i + 1}: Company read from Column B: '$company'")
}

// Read InventorySheet (Column E)
val inventorySheet = getCellStringValue(row.getCell(COLUMN_INVENTORY_SHEET_INDEX))?.trim()
if (!inventorySheet.isNullOrBlank()) {
item.inventorySheet = inventorySheet
}

// Read Type (Column J)
val type = getCellStringValue(row.getCell(COLUMN_TYPE_INDEX))?.trim()
if (!type.isNullOrBlank()) {
item.type = type
}

// Read Store ID (Column N) - String type
val storeId = getCellStringValue(row.getCell(COLUMN_STORE_ID_INDEX))?.trim()
if (!storeId.isNullOrBlank()) {
item.store_id = storeId
}

// Read Store Location (Column O)
val storeLocation = getCellStringValue(row.getCell(COLUMN_STORE_LOCATION_INDEX))?.trim()
if (!storeLocation.isNullOrBlank()) {
item.storeLocation = storeLocation
}

// Read Warehouse (Column P)
val warehouseFromExcel = getCellStringValue(row.getCell(COLUMN_WAREHOUSE_INDEX))?.trim()
if (!warehouseFromExcel.isNullOrBlank()) {
item.warehouse = warehouseFromExcel
}

// Read Area (Column Q)
val area = getCellStringValue(row.getCell(COLUMN_AREA_INDEX))?.trim()
if (!area.isNullOrBlank()) {
item.area = area
}

// Read Slot (Column R)
val slot = getCellStringValue(row.getCell(COLUMN_SLOT_INDEX))?.trim()
if (!slot.isNullOrBlank()) {
item.slot = slot
}

// Combine Column N (Store ID), Column P (Warehouse), Column Q (Area), and Column R (Slot) to form LocationCode
// Format: "N-P-Q-R" (e.g., "ST01-WH01-A01-S01")
val locationCodeParts = mutableListOf<String>()
if (!storeId.isNullOrBlank()) {
locationCodeParts.add(storeId)
}
if (!warehouseFromExcel.isNullOrBlank()) {
locationCodeParts.add(warehouseFromExcel)
}
if (!area.isNullOrBlank()) {
locationCodeParts.add(area)
}
if (!slot.isNullOrBlank()) {
locationCodeParts.add(slot)
}

val locationCodeFromExcel = if (locationCodeParts.isNotEmpty()) {
locationCodeParts.joinToString("-")
} else {
null
}

if (!locationCodeFromExcel.isNullOrBlank()) {
item.LocationCode = locationCodeFromExcel
logger.info("Row ${i + 1}: LocationCode created: '$locationCodeFromExcel' (Format: N-P-Q-R, N='$storeId', P='$warehouseFromExcel', Q='$area', R='$slot')")
} else {
logger.warn("Row ${i + 1}: LocationCode cannot be created - Column N='$storeId', P='$warehouseFromExcel', Q='$area', R='$slot' (all empty or null)")
}
// Read MTMS Pick Routing ID (Column S) - Int type
val mtmsPickRoutingId = try {
val cell = row.getCell(COLUMN_MTMS_PICK_ROUTING_ID_INDEX)
when {
cell == null -> null
cell.cellType == CellType.NUMERIC -> cell.numericCellValue.toInt()
cell.cellType == CellType.STRING -> {
val strValue = cell.stringCellValue.trim()
if (strValue.isNotBlank()) strValue.toIntOrNull() else null
}
else -> null
}
} catch (e: Exception) {
logger.warn("Row ${i + 1}: Failed to read MTMS Pick Routing ID from column S - ${e.message}")
null
}
if (mtmsPickRoutingId != null) {
item.MTMSPickRoutingID = mtmsPickRoutingId
}

} catch (e: Exception) {
logger.error("Row ${i + 1}: Failed to read Excel columns for item '$itemCode' - ${e.message}")
logWriter.writeRowError(i, "Failed to read Excel columns - ${e.message}")
// Continue processing - don't fail the entire row
}


//Create StockInLine (for each row/item)
// Get warehouse from item's LocationCode
// Get warehouse from item's LocationCode, inventorySheet, company, and storeLocation
// Match: item.LocationCode -> warehouse.code AND item.inventorySheet -> warehouse.stockTakeTable AND item.company -> warehouse.company AND item.storeLocation -> warehouse.storeLocation
val locationCode = item.LocationCode
val warehouse = if (locationCode != null) {
val inventorySheet = item.inventorySheet
val company = item.company
val storeLocation = item.storeLocation
logger.info("Row ${i + 1}: Looking up warehouse with LocationCode: '$locationCode', inventorySheet: '$inventorySheet', company: '$company', storeLocation: '$storeLocation'")

val warehouse = if (locationCode != null && locationCode.isNotBlank() &&
inventorySheet != null && inventorySheet.isNotBlank() &&
company != null && company.isNotBlank() &&
storeLocation != null && storeLocation.isNotBlank()) {
try {
warehouseService.findByCode(locationCode)
val foundWarehouse = warehouseService.findByCodeAndStockTakeTableAndCompanyAndStoreLocation(locationCode, inventorySheet, company, storeLocation)
if (foundWarehouse != null) {
logger.info("Row ${i + 1}: Found warehouse: code='${foundWarehouse.code}', stockTakeTable='${foundWarehouse.stockTakeTable}', company='${foundWarehouse.company}', storeLocation='${foundWarehouse.storeLocation}', id=${foundWarehouse.id}")
} else {
logger.warn("Row ${i + 1}: Warehouse not found in database for LocationCode: '$locationCode', inventorySheet: '$inventorySheet', company: '$company', storeLocation: '$storeLocation'")
}
foundWarehouse
} catch (e: Exception) {
logger.warn("Row ${i + 1}: Failed to find warehouse with LocationCode '$locationCode' - ${e.message}")
logger.warn("Row ${i + 1}: Failed to find warehouse with LocationCode '$locationCode', inventorySheet '$inventorySheet', company '$company', storeLocation '$storeLocation' - ${e.message}")
null
}
} else {
logger.warn("Row ${i + 1}: Item '$itemCode' has no LocationCode, warehouse will be null")
if (locationCode.isNullOrBlank()) {
logger.warn("Row ${i + 1}: Item '$itemCode' has no LocationCode (null or blank), warehouse will be null")
}
if (inventorySheet.isNullOrBlank()) {
logger.warn("Row ${i + 1}: Item '$itemCode' has no inventorySheet (null or blank), warehouse will be null")
}
if (company.isNullOrBlank()) {
logger.warn("Row ${i + 1}: Item '$itemCode' has no company (null or blank), warehouse will be null")
}
if (storeLocation.isNullOrBlank()) {
logger.warn("Row ${i + 1}: Item '$itemCode' has no storeLocation (null or blank), warehouse will be null")
}
null
}

@@ -841,62 +964,139 @@ open class ItemsService(
null
}

// Update StockInLine to RECEIVED status (this will trigger InventoryLot & InventoryLotLine creation)
// Step 7a: First update to PENDING with qcAccept=true to create InventoryLot
// This will create InventoryLot and automatically set status to RECEIVED
saveStockInLineReq.apply {
id = savedStockInLine.id
status = StockInLineStatus.RECEIVED.status
status = StockInLineStatus.PENDING.status
this.dnNo = dnNo
this.inventoryLotLines = inventoryLotLines
this.qcAccept = true
this.acceptedQty = qty
this.acceptQty = qty
// Don't set inventoryLotLines yet
}
val finalStockInLine = try {
val updatedForLot = try {
stockInLineService.update(saveStockInLineReq)
} catch (e: Exception) {
logger.error("Row ${i + 1}: Failed to update StockInLine to RECEIVED for item '$itemCode' - ${e.message}")
logWriter.writeRowError(i, "Failed to update StockInLine to RECEIVED - ${e.message}")
logger.error("Row ${i + 1}: Failed to update StockInLine to PENDING (for InventoryLot) for item '$itemCode' - ${e.message}")
logWriter.writeRowError(i, "Failed to update StockInLine to PENDING - ${e.message}")
errorCount++
errors.add("Row ${i + 1}: Failed to update StockInLine to RECEIVED - ${e.message}")
errors.add("Row ${i + 1}: Failed to update StockInLine to PENDING - ${e.message}")
continue
}
logger.info("Row ${i + 1}: Updated StockInLine to RECEIVED (ID: ${finalStockInLine.id}) for item '$itemCode' with lotNo: $lotNo, dnNo: $dnNo")

val inventoryLotLine = if (finalStockInLine.id != null && warehouse?.id != null) {
try {
// Check if update was successful
if (updatedForLot?.id == null) {
logger.error("Row ${i + 1}: StockInLine update returned null ID for item '$itemCode'")
logWriter.writeRowError(i, "StockInLine update returned null")
errorCount++
errors.add("Row ${i + 1}: StockInLine update returned null")
continue
}

logger.info("Row ${i + 1}: Updated StockInLine to PENDING (ID: ${updatedForLot.id}) - InventoryLot should be created")

// Refresh StockInLine entity to get the InventoryLot that was just created
val refreshedStockInLine = stockInLineRepository.findById(updatedForLot.id!!).orElse(null)
if (refreshedStockInLine?.inventoryLot == null) {
logger.warn("Row ${i + 1}: InventoryLot was not created for StockInLine ${updatedForLot.id}")
logWriter.writeRowError(i, "InventoryLot was not created")
} else {
logger.info("Row ${i + 1}: InventoryLot created (ID: ${refreshedStockInLine.inventoryLot?.id}, lotNo: ${refreshedStockInLine.inventoryLot?.lotNo})")
}

// Step 7b: Second update to RECEIVED with inventoryLotLines to create InventoryLotLine
// Only proceed if warehouse is available (InventoryLotLine requires warehouse)
if (warehouse?.id == null) {
logger.warn("Row ${i + 1}: Warehouse is null (no LocationCode), skipping InventoryLotLine creation for item '$itemCode'")
logWriter.writeRowError(i, "Warehouse is null - InventoryLotLine will not be created")
// Continue without InventoryLotLine - StockTakeLine will not be updated with inventoryLotLineId
} else {
// Warehouse is available, proceed with InventoryLotLine creation
saveStockInLineReq.apply {
id = savedStockInLine.id
status = StockInLineStatus.RECEIVED.status // Explicitly set to RECEIVED
this.dnNo = dnNo
this.acceptedQty = qty
this.acceptQty = qty
this.inventoryLotLines = inventoryLotLines // REQUIRED for InventoryLotLine creation
}
val finalStockInLine = try {
stockInLineService.update(saveStockInLineReq)
} catch (e: Exception) {
logger.error("Row ${i + 1}: Failed to update StockInLine to RECEIVED (for InventoryLotLine) for item '$itemCode' - ${e.message}")
logWriter.writeRowError(i, "Failed to update StockInLine to RECEIVED - ${e.message}")
errorCount++
errors.add("Row ${i + 1}: Failed to update StockInLine to RECEIVED - ${e.message}")
continue
}

if (finalStockInLine?.id == null) {
logger.error("Row ${i + 1}: StockInLine update to RECEIVED returned null ID for item '$itemCode'")
logWriter.writeRowError(i, "StockInLine update to RECEIVED returned null")
errorCount++
errors.add("Row ${i + 1}: StockInLine update to RECEIVED returned null")
continue
}

logger.info("Row ${i + 1}: Updated StockInLine to RECEIVED (ID: ${finalStockInLine.id}) for item '$itemCode' with lotNo: $lotNo, dnNo: $dnNo")


// Step 8: Find InventoryLotLine and update StockTakeLine
// Flush first to ensure InventoryLotLine is persisted
stockInLineRepository.flush()

// Verify InventoryLotLine was created by querying directly
val inventoryLotLine = try {
inventoryLotLineRepository.findByInventoryLotStockInLineIdAndWarehouseId(
inventoryLotStockInLineId = finalStockInLine.id!!,
warehouseId = warehouse.id!!
)
} catch (e: Exception) {
logger.warn("Row ${i + 1}: Failed to find InventoryLotLine for StockInLine ${finalStockInLine.id} and warehouse ${warehouse.id} - ${e.message}")
null
logger.warn("Row ${i + 1}: Failed to find InventoryLotLine - ${e.message}")
// Try alternative: find by InventoryLot
try {
val refreshedStockInLine = stockInLineRepository.findById(finalStockInLine.id!!).orElse(null)
refreshedStockInLine?.inventoryLot?.id?.let { inventoryLotId ->
inventoryLotLineRepository.findAllByInventoryLotId(inventoryLotId)
.firstOrNull { it.warehouse?.id == warehouse.id }
}
} catch (e2: Exception) {
logger.warn("Row ${i + 1}: Alternative search failed - ${e2.message}")
null
}
}
} else {
logger.warn("Row ${i + 1}: Cannot find InventoryLotLine - finalStockInLine.id: ${finalStockInLine.id}, warehouse.id: ${warehouse?.id}")
null
}

if (inventoryLotLine != null) {
val updateStockTakeLineReq = SaveStockTakeLineRequest(
id = savedStockTakeLine.id,
stockTakeId = savedStockTake.id,
initialQty = savedStockTakeLine.initialQty,
finalQty = savedStockTakeLine.finalQty,
uomId = savedStockTakeLine.uom?.id,
status = StockTakeLineStatus.COMPLETED.value,
completeDate = LocalDateTime.now(),
inventoryLotLineId = inventoryLotLine.id,
remarks = savedStockTakeLine.remarks
)
try {
stockTakeLineService.saveStockTakeLine(updateStockTakeLineReq)
logger.info("Row ${i + 1}: Updated StockTakeLine (ID: ${savedStockTakeLine.id}) to COMPLETED with inventoryLotLineId: ${inventoryLotLine.id}")
} catch (e: Exception) {
logger.error("Row ${i + 1}: Failed to update StockTakeLine for item '$itemCode' - ${e.message}")
logWriter.writeRowError(i, "Failed to update StockTakeLine - ${e.message}")
// Don't continue here - this is not critical, just log the error
if (inventoryLotLine != null) {
logger.info("Row ${i + 1}: InventoryLotLine found (ID: ${inventoryLotLine.id}, qty: ${inventoryLotLine.inQty})")
} else {
logger.warn("Row ${i + 1}: InventoryLotLine not found for StockInLine ${finalStockInLine.id}")
logWriter.writeRowError(i, "InventoryLotLine not found")
}

if (inventoryLotLine != null) {
val updateStockTakeLineReq = SaveStockTakeLineRequest(
id = savedStockTakeLine.id,
stockTakeId = savedStockTake.id,
initialQty = savedStockTakeLine.initialQty,
finalQty = savedStockTakeLine.finalQty,
uomId = savedStockTakeLine.uom?.id,
status = StockTakeLineStatus.COMPLETED.value,
completeDate = LocalDateTime.now(),
inventoryLotLineId = inventoryLotLine.id,
remarks = savedStockTakeLine.remarks
)
try {
stockTakeLineService.saveStockTakeLine(updateStockTakeLineReq)
logger.info("Row ${i + 1}: Updated StockTakeLine (ID: ${savedStockTakeLine.id}) to COMPLETED with inventoryLotLineId: ${inventoryLotLine.id}")
} catch (e: Exception) {
logger.error("Row ${i + 1}: Failed to update StockTakeLine for item '$itemCode' - ${e.message}")
logWriter.writeRowError(i, "Failed to update StockTakeLine - ${e.message}")
}
} else {
logger.warn("Row ${i + 1}: InventoryLotLine not found, StockTakeLine will not be updated with inventoryLotLineId")
logWriter.writeRowError(i, "InventoryLotLine not found for StockTakeLine update")
}
} else {
logger.warn("Row ${i + 1}: InventoryLotLine not found, StockTakeLine will not be updated with inventoryLotLineId")
logWriter.writeRowError(i, "InventoryLotLine not found for StockTakeLine update")
}

// Update fields - Replace existing data with Excel data
@@ -936,21 +1136,27 @@ open class ItemsService(
missingInventoryItems.add("Row ${i + 1}: Item '$itemCode' - Item ID is null")
inventoryFailedCount++
} else {
// Check if inventory exists
val existingInventory = try {
inventoryRepository.findByItemId(itemId).orElse(null)
// Check if inventory exists - Find ALL inventory records for this item
val existingInventories = try {
inventoryRepository.findAllByItemIdAndDeletedIsFalse(itemId)
} catch (e: Exception) {
logger.warn("Row ${i + 1}: Failed to find existing inventory for item '$itemCode' (ID: $itemId) - ${e.message}")
null
emptyList()
}

// Delete existing inventory if it exists
if (existingInventory != null) {
// Delete ALL existing inventory records if any exist
if (existingInventories.isNotEmpty()) {
try {
inventoryRepository.delete(existingInventory)
val deletedCount = existingInventories.size
existingInventories.forEach { inventory ->
inventoryRepository.delete(inventory)
}
inventoryRepository.flush()
logger.info("Row ${i + 1}: Deleted existing inventory record for item '${item.name}' (code: $itemCode)")
logWriter.writeInventoryDeleted(i, existingInventory.id)
logger.info("Row ${i + 1}: Deleted $deletedCount existing inventory record(s) for item '${item.name}' (code: $itemCode)")
if (deletedCount > 1) {
logger.warn("Row ${i + 1}: Found $deletedCount duplicate inventory records for item '$itemCode' - all deleted")
}
logWriter.writeInventoryDeleted(i, existingInventories.first().id)
} catch (e: Exception) {
logger.warn("Row ${i + 1}: Failed to delete existing inventory for item '$itemCode' - ${e.message}")
logWriter.writeInventoryDeleteError(i, e.message ?: "Unknown error")
@@ -960,16 +1166,18 @@ open class ItemsService(
// Read quantity from Column V (will be used for onHandQty)
val stockTakeCount = try {
val cell = row.getCell(COLUMN_ON_HOLD_QTY_INDEX)
when {
val extractedValue = when {
cell == null -> BigDecimal.ZERO
cell.cellType == CellType.NUMERIC -> cell.numericCellValue.toBigDecimal()
cell.cellType == CellType.STRING -> {
val strValue = cell.stringCellValue.trim()
if (strValue.isNotBlank()) strValue.toBigDecimalOrNull() ?: BigDecimal.ZERO
else BigDecimal.ZERO
else strValue.toBigDecimalOrNull() ?: BigDecimal.ZERO
}
else -> BigDecimal.ZERO
}
// Add 500 to the extracted value
extractedValue + BigDecimal(500)
} catch (e: Exception) {
logger.warn("Row ${i + 1}: Failed to read quantity from column V - ${e.message}")
BigDecimal.ZERO
@@ -1032,12 +1240,13 @@ open class ItemsService(
errorCount = errorCount,
inventoryCreatedCount = inventoryCreatedCount,
inventoryFailedCount = inventoryFailedCount,
duplicateSkippedCount = duplicateSkippedCount,
missingInventoryItems = missingInventoryItems,
errors = errors
)

logger.info("--------- End - Patch Items from Excel -------")
logger.info("Success: $successCount, Errors: $errorCount")
logger.info("Success: $successCount, Errors: $errorCount, Duplicates Skipped: $duplicateSkippedCount")
logger.info("Inventory Created: $inventoryCreatedCount, Inventory Failed: $inventoryFailedCount")
logger.info("Log file saved to: ${logWriter.getLogFilePath()}")



+ 11
- 0
src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt View File

@@ -342,4 +342,15 @@ open class WarehouseService(
// calculate total sort value: floor * 10000 + letter * 100 + slot
return floorOrder * 10000L + letterOrder * 100L + slot
}

open fun findByCodeAndStockTakeTableAndCompanyAndStoreLocation(
code: String,
stockTakeTable: String,
company: String,
storeLocation: String
): Warehouse? {
return warehouseRepository.findByCodeAndStockTakeTableAndCompanyAndStoreLocationAndDeletedIsFalse(
code, stockTakeTable, company, storeLocation
)
}
}

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt View File

@@ -26,4 +26,7 @@ interface InventoryRepository: AbstractRepository<Inventory, Long> {
fun findInventoryInfoByItemInAndDeletedIsFalse(items: List<Items>): List<InventoryInfo>

fun findByItemId(itemId: Long): Optional<Inventory>

@Query("SELECT i FROM Inventory i WHERE i.item.id = :itemId AND i.deleted = false")
fun findAllByItemIdAndDeletedIsFalse(itemId: Long): List<Inventory>
}

+ 5
- 0
src/main/resources/db/changelog/changes/20251203_01_KelvinY/01_add_company_to_items.sql View File

@@ -0,0 +1,5 @@
-- liquibase formatted sql
-- changeset KelvinY:add_company_to_items

ALTER TABLE `fpsmsdb`.`items`
ADD COLUMN `company` VARCHAR(30) NULL;

Loading…
Cancel
Save