|
|
|
@@ -9,6 +9,7 @@ import com.ffii.fpsms.modules.master.entity.Warehouse |
|
|
|
import com.ffii.fpsms.modules.master.entity.WarehouseRepository |
|
|
|
import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo |
|
|
|
import com.ffii.fpsms.modules.master.web.models.SaveWarehouseRequest |
|
|
|
import com.ffii.fpsms.modules.master.web.models.NewWarehouseRequest |
|
|
|
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository |
|
|
|
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository |
|
|
|
import com.ffii.fpsms.modules.stock.entity.StockInLine |
|
|
|
@@ -23,7 +24,7 @@ import org.slf4j.LoggerFactory |
|
|
|
import org.springframework.stereotype.Service |
|
|
|
import java.math.BigDecimal |
|
|
|
import kotlin.jvm.optionals.getOrNull |
|
|
|
|
|
|
|
import com.ffii.fpsms.modules.master.web.models.ExcelWarehouseData |
|
|
|
@Service |
|
|
|
open class WarehouseService( |
|
|
|
private val jdbcDao: JdbcDao, |
|
|
|
@@ -109,4 +110,233 @@ open class WarehouseService( |
|
|
|
logger.info("--------- End - Import Warehouse Excel -------") |
|
|
|
return "Import Excel success"; |
|
|
|
} |
|
|
|
open fun importNewExcel(workbook: Workbook?): String { |
|
|
|
logger.info("--------- Start - Import Warehouse Excel -------"); |
|
|
|
|
|
|
|
if (workbook == null) { |
|
|
|
logger.error("No Excel Import"); |
|
|
|
return "Import Excel failure"; |
|
|
|
} |
|
|
|
val sheet: Sheet = workbook.getSheetAt(0); |
|
|
|
|
|
|
|
// Columns |
|
|
|
val COLUMN_WAREHOSE_INDEX = 1; |
|
|
|
val COLUMN_ZONE_INDEX = 2; |
|
|
|
val COLUMN_SLOT_INDEX = 3; |
|
|
|
val COLUMN_FLOOR_INDEX = 4; |
|
|
|
val COLUMN_PICK_ROUTING_INDEX = 5; |
|
|
|
val COLUMN_STORE_LOCATION_INDEX = 6; |
|
|
|
val COLUMN_STOCK_TAKE_TABLE_INDEX = 7; |
|
|
|
val COLUMN_COMPANY_INDEX = 8; |
|
|
|
|
|
|
|
val START_ROW_INDEX = 5; |
|
|
|
|
|
|
|
// 第一步:收集所有 Excel 数据(包含行索引,用于调试) |
|
|
|
data class ExcelWarehouseDataWithRow( |
|
|
|
val rowIndex: Int, |
|
|
|
val warehouse: String, |
|
|
|
val area: String, |
|
|
|
val slot: String |
|
|
|
) |
|
|
|
|
|
|
|
val excelDataList = mutableListOf<ExcelWarehouseDataWithRow>() |
|
|
|
var skippedRows = 0 |
|
|
|
|
|
|
|
for (i in START_ROW_INDEX..<sheet.lastRowNum) { |
|
|
|
val row = sheet.getRow(i) |
|
|
|
if (row == null) { |
|
|
|
skippedRows++ |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
val warehouse = ExcelUtils.getStringValue(row.getCell(COLUMN_WAREHOSE_INDEX))?.trim() |
|
|
|
val area = ExcelUtils.getStringValue(row.getCell(COLUMN_ZONE_INDEX))?.trim() |
|
|
|
val slot = ExcelUtils.getStringValue(row.getCell(COLUMN_SLOT_INDEX))?.trim() |
|
|
|
|
|
|
|
if (warehouse.isNullOrBlank() || area.isNullOrBlank() || slot.isNullOrBlank()) { |
|
|
|
skippedRows++ |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
excelDataList.add(ExcelWarehouseDataWithRow(i, warehouse, area, slot)) |
|
|
|
} catch (e: Exception) { |
|
|
|
logger.error("Read Error (Row ${i + 1}): ${e.message}") |
|
|
|
skippedRows++ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
logger.info("Total rows in Excel: ${sheet.lastRowNum - START_ROW_INDEX + 1}, Valid rows collected: ${excelDataList.size}, Skipped: $skippedRows") |
|
|
|
|
|
|
|
// 第二步:计算 order 映射(使用 ExcelWarehouseData) |
|
|
|
val excelDataForOrder = excelDataList.map { |
|
|
|
ExcelWarehouseData(it.warehouse, it.area, it.slot) |
|
|
|
} |
|
|
|
val orderMap = calculateOrderMap(excelDataForOrder) |
|
|
|
|
|
|
|
logger.info("Order map size: ${orderMap.size}") |
|
|
|
|
|
|
|
// 第三步:在循环中使用 orderMap 匹配 |
|
|
|
var updateCount = 0 |
|
|
|
var createCount = 0 |
|
|
|
var skippedInSecondLoop = 0 |
|
|
|
val processedKeys = mutableSetOf<String>() |
|
|
|
|
|
|
|
for (i in START_ROW_INDEX..<sheet.lastRowNum) { |
|
|
|
val row = sheet.getRow(i) |
|
|
|
if (row == null) continue |
|
|
|
|
|
|
|
try { |
|
|
|
val warehouse = ExcelUtils.getStringValue(row.getCell(COLUMN_WAREHOSE_INDEX))?.trim() |
|
|
|
val area = ExcelUtils.getStringValue(row.getCell(COLUMN_ZONE_INDEX))?.trim() |
|
|
|
val slot = ExcelUtils.getStringValue(row.getCell(COLUMN_SLOT_INDEX))?.trim() |
|
|
|
val store_id = ExcelUtils.getStringValue(row.getCell(COLUMN_FLOOR_INDEX))?.trim() |
|
|
|
val storeLocation = ExcelUtils.getStringValue(row.getCell(COLUMN_STORE_LOCATION_INDEX))?.trim() |
|
|
|
val stockTakeTable = ExcelUtils.getStringValue(row.getCell(COLUMN_STOCK_TAKE_TABLE_INDEX))?.trim() |
|
|
|
val company = ExcelUtils.getStringValue(row.getCell(COLUMN_COMPANY_INDEX))?.trim() |
|
|
|
|
|
|
|
if (warehouse.isNullOrBlank() || area.isNullOrBlank() || slot.isNullOrBlank()) { |
|
|
|
skippedInSecondLoop++ |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
// 使用唯一标识从 orderMap 获取 order |
|
|
|
val uniqueKey = "$warehouse-$area-$slot" |
|
|
|
val order = orderMap[uniqueKey] |
|
|
|
|
|
|
|
if (order == null) { |
|
|
|
logger.warn("Order not found for key: $uniqueKey at row ${i + 1}") |
|
|
|
skippedInSecondLoop++ |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
val capacity = BigDecimal(10000) |
|
|
|
val code = "$store_id-$warehouse-$area-$slot" |
|
|
|
val name = "$store_id-$storeLocation" |
|
|
|
val description = "$store_id-$storeLocation" |
|
|
|
|
|
|
|
// 检查是否已经处理过这个 uniqueKey(避免重复处理) |
|
|
|
if (processedKeys.contains(uniqueKey)) { |
|
|
|
logger.warn("Duplicate key found: $uniqueKey at row ${i + 1}, skipping") |
|
|
|
skippedInSecondLoop++ |
|
|
|
continue |
|
|
|
} |
|
|
|
processedKeys.add(uniqueKey) |
|
|
|
|
|
|
|
// 查找现有仓库 |
|
|
|
val existingWarehouse = warehouseRepository.findAll() |
|
|
|
.firstOrNull { |
|
|
|
it.code == code && it.deleted == false |
|
|
|
} ?: warehouseRepository.findAll() |
|
|
|
.firstOrNull { |
|
|
|
it.warehouse == warehouse && |
|
|
|
it.area == area && |
|
|
|
it.slot == slot && |
|
|
|
it.deleted == false |
|
|
|
} |
|
|
|
|
|
|
|
if (existingWarehouse != null) { |
|
|
|
// 更新 |
|
|
|
existingWarehouse.apply { |
|
|
|
this.code = code |
|
|
|
this.name = name |
|
|
|
this.description = description |
|
|
|
this.warehouse = warehouse |
|
|
|
this.area = area |
|
|
|
this.store_id = store_id |
|
|
|
this.slot = slot |
|
|
|
this.order = order |
|
|
|
this.storeLocation = storeLocation |
|
|
|
this.stockTakeTable = stockTakeTable |
|
|
|
this.company = company |
|
|
|
} |
|
|
|
warehouseRepository.save(existingWarehouse) |
|
|
|
updateCount++ |
|
|
|
} else { |
|
|
|
// 创建 |
|
|
|
val newWarehouse = Warehouse().apply { |
|
|
|
this.code = code |
|
|
|
this.name = name |
|
|
|
this.description = description |
|
|
|
this.capacity = capacity |
|
|
|
this.warehouse = warehouse |
|
|
|
this.area = area |
|
|
|
this.store_id = store_id |
|
|
|
this.slot = slot |
|
|
|
this.order = order |
|
|
|
this.storeLocation = storeLocation |
|
|
|
this.stockTakeTable = stockTakeTable |
|
|
|
this.company = company |
|
|
|
} |
|
|
|
warehouseRepository.save(newWarehouse) |
|
|
|
createCount++ |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
logger.error("Import Error (Row ${i + 1}): ${e.message}") |
|
|
|
skippedInSecondLoop++ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
logger.info("--------- End - Import Warehouse Excel - Created: $createCount, Updated: $updateCount, Skipped in second loop: $skippedInSecondLoop -------") |
|
|
|
return "Import Excel success - Created: $createCount, Updated: $updateCount, Skipped: $skippedInSecondLoop"; |
|
|
|
} |
|
|
|
|
|
|
|
// 计算 order 映射:接收 Excel 数据列表,返回 Map<唯一标识, order> |
|
|
|
private fun calculateOrderMap(excelDataList: List<ExcelWarehouseData>): Map<String, Int> { |
|
|
|
data class WarehouseWithSort( |
|
|
|
val data: ExcelWarehouseData, |
|
|
|
val sortValue: Long, |
|
|
|
val uniqueKey: String |
|
|
|
) |
|
|
|
|
|
|
|
val sortedWarehouses = excelDataList.map { data -> |
|
|
|
val slot = data.slot.toIntOrNull() ?: 0 |
|
|
|
val sortValue = calculateSortValue(data.warehouse, data.area, slot) |
|
|
|
val uniqueKey = "${data.warehouse}-${data.area}-${data.slot}" |
|
|
|
WarehouseWithSort(data, sortValue, uniqueKey) |
|
|
|
}.sortedBy { it.sortValue } |
|
|
|
|
|
|
|
// 分配连续的 order(1, 2, 3...),类似 ROW_NUMBER() |
|
|
|
return sortedWarehouses.mapIndexed { index, warehouseWithSort -> |
|
|
|
warehouseWithSort.uniqueKey to (index + 1) // 唯一标识 -> order |
|
|
|
}.toMap() |
|
|
|
} |
|
|
|
|
|
|
|
// 计算单个仓库的排序值(与 MySQL 逻辑一致) |
|
|
|
private fun calculateSortValue(warehouseStr: String, areaStr: String, slot: Int): Long { |
|
|
|
// 提取楼层号 |
|
|
|
val floorNumber = if (warehouseStr.length >= 2) { |
|
|
|
warehouseStr.substring(1, 2).toIntOrNull() ?: 99 |
|
|
|
} else 99 |
|
|
|
|
|
|
|
// 提取字母部分 |
|
|
|
val letterPart = if (areaStr.startsWith("#") && areaStr.length >= 2) { |
|
|
|
areaStr.substring(1, 2) |
|
|
|
} else null |
|
|
|
|
|
|
|
// 计算楼层排序值 |
|
|
|
val floorOrder = when (floorNumber) { |
|
|
|
2 -> 1 |
|
|
|
3 -> 2 |
|
|
|
4 -> 3 |
|
|
|
else -> 99 |
|
|
|
} |
|
|
|
|
|
|
|
// 计算字母排序值(与 MySQL 逻辑一致) |
|
|
|
val letterOrder = when { |
|
|
|
letterPart == null -> 9999 |
|
|
|
letterPart in listOf("C", "B", "A", "E", "F", "G", "H", "I", "N", "M", "L", "P", "Q", "R", "S", "Y", "O", "K", "D") -> { |
|
|
|
val orderMap = mapOf( |
|
|
|
"C" to 1, "B" to 2, "A" to 3, "E" to 4, "F" to 5, "G" to 6, "H" to 7, "I" to 8, |
|
|
|
"N" to 9, "M" to 10, "L" to 11, "P" to 12, "Q" to 13, "R" to 14, "S" to 15, |
|
|
|
"Y" to 16, "O" to 17, "K" to 18, "D" to 19 |
|
|
|
) |
|
|
|
orderMap[letterPart] ?: 9999 |
|
|
|
} |
|
|
|
else -> 1000 + letterPart[0].code |
|
|
|
} |
|
|
|
|
|
|
|
// 计算总排序值:楼层 * 10000 + 字母 * 100 + slot |
|
|
|
return floorOrder * 10000L + letterOrder * 100L + slot |
|
|
|
} |
|
|
|
} |