Parcourir la source

update

master
CANCERYS\kw093 il y a 3 semaines
Parent
révision
344041d703
7 fichiers modifiés avec 303 ajouts et 49 suppressions
  1. +4
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ItemUomRespository.kt
  2. +1
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/UomConversionRepository.kt
  3. +57
    -1
      src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt
  4. +1
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/ItemsController.kt
  5. +38
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/models/NewItemRequest.kt
  6. +11
    -2
      src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt
  7. +191
    -44
      src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt

+ 4
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ItemUomRespository.kt Voir le fichier

@@ -20,6 +20,10 @@ interface ItemUomRespository : AbstractRepository<ItemUom, Long> {
fun findByItemIdAndSalesUnitIsTrueAndDeletedIsFalse(itemId: Serializable): ItemUom?
fun findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(itemId: Serializable): ItemUom?

fun findByItemIdAndBaseUnitIsTrueAndDeletedIsFalse(itemId: Serializable): ItemUom?
fun findByItemIdAndPickingUnitIsTrueAndDeletedIsFalse(itemId: Serializable): ItemUom?

fun findByItemM18IdAndPurchaseUnitIsTrueAndDeletedIsFalse(itemM18Id: Long): ItemUom?
fun findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse(itemId: Long): ItemUom?
fun findByItemIdAndUomIdAndDeletedIsFalse(itemId: Long, uomId: Long): ItemUom?
}

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/UomConversionRepository.kt Voir le fichier

@@ -15,4 +15,5 @@ interface UomConversionRepository : AbstractRepository<UomConversion, Long> {
fun findByM18IdAndDeletedFalse(m18Id: Long): UomConversion?;

fun findByLastModifyDateAndM18IdAndDeletedFalse(lastModifyDate: LocalDateTime, m18Id: Long): UomConversion?;
fun findByUdfudescAndDeletedFalse(udfudesc: String): UomConversion?;
}

+ 57
- 1
src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt Voir le fichier

@@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.master.service

import com.ffii.fpsms.modules.master.entity.ItemUom
import com.ffii.fpsms.modules.master.entity.ItemUomRespository
import com.ffii.fpsms.modules.master.web.models.ItemUomRequest
import com.ffii.fpsms.modules.master.web.models.*
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
@@ -44,6 +44,25 @@ open class ItemUomService(
return itemUomRespository.findByItemM18IdAndPurchaseUnitIsTrueAndDeletedIsFalse(m18ItemId)
}

open fun findBaseUnitByItemId(itemId: Long): ItemUom? {
return itemUomRespository.findByItemIdAndBaseUnitIsTrueAndDeletedIsFalse(itemId)
}

open fun findPickingUnitByItemId(itemId: Long): ItemUom? {
return itemUomRespository.findByItemIdAndPickingUnitIsTrueAndDeletedIsFalse(itemId)
}

private fun findTargetItemUom(itemId: Long, targetUnit: String): ItemUom? {
return when (targetUnit.lowercase()) {
"baseunit", "base_unit" -> findBaseUnitByItemId(itemId)
"stockunit", "stock_unit" -> findStockUnitByItemId(itemId)
"purchaseunit", "purchase_unit" -> findPurchaseUnitByItemId(itemId)
"salesunit", "sales_unit" -> findSalesUnitByItemId(itemId)
"pickingunit", "picking_unit" -> findPickingUnitByItemId(itemId)
else -> null
}
}

open fun convertPurchaseQtyToStockQty(itemId: Long, purchaseQty: BigDecimal): BigDecimal {
val purchaseUnit = findPurchaseUnitByItemId(itemId) ?: return purchaseQty;
val stockUnit = findStockUnitByItemId(itemId) ?: return purchaseQty;
@@ -96,4 +115,41 @@ open class ItemUomService(
itemUomRespository.deleteAllByIdIn(deleteIds)
}
}


open fun convertUomByItem(request: ConvertUomByItemRequest): ConvertUomByItemResponse {
// Find source ItemUom by itemId and uomId
val sourceItemUom = itemUomRespository.findByItemIdAndUomIdAndDeletedIsFalse(request.itemId, request.uomId)
?: throw IllegalArgumentException("Source ItemUom not found for itemId=${request.itemId}, uomId=${request.uomId}")

// Find target ItemUom by itemId and targetUnit
val targetItemUom = findTargetItemUom(request.itemId, request.targetUnit)
?: throw IllegalArgumentException("Target ItemUom not found for itemId=${request.itemId}, targetUnit=${request.targetUnit}")

// Convert quantity using ratioN/ratioD via base unit
val one = BigDecimal.ONE
val sourceRatioN = sourceItemUom.ratioN ?: one
val sourceRatioD = sourceItemUom.ratioD ?: one
val targetRatioN = targetItemUom.ratioN ?: one
val targetRatioD = targetItemUom.ratioD ?: one

// Convert source qty to base: qty * ratioN / ratioD
val baseQty = request.qty.multiply(sourceRatioN).divide(sourceRatioD, 2, RoundingMode.HALF_UP)

// Convert base to target: baseQty * ratioD / ratioN
val newQty = baseQty.multiply(targetRatioD).divide(targetRatioN, 2, RoundingMode.HALF_UP)

// Get UomConversion from target ItemUom
val uomConversion = targetItemUom.uom
?: throw IllegalArgumentException("Target UomConversion not found for target ItemUom")

return ConvertUomByItemResponse(
newQty = newQty,
udfudesc = uomConversion.udfudesc,
udfShortDesc = uomConversion.udfShortDesc
)
}


}

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/master/web/ItemsController.kt Voir le fichier

@@ -132,5 +132,5 @@ fun getItemsWithDetailsByPage(request: HttpServletRequest): RecordsRes<Map<Strin

return RecordsRes(paginatedList, fullList.size)
}
}

+ 38
- 1
src/main/java/com/ffii/fpsms/modules/master/web/models/NewItemRequest.kt Voir le fichier

@@ -3,7 +3,7 @@ package com.ffii.fpsms.modules.master.web.models
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import java.time.LocalDateTime
import java.math.BigDecimal
//enum class ItemType(val type: String) {
// MATERIAL("mat"),
// BY_PRODUCT("byp"),
@@ -57,4 +57,41 @@ data class NewItemRequest(
// val uom: List<NewUomRequest>?,
// val weightUnit: List<NewWeightUnitRequest>?,
)
data class UomConversionResponse(
val uomId: Long?,
val uomCode: String?,
val udfudesc: String?,
val udfShortDesc: String?
)
data class ConversionUomRequest(
val itemid: Long?,
val itemcode: String?,
val qty: String?,

val currentUomCode: String?,
val currentUomid: Long?,
val currentUomUdfudesc: String?,

val newUomCode: String?,
val newUomid: Long?,
val newUomUdfudesc: String?

)
data class ConversionUomResponse(
val qty: BigDecimal,
val UomCode: String,
val UomUdfudesc: String,
val udfShortDesc: String
)
data class ConvertUomByItemRequest(
val itemId: Long,
val qty: BigDecimal,
val uomId: Long,
val targetUnit: String
)
data class ConvertUomByItemResponse(
val newQty: BigDecimal,
val udfudesc: String?,
val udfShortDesc: String?
)


+ 11
- 2
src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt Voir le fichier

@@ -93,10 +93,19 @@ data class jobOrderLineInfo(
val itemCode: String?,
val itemName: String?,
val type: String?,

val reqQty: Int?,
val baseReqQty: Int?,

val stockQty: Int?,
val uom: String?,
val shortUom: String?,
val baseStockQty: Int?,

val reqUom: String?,
val reqBaseUom: String?,

val stockUom: String?,
val stockBaseUom: String?,
val availableStatus: String?,
val bomProcessId: Long?,
val bomProcessSeqNo: Long?

+ 191
- 44
src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt Voir le fichier

@@ -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
)
}
)


Chargement…
Annuler
Enregistrer