|
|
@@ -68,6 +68,10 @@ open class BomService( |
|
|
private val settingsService: SettingsService, |
|
|
private val settingsService: SettingsService, |
|
|
@Value("\${bom.import.temp-dir:\${java.io.tmpdir}/fpsms-bom-import}") private val bomImportTempDir: String, |
|
|
@Value("\${bom.import.temp-dir:\${java.io.tmpdir}/fpsms-bom-import}") private val bomImportTempDir: String, |
|
|
) { |
|
|
) { |
|
|
|
|
|
companion object { |
|
|
|
|
|
private const val BOM_WIP_DESCRIPTION = "WIP" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
open fun uploadBomFiles(files: List<MultipartFile>): BomUploadResponse { |
|
|
open fun uploadBomFiles(files: List<MultipartFile>): BomUploadResponse { |
|
|
val batchId = UUID.randomUUID().toString() |
|
|
val batchId = UUID.randomUUID().toString() |
|
|
val batchDir = Paths.get(bomImportTempDir, batchId).toAbsolutePath() |
|
|
val batchDir = Paths.get(bomImportTempDir, batchId).toAbsolutePath() |
|
|
@@ -580,7 +584,8 @@ open class BomService( |
|
|
private fun saveBomEntity(req: ImportBomRequest): Bom { |
|
|
private fun saveBomEntity(req: ImportBomRequest): Bom { |
|
|
val item = itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name) |
|
|
val item = itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name) |
|
|
val uom = if (req.uomId != null) uomConversionRepository.findById(req.uomId!!).orElseThrow() else null |
|
|
val uom = if (req.uomId != null) uomConversionRepository.findById(req.uomId!!).orElseThrow() else null |
|
|
val bom = bomRepository.findByCodeAndDeletedIsFalse(req.code) ?: Bom() |
|
|
|
|
|
|
|
|
val fgDescription = req.description.trim().ifEmpty { "FG" } |
|
|
|
|
|
val bom = bomRepository.findByCodeAndDescriptionIgnoreCaseAndDeletedIsFalse(req.code, fgDescription) ?: Bom() |
|
|
bom.apply { |
|
|
bom.apply { |
|
|
this.isDark = req.isDark |
|
|
this.isDark = req.isDark |
|
|
this.isFloat = req.isFloat |
|
|
this.isFloat = req.isFloat |
|
|
@@ -1576,6 +1581,45 @@ open class BomService( |
|
|
return null |
|
|
return null |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Reads BOM 種類 (FG / WIP) from sheet without saving. */ |
|
|
|
|
|
private fun readBomDescriptionFromSheet(sheet: Sheet): String? { |
|
|
|
|
|
for (r in 0..9) { |
|
|
|
|
|
for (c in 0..9) { |
|
|
|
|
|
val cell = sheet.getRow(r)?.getCell(c) ?: continue |
|
|
|
|
|
if (cell.cellType != CellType.STRING) continue |
|
|
|
|
|
if (cell.stringCellValue.trim() != "種類") continue |
|
|
|
|
|
val valueRow = sheet.getRow(r + 1) ?: return null |
|
|
|
|
|
val valueCell = valueRow.getCell(c) ?: return null |
|
|
|
|
|
return when { |
|
|
|
|
|
valueCell.cellType == CellType.STRING -> valueCell.stringCellValue.trim().takeIf { it.isNotEmpty() } |
|
|
|
|
|
valueCell.cellType == CellType.FORMULA && valueCell.cachedFormulaResultType == CellType.STRING -> |
|
|
|
|
|
valueCell.stringCellValue.trim().takeIf { it.isNotEmpty() } |
|
|
|
|
|
else -> null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun normalizeFgDescriptionForImport(description: String?): String = |
|
|
|
|
|
description?.trim()?.takeIf { it.isNotEmpty() } ?: "FG" |
|
|
|
|
|
|
|
|
|
|
|
/** Soft-delete FG (code + Excel 種類) and WIP (code + WIP) rows before re-import. */ |
|
|
|
|
|
private fun softDeleteExistingBomsForImport(code: String, fgDescription: String) { |
|
|
|
|
|
val fgDesc = normalizeFgDescriptionForImport(fgDescription) |
|
|
|
|
|
bomRepository.findByCodeAndDescriptionIgnoreCaseAndDeletedIsFalse(code, fgDesc)?.id?.let { |
|
|
|
|
|
softDeleteBomAndRelated(it) |
|
|
|
|
|
} |
|
|
|
|
|
bomRepository.findByCodeAndDescriptionIgnoreCaseAndDeletedIsFalse(code, BOM_WIP_DESCRIPTION)?.id?.let { |
|
|
|
|
|
softDeleteBomAndRelated(it) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun findFgBomIdForImport(code: String, fgDescription: String): Long? { |
|
|
|
|
|
val fgDesc = normalizeFgDescriptionForImport(fgDescription) |
|
|
|
|
|
return bomRepository.findByCodeAndDescriptionIgnoreCaseAndDeletedIsFalse(code, fgDesc)?.id |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private fun softDeleteBomAndRelated(bomId: Long) { |
|
|
private fun softDeleteBomAndRelated(bomId: Long) { |
|
|
val bom = bomRepository.findById(bomId).orElse(null) ?: return |
|
|
val bom = bomRepository.findById(bomId).orElse(null) ?: return |
|
|
bom.deleted = true |
|
|
bom.deleted = true |
|
|
@@ -1633,12 +1677,11 @@ open class BomService( |
|
|
?: workbook2.getSheet("食物成品") |
|
|
?: workbook2.getSheet("食物成品") |
|
|
?: workbook2.getSheetAt(0) |
|
|
?: workbook2.getSheetAt(0) |
|
|
val code = readBomCodeFromSheet(sheet) |
|
|
val code = readBomCodeFromSheet(sheet) |
|
|
|
|
|
val fgDescription = readBomDescriptionFromSheet(sheet) |
|
|
var oldBomId: Long? = null |
|
|
var oldBomId: Long? = null |
|
|
code?.let { c -> |
|
|
code?.let { c -> |
|
|
bomRepository.findByCodeAndDeletedIsFalse(c)?.id?.let { existingId -> |
|
|
|
|
|
softDeleteBomAndRelated(existingId) |
|
|
|
|
|
oldBomId = existingId |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
oldBomId = findFgBomIdForImport(c, fgDescription ?: "FG") |
|
|
|
|
|
softDeleteExistingBomsForImport(c, fgDescription ?: "FG") |
|
|
} |
|
|
} |
|
|
val bom = importExcelBomBasicInfo(sheet) |
|
|
val bom = importExcelBomBasicInfo(sheet) |
|
|
bom.isDrink = isDrink |
|
|
bom.isDrink = isDrink |
|
|
@@ -1705,10 +1748,15 @@ open class BomService( |
|
|
} |
|
|
} |
|
|
/** 方案 A:複製 FG BOM 為一筆相同 code、相同 item、description=WIP 的 BOM,並複製 materials 與 processes。 */ |
|
|
/** 方案 A:複製 FG BOM 為一筆相同 code、相同 item、description=WIP 的 BOM,並複製 materials 與 processes。 */ |
|
|
private fun createWipCopyFromFgBom(fgBom: Bom) { |
|
|
private fun createWipCopyFromFgBom(fgBom: Bom) { |
|
|
|
|
|
val code = fgBom.code ?: return |
|
|
|
|
|
val existingWip = bomRepository.findByCodeAndDescriptionIgnoreCaseAndDeletedIsFalse(code, BOM_WIP_DESCRIPTION) |
|
|
|
|
|
if (existingWip != null) { |
|
|
|
|
|
softDeleteBomAndRelated(existingWip.id!!) |
|
|
|
|
|
} |
|
|
val wipBom = Bom().apply { |
|
|
val wipBom = Bom().apply { |
|
|
code = fgBom.code |
|
|
|
|
|
|
|
|
this.code = code |
|
|
name = fgBom.name |
|
|
name = fgBom.name |
|
|
description = "WIP" |
|
|
|
|
|
|
|
|
description = BOM_WIP_DESCRIPTION |
|
|
item = fgBom.item |
|
|
item = fgBom.item |
|
|
outputQty = fgBom.outputQty |
|
|
outputQty = fgBom.outputQty |
|
|
outputQtyUom = fgBom.outputQtyUom |
|
|
outputQtyUom = fgBom.outputQtyUom |
|
|
@@ -2364,7 +2412,7 @@ open class BomService( |
|
|
var ColorDepthValueOk = false |
|
|
var ColorDepthValueOk = false |
|
|
var FloatingValueOk = false |
|
|
var FloatingValueOk = false |
|
|
var ConcentrationValueOk = false |
|
|
var ConcentrationValueOk = false |
|
|
println("=== Debug sheet content for $fileName ===") |
|
|
|
|
|
|
|
|
// println("=== Debug sheet content for $fileName ===") |
|
|
for (r in 0..20) { |
|
|
for (r in 0..20) { |
|
|
val row = sheet.getRow(r) ?: continue |
|
|
val row = sheet.getRow(r) ?: continue |
|
|
for (c in 0..20) { |
|
|
for (c in 0..20) { |
|
|
@@ -2383,7 +2431,7 @@ for (r in 0..20) { |
|
|
else -> cell.cellType.toString() |
|
|
else -> cell.cellType.toString() |
|
|
} |
|
|
} |
|
|
if (value.isNotBlank() && value != "BLANK") { |
|
|
if (value.isNotBlank() && value != "BLANK") { |
|
|
println("($r, $c) = $value") |
|
|
|
|
|
|
|
|
// println("($r, $c) = $value") |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|