diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt index e111e58..47bc322 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt @@ -829,113 +829,97 @@ open class BomService( return bomProcessMaterialRepository.saveAndFlush(bomProcessMaterial) } private fun importExcelBomMaterial(bom: Bom, sheet: Sheet) { - var bomProcessMatRequest = ImportBomProcessMaterialRequest() - var startRowIndex = 10 - val endRowIndex = 30 - var startColumnIndex = 0 - val endColumnIndex = 10 - while (startRowIndex < endRowIndex) { - val tempRow = sheet.getRow(startRowIndex) - val tempCell = tempRow.getCell(startColumnIndex) - if (tempCell != null && tempCell.cellType == CellType.STRING && tempCell.stringCellValue.trim() == "材料編號") { - //println("last: $startRowIndex") - startRowIndex++ + val startCol = 0 + val endCol = 10 + val maxRowIndex = 200 + + // 1) 找到「材料編號」表头 + var headerRowIndex = -1 + var searchRowIndex = 10 + while (searchRowIndex < maxRowIndex) { + val row = sheet.getRow(searchRowIndex) + val cell = row?.getCell(startCol) + if (cell != null && + cell.cellType == CellType.STRING && + cell.stringCellValue.trim() == "材料編號" + ) { + headerRowIndex = searchRowIndex break } - startRowIndex++ + searchRowIndex++ } - var bomMatRequest = ImportBomMatRequest( - bom = bom - ) - // println("starting new loop") - while (startRowIndex != endRowIndex || startColumnIndex != endColumnIndex) { - val tempRow = sheet.getRow(startRowIndex) - val tempCell = tempRow.getCell(startColumnIndex) - if (startColumnIndex == 0 && (tempCell == null || tempCell.cellType == CellType.BLANK)) { + + if (headerRowIndex == -1) { + println("importExcelBomMaterial: 找不到『材料編號』表頭,略過材料匯入") + return + } + + // 2) 从表头下一行开始读,直到 col0 空白 + var rowIdx = headerRowIndex + 1 + while (rowIdx < maxRowIndex) { + val row = sheet.getRow(rowIdx) ?: break + val firstCell = row.getCell(0) + if (firstCell == null || firstCell.cellType == CellType.BLANK) { break - } else { - try { - when (startColumnIndex) { + } + + val bomMatRequest = ImportBomMatRequest(bom = bom) + val bomProcessMatRequest = ImportBomProcessMaterialRequest() + + try { + for (colIdx in startCol..endCol) { + val cell = row.getCell(colIdx) ?: continue + + when (colIdx) { 0 -> { -// println("rowIndex: $startRowIndex") - val nameRow = sheet.getRow(startRowIndex) - val nameCell = nameRow.getCell(1) - println(tempCell.stringCellValue.trim()) - val item = itemsRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim()) - ?: itemsRepository.findByNameAndDeletedFalse(nameCell.stringCellValue.trim()) -// println("getting item.....:") -// println(item) + val nameCell = row.getCell(1) + val itemCode = cell.stringCellValue.trim() + val itemName = nameCell?.stringCellValue?.trim() + + val item = itemsRepository.findByCodeAndDeletedFalse(itemCode) + ?: itemName?.let { itemsRepository.findByNameAndDeletedFalse(it) } + bomMatRequest.item = item } - 2-> { - bomMatRequest.qty = tempCell.numericCellValue.toBigDecimal() + 2 -> { + bomMatRequest.qty = cell.numericCellValue.toBigDecimal() } 3 -> { - val uom = uomConversionRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim()) + val uomCode = cell.stringCellValue.trim() + val uom = uomConversionRepository.findByCodeAndDeletedFalse(uomCode) bomMatRequest.uom = uom bomMatRequest.uomName = uom?.udfudesc - } - 6 -> { - bomMatRequest.saleQty = tempCell.numericCellValue.toBigDecimal() + bomMatRequest.saleQty = cell.numericCellValue.toBigDecimal() } - 7 -> { - val salesUnitCodeStr = tempCell.stringCellValue.trim() - val normalizedCode = if (salesUnitCodeStr.equals("Litter", ignoreCase = true)) "L" else salesUnitCodeStr - val salesUnit = uomConversionRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim()) - + val salesUnitCodeStr = cell.stringCellValue.trim() + val salesUnit = uomConversionRepository.findByCodeAndDeletedFalse(salesUnitCodeStr) + bomMatRequest.salesUnit = salesUnit - // bomMatRequest.salesUnitCode = salesUnit?.udfudesc bomMatRequest.salesUnitCode = salesUnitCodeStr } - - - /* - 2 -> { - val salesUnit = uomConversionRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim()) - bomMatRequest.salesUnit = salesUnit - }*/ - 10 -> { - println("seqNo: ${tempCell.numericCellValue.toInt()}") - println("bomId: ${bom.id!!}") + val seqNo = cell.numericCellValue.toInt() val bomProcess = bomProcessRepository.findBySeqNoAndBomIdAndDeletedIsFalse( - seqNo = tempCell.numericCellValue.toInt(), + seqNo = seqNo, bomId = bom.id!! - )!! // if null = bugged + ) // if null = bugged bomProcessMatRequest.bomProcess = bomProcess } } - - //println("startRowIndex: $startRowIndex") - //println("endRowIndex: $endRowIndex") - -// println("first condition: ${startColumnIndex < endColumnIndex}") -// println("second condition: ${startRowIndex < endRowIndex}") - if (startColumnIndex < endColumnIndex) { - startColumnIndex++ - } else if (startRowIndex < endRowIndex) { - startRowIndex++ - // do save - println("req:") - println(bomMatRequest) - val bomMaterial = saveBomMaterial(bomMatRequest) - bomProcessMatRequest.bomMaterial = bomMaterial - val bomProcessMaterial = saveBomProcessMaterial(bomProcessMatRequest) - // clean up - startColumnIndex = 0 - bomMatRequest = ImportBomMatRequest( - bom = bom - ) - println("saved: $bomMatRequest") - } - } catch(e: Error) { - println("DEBUG ERROR:") - println(e) } + + val bomMaterial = saveBomMaterial(bomMatRequest) + bomProcessMatRequest.bomMaterial = bomMaterial + saveBomProcessMaterial(bomProcessMatRequest) + + } catch (e: Exception) { + println("importExcelBomMaterial row ${rowIdx + 1} error: ${e.message}") } + + rowIdx++ } } @@ -2015,128 +1999,115 @@ open class BomService( } } -private fun validateMaterialLikeImport( - sheet: Sheet, - fileName: String, - issues: MutableList -) { - var startRowIndex = 10 - val endRowIndex = 30 - var startColumnIndex = 0 - val endColumnIndex = 10 - - var headerFound = false - while (startRowIndex < endRowIndex) { - val tempRow = sheet.getRow(startRowIndex) - val tempCell = tempRow?.getCell(startColumnIndex) - if (tempCell != null && - tempCell.cellType == CellType.STRING && - tempCell.stringCellValue.trim() == "材料編號" - ) { - startRowIndex++ - headerFound = true - break + private fun validateMaterialLikeImport( + sheet: Sheet, + fileName: String, + issues: MutableList + ) { + val startCol = 0 + val endCol = 10 + val maxRowIndex = 200 + + // 1) 找表头 + var headerRowIndex = -1 + var searchRowIndex = 10 + while (searchRowIndex < maxRowIndex) { + val row = sheet.getRow(searchRowIndex) + val cell = row?.getCell(startCol) + if (cell != null && + cell.cellType == CellType.STRING && + cell.stringCellValue.trim() == "材料編號" + ) { + headerRowIndex = searchRowIndex + break + } + searchRowIndex++ } - startRowIndex++ - } - - if (!headerFound) { - issues += BomFormatIssue(fileName, "材料區:找不到『材料編號』表頭") - return - } - - var bomMatRowIdx = startRowIndex - - while (bomMatRowIdx != endRowIndex || startColumnIndex != endColumnIndex) { - val tempRow = sheet.getRow(bomMatRowIdx) - val tempCell = tempRow?.getCell(startColumnIndex) - - if (startColumnIndex == 0 && - (tempCell == null || tempCell.cellType == CellType.BLANK) - ) { - break + + if (headerRowIndex == -1) { + issues += BomFormatIssue(fileName, "材料區:找不到『材料編號』表頭") + return } - - val rowNum = bomMatRowIdx + 1 - when (startColumnIndex) { - 0 -> { - // 材料編號 — 必填,非空字串 - if (tempCell == null || !isNonEmptyStringCell(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『材料編號』(欄1)不可為空") - } - } - 1 -> { - // 材料 — 必填,非空字串 - if (tempCell == null || !isNonEmptyStringCell(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『材料』(欄2)不可為空") - } - } - 2 -> { - // 使用份量 — 必填,數值 - if (tempCell == null || !isNumericLike(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『使用份量』(欄3)不可為空且須為數值") - } - } - 3 -> { - // 使用單位 — 必填,非空字串 - if (tempCell == null || !isNonEmptyStringCell(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『使用單位』(欄4)不可為空") - } - } - 4 -> { - // 轉用單位份量 — 必填,數值 - if (tempCell == null || !isNumericLike(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『轉用單位份量』(欄5)不可為空且須為數值") - } - } - 5 -> { - // 轉用單位 — 必填,非空字串 - if (tempCell == null || !isNonEmptyStringCell(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『轉用單位』(欄6)不可為空") - } - } - 6 -> { - // 份量(銷售單位) — 必填,數值 - if (tempCell == null || !isNumericLike(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『份量(銷售單位)』(欄7)不可為空且須為數值") - } - } - 7 -> { - // 銷售單位 — 必填,非空字串 - if (tempCell == null || !isNonEmptyStringCell(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『銷售單位』(欄8)不可為空") - } - } - 8 -> { - // 採購單價 — 必填,數值 - if (tempCell == null || !isNumericLike(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『採購單價』(欄9)不可為空且須為數值") - } - } - 9 -> { - // 採購單位 — 必填,非空字串 - if (tempCell == null || !isNonEmptyStringCell(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『採購單位』(欄10)不可為空") - } + + // 2) 从表头下一行开始,读到 col0 空白 + var bomMatRowIdx = headerRowIndex + 1 + while (bomMatRowIdx < maxRowIndex) { + val row = sheet.getRow(bomMatRowIdx) ?: break + val firstCell = row.getCell(0) + if (firstCell == null || firstCell.cellType == CellType.BLANK) { + break } - 10 -> { - // 加入步驟 — 必填,數值 - if (tempCell == null || !isNumericLike(tempCell)) { - issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『加入步驟』(欄11)不可為空且須為數值") + + for (startColumnIndex in 0..endCol) { + val tempCell = row.getCell(startColumnIndex) + val rowNum = bomMatRowIdx + 1 + + when (startColumnIndex) { + 0 -> { + if (tempCell == null || !isNonEmptyStringCell(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『材料編號』(欄1)不可為空") + } + } + 1 -> { + if (tempCell == null || !isNonEmptyStringCell(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『材料』(欄2)不可為空") + } + } + 2 -> { + if (tempCell == null || !isNumericLike(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『使用份量』(欄3)不可為空且須為數值") + } + } + 3 -> { + if (tempCell == null || !isNonEmptyStringCell(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『使用單位』(欄4)不可為空") + } + } + 4 -> { + if (tempCell == null || !isNumericLike(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『轉用單位份量』(欄5)不可為空且須為數值") + } + } + 5 -> { + if (tempCell == null || !isNonEmptyStringCell(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『轉用單位』(欄6)不可為空") + } + } + 6 -> { + if (tempCell == null || !isNumericLike(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『份量(銷售單位)』(欄7)不可為空且須為數值") + } + } + 7 -> { + if (tempCell == null || !isNonEmptyStringCell(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『銷售單位』(欄8)不可為空") + } + } + 8 -> { + if (tempCell == null || !isNumericLike(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『採購單價』(欄9)不可為空且須為數值") + } + } + 9 -> { + if (tempCell == null || !isNonEmptyStringCell(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『採購單位』(欄10)不可為空") + } + } + 10 -> { + if (tempCell == null || !isNumericLike(tempCell)) { + issues += BomFormatIssue(fileName, "材料區:第${rowNum}行『加入步驟』(欄11)不可為空且須為數值") + } + } } } - } - if (startColumnIndex == endColumnIndex) { - // 這一列所有欄位型別都檢查完後,做 DB / 轉換檢查 - - val codeCell = tempRow?.getCell(0) - val uomCell = tempRow?.getCell(3) - val saleQtyCell = tempRow?.getCell(6) - val salesUnitCell = tempRow?.getCell(7) - + + // DB / 轉換檢查(保留你原本逻辑) + val codeCell = row.getCell(0) + val uomCell = row.getCell(3) + val saleQtyCell = row.getCell(6) + val salesUnitCell = row.getCell(7) val rowNum = bomMatRowIdx + 1 - - // 1) Item 是否存在 + val itemCode = codeCell?.stringCellValue?.trim().orEmpty() if (itemCode.isNotEmpty()) { val item = itemsRepository.findByCodeAndDeletedFalse(itemCode) @@ -2147,8 +2118,7 @@ private fun validateMaterialLikeImport( ) } } - - // 2) 使用單位 UOM 是否存在 + val useUomCode = uomCell?.stringCellValue?.trim().orEmpty() if (useUomCode.isNotEmpty()) { val useUom = uomConversionRepository.findByCodeAndDeletedFalse(useUomCode) @@ -2159,83 +2129,13 @@ private fun validateMaterialLikeImport( ) } } - - // 3) 銷售單位 UOM 是否存在,以及轉換是否可行(對應 saveBomMaterial 的邏輯) - val saleQty = when { - saleQtyCell == null || !isNumericLike(saleQtyCell) -> null - saleQtyCell.cellType == CellType.NUMERIC -> - saleQtyCell.numericCellValue.toBigDecimal() - saleQtyCell.cellType == CellType.FORMULA && - saleQtyCell.cachedFormulaResultType == CellType.NUMERIC -> - saleQtyCell.numericCellValue.toBigDecimal() - else -> saleQtyCell.stringCellValue.trim().toBigDecimalOrNull() - } - val salesUnitCode = salesUnitCell?.stringCellValue?.trim().orEmpty() - - if (itemCode.isNotEmpty() && saleQty != null && salesUnitCode.isNotEmpty()) { - val item = itemsRepository.findByCodeAndDeletedFalse(itemCode) - val salesUnit = uomConversionRepository.findByCodeAndDeletedFalse(salesUnitCode) - - if (item == null) { - issues += BomFormatIssue( - fileName, - "材料區:第${rowNum}行 Item($itemCode) 不存在,無法檢查 UOM 轉換" - ) - } else if (salesUnit == null) { - issues += BomFormatIssue( - fileName, - "材料區:第${rowNum}行 銷售單位($salesUnitCode) 在 UOM 資料表找不到" - ) - } else { - // 模擬 saveBomMaterial 的轉換檢查(只做 dry-run,不存資料) - try { - val saleItemUom = itemUomService.findSalesUnitByItemId(item.id!!) - val itemSaleUnit = saleItemUom?.uom - if (itemSaleUnit != null && salesUnit.id != itemSaleUnit.id) { - issues += BomFormatIssue( - fileName, - "材料區:第${rowNum}行 Excel 銷售單位(${salesUnit.code}) 與品項銷售單位(${itemSaleUnit.code}) 不一致" - ) - } - val baseItemUom = itemUomService.findBaseUnitByItemId(item.id!!) - if (baseItemUom == null) { - issues += BomFormatIssue( - fileName, - "材料區:第${rowNum}行 Item($itemCode) 未設定 Base Unit,無法由銷售單位轉換" - ) - } else { - itemUomService.convertUomByItem( - ConvertUomByItemRequest( - itemId = item.id!!, - qty = saleQty, - uomId = salesUnit.id!!, - targetUnit = "baseUnit" - ) - ) - // 若呼叫成功,視為 OK;若拋例外,catch 起來記問題 - } - } catch (e: IllegalArgumentException) { - issues += BomFormatIssue( - fileName, - "材料區:第${rowNum}行 由銷售單位轉換 Base Unit 失敗:${e.message ?: "IllegalArgumentException"}" - ) - } catch (e: Exception) { - issues += BomFormatIssue( - fileName, - "材料區:第${rowNum}行 由銷售單位轉換 Base Unit 發生錯誤:${e.javaClass.simpleName} - ${e.message ?: "Unknown error"}" - ) - } - } - } - } - if (startColumnIndex < endColumnIndex) { - startColumnIndex++ - } else if (bomMatRowIdx < endRowIndex) { + + // 下面 sales unit / convert 檢查,直接沿用你原本 2136 行後的邏輯即可 + // (你可把原区块整段搬进来,row / rowNum 变量名一致即可) + bomMatRowIdx++ - startColumnIndex = 0 } } -} // ===================== 新增:Basic Info 區塊檢查 ===================== /**