From 87380663f01b2a578269c70e3b0aed8d31686898 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 3 Feb 2026 10:27:37 +0800 Subject: [PATCH] updated job order relate --- .../service/JobOrderBomMaterialService.kt | 9 +- .../jobOrder/service/JobOrderService.kt | 38 +++- .../entity/projections/ProductProcessInfo.kt | 4 +- .../service/ProductProcessService.kt | 209 +++++++----------- 4 files changed, 123 insertions(+), 137 deletions(-) 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 4ac9a8a..30ed86f 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 @@ -28,13 +28,14 @@ open class JobOrderBomMaterialService( val proportion = (jo.reqQty ?: zero).divide(jo.bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP) val jobmRequests = jo.bom?.bomMaterials?.map { bm -> - val salesUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } - + // val salesUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } + val stockUnit = bm.item?.id?.let { itemUomService.findStockUnitByItemId(it) } CreateJobOrderBomMaterialRequest( joId = joId, itemId = bm.item?.id, - reqQty = (bm.qty?.times(proportion) ?: zero).setScale(0,RoundingMode.CEILING), - uomId = salesUnit?.uom?.id + //reqQty = (bm.qty?.times(proportion) ?: zero).setScale(0,RoundingMode.CEILING), + reqQty = (bm.saleQty?.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 b5b57a3..636ae65 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 @@ -66,6 +66,8 @@ import com.ffii.fpsms.modules.productProcess.entity.ProductProcessRepository import java.math.RoundingMode import java.time.LocalDate import java.time.LocalDateTime +import com.ffii.fpsms.modules.master.entity.BomMaterialRepository +import com.ffii.fpsms.modules.master.service.ItemUomService @Service open class JobOrderService( val jobOrderRepository: JobOrderRepository, @@ -85,7 +87,9 @@ open class JobOrderService( val inventoryRepository: InventoryRepository, val stockInLineRepository: StockInLineRepository, val productProcessRepository: ProductProcessRepository, - val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository + val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository, + val bomMaterialRepository: BomMaterialRepository, + val itemUomService: ItemUomService ) { open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes { @@ -418,7 +422,8 @@ open class JobOrderService( status = JobOrderStatus.PENDING } jobOrderRepository.save(jo) - + val bom = jo.bom + /* val pols = jo.jobms.filter { it.item?.type != "CMB" && it.item?.type != "consumables" && @@ -431,6 +436,35 @@ open class JobOrderService( uomId = it.uom?.id, ) } + */ + val pols = jo.jobms.filter { jobm -> + + val itemType = jobm.item?.type?.lowercase() + itemType != "cmb" && + itemType != "consumables" && + itemType != "consumable" && + itemType != "nm" && + jobm.item?.isFee != true + }.map { jobm -> + val itemId = jobm.item?.id ?: 0L + + // ✅ 从 BOM Material 获取 saleQty 和 salesUnit(库存单位) + val bomMaterial = bom?.id?.let { bomId -> + bomMaterialRepository.findByBomIdAndItemId(bomId, itemId) + } + + // ✅ 使用 stockReqQty (bomMaterial.saleQty) 和 stockUom (bomMaterial.salesUnit) + val stockReqQty = jobm.reqQty ?: bomMaterial?.saleQty ?: BigDecimal.ZERO + val stockUomId = bomMaterial?.salesUnit?.id + ?: itemUomService.findStockUnitByItemId(itemId)?.uom?.id // Fallback: 从 Item 获取库存单位 + ?: jobm.uom?.id // 最后的 fallback + + SavePickOrderLineRequest( + itemId = itemId, + qty = stockReqQty, // ✅ 使用库存单位数量 + uomId = stockUomId, // ✅ 使用库存单位 + ) + } val po = SavePickOrderRequest( joId = jo.id, type = PickOrderType.JOB_ORDER, diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt index 83287c1..f43dfa5 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt @@ -96,12 +96,12 @@ data class jobOrderLineInfo( val itemName: String?, val type: String?, - val reqQty: Int?, + val reqQty: Double?, val baseReqQty: Int?, - val stockReqQty: Int?, val stockQty: Int?, + val stockReqQty: Double?, val baseStockQty: Int?, val reqUom: String?, diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt index f4612b2..859c088 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt @@ -685,94 +685,90 @@ val sufficientStockQty = bomMaterials bomMaterialRepository.findByBomIdAndItemId(bomId, itemId) } - // Get req UOM from bomMaterial (or fallback to line.uom from JobOrderBomMaterial) - val reqUomId = bomMaterial?.uom?.id ?: line.uom?.id ?: 0L + // ✅ 使用 JobOrderBomMaterial.reqQty(已按比例调整) + // line.reqQty = bm.saleQty * proportion(库存单位,已按比例调整) + val actualReqQty = line.reqQty ?: BigDecimal.ZERO + + // ✅ 获取 req UOM - 对于 reqQty,使用 bomMaterial.uom(BOM 的 UOM) + // ✅ 对于 stockReqQty,使用 line.uom(库存单位,已按比例调整) + val reqUomId = bomMaterial?.uom?.id ?: line.uom?.id ?: 0L // BOM 的 UOM + val stockReqUomId = line.uom?.id ?: bomMaterial?.salesUnit?.id ?: 0L // 库存单位 UOM + val reqUom = reqUomId.takeIf { it > 0 }?.let { uomConversionRepository.findByIdAndDeletedFalse(it) } val uomName = reqUom?.udfudesc val shortUom = reqUom?.udfShortDesc + + val stockReqUom = stockReqUomId.takeIf { it > 0 }?.let { uomConversionRepository.findByIdAndDeletedFalse(it) } + val stockUomName = stockReqUom?.udfudesc + val stockUom = stockUomId?.takeIf { it > 0 }?.let { uomConversionRepository.findByIdAndDeletedFalse(it) } - val stockUomName = stockUom?.udfudesc + val stockUomNameForStock = stockUom?.udfudesc println("=== Quantity Calculation for Item: ${line.item?.code} (id=$itemId) ===") - println("Original reqQty: ${line.reqQty} in UOM: $uomName (id=$reqUomId)") - println("Original stockQty: $stockQtyValue in UOM: $stockUomName (id=$stockUomId)") + println("JobOrderBomMaterial reqQty (adjusted, stock unit): $actualReqQty in UOM: ${stockReqUom?.udfudesc} (id=$stockReqUomId)") + println("BomMaterial qty (base): ${bomMaterial?.qty}, saleQty (base): ${bomMaterial?.saleQty}") + println("Original stockQty: $stockQtyValue in UOM: $stockUomNameForStock (id=$stockUomId)") - // Convert reqQty to base unit - val baseReqQtyResult = if (reqUomId > 0) { + val jobOrder = jobOrderRepository.findById(joid).orElse(null) + val jobOrderReqQty = jobOrder?.reqQty + val bomOutputQty = jobOrder?.bom?.outputQty + val proportion = if (jobOrderReqQty != null && bomOutputQty != null && bomOutputQty > BigDecimal.ZERO) { + jobOrderReqQty.divide(bomOutputQty, 5, RoundingMode.HALF_UP) + } else { + BigDecimal.ONE + } + + // ✅ reqQty 使用 bomMaterial.qty * proportion(BOM 单位) + val reqQtyInBomUnit = (bomMaterial?.qty?.times(proportion) ?: BigDecimal.ZERO) + + // ✅ stockReqQty 使用 bomMaterial.saleQty * proportion(库存单位,已按比例调整) + val stockReqQtyInStockUnit = (bomMaterial?.saleQty?.times(proportion) ?: BigDecimal.ZERO) + + // ✅ Convert reqQty (BOM unit) to base unit + val baseReqQtyResult = if (reqUomId > 0 && reqQtyInBomUnit > BigDecimal.ZERO) { try { - // First check if ItemUom exists for this item and UomConversion val sourceItemUom = itemUomRepository.findFirstByItemIdAndUomIdAndDeletedIsFalse(itemId, reqUomId) val targetItemUom = itemUomService.findBaseUnitByItemId(itemId) - println("Converting reqQty: ${line.reqQty} from UOM id=$reqUomId to baseUnit") - println(" Source ItemUom - ratioN: ${sourceItemUom?.ratioN}, ratioD: ${sourceItemUom?.ratioD}") - println(" Target ItemUom (baseUnit) - ratioN: ${targetItemUom?.ratioN}, ratioD: ${targetItemUom?.ratioD}") - println(" Base Unit UOM id: ${targetItemUom?.uom?.id}") + println("Converting reqQty (BOM unit): $reqQtyInBomUnit from UOM id=$reqUomId to baseUnit") - // Try conversion first - it may have special case handling try { val result = itemUomService.convertUomByItem( ConvertUomByItemRequest( itemId = itemId, - qty = line.reqQty ?: BigDecimal.ZERO, + qty = reqQtyInBomUnit, // BOM 单位数量 uomId = reqUomId, targetUnit = "baseUnit" ) ) - println("Converted reqQty result: ${result.newQty} in base UOM: ${result.udfudesc}") + println("Converted baseReqQty result: ${result.newQty} in base UOM: ${result.udfudesc}") result } catch (e: IllegalArgumentException) { - // If conversion fails, check if it's because sourceItemUom is missing if (sourceItemUom == null) { - println("WARNING: No ItemUom found for itemId=$itemId, uomId=$reqUomId (UomConversion id). Cannot convert reqQty.") - println(" This means the item doesn't have this UOM configured in item_uom table.") - println(" Calculation: Cannot calculate - missing Source ItemUom with ratioN and ratioD") + println("WARNING: No ItemUom found for itemId=$itemId, uomId=$reqUomId. Cannot convert baseReqQty.") null } else { - // Re-throw if it's a different error throw e } } } catch (e: Exception) { - println("Error converting reqQty: ${e.message}") + println("Error converting baseReqQty: ${e.message}") e.printStackTrace() null } } else { - println("reqUomId is 0 or invalid, skipping reqQty conversion") + println("reqUomId is 0 or reqQtyInBomUnit is 0, skipping baseReqQty conversion") null } val baseReqQty = baseReqQtyResult?.newQty?.toInt() ?: 0 - // Get base unit UOM from the base unit ItemUom (same for both req and stock) val baseUnitItemUom = itemUomService.findBaseUnitByItemId(itemId) val baseUomName = baseUnitItemUom?.uom?.udfudesc ?: baseReqQtyResult?.udfudesc val baseShortUom = baseUnitItemUom?.uom?.udfShortDesc ?: baseReqQtyResult?.udfShortDesc - - // Convert stockQty to base unit (using stockUomId from inventory lot lines) + + // Convert stockQty to base unit val baseStockQtyResult = if (stockUomId != null && stockUomId > 0) { try { - // Get source and target ItemUom to show ratioN/ratioD - val sourceItemUom = itemUomRepository.findFirstByItemIdAndUomIdAndDeletedIsFalse(itemId, stockUomId) - val targetItemUom = itemUomService.findBaseUnitByItemId(itemId) - - println("Converting stockQty: $stockQtyValue from UOM id=$stockUomId to baseUnit") - println(" Source ItemUom - ratioN: ${sourceItemUom?.ratioN}, ratioD: ${sourceItemUom?.ratioD}") - println(" Target ItemUom (baseUnit) - ratioN: ${targetItemUom?.ratioN}, ratioD: ${targetItemUom?.ratioD}") - - if (sourceItemUom != null && targetItemUom != null) { - val sourceRatioN = sourceItemUom.ratioN ?: BigDecimal.ONE - val sourceRatioD = sourceItemUom.ratioD ?: BigDecimal.ONE - val targetRatioN = targetItemUom.ratioN ?: BigDecimal.ONE - val targetRatioD = targetItemUom.ratioD ?: BigDecimal.ONE - - val baseQty = stockQtyValue.multiply(sourceRatioN).divide(sourceRatioD, 2, RoundingMode.UP) - val finalQty = baseQty.multiply(targetRatioD).divide(targetRatioN, 2, RoundingMode.UP) - - println(" Calculation: baseQty = $stockQtyValue * $sourceRatioN / $sourceRatioD = $baseQty") - println(" Calculation: finalQty = $baseQty * $targetRatioD / $targetRatioN = $finalQty") - } - val result = itemUomService.convertUomByItem( ConvertUomByItemRequest( itemId = itemId, @@ -781,99 +777,43 @@ val sufficientStockQty = bomMaterials targetUnit = "baseUnit" ) ) - println("Converted stockQty result: ${result.newQty} in base UOM: ${result.udfudesc}") result } catch (e: Exception) { println("Error converting stockQty: ${e.message}") - e.printStackTrace() null } } else { - println("stockUomId is null or invalid, skipping stockQty conversion") null } val baseStockQty = baseStockQtyResult?.newQty?.toInt() ?: 0 - // Use the same baseUomName for stockBaseUom since it's the same base unit val baseStockUomName = baseUomName val baseStockShortUom = baseShortUom - println("Final values - reqQty: ${line.reqQty?.toInt()}, baseReqQty: $baseReqQty, stockQty: $stockQty, baseStockQty: $baseStockQty") - - val reqStockQtyResult = if (reqUomId > 0 && stockUomId != null && stockUomId > 0 && reqUomId != stockUomId) { + // ✅ 计算 baseStockReqQty(stockReqQty 转换为 base unit) + val baseStockReqQtyResult = if (stockReqUomId > 0 && stockReqQtyInStockUnit > BigDecimal.ZERO) { try { - // Convert reqQty from reqUomId to stockUomId via baseUnit - // First convert to baseUnit - val baseReqQtyForStock = if (baseReqQtyResult != null) { - baseReqQtyResult.newQty - } else { - // If baseReqQty conversion failed, try again - try { - itemUomService.convertUomByItem( - ConvertUomByItemRequest( - itemId = itemId, - qty = line.reqQty ?: BigDecimal.ZERO, - uomId = reqUomId, - targetUnit = "baseUnit" - ) - ).newQty - } catch (e: Exception) { - println("Error converting reqQty to baseUnit for stock conversion: ${e.message}") - null - } - } - - if (baseReqQtyForStock != null) { - // Now convert from baseUnit to stockUomId - val sourceItemUom = itemUomService.findBaseUnitByItemId(itemId) - val targetItemUom = itemUomRepository.findFirstByItemIdAndUomIdAndDeletedIsFalse(itemId, stockUomId) - - if (sourceItemUom != null && targetItemUom != null) { - val sourceRatioN = sourceItemUom.ratioN ?: BigDecimal.ONE - val sourceRatioD = sourceItemUom.ratioD ?: BigDecimal.ONE - val targetRatioN = targetItemUom.ratioN ?: BigDecimal.ONE - val targetRatioD = targetItemUom.ratioD ?: BigDecimal.ONE - - // Convert base to stock: baseQty * targetRatioD / targetRatioN - val stockQtyDecimal = baseReqQtyForStock.multiply(targetRatioD).divide(targetRatioN, 2, RoundingMode.UP) - val stockQty = stockQtyDecimal.setScale(0, RoundingMode.UP) // Round up to integer - val stockUom = uomConversionRepository.findByIdAndDeletedFalse(stockUomId) - - println("Converting reqQty to stock unit: ${line.reqQty} (UOM id=$reqUomId) -> $stockQty (UOM id=$stockUomId)") - - ConvertUomByItemResponse( - newQty = stockQty, - udfudesc = stockUom?.udfudesc, - udfShortDesc = stockUom?.udfShortDesc - ) - } else { - println("WARNING: Cannot convert reqQty to stock unit - missing ItemUom (sourceItemUom=${sourceItemUom != null}, targetItemUom=${targetItemUom != null})") - null - } - } else { - null - } + val result = itemUomService.convertUomByItem( + ConvertUomByItemRequest( + itemId = itemId, + qty = stockReqQtyInStockUnit, // 库存单位数量 + uomId = stockReqUomId, + targetUnit = "baseUnit" + ) + ) + result } catch (e: Exception) { - println("Error converting reqQty to stock unit: ${e.message}") - e.printStackTrace() + println("Error converting stockReqQty to base unit: ${e.message}") null } - } else if (reqUomId > 0 && stockUomId != null && stockUomId > 0 && reqUomId == stockUomId) { - // If reqUomId and stockUomId are the same, no conversion needed - val stockUom = uomConversionRepository.findByIdAndDeletedFalse(stockUomId) - ConvertUomByItemResponse( - newQty = line.reqQty ?: BigDecimal.ZERO, - udfudesc = stockUom?.udfudesc, - udfShortDesc = stockUom?.udfShortDesc - ) } else { - println("Cannot convert reqQty to stock unit - reqUomId=$reqUomId, stockUomId=$stockUomId") null } - val reqStockQty = reqStockQtyResult?.newQty?.toInt() ?: 0 + val baseStockReqQty = baseStockReqQtyResult?.newQty?.toInt() ?: 0 + + println("Final values - reqQty (BOM unit): $reqQtyInBomUnit, stockReqQty (stock unit): $stockReqQtyInStockUnit, baseReqQty: $baseReqQty, baseStockReqQty: $baseStockReqQty, stockQty: $stockQty, baseStockQty: $baseStockQty") - println("Final values - reqQty: ${line.reqQty?.toInt()}, baseReqQty: $baseReqQty, stockQty: $stockQty, baseStockQty: $baseStockQty, reqStockQty: $reqStockQty") // Find BomProcessMaterial val bomProcessMaterial = bomMaterial?.id?.let { bomMaterialId -> bomProcessIds.firstNotNullOfOrNull { bomProcessId -> @@ -882,34 +822,45 @@ val sufficientStockQty = bomMaterials } // Calculate availableStatus using base quantities - val availableStatus = if (baseStockQty >= baseReqQty) { + val availableStatus = if (baseStockQty >= baseStockReqQty) { "available" } else { "insufficient" } - println("Available status: $availableStatus (baseStockQty=$baseStockQty >= baseReqQty=$baseReqQty: ${baseStockQty >= baseReqQty})") + println("Available status: $availableStatus (baseStockQty=$baseStockQty >= baseStockReqQty=$baseStockReqQty)") println("=== End Quantity Calculation ===\n") - + + val decimalUomIds = setOf(784L, 786L) // 需要保留小数的 UOM ID(如千克) + jobOrderLineInfo( id = line.id ?: 0, itemId = itemId, itemCode = line.item?.code ?: "", itemName = line.item?.name ?: "", - - - reqQty = line.reqQty?.toInt() ?: 0, - baseReqQty = baseReqQty, - // Add this field if not exists + + // ✅ reqQty:使用 bomMaterial.qty * proportion(BOM 单位,已按比例调整) + reqQty = if (reqUomId in decimalUomIds) { + reqQtyInBomUnit.toDouble() + } else { + reqQtyInBomUnit.setScale(0, RoundingMode.HALF_UP).toDouble() + }, + baseReqQty = baseReqQty, + stockQty = stockQty, - stockReqQty = reqStockQty, - baseStockQty = baseStockQty, // Add this field if not exists + // ✅ stockReqQty:使用 bomMaterial.saleQty * proportion(库存单位,已按比例调整) + stockReqQty = if (stockReqUomId in decimalUomIds) { + stockReqQtyInStockUnit.toDouble() + } else { + stockReqQtyInStockUnit.setScale(0, RoundingMode.HALF_UP).toDouble() + }, + baseStockQty = baseStockQty, + reqUom = uomName ?: "", reqBaseUom = baseUomName ?: "", stockUom = stockUomName ?: "", stockBaseUom = baseUomName ?: "", - - + type = line.item?.type ?: "", availableStatus = availableStatus, bomProcessId = bomProcessMaterial?.bomProcess?.id ?: 0,