package com.ffii.fpsms.modules.jobOrder.service

import com.ffii.core.exception.BadRequestException
import com.ffii.core.response.RecordsRes
import com.ffii.core.utils.CanonPrinterUtil
import com.ffii.core.utils.GsonUtils
import com.ffii.core.utils.PdfUtils
import com.ffii.core.utils.QrCodeUtil
import com.ffii.core.utils.ZebraPrinterUtil
import com.ffii.fpsms.modules.common.SecurityUtils
import com.ffii.fpsms.modules.jobOrder.entity.JobOrder
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository
import com.ffii.fpsms.modules.master.service.BomService
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderLineRequest
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderRequest
import com.ffii.fpsms.modules.user.service.UserService

import com.google.gson.reflect.TypeToken
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

import java.math.BigDecimal
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository
import java.time.format.DateTimeFormatter
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository
import com.ffii.fpsms.modules.jobOrder.web.model.*
import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest
import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest
import com.ffii.fpsms.modules.master.service.PrinterService
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.entity.StockOut
import com.ffii.fpsms.modules.stock.entity.StockOutLine
import com.ffii.fpsms.modules.stock.web.model.StockOutStatus
import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPolRequest
import net.sf.jasperreports.engine.JasperCompileManager
import net.sf.jasperreports.engine.JasperExportManager
import net.sf.jasperreports.engine.JasperPrint
import org.springframework.core.io.ClassPathResource
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobTypeResponse
import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.service.QrContent
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.math.exp
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
import com.ffii.fpsms.modules.master.web.models.ConvertUomByItemRequest
import com.ffii.fpsms.modules.stock.service.StockInLineService
import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest
import com.ffii.fpsms.modules.master.print.A4PrintDriverRegistry
@Service
open class JobOrderService(
    val jobOrderRepository: JobOrderRepository,
    val bomService: BomService,
    val userService: UserService,
    val productionScheduleLineRepository: ProductionScheduleLineRepository,
    val pickOrderService: PickOrderService,
    val joPickOrderService: JoPickOrderService,
    val pickOrderRepository: PickOrderRepository,
    val pickOrderLineRepository: PickOrderLineRepository,
    val suggestedPickLotService: SuggestedPickLotService,
    val inventoryLotLineRepository: InventoryLotLineRepository,
    val stockOutRepository: StockOutRepository,
    val stockOutLineRepository: StockOutLIneRepository,
    private val printerService: PrinterService,
    val jobTypeRepository: JobTypeRepository,
    val inventoryRepository: InventoryRepository,
    val stockInLineRepository: StockInLineRepository,
    val stockInLineService: StockInLineService,
    val productProcessRepository: ProductProcessRepository,
    val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository,
    val bomMaterialRepository: BomMaterialRepository,
    val itemUomService: ItemUomService

) {

    open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> {
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);
        println("allJobOrdersByPage")
        println(request)
        val response = jobOrderRepository.findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc(
            code = request.code ?: "",
            bomName = request.itemName ?: "",
            pageable = pageable
        )   

        val planStartFrom = request.planStart
        val planStartTo = request.planStartTo
        val records = response.content
            .filter {
//                println("from ($planStartFrom):" +(planStartFrom == null || (it.planStart != null && (planStartFrom.isEqual(it.planStart) || planStartFrom.isBefore(it.planStart)))))
//                println("to: ($planStartTo)" +(planStartTo == null || (it.planStart != null && (planStartTo.isEqual(it.planStart) || planStartTo.isAfter(it.planStart)))))
               (planStartFrom == null || (it.planStart != null && (planStartFrom.isEqual(it.planStart) || planStartFrom.isBefore(it.planStart)))) &&
                (planStartTo == null || (it.planStart != null && (planStartTo.isEqual(it.planStart) || planStartTo.isAfter(it.planStart))))
            }

        val total = response.totalElements
        return RecordsRes<JobOrderInfo>(records, total.toInt());
    }
    open fun allJobOrdersByPageWithTypeName(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfoWithTypeName> {
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);
        println("allJobOrdersByPageWithTypeName")
        println(request)
        
        // 修复：使用包含日期过滤的查询方法，在数据库层面先过滤再分页
        val response = jobOrderRepository.findJobOrderInfoWithDateFilter(
            code = if (request.code.isNullOrBlank()) null else request.code,
            bomName = if (request.itemName.isNullOrBlank()) null else request.itemName,
            planStartFrom = request.planStart,
            planStartTo = request.planStartTo,
            joSearchStatus = request.joSearchStatus?.trim()?.lowercase().orEmpty().ifBlank { "all" },
            pageable = pageable
        )   
        
        val jobTypeIds = response.content.mapNotNull { it.jobTypeId }.distinct()
        val jobTypes = if (jobTypeIds.isNotEmpty()) {
            jobTypeRepository.findAllById(jobTypeIds).associateBy { it.id }
        } else {
            emptyMap()
        }
        
        // 获取所有 JobOrder IDs，用于批量加载 jobms
        val jobOrderIds = response.content.map { it.id }.distinct()
        val jobOrdersMap = if (jobOrderIds.isNotEmpty()) {
            jobOrderRepository.findAllById(jobOrderIds).associateBy { it.id }
        } else {
            emptyMap()
        }

        val processByJobOrderId = if (jobOrderIds.isNotEmpty()) {
            productProcessRepository.findByJobOrder_IdInAndDeletedIsFalse(jobOrderIds).groupBy { it.jobOrder?.id }
        } else {
            emptyMap()
        }
        
        // 获取所有涉及的 itemIds，用于批量加载 inventory
        val allItemIds = jobOrdersMap.values
            .flatMap { it.jobms }
            .mapNotNull { it.item?.id }
            .distinct()
        
        val inventoriesMap = if (allItemIds.isNotEmpty()) {
            inventoryRepository.findInventoryInfoByItemIdInAndDeletedIsFalse(allItemIds)
                .associateBy { it.itemId }
        } else {
            emptyMap()
        }
        
        // 移除内存中的日期过滤，因为已经在数据库层面过滤了
        val records = response.content
            .map { info ->
                val jobOrder = jobOrdersMap[info.id]
                
                // 计算 sufficientCount 和 insufficientCount
                val (sufficientCount, insufficientCount) = if (jobOrder != null) {
                    calculateStockCounts(jobOrder, inventoriesMap)
                } else {

                    Pair(null, null)
                }
                
                JobOrderInfoWithTypeName(
                    id = info.id,
                    code = info.code,
                    itemCode = info.itemCode,
                    itemName = info.itemName,
                    name = info.name,
                    reqQty = info.reqQty,
                    item = info.item,
                    stockInLineId = info.stockInLineId,
                    stockInLineStatus = info.stockInLineStatus,
                    sufficientCount = sufficientCount,
                    insufficientCount = insufficientCount,
                    silHandlerId = info.silHandlerId,
                    planStart = info.planStart,
                    productionPriority = info.productionPriority,
                    status = run {
                        val processStatuses = processByJobOrderId[info.id]
                            .orEmpty()
                            .map { it.status.value.lowercase() }
                        val hasStartedProcess = processStatuses.any { it == "in_progress" || it == "processing" }
                        val joStatus = info.status.trim().lowercase()
                        if (hasStartedProcess && joStatus == "pending") "processing" else info.status
                    },
                    jobTypeId = info.jobTypeId,
                    jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name },
                    lotNo = info.lotNo
                )
            }
            .filter { info ->
                // Filter by jobTypeName if provided
                val jobTypeNameMatch = request.jobTypeName == null || 
                    request.jobTypeName.isBlank() || 
                    info.jobTypeName?.equals(request.jobTypeName, ignoreCase = true) == true ||
                    info.jobTypeName?.contains(request.jobTypeName, ignoreCase = true) == true
                jobTypeNameMatch
            }.sortedByDescending { it.productionPriority }

        // 修复：使用 response.totalElements，这是过滤后的总数
        val total = response.totalElements
        return RecordsRes<JobOrderInfoWithTypeName>(records, total.toInt());
    }
    
    private fun calculateStockCounts(
        jobOrder: JobOrder,
        inventoriesMap: Map<Long?, com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo>
    ): Pair<Int, Int> {
        // 过滤掉 consumables 和 CMB 类型的物料
        val nonConsumablesJobms = jobOrder.jobms.filter { jobm ->
            val itemType = jobm.item?.type?.lowercase()
            itemType != "consumables" && itemType != "consumable" && itemType != "cmb" && itemType != "nm"
        }
        
        if (nonConsumablesJobms.isEmpty()) {
            return Pair(0, 0)
        }
        
        var sufficientCount = 0
        var insufficientCount = 0
        
        //println("=== JobOrderService.calculateStockCounts for JobOrder: ${jobOrder.code} ===")
        
        nonConsumablesJobms.forEach { jobm ->
            val itemId = jobm.item?.id
            val itemCode = jobm.item?.code ?: "N/A"
            val itemName = jobm.item?.name ?: "N/A"
            
            if (itemId != null) {
                val inventory = inventoriesMap[itemId]
                val availableQty = if (inventory != null) {
                    inventory.availableQty ?: (
                        (inventory.onHandQty ?: BigDecimal.ZERO) - 
                        (inventory.onHoldQty ?: BigDecimal.ZERO) - 
                        (inventory.unavailableQty ?: BigDecimal.ZERO)
                    )
                } else {
                    BigDecimal.ZERO
                }
                
                // ✅ 获取 reqQty 和 availableQty 的单位信息
                val reqQty = jobm.reqQty ?: BigDecimal.ZERO
                val reqUomId = jobm.uom?.id ?: 0L
                val reqUomName = jobm.uom?.udfudesc ?: "N/A"
                
                // ✅ 修复：使用 itemUomService 获取 stockUomId（与 ProductProcessService 保持一致）
                val stockUnitItemUom = itemUomService.findStockUnitByItemId(itemId)
                val stockUomId = stockUnitItemUom?.uom?.id ?: 0L
                val stockUomName = stockUnitItemUom?.uom?.udfudesc ?: "N/A"
                
                // ✅ 转换为 base unit 进行比较（与 ProductProcessService 保持一致）
                val baseReqQtyResult = if (reqUomId > 0 && reqQty > BigDecimal.ZERO) {
                    try {
                        itemUomService.convertUomByItem(
                            ConvertUomByItemRequest(
                                itemId = itemId,
                                qty = reqQty,
                                uomId = reqUomId,
                                targetUnit = "baseUnit"
                            )
                        )
                    } catch (e: Exception) {
                        println("Error converting reqQty to base unit: ${e.message}")
                        null
                    }
                } else {
                    null
                }
                
                val baseAvailableQtyResult = if (stockUomId > 0 && availableQty > BigDecimal.ZERO) {
                    try {
                        itemUomService.convertUomByItem(
                            ConvertUomByItemRequest(
                                itemId = itemId,
                                qty = availableQty,
                                uomId = stockUomId,
                                targetUnit = "baseUnit"
                            )
                        )
                    } catch (e: Exception) {
                        println("Error converting availableQty to base unit: ${e.message}")
                        null
                    }
                } else {
                    null
                }
                
                val baseReqQty = baseReqQtyResult?.newQty ?: BigDecimal.ZERO
                val baseAvailableQty = baseAvailableQtyResult?.newQty ?: BigDecimal.ZERO
                val baseUomName = baseReqQtyResult?.udfudesc ?: baseAvailableQtyResult?.udfudesc ?: "N/A"
                
                // ✅ 使用 base unit 进行比较
                if (baseAvailableQty >= baseReqQty) {
                    sufficientCount++
                    //println("✅ SUFFICIENT - Item: $itemCode ($itemName) - reqQty: $reqQty ($reqUomName) = $baseReqQty ($baseUomName), availableQty: $availableQty ($stockUomName) = $baseAvailableQty ($baseUomName)")
                } else {
                    insufficientCount++
                    //println("❌ INSUFFICIENT - Item: $itemCode ($itemName) - reqQty: $reqQty ($reqUomName) = $baseReqQty ($baseUomName), availableQty: $availableQty ($stockUomName) = $baseAvailableQty ($baseUomName)")
                }
            } else {
                // 如果没有 itemId，视为不足
                insufficientCount++
                //println("❌ INSUFFICIENT - Item: $itemCode ($itemName) - No itemId")
            }
        }
        
        //println("=== Result: sufficient=$sufficientCount, insufficient=$insufficientCount ===")
        
        return Pair(sufficientCount, insufficientCount)
    }
    open fun jobOrderDetail(id: Long): JobOrderDetail {
        val sqlResult = jobOrderRepository.findJobOrderDetailById(id) ?: throw NoSuchElementException();

        val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type
        val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(it, type) }
        return JobOrderDetail(
            id = sqlResult.id,
            code = sqlResult.code,
            itemCode = sqlResult.itemCode,
            name = sqlResult.name,
            reqQty = sqlResult.reqQty,
            uom = sqlResult.uom,
            bomId = sqlResult.bomId,
            pickLines = jsonResult,
            status = sqlResult.status,
            shortUom = sqlResult.shortUom
        )
    }

    open fun jobOrderDetailByPsId(prodScheduleLineId: Long): JobOrderDetail {
        val sqlResult = jobOrderRepository.findJobOrderByProdScheduleLineId(prodScheduleLineId) ?: throw NoSuchElementException("Job Order not found: $prodScheduleLineId");

        val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type
        val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(it, type) }
        return JobOrderDetail(
            id = sqlResult.id,
            code = sqlResult.code,
            itemCode = sqlResult.itemCode,
            name = sqlResult.name,
            reqQty = sqlResult.reqQty,
            uom = sqlResult.uom,
            bomId = sqlResult.bomId,
            pickLines = jsonResult,
            status = sqlResult.status,
            shortUom = sqlResult.shortUom
        )
    }

    open fun jobOrderDetailByProdScheduleLineId(prodScheduleLineId: Long): JobOrderDetail =
        jobOrderDetailByPsId(prodScheduleLineId)

    open fun jobOrderDetailByItemId(itemId: Long): JobOrderDetail {
        val sqlResult = jobOrderRepository.findJobOrderByItemId(itemId) ?: throw NoSuchElementException("Job Order not found with itemId: $itemId");

        val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type
        val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(it, type) }
        return JobOrderDetail(
            id = sqlResult.id,
            code = sqlResult.code,
            itemCode = sqlResult.itemCode,
            name = sqlResult.name,
            bomId = sqlResult.bomId,
            reqQty = sqlResult.reqQty,
            uom = sqlResult.uom,
            pickLines = jsonResult,
            status = sqlResult.status,
            shortUom = sqlResult.shortUom
        )
    }


    open fun jobOrderDetailByCode(code: String): JobOrderDetail {
        val sqlResult = jobOrderRepository.findJobOrderDetailByCode(code) ?: throw NoSuchElementException("Job Order not found: $code");

        val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type
        val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(it, type) }
        return JobOrderDetail(
            id = sqlResult.id,
            code = sqlResult.code,
            itemCode = sqlResult.itemCode,
            name = sqlResult.name,
            bomId = sqlResult.bomId,
            reqQty = sqlResult.reqQty,
            uom = sqlResult.uom,
            pickLines = jsonResult,
            status = sqlResult.status,
            shortUom = sqlResult.shortUom
        )
    }

    open fun assignJobNo(planStart: LocalDateTime): String {
        val planStartDate = planStart.toLocalDate()
        val suffixFormat = "%03d"
        val pattern = "yyMMdd"
        val formatter = DateTimeFormatter.ofPattern(pattern)
    
        val prefix = "JO"
        val midfix = planStart.format(formatter)   // 👈 用 planStart，而不是 LocalDate.now()
        val suffix = String.format(suffixFormat, 1)
    
        val latestCode = jobOrderRepository.findLatestCodeByPrefix("${prefix}-${midfix}")
        if (latestCode != null) {
            val splitLatestCode = latestCode.split("-")
            if (splitLatestCode.size > 2) {
                val latestNo = splitLatestCode[2].toInt()
                return listOf(prefix, midfix, String.format(suffixFormat, latestNo + 1)).joinToString("-")
            }
        }
    
        return listOf(prefix, midfix, suffix).joinToString("-")
    }
    open fun createJobOrder(request: CreateJobOrderRequest): MessageResponse {
        val jo = JobOrder()
        val bom = request.bomId?.let { bomService.findById(it) }
        val approver = request.approverId?.let { userService.find(it).getOrNull() } ?: SecurityUtils.getUser().getOrNull()
        val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() }
        val code = assignJobNo(request.planStart ?: LocalDateTime.now())
        val status = JobOrderStatus.entries.find { it.value == request.status }
        
        //val jobTypeId = jobTypeRepository.findByName(request.jobType).orElse(null)?.id
        val jobTypeId = request.jobTypeId
        jo.apply {
            this.code = code
            this.bom = bom
            //TODO: planStart & planEnd
            planStart = request.planStart ?: LocalDateTime.now()
            planEnd = request.planEnd ?: LocalDateTime.now()
            reqQty = request.reqQty
            this.status = status
            type = request.type
            this.approver = approver
            this.prodScheduleLine = prodScheduleLine
            this.jobTypeId = jobTypeId
            this.isHidden = false
        }

        val savedJo = jobOrderRepository.saveAndFlush(jo);
        savedJo.bom?.item?.id?.let { fgItemId ->
            stockInLineService.create(
                SaveStockInLineRequest(
                    itemId = fgItemId,
                    acceptedQty = BigDecimal.ZERO,
                    acceptQty = null,
                    jobOrderId = savedJo.id,
                    status = "pending",
                    expiryDate = null,
                    productLotNo = null,
                    productionDate = null,
                    receiptDate = null,
                )
            )
        }
        return MessageResponse(
            id = savedJo.id,
            name = null,
            code = savedJo.code,
            type = savedJo.type,
            message = "Success",
            errorPosition = null
        )
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun updateJobOrder(request: JobOrderUpdateRequest): MessageResponse {
        val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
        val newStatus: JobOrderStatus = when (request.status) { //TODO: improve if can
            "pending" -> { JobOrderStatus.PENDING }
            "processing" -> { JobOrderStatus.PROCESSING }
            "planning" -> { JobOrderStatus.PLANNING }
            "storing" -> { JobOrderStatus.STORING }
            "completed" -> { JobOrderStatus.COMPLETED }
            "packaging" -> { JobOrderStatus.PACKAGING }
            else -> { throw BadRequestException() }
        }

        jo.apply {
            status = newStatus
        }
        val savedJo = jobOrderRepository.save(jo)

        return MessageResponse(
            id = savedJo.id,
            code = savedJo.code,
            name = savedJo.bom?.name,
            type = null,
            message = "Job Order status updated to " + savedJo.status,
            errorPosition = null,
            entity = mapOf("status" to jo.status?.value)
        )
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun setJobOrderHidden(request: SetJobOrderHiddenRequest): MessageResponse {
        val jo = jobOrderRepository.findById(request.id).getOrNull() ?: throw NoSuchElementException()
        jo.isHidden = request.hidden
        val savedJo = jobOrderRepository.save(jo)
        return MessageResponse(
            id = savedJo.id,
            code = savedJo.code,
            name = savedJo.bom?.name,
            type = null,
            message = "Success",
            errorPosition = null,
        )
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse {
        return releaseJobOrderInternal(request.id, deferStockOutUntilAssign = false)
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun releaseJobOrderForWorkbench(request: JobOrderCommonActionRequest): MessageResponse {
        return releaseJobOrderInternal(request.id, deferStockOutUntilAssign = true)
    }

    private fun releaseJobOrderInternal(
        jobOrderId: Long,
        deferStockOutUntilAssign: Boolean,
    ): MessageResponse {
        val jo = jobOrderId.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
        jo.apply {
            status = JobOrderStatus.PENDING
        }
        jobOrderRepository.save(jo)
        val bom = jo.bom
        /* 
        val pols = jo.jobms.filter { 
            it.item?.type != "CMB" && 
            it.item?.type != "consumables" && 
            it.item?.type != "NM" &&
            it.item?.isFee != true  // 添加：跳过 isFee 为 true 的物料
        }.map {
            SavePickOrderLineRequest(
                itemId = it.item?.id,
                qty = it.reqQty ?: BigDecimal.ZERO,
                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?.stockQty ?: BigDecimal.ZERO
        val stockUomId = bomMaterial?.stockUnit?.toLong()
            ?: itemUomService.findStockUnitByItemId(itemId)?.uom?.id  // Fallback: 从 Item 获取库存单位
            ?: jobm.uom?.id // 最后的 fallback
        
        SavePickOrderLineRequest(
            itemId = itemId,
            qty = stockReqQty,  // ✅ 使用库存单位数量
            uomId = stockUomId,  // ✅ 使用库存单位
        )
    }
    if (pols.isEmpty()) {
      
        jo.status = JobOrderStatus.PROCESSING 
        jobOrderRepository.save(jo)
        return MessageResponse(
            id = jo.id,
            code = jo.code,
            name = jo.bom?.name,
            type = null,
            message = null,
            errorPosition = null,
            entity = mapOf("status" to jo.status?.value)
        )
    }
        val po = SavePickOrderRequest(
            joId = jo.id,
            type = PickOrderType.JOB_ORDER,
            targetDate = jo.planStart?.toLocalDate() ?: LocalDate.now(),
            pickOrderLine = pols
        )

        val createdPickOrder = pickOrderService.create(po)
        val consoCode = pickOrderService.assignConsoCode()
        val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)

        if (pickOrderEntity != null) {
            pickOrderEntity.consoCode = consoCode
            pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
            pickOrderRepository.saveAndFlush(pickOrderEntity)
            if (!deferStockOutUntilAssign) {
                //  Create stock out record and pre-create stock out lines
                val stockOut = StockOut().apply {
                    this.type = "job"
                    this.consoPickOrderCode = consoCode
                    this.status = StockOutStatus.PENDING.status
                    this.handler = SecurityUtils.getUser().getOrNull()?.id
                }
                val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

            //  添加 suggested pick lots 创建逻辑
            val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
            println("DEBUG: pickOrderLines=${lines.map { it.id to it.item?.code }}")
            if (lines.isNotEmpty()) {
                val suggestions = suggestedPickLotService.suggestionForPickOrderLinesForJobOrder(
                    SuggestedPickLotForPolRequest(pickOrderLines = lines)
                )
                println("DEBUG: suggestions.size=${suggestions.suggestedList.size}")
                    suggestions.suggestedList.forEach { s ->
                        println(
                            "DEBUG: suggestion polId=${s.pickOrderLine?.id}, lotLineId=${s.suggestedLotLine?.id}, " +
                            "lotNo=${s.suggestedLotLine?.inventoryLot?.lotNo}, qty=${s.qty}, pickSuggested=${s.pickSuggested}"
                        )
                    }
                val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
                println("DEBUG: saved suggestions size=${saveSuggestedPickLots.size}")
                saveSuggestedPickLots.forEach { s ->
                    println(
                        "DEBUG: saved polId=${s.pickOrderLine?.id}, lotLineId=${s.suggestedLotLine?.id}, " +
                        "lotNo=${s.suggestedLotLine?.inventoryLot?.lotNo}, qty=${s.qty}"
                    )
                }
                //  Hold inventory quantities
                val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
                    saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
                )

                saveSuggestedPickLots.forEach { lot ->
                    if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) {
                        val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine)
                        if (lineIndex >= 0) {
                            inventoryLotLines[lineIndex].holdQty =
                                (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO)
                        }
                    }
                }
                inventoryLotLineRepository.saveAll(inventoryLotLines)

            
                //  Pre-create stock out lines for suggested lots
                saveSuggestedPickLots.forEach { lot ->
                    val polId = lot.pickOrderLine?.id
                    val illId = lot.suggestedLotLine?.id
                    if (polId != null && illId != null) {
                        val existingLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
                        if (existingLines.isEmpty()) {
                            val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null)
                            val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null)

                            if (pickOrderLine != null && inventoryLotLine != null) {
                                val line = StockOutLine().apply {
                                    this.stockOut = savedStockOut
                                    this.pickOrderLine = pickOrderLine
                                    this.inventoryLotLine = inventoryLotLine
                                    this.item = pickOrderLine.item
                                    this.status = StockOutLineStatus.PENDING.status
                                    this.qty = 0.0
                                    this.type = "NOR"
                                }
                                stockOutLineRepository.save(line)
                            }
                        }
                    }
                }
            }
            } else {
                println("JO workbench release defer enabled: skip legacy suggestion/hold/stockout prebuild. pickOrderId=${pickOrderEntity.id}")
            }
        }
        val itemIds = pols.mapNotNull { it.itemId }
        if (itemIds.isNotEmpty()) {
            joPickOrderService.createJoPickOrdersForPickOrder(createdPickOrder.id!!, itemIds)
            joPickOrderService.createJoPickOrderRecordsForPickOrder(createdPickOrder.id!!, itemIds)
        }


        return MessageResponse(
            id = jo.id,
            code = jo.code,
            name = jo.bom?.name,
            type = null,
            message = null,
            errorPosition = null,
            entity = mapOf("status" to jo.status?.value)
        )
    }


    @Transactional(rollbackFor = [Exception::class])
    open fun startJobOrder(request: JobOrderCommonActionRequest): MessageResponse {
        val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
        jo.apply {
            status = JobOrderStatus.PROCESSING
        }
        jobOrderRepository.save(jo)

        return MessageResponse(
            id = jo.id,
            code = jo.code,
            name = jo.bom?.name,
            type = null,
            message = null,
            errorPosition = null,
            entity = mapOf("status" to jo.status?.value)
        )
    }

    //Pick Record
    private fun validatePickRecordFloor(floor: String?): String {
        val normalizedFloor = floor?.trim()?.uppercase()
            ?: throw BadRequestException("floor is required for pick record print")
        if (normalizedFloor !in setOf("2F", "3F", "4F", "ALL")) {
            throw BadRequestException("floor must be one of 2F, 3F, 4F, ALL")
        }
        return normalizedFloor
    }

    private fun validatePlasticBoxCartonQty(qty: Int?): Int {
        val value = qty ?: throw BadRequestException("plasticBoxCartonQty is required")
        if (value < 1) {
            throw BadRequestException("plasticBoxCartonQty must be at least 1")
        }
        return value
    }

    private data class AllFloorsPlasticBoxCartonQty(
        val qty2f: Int,
        val qty3f: Int,
        val qty4f: Int,
        val sum: Int,
    )

    private fun normalizeFloorPlasticBoxCartonQty(qty: Int?): Int {
        if (qty == null) return 0
        if (qty < 0) {
            throw BadRequestException("plastic box carton qty cannot be negative")
        }
        return qty
    }

    private fun resolveAllFloorsPlasticBoxCartonQty(
        qty2f: Int?,
        qty3f: Int?,
        qty4f: Int?,
    ): AllFloorsPlasticBoxCartonQty {
        val q2 = normalizeFloorPlasticBoxCartonQty(qty2f)
        val q3 = normalizeFloorPlasticBoxCartonQty(qty3f)
        val q4 = normalizeFloorPlasticBoxCartonQty(qty4f)
        return AllFloorsPlasticBoxCartonQty(q2, q3, q4, q2 + q3 + q4)
    }

    private fun updatePickOrderPlasticBoxCartonQty(pickOrderId: Long, floor: String, qty: Int) {
        val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: return
        when (floor) {
            "2F" -> pickOrder.plasticBoxCartonQty2f = qty
            "3F" -> pickOrder.plasticBoxCartonQty3f = qty
            "4F" -> pickOrder.plasticBoxCartonQty4f = qty
        }
        pickOrderRepository.save(pickOrder)
    }

    private fun persistAllFloorsPlasticBoxCartonQty(pickOrderId: Long, all: AllFloorsPlasticBoxCartonQty) {
        updatePickOrderPlasticBoxCartonQty(pickOrderId, "2F", all.qty2f)
        updatePickOrderPlasticBoxCartonQty(pickOrderId, "3F", all.qty3f)
        updatePickOrderPlasticBoxCartonQty(pickOrderId, "4F", all.qty4f)
    }

    open fun getPickRecordPlasticBoxCartonQty(pickOrderId: Long): PickRecordPlasticBoxCartonQtyResponse {
        val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow {
            NoSuchElementException("Pick order not found with ID: $pickOrderId")
        }
        return PickRecordPlasticBoxCartonQtyResponse(
            plasticBoxCartonQty2f = pickOrder.plasticBoxCartonQty2f,
            plasticBoxCartonQty3f = pickOrder.plasticBoxCartonQty3f,
            plasticBoxCartonQty4f = pickOrder.plasticBoxCartonQty4f,
        )
    }

    private fun resolvePlasticBoxCartonQtyForPickRecord(request: ExportPickRecordRequest): Int {
        val floor = validatePickRecordFloor(request.floor)
        return if (floor == "ALL") {
            val all = resolveAllFloorsPlasticBoxCartonQty(
                request.plasticBoxCartonQty2f,
                request.plasticBoxCartonQty3f,
                request.plasticBoxCartonQty4f,
            )
            persistAllFloorsPlasticBoxCartonQty(request.pickOrderIds, all)
            all.sum
        } else {
            request.plasticBoxCartonQty
                ?: throw BadRequestException("plasticBoxCartonQty is required")
        }
    }

    @Throws(IOException::class)
    @Transactional
    open fun exportPickRecord(request: ExportPickRecordRequest): Map<String, Any> {
        val PICKRECORD_PDF = "PickRecord/PickRecordPDF.jrxml"
        val resource = ClassPathResource(PICKRECORD_PDF)
        if(!resource.exists()){
            throw FileNotFoundException("Report file not found: $PICKRECORD_PDF")
        }
        val inputStream = resource.inputStream
        val pickRecord = JasperCompileManager.compileReport(inputStream)
        val pickRecordInfo = joPickOrderService.getJobOrderPickOrderLotDetails(request.pickOrderIds).toMutableList()


        val fields = mutableListOf<MutableMap<String, Any>>()
        val params = mutableMapOf<String, Any>()
        val normalizedFloor = request.floor?.trim()?.uppercase()
        val selectedFloor = normalizedFloor?.takeIf { it in setOf("2F", "3F", "4F") }
        val floorLabel = when (normalizedFloor) {
            "2F", "3F", "4F" -> normalizedFloor.replace("F", "/F")
            "ALL" -> "2/F+3/F+4/F+N/A"
            else -> ""
        }

        val sortedPickRecordInfo = pickRecordInfo
            .filter { info ->
                val route = (info["routerRoute"] as? String)?.trim().orEmpty()
                if (selectedFloor == null) {
                    true
                } else {
                    route.isBlank() || route.equals("N/A", ignoreCase = true) || route.startsWith(selectedFloor, ignoreCase = true)
                }
            }
            .sortedBy { info ->
            (info["routerIndex"] as? Int) ?: Int.MAX_VALUE
        }

        sortedPickRecordInfo.forEachIndexed { index, info ->
            val field = mutableMapOf<String, Any>()

            val itemName = info["itemName"] as? String?: "N/A"
            val itemCode = info["itemCode"] as? String?: "N/A"
            val lotNo = info["lotNo"] as? String?: "N/A"
            val uomShortDesc = info["uomShortDesc"] as? String?: "N/A"
            val uomDesc = info["uomDesc"] as? String?: "N/A"
            val route = info["routerRoute"] as? String?: "N/A"
            val actualPickQty = info["actualPickQty"] as? BigDecimal?: "N/A"

            field["sequenceNumber"] = (index + 1).toString()
            field["itemName"] = itemName
            field["itemNo"] = itemCode
            field["lotNo"] = lotNo
            field["shortName"] = uomShortDesc
            field["uom"] = uomDesc
            field["route"] = route
            field["qty"] = actualPickQty.toString()

            fields.add(field)
        }

        params["JobOrderCode"] = pickRecordInfo.firstOrNull()?.get("jobOrderCode") as? String ?: "N/A"
        params["ProductionDate"] = pickRecordInfo.firstOrNull()?.get("jobOrderPlanStart") as? String ?: "N/A"
        val reqQty = pickRecordInfo.firstOrNull()?.get("jobOrderReqQty") as? BigDecimal
        params["RequiredQuantity"] = reqQty?.toString() ?: "N/A"
        params["FGCode"] = pickRecordInfo.firstOrNull()?.get("fgCode") as? String ?: "N/A"
        params["FGName"] = pickRecordInfo.firstOrNull()?.get("fgName") as? String ?: "N/A"
        params["Floor"] = floorLabel

        /* Debug UOM information
        val bomItemUomIdRaw = pickRecordInfo.firstOrNull()?.get("bomItemUomId")
        val bomItemId = pickRecordInfo.firstOrNull()?.get("bomItemId")
        val uomCode = pickRecordInfo.firstOrNull()?.get("uomCode") as? String
        val uomDesc = pickRecordInfo.firstOrNull()?.get("uomDesc") as? String
        val uomShortDesc = pickRecordInfo.firstOrNull()?.get("uomShortDesc") as? String
        val unit = pickRecordInfo.firstOrNull()?.get("unit") as? String

        println("Debug - UOM Info:")
        println("bomItemId: $bomItemId")
        println("bomItemUomId: $bomItemUomIdRaw")
        println("uomCode: $uomCode")
        println("uomDesc: $uomDesc")
        println("uomShortDesc: $uomShortDesc")
        println("unit (from BOM): $unit")*/

        params["unit"] = pickRecordInfo.firstOrNull()?.get("uomConversionDesc") as? String ?: "N/A"
        val plasticBoxCartonQtyForPdf = resolvePlasticBoxCartonQtyForPickRecord(request)
        params["PlasticBoxCartonQty"] = plasticBoxCartonQtyForPdf.toString()

        val pickOrderCode = pickRecordInfo.firstOrNull()?.get("pickOrderCode") as? String ?: "unknown"
        return mapOf(
            "report" to PdfUtils.fillReport(pickRecord, fields, params),
            "filename" to pickOrderCode
        )
    }

    //Print Pick Record
    @Transactional
    open fun printPickRecord(request: PrintPickRecordRequest){
        val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer")
        val floor = validatePickRecordFloor(request.floor)
        val exportRequest = if (floor == "ALL") {
            ExportPickRecordRequest(
                pickOrderIds = request.pickOrderId,
                floor = request.floor,
                plasticBoxCartonQty2f = request.plasticBoxCartonQty2f,
                plasticBoxCartonQty3f = request.plasticBoxCartonQty3f,
                plasticBoxCartonQty4f = request.plasticBoxCartonQty4f,
            )
        } else {
            val plasticBoxCartonQty = validatePlasticBoxCartonQty(request.plasticBoxCartonQty)
            updatePickOrderPlasticBoxCartonQty(request.pickOrderId, floor, plasticBoxCartonQty)
            ExportPickRecordRequest(
                pickOrderIds = request.pickOrderId,
                floor = request.floor,
                plasticBoxCartonQty = plasticBoxCartonQty,
            )
        }

        val pdf = exportPickRecord(exportRequest)

        val jasperPrint = pdf["report"] as JasperPrint

        val tempPdfFile = File.createTempFile("print_job_",".pdf")

        try{
            JasperExportManager.exportReportToPdfFile(jasperPrint,tempPdfFile.absolutePath)

            val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty

            printer.ip?.let { ip ->
                val port = printer.port ?: 9100
                val driver = A4PrintDriverRegistry.getDriver(printer.brand)
                driver.print(tempPdfFile, ip, port, printQty)
            }
        } finally {
            //tempPdfFile.delete()
        }
    }

    //FG Stock In Label
    @Throws(IOException::class)
    @Transactional
    open fun exportFGStockInLabel(request: ExportFGStockInLabelRequest): Map<String, Any> {
        val FGSTOCKINLABEL_PDF = "FGStockInLabel/FGStockInLabel.jrxml"
        val resource = ClassPathResource(FGSTOCKINLABEL_PDF)
        if(!resource.exists()){
            throw FileNotFoundException("Report file not found: $FGSTOCKINLABEL_PDF")
        }
        val inputStream = resource.inputStream
        val fgStockInLabel = JasperCompileManager.compileReport(inputStream)

        val params = mutableMapOf<String, Any>()

        //GET STOCK IN LINE FROM REQUEST
        val stockInLineId = request.stockInLineId
            ?: throw BadRequestException("stockInLineId is required")
        val stockInLineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(stockInLineId)

        //LINK TO JOB ORDER
        val jobOrderId = stockInLineInfo.jobOrderId
            ?: throw BadRequestException("StockInLine does not have an associated JobOrder")
        val jobOrder = jobOrderRepository.findById(jobOrderId).orElseThrow()

        //HANDLE UOM
        val uom = stockInLineInfo.uom
        val uomDesc = uom?.udfudesc ?: ""
        val uomShortDesc = uom?.udfShortDesc ?: ""

        //HANDLE DATES
        val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
        val productionDate = stockInLineInfo.productionDate?.toLocalDate()?.format(dateFormatter) ?: ""
        val expiryDate = stockInLineInfo.expiryDate?.format(dateFormatter) ?: ""

        params["jobOrderCode"] = jobOrder.code ?: "N/A"
        params["finishedGoodItemCode"] = stockInLineInfo.itemNo ?: "N/A"
        params["finishedGoodItemName"] = stockInLineInfo.itemName ?: "N/A"
        params["finishedGoodLotNo"] = stockInLineInfo.lotNo ?: "N/A"
        params["FGStockInQty"] = stockInLineInfo.acceptedQty?.toString() ?: "0"
        params["productionDate"] = productionDate
        params["expiryDate"] = expiryDate
        params["uom"] = uomDesc
        params["shortName"] = uomShortDesc

        val qrContent = QrContent(stockInLineInfo.itemId, stockInLineId)
        val qrCodeContent = Json.encodeToString(qrContent)
        val qrCodeImage = QrCodeUtil.generateQRCodeImage(qrCodeContent)
        params["qrCode"] = qrCodeImage

        val fields = mutableListOf<MutableMap<String, Any>>()

        val filename = params["jobOrderCode"] as String
        return mapOf(
            "report" to PdfUtils.fillReport(fgStockInLabel, fields, params),
            "filename" to filename
        )
    }


    @Transactional
    open fun printFGStockInLabel(request: PrintFGStockInLabelRequest){
        val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer")

        val pdf = exportFGStockInLabel(
            ExportFGStockInLabelRequest(
                stockInLineId = request.stockInLineId
            )
        )

        val jasperPrint = pdf["report"] as JasperPrint

        val tempPdfFile = File.createTempFile("print_job_",".pdf")

        try{
            JasperExportManager.exportReportToPdfFile(jasperPrint,tempPdfFile.absolutePath)
            val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty
            printer.ip?.let { ip ->
                printer.port?.let { port ->
                    ZebraPrinterUtil.printPdfToZebra(
                        tempPdfFile,
                        ip,
                        port,
                        printQty,
                        ZebraPrinterUtil.PrintDirection.ROTATED,
                        printer.dpi
                    )
                }
            }

            println("Test PDF saved to: ${tempPdfFile.absolutePath}")

        } finally {
            //tempPdfFile.delete
        }
    }

    open fun getAllJobTypes(): List<JobTypeResponse> {
        return jobTypeRepository.findAll().map { JobTypeResponse(it.id, it.name) }
    }
    open fun updateJoPlanStart(request: UpdateJoPlanStartRequest): MessageResponse {
        val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
        
        // 将 String 转换为 LocalDateTime
        val planStartDateTime = if (request.planStart.isNotBlank()) {
            try {
                // 支持 ISO 格式: "YYYY-MM-DDTHH:mm:ss" 或 "YYYY-MM-DD"
                if (request.planStart.contains("T")) {
                    LocalDateTime.parse(request.planStart)
                } else {
                    // 如果只有日期，设置为当天的 00:00:00
                    LocalDate.parse(request.planStart).atStartOfDay()
                }
            } catch (e: Exception) {
                throw BadRequestException("Invalid date format: ${request.planStart}")
            }
        } else {
            null
        }
        
        jo.apply {
            planStart = planStartDateTime
        }
        jobOrderRepository.save(jo)
        val planStartDate = planStartDateTime?.toLocalDate()
        val productProcesses = productProcessRepository.findByJobOrder_Id(jo.id)
        productProcesses.forEach { productProcess ->
            productProcess.apply {
                date = planStartDate
            }
            productProcessRepository.save(productProcess)
        }
        return MessageResponse(
            id = jo.id, 
            code = jo.code, 
            name = jo.bom?.name, 
            type = null, 
            message = null, 
            errorPosition = null, 
            entity = mapOf("status" to jo.status?.value)
        )
    }
    /*
    open fun checkJobOrderCreated(request: CheckJobOrderCreatedRequest): CheckJobOrderCreatedResponse {
        val jobOrders = jobOrderRepository.findByBomIdAndDate(request.bomId, request.date)
        return CheckJobOrderCreatedResponse(
            isCreated = jobOrders.isNotEmpty(),
            jobOrders = jobOrders.map { JobOrderBasicInfoResponse(it.id, it.code, it.bom?.name, it.reqQty) }
        )
    }
*/
open fun updateJoReqQty(request: UpdateJoReqQtyRequest): MessageResponse {
    val jobOrder = jobOrderRepository.findById(request.id).orElse(null)
        ?: throw NoSuchElementException("Job Order not found with id: ${request.id}")
    
    val newReqQty = BigDecimal.valueOf(request.reqQty.toLong())
    
    // 更新 JobOrder 的 reqQty
    jobOrder.reqQty = newReqQty
    jobOrderRepository.save(jobOrder)
    
    // 更新相关的 JobOrderBomMaterial 的 reqQty（根据新的比例重新计算）
    val bom = jobOrder.bom
    if (bom != null && bom.outputQty != null && bom.outputQty!! > BigDecimal.ZERO) {
        val proportion = newReqQty.divide(bom.outputQty!!, 5, RoundingMode.HALF_UP)
        val jobOrderBomMaterials = jobOrderBomMaterialRepository.findAllByJobOrderId(jobOrder.id)
        
        jobOrderBomMaterials.forEach { jobm ->
            // 找到对应的 BOM Material
            val bomMaterial = bom.bomMaterials?.find { it.item?.id == jobm.item?.id }
            if (bomMaterial != null && bomMaterial.qty != null) {
                jobm.reqQty = (bomMaterial.qty!!.times(proportion)).setScale(0, RoundingMode.CEILING)
                jobOrderBomMaterialRepository.save(jobm)
            }
        }
    }
    
    return MessageResponse(
        id = jobOrder.id,
        code = jobOrder.code,
        name = jobOrder.bom?.name,
        type = "success",
        message = "Job Order ReqQty Updated",
        errorPosition = null,
        entity = mapOf("reqQty" to request.reqQty)
    )
}
}