package com.ffii.fpsms.modules.productProcess.service

import com.ffii.fpsms.modules.productProcess.entity.*
import com.ffii.fpsms.modules.productProcess.entity.projections.ProductProcessInfo
import com.ffii.fpsms.modules.productProcess.entity.projections.ProductProcessLineInfo
import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus
import com.ffii.fpsms.modules.productProcess.web.model.*
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.PageRequest
import com.ffii.fpsms.modules.master.entity.EquipmentDetailRepository
import com.ffii.fpsms.modules.productProcess.entity.projections.jobOrderLineInfo
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import com.ffii.fpsms.modules.user.entity.UserRepository
import com.ffii.fpsms.modules.user.entity.User

import com.ffii.fpsms.modules.master.entity.BomRepository
import com.ffii.fpsms.modules.master.entity.BomProcessRepository
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.entity.ProcessRepository
import com.ffii.fpsms.modules.master.entity.EquipmentRepository
import java.math.BigDecimal
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
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.stock.service.StockInLineService
import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest
import com.ffii.fpsms.modules.master.entity.BomProcessMaterialRepository
import com.ffii.fpsms.modules.master.entity.BomMaterialRepository
import com.ffii.fpsms.modules.productProcess.entity.ProductionProcessIssue
import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import java.time.ZoneOffset
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
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import org.springframework.dao.DataIntegrityViolationException
@Service
@Transactional
open class ProductProcessService(
    private val productProcessRepository: ProductProcessRepository,
    private val productProcessLineRepository: ProductProcessLineRepository,
    private val productionProcessIssueRepository: ProductionProcessIssueRepository,
    private val bomRepository: BomRepository,
    private val bomProcessRepository: BomProcessRepository,
    private val jobOrderRepository: JobOrderRepository,
    private val itemsRepository: ItemsRepository,
    private val userRepository: UserRepository,
    private val processRepository: ProcessRepository,
    private val equipmentRepository: EquipmentRepository,
    private val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val productionScheduleLineRepository: ProductionScheduleLineRepository,
    private val stockInLineService: StockInLineService,
    private val bomProcessMaterialRepository: BomProcessMaterialRepository,
    private val bomMaterialRepository: BomMaterialRepository,
    private val equipmentDetailRepository: EquipmentDetailRepository,
    private val jobTypeRepository: JobTypeRepository,
    private val pickOrderRepository: PickOrderRepository,
    private val joPickOrderRepository: JoPickOrderRepository,
    private val itemUomRepository: ItemUomRespository,
    private val uomConversionRepository: UomConversionRepository,
    private val itemUomService: ItemUomService,
    private val stockInLineRepository: StockInLineRepository
) {

    open fun findAll(pageable: Pageable): Page<ProductProcess> {
        println("   Service: Finding all ProductProcess with page: ${pageable.pageNumber}, size: ${pageable.pageSize}")
        val result = productProcessRepository.findAll(pageable)
        println(" Service: Found ${result.totalElements} records")
        return result
    }

    open fun findById(id: Long): ProductProcess {
        println("    Service: Finding ProductProcess by ID: $id")

        return productProcessRepository.findById(id)
            .orElseThrow { IllegalArgumentException("ProductProcess not found with id: $id") }
    }

    open fun findByCode(code: String): ProductProcess {
        println("    Service: Finding ProductProcess by code: $code")
        return productProcessRepository.findByProductProcessCode(code)
            .orElseThrow { IllegalArgumentException("ProductProcess not found with code: $code") }
    }

    open fun create(request: SaveProductProcessRequest): SaveProductProcessResponse {
        println("🔧 Service: Creating ProductProcess with bomId: ${request.bomId}")

        // 1. 查询 BOM
        val bom = bomRepository.findById(request.bomId)
            .orElseThrow { IllegalArgumentException("BOM not found with id: ${request.bomId}") }

        println("📦 Service: BOM found - name: ${bom.name}, item: ${bom.item?.name ?: "N/A"}")

        // 2. 生成 productProcessCode（格式：PP-20251026-001）
        //val datePrefix = (request.date ?: LocalDate.now()).format(DateTimeFormatter.ofPattern("yyyyMMdd"))
        val datePrefix = (LocalDate.now()).format(DateTimeFormatter.ofPattern("yyyyMMdd"))
        val productProcessCode = generateProductProcessCode(datePrefix, request.bomId!!)
        println("🔢 Service: Generated code: $productProcessCode")

        // 3. 创建 ProductProcess
        val productProcess = ProductProcess().apply {
            this.productProcessCode = productProcessCode
            this.bom = bom
            this.item = bom.item
            this.jobOrder = request.jobOrderId?.let { jobOrderRepository.findById(it).orElse(null) }
            this.status = ProductProcessStatus.PENDING
            //this.date = request.date ?: LocalDate.now()
            this.date = LocalDate.now()
        }

        val savedProcess = productProcessRepository.save(productProcess)
        println(" Service: ProductProcess created with ID: ${savedProcess.id}")

        // 4. 查询 BOM 的所有工序步骤
        val bomProcesses = bomProcessRepository.findByBomIdOrderBySeqNo(request.bomId)
        println("    Service: Found ${bomProcesses.size} BOM processes")

        // 5. 为每个 BOM Process 创建 ProductProcessLine
        bomProcesses.forEachIndexed { index, bomProcess ->  //  修复 forEach
            val line = ProductProcessLine().apply {
                this.productProcess = savedProcess
                this.bomProcess = bomProcess
                this.seqNo = bomProcess.seqNo
                this.name = bomProcess.process?.name
                this.description = bomProcess.description
                this.equipmentType = bomProcess.equipment?.name
                this.isOriginal = true
            }
            productProcessLineRepository.save(line)
            println("➕ Service: Created line ${index + 1} - seq: ${line.seqNo}, name: ${line.name}")
        }

        println(" Service: All ${bomProcesses.size} lines created automatically")

        return SaveProductProcessResponse(
            id = savedProcess.id!!,
            productProcessCode = productProcessCode,
            linesCreated = bomProcesses.size
        )
    }

    private fun generateProductProcessCode(datePrefix: String, bomId: Long): String {
        val searchPattern = "PP-$datePrefix-$bomId-"
        val latestCode = productProcessRepository
            .findTopByProductProcessCodeStartingWithOrderByProductProcessCodeDesc(searchPattern)
            ?.productProcessCode

        val latestNo = latestCode
            ?.substringAfterLast("-", missingDelimiterValue = "")
            ?.toIntOrNull()
            ?: 0

        val nextNumber = (kotlin.math.max(0, latestNo) + 1).toString().padStart(3, '0')
        return "$searchPattern$nextNumber"
    }

    private fun isDuplicateProductProcessCode(e: DataIntegrityViolationException): Boolean {
        val msg = (e.rootCause?.message ?: e.message ?: "").lowercase()
        return msg.contains("duplicate entry") && msg.contains("productprocesscode")
    }

    private inline fun <T> retryOnDuplicateProductProcessCode(maxRetries: Int = 8, block: () -> T): T {
        var last: DataIntegrityViolationException? = null
        repeat(maxRetries) { attempt ->
            try {
                return block()
            } catch (e: DataIntegrityViolationException) {
                if (!isDuplicateProductProcessCode(e)) throw e
                last = e
                println("⚠️ Duplicate productProcessCode, retrying... attempt=${attempt + 1}/$maxRetries")
            }
        }
        throw last ?: IllegalStateException("Duplicate productProcessCode after retries")
    }


    //  添加：查询工序的所有步骤
    open fun getLines(productProcessId: Long): List<ProductProcessLine> {
        println("   Service: Getting lines for process ID: $productProcessId")
        val lines = productProcessLineRepository.findByProductProcess_Id(productProcessId)
        println(" Service: Found ${lines.size} lines")
        return lines
    }

    open fun startProcess(id: Long, operatorId: Long): ProductProcess {
        println("▶️ Service: Starting ProductProcess ID: $id with operatorId: $operatorId")
        val productProcess = findById(id)
        productProcess.status = ProductProcessStatus.IN_PROGRESS
        productProcess.startTime = LocalDateTime.now()
        val saved = productProcessRepository.save(productProcess)
        println(" Service: Process started, status: ${saved.status}")
        return saved
    }

    open fun stopProcess(id: Long, request: StopProcessRequest): ProductionProcessIssue {
        println("⏸️ Service: Stopping ProductProcess ID: $id")
        println("📦 Service: Stop reason: ${request.reason}")

        val productProcess = findById(id)
        productProcess.status = ProductProcessStatus.STOPPED

        val issue = ProductionProcessIssue().apply {
            this.productProcess = productProcess
            this.operatorName = request.operatorName
            this.reason = request.reason
            this.stopTime = LocalDateTime.now()
        }

        productProcessRepository.save(productProcess)
        val savedIssue = productionProcessIssueRepository.save(issue)
        println(" Service: Process stopped, issue ID: ${savedIssue.id}")
        return savedIssue
    }

    open fun resumeProcess(id: Long, issueId: Long): ProductionProcessIssue {
        println("▶️ Service: Resuming ProductProcess ID: $id, issueId: $issueId")

        val productProcess = findById(id)
        productProcess.status = ProductProcessStatus.IN_PROGRESS

        val issue = productionProcessIssueRepository.findById(issueId)
            .orElseThrow { IllegalArgumentException("Issue not found") }

        issue.resumeTime = LocalDateTime.now()
        issue.totalTime = ChronoUnit.MINUTES.between(issue.stopTime, issue.resumeTime).toInt()

        productProcessRepository.save(productProcess)
        val savedIssue = productionProcessIssueRepository.save(issue)
        println(" Service: Process resumed, total stop time: ${savedIssue.totalTime} minutes")
        return savedIssue
    }

    open fun completeProcess(id: Long): ProductProcess {
        println("✔️ Service: Completing ProductProcess ID: $id")
        val productProcess = findById(id)
        productProcess.status = ProductProcessStatus.COMPLETED
        productProcess.endTime = LocalDateTime.now()
        val saved = productProcessRepository.save(productProcess)
        println(" Service: Process completed")
        return saved
    }

    open fun addLine(productProcessId: Long, request: SaveProductProcessLineRequest): SaveProductProcessLineResponse {
        println("➕ Service: Adding line to ProductProcess ID: $productProcessId")
        println("📦 Service: Line data - name: ${request.name}, equipmentType: ${request.equipmentType}")

        val productProcess = findById(productProcessId)

        val line = ProductProcessLine().apply {
            this.productProcess = productProcess
            this.name = request.name
            this.description = request.description
            this.equipmentType = request.equipmentType
            this.isOriginal = false
        }

        val saved = productProcessLineRepository.save(line)
        println(" Service: Line added with ID: ${saved.id}")
        return SaveProductProcessLineResponse(saved.id!!)
    }

    open fun updateLineOutput(lineId: Long, request: UpdateLineOutputRequest): ProductProcessLine {
        println("📊 Service: Updating line output for line ID: $lineId")

        val line = productProcessLineRepository.findById(lineId)
            .orElseThrow { IllegalArgumentException("Line not found") }

        line.apply {
            outputFromProcessQty = request.outputQty
            outputFromProcessUom = request.outputUom
            defectQty = request.defectQty
            defectUom = request.defectUom
            scrapQty = request.scrapQty
            scrapUom = request.scrapUom
            byproductName = request.byproductName
            byproductQty = request.byproductQty
            byproductUom = request.byproductUom
            endTime = LocalDateTime.now()
        }

        val saved = productProcessLineRepository.save(line)
        println(" Service: Line output updated")
        return saved
    }

    open fun getIssues(productProcessId: Long): List<ProductionProcessIssue> {
        println("   Service: Getting issues for ProductProcess ID: $productProcessId")
        val issues = productionProcessIssueRepository.findByProductProcess_Id(productProcessId)
        println(" Service: Found ${issues.size} issues")
        return issues
    }

    open fun findByJobOrderId(jobOrderId: Long): List<ProductProcess> {
        println("    Service: Finding ProductProcess by jobOrderId: $jobOrderId")
        val result = productProcessRepository.findByJobOrder_Id(jobOrderId)
        println(" Service: Found ${result.size} processes")
        return result
    }

    open fun findByJobOrderIdWithLines(jobOrderId: Long): List<ProductProcessWithLinesResponse> {
        println("    Service: Finding ProductProcess with lines by jobOrderId: $jobOrderId")
        val processes = productProcessRepository.findByJobOrder_Id(jobOrderId)

        val result = processes.map { process ->
            val lines = productProcessLineRepository.findByProductProcess_Id(process.id!!)
            ProductProcessWithLinesResponse(
                id = process.id!!,
                productProcessCode = process.productProcessCode,
                status = process.status.value,
                startTime = process.startTime,
                endTime = process.endTime,
                date = process.date,
                lines = lines.map { line ->
                    ProductProcessLineResponse(
                        id = line.id!!,
                        seqNo = line.seqNo,
                        name = line.name,
                        description = line.description,
                        equipmentType = line.equipmentType,
                        startTime = line.startTime,
                        endTime = line.endTime,
                        outputFromProcessQty = line.outputFromProcessQty,
                        outputFromProcessUom = line.outputFromProcessUom,
                        defectQty = line.defectQty,
                        scrapQty = line.scrapQty,
                        byproductName = line.byproductName,
                        byproductQty = line.byproductQty
                    )
                }
            )
        }

        println(" Service: Returning ${result.size} processes with ${result.sumOf { it.lines.size }} total lines")
        return result
    }

    open fun findAllAsDto(pageable: Pageable): Page<ProductProcessSimpleResponse> {
        println("   Service: Finding all ProductProcess as DTO with page: ${pageable.pageNumber}, size: ${pageable.pageSize}")
        val entityPage = productProcessRepository.findAll(pageable)

        val dtoList = entityPage.content.map { entity ->
            ProductProcessSimpleResponse(
                id = entity.id!!,
                productProcessCode = entity.productProcessCode,
                status = entity.status.value,
                startTime = entity.startTime,
                endTime = entity.endTime,
                date = entity.date,
                bomId = entity.bom?.id,
                jobOrderId = entity.jobOrder?.id
            )
        }

        println(" Service: Converted ${dtoList.size} entities to DTOs")
        return org.springframework.data.domain.PageImpl(dtoList, pageable, entityPage.totalElements)
    }

    open fun findByIdAsDto(id: Long): ProductProcessSimpleResponse {
        println("    Service: Finding ProductProcess by ID: $id as DTO")
        val entity = productProcessRepository.findById(id)
            .orElseThrow { IllegalArgumentException("ProductProcess not found with id: $id") }

        return ProductProcessSimpleResponse(
            id = entity.id!!,
            productProcessCode = entity.productProcessCode,
            status = entity.status.value,
            startTime = entity.startTime,
            endTime = entity.endTime,
            date = entity.date,
            bomId = entity.bom?.id,
            jobOrderId = entity.jobOrder?.id
        )
    }

    open fun stopProcessAsDto(id: Long, request: StopProcessRequest): ProductionProcessIssueResponse {
        val issue = stopProcess(id, request)
        return ProductionProcessIssueResponse(
            id = issue.id!!,
            productProcessId = issue.productProcess?.id,
            operatorId = issue.operator?.id,
            operatorName = issue.operatorName,
            reason = issue.reason!!,
            stopTime = issue.stopTime!!,
            resumeTime = issue.resumeTime,
            totalTime = issue.totalTime
        )
    }

    open fun resumeProcessAsDto(id: Long, issueId: Long): ProductionProcessIssueResponse {
        val issue = resumeProcess(id, issueId)
        return ProductionProcessIssueResponse(
            id = issue.id!!,
            productProcessId = issue.productProcess?.id,
            operatorId = issue.operator?.id,
            operatorName = issue.operatorName,
            reason = issue.reason!!,
            stopTime = issue.stopTime!!,
            resumeTime = issue.resumeTime,
            totalTime = issue.totalTime
        )
    }

    open fun getIssuesAsDto(productProcessId: Long): List<ProductionProcessIssueResponse> {
        val issues = getIssues(productProcessId)
        return issues.map { issue ->
            ProductionProcessIssueResponse(
                id = issue.id!!,
                productProcessId = issue.productProcess?.id,
                operatorId = issue.operator?.id,
                operatorName = issue.operatorName,
                reason = issue.reason!!,
                stopTime = issue.stopTime!!,
                resumeTime = issue.resumeTime,
                totalTime = issue.totalTime
            )
        }
    }

    open fun startProcessAsDto(id: Long, operatorId: Long): ProductProcessSimpleResponse {
        val entity = startProcess(id, operatorId)
        return ProductProcessSimpleResponse(
            id = entity.id!!,
            productProcessCode = entity.productProcessCode,
            status = entity.status.value,
            startTime = entity.startTime,
            endTime = entity.endTime,
            date = entity.date,
            bomId = entity.bom?.id,
            jobOrderId = entity.jobOrder?.id
        )
    }

    open fun completeProcessAsDto(id: Long): ProductProcessSimpleResponse {
        val entity = completeProcess(id)
        return ProductProcessSimpleResponse(
            id = entity.id!!,
            productProcessCode = entity.productProcessCode,
            status = entity.status.value,
            startTime = entity.startTime,
            endTime = entity.endTime,
            date = entity.date,
            bomId = entity.bom?.id,
            jobOrderId = entity.jobOrder?.id
        )
    }

    open fun updateLineOutputAsDto(lineId: Long, request: UpdateLineOutputRequest): ProductProcessLineResponse {
        val entity = updateLineOutput(lineId, request)
        return ProductProcessLineResponse(
            id = entity.id!!,
            seqNo = entity.seqNo,
            name = entity.name,
            description = entity.description,
            equipmentType = entity.equipmentType,
            startTime = entity.startTime,
            endTime = entity.endTime,
            outputFromProcessQty = entity.outputFromProcessQty,
            outputFromProcessUom = entity.outputFromProcessUom,
            defectQty = entity.defectQty,
            scrapQty = entity.scrapQty,
            byproductName = entity.byproductName,
            byproductQty = entity.byproductQty
        )
    }

    open fun getLinesAsDto(productProcessId: Long): List<ProductProcessLineResponse> {
        val lines = getLines(productProcessId)
        return lines.map { line ->
            ProductProcessLineResponse(
                id = line.id!!,
                seqNo = line.seqNo,
                name = line.name,
                description = line.description,
                equipmentType = line.equipmentType,
                startTime = line.startTime,
                endTime = line.endTime,
                outputFromProcessQty = line.outputFromProcessQty,
                outputFromProcessUom = line.outputFromProcessUom,
                defectQty = line.defectQty,
                scrapQty = line.scrapQty,
                byproductName = line.byproductName,
                byproductQty = line.byproductQty
            )
        }
    }

    open fun startLine(lineId: Long, userId: Long): ProductProcessLine {
        println("▶️ Service: Starting ProductProcessLine ID: $lineId with userId: $userId")
        val line = productProcessLineRepository.findById(lineId)
            .orElseThrow { IllegalArgumentException("Line not found") }

        //  修复：使用 UserRepository 获取 User 对象
        val user = userRepository.findById(userId)
            .orElseThrow { IllegalArgumentException("User not found with id: $userId") }

        line.handler = user
        line.startTime = LocalDateTime.now()
        line.status = "InProgress"

        val saved = productProcessLineRepository.save(line)
        syncProductProcessStatusFromLines(saved.productProcess.id)
        println(" Service: Line started, handlerId: ${saved.handler?.id}")
        return saved
    }

    open fun findPendingLinesByHandlerId(handlerId: Long): List<ProductProcessLine> {
        println("    Service: Finding pending lines for handlerId: $handlerId")
        val lines = productProcessLineRepository.findByHandler_IdAndStartTimeIsNotNullAndEndTimeIsNull(handlerId)
        println(" Service: Found ${lines.size} pending lines")
        return lines
    }

    open fun productProcessDetailfindbyjoid(joid: Long): List<ProductProcessInfo> {
        val productProcesses = productProcessRepository.findByJobOrder_Id(joid)
        val jobOrder = jobOrderRepository.findById(joid).orElse(null)
        val bom = bomRepository.findById(jobOrder?.bom?.id ?: 0L).orElse(null)
        val bomProcess = bomProcessRepository.findByBomId(jobOrder?.bom?.id ?: 0L)
        // get bom materials
        val bomMaterials = jobOrderBomMaterialRepository.findAllByJobOrderId(jobOrder?.id ?: 0)
        val bomProcessIds = bomProcess.mapNotNull { it.id }

        val itemIds = bomMaterials.mapNotNull { it.item?.id }

        // calculate each item's available stock
        // Around line 504-524, update the stockQtyMap calculation
        val stockQtyMap = bomMaterials.mapNotNull { material ->
            val itemId = material.item?.id ?: return@mapNotNull null
            val availableLots = inventoryLotLineRepository
                .findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE)
            val stockQty = availableLots.sumOf { lot ->
                (lot.inQty ?: BigDecimal.ZERO)
                    .minus(lot.outQty ?: BigDecimal.ZERO)
                    .minus(lot.holdQty ?: 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
            var stockUomId = stockItemUom?.uom?.id

            // Fallback: If no lots exist, get stockUom from item's stockUnit ItemUom
            if (stockUomId == null && availableLots.isEmpty()) {
                val stockUnitItemUom = itemUomService.findStockUnitByItemId(itemId)
                stockUomId = stockUnitItemUom?.uom?.id
                if (stockUomId != null) {
                    println("DEBUG itemId=$itemId: Using fallback stockUnit ItemUom - stockUom.uom.id=$stockUomId")
                } else {
                    println("WARNING: No stockUom found for itemId=$itemId in available lots and no stockUnit ItemUom configured")
                }
            } else {
                if (stockItemUom == null) {
                    println("WARNING: No stockUom found for itemId=$itemId in available lots")
                }
                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()


        fun calculateColourScore(value: Int?): String {
            return when (value) {
                0 -> "淺"
                1 -> "深"
                else -> ""
            }
        }

        fun calculateAllergicSubstanceScore(value: Int?): String {
            return when (value) {
                0 -> "No"
                5 -> "Yes"
                else -> "N/A"
            }
        }

        fun calculateFloatScore(value: Int?): String {
            return when (value) {
                0 -> "沉"
                1 -> "浮"
                else -> ""
            }
        }

        return productProcesses.map { process ->

            val jobType = jobTypeRepository.findById(process.jobOrder?.jobTypeId ?: 0L).orElse(null)

            //val joPickOrders = joPickOrderRepository.findByJobOrderId(process.jobOrder?.id?:0L)
            println("jobType id ${process.jobOrder?.jobTypeId}")
            val newCreatedLineIds = findNewCreatedLineIds(process.id ?: 0L, bom?.id ?: 0L)
            ProductProcessInfo(
                id = process.id ?: 0,
                bomId = process.bom?.id ?: 0,
                jobOrderId = process.jobOrder?.id ?: 0,
                jobOrderCode = jobOrder?.code ?: "",
                jobOrderStatus = jobOrder?.status?.value ?: "",
                jobType = jobType?.name ?: "",
                bomDescription = bom?.description ?: "",
                itemId = bom?.item?.id ?: 0,
                itemCode = bom?.item?.code ?: "",
                itemName = bom?.item?.name ?: "",
                timeSequence = bom?.timeSequence ?: 0,
                complexity = bom?.complexity ?: 0,
                bomBaseQty = bom.outputQty ?: BigDecimal.ZERO,
                isDark = calculateColourScore(bom?.isDark ?: 0),
                isDense = bom?.isDense ?: 0,
                isFloat = calculateFloatScore(bom?.isFloat ?: 0),
                scrapRate = bom?.scrapRate ?: -1,
                allergicSubstance = calculateAllergicSubstanceScore(bom?.allergicSubstances),
                outputQtyUom = bom?.outputQtyUom ?: "",
                //matchStatus = joPickOrders?.matchStatus?.value?:"",
                //outputQty = bom?.outputQty?.toInt()?:0,
                outputQty = jobOrder?.reqQty?.toInt() ?: 0,
                productProcessCode = process.productProcessCode ?: "",
                status = process.status ?: ProductProcessStatus.PENDING,
                startTime = process.startTime ?: LocalDateTime.now(),
                endTime = process.endTime ?: LocalDateTime.now(),
                date = process.date ?: LocalDate.now(),
                productionPriority = process.productionPriority ?: 50,
                submitedBagRecord = process.submitedBagRecord ?: false,
                totalStockQty = totalStockQty,
                insufficientStockQty = insufficientStockQty,
                sufficientStockQty = sufficientStockQty,
                productProcessLines = productProcessLineRepository.findByProductProcess_Id(process.id ?: 0)
                    .map { line ->
                        val equipmentDetail =
                            equipmentDetailRepository.findById(line.equipmentDetailId ?: 0L).orElse(null)
                        println("equipmentDetail ${equipmentDetail?.code}")
                        println("equipmentDetail id${line.equipmentDetailId}")
                        println("line id${line.id}")
                        ProductProcessLineInfo(
                            id = line.id ?: 0,
                            processId = process.id ?: 0,
                            bomprocessId = line.bomProcess?.id ?: 0,
                            operatorId = line.operator?.id ?: 0,
                            operatorName = line.operator?.name ?: "",
                            equipmentId = line.equipment?.id ?: 0,
                            equipmentDetailId = line.equipmentDetailId ?: 0,
                            handlerId = line.handler?.id ?: 0,
                            seqNo = line.seqNo ?: 0,
                            name = line.name ?: "",
                            description = line.description ?: "",
                            equipment_name = line.equipmentType ?: "",
                            equipmentDetailCode = equipmentDetail?.code ?: "",
                            status = line.status ?: "",
                            durationInMinutes = line.processingTime ?: 0,
                            prepTimeInMinutes = line.setupTime ?: 0,
                            postProdTimeInMinutes = line.changeoverTime ?: 0,
                            byproductId = line.byproduct?.id ?: 0,
                            byproductName = line.byproduct?.name ?: "",
                            byproductQty = line.byproductQty ?: 0,
                            byproductUom = line.byproductUom ?: "",
                            scrapQty = line.scrapQty ?: 0,
                            defectQty = line.defectQty ?: 0,
                            defectUom = line.defectUom ?: "",
                            defectDescription = line.defectDescription ?: "",
                            defectQty2 = line.defectQty2 ?: 0,
                            defectUom2 = line.defectUom2 ?: "",
                            defectDescription2 = line.defectDescription2 ?: "",
                            defectQty3 = line.defectQty3 ?: 0,
                            defectUom3 = line.defectUom3 ?: "",
                            defectDescription3 = line.defectDescription3 ?: "",
                            outputFromProcessQty = line.outputFromProcessQty ?: 0,
                            outputFromProcessUom = line.outputFromProcessUom ?: "",
                            startTime = line.startTime,
                            endTime = line.endTime,
                            isOringinal = line.isOriginal ?: false

                        )
                    }.sortedBy { it.seqNo },
                jobOrderLines = bomMaterials.map { line ->
                    val itemId = line.item?.id ?: 0L
                    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)
                    }

                    // ✅ 使用 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?.stockUnit?.toLong() ?: 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 stockUomNameForStock = stockUom?.udfudesc

                    println("=== Quantity Calculation for Item: ${line.item?.code} (id=$itemId) ===")
                    println("JobOrderBomMaterial reqQty (adjusted, stock unit): $actualReqQty in UOM: ${stockReqUom?.udfudesc} (id=$stockReqUomId)")
                    println("BomMaterial qty (base): ${bomMaterial?.qty}, stockQty (base): ${bomMaterial?.stockQty}")
                    println("Original stockQty: $stockQtyValue in UOM: $stockUomNameForStock (id=$stockUomId)")

                    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.UP)
                        } else {
                            BigDecimal.ONE
                        }

                    // ✅ reqQty 使用 bomMaterial.qty * proportion（BOM 单位）
                    val reqQtyInBomUnit = (bomMaterial?.qty?.times(proportion) ?: BigDecimal.ZERO)

                    // ✅ stockReqQty 使用 bomMaterial.stockQty * proportion（库存单位，已按比例调整）
                    val stockReqQtyInStockUnit = (bomMaterial?.stockQty?.times(proportion) ?: BigDecimal.ZERO)

                    // ✅ Convert reqQty (BOM unit) to base unit
                    val baseReqQtyResult = if (reqUomId > 0 && reqQtyInBomUnit > BigDecimal.ZERO) {
                        try {
                            val sourceItemUom =
                                itemUomRepository.findFirstByItemIdAndUomIdAndDeletedIsFalse(itemId, reqUomId)
                            val targetItemUom = itemUomService.findBaseUnitByItemId(itemId)

                            println("Converting reqQty (BOM unit): $reqQtyInBomUnit from UOM id=$reqUomId to baseUnit")

                            try {
                                val result = itemUomService.convertUomByItem(
                                    ConvertUomByItemRequest(
                                        itemId = itemId,
                                        qty = reqQtyInBomUnit,  // BOM 单位数量
                                        uomId = reqUomId,
                                        targetUnit = "baseUnit"
                                    )
                                )
                                println("Converted baseReqQty result: ${result.newQty} in base UOM: ${result.udfudesc}")
                                result
                            } catch (e: IllegalArgumentException) {
                                if (sourceItemUom == null) {
                                    println("WARNING: No ItemUom found for itemId=$itemId, uomId=$reqUomId. Cannot convert baseReqQty.")
                                    null
                                } else {
                                    throw e
                                }
                            }
                        } catch (e: Exception) {
                            println("Error converting baseReqQty: ${e.message}")
                            e.printStackTrace()
                            null
                        }
                    } else {
                        println("reqUomId is 0 or reqQtyInBomUnit is 0, skipping baseReqQty conversion")
                        null
                    }

                    val baseReqQty = baseReqQtyResult?.newQty?.toLong() ?: 0L
                    val baseUnitItemUom = itemUomService.findBaseUnitByItemId(itemId)
                    val baseUomName = baseUnitItemUom?.uom?.udfudesc ?: baseReqQtyResult?.udfudesc
                    val baseShortUom = baseUnitItemUom?.uom?.udfShortDesc ?: baseReqQtyResult?.udfShortDesc

                    // Convert stockQty to base unit
                    val baseStockQtyResult = if (stockUomId != null && stockUomId > 0) {
                        try {
                            val result = itemUomService.convertUomByItem(
                                ConvertUomByItemRequest(
                                    itemId = itemId,
                                    qty = stockQtyValue,
                                    uomId = stockUomId,
                                    targetUnit = "baseUnit"
                                )
                            )
                            result
                        } catch (e: Exception) {
                            println("Error converting stockQty: ${e.message}")
                            null
                        }
                    } else {
                        null
                    }

                    val baseStockQty = baseStockQtyResult?.newQty?.toLong() ?: 0L
                    val baseStockUomName = baseUomName
                    val baseStockShortUom = baseShortUom

                    // ✅ 计算 baseStockReqQty（stockReqQty 转换为 base unit）
                    val baseStockReqQtyResult = if (stockReqUomId > 0 && stockReqQtyInStockUnit > BigDecimal.ZERO) {
                        try {
                            val result = itemUomService.convertUomByItem(
                                ConvertUomByItemRequest(
                                    itemId = itemId,
                                    qty = stockReqQtyInStockUnit,  // 库存单位数量
                                    uomId = stockReqUomId,
                                    targetUnit = "baseUnit"
                                )
                            )
                            result
                        } catch (e: Exception) {
                            println("Error converting stockReqQty to base unit: ${e.message}")
                            null
                        }
                    } else {
                        null
                    }

                    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")

                    // Find BomProcessMaterial
                    val bomProcessMaterial = bomMaterial?.id?.let { bomMaterialId ->
                        bomProcessIds.firstNotNullOfOrNull { bomProcessId ->
                            bomProcessMaterialRepository.findByBomProcessIdAndBomMaterialId(bomProcessId, bomMaterialId)
                        }
                    }

                    // Calculate availableStatus using base quantities
                    val availableStatus = if (baseStockQty >= baseStockReqQty) {
                        "available"
                    } else {
                        "insufficient"
                    }

                    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：使用 bomMaterial.qty * proportion（BOM 单位，已按比例调整）
                        reqQty = if (reqUomId in decimalUomIds) {
                            reqQtyInBomUnit.toDouble()
                        } else {
                            reqQtyInBomUnit.setScale(0, RoundingMode.UP).toDouble()
                        },
                        baseReqQty = baseReqQty,

                        stockQty = stockQty,
                        // ✅ stockReqQty：使用 bomMaterial.stockQty * proportion（库存单位，已按比例调整）
                        stockReqQty = if (stockReqUomId in decimalUomIds) {
                            stockReqQtyInStockUnit.toDouble()
                        } else {
                            stockReqQtyInStockUnit.setScale(0, RoundingMode.UP).toDouble()
                        },
                        baseStockQty = baseStockQty,

                        reqUom = uomName ?: "",
                        reqBaseUom = baseUomName ?: "",
                        stockUom = stockUomName ?: "",
                        stockBaseUom = baseUomName ?: "",

                        type = line.item?.type ?: "",
                        availableStatus = availableStatus,
                        bomProcessId = bomProcessMaterial?.bomProcess?.id ?: 0,
                        bomProcessSeqNo = bomProcessMaterial?.bomProcess?.seqNo ?: 0
                    )
                }
            )
        }
    }

    open fun createProductProcessByJobOrderId(jobOrderId: Long, productionPriority: Int?): MessageResponse {
        val jobOrder = jobOrderRepository.findById(jobOrderId).orElse(null)
        val bom = bomRepository.findById(jobOrder?.bom?.id ?: 0L).orElse(null)
        // val bom = jobOrder.bom.let { bomRepository.findById(it).orElse(null) }
        val bomProcess = bomProcessRepository.findByBomId(jobOrder?.bom?.id ?: 0L)
        val item = itemsRepository.findById(bom?.item?.id ?: 0L).orElse(null)
        val datePrefix = (LocalDate.now()).format(DateTimeFormatter.ofPattern("yyyyMMdd"))
        val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")

        val productProcess = ProductProcess().apply {
            this.jobOrder = jobOrder
            this.item = item
            this.status = ProductProcessStatus.PENDING
            this.date = jobOrder?.planStart?.toLocalDate()
            this.bom = bom

            this.productionPriority = productionPriority
        }

        val savedProcess = retryOnDuplicateProductProcessCode {
            val newCode = generateProductProcessCode(datePrefix, bom?.id ?: 0L)
            productProcess.productProcessCode = newCode
            productProcessRepository.saveAndFlush(productProcess)
        }
        bomProcess.forEach { bomProcess ->
            val process = processRepository.findById(bomProcess?.process?.id ?: 0L).orElse(null)
            val equipment = equipmentRepository.findById(bomProcess?.equipment?.id ?: 0L).orElse(null)
            val productProcessLine = ProductProcessLine().apply {
                this.productProcess = savedProcess
                this.bomProcess = bomProcess
                // this.equipment = equipment
                this.seqNo = bomProcess.seqNo ?: 0
                this.name = process?.name ?: ""
                this.description = bomProcess.description ?: ""
                this.equipmentType = equipment?.code ?: ""
                this.status = "Pending"
                this.processingTime = bomProcess.durationInMinute
                this.setupTime = bomProcess.prepTimeInMinute
                this.changeoverTime = bomProcess.postProdTimeInMinute
                this.isOriginal = true
            }
            productProcessLineRepository.save(productProcessLine)
        }

        return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
            message = null,
            errorPosition = null,
        )
    }

    open fun UpdateProductProcessLineOperatorIdOrEquipmentId(request: UpdateProductProcessLineOperatorIdOrEquipmentIdRequest): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null)


        val equipmentTypeSubTypeEquipmentNo = request.equipmentTypeSubTypeEquipmentNo
        println("equipmentTypeSubTypeEquipmentNo ${equipmentTypeSubTypeEquipmentNo}")
        val staffNo = request.staffNo
        println("staffNo ${staffNo}")
        if (staffNo.isNullOrBlank()) {
            println("staffNo is null or blank")
            return MessageResponse(
                id = request.productProcessLineId,
                code = "400",
                name = "Staff No Required",
                type = "error",
                message = "Staff No is required",
                errorPosition = "staffNo"
            )
        }
        val user = userRepository.findByStaffNo(staffNo ?: "").orElse(null)
        println("user ${user?.id}")
        if (user == null) {
            println("User not found with staffNo: $staffNo")
            return MessageResponse(
                id = request.productProcessLineId,
                code = "404",
                name = "User Not Found",
                type = "error",
                message = "User not found with staffNo: $staffNo",
                errorPosition = "staffNo"
            )
        }
        val equipmentDetail = equipmentDetailRepository.findByCode(equipmentTypeSubTypeEquipmentNo ?: "")
        println("equipmentDetail ${equipmentDetail?.id}")
        val equipmentId = equipmentDetail?.equipmentTypeId
        println("equipmentId ${equipmentId}")
        val bomProcess = bomProcessRepository.findById(productProcessLine?.bomProcess?.id ?: 0L).orElse(null)
        val bomProcessEquipment = bomProcess?.equipment
        println("bomProcessEquipment ${bomProcessEquipment?.id}")
        if (equipmentId != null && user != null) {
            // 检查 equipmentId 是否与 bomProcessEquipment 匹配
            if (equipmentId != bomProcessEquipment?.id) {
                println("productProcessLine id${request.productProcessLineId}")

                println("user ${user?.id}")
                println("bomProcess ${bomProcess?.id}")
                println("not match equipment id${equipmentId} and ${bomProcessEquipment?.id}")

                // 返回错误响应
                return MessageResponse(
                    id = request.productProcessLineId,
                    code = "400",
                    name = "Equipment Validation Failed",
                    type = "error",
                    message = "Input Equipment is not match with prcess",
                    errorPosition = "equipmentId"
                )
            }
        }
        if (equipmentId != null && (equipmentId == bomProcessEquipment?.id)) {

            productProcessLine?.equipment = bomProcessEquipment
            productProcessLine?.equipmentDetailId = equipmentDetail?.id
            productProcessLineRepository.save(productProcessLine)
        }

        productProcessLine.operator = user
        productProcessLineRepository.save(productProcessLine)


        return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
            message = null,
            errorPosition = null,
        )
    }

    open fun NewUpdateProductProcessLineOperatorIdOrEquipmentIdAndEquipmentDetail(request: NewUpdateProductProcessLineOperatorIdOrEquipmentIdAndEquipmentDetailRequest): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null)
        val equipmentDetail = equipmentDetailRepository.findByEquipmentCode(request.equipmentCode)

        val user = userRepository.findByStaffNo(request.staffNo ?: "").orElse(null)
        val bomProcess = bomProcessRepository.findById(productProcessLine?.bomProcess?.id ?: 0L).orElse(null)
        val bomProcessEquipment = bomProcess?.equipment
        val requiresEquipment = bomProcessEquipment != null
        // ===== 校验区 =====

        // 情况：设备有 + 人空 → 不通过
        if (equipmentDetail != null && user == null) {
            return MessageResponse(
                id = request.productProcessLineId,
                code = "400",
                name = "User Required",
                type = "error",
                message = "Staff No is required",
                errorPosition = "staffNo"
            )
        }

        // 情况：设备有 + 人有 + 设备不匹配 → 不通过
        if (equipmentDetail != null && user != null &&
            equipmentDetail.equipmentTypeId != bomProcessEquipment?.id
        ) {
            println("not match equipment id${equipmentDetail.equipmentTypeId} and ${bomProcessEquipment?.id}")
            return MessageResponse(
                id = request.productProcessLineId,
                code = "400",
                name = "Equipment Validation Failed",
                type = "error",
                message = "Input Equipment is not match with process",
                errorPosition = "equipmentId"
            )
        }
        // *** 新增：这道工序需要设备，但是只扫了人、没扫设备码 → 不通过 ***
        if (requiresEquipment && user != null && equipmentDetail == null) {
            return MessageResponse(
                id = request.productProcessLineId,
                code = "400",
                name = "Equipment Required",
                type = "error",
                message = "Equipment is required for this process",
                errorPosition = "equipmentId"
            )
        }

        // 情况：设备空 + 人空 → 不通过（如你不需要这个，可以去掉或改成通过）
        if (equipmentDetail == null && user == null) {
            return MessageResponse(
                id = request.productProcessLineId,
                code = "400",
                name = "User Required",
                type = "error",
                message = "Staff No is required",
                errorPosition = "staffNo"
            )
        }
        if (user != null && productProcessLine.operator != null) {
            val existingOperatorId = productProcessLine.operator?.id
            val newOperatorId = user.id

            // 如果不是同一个用户，拒绝更新
            if (existingOperatorId != null && existingOperatorId != newOperatorId) {
                return MessageResponse(
                    id = request.productProcessLineId,
                    code = "409",
                    name = "Conflict",
                    type = "error",
                    message = "This process line is already assigned to another operator",
                    errorPosition = "operator"
                )
            }
        }
        // ===== 通过校验，开始更新 =====

        // 设备有（且已通过匹配校验） → 更新 equipment / equipmentDetailId
        if (equipmentDetail != null) {
            val equipment = equipmentRepository.findById(equipmentDetail.equipmentTypeId!!).orElse(null)
            productProcessLine?.equipment = equipment
            productProcessLine?.equipmentDetailId = equipmentDetail.id
        }

        // 人有（设备可以有也可以没有） → 更新 operator
        if (user != null) {
            productProcessLine?.operator = user
        }

        productProcessLineRepository.save(productProcessLine)

        return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
            message = null,
            errorPosition = null,
        )
    }

    open fun UpdateProductProcessLineHandlerId(request: UpdateProductProcessLineHandlerIdRequest): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null)
        val handlerId = request.handlerId
        val user = userRepository.findById(handlerId ?: 0L).orElse(null)
        if (user != null) {
            productProcessLine.handler = user
            productProcessLineRepository.save(productProcessLine)
        }
        productProcessLine.startTime = LocalDateTime.now()
        productProcessLineRepository.save(productProcessLine)
        syncProductProcessStatusFromLines(productProcessLine.productProcess?.id ?: 0L)
        return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
            message = null,
            errorPosition = null,
        )
    }

    open fun getJobOrderProcessLineDetail(productProcessLineId: Long): JobOrderProcessLineDetailResponse {
        val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
        val productProcess =
            productProcessRepository.findById(productProcessLine?.productProcess?.id ?: 0L).orElse(null)
        val bomProcessId = productProcessLine?.bomProcess?.id
        println("bomProcessId ${bomProcessId}")
        val bomProcess: BomProcess? = bomProcessId?.let {
            bomProcessRepository.findAll().firstOrNull { it.id == bomProcessId }
        }
        println("bomProcess ${bomProcess?.id}")
        println("bomProcess durationInMinute ${bomProcess?.durationInMinute}")
        val productProcessIssue = productionProcessIssueRepository.findByProductProcessLineId(productProcessLineId)
            .filter { it.status == "Paused" }.firstOrNull()

        // ✅ 计算所有已恢复的暂停记录的总暂停时间（毫秒）
        val allIssues = productionProcessIssueRepository.findByProductProcessLineId(productProcessLineId)
        val totalPausedTimeMs = allIssues
            .filter { it.status == "Resumed" && it.stopTime != null && it.resumeTime != null }
            .sumOf { issue ->
                val stopTime = issue.stopTime?.toEpochSecond(ZoneOffset.UTC) ?: 0L
                val resumeTime = issue.resumeTime?.toEpochSecond(ZoneOffset.UTC) ?: 0L
                (resumeTime - stopTime) * 1000 // 转换为毫秒
            }

        return JobOrderProcessLineDetailResponse(
            id = productProcessLine.id ?: 0,
            productProcessId = productProcessLine.productProcess?.id ?: 0,
            bomProcessId = productProcessLine.bomProcess?.id ?: 0,
            operatorId = productProcessLine.operator?.id ?: 0,
            operatorName = productProcessLine.operator?.name ?: "",
            handlerId = productProcessLine.handler?.id ?: 0,
            durationInMinutes = bomProcess?.durationInMinute,

            productProcessIssueId = productProcessIssue?.id ?: 0,
            productProcessIssueStatus = productProcessIssue?.status ?: "",

            seqNo = productProcessLine.seqNo ?: 0,
            name = productProcessLine.name ?: "",
            description = productProcessLine.description ?: "",
            equipmentType = productProcessLine.equipmentType,
            equipmentId = productProcessLine.equipment?.id ?: 0,
            startTime = productProcessLine.startTime ?: LocalDateTime.now(),
            endTime = productProcessLine.endTime ?: LocalDateTime.now(),
            stopTime = productProcessIssue?.stopTime,
            submitedBagRecord = productProcess.submitedBagRecord ?: false,
            // ✅ 添加总暂停时间（毫秒）
            totalPausedTimeMs = totalPausedTimeMs.toLong(),
            status = productProcessLine.status ?: "",
            outputFromProcessQty = productProcessLine.outputFromProcessQty ?: 0,
            outputFromProcessUom = productProcessLine.outputFromProcessUom ?: "",
            defectQty = productProcessLine.defectQty ?: 0,
            defectUom = productProcessLine.defectUom ?: "",
            defectDescription = productProcessLine.defectDescription ?: "",
            defectQty2 = productProcessLine.defectQty2 ?: 0,
            defectUom2 = productProcessLine.defectUom2 ?: "",
            defectDescription2 = productProcessLine.defectDescription2 ?: "",
            defectQty3 = productProcessLine.defectQty3 ?: 0,
            defectUom3 = productProcessLine.defectUom3 ?: "",
            defectDescription3 = productProcessLine.defectDescription3 ?: "",
            scrapQty = productProcessLine.scrapQty ?: 0,
            scrapUom = productProcessLine.scrapUom ?: "",
            byproductId = productProcessLine.byproduct?.id ?: 0,
            byproductName = productProcessLine.byproduct?.name ?: "",
            byproductQty = productProcessLine.byproductQty ?: 0,
            byproductUom = productProcessLine.byproductUom ?: ""
        )
    }

    open fun updateProductProcessLineStatus(productProcessLineId: Long, status: String): MessageResponse {
        println("   Service: Updating ProductProcessLine Status: $productProcessLineId")
        val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
        println("   Service: ProductProcessLine: $productProcessLine")
        productProcessLine.status = status
        // productProcessLine.endTime = LocalDateTime.now()
        productProcessLineRepository.save(productProcessLine)
        println("   Service: ProductProcessLine Status Updated: ${productProcessLine.status}")
        syncProductProcessStatusFromLines(productProcessLine.productProcess?.id ?: 0L)

        return MessageResponse(
            id = productProcessLineId,
            code = "200",
            name = "ProductProcessLine Status Updated",
            type = "success",
            message = "ProductProcessLine Status Updated",
            errorPosition = null,
        )
    }

    open fun passProductProcessLine(productProcessLineId: Long): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
        val productProcess =
            productProcessRepository.findById(productProcessLine?.productProcess?.id ?: 0L).orElse(null)
        // 如果 startTime 为空，才设置它（保留已存在的 startTime）
        println("   Service: ProductProcessLine StartTime: ${productProcessLine.startTime}")
        println("   Service: ProductProcess StartTime: ${productProcess.startTime}")
        if (productProcessLine.startTime == null) {
            productProcessLine.startTime = LocalDateTime.now()
            productProcessLineRepository.save(productProcessLine)
        }
        if (productProcess.startTime == null) {
            productProcess.startTime = LocalDateTime.now()
            productProcessRepository.save(productProcess)
        }

        // 总是设置 endTime（因为 Pass 意味着完成）
        productProcessLine.endTime = LocalDateTime.now()
        productProcessLineRepository.save(productProcessLine)
        println("   Service: ProductProcessLine EndTime: ${productProcessLine.endTime}")
        // 更新状态为 "Pass"
        updateProductProcessLineStatus(productProcessLineId, "Pass")

        val productProcessId = productProcessLine?.productProcess?.id ?: 0L
        syncProductProcessStatusFromLines(productProcessId)

        return MessageResponse(
            id = productProcessLineId,
            code = "200",
            name = "ProductProcessLine Passed",
            type = "success",
            message = "ProductProcessLine Passed",
            errorPosition = null,
        )
    }

    open fun CompleteProductProcessStatusIfAllLinesCompleted(productProcessId: Long): MessageResponse {
        val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
        println("   Service: ProductProcess: $productProcess")
        val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)

        println("   Service: ProductProcessLines: $productProcessLines")
        if (productProcessLines.all { it.status == "Completed" || it.status == "Pass" }) {
            productProcess.status = ProductProcessStatus.COMPLETED
            if (productProcess.endTime == null) {
                productProcess.endTime = LocalDateTime.now()
            }
            productProcessRepository.save(productProcess)
            println("   Service: ProductProcess Status Updated: ${productProcess.status}")
        } else {
            println("   Service: ProductProcess Lines are not completed")
        }
        return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
            message = null,
            errorPosition = null,
        )
    }

    open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest): MessageResponse {
        val outputFromProcessQty = request.outputFromProcessQty
        val outputFromProcessUom = request.outputFromProcessUom
        val defectQty = request.defectQty
        val defectUom = request.defectUom
        val defectDescription = request.defectDescription
        val defect2Qty = request.defect2Qty
        val defect2Uom = request.defect2Uom
        val defect2Description = request.defectDescription2
        val defect3Qty = request.defect3Qty
        val defect3Uom = request.defect3Uom
        val defect3Description = request.defectDescription3
        val scrapQty = request.scrapQty
        val scrapUom = request.scrapUom
        val byproductName = request.byproductName
        val byproductQty = request.byproductQty
        val byproductUom = request.byproductUom
        val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null)
        if (outputFromProcessQty != null) {
            productProcessLine.outputFromProcessQty = outputFromProcessQty
        }
        if (outputFromProcessUom != null) {
            productProcessLine.outputFromProcessUom = outputFromProcessUom
        }
        if (defectQty != null) {
            productProcessLine.defectQty = defectQty
        }
        if (defectUom != null) {
            productProcessLine.defectUom = defectUom
        }

        if (defectDescription != null) {
            productProcessLine.defectDescription = defectDescription
        }

        if (defect2Qty != null) {
            productProcessLine.defectQty2 = defect2Qty
        }
        if (defect2Uom != null) {
            productProcessLine.defectUom2 = defect2Uom
        }
        if (defect2Description != null) {
            productProcessLine.defectDescription2 = defect2Description
        }

        if (defect3Qty != null) {
            productProcessLine.defectQty3 = defect3Qty
        }
        if (defect3Uom != null) {
            productProcessLine.defectUom3 = defect3Uom
        }
        if (defect3Description != null) {
            productProcessLine.defectDescription3 = defect3Description
        }
        if (scrapQty != null) {
            productProcessLine.scrapQty = scrapQty
        }
        if (scrapUom != null) {
            productProcessLine.scrapUom = scrapUom
        }
        if (byproductName != null) {
            productProcessLine.byproductName = byproductName
        }
        if (byproductQty != null) {
            productProcessLine.byproductQty = byproductQty
        }
        if (byproductUom != null) {
            productProcessLine.byproductUom = byproductUom
        }
        productProcessLineRepository.save(productProcessLine)
        CompleteProductProcessLine(request.productProcessLineId)
        return MessageResponse(
            id = request.productProcessLineId,
            code = "200",
            name = "ProductProcessLine Qty Updated",
            type = "success",
            message = "ProductProcessLine Qty Updated",
            errorPosition = null,
        )
    }

    open fun getAllJoborderProductProcessInfo(bomType: String?): List<AllJoborderProductProcessInfoResponse> {
        val productProcesses = productProcessRepository.findAllByDeletedIsFalse()
        val normalizedType = bomType?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
        val filteredProductProcesses = productProcesses.filter { process ->
            if (normalizedType == null) return@filter true
            process.bom?.type?.trim()?.lowercase() == normalizedType
        }

        return filteredProductProcesses.map { productProcesses ->
            val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcesses.id ?: 0L)
            val jobOrder = jobOrderRepository.findById(productProcesses.jobOrder?.id ?: 0L).orElse(null)
            val FinishedProductProcessLineCount =
                productProcessLineRepository.findByProductProcess_Id(productProcesses.id ?: 0L)
                    .count { it.status == "Completed" }
            val stockInLine = jobOrder?.stockInLines?.firstOrNull()
            val stockInLineId = stockInLine?.id
            val pickOrder = pickOrderRepository.findAllByJobOrder_Id(jobOrder?.id ?: 0L).firstOrNull()
            val joPickOrdersList = joPickOrderRepository.findByPickOrderId(pickOrder?.id ?: 0L)
            val itemUom =
                itemUomRepository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(productProcesses.item?.id ?: 0L)
            val bomUom = uomConversionRepository.findById(itemUom?.uom?.id ?: 0L).orElse(null)
            //val silHandlerId = stockInLine?.escalationLog?.firstOrNull { it.status == "pending" }?.handler?.id
            val timeNeedToComplete = bomProcessRepository.findByBomId(productProcesses.bom?.id ?: 0L)
                .filter { !it.deleted }
                .sumOf { it.durationInMinute ?: 0 }
            val bomIsDrink = productProcesses.bom?.isDrink
            AllJoborderProductProcessInfoResponse(
                id = productProcesses.id ?: 0L,
                productProcessCode = productProcesses.productProcessCode,
                status = productProcesses.status.value,
                startTime = productProcesses.startTime,
                endTime = productProcesses.endTime,
                isDrink = bomIsDrink,
                matchStatus = if (joPickOrdersList.isNotEmpty() &&
                    joPickOrdersList.all { it.matchStatus == JoPickOrderStatus.completed }
                ) {
                    "completed"
                } else if (joPickOrdersList.any { it.matchStatus == JoPickOrderStatus.scanned }) {
                    "scanned"
                } else {
                    "pending"
                },
                RequiredQty = jobOrder?.reqQty?.toInt() ?: 0,
                Uom = bomUom?.udfudesc,
                lotNo = stockInLine?.lotNo,
                productionPriority = productProcesses.productionPriority,
                date = productProcesses.date,
                bomId = productProcesses.bom?.id,
                TimeNeedToComplete = timeNeedToComplete,
                assignedTo = pickOrder?.assignTo?.id,
                itemName = productProcesses.item?.name,
                itemCode = productProcesses.item?.code,
                bomDescription = productProcesses.bom?.description,
                pickOrderId = pickOrder?.id,
                pickOrderStatus = pickOrder?.status?.value,
                jobOrderId = productProcesses.jobOrder?.id,
                stockInLineId = stockInLineId,
                stockInLineStatus = stockInLine?.status,
                jobOrderCode = jobOrder?.code,
                productProcessLineCount = productProcessLines.size,
                FinishedProductProcessLineCount = FinishedProductProcessLineCount,
                lines = productProcessLines
                    .map { line ->
                        ProductProcessInfoResponse(
                            id = line.id ?: 0,
                            operatorId = line.operator?.id,
                            operatorName = line.operator?.name ?: "",
                            equipmentId = line.equipment?.id,
                            equipmentName = line.equipmentType ?: "",
                            startTime = line.startTime,
                            endTime = line.endTime,
                            status = line.status ?: ""
                        )
                    }
            )
        }.filter { response ->
            // 过滤掉已完成上架的 job order
            val jobOrder = jobOrderRepository.findById(response.jobOrderId ?: 0L).orElse(null)
            val stockInLineStatus = jobOrder?.stockInLines?.firstOrNull()?.status
            stockInLineStatus != "completed"
            // && stockInLineStatus != "escalated"   // 你已經註解掉，會把 escalated 顯示出來
            && stockInLineStatus != "rejected"
            && jobOrder?.status != JobOrderStatus.PLANNING
        }.sortedWith(
            compareByDescending<AllJoborderProductProcessInfoResponse> { it.date ?: LocalDate.MIN }
                .thenBy { it.productionPriority ?: Int.MAX_VALUE }
        )
    }

    /**
     * QC 列表：按 jobOrder 粒度分页，qcReady 由该 jobOrder 下所有 productProcessLine 是否全部 Completed/Pass 决定。
     *
     * 注意：date/itemCode/jobOrderCode/bomIds 用来筛选“候选 jobOrder”，但 qcReady 判断会用该 jobOrder 下全部 productProcessLine。
     */
    open fun searchJoborderProductProcessesPaged(
        date: LocalDate?,
        itemCode: String?,
        jobOrderCode: String?,
        bomIds: List<Long>?,
        qcReady: Boolean?,
        bomType: String?,
        includePutaway: Boolean?,
        putawayStatus: String?,
        page: Int,
        size: Int
    ): JobOrderProductProcessPageResponse {
        val safeSize = if (size <= 0) 50 else size
        val safePage = if (page < 0) 0 else page

        val trimmedItemCode = itemCode?.trim()?.takeIf { it.isNotBlank() }
        val trimmedJobOrderCode = jobOrderCode?.trim()?.takeIf { it.isNotBlank() }
        val normalizedType = bomType?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
        val normalizedBomIds = bomIds?.distinct().orEmpty()
        val useBomIds = normalizedBomIds.isNotEmpty()
        val bomIdsForQuery = if (useBomIds) normalizedBomIds else listOf(-1L)

        // 1) DB 端先完成候選 jobOrder 篩選 + 分組排序，避免全表拉回 JVM
        val candidateAggregates = productProcessRepository.findCandidateJobOrderAggregates(
            date = date,
            itemCode = trimmedItemCode,
            jobOrderCode = trimmedJobOrderCode,
            bomType = normalizedType,
            useBomIds = useBomIds,
            bomIds = bomIdsForQuery,
        )
        val candidateJobOrderIds = candidateAggregates.map { it.jobOrderId }

        if (candidateJobOrderIds.isEmpty()) {
            return JobOrderProductProcessPageResponse(
                content = emptyList(),
                totalJobOrders = 0L,
                page = safePage,
                size = safeSize
            )
        }

        // 2) 批量查詢替代 N+1：stockIn / line 狀態都一次取回
        val stockInLineByJobOrderId = stockInLineRepository
            .findAllByJobOrder_IdInAndDeletedFalse(candidateJobOrderIds)
            .groupBy { it.jobOrder?.id }
            .mapNotNull { (jobOrderId, rows) ->
                if (jobOrderId == null) null else jobOrderId to rows.firstOrNull()
            }
            .toMap()

        val lineStatusByJobOrderId = productProcessLineRepository
            .countLineStatusByJobOrderIds(candidateJobOrderIds)
            .associateBy { it.jobOrderId }

        val putawayFilter = putawayStatus?.trim()?.lowercase()
        val allowPutaway = includePutaway == true

        data class JobOrderQcKey(
            val jobOrderId: Long,
            val maxDate: LocalDate,
            val minPriority: Int,
            val ready: Boolean
        )
        val candidateProcesses = productProcessRepository
        .findByJobOrder_IdInAndDeletedIsFalse(candidateJobOrderIds)
    
        val candidateProcessIds = candidateProcesses.mapNotNull { it.id }
        
        val candidateLines = if (candidateProcessIds.isNotEmpty()) {
            productProcessLineRepository.findByProductProcess_IdInWithOperatorAndEquipment(candidateProcessIds)
        } else emptyList()
        
        val linesByJobOrder = candidateLines.groupBy { it.productProcess.jobOrder?.id ?: 0L }
        
        val done = { s: String? ->
            val x = s?.trim()?.lowercase()
            x == "completed" || x == "pass"
        }
        val qcKeys = candidateAggregates.mapNotNull { agg ->
            val jobOrderId = agg.jobOrderId
            val stockInLine = stockInLineByJobOrderId[jobOrderId]
            val stockStatus = stockInLine?.status?.trim()?.lowercase()

            val includedInList =
                stockStatus != "rejected" &&
                (allowPutaway || stockStatus != "completed")

            val includedByPutawayStatus =
                when (putawayFilter) {
                    null, "", "all" -> true
                    "completed" -> stockStatus == "completed"
                    "notcompleted", "not_completed", "not-completed", "waiting" -> stockStatus != "completed"
                    else -> true
                }

            if (!includedInList || !includedByPutawayStatus) return@mapNotNull null

            val lineAggregate = lineStatusByJobOrderId[jobOrderId]
            val totalLines = lineAggregate?.totalLines ?: 0L
            val done = { s: String? ->
                val x = s?.trim()?.lowercase()
                x == "completed" || x == "pass"
            }
            
            
            val jobLines = linesByJobOrder[jobOrderId].orEmpty()
            val packing = jobLines.filter { (it.name ?: "").trim() == "包裝" }
            val nonPacking = jobLines.filter { (it.name ?: "").trim() != "包裝" }

            val nonPackingOk = nonPacking.isEmpty() || nonPacking.all { done(it.status) }
            val packingOk = packing.isEmpty() || packing.any { done(it.status) }

            val ready = includedInList && stockInLine != null && nonPackingOk && packingOk
                        
            JobOrderQcKey(
                jobOrderId = jobOrderId,
                maxDate = agg.maxDate ?: LocalDate.MIN,
                minPriority = agg.minPriority ?: Int.MAX_VALUE,
                ready = ready,
            )
        }

        val filteredQcKeys = qcKeys.filter { key -> qcReady == null || key.ready == qcReady }
        val totalJobOrders = filteredQcKeys.size.toLong()

        val sortedQcKeys = filteredQcKeys.sortedWith(
            compareByDescending<JobOrderQcKey> { it.maxDate }.thenBy { it.minPriority }
        )

        val from = safePage * safeSize
        if (from >= sortedQcKeys.size) {
            return JobOrderProductProcessPageResponse(emptyList(), totalJobOrders, safePage, safeSize)
        }

        val to = minOf(from + safeSize, sortedQcKeys.size)
        val pagedJobOrderIds = sortedQcKeys.subList(from, to).map { it.jobOrderId }
        val pagedJobOrderIdSet = pagedJobOrderIds.toSet()

        // 4) 只對當前頁資料做批量關聯查詢，並且排序/資料縮小盡量下推 DB
        val contentProcesses = productProcessRepository
            .findByJobOrder_IdInAndDeletedIsFalseOrderByDateDescProductionPriorityAsc(pagedJobOrderIds)
            .filter { p -> p.jobOrder?.id != null && pagedJobOrderIdSet.contains(p.jobOrder!!.id!!) }

        val contentProcessIds = contentProcesses.mapNotNull { it.id }
        val lines = if (contentProcessIds.isNotEmpty()) {
            productProcessLineRepository.findByProductProcess_IdInWithOperatorAndEquipment(contentProcessIds)
        } else {
            emptyList()
        }
        val linesByProcessId = lines.groupBy { it.productProcess.id ?: 0L }

        val jobOrderById = jobOrderRepository.findAllById(pagedJobOrderIds).associateBy { it.id }

        val pickOrderByJobOrderId = mutableMapOf<Long, com.ffii.fpsms.modules.pickOrder.entity.PickOrder>()
        pickOrderRepository
            .findAllByJobOrder_IdInAndDeletedFalseOrderByJobOrder_IdAscCreatedDesc(pagedJobOrderIds)
            .forEach { po ->
                val joId = po.jobOrder?.id ?: return@forEach
                pickOrderByJobOrderId.putIfAbsent(joId, po)
            }

        val pickOrderIds = pickOrderByJobOrderId.values.mapNotNull { it.id }.distinct()
        val joPickOrdersByPickOrderId = if (pickOrderIds.isNotEmpty()) {
            joPickOrderRepository.findByPickOrderIdIn(pickOrderIds)
                .filter { !it.deleted && it.pickOrderId != null }
                .groupBy { it.pickOrderId!! }
        } else {
            emptyMap()
        }

        // 缓存：按 bomId 计算 timeNeedToComplete
        val bomIdsInScope = contentProcesses
            .mapNotNull { it.bom?.id }
            .distinct()

        val timeNeedToCompleteCache = if (bomIdsInScope.isNotEmpty()) {
            bomProcessRepository.sumDurationByBomIds(bomIdsInScope)
                .associate { it.bomId to it.totalMinutes.toInt() }
        } else {
            emptyMap()
        }

        // 缓存：Uom（itemUom -> uomConversion.udfudesc）
        val itemIdsInScope = contentProcesses
            .mapNotNull { it.item?.id }
            .distinct()

        val stockItemUoms = if (itemIdsInScope.isNotEmpty()) {
            itemUomRepository.findAllByItemIdInAndStockUnitIsTrueAndDeletedIsFalse(itemIdsInScope)
        } else {
            emptyList()
        }
        val itemUomByItemId = mutableMapOf<Long, com.ffii.fpsms.modules.master.entity.ItemUom>()
        stockItemUoms.forEach { iu ->
            val itemId = iu.item?.id ?: return@forEach
            itemUomByItemId.putIfAbsent(itemId, iu)
        }

        val uomIds = itemUomByItemId.values.mapNotNull { it.uom?.id }.distinct()
        val uomById = if (uomIds.isNotEmpty()) {
            uomConversionRepository.findAllByIdInAndDeletedFalse(uomIds).associateBy { it.id }
        } else {
            emptyMap()
        }
        val uomByItemId = itemUomByItemId.mapValues { (_, iu) -> uomById[iu.uom?.id]?.udfudesc }

        val content = contentProcesses.map { productProcess ->
            val jobOrderId = productProcess.jobOrder?.id ?: 0L
            val jobOrder = jobOrderById[jobOrderId]
            val stockInLine = stockInLineByJobOrderId[jobOrderId]
            val pickOrder = pickOrderByJobOrderId[jobOrderId]
            val pickOrderId = pickOrder?.id
            val joPickOrdersList = if (pickOrderId != null) joPickOrdersByPickOrderId[pickOrderId].orEmpty() else emptyList()

            val productProcessLines = linesByProcessId[productProcess.id ?: 0L].orEmpty()
            val finishedCount = productProcessLines.count {
                val s = it.status?.trim()?.lowercase()
                s == "completed" || s == "pass"
            }

            val bomIsDrink = productProcess.bom?.isDrink
            val matchStatus = if (joPickOrdersList.isNotEmpty() &&
                joPickOrdersList.all { it.matchStatus == JoPickOrderStatus.completed }
            ) {
                "completed"
            } else if (joPickOrdersList.any { it.matchStatus == JoPickOrderStatus.scanned }) {
                "scanned"
            } else {
                "pending"
            }

            val bomId = productProcess.bom?.id
            val timeNeedToComplete = bomId?.let { timeNeedToCompleteCache[it] } ?: 0
            val uom = productProcess.item?.id?.let { uomByItemId[it] }

            AllJoborderProductProcessInfoResponse(
                id = productProcess.id ?: 0L,
                productProcessCode = productProcess.productProcessCode,
                status = productProcess.status.value,
                startTime = productProcess.startTime,
                endTime = productProcess.endTime,
                isDrink = bomIsDrink,
                matchStatus = matchStatus,
                RequiredQty = jobOrder?.reqQty?.toInt() ?: 0,
                Uom = uom,
                lotNo = stockInLine?.lotNo,
                productionPriority = productProcess.productionPriority,
                date = productProcess.date,
                bomId = bomId,
                TimeNeedToComplete = timeNeedToComplete,
                assignedTo = pickOrder?.assignTo?.id,
                itemName = productProcess.item?.name,
                itemCode = productProcess.item?.code,
                bomDescription = productProcess.bom?.description,
                pickOrderId = pickOrder?.id,
                pickOrderStatus = pickOrder?.status?.value,
                jobOrderId = productProcess.jobOrder?.id,
                stockInLineId = stockInLine?.id,
                stockInLineStatus = stockInLine?.status,
                jobOrderCode = jobOrder?.code,
                productProcessLineCount = productProcessLines.size,
                FinishedProductProcessLineCount = finishedCount,
                lines = productProcessLines.map { line ->
                    ProductProcessInfoResponse(
                        id = line.id ?: 0,
                        operatorId = line.operator?.id,
                        operatorName = line.operator?.name ?: "",
                        equipmentId = line.equipment?.id,
                        equipmentName = line.equipmentType ?: "",
                        startTime = line.startTime,
                        endTime = line.endTime,
                        status = line.status ?: ""
                    )
                }
            )
        }

        return JobOrderProductProcessPageResponse(
            content = content,
            totalJobOrders = totalJobOrders,
            page = safePage,
            size = safeSize
        )
    }

    /**
     * FG QC / 上架 reminders: same eligibility as 完成QC工單 (qcReady): all lines Completed/Pass, stock-in exists and not completed/rejected;
     * only job orders that have a product process dated **today or yesterday** (server local date).
     */
    open fun findJobOrderFgQcAndPutAwayAlertsForTodayYesterday(): JobOrderFgAlertsResponse {
        val today = LocalDate.now()
        val yesterday = today.minusDays(1)

        val processesToday = productProcessRepository.findAllByDeletedIsFalseAndDate(today)
        val processesYesterday = productProcessRepository.findAllByDeletedIsFalseAndDate(yesterday)
        val candidateJobOrderIds = (processesToday + processesYesterday)
            .asSequence()
            .filter { it.jobOrder?.isHidden != true }
            .mapNotNull { it.jobOrder?.id }
            .distinct()
            .toList()

        if (candidateJobOrderIds.isEmpty()) {
            return JobOrderFgAlertsResponse(qc = emptyList(), putAway = emptyList())
        }

        val allProcesses = productProcessRepository.findByJobOrder_IdInAndDeletedIsFalse(candidateJobOrderIds)
        val allProcessIds = allProcesses.mapNotNull { it.id }
        val lines = if (allProcessIds.isNotEmpty()) {
            productProcessLineRepository.findByProductProcess_IdInWithOperatorAndEquipment(allProcessIds)
        } else {
            emptyList()
        }

        val linesByProcessId = lines.groupBy { it.productProcess.id ?: 0L }
        val processesByJobOrderId = allProcesses
            .filter { it.jobOrder?.id != null }
            .groupBy { it.jobOrder!!.id!! }

        val jobOrders = jobOrderRepository.findAllById(candidateJobOrderIds)
        val jobOrderById = jobOrders.associateBy { it.id }

        val qcRows = mutableListOf<JobOrderFgAlertRowResponse>()
        val putAwayRows = mutableListOf<JobOrderFgAlertRowResponse>()

        for (jobOrderId in candidateJobOrderIds) {
            val jobOrder = jobOrderById[jobOrderId] ?: continue
            if (jobOrder.status == JobOrderStatus.PLANNING) continue

            val stockInLine = stockInLineRepository.findFirstByJobOrder_IdAndDeletedFalse(jobOrderId) ?: continue
            if (stockInLine.purchaseOrderLine != null) continue

            val statusNorm = (stockInLine.status ?: "").trim().lowercase()
            if (statusNorm == "completed" || statusNorm == "rejected") continue

            val processes = processesByJobOrderId[jobOrderId].orEmpty()
            if (processes.isEmpty()) continue

            val jobOrderLines = processes.flatMap { p -> linesByProcessId[p.id ?: 0L].orEmpty() }
            val allLinesDone = jobOrderLines.isNotEmpty() &&
                jobOrderLines.all { it.status == "Completed" || it.status == "Pass" }
            if (!allLinesDone) continue

            val maxDate = processes.mapNotNull { it.date }.maxOrNull()
            val row = JobOrderFgAlertRowResponse(
                stockInLineId = stockInLine.id!!,
                jobOrderId = jobOrderId,
                jobOrderCode = jobOrder.code,
                itemNo = stockInLine.itemNo,
                itemName = stockInLine.item?.name,
                status = stockInLine.status,
                processDate = maxDate,
                lotNo = stockInLine.lotNo,
            )

            when (statusNorm) {
                "received", "partially_completed" -> putAwayRows.add(row)
                else -> qcRows.add(row)
            }
        }

        val sortByDate = compareByDescending<JobOrderFgAlertRowResponse> { it.processDate ?: LocalDate.MIN }
        return JobOrderFgAlertsResponse(
            qc = qcRows.sortedWith(sortByDate),
            putAway = putAwayRows.sortedWith(sortByDate),
        )
    }

    open fun updateProductProcessLineStartTime(productProcessLineId: Long): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
        productProcessLine.startTime = LocalDateTime.now()
        productProcessLineRepository.save(productProcessLine)
        return MessageResponse(
            id = productProcessLineId,
            code = "200",
            name = "ProductProcessLine StartTime Updated",
            type = "success",
            message = "ProductProcessLine StartTime Updated",
            errorPosition = null,
        )
    }

    open fun updateProductProcessLineEndTime(productProcessLineId: Long): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
        productProcessLine.endTime = LocalDateTime.now()
        productProcessLineRepository.save(productProcessLine)
        return MessageResponse(
            id = productProcessLineId,
            code = "200",
            name = "ProductProcessLine EndTime Updated",
            type = "success",
            message = "ProductProcessLine EndTime Updated",
            errorPosition = null,
        )
    }

    open fun updateProductProcessStartTime(productProcessId: Long): MessageResponse {
        val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
        productProcess.startTime = LocalDateTime.now()
        productProcessRepository.save(productProcess)
        return MessageResponse(
            id = productProcessId,
            code = "200",
            name = "ProductProcessLine StartTime Updated",
            type = "success",
            message = "ProductProcessLine StartTime Updated",
            errorPosition = null,
        )
    }

    open fun updateProductProcessEndTime(productProcessId: Long): MessageResponse {
        val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
        productProcess.endTime = LocalDateTime.now()
        productProcessRepository.save(productProcess)
        return MessageResponse(
            id = productProcessId,
            code = "200",
            name = "ProductProcess EndTime Updated",
            type = "success",
            message = "ProductProcess EndTime Updated",
            errorPosition = null,
        )
    }

    open fun updateProductProcessStatus(productProcessId: Long, status: ProductProcessStatus): MessageResponse {
        val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
        productProcess.status = status
        productProcessRepository.save(productProcess)
        return MessageResponse(
            id = productProcessId,
            code = "200",
            name = "ProductProcess Status Updated",
            type = "success",
            message = "ProductProcess Status Updated",
            errorPosition = null,
        )
    }

    private fun normalizeLineStatus(status: String?): String =
        status?.trim()?.lowercase()?.replace(" ", "") ?: ""

    private fun isLineDone(status: String?): Boolean {
        val n = normalizeLineStatus(status)
        return n == "completed" || n == "pass"
    }

    private fun isLineActive(status: String?): Boolean {
        val n = normalizeLineStatus(status)
        return n == "inprogress" || n == "paused"
    }

    /**
     * Align parent [ProductProcess.status] with all line states.
     * - All Completed/Pass -> completed (via [ifAllLinesCompletedOrPassed])
     * - Any line started but not all done -> in_progress
     * - Does not override STOPPED or CANCELLED on the parent.
     */
    open fun syncProductProcessStatusFromLines(productProcessId: Long) {
        if (productProcessId <= 0L) return
        val productProcess = productProcessRepository.findById(productProcessId).orElse(null) ?: return
        if (productProcess.status == ProductProcessStatus.STOPPED ||
            productProcess.status == ProductProcessStatus.CANCELLED
        ) {
            return
        }

        val lines = productProcessLineRepository.findByProductProcess_Id(productProcessId)
        if (lines.isEmpty()) return

        if (lines.all { isLineDone(it.status) }) {
            ifAllLinesCompletedOrPassed(productProcessId)
            return
        }

        val anyStarted = lines.any { line ->
            isLineActive(line.status) ||
                isLineDone(line.status) ||
                line.startTime != null
        }

        if (anyStarted) {
            if (productProcess.startTime == null) {
                productProcess.startTime =
                    lines.mapNotNull { it.startTime }.minOrNull() ?: LocalDateTime.now()
            }
            if (productProcess.status != ProductProcessStatus.IN_PROGRESS) {
                productProcess.status = ProductProcessStatus.IN_PROGRESS
            }
            productProcessRepository.save(productProcess)
        }
    }

    open fun StartProductProcessLine(productProcessLineId: Long): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
        updateProductProcessLineStartTime(productProcessLineId)
        updateProductProcessLineStatus(productProcessLineId, "InProgress")
        val productProcessId = productProcessLine?.productProcess?.id ?: 0L
        syncProductProcessStatusFromLines(productProcessId)
        return MessageResponse(
            id = productProcessLine?.id,
            code = "200",
            name = "ProductProcess StartTime Updated",
            type = "success",
            message = "ProductProcess StartTime Updated",
            errorPosition = null,
        )
    }

    open fun CompleteProductProcessLine(productProcessLineId: Long): MessageResponse {
        updateProductProcessLineEndTime(productProcessLineId)
        updateProductProcessLineStatus(productProcessLineId, "Completed")
        val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
        val productProcessId = productProcessLine?.productProcess?.id ?: 0L
        val allproductProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)
        if (productProcessLine.startTime == null) {
            productProcessLine.startTime = LocalDateTime.now()
            productProcessLineRepository.save(productProcessLine)
        }
        syncProductProcessStatusFromLines(productProcessId)
        return MessageResponse(
            id = productProcessLine.id,
            code = "200",
            name = "ProductProcess StartTime Updated",
            type = "success",
            message = "ProductProcess StartTime Updated",
            errorPosition = null,
        )
    }

    open fun ifAllLinesCompletedOrPassed(productProcessId: Long): MessageResponse {
        // 获取所有 product process lines
        val allproductProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)

        // 检查是否所有 lines 都是 "Completed" 或 "Pass"
        if (allproductProcessLines.all { it.status == "Completed" || it.status == "Pass" }) {
            // 更新 product process 的 endTime 和状态
            updateProductProcessEndTime(productProcessId)
            updateProductProcessStatus(productProcessId, ProductProcessStatus.COMPLETED)

            val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
            val jobOrder = jobOrderRepository.findById(productProcess?.jobOrder?.id ?: 0L).orElse(null)

            if (jobOrder != null) {
                jobOrder.status = JobOrderStatus.STORING
                jobOrderRepository.save(jobOrder)
                
                val existingSil = jobOrder.id?.let { stockInLineRepository.findFirstByJobOrder_IdAndDeletedFalse(it) }
                if (existingSil != null) {
                    stockInLineService.update(
                        SaveStockInLineRequest(
                            id = existingSil.id,
                            itemId = existingSil.item?.id ?: productProcess?.item?.id ?: 0L,
                            acceptedQty = jobOrder.reqQty ?: BigDecimal.ZERO,
                            acceptQty = jobOrder.reqQty ?: BigDecimal.ZERO,
                            productLotNo = jobOrder.code,
                            productionDate = LocalDate.now(),
                            jobOrderId = jobOrder.id,
                            expiryDate = null,
                            status = "pending",
                            receiptDate = null,
                        )
                    )
                } else {
                    stockInLineService.create(
                        SaveStockInLineRequest(
                            itemId = productProcess?.item?.id ?: 0L,
                            acceptedQty = jobOrder?.reqQty ?: BigDecimal.ZERO,
                            productLotNo = jobOrder?.code,
                            productionDate = LocalDate.now(),
                            jobOrderId = jobOrder.id,
                            acceptQty = jobOrder?.reqQty ?: BigDecimal.ZERO,
                            expiryDate = null,
                            status = "",
                        )
                    )
                }
                
            }
        }

        return MessageResponse(
            id = productProcessId,
            code = "200",
            name = "ProductProcess Check Completed",
            type = "success",
            message = "ProductProcess Check Completed",
            errorPosition = null,
        )
    }

    open fun SaveProductProcessIssueTime(request: SaveProductProcessIssueTimeRequest): MessageResponse {

        val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null)
        val productProcess = productProcessRepository.findById(productProcessLine.productProcess?.id ?: 0L).orElse(null)
        //val productProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessId)


        val startTime = productProcessLine?.startTime

        val stopTime = LocalDateTime.now()

        val operatorId = productProcessLine.operator?.id
        val Operator = userRepository.findById(operatorId).orElse(null)
        val reason = request.reason
        val productProcessIssue = ProductionProcessIssue().apply {
            this.productProcess = productProcess
            this.productProcessLineId = productProcessLine.id
            this.operator = productProcessLine.operator
            this.operatorName = Operator.name
            this.reason = reason
            this.status = "Paused"
            //this.productProcess = operator.name
            //this.startTime = startTime
            this.stopTime = stopTime
        }
        productionProcessIssueRepository.save(productProcessIssue)
        productProcessLine.status = "Paused"
        productProcessLineRepository.save(productProcessLine)
        return MessageResponse(
            id = 0,
            code = "200",
            name = "ProductProcess IssueTime Updated",
            type = "success",
            message = "ProductProcess IssueTime Updated",
            errorPosition = null,
        )
    }


    open fun SaveProductProcessResumeTime(productProcessIssueId: Long): MessageResponse {
        println("   Service: Saving ProductProcess Resume Time: $productProcessIssueId")
        val productProcessLineIssue = productionProcessIssueRepository.findById(productProcessIssueId).orElse(null)
        val productProcessLine =
            productProcessLineRepository.findById(productProcessLineIssue.productProcessLineId ?: 0L).orElse(null)
        val resumeTime = LocalDateTime.now()
        println("   Service: Resume Time: $resumeTime")
        productProcessLineIssue?.resumeTime = resumeTime
        println("   Service: Resume Time: $resumeTime")
        val totalTime =
            resumeTime.toEpochSecond(ZoneOffset.UTC) - (productProcessLineIssue.stopTime?.toEpochSecond(ZoneOffset.UTC)
                ?: 0L)
        productProcessLineIssue?.totalTime = totalTime.toInt()
        productProcessLineIssue?.status = "Resumed"
        productionProcessIssueRepository.save(productProcessLineIssue)
        productProcessLine.status = "InProgress"
        productProcessLineRepository.save(productProcessLine)
        syncProductProcessStatusFromLines(productProcessLine.productProcess?.id ?: 0L)

        return MessageResponse(
            id = 0,
            code = "200",
            name = "ProductProcess ResumeTime Updated",
            type = "success",
            message = "ProductProcess ResumeTime Updated",
            errorPosition = null,
        )

    }

    open fun UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTime(request: UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest): MessageResponse {
        val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null)
        val processingTime = request.processingTime
        val setupTime = request.setupTime
        val changeoverTime = request.changeoverTime
        if (processingTime != null) {
            productProcessLine.processingTime = processingTime
            productProcessLineRepository.save(productProcessLine)
        }
        if (setupTime != null) {
            productProcessLine.setupTime = setupTime
            productProcessLineRepository.save(productProcessLine)
        }
        if (changeoverTime != null) {
            productProcessLine.changeoverTime = changeoverTime
            productProcessLineRepository.save(productProcessLine)
        }
        return MessageResponse(
            id = request.productProcessLineId,
            code = "200",
            name = "ProductProcess ProcessingTimeSetupTimeChangeoverTime Updated",
            type = "success",
            message = "ProductProcess ProcessingTimeSetupTimeChangeoverTime Updated",
            errorPosition = null,
        )
    }

    open fun UpdateProductProcessPriority(productProcessId: Long, productionPriority: Int): MessageResponse {
        val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
        productProcess.productionPriority = productionPriority
        productProcessRepository.save(productProcess)
        return MessageResponse(
            id = productProcessId,
            code = "200",
            name = "ProductProcess Priority Updated",
            type = "success",
            message = "ProductProcess Priority Updated",
            errorPosition = null,
        )
    }

    open fun createNewProductProcessLine(productProcessLineId: Long): MessageResponse {
        val sourceLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
            ?: return MessageResponse(
                id = productProcessLineId,
                code = "404",
                name = "ProductProcess Line Not Found",
                type = "error",
                message = "ProductProcess Line with ID $productProcessLineId not found",
                errorPosition = null,
            )

        val productProcessId = sourceLine.productProcess?.id ?: return MessageResponse(
            id = productProcessLineId,
            code = "400",
            name = "Invalid ProductProcess",
            type = "error",
            message = "ProductProcess Line has no associated ProductProcess",
            errorPosition = null,
        )

        val originalSeqNo = sourceLine.seqNo ?: return MessageResponse(
            id = productProcessLineId,
            code = "400",
            name = "Invalid SeqNo",
            type = "error",
            message = "ProductProcess Line has no seqNo",
            errorPosition = null,
        )

        // 先获取同一 productProcess 的所有 lines（在更新前）
        val allLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)

        // 更新所有其他 seqNo >= originalSeqNo + 1 的 lines（不包括原 line）
        allLines.filter {
            it.id != sourceLine.id &&
                    it.seqNo != null &&
                    it.seqNo!! >= originalSeqNo + 1
        }.forEach { line ->
            line.seqNo = (line.seqNo ?: 0) + 1
            productProcessLineRepository.save(line)
        }

        // 创建新的 line，复制所有字段
        val newLine = ProductProcessLine().apply {
            // 复制基本字段
            this.productProcess = sourceLine.productProcess
            this.bomProcess = sourceLine.bomProcess
            this.operator = sourceLine.operator
            this.equipment = sourceLine.equipment
            this.equipmentDetailId = sourceLine.equipmentDetailId
            this.handler = sourceLine.handler
            this.seqNo = originalSeqNo + 1  // 新 line 的 seqNo = originalSeqNo + 1
            this.name = sourceLine.name
            this.description = sourceLine.description
            this.equipmentType = sourceLine.equipmentType
            this.status = "Pending"  // 新创建的 line 状态总是设为 Pending
            this.byproduct = sourceLine.byproduct
            this.byproductName = sourceLine.byproductName
            this.byproductQty = sourceLine.byproductQty
            this.byproductUom = sourceLine.byproductUom
            this.scrapQty = sourceLine.scrapQty
            this.scrapUom = sourceLine.scrapUom
            this.defectQty = sourceLine.defectQty
            this.defectUom = sourceLine.defectUom
            this.defectDescription = sourceLine.defectDescription
            this.defectQty2 = sourceLine.defectQty2
            this.defectUom2 = sourceLine.defectUom2
            this.defectDescription2 = sourceLine.defectDescription2
            this.defectQty3 = sourceLine.defectQty3
            this.defectUom3 = sourceLine.defectUom3
            this.defectDescription3 = sourceLine.defectDescription3
            this.outputFromProcessQty = sourceLine.outputFromProcessQty
            this.outputFromProcessUom = sourceLine.outputFromProcessUom
            this.processingTime = sourceLine.processingTime
            this.setupTime = sourceLine.setupTime
            this.changeoverTime = sourceLine.changeoverTime
            this.startTime = null
            this.endTime = null
            this.isOriginal = false
        }

        // 保存新 line（原 line 的 seqNo 保持不变，不需要更新）
        val savedNewLine = productProcessLineRepository.save(newLine)

        return MessageResponse(
            id = savedNewLine.id ?: productProcessLineId,
            code = "200",
            name = "ProductProcess Line Created",
            type = "success",
            message = "ProductProcess Line Created successfully with seqNo ${originalSeqNo + 1}",
            errorPosition = null,
        )
    }

    open fun getJobProcessStatus(
        date: LocalDate?,
        productProcessStatus: ProductProcessStatus?
    ): List<JobProcessStatusResponse> {
        val productProcesses = productProcessRepository.findAllByDeletedIsFalse()
            .let { list -> if (date == null) list else list.filter { it.date == date } }
            .let { list ->
                if (productProcessStatus == null) list
                else list.filter { it.status == productProcessStatus }
            }

        return productProcesses.mapNotNull { process ->
            val jobOrder = jobOrderRepository.findById(process.jobOrder?.id ?: 0L).orElse(null)

            // 仍然保留：PLANNING 不显示（如你也要显示，删掉这段）
            if (jobOrder?.status == JobOrderStatus.PLANNING) return@mapNotNull null

            val lines = productProcessLineRepository.findByProductProcess_Id(process.id ?: 0L)
                .sortedBy { it.seqNo }

            val firstStartTime = lines.firstOrNull { it.startTime != null }?.startTime
            val calculatedPlanEndTime = if (firstStartTime != null) {
                var totalRemainingMinutes = 0L
                lines.forEach { line ->
                    if (line.endTime == null) {
                        totalRemainingMinutes += (line.processingTime ?: 0).toLong()
                        totalRemainingMinutes += (line.setupTime ?: 0).toLong()
                        totalRemainingMinutes += (line.changeoverTime ?: 0).toLong()
                    }
                }
                firstStartTime.plusMinutes(totalRemainingMinutes)
            } else {
                jobOrder?.planEnd
            }

            JobProcessStatusResponse(
                jobOrderId = jobOrder?.id ?: 0L,
                jobOrderCode = jobOrder?.code ?: "",
                itemCode = process.item?.code ?: "",
                itemName = process.item?.name ?: "",
                status = process.status?.value ?: "",
                planEndTime = calculatedPlanEndTime,
                processes = lines.map { line ->
                        val equipmentName = try {
                            line.bomProcess?.equipment?.name
                        } catch (_: jakarta.persistence.EntityNotFoundException) {
                            null
                        }

                        val equipmentDetailName = line.equipmentDetailId?.let { id ->
                            equipmentDetailRepository.findById(id).orElse(null)?.name
                        }

                        ProcessStatusInfo(
                            processName = line.name,
                            equipmentName = equipmentName,
                            equipmentDetailName = equipmentDetailName,
                            handlerName = line.handler?.name,
                            startTime = line.startTime,
                            endTime = line.endTime,
                            processingTime = line.processingTime,
                            setupTime = line.setupTime,
                            changeoverTime = line.changeoverTime,
                            isRequired = true
                        )
                    }
            )
        }
    }

    private fun findNewCreatedLineIds(productProcessId: Long, bomId: Long): Set<Long> {
        // 获取 BOM 的所有 bomProcess，创建一个映射：bomProcessId -> seqNo
        val bomProcessMap = bomProcessRepository.findByBomId(bomId)
            .associate { it.id to (it.seqNo ?: 0L) }

        if (bomProcessMap.isEmpty()) {
            return emptySet()
        }

        // 获取所有 line，按 seqNo 排序
        val allLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)
            .sortedBy { it.seqNo ?: 0L }

        //println("=== findNewCreatedLineIds DEBUG START ===")
        //println("BOM bomProcessMap: $bomProcessMap")
        //println("All lines (sorted by seqNo):")
        allLines.forEach { line ->
            //println("  id=${line.id}, seqNo=${line.seqNo}, bomProcessId=${line.bomProcess?.id}")
        }

        // 创建一个集合来跟踪哪些 line 是新创建的
        val newCreatedLineIds = mutableSetOf<Long>()

        // 迭代检查，直到所有剩余的 line 都匹配
        var iteration = 0
        var hasChanges = true
        while (hasChanges) {
            iteration++
            hasChanges = false

            //println("\n--- Iteration $iteration ---")

            // 获取剩余的 line（排除已标记为新创建的），按 seqNo 排序
            val remainingLines = allLines.filter { it.id !in newCreatedLineIds }
                .sortedBy { it.seqNo ?: 0L }

            //println("Remaining lines (excluding new created):")
            remainingLines.forEach { line ->
                //println("  id=${line.id}, seqNo=${line.seqNo}, bomProcessId=${line.bomProcess?.id}")
            }

            //println("New created line IDs so far: $newCreatedLineIds")

            // 计算每个剩余 line 的期望 seqNo（应该是连续的 1, 2, 3...）
            val expectedSeqNoMap = remainingLines.mapIndexed { index, line ->
                line.id to (index + 1).toLong()
            }.toMap()

            println("Expected seqNo map:")
            expectedSeqNoMap.forEach { (lineId, expectedSeqNo) ->
                println("  lineId=$lineId -> expectedSeqNo=$expectedSeqNo")
            }

            // 检查每个剩余 line
            for (line in remainingLines) {
                val bomProcessId = line.bomProcess?.id
                val expectedSeqNo = expectedSeqNoMap[line.id] ?: continue

                //println("\nChecking line id=${line.id}, seqNo=${line.seqNo}, bomProcessId=$bomProcessId, expectedSeqNo=$expectedSeqNo")

                if (bomProcessId == null) {
                    println("  -> No bomProcessId, marking as new created")
                    newCreatedLineIds.add(line.id ?: 0L)
                    hasChanges = true
                    continue
                }

                // 查找这个 bomProcessId 在 BOM 中的实际 seqNo
                val bomProcessSeqNo = bomProcessMap[bomProcessId]

                //println("  -> BOM bomProcessId=$bomProcessId has seqNo=$bomProcessSeqNo in BOM")

                if (bomProcessSeqNo == null) {
                    println("  -> bomProcessId not found in BOM, marking as new created")
                    newCreatedLineIds.add(line.id ?: 0L)
                    hasChanges = true
                    continue
                }

                // 检查 line 的期望 seqNo 是否匹配 BOM 中 bomProcess 的 seqNo
                if (expectedSeqNo != bomProcessSeqNo) {
                    println("  -> MISMATCH: expectedSeqNo=$expectedSeqNo != bomProcessSeqNo=$bomProcessSeqNo, marking as new created")
                    newCreatedLineIds.add(line.id ?: 0L)
                    hasChanges = true
                } else {
                    println("  -> MATCH: expectedSeqNo=$expectedSeqNo == bomProcessSeqNo=$bomProcessSeqNo, keeping as original")
                }
            }
        }
        //println("\n=== Final Result ===")
        //println("New created line IDs: $newCreatedLineIds")
        println("=== findNewCreatedLineIds DEBUG END ===\n")

        return newCreatedLineIds


    }

    // ===== Drink Production Qty Dashboard =====
    /** FP-MTMS Version Checklist | Functions Ref. No. 27 | v1.0.0 | 2026-07-20 */
    open fun getDrinkProductionQty(date: LocalDate?): List<DrinkProductionQtyResponse> {
        val targetDate = date ?: LocalDate.now()
        val startOfDay = targetDate.atStartOfDay()
        val endOfDayExclusive = targetDate.plusDays(1).atStartOfDay()

        // 依「生產日」彙總：與既有 dashboard 的用法一致，採半開區間 [startOfDay, endExclusive)
        val jobOrders = jobOrderRepository.findByDeletedFalseAndPlanStartFromBeforeExclusiveOrderByIdAsc(
            planStartFrom = startOfDay,
            planStartToExclusive = endOfDayExclusive,
        )

        if (jobOrders.isEmpty()) {
            return emptyList()
        }

        data class GroupKey(
            val itemCode: String?,
            val itemName: String?,
        )

        data class DrinkOrderRow(
            val itemCode: String?,
            val itemName: String?,
            val uom: String?,
            val reqQty: BigDecimal,
            val productionQty: BigDecimal,
            val jobOrderId: Long?,
            val jobOrderCode: String?,
            val productionDate: LocalDate?,
        )

        val drinkOrders = jobOrders
            .filter { it.bom?.isDrink == true }
            .filter { it.status != JobOrderStatus.PLANNING }

        if (drinkOrders.isEmpty()) {
            return emptyList()
        }

        val jobOrderIds = drinkOrders.mapNotNull { it.id }
        val itemIds = drinkOrders.mapNotNull { it.bom?.item?.id }.distinct()

        val processesByJobOrderId = productProcessRepository
            .findByJobOrder_IdInAndDeletedIsFalse(jobOrderIds)
            .groupBy { it.jobOrder?.id }
            .mapValues { (_, processes) -> processes.firstOrNull() }

        val processIds = processesByJobOrderId.values.mapNotNull { it?.id }.distinct()
        val linesByProcessId = if (processIds.isNotEmpty()) {
            productProcessLineRepository.findByProductProcess_IdIn(processIds)
                .groupBy { it.productProcess?.id ?: 0L }
        } else {
            emptyMap()
        }

        val stockInLineByJobOrderId = if (jobOrderIds.isNotEmpty()) {
            stockInLineRepository.findAllByJobOrder_IdInAndDeletedFalse(jobOrderIds)
                .associateBy { it.jobOrder?.id }
        } else {
            emptyMap()
        }

        val stockItemUoms = if (itemIds.isNotEmpty()) {
            itemUomRepository.findAllByItemIdInAndStockUnitIsTrueAndDeletedIsFalse(itemIds)
        } else {
            emptyList()
        }
        val itemUomByItemId = mutableMapOf<Long, com.ffii.fpsms.modules.master.entity.ItemUom>()
        stockItemUoms.forEach { iu ->
            val itemId = iu.item?.id ?: return@forEach
            itemUomByItemId.putIfAbsent(itemId, iu)
        }
        val uomIds = itemUomByItemId.values.mapNotNull { it.uom?.id }.distinct()
        val uomById = if (uomIds.isNotEmpty()) {
            uomConversionRepository.findAllByIdInAndDeletedFalse(uomIds).associateBy { it.id }
        } else {
            emptyMap()
        }
        val uomDescByItemId = itemUomByItemId.mapValues { (_, iu) ->
            uomById[iu.uom?.id]?.udfudesc
        }

        val drinkRows = drinkOrders.map { jo ->
            val bom = jo.bom
            val itemId = bom?.item?.id
            val process = processesByJobOrderId[jo.id]
            val lines = process?.id?.let { linesByProcessId[it].orEmpty() }.orEmpty()
            val stockInLine = stockInLineByJobOrderId[jo.id]

            DrinkOrderRow(
                itemCode = bom?.item?.code,
                itemName = bom?.item?.name,
                uom = resolveDrinkProductionUom(bom, itemId, uomDescByItemId),
                reqQty = jo.reqQty ?: BigDecimal.ZERO,
                productionQty = resolveDrinkProductionQty(jo, lines, stockInLine),
                jobOrderId = jo.id,
                jobOrderCode = jo.code,
                productionDate = jo.planStart?.toLocalDate(),
            )
        }

        return drinkRows
            .groupBy { GroupKey(it.itemCode, it.itemName) }
            .map { (key, rows) ->
                DrinkProductionQtyResponse(
                    itemCode = key.itemCode,
                    itemName = key.itemName,
                    uom = rows.firstNotNullOfOrNull { it.uom?.takeIf { u -> u.isNotBlank() } },
                    totalReqQty = rows.fold(BigDecimal.ZERO) { acc, row ->
                        acc + row.reqQty
                    },
                    totalQty = rows.fold(BigDecimal.ZERO) { acc, row ->
                        acc + row.productionQty
                    },
                    jobOrders = rows.map { row ->
                        DrinkProductionQtyJobOrderDetail(
                            jobOrderId = row.jobOrderId ?: 0L,
                            jobOrderCode = row.jobOrderCode,
                            productionDate = row.productionDate,
                            reqQty = row.reqQty,
                            productionQty = row.productionQty,
                        )
                    }.sortedBy { it.jobOrderCode ?: "" },
                )
            }
            .sortedWith(
                compareBy<DrinkProductionQtyResponse> { it.itemCode ?: "" }
                    .thenBy { it.itemName ?: "" }
            )
    }

    private fun resolveDrinkProductionUom(
        bom: Bom?,
        itemId: Long?,
        uomDescByItemId: Map<Long, String?>,
    ): String? {
        bom?.outputQtyUom?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
        bom?.excelUom?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
        itemId?.let { uomDescByItemId[it] }?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
        return null
    }

    /** 實際生產數量：優先工序產出 → QC 入庫接受量 → 工單 reqQty（當日計劃生產量） */
    private fun resolveDrinkProductionQty(
        jobOrder: com.ffii.fpsms.modules.jobOrder.entity.JobOrder,
        lines: List<ProductProcessLine>,
        stockInLine: com.ffii.fpsms.modules.stock.entity.StockInLine?,
    ): BigDecimal {
        val lineOutputQty = lines
            .sortedByDescending { it.seqNo ?: 0L }
            .firstNotNullOfOrNull { line ->
                line.outputFromProcessQty?.takeIf { it > 0 }?.toBigDecimal()
            }
        if (lineOutputQty != null) {
            return lineOutputQty
        }

        val acceptedQty = stockInLine?.acceptedQty
        if (acceptedQty != null && acceptedQty > BigDecimal.ZERO) {
            return acceptedQty
        }

        return jobOrder.reqQty ?: BigDecimal.ZERO
    }

    // ===== Operator KPI Dashboard =====
    open fun getOperatorKpi(date: LocalDate?): List<OperatorKpiResponse> {
        val targetDate = date ?: LocalDate.now()
        val startOfDay = targetDate.atStartOfDay()
        val endOfDay = targetDate.plusDays(1).atStartOfDay().minusSeconds(1)

        // 找出在該日期內有重疊的所有 line
        val lines = productProcessLineRepository.findAllOverlappingWithDateRange(startOfDay, endOfDay)

        if (lines.isEmpty()) {
            return emptyList()
        }

        // 依 operator 分組，只保留有 operator 的 line
        val linesByOperator = lines
            .filter { it.operator?.id != null }
            .groupBy { it.operator!!.id!! }

        return linesByOperator.map { (operatorId, operatorLines) ->
            val operator = userRepository.findById(operatorId).orElse(null)

            // 將所有 interval 截斷到當天範圍內，並合併重疊區間
            val intervals = operatorLines.mapNotNull { line ->
                val start = line.startTime ?: return@mapNotNull null
                val rawEnd = line.endTime ?: LocalDateTime.now()

                val effectiveStart = if (start.isBefore(startOfDay)) startOfDay else start
                val effectiveEnd = if (rawEnd.isAfter(endOfDay)) endOfDay else rawEnd

                if (effectiveEnd.isBefore(effectiveStart)) {
                    null
                } else {
                    effectiveStart to effectiveEnd
                }
            }.sortedBy { it.first }

            var mergedTotalMinutes = 0L
            var currentStart: LocalDateTime? = null
            var currentEnd: LocalDateTime? = null

            for ((s, e) in intervals) {
                if (currentStart == null) {
                    currentStart = s
                    currentEnd = e
                } else if (!s.isAfter(currentEnd)) {
                    // 重疊或相鄰，擴展 end
                    if (e.isAfter(currentEnd)) {
                        currentEnd = e
                    }
                } else {
                    // 不重疊，結束上一段
                    mergedTotalMinutes += ChronoUnit.MINUTES.between(currentStart, currentEnd)
                    currentStart = s
                    currentEnd = e
                }
            }
            if (currentStart != null && currentEnd != null) {
                mergedTotalMinutes += ChronoUnit.MINUTES.between(currentStart, currentEnd)
            }

            val currentProcesses = operatorLines
                .filter { it.endTime == null || (it.status != null && it.status != "Completed" && it.status != "Pass") }
                .map { line ->
                    val productProcess = line.productProcess
                    val jobOrder = productProcess.jobOrder
                    val bom = jobOrder?.bom 
                    val equipmentDetail = line.equipmentDetailId?.let { id ->
                        equipmentDetailRepository.findByIdAndDeletedFalse(id)
                    }

                    OperatorKpiProcessInfo(
                        jobOrderId = jobOrder?.id,
                        jobOrderCode = jobOrder?.code,
                        productProcessId = productProcess.id,
                        productProcessLineId = line.id,
                        processName = line.name,
                        equipmentName = line.equipmentType,
                        equipmentDetailName = equipmentDetail?.name,
                        startTime = line.startTime,
                        endTime = line.endTime,
                        processingTime = line.processingTime,
                        itemCode = bom?.item?.code,
                        itemName = bom?.item?.name,

                    )
                }
                val totalJobOrderCount = operatorLines
                .mapNotNull { it.productProcess.jobOrder?.id }
                .distinct()
                .size
            OperatorKpiResponse(
                operatorId = operatorId,
                operatorName = operator?.name,
                staffNo = operator?.staffNo,
                totalProcessingMinutes = mergedTotalMinutes,
                totalJobOrderCount = totalJobOrderCount,
                currentProcesses = currentProcesses
            )
        }.sortedByDescending { it.totalProcessingMinutes }
    }

    // ===== Equipment Status Dashboard =====
    open fun getEquipmentStatusByType(): List<EquipmentStatusByTypeResponse> {
        // 讀取所有未刪除的設備明細
        val details = equipmentDetailRepository.findAllByDeletedFalse()
        if (details.isEmpty()) {
            return emptyList()
        }

        // 預先查詢所有處於進行中的 line，以減少 DB round-trip
        val detailIdToLines: Map<Long, List<ProductProcessLine>> = details
            .mapNotNull { it.id }
            .associateWith { detailId ->
                productProcessLineRepository.findByEquipmentDetailIdAndDeletedFalseAndEndTimeIsNull(detailId)
            }

        // 取得所有關聯的 equipmentTypeId -> equipment (爐類型)
        val equipmentTypeIdSet = details.mapNotNull { it.equipmentTypeId }.toSet()
        val equipmentsByTypeId: Map<Long, Equipment> = equipmentRepository.findAll()
            .filter { equipmentTypeIdSet.contains(it.id) }
            .associateBy { it.id!! }

        val perDetail = details.mapNotNull { detail ->
            val detailId = detail.id ?: return@mapNotNull null
            val runningLines = detailIdToLines[detailId].orEmpty()
            val activeLine = runningLines.maxByOrNull { it.startTime ?: LocalDateTime.MIN }

            val equipmentTypeId = detail.equipmentTypeId
            val equipment = equipmentTypeId?.let { equipmentsByTypeId[it] }

            val (status, currentProcessInfo) = if (activeLine != null) {
                val process = activeLine.productProcess
                val jobOrder = process.jobOrder
                val operator = activeLine.operator

                "Processing" to EquipmentStatusProcessInfo(
                    jobOrderId = jobOrder?.id,
                    jobOrderCode = jobOrder?.code,
                    productProcessId = process.id,
                    productProcessLineId = activeLine.id,
                    processName = activeLine.name,
                    operatorName = operator?.name,
                    startTime = activeLine.startTime,
                    processingTime = activeLine.processingTime
                )
            } else {
                val repairStatus = detail.repairAndMaintenanceStatus
                val statusStr = if (repairStatus == true) "Repair" else "Idle"
                statusStr to null
            }

            EquipmentStatusPerDetail(
                equipmentDetailId = detailId,
                equipmentDetailCode = detail.code,
                equipmentDetailName = detail.name,
                equipmentId = equipment?.id,
                equipmentTypeName = equipment?.name,
                status = status,
                repairAndMaintenanceStatus = detail.repairAndMaintenanceStatus,
                latestRepairAndMaintenanceDate = detail.latestRepairAndMaintenanceDate,
                lastRepairAndMaintenanceDate = detail.lastRepairAndMaintenanceDate,
                repairAndMaintenanceRemarks = detail.repairAndMaintenanceRemarks,
                currentProcess = currentProcessInfo
            )
        }

        return perDetail
            .groupBy { it.equipmentId ?: -1L }
            .map { (equipmentId, list) ->
                val equipment = equipmentId.takeIf { it != -1L }?.let { id ->
                    equipmentsByTypeId[id]
                }
                EquipmentStatusByTypeResponse(
                    equipmentTypeId = equipmentId,
                    equipmentTypeName = equipment?.name,
                    details = list.sortedBy { it.equipmentDetailCode }
                )
            }


    }
        // 辅助函数：判断是否是原始创建的 line（使用缓存的结果）
        private fun isOriginalLine(productProcessLine: ProductProcessLine, newCreatedLineIds: Set<Long>): Boolean {
            val currentLineId = productProcessLine.id ?: 0L
            return currentLineId !in newCreatedLineIds
        }

        open fun deleteProductProcessLine(productProcessLineId: Long): MessageResponse {
            val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
                ?: return MessageResponse(
                    id = productProcessLineId,
                    code = "404",
                    name = "ProductProcess Line Not Found",
                    type = "error",
                    message = "ProductProcess Line with ID $productProcessLineId not found",
                    errorPosition = null,
                )

            val productProcessId = productProcessLine.productProcess?.id ?: return MessageResponse(
                id = productProcessLineId,
                code = "400",
                name = "Invalid ProductProcess",
                type = "error",
                message = "ProductProcess Line has no associated ProductProcess",
                errorPosition = null,
            )

            // 检查 JobOrder 状态
            val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
            val jobOrder = productProcess?.jobOrder
            if (jobOrder?.status != JobOrderStatus.PLANNING) {
                return MessageResponse(
                    id = productProcessLineId,
                    code = "400",
                    name = "JobOrder Not In Planning",
                    type = "error",
                    message = "Cannot delete line when JobOrder is not in planning status",
                    errorPosition = null,
                )
            }

            // 检查是否是原始 line（使用 ID 判断）
            val bomId = productProcess?.bom?.id ?: return MessageResponse(
                id = productProcessLineId,
                code = "400",
                name = "Invalid BOM",
                type = "error",
                message = "ProductProcess has no associated BOM",
                errorPosition = null,
            )

            // 获取新创建的 line IDs
            val newCreatedLineIds = findNewCreatedLineIds(productProcessId, bomId)

            // 检查是否是原始 line
            if (productProcessLine.isOriginal == true) {
                return MessageResponse(
                    id = productProcessLineId,
                    code = "400",
                    name = "Cannot Delete Original Line",
                    type = "error",
                    message = "Cannot delete original process line. Only newly created lines can be deleted.",
                    errorPosition = null,
                )
            }

            val deletedSeqNo = productProcessLine.seqNo ?: 0L

            // 删除 line
            productProcessLineRepository.delete(productProcessLine)

            // 获取所有剩余的 lines，按 seqNo 排序，然后调整 seqNo
            val remainingLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)
                .sortedBy { it.seqNo ?: 0L }

            // 更新所有 seqNo > deletedSeqNo 的 lines，将它们的 seqNo 减 1
            remainingLines.filter {
                it.seqNo != null &&
                        it.seqNo!! > deletedSeqNo
            }.forEach { line ->
                line.seqNo = (line.seqNo ?: 0) - 1
                productProcessLineRepository.save(line)
            }

            return MessageResponse(
                id = productProcessLineId,
                code = "200",
                name = "ProductProcess Line Deleted",
                type = "success",
                message = "ProductProcess Line deleted successfully",
                errorPosition = null,
            )
        }
    }


