|
|
|
@@ -28,7 +28,7 @@ import com.ffii.fpsms.modules.master.entity.Process |
|
|
|
import com.ffii.fpsms.modules.master.entity.Equipment |
|
|
|
import com.ffii.fpsms.modules.master.entity.BomProcess |
|
|
|
import com.ffii.fpsms.modules.master.entity.Bom |
|
|
|
|
|
|
|
import com.ffii.fpsms.modules.master.service.ItemUomService |
|
|
|
import com.ffii.fpsms.modules.master.web.models.MessageResponse |
|
|
|
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository |
|
|
|
import com.ffii.fpsms.modules.master.service.ProductionScheduleService |
|
|
|
@@ -48,6 +48,8 @@ import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository |
|
|
|
import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus |
|
|
|
import com.ffii.fpsms.modules.master.entity.UomConversionRepository |
|
|
|
import com.ffii.fpsms.modules.master.entity.ItemUomRespository |
|
|
|
import com.ffii.fpsms.modules.master.web.models.* |
|
|
|
import java.math.RoundingMode |
|
|
|
@Service |
|
|
|
@Transactional |
|
|
|
open class ProductProcessService( |
|
|
|
@@ -71,8 +73,9 @@ open class ProductProcessService( |
|
|
|
private val jobTypeRepository: JobTypeRepository, |
|
|
|
private val pickOrderRepository: PickOrderRepository, |
|
|
|
private val joPickOrderRepository: JoPickOrderRepository, |
|
|
|
private val uomConversionRepository: UomConversionRepository, |
|
|
|
private val itemUomRepository: ItemUomRespository, |
|
|
|
private val uomConversionRepository: UomConversionRepository, |
|
|
|
private val itemUomService: ItemUomService |
|
|
|
) { |
|
|
|
|
|
|
|
open fun findAll(pageable: Pageable): Page<ProductProcess> { |
|
|
|
@@ -507,32 +510,44 @@ open class ProductProcessService( |
|
|
|
.minus(lot.outQty ?: BigDecimal.ZERO) |
|
|
|
.minus(lot.holdQty ?: BigDecimal.ZERO) |
|
|
|
} |
|
|
|
itemId to stockQty |
|
|
|
}.toMap() |
|
|
|
|
|
|
|
// calculate statistics |
|
|
|
val totalStockQty = stockQtyMap.values.sumOf { it.toInt() } |
|
|
|
val insufficientStockQty = bomMaterials |
|
|
|
.filter { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
val stockQty = stockQtyMap[itemId] ?: BigDecimal.ZERO |
|
|
|
stockQty < (material.reqQty ?: BigDecimal.ZERO) |
|
|
|
} |
|
|
|
.sumOf { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
stockQtyMap[itemId]?.toInt() ?: 0 |
|
|
|
} |
|
|
|
val sufficientStockQty = bomMaterials |
|
|
|
.filter { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
val stockQty = stockQtyMap[itemId] ?: BigDecimal.ZERO |
|
|
|
stockQty >= (material.reqQty ?: BigDecimal.ZERO) |
|
|
|
// Get the stockUom from the first available lot line |
|
|
|
// stockItemUomId is the ItemUom id, we need to get the UomConversion id from it |
|
|
|
val stockItemUom = availableLots.firstOrNull()?.stockUom |
|
|
|
if (stockItemUom == null) { |
|
|
|
println("WARNING: No stockUom found for itemId=$itemId in available lots") |
|
|
|
} |
|
|
|
.sumOf { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
stockQtyMap[itemId]?.toInt() ?: 0 |
|
|
|
val stockUomId = stockItemUom?.uom?.id |
|
|
|
if (stockUomId == null && stockItemUom != null) { |
|
|
|
println("WARNING: stockItemUom.id=${stockItemUom.id} exists but has no uom (UomConversion)") |
|
|
|
} |
|
|
|
println("DEBUG itemId=$itemId: stockItemUom.id=${stockItemUom?.id}, stockUom.uom.id=$stockUomId, availableLots count=${availableLots.size}") |
|
|
|
itemId to Pair(stockQty, stockUomId) |
|
|
|
}.toMap() |
|
|
|
|
|
|
|
// calculate statistics |
|
|
|
val totalStockQty = stockQtyMap.values.sumOf { (qty, _) -> qty.toInt() } |
|
|
|
val insufficientStockQty = bomMaterials |
|
|
|
.filter { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
val (stockQty, _) = stockQtyMap[itemId] ?: (BigDecimal.ZERO to null) |
|
|
|
stockQty < (material.reqQty ?: BigDecimal.ZERO) |
|
|
|
} |
|
|
|
.sumOf { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
val (stockQty, _) = stockQtyMap[itemId] ?: (BigDecimal.ZERO to null) |
|
|
|
stockQty.toInt() |
|
|
|
} |
|
|
|
val sufficientStockQty = bomMaterials |
|
|
|
.filter { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
val (stockQty, _) = stockQtyMap[itemId] ?: (BigDecimal.ZERO to null) |
|
|
|
stockQty >= (material.reqQty ?: BigDecimal.ZERO) |
|
|
|
} |
|
|
|
.sumOf { material -> |
|
|
|
val itemId = material.item?.id ?: 0L |
|
|
|
val (stockQty, _) = stockQtyMap[itemId] ?: (BigDecimal.ZERO to null) |
|
|
|
stockQty.toInt() |
|
|
|
} |
|
|
|
// 获取 productionPriority |
|
|
|
val itemId = jobOrder?.bom?.item?.id |
|
|
|
val planEndDate = jobOrder?.planEnd?.toLocalDate() |
|
|
|
@@ -645,44 +660,176 @@ open class ProductProcessService( |
|
|
|
}.sortedBy { it.seqNo }, |
|
|
|
jobOrderLines = bomMaterials.map { line -> |
|
|
|
val itemId = line.item?.id ?: 0L |
|
|
|
val stockQty = stockQtyMap[itemId]?.toInt() ?: 0 |
|
|
|
// 首先通过 bomId 和 itemId 找到 BomMaterial |
|
|
|
val (stockQtyValue, stockUomId) = stockQtyMap[itemId] ?: (BigDecimal.ZERO to null) |
|
|
|
val stockQty = stockQtyValue.toInt() |
|
|
|
|
|
|
|
// Find BomMaterial |
|
|
|
val bomMaterial = bom?.id?.let { bomId -> |
|
|
|
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 |
|
|
|
val reqUom = reqUomId.takeIf { it > 0 }?.let { uomConversionRepository.findByIdAndDeletedFalse(it) } |
|
|
|
val uomName = reqUom?.udfudesc |
|
|
|
val shortUom = reqUom?.udfShortDesc |
|
|
|
val stockUom = stockUomId?.takeIf { it > 0 }?.let { uomConversionRepository.findByIdAndDeletedFalse(it) } |
|
|
|
val stockUomName = 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)") |
|
|
|
|
|
|
|
// Convert reqQty to base unit |
|
|
|
val baseReqQtyResult = if (reqUomId > 0) { |
|
|
|
try { |
|
|
|
// First check if ItemUom exists for this item and UomConversion |
|
|
|
val sourceItemUom = itemUomRepository.findByItemIdAndUomIdAndDeletedIsFalse(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}") |
|
|
|
|
|
|
|
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") |
|
|
|
null |
|
|
|
} else if (targetItemUom == null) { |
|
|
|
println("WARNING: No baseUnit ItemUom found for itemId=$itemId. Cannot convert reqQty.") |
|
|
|
println(" Calculation: Cannot calculate - missing Target ItemUom (baseUnit)") |
|
|
|
null |
|
|
|
} else { |
|
|
|
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 = (line.reqQty ?: BigDecimal.ZERO).multiply(sourceRatioN).divide(sourceRatioD, 2, RoundingMode.HALF_UP) |
|
|
|
val finalQty = baseQty.multiply(targetRatioD).divide(targetRatioN, 2, RoundingMode.HALF_UP) |
|
|
|
|
|
|
|
println(" Calculation: baseQty = ${line.reqQty} * $sourceRatioN / $sourceRatioD = $baseQty") |
|
|
|
println(" Calculation: finalQty = $baseQty * $targetRatioD / $targetRatioN = $finalQty") |
|
|
|
|
|
|
|
val result = itemUomService.convertUomByItem( |
|
|
|
ConvertUomByItemRequest( |
|
|
|
itemId = itemId, |
|
|
|
qty = line.reqQty ?: BigDecimal.ZERO, |
|
|
|
uomId = reqUomId, |
|
|
|
targetUnit = "baseUnit" |
|
|
|
) |
|
|
|
) |
|
|
|
println("Converted reqQty result: ${result.newQty} in base UOM: ${result.udfudesc}") |
|
|
|
result |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
println("Error converting reqQty: ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
null |
|
|
|
} |
|
|
|
} else { |
|
|
|
println("reqUomId is 0 or invalid, skipping reqQty 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) |
|
|
|
val baseStockQtyResult = if (stockUomId != null && stockUomId > 0) { |
|
|
|
try { |
|
|
|
// Get source and target ItemUom to show ratioN/ratioD |
|
|
|
val sourceItemUom = itemUomRepository.findByItemIdAndUomIdAndDeletedIsFalse(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.HALF_UP) |
|
|
|
val finalQty = baseQty.multiply(targetRatioD).divide(targetRatioN, 2, RoundingMode.HALF_UP) |
|
|
|
|
|
|
|
println(" Calculation: baseQty = $stockQtyValue * $sourceRatioN / $sourceRatioD = $baseQty") |
|
|
|
println(" Calculation: finalQty = $baseQty * $targetRatioD / $targetRatioN = $finalQty") |
|
|
|
} |
|
|
|
|
|
|
|
val result = itemUomService.convertUomByItem( |
|
|
|
ConvertUomByItemRequest( |
|
|
|
itemId = itemId, |
|
|
|
qty = stockQtyValue, |
|
|
|
uomId = stockUomId, |
|
|
|
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") |
|
|
|
|
|
|
|
|
|
|
|
// 使用 bom_material.uom_name 而不是关联的 uom 对象 |
|
|
|
val uom = uomConversionRepository.findByCodeAndDeletedFalse(bomMaterial?.uomName?:"") |
|
|
|
val uomName = uom?.udfudesc |
|
|
|
val shortUom = uom?.udfudesc |
|
|
|
// 然后遍历 bomProcessIds 找到第一个匹配的 BomProcessMaterial |
|
|
|
// Find BomProcessMaterial |
|
|
|
val bomProcessMaterial = bomMaterial?.id?.let { bomMaterialId -> |
|
|
|
bomProcessIds.firstNotNullOfOrNull { bomProcessId -> |
|
|
|
bomProcessMaterialRepository.findByBomProcessIdAndBomMaterialId(bomProcessId, bomMaterialId) |
|
|
|
} |
|
|
|
} |
|
|
|
// 计算 availableStatus |
|
|
|
val availableStatus = if (stockQty >= (line.reqQty?.toInt() ?: 0)) { |
|
|
|
|
|
|
|
// Calculate availableStatus using base quantities |
|
|
|
val availableStatus = if (baseStockQty >= baseReqQty) { |
|
|
|
"available" |
|
|
|
} else { |
|
|
|
"insufficient" |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println("Available status: $availableStatus (baseStockQty=$baseStockQty >= baseReqQty=$baseReqQty: ${baseStockQty >= baseReqQty})") |
|
|
|
println("=== End Quantity Calculation ===\n") |
|
|
|
|
|
|
|
jobOrderLineInfo( |
|
|
|
id = line.id?:0, |
|
|
|
id = line.id ?: 0, |
|
|
|
itemId = itemId, |
|
|
|
itemCode = line.item?.code?:"", |
|
|
|
itemName = line.item?.name?:"", |
|
|
|
reqQty = line.reqQty?.toInt() ?: 0, |
|
|
|
itemCode = line.item?.code ?: "", |
|
|
|
itemName = line.item?.name ?: "", |
|
|
|
|
|
|
|
|
|
|
|
reqQty = line.reqQty?.toInt() ?: 0, |
|
|
|
baseReqQty = baseReqQty, |
|
|
|
// Add this field if not exists |
|
|
|
stockQty = stockQty, |
|
|
|
baseStockQty = baseStockQty, // Add this field if not exists |
|
|
|
reqUom = uomName ?: "", |
|
|
|
reqBaseUom = baseUomName ?: "", |
|
|
|
stockUom = stockUomName ?: "", |
|
|
|
stockBaseUom = baseUomName ?: "", |
|
|
|
|
|
|
|
|
|
|
|
uom = uomName?:"", |
|
|
|
shortUom = shortUom?:"", |
|
|
|
type = line.item?.type?: "", |
|
|
|
type = line.item?.type ?: "", |
|
|
|
availableStatus = availableStatus, |
|
|
|
bomProcessId = bomProcessMaterial?.bomProcess?.id?:0, |
|
|
|
bomProcessSeqNo = bomProcessMaterial?.bomProcess?.seqNo?:0 |
|
|
|
bomProcessId = bomProcessMaterial?.bomProcess?.id ?: 0, |
|
|
|
bomProcessSeqNo = bomProcessMaterial?.bomProcess?.seqNo ?: 0 |
|
|
|
) |
|
|
|
} |
|
|
|
) |
|
|
|
|