Pārlūkot izejas kodu

update import inventory function for new excel

reset-do-picking-order
kelvin.yau pirms 1 nedēļas
vecāks
revīzija
cf4885d46e
1 mainītis faili ar 97 papildinājumiem un 92 dzēšanām
  1. +97
    -92
      src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt

+ 97
- 92
src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt Parādīt failu

@@ -9,12 +9,14 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.transaction.annotation.Transactional
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.stock.service.StockInLineService
import com.ffii.fpsms.modules.stock.web.model.StockInRequest
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
@@ -32,7 +34,9 @@ open class InventorySetup {
@Autowired
private lateinit var stockInLineService: StockInLineService

private val DEFAULT_WAREHOUSE_CODE = "2F-W201-#A-01"
@Autowired
private lateinit var itemUomService: ItemUomService

private val DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy/MM/dd")

@Transactional(rollbackFor = [Exception::class])
@@ -46,101 +50,98 @@ open class InventorySetup {
val workbook: Workbook = XSSFWorkbook(inputStream)

try {
val sheet: Sheet = workbook.getSheetAt(0)
val inventoryDataList = mutableListOf<InventoryData>()

// Column indices
val COLUMN_ITEM_NO_INDEX = 6 // Column G
val COLUMN_STORE_ID_INDEX = 14 // Column O
val COLUMN_WAREHOUSE_INDEX = 16 // Column Q
val COLUMN_AREA_INDEX = 17 // Column R
val COLUMN_SLOT_INDEX = 18 // Column S
val COLUMN_PRODUCT_LOT_NO_INDEX = 21 // Column V
val COLUMN_QUANTITY_INDEX = 22 // Column W
val COLUMN_EXPIRY_DATE_INDEX = 26 // Column AA
// New template: B=store_id, C=warehouse, D=area, E=slot, F=item code, J=productionLotNo, K=quantity (sales unit), O=expiry date
val COLUMN_STORE_ID_INDEX = 1 // B
val COLUMN_WAREHOUSE_INDEX = 2 // C
val COLUMN_AREA_INDEX = 3 // D
val COLUMN_SLOT_INDEX = 4 // E
val COLUMN_ITEM_CODE_INDEX = 5 // F
val COLUMN_PRODUCT_LOT_NO_INDEX = 9 // J
val COLUMN_QUANTITY_INDEX = 10 // K (sales unit)
val COLUMN_EXPIRY_DATE_INDEX = 14 // O

val START_ROW_INDEX = 2 // Start from Excel row 3 (0-based: index 2)
val START_ROW_INDEX = 6 // Start from Excel row 7 (0-based: index 6)

// ────────────────────────────────────────────────
// Process data rows (starting from Excel row 3, index 2)
// Process all sheets, data rows from row 7
// ────────────────────────────────────────────────
for (rowIndex in START_ROW_INDEX..sheet.lastRowNum) {
val row = sheet.getRow(rowIndex) ?: continue
for (sheetIndex in 0 until workbook.numberOfSheets) {
val sheet: Sheet = workbook.getSheetAt(sheetIndex)
val sheetName = sheet.sheetName
for (rowIndex in START_ROW_INDEX..sheet.lastRowNum) {
val row = sheet.getRow(rowIndex) ?: continue

val itemNo = ExcelUtils.getStringValue(row.getCell(COLUMN_ITEM_NO_INDEX))?.trim() ?: ""
val itemNo = ExcelUtils.getStringValue(row.getCell(COLUMN_ITEM_CODE_INDEX))?.trim() ?: ""

// Skip if itemNo is empty (required field)
if (itemNo.isEmpty()) {
println("SKIP row ${rowIndex + 1} - Missing itemNo")
continue
}
if (itemNo.isEmpty()) {
println("SKIP sheet '$sheetName' row ${rowIndex + 1} - Missing item code")
continue
}

// Read warehouse location fields
val store_id = ExcelUtils.getStringValue(row.getCell(COLUMN_STORE_ID_INDEX))?.trim() ?: ""
val warehouse = ExcelUtils.getStringValue(row.getCell(COLUMN_WAREHOUSE_INDEX))?.trim() ?: ""
val area = ExcelUtils.getStringValue(row.getCell(COLUMN_AREA_INDEX))?.trim() ?: ""
val slot = ExcelUtils.getStringValue(row.getCell(COLUMN_SLOT_INDEX))?.trim() ?: ""

// Generate LocationCode: store_id-warehouse-area-slot
val locationCode = if (store_id.isNotEmpty() && warehouse.isNotEmpty() &&
area.isNotEmpty() && slot.isNotEmpty()) {
"$store_id-$warehouse-$area-$slot"
} else {
""
}
val store_id = ExcelUtils.getStringValue(row.getCell(COLUMN_STORE_ID_INDEX))?.trim() ?: ""
val warehouse = ExcelUtils.getStringValue(row.getCell(COLUMN_WAREHOUSE_INDEX))?.trim() ?: ""
val area = ExcelUtils.getStringValue(row.getCell(COLUMN_AREA_INDEX))?.trim() ?: ""
val slot = ExcelUtils.getStringValue(row.getCell(COLUMN_SLOT_INDEX))?.trim() ?: ""

val quantityStr = ExcelUtils.getStringValue(row.getCell(COLUMN_QUANTITY_INDEX))?.trim()
val quantity = quantityStr?.toBigDecimalOrNull()
val locationCode = if (store_id.isNotEmpty() && warehouse.isNotEmpty() &&
area.isNotEmpty() && slot.isNotEmpty()) {
"$store_id-$warehouse-$area-$slot"
} else {
""
}

if (quantity == null || quantity <= BigDecimal.ZERO) {
println("SKIP row ${rowIndex + 1} - Invalid quantity: $quantityStr")
continue
}
if (locationCode.isEmpty()) {
println("SKIP sheet '$sheetName' row ${rowIndex + 1} - No location (store_id/warehouse/area/slot required)")
continue
}

val productLotNo = ExcelUtils.getStringValue(row.getCell(COLUMN_PRODUCT_LOT_NO_INDEX))?.trim()
val quantityStr = ExcelUtils.getStringValue(row.getCell(COLUMN_QUANTITY_INDEX))?.trim()
val salesQuantity = quantityStr?.toBigDecimalOrNull()

// Parse expiry date with fallback to default (1 month later)
val expiryDateStr = ExcelUtils.getStringValue(row.getCell(COLUMN_EXPIRY_DATE_INDEX))?.trim()
val expiryDate = expiryDateStr?.let {
try {
LocalDate.parse(it, DATE_FORMAT)
} catch (e: Exception) {
println("WARNING row ${rowIndex + 1} - Invalid expiry date format: $it (expected YYYY/MM/DD), using default: 1 month later")
null
if (salesQuantity == null || salesQuantity <= BigDecimal.ZERO) {
println("SKIP sheet '$sheetName' row ${rowIndex + 1} - Invalid or zero quantity: $quantityStr")
continue
}
} ?: run {
// If expiryDateStr is null or empty, or parsing failed, use default
LocalDate.now().plusMonths(1)
}

// If parsing failed, use default
val finalExpiryDate = expiryDate ?: LocalDate.now().plusMonths(1)

println("=== Processing row ${rowIndex + 1} (itemNo: $itemNo, qty: $quantity, expiryDate: $finalExpiryDate, locationCode: ${locationCode.ifEmpty { "DEFAULT" }}) ===")
val productLotNo = ExcelUtils.getStringValue(row.getCell(COLUMN_PRODUCT_LOT_NO_INDEX))?.trim()

inventoryDataList.add(
InventoryData(
itemNo = itemNo,
locationCode = locationCode,
quantity = quantity,
productLotNo = productLotNo,
expiryDate = finalExpiryDate
val expiryDateStr = ExcelUtils.getStringValue(row.getCell(COLUMN_EXPIRY_DATE_INDEX))?.trim()
val expiryDate = expiryDateStr?.let {
try {
LocalDate.parse(it, DATE_FORMAT)
} catch (e: Exception) {
println("WARNING sheet '$sheetName' row ${rowIndex + 1} - Invalid expiry date format: $it (expected yyyy/MM/dd), using default: 1 month later")
null
}
} ?: LocalDate.now().plusMonths(1)

val finalExpiryDate = expiryDate ?: LocalDate.now().plusMonths(1)

inventoryDataList.add(
InventoryData(
itemNo = itemNo,
locationCode = locationCode,
quantitySalesUnit = salesQuantity,
productLotNo = productLotNo,
expiryDate = finalExpiryDate
)
)
)
}
}

println("Total valid rows collected: ${inventoryDataList.size}")

// ────────────────────────────────────────────────
// Create stock in for each item
// Create stock in (convert sales qty -> stock qty per item)
// ────────────────────────────────────────────────
var createdCount = 0
var notFoundCount = 0
var warehouseNotFoundCount = 0
var warehouseSkippedCount = 0

for (data in inventoryDataList) {
try {
// Find item by code (itemNo)
val item = itemsRepository.findFirstByCodeAndDeletedFalse(data.itemNo)

if (item == null) {
@@ -154,23 +155,11 @@ open class InventorySetup {
continue
}

// Find warehouse by LocationCode, fallback to default if not found
var warehouse = if (data.locationCode.isNotEmpty()) {
warehouseRepository.findByCodeAndDeletedIsFalse(data.locationCode)
} else {
null
}

// If warehouse not found, use default warehouse
val warehouse = warehouseRepository.findByCodeAndDeletedIsFalse(data.locationCode)
if (warehouse == null) {
println("WAREHOUSE NOT FOUND - Warehouse with code '${data.locationCode}' does not exist, using default: $DEFAULT_WAREHOUSE_CODE")
warehouse = warehouseRepository.findByCodeAndDeletedIsFalse(DEFAULT_WAREHOUSE_CODE)
warehouseNotFoundCount++

if (warehouse == null) {
println("ERROR - Default warehouse '$DEFAULT_WAREHOUSE_CODE' also not found in database")
continue
}
println("SKIP - No warehouse for location '${data.locationCode}' (no default)")
warehouseSkippedCount++
continue
}

if (warehouse.id == null) {
@@ -178,18 +167,34 @@ open class InventorySetup {
continue
}

// Generate dnNo: LT-YYYYMMDD-itemCode
val stockQuantityRaw = itemUomService.findSalesUnitByItemId(item.id!!)?.uom?.id?.let { salesUomId ->
itemUomService.convertQtyToStockQty(item.id!!, salesUomId, data.quantitySalesUnit)
} ?: data.quantitySalesUnit
val stockQuantity = stockQuantityRaw.setScale(0, RoundingMode.FLOOR)

if (stockQuantity != stockQuantityRaw) {
println("FLOOR applied for item ${data.itemNo}: $stockQuantityRaw -> $stockQuantity (sales qty: ${data.quantitySalesUnit})")
}

if (stockQuantity <= BigDecimal.ZERO) {
if (stockQuantityRaw > BigDecimal.ZERO) {
println("SKIP - Stock quantity became 0 after floor for item ${data.itemNo} (sales qty: ${data.quantitySalesUnit}, converted before floor: $stockQuantityRaw)")
} else {
println("SKIP - Stock quantity <= 0 for item ${data.itemNo} (sales qty: ${data.quantitySalesUnit})")
}
continue
}

val today = LocalDate.now()
val dnNo = "LT-${today.format(DateTimeFormatter.ofPattern("yyyyMMdd"))}-${data.itemNo}"

// Use generic stock in
val stockInRequest = StockInRequest(
itemId = item.id!!,
itemNo = data.itemNo,
demandQty = data.quantity,
acceptedQty = data.quantity,
demandQty = stockQuantity,
acceptedQty = stockQuantity,
expiryDate = data.expiryDate,
lotNo = null, // System will auto-generate
lotNo = null,
productLotNo = data.productLotNo,
dnNo = dnNo,
type = "OPEN",
@@ -198,7 +203,7 @@ open class InventorySetup {

stockInLineService.createStockIn(stockInRequest)
createdCount++
println("CREATED stock in for item: ${data.itemNo} (dnNo: $dnNo, warehouse: ${warehouse.code})")
println("CREATED stock in for item: ${data.itemNo} (sales qty: ${data.quantitySalesUnit} -> stock qty: $stockQuantity, warehouse: ${warehouse.code})")
} catch (e: Exception) {
println("Error processing inventory row (itemNo: ${data.itemNo}): ${e.message}")
e.printStackTrace()
@@ -206,7 +211,7 @@ open class InventorySetup {
}
}

println("Import finished. Created $createdCount stock in records, Not found: $notFoundCount items, Warehouse not found (used default): $warehouseNotFoundCount.")
println("Import finished. Created $createdCount stock in records, Not found: $notFoundCount items, Skipped (no location): $warehouseSkippedCount.")
return createdCount

} finally {
@@ -218,7 +223,7 @@ open class InventorySetup {
data class InventoryData(
val itemNo: String,
val locationCode: String,
val quantity: BigDecimal,
val quantitySalesUnit: BigDecimal,
val productLotNo: String?,
val expiryDate: LocalDate
)


Notiek ielāde…
Atcelt
Saglabāt