diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt index 30ed86f..1453d1f 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt @@ -34,7 +34,7 @@ open class JobOrderBomMaterialService( joId = joId, itemId = bm.item?.id, //reqQty = (bm.qty?.times(proportion) ?: zero).setScale(0,RoundingMode.CEILING), - reqQty = (bm.saleQty?.times(proportion) ?: zero).setScale(0, RoundingMode.CEILING), + reqQty = (bm.stockQty?.times(proportion) ?: zero).setScale(0, RoundingMode.CEILING), uomId = stockUnit?.uom?.id ) } ?: listOf() diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index 9bc979f..7b196de 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -454,10 +454,10 @@ open class JobOrderService( } // ✅ 使用 stockReqQty (bomMaterial.saleQty) 和 stockUom (bomMaterial.salesUnit) - val stockReqQty = jobm.reqQty ?: bomMaterial?.saleQty ?: BigDecimal.ZERO - val stockUomId = bomMaterial?.salesUnit?.id + val stockReqQty = jobm.reqQty ?: bomMaterial?.stockQty ?: BigDecimal.ZERO + val stockUomId = bomMaterial?.stockUnit?.toLong() ?: itemUomService.findStockUnitByItemId(itemId)?.uom?.id // Fallback: 从 Item 获取库存单位 - ?: jobm.uom?.id // 最后的 fallback + ?: jobm.uom?.id // 最后的 fallback SavePickOrderLineRequest( itemId = itemId, diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterial.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterial.kt index 8ec8fbd..4fa0e57 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterial.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterial.kt @@ -52,7 +52,12 @@ open class BomMaterial : BaseEntity() { open var baseUnit: Integer? = null @Column(name = "baseUnitName", length = 100) open var baseUnitName: String? = null - + @Column(name = "stockQty", precision = 14, scale = 2) + open var stockQty: BigDecimal? = null + @Column(name = "stockUnit", nullable = false) + open var stockUnit: Integer? = null + @Column(name = "stockUnitName", length = 100) + open var stockUnitName: String? = null @NotNull @ManyToOne(optional = false) @JoinColumn(name = "bomId", nullable = false) 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 3e53450..31315d0 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 @@ -150,23 +150,25 @@ open class BomService( var baseUnit: Integer? = null var baseUnitName: String? = null + var stockQty: BigDecimal? = null + var stockUnit: Integer? = null + var stockUnitName: String? = null + if (item?.id != null) { val itemId = item.id!! try { - // ---- 1) 获取 item 的真实 stock unit ---- - val stockItemUom = itemUomService.findStockUnitByItemId(itemId) - val itemStockUnit = stockItemUom?.uom + // ---- 1) 获取 item 的真实 sale unit ---- + val saleItemUom = itemUomService.findSalesUnitByItemId(itemId) + val itemSaleUnit = saleItemUom?.uom - // saleUnitId: 使用 item 的 stock unit uom id - saleUnitId = itemStockUnit?.id - - // saleUnitCode: 从 Excel 数据查找 uom_conversion,如果失败则使用 Excel 数据本身 + // ---- 2) 确定 sale unit(来自 Excel column 7)---- if (excelSalesUnit != null) { // Excel 找到了 uom_conversion + saleUnitId = excelSalesUnit.id saleUnitCode = excelSalesUnit.udfudesc - // 检查 Excel sales unit 与 item stock unit 是否匹配 - if (itemStockUnit != null && excelSalesUnit.id != itemStockUnit.id) { + // 检查 Excel sales unit 与 item sale unit 是否匹配 + if (itemSaleUnit != null && excelSalesUnit.id != itemSaleUnit.id) { bomMaterialImportIssues.add( BomMaterialImportIssue( bomId = bomId, @@ -174,7 +176,7 @@ open class BomService( itemId = item.id, itemCode = item.code, itemName = item.name, - reason = "Excel sales unit (${excelSalesUnit.code}/${excelSalesUnit.udfudesc}) does not match item stock unit (${itemStockUnit.code}/${itemStockUnit.udfudesc})", + reason = "Excel sales unit (${excelSalesUnit.code}/${excelSalesUnit.udfudesc}) does not match item sale unit (${itemSaleUnit.code}/${itemSaleUnit.udfudesc})", srcQty = excelSaleQty, srcUomCode = excelSalesUnit.code ) @@ -182,7 +184,7 @@ open class BomService( } } else { // Excel 没有找到 uom_conversion,使用 Excel 数据本身 - saleUnitCode = excelSalesUnitCode ?: itemStockUnit?.udfudesc + saleUnitCode = excelSalesUnitCode if (excelSalesUnitCode != null) { bomMaterialImportIssues.add( @@ -200,26 +202,85 @@ open class BomService( } } - // ---- 2) 从 saleQty + item 的真实 stock unit 转换为 baseQty ---- - if (excelSaleQty != null && itemStockUnit != null) { - val baseResult = itemUomService.convertUomByItem( - ConvertUomByItemRequest( - itemId = itemId, - qty = excelSaleQty, - uomId = itemStockUnit.id!!, - targetUnit = "baseUnit" + // ---- 3) 从 saleQty(sale unit)转换为 baseQty(base unit)---- + if (excelSaleQty != null && excelSalesUnit != null && excelSalesUnit.id != null) { + try { + // 先检查 item 是否有 base unit + val baseItemUom = itemUomService.findBaseUnitByItemId(itemId) + if (baseItemUom == null) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to baseQty: item base unit not found", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + } else { + val baseResult = itemUomService.convertUomByItem( + ConvertUomByItemRequest( + itemId = itemId, + qty = excelSaleQty, + uomId = excelSalesUnit.id!!, + targetUnit = "baseUnit" + ) + ) + baseQty = baseResult.newQty + + // 获取 base unit 信息 + baseUnit = baseItemUom.uom?.id?.toInt()?.let { Integer.valueOf(it) } as? Integer + baseUnitName = baseItemUom.uom?.udfudesc + + // 验证转换结果 + if (baseQty == null) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to baseQty: conversion returned null", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + } + } + } catch (e: IllegalArgumentException) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to baseQty: ${e.message ?: "IllegalArgumentException"}", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) ) - ) - baseQty = baseResult.newQty - } - - // ---- 3) 获取 item 的真实 base unit ---- - val baseItemUom = itemUomService.findBaseUnitByItemId(itemId) - baseUnit = baseItemUom?.uom?.id?.toInt()?.let { Integer.valueOf(it) } as? Integer - baseUnitName = baseItemUom?.uom?.udfudesc - - // 如果 baseQty 转换失败,记录问题 - if (baseQty == null && excelSaleQty != null && itemStockUnit != null) { + println("【BOM Import Warning】bomCode=$bomCode, item=${item.code} 转 baseQty 失败: ${e.message}") + } catch (e: Exception) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to baseQty: ${e.javaClass.simpleName} - ${e.message ?: "Unknown error"}", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + println("【BOM Import Error】bomCode=$bomCode, item=${item.code} 转 baseQty 失败: ${e.message}") + } + } else if (excelSaleQty != null && excelSalesUnit == null) { bomMaterialImportIssues.add( BomMaterialImportIssue( bomId = bomId, @@ -227,12 +288,92 @@ open class BomService( itemId = item.id, itemCode = item.code, itemName = item.name, - reason = "Cannot convert saleQty to baseQty: conversion failed", + reason = "Cannot convert saleQty to baseQty: Excel sales unit not found", srcQty = excelSaleQty, - srcUomCode = itemStockUnit.code + srcUomCode = excelSalesUnitCode ) ) - } else if (excelSaleQty != null && itemStockUnit == null) { + } + + // ---- 4) 从 saleQty(sale unit)转换为 stockQty(stock unit)---- + if (excelSaleQty != null && excelSalesUnit != null && excelSalesUnit.id != null) { + try { + // 先检查 item 是否有 stock unit + val stockItemUom = itemUomService.findStockUnitByItemId(itemId) + if (stockItemUom == null) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to stockQty: item stock unit not found", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + } else { + val stockResult = itemUomService.convertUomByItem( + ConvertUomByItemRequest( + itemId = itemId, + qty = excelSaleQty, + uomId = excelSalesUnit.id!!, + targetUnit = "stockUnit" + ) + ) + stockQty = stockResult.newQty + + // 获取 stock unit 信息 + stockUnit = stockItemUom.uom?.id?.toInt()?.let { Integer.valueOf(it) } as? Integer + stockUnitName = stockItemUom.uom?.udfudesc + + // 验证转换结果 + if (stockQty == null) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to stockQty: conversion returned null", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + } + } + } catch (e: IllegalArgumentException) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to stockQty: ${e.message ?: "IllegalArgumentException"}", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + println("【BOM Import Warning】bomCode=$bomCode, item=${item.code} 转 stockQty 失败: ${e.message}") + } catch (e: Exception) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to stockQty: ${e.javaClass.simpleName} - ${e.message ?: "Unknown error"}", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + println("【BOM Import Error】bomCode=$bomCode, item=${item.code} 转 stockQty 失败: ${e.message}") + } + } else if (excelSaleQty != null && excelSalesUnit == null) { bomMaterialImportIssues.add( BomMaterialImportIssue( bomId = bomId, @@ -240,13 +381,37 @@ open class BomService( itemId = item.id, itemCode = item.code, itemName = item.name, - reason = "Cannot convert saleQty to baseQty: item stock unit not found", + reason = "Cannot convert saleQty to stockQty: Excel sales unit not found", srcQty = excelSaleQty, - srcUomCode = null + srcUomCode = excelSalesUnitCode ) ) } + // 最终检查:如果 stockQty 仍然为 null,记录问题 + if (stockQty == null && excelSaleQty != null && excelSalesUnit != null && excelSalesUnit.id != null) { + // 检查是否已经记录过这个问题(避免重复) + val alreadyReported = bomMaterialImportIssues.any { issue -> + issue.itemId == item.id && + issue.reason.contains("stockQty", ignoreCase = true) && + issue.srcQty == excelSaleQty + } + if (!alreadyReported) { + bomMaterialImportIssues.add( + BomMaterialImportIssue( + bomId = bomId, + bomCode = bomCode, + itemId = item.id, + itemCode = item.code, + itemName = item.name, + reason = "Cannot convert saleQty to stockQty: conversion failed (final check)", + srcQty = excelSaleQty, + srcUomCode = excelSalesUnit.code + ) + ) + } + } + } catch (e: IllegalArgumentException) { bomMaterialImportIssues.add( BomMaterialImportIssue( @@ -301,15 +466,21 @@ open class BomService( this.uom = req.uom // BOM 原始 UOM(column 3,保留) this.uomName = req.uomName - // 新逻辑:使用 Excel column 6 的 saleQty 和 item stock unit + // 新逻辑:使用 Excel column 6 的 saleQty 和 column 7 的 sales unit this.saleQty = saleQty - this.salesUnit = item?.id?.let { itemUomService.findStockUnitByItemId(it)?.uom } + this.salesUnit = excelSalesUnit // 使用 Excel 的 sales unit,不是 item 的 stock unit this.salesUnitCode = saleUnitCode + // 从 sale unit 转换为 base unit this.baseQty = baseQty this.baseUnit = baseUnit this.baseUnitName = baseUnitName + // 从 sale unit 转换为 stock unit(新增) + this.stockQty = stockQty + this.stockUnit = stockUnit + this.stockUnitName = stockUnitName + this.bom = req.bom } return bomMaterialRepository.saveAndFlush(bomMaterial) diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductProcessLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductProcessLineRepository.kt index 74ea89b..f39929f 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductProcessLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/ProductProcessLineRepository.kt @@ -2,12 +2,13 @@ package com.ffii.fpsms.modules.productProcess.entity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository - +import org.springframework.data.jpa.repository.Query @Repository interface ProductProcessLineRepository : JpaRepository { fun findByProductProcess_Id(productProcessId: Long): List fun findByProductProcess_IdAndHandler_Id(productProcessId: Long, handlerId: Long): List fun findByHandler_IdAndStartTimeIsNotNullAndEndTimeIsNull(handlerId: Long): List fun findByProductProcess_IdIn(ids: List): List - + @Query("SELECT l FROM ProductProcessLine l LEFT JOIN FETCH l.equipment WHERE l.productProcess.id = :productProcessId") +fun findByProductProcess_IdWithEquipment(productProcessId: Long): List } \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20260204_Enson/01_add_bom_materail.sql b/src/main/resources/db/changelog/changes/20260204_Enson/01_add_bom_materail.sql new file mode 100644 index 0000000..2716bde --- /dev/null +++ b/src/main/resources/db/changelog/changes/20260204_Enson/01_add_bom_materail.sql @@ -0,0 +1,7 @@ +-- liquibase formatted sql +-- changeset KelvinY:add_baseScore_to_bom + +ALTER TABLE `fpsmsdb`.`bom_material` +ADD COLUMN `stockQty` DECIMAL(14, 2) NULL DEFAULT NULL AFTER `salesUnitCode`, +ADD COLUMN `stockUnit` INTEGER NULL DEFAULT NULL AFTER `stockQty`, +ADD COLUMN `stockUnitName` VARCHAR(255) NULL DEFAULT NULL AFTER `stockUnit`;