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

import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrder
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecord
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecordRepository
import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService
import com.ffii.fpsms.modules.user.service.UserService
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus
import java.time.LocalDateTime
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderListForPrintQrCodeResponse
import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest
import java.util.Optional
import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanIssueRequest
import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue
import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository
import java.math.BigDecimal
import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus
import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.jobOrder.web.model.AllJoPickOrderResponse
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.productProcess.entity.ProductProcessRepository
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.productProcess.entity.ProductProcessLineRepository
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderProcessRepository
import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderLotsHierarchicalResponse
import com.ffii.fpsms.modules.jobOrder.web.model.PickOrderInfoResponse
import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderBasicInfoResponse
import com.ffii.fpsms.modules.jobOrder.web.model.PickOrderLineWithLotsResponse
import com.ffii.fpsms.modules.jobOrder.web.model.LotDetailResponse
import com.ffii.fpsms.modules.jobOrder.web.model.StockOutLineDetailResponse

import com.ffii.fpsms.modules.stock.entity.enum.StockInLineStatus
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import java.time.LocalDate
import com.ffii.fpsms.modules.jobOrder.web.model.MaterialPickStatusItem
@Service
open class JoPickOrderService(
    private val joPickOrderRepository: JoPickOrderRepository,
    private val joPickOrderRecordRepository: JoPickOrderRecordRepository,
    private val pickOrderService: PickOrderService,
    private val userService: UserService,
    private val pickOrderRepository: PickOrderRepository,
    private val jdbcDao: JdbcDao,
    private val pickExecutionIssueRepository: PickExecutionIssueRepository,
    private val pickOrderLineRepository: PickOrderLineRepository,
    private val suggestPickLotRepository: SuggestPickLotRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val stockOutLineRepository: StockOutLIneRepository,
    private val jobOrderRepository: JobOrderRepository,
    private val jobTypeRepository: JobTypeRepository,
    private val itemsRepository: ItemsRepository,
    private val productProcessRepository: ProductProcessRepository,
    private val productProcessLineRepository: ProductProcessLineRepository,
    private val stockOutRepository: StockOutRepository,
    private val jobOrderProcessRepository: JobOrderProcessRepository,
    private val stockInLineRepository: StockInLineRepository

) {
    
    open fun save(record: JoPickOrder): JoPickOrder {
        return joPickOrderRepository.save(record)
    }
    
    open fun saveRecord(record: JoPickOrderRecord): JoPickOrderRecord {
        return joPickOrderRecordRepository.save(record)
    }
    
    //  Update JoPickOrder status to released
    open fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long?, matchStatus: String?): List<JoPickOrder> {
        val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId)

        // Get pick order details to populate new fields
        val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
        
        joPickOrders.forEach { 
            it.handledBy = userId
            if (matchStatus != null) {               // 只在有传入状态时才修改
                it.matchStatus = JoPickOrderStatus.valueOf(matchStatus)
            }// Set initial status
            
            // Set ticket release time (similar to pick order released date)
            it.ticketReleaseTime = LocalDateTime.now()
            
            // Populate additional fields from pick order
            pickOrder?.let { po ->
                it.pickOrderCode = po.code
                it.targetDate = po.targetDate
                it.jobOrderId = po.jobOrder?.id
                it.jobOrderCode = po.jobOrder?.code
            }
        }
        return joPickOrderRepository.saveAll(joPickOrders)
    }
    //  Complete JoPickOrder
    open fun completeJoPickOrdersForPickOrder(pickOrderId: Long): List<JoPickOrder> {
        val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId)
        joPickOrders.forEach { 
            it.matchStatus = JoPickOrderStatus.completed
            it.ticketCompleteTime = LocalDateTime.now() // Set complete time
        }
        return joPickOrderRepository.saveAll(joPickOrders)
    }
    
    //  Update JoPickOrderRecord status to released
    open fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long?): List<JoPickOrderRecord> {
        val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId)

        // Get pick order details to populate nw fields
        val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
        
        joPickOrderRecords.forEach { 
            it.handledBy = userId
            it.matchStatus = JoPickOrderStatus.pending // Set initial status
            
            // Set ticket release time
            it.ticketReleaseTime = LocalDateTime.now()
            
            // Populate additional fields from pick order
            pickOrder?.let { po ->
                it.pickOrderCode = po.code
                it.targetDate = po.targetDate
                it.jobOrderId = po.jobOrder?.id
                it.jobOrderCode = po.jobOrder?.code
            }
        }
        return joPickOrderRecordRepository.saveAll(joPickOrderRecords)
    }
    
    //  Complete JoPickOrderRecord
    open fun completeJoPickOrderRecordsForPickOrder(pickOrderId: Long): List<JoPickOrderRecord> {
        val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId)
        joPickOrderRecords.forEach { 
            it.matchStatus = JoPickOrderStatus.completed
            it.ticketCompleteTime = LocalDateTime.now() // Set complete time
        }
        return joPickOrderRecordRepository.saveAll(joPickOrderRecords)
    }
    
    //  Find JoPickOrder records by pick order ID
    open fun findByPickOrderId(pickOrderId: Long): List<JoPickOrder> {
        return joPickOrderRepository.findByPickOrderId(pickOrderId)
    }
    
    //  Find JoPickOrderRecord records by pick order ID
    open fun findRecordsByPickOrderId(pickOrderId: Long): List<JoPickOrderRecord> {
        return joPickOrderRecordRepository.findByPickOrderId(pickOrderId)
    }
    
    //  Create JoPickOrder records for a pick order
    @Transactional
    open fun createJoPickOrdersForPickOrder(pickOrderId: Long, itemIds: List<Long>): List<JoPickOrder> {
        // Get pick order and item details
        val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
        
        val joPickOrders = itemIds.map { itemId ->
            // Get item code from database - 修复：使用 queryForList
            val itemCodeResult = jdbcDao.queryForList(
                "SELECT code FROM fpsmsdb.items WHERE id = :itemId",
                mapOf("itemId" to itemId)
            )
            val itemCode = if (itemCodeResult.isNotEmpty()) {
                itemCodeResult.first()["code"] as String?
            } else {
                null
            }
            
            JoPickOrder(
                pickOrderId = pickOrderId,
                itemId = itemId,
                matchStatus = JoPickOrderStatus.pending,
                handledBy = null,
                jobOrderId = pickOrder?.jobOrder?.id,
                jobOrderCode = pickOrder?.jobOrder?.code,
                targetDate = pickOrder?.targetDate,
                itemCode = itemCode,
                pickOrderCode = pickOrder?.code
            )
        }
        return joPickOrderRepository.saveAll(joPickOrders)
    }
    //  Create JoPickOrderRecord records for a pick order
    @Transactional
    open fun createJoPickOrderRecordsForPickOrder(pickOrderId: Long, itemIds: List<Long>): List<JoPickOrderRecord> {
        // Get pick order and item details
        val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
        
        val joPickOrderRecords = itemIds.map { itemId ->
            // Get item code from database - 修复：使用 queryForList
            val itemCodeResult = jdbcDao.queryForList(
                "SELECT code FROM fpsmsdb.items WHERE id = :itemId",
                mapOf("itemId" to itemId)
            )
            val itemCode = if (itemCodeResult.isNotEmpty()) {
                itemCodeResult.first()["code"] as String?
            } else {
                null
            }
            
            JoPickOrderRecord(
                pickOrderId = pickOrderId,
                itemId = itemId,
                matchStatus = JoPickOrderStatus.pending,
                handledBy = null,
                jobOrderId = pickOrder?.jobOrder?.id,
                jobOrderCode = pickOrder?.jobOrder?.code,
                targetDate = pickOrder?.targetDate,
                itemCode = itemCode,
                pickOrderCode = pickOrder?.code,

            )
        }
        return joPickOrderRecordRepository.saveAll(joPickOrderRecords)
    }
    open fun getAllJobOrderLotsWithDetailsHierarchical(userId: Long): JobOrderLotsHierarchicalResponse {
        println("=== Debug: getAllJobOrderLotsWithDetailsHierarchical ===")
        println("today: ${LocalDate.now()}")
        println("userId filter: $userId")
        
        // Get all pick order IDs assigned to the user (both RELEASED and PENDING with joId)
        val user = userService.find(userId).orElse(null)
        if (user == null) {
            println("❌ User not found: $userId")
            // ✅ 修复：返回 JobOrderLotsHierarchicalResponse
            return JobOrderLotsHierarchicalResponse(
                pickOrder = PickOrderInfoResponse(
                    id = null,
                    code = null,
                    consoCode = null,
                    targetDate = null,
                    type = null,
                    status = null,
                    assignTo = null,
                    jobOrder = JobOrderBasicInfoResponse(0, "", "")
                ),
                pickOrderLines = emptyList()
            )
        }
        val statusList = listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED)

        // Get all pick orders assigned to user with PENDING or RELEASED status that have joId
        val allAssignedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn(
            userId,
            statusList
        ).filter { it.jobOrder != null } // Only pick orders with joId

        println("    DEBUG: Found ${allAssignedPickOrders.size} job order pick orders assigned to user $userId")

        // Filter based on assignment and status
        val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) {
            // Check if there are any RELEASED orders assigned to this user (active work)
            val assignedReleasedOrders = allAssignedPickOrders.filter {
                it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId
            }

            if (assignedReleasedOrders.isNotEmpty()) {
                // If there are assigned RELEASED orders, show only those
                println("    DEBUG: Found ${assignedReleasedOrders.size} assigned RELEASED job orders, showing only those")
                assignedReleasedOrders
            } else {
                // If no assigned RELEASED orders, show only the latest COMPLETED order
                val completedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.COMPLETED }
                if (completedOrders.isNotEmpty()) {
                    val latestCompleted =
                        completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN }
                    println("    DEBUG: No assigned RELEASED job orders, showing latest completed order: ${latestCompleted?.code}")
                    listOfNotNull(latestCompleted)
                } else {
                    println("    DEBUG: No job orders found")
                    emptyList()
                }
            }
        } else {
            emptyList()
        }

        val pickOrderIds = filteredPickOrders.map { it.id!! }
        println("   Job Order Pick order IDs to fetch: $pickOrderIds")

        if (pickOrderIds.isEmpty()) {
            return JobOrderLotsHierarchicalResponse(
                pickOrder = PickOrderInfoResponse(
                    id = null,
                    code = null,
                    consoCode = null,
                    targetDate = null,
                    type = null,
                    status = null,
                    assignTo = null,
                    jobOrder = JobOrderBasicInfoResponse(0, "", "")
                ),
                pickOrderLines = emptyList()
            )
        }

        // 使用 Repository 获取数据
        try {
            val pickOrders = pickOrderRepository.findAllByIdIn(pickOrderIds)
                .filter { it.deleted == false && it.assignTo?.id == userId }

            if (pickOrders.isEmpty()) {
                return JobOrderLotsHierarchicalResponse(
                    pickOrder = PickOrderInfoResponse(
                        id = null,
                        code = null,
                        consoCode = null,
                        targetDate = null,
                        type = null,
                        status = null,
                        assignTo = null,
                        jobOrder = JobOrderBasicInfoResponse(0, "", "")
                    ),
                    pickOrderLines = emptyList()
                )
            }

            val pickOrder = pickOrders.first() // 取第一个（应该只有一个）
            val jobOrder = pickOrder.jobOrder ?: return JobOrderLotsHierarchicalResponse(
                pickOrder = PickOrderInfoResponse(
                    id = null,
                    code = null,
                    consoCode = null,
                    targetDate = null,
                    type = null,
                    status = null,
                    assignTo = null,
                    jobOrder = JobOrderBasicInfoResponse(0, "", "")
                ),
                pickOrderLines = emptyList()
            )
            // 获取 pick order lines
            val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!)
                .filter { it.deleted == false }

            // 获取所有 pick order line IDs
            val pickOrderLineIds = pickOrderLines.map { it.id!! }

            // 获取 suggested pick lots
            val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) {
                suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds)
                    .filter { it.deleted == false }
            } else {
                emptyList()
            }

            // 获取所有 inventory lot line IDs
            val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id }

            // 获取 inventory lot lines
            val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) {
                inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds)
                    .filter { it.deleted == false }
            } else {
                emptyList()
            }

            // 获取 inventory lots
            val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct()
            val inventoryLots = if (inventoryLotIds.isNotEmpty()) {
                inventoryLotRepository.findAllByIdIn(inventoryLotIds)
                    .filter { it.deleted == false }
            } else {
                emptyList()
            }

            // 获取 stock out lines
            val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) {
                pickOrderLineIds.flatMap { polId ->
                    inventoryLotLineIds.flatMap { illId ->
                        stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
                    }
                }
            } else {
                emptyList()
            }
            // 取得所有 stock out line（含無 lot 情況）
            val stockOutLinesByPickOrderLine = pickOrderLineIds.associateWith { polId ->
                stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId)
            }
            // 获取 jo_pick_order 记录
            val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!)

            // 构建 pick order info
            val pickOrderInfo = mapOf(
                "id" to pickOrder.id,
                "code" to pickOrder.code,
                "consoCode" to pickOrder.consoCode,
                "targetDate" to pickOrder.targetDate?.let {
                    "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                },
                "type" to pickOrder.type?.value,
                "status" to pickOrder.status?.value,
                "assignTo" to pickOrder.assignTo?.id,
                "jobOrder" to mapOf(
                    "id" to jobOrder.id,
                    "code" to jobOrder.code,
                    "name" to "Job Order ${jobOrder.code}"
                )
            )

            // 构建 pick order lines with lots
            val pickOrderLinesResult = pickOrderLines.map { pol ->
                val item = pol.item
                val uom = pol.uom
                val lineId = pol.id!!
                val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id }
                val jpo = joPickOrders.firstOrNull { it.itemId == item?.id }
                println("jpo: $jpo")
                val user = userService.find(jpo?.handledBy?:0L)
                println("user: $user")
                val handlerName = jpo?.handledBy?.let { uid ->
                    userService.find(uid).orElse(null)?.name
                }
                println("handlerName: $handlerName")
                // 构建 lots 数据
                val lots = lineSuggestedLots.mapNotNull { spl ->
                    val ill = spl.suggestedLotLine
                    if (ill == null || ill.deleted == true) return@mapNotNull null

                    val il = ill.inventoryLot
                    if (il == null || il.deleted == true) return@mapNotNull null

                    val warehouse = ill.warehouse
                    val sol = stockOutLines.firstOrNull {
                        it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id
                    }
                    val jpo = joPickOrders.firstOrNull { it.itemId == item?.id }
                    val handlerName = jpo?.handledBy?.let { uid ->
                        userService.find(uid).orElse(null)?.name
                    }
                    println("handlerName: $handlerName")
                    val availableQty = if (sol?.status == "rejected") {
                        null
                    } else {
                        (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty
                            ?: BigDecimal.ZERO)
                    }

                    val lotAvailability = when {
                        il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired"
                        sol?.status == "rejected" -> "rejected"
                        ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable"
                        availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock"
                        else -> "available"
                    }

                    val processingStatus = when (sol?.status) {
                        "completed" -> "completed"
                        "rejected" -> "rejected"
                        "created" -> "pending"
                        else -> "pending"
                    }

                    LotDetailResponse(
                        lotId = ill.id,
                        lotNo = il.lotNo,
                        expiryDate = il.expiryDate?.let {
                            "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                        },
                        location = warehouse?.code,
                        availableQty = availableQty?.toDouble(),
                        requiredQty = spl.qty?.toDouble() ?: 0.0,
                        actualPickQty = sol?.qty ?: 0.0,
                        processingStatus = processingStatus,
                        lotAvailability = lotAvailability,
                        pickOrderId = pickOrder.id,
                        pickOrderCode = pickOrder.code,
                        pickOrderConsoCode = pickOrder.consoCode,
                        pickOrderLineId = pol.id,
                        stockOutLineId = sol?.id,
                        suggestedPickLotId = spl.id,
                        stockOutLineQty = sol?.qty ?: 0.0,
                        stockOutLineStatus = sol?.status,
                        routerIndex = warehouse?.order?.toString(),
                        routerArea = warehouse?.code,
                        routerRoute = warehouse?.code,
                        uomShortDesc = uom?.udfShortDesc,
                        matchStatus = jpo?.matchStatus?.value,
                        matchBy = jpo?.matchBy,
                        matchQty = jpo?.matchQty?.toDouble(),
                        stockInLineId = null,
                    )
                }

                PickOrderLineWithLotsResponse(
                    id = pol.id!!,
                    itemId = item?.id,
                    itemCode = item?.code,
                    itemName = item?.name,
                    requiredQty = pol.qty?.toDouble(),
                    uomCode = uom?.code,
                    uomDesc = uom?.udfudesc,
                    status = pol.status?.value,
                    lots = lots,
                    handler = handlerName
                )
            }

            // ✅ 修复第469行：返回 JobOrderLotsHierarchicalResponse
            return JobOrderLotsHierarchicalResponse(
                pickOrder = PickOrderInfoResponse(
                    id = pickOrder.id,
                    code = pickOrder.code,
                    consoCode = pickOrder.consoCode,
                    targetDate = pickOrder.targetDate?.let {
                        "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                    },
                    type = pickOrder.type?.value,
                    status = pickOrder.status?.value,
                    assignTo = pickOrder.assignTo?.id,
                    jobOrder = JobOrderBasicInfoResponse(
                        id = jobOrder.id!!,
                        code = jobOrder.code ?: "", // ✅ 修复第482行：处理空值
                        name = "Job Order ${jobOrder.code ?: ""}"
                    )
                ),
                pickOrderLines = pickOrderLinesResult // ✅ 修复第486行：使用转换后的结果
            )

        } catch (e: Exception) {
            println("❌ Error executing Job Order hierarchical query: ${e.message}")
            e.printStackTrace()
            // ✅ 修复 catch 块：返回 JobOrderLotsHierarchicalResponse 而不是 Map
            return JobOrderLotsHierarchicalResponse(
                pickOrder = PickOrderInfoResponse(
                    id = null,
                    code = null,
                    consoCode = null,
                    targetDate = null,
                    type = null,
                    status = null,
                    assignTo = null,
                    jobOrder = JobOrderBasicInfoResponse(0, "", "")
                ),
                pickOrderLines = emptyList()
            )
        }
    }
    // Get completed job order pick orders (for second tab)
    //  Fix the getCompletedJobOrderLotsHierarchical method
    open fun getCompletedJobOrderLotsHierarchical(userId: Long): Map<String, Any?> {
        println("=== Debug: getCompletedJobOrderLotsHierarchical (repo version) ===")
        println("today: ${LocalDate.now()}")
        println("userId filter: $userId")
    
        val user = userService.find(userId).orElse(null)
        if (user == null) {
            println("❌ User not found: $userId")
            return mapOf("pickOrder" to null, "pickOrderLines" to emptyList<Map<String, Any?>>())
        }
    
        // 1. 找出当前用户所有 COMPLETED 的 JO pick order
        val completedPickOrders = pickOrderRepository
            .findAllByAssignToIdAndStatusIn(userId, listOf(PickOrderStatus.COMPLETED))
            .filter { it.jobOrder != null }
    
        println("    DEBUG: Found ${completedPickOrders.size} completed job order pick orders assigned to user $userId")
    
        if (completedPickOrders.isEmpty()) {
            return mapOf("pickOrder" to null, "pickOrderLines" to emptyList<Map<String, Any?>>())
        }
    
        // 2. 取最新的一张
        val latestCompletedOrder = completedPickOrders.maxByOrNull {
            it.completeDate ?: it.modified ?: LocalDateTime.MIN
        } ?: return mapOf("pickOrder" to null, "pickOrderLines" to emptyList<Map<String, Any?>>())
    
        println("    DEBUG: Using latest completed order: ${latestCompletedOrder.code}")
    
        val pickOrder = latestCompletedOrder
        val jobOrder = pickOrder.jobOrder!!
    
        // 3. 组装 pickOrder 信息
        val pickOrderInfo = mapOf(
            "id" to pickOrder.id,
            "code" to pickOrder.code,
            "consoCode" to pickOrder.consoCode,
            "targetDate" to pickOrder.targetDate?.let {
                "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}"
            },
            "type" to "jo",
            "status" to pickOrder.status?.value,
            "assignTo" to pickOrder.assignTo?.id,
            "jobOrder" to mapOf(
                "id" to jobOrder.id,
                "code" to jobOrder.code,
                "name" to "Job Order ${jobOrder.code}"
            )
        )
    
        // 4. 找出所有 pick order line
        val pickOrderLines = pickOrderLineRepository
            .findAllByPickOrderId(pickOrder.id!!)
            .filter { it.deleted == false }
    
        if (pickOrderLines.isEmpty()) {
            return mapOf("pickOrder" to pickOrderInfo, "pickOrderLines" to emptyList<Map<String, Any?>>())
        }
    
        val pickOrderLineIds = pickOrderLines.mapNotNull { it.id }
    
        // 5. 建议拣货 lot
        val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) {
            suggestPickLotRepository
                .findAllByPickOrderLineIdIn(pickOrderLineIds)
                .filter { it.deleted == false }
        } else emptyList()
    
        val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
    
        val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) {
            inventoryLotLineRepository
                .findAllByIdIn(inventoryLotLineIds)
                .filter { it.deleted == false }
        } else emptyList()
    
        val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct()
        val inventoryLots = if (inventoryLotIds.isNotEmpty()) {
            inventoryLotRepository
                .findAllByIdIn(inventoryLotIds)
                .filter { it.deleted == false }
        } else emptyList()
    
        // 6. stock_out_line（含所有状态）
        val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) {
            pickOrderLineIds.flatMap { polId ->
                inventoryLotLineIds.flatMap { illId ->
                    stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
                }
            }
        } else emptyList()
    
        // 7. 对应的 jo_pick_order（用于 match_* 和 handler）
        val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!)
    
        // 8. 组装每一行 + lots
        val pickOrderLinesResult = pickOrderLines.map { pol ->
            val item = pol.item
            val uom = pol.uom
            val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id }
    
            val jpo = joPickOrders.firstOrNull { it.itemId == item?.id }
            val handlerName = jpo?.matchBy?.let { uid ->
                userService.find(uid).orElse(null)?.name
            }
    
            val lots = lineSuggestedLots.mapNotNull { spl ->
                val ill = spl.suggestedLotLine ?: return@mapNotNull null
                if (ill.deleted == true) return@mapNotNull null
    
                val il = ill.inventoryLot ?: return@mapNotNull null
                if (il.deleted == true) return@mapNotNull null
    
                val warehouse = ill.warehouse
                val sol = stockOutLines.firstOrNull {
                    it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id
                }
    
                val availableQty = if (sol?.status == "rejected") {
                    null
                } else {
                    (ill.inQty ?: BigDecimal.ZERO) -
                        (ill.outQty ?: BigDecimal.ZERO) -
                        (ill.holdQty ?: BigDecimal.ZERO)
                }
    
                val lotAvailability = when {
                    il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired"
                    sol?.status == "rejected" -> "rejected"
                    ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable"
                    availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock"
                    else -> "available"
                }
    
                val processingStatus = when (sol?.status) {
                    "completed" -> "completed"
                    "rejected" -> "rejected"
                    "created" -> "pending"
                    else -> "pending"
                }
    
                mapOf(
                    "lotId" to ill.id,
                    "lotNo" to il.lotNo,
                    "expiryDate" to il.expiryDate?.let {
                        "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}"
                    },
                    "location" to warehouse?.code,
                    "availableQty" to availableQty?.toDouble(),
                    "requiredQty" to (spl.qty?.toDouble() ?: 0.0),
                    "actualPickQty" to (sol?.qty ?: 0.0),
                    "processingStatus" to processingStatus,
                    "lotAvailability" to lotAvailability,
                    "pickOrderId" to pickOrder.id,
                    "pickOrderCode" to pickOrder.code,
                    "pickOrderConsoCode" to pickOrder.consoCode,
                    "pickOrderLineId" to pol.id,
                    "stockOutLineId" to sol?.id,
                    "stockOutLineStatus" to sol?.status,
                    "stockOutLineQty" to (sol?.qty ?: 0.0),
                    "routerIndex" to warehouse?.order?.toIntOrNull(),
                    "routerArea" to warehouse?.code,
                    "routerRoute" to warehouse?.code,
                    "uomShortDesc" to uom?.udfShortDesc,
                    "matchStatus" to jpo?.matchStatus?.value,
                    "matchBy" to jpo?.matchBy,
                    "matchQty" to jpo?.matchQty,
                    "handler" to handlerName
                )
            }
    
            mapOf(
                "id" to pol.id,
                "itemId" to item?.id,
                "itemCode" to item?.code,
                "itemName" to item?.name,
                "requiredQty" to pol.qty?.toDouble(),
                "uomCode" to uom?.code,
                "uomDesc" to uom?.udfudesc,
                "handler" to handlerName,
                "lots" to lots
            )
        }
    
        return mapOf(
            "pickOrder" to pickOrderInfo,
            "pickOrderLines" to pickOrderLinesResult
        )
    }

    // Get completed job order pick order records (for third tab)
    open fun getCompletedJobOrderPickOrderRecords(userId: Long): List<Map<String, Any?>> {
        println("=== getCompletedJobOrderPickOrderRecords ===")
        println("userId: $userId")
        
        return try {
            // Get all completed JoPickOrderRecord records for the user
            val allRecords = joPickOrderRecordRepository.findAll()
            val completedRecords = allRecords.filter { 
                it.matchStatus == JoPickOrderStatus.completed &&
                it.handledBy == userId
            }

            println("Found ${completedRecords.size} completed job order pick order records for user $userId")
            val sortedRecords = completedRecords.sortedByDescending { it.created } 
            val recordDetails = sortedRecords.mapNotNull { record ->
                // Get pick order details
                val pickOrder = pickOrderRepository.findById(record.pickOrderId!!).orElse(null)
                if (pickOrder != null && pickOrder.jobOrder != null) {
                    val jobOrder = pickOrder.jobOrder
                    mapOf(
                        "recordId" to record.id,
                        "pickOrderId" to pickOrder.id,
                        "pickOrderCode" to pickOrder.code,
                        "jobOrderId" to jobOrder?.id,
                        "jobOrderCode" to jobOrder?.code,
                        "jobOrderName" to jobOrder?.bom?.name,
                        "itemId" to record.itemId,
                        "match_status" to record.matchStatus,
                        "match_by" to record.matchBy,
                        "match_qty" to record.matchQty,
                        "handledBy" to record.handledBy,
                        "created" to record.created,
                        "completeDate" to pickOrder.completeDate
                    )
                } else {
                    println("❌ Record ${record.id} has no valid pick order or job order, skipping.")
                    null
                }
            }

            println("Returning ${recordDetails.size} completed job order pick order records")
            recordDetails
        } catch (e: Exception) {
            println("❌ Error in getCompletedJobOrderPickOrderRecords: ${e.message}")
            e.printStackTrace()
            emptyList()
        }
    }

  // 修复 getUnassignedJobOrderPickOrders 方法
open fun getUnassignedJobOrderPickOrders(): List<Map<String, Any?>> {
    println("=== getUnassignedJobOrderPickOrders ===")
    
    return try {
        //  修复：使用正确的 repository 方法
        val unassignedPickOrders = pickOrderRepository.findAllByStatusAndAssignToIsNullAndDeletedFalse(
            com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
        ).filter { pickOrder -> 
            pickOrder.jobOrder != null // Only job order pick orders
        }

        println("Found ${unassignedPickOrders.size} unassigned job order pick orders")

        val jobOrderPickOrders = unassignedPickOrders.mapNotNull { pickOrder ->
            val jobOrder = pickOrder.jobOrder
            if (jobOrder != null) {
                mapOf(
                    "pickOrderId" to pickOrder.id,
                    "pickOrderCode" to pickOrder.code,
                    "pickOrderConsoCode" to pickOrder.consoCode,
                    "pickOrderTargetDate" to pickOrder.targetDate,
                    "pickOrderStatus" to pickOrder.status,
                    "jobOrderId" to jobOrder.id,
                    "jobOrderCode" to jobOrder.code,
                    "jobOrderName" to jobOrder.bom?.name,
                    "reqQty" to jobOrder.reqQty,
                    "uom" to jobOrder.bom?.uom?.code,
                    "planStart" to jobOrder.planStart,
                    "planEnd" to jobOrder.planEnd
                )
            } else {
                println("❌ Pick order ${pickOrder.id} has no job order, skipping.")
                null
            }
        }

        println("Returning ${jobOrderPickOrders.size} unassigned job order pick orders")
        jobOrderPickOrders
    } catch (e: Exception) {
        println("❌ Error in getUnassignedJobOrderPickOrders: ${e.message}")
        e.printStackTrace()
        emptyList()
    }
}

//  修复：assignJobOrderPickOrderToUser 方法中的自引用问题
@Transactional
open fun assignJobOrderPickOrderToUser(pickOrderId: Long, userId: Long): MessageResponse {
    println("=== assignJobOrderPickOrderToUser ===")
    println("pickOrderId: $pickOrderId, userId: $userId")
    
    return try {
        val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
        if (pickOrder == null) {
        return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
                message = "Pick order not found",
                errorPosition = null
        )
    }
    
        if (pickOrder.assignTo != null) {
        return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
                message = "Pick order is already assigned",
                errorPosition = null
            )
        }
        
        // Assign pick order to user
        pickOrder.assignTo = userService.find(userId).orElse(null)
        pickOrderRepository.save(pickOrder)
        
        //  修复：使用 this 而不是 joPickOrderService
        this.updateHandledByForPickOrder(pickOrderId, userId, null)
        this.updateRecordHandledByForPickOrder(pickOrderId, userId)
        MessageResponse(
            id = pickOrder.id,
            code = pickOrder.code,
            name = pickOrder.jobOrder?.bom?.name,
            type = null,
            message = "Successfully assigned",
            errorPosition = null
        )
    } catch (e: Exception) {
        println("❌ Error in assignJobOrderPickOrderToUser: ${e.message}")
        e.printStackTrace()
        MessageResponse(
            id = null,
            code = null,
            name = null,
            type = null,
            message = "Error occurred: ${e.message}",
            errorPosition = null
        )
    }
    }
    open fun unAssignJobOrderPickOrderToUser(pickOrderId: Long): MessageResponse {
        println("=== unAssignJobOrderPickOrderToUser ===")
        println("pickOrderId: $pickOrderId")
        
        return try {
            val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
            if (pickOrder == null) {
                return MessageResponse(
                    id = null,
                    code = null,
                    name = null,
                    type = null,
                    message = "Pick order not found",
                    errorPosition = null
                )
            }
            pickOrder.assignTo = null
            pickOrderRepository.save(pickOrder)
            this.updateHandledByForPickOrder(pickOrderId, null,null)
            this.updateRecordHandledByForPickOrder(pickOrderId, null)
            MessageResponse(
                id = pickOrder.id,
                code = pickOrder.code,
                name = pickOrder.jobOrder?.bom?.name,
                type = null,
                message = "Successfully unassigned",
                errorPosition = null
            )
        } catch (e: Exception) {
            println("❌ Error in unAssignJobOrderPickOrderToUser: ${e.message}")
            e.printStackTrace()
            MessageResponse(
                id = null,
                code = null,
                name = null,
                type = null,
                message = "Error occurred: ${e.message}",
                errorPosition = null
            )
        }
    }
    //  Fix the updateMatchStatus method
    open fun updateMatchStatus(pickOrderId: Long, itemId: Long, userId: Long, qty: Int): MessageResponse {
        try {
            println("=== Debug: updateMatchStatus ===")
            println("pickOrderId: $pickOrderId, itemId: $itemId, userId: $userId, qty: $qty")
            
            val joPickOrder = joPickOrderRepository.findByPickOrderIdAndItemId(pickOrderId, itemId)
            
            if (joPickOrder.isEmpty) {
                return MessageResponse(
                    id = null,
                    name = null,
                    code = "ERROR",
                    type = "NOT_FOUND",
                    message = "Job Order Pick Order not found for pickOrderId: $pickOrderId, itemId: $itemId",
                    errorPosition = null
                )
            }
            
            val joPickOrderEntity = joPickOrder.get()
            
            //  设置扫描状态和相关字段
            joPickOrderEntity.matchStatus = JoPickOrderStatus.scanned
            joPickOrderEntity.matchBy = userId
            joPickOrderEntity.matchQty = qty  //  使用传递的 qty
            joPickOrderRepository.save(joPickOrderEntity)
            
            //  同时更新 jo_pick_order_record
            val joPickOrderRecord = joPickOrderRecordRepository.findByPickOrderIdAndItemId(pickOrderId, itemId)
            if (joPickOrderRecord.isPresent) {
                val recordEntity = joPickOrderRecord.get()
                recordEntity.matchStatus = JoPickOrderStatus.scanned
                recordEntity.matchBy = userId
                recordEntity.matchQty = qty  //  使用相同的 qty
                joPickOrderRecordRepository.save(recordEntity)
            }
            
            println(" Updated match status: pickOrderId=$pickOrderId, itemId=$itemId, matchQty=$qty")
            
            return MessageResponse(
                id = null,
                name = null,
                code = "SUCCESS",
                type = "UPDATED",
                message = "Second QR scan status updated to scanned with quantity $qty",
                errorPosition = null
            )
            
        } catch (e: Exception) {
            println("❌ Error updating second QR scan status: ${e.message}")
            return MessageResponse(
                id = null,
                name = null,
                code = "ERROR",
                type = "EXCEPTION",
                message = "Error updating second QR scan status: ${e.message}",
                errorPosition = null
            )
        }
    }
open fun submitSecondScanQty(request: SecondScanSubmitRequest): MessageResponse {
    try {
        println("=== Debug: submitSecondScanQty ===")
        println("pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}, qty: ${request.qty}")
        
        val joPickOrder = joPickOrderRepository.findByPickOrderIdAndItemId(request.pickOrderId, request.itemId)
        
        if (joPickOrder.isEmpty) {
            return MessageResponse(
                id = null,
                name = null,
                code = "ERROR",
                type = "NOT_FOUND",
                message = "Job Order Pick Order not found for pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}",
                errorPosition = null
            )
        }
        
        val joPickOrderEntity = joPickOrder.get()
        joPickOrderEntity.matchQty = request.qty.toInt()
        
        //  Set status to completed when submitting quantity
        joPickOrderEntity.matchStatus = JoPickOrderStatus.completed
        
        request.userId?.let { uid ->
            joPickOrderEntity.matchBy = uid
        }
        if (joPickOrderEntity.ticketCompleteTime == null) {
            joPickOrderEntity.ticketCompleteTime = LocalDateTime.now()
        }
        
        joPickOrderRepository.save(joPickOrderEntity)
        
        println(" Updated jo_pick_order: status=${joPickOrderEntity.matchStatus}, qty=${joPickOrderEntity.matchQty}, completeTime=${joPickOrderEntity.ticketCompleteTime}")
        
        return MessageResponse(
            id = null,
            name = null,
            code = "SUCCESS",
            type = "UPDATED",
            message = "Second scan quantity submitted successfully",
            errorPosition = null
        )
        
    } catch (e: Exception) {
        println("❌ Error submitting second scan quantity: ${e.message}")
        return MessageResponse(
            id = null,
            name = null,
            code = "ERROR",
            type = "EXCEPTION",
            message = "Error submitting second scan quantity: ${e.message}",
            errorPosition = null
        )
    }
}


//  Fix the recordSecondScanIssue method
open fun recordSecondScanIssue(request: SecondScanIssueRequest): MessageResponse {
    try {
        println("=== Debug: recordSecondScanIssue ===")
        println("pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}")
        
        val joPickOrder = joPickOrderRepository.findByPickOrderIdAndItemId(request.pickOrderId, request.itemId)
        
        if (joPickOrder.isEmpty) {
            return MessageResponse(
                id = null,
                name = null,
                code = "ERROR",
                type = "NOT_FOUND",
                message = "Job Order Pick Order not found for pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}",
                errorPosition = null
            )
        }
        
        //  Get pick order and pick order line details with item and lot information
        val pickOrderDetails = jdbcDao.queryForList(
            """
            SELECT 
                po.code as pickOrderCode,
                DATE_FORMAT(po.created, '%Y-%m-%d') as pickOrderCreateDate,
                pol.id as pickOrderLineId,
                i.code as itemCode,
                i.name as itemName,
                pol.qty as requiredQty,
                u.name as pickerName
            FROM fpsmsdb.pick_order po
            JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
            JOIN fpsmsdb.items i ON i.id = pol.itemId
            LEFT JOIN fpsmsdb.user u ON u.id = :createdBy
            WHERE po.id = :pickOrderId 
            AND pol.itemId = :itemId 
            AND pol.deleted = false
            AND po.deleted = false
            """,
            mapOf(
                "pickOrderId" to request.pickOrderId,
                "itemId" to request.itemId,
                "createdBy" to request.createdBy
            )
        )
        
        //  修复：使用正确的变量名 pickOrderDetails
        if (pickOrderDetails.isEmpty()) {
            return MessageResponse(
                id = null,
                name = null,
                code = "ERROR",
                type = "NOT_FOUND",
                message = "Pick order not found for pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}",
                errorPosition = null
            )
        }
        
        //  修复：使用正确的变量名 pickOrderDetails
        val orderData = pickOrderDetails.first()
        val pickOrderCode = orderData["pickOrderCode"] as String?
        val pickOrderCreateDate = orderData["pickOrderCreateDate"]?.let {
            LocalDate.parse(it.toString())
        }
        val pickOrderLineId = (orderData["pickOrderLineId"] as Number).toLong()
        val itemCode = orderData["itemCode"] as String?
        val itemName = orderData["itemName"] as String?
        val pickerName = orderData["pickerName"] as String?
        val requiredQty = orderData["requiredQty"]?.let {
            BigDecimal(it.toString())
        }
        
        //  获取 suggested lot 信息（用于 lot_id, lot_no, store_location）
        val lotResults = jdbcDao.queryForList(
            """
            SELECT 
                ill.id as lotId,
                il.lotNo as lotNo,
                w.name as storeLocation
            FROM fpsmsdb.suggested_pick_lot spl
            JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id
            JOIN fpsmsdb.inventory_lot il ON ill.inventoryLotId = il.id
            LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
            WHERE spl.pickOrderLineId = :pickOrderLineId
            AND spl.deleted = false
            AND ill.deleted = false
            LIMIT 1
            """,
            mapOf("pickOrderLineId" to pickOrderLineId)
        )
        
        val lotId = if (lotResults.isNotEmpty()) {
            (lotResults.first()["lotId"] as Number?)?.toLong()
        } else null
        
        val lotNo = if (lotResults.isNotEmpty()) {
            lotResults.first()["lotNo"] as String?
        } else null
        
        val storeLocation = if (lotResults.isNotEmpty()) {
            lotResults.first()["storeLocation"] as String?
        } else null

        val joPickOrderEntity = joPickOrder.get()
        joPickOrderEntity.matchStatus = JoPickOrderStatus.completed
        joPickOrderEntity.matchQty = request.qty.toInt()

        if (joPickOrderEntity.ticketCompleteTime == null) {
            joPickOrderEntity.ticketCompleteTime = LocalDateTime.now()
        }

        joPickOrderRepository.save(joPickOrderEntity)
        
        //  生成 issueNo
        val issueNo = generateIssueNoForJo()
        
        //  Create pick execution issue with complete data
        //  Create pick execution issue with complete data
        val pickExecutionIssue = PickExecutionIssue(
            id = null,  //  1
            pickOrderId = request.pickOrderId,  //  2
            pickOrderCode = pickOrderCode ?: "PI-${request.pickOrderId}",  //  3
            pickOrderCreateDate = pickOrderCreateDate,  //  4
            pickExecutionDate = LocalDate.now(),  //  5
            pickOrderLineId = pickOrderLineId,  //  6
            issueNo = issueNo,  //  7
            issueCategory = IssueCategory.match_issue,  //  8 - 因为这是 JO 的 match 问题
            itemId = request.itemId,  //  9
            itemCode = itemCode,  //  10
            itemDescription = itemName,  //  11
            lotId = lotId,  //  12
            lotNo = lotNo,  //  13
            storeLocation = storeLocation,  //  14
            requiredQty = requiredQty,  //  15
            actualPickQty = request.qty.toBigDecimal(),  //  16
            missQty = request.missQty.toBigDecimal(),  //  17
            badItemQty = request.badItemQty.toBigDecimal(),  //  18
            issueRemark = request.reason,  //  19
            pickerName = pickerName,  //  20
            handleStatus = HandleStatus.pending,  //  21
            handleDate = null,  //  22
            handledBy = null,  //  23
            created = LocalDateTime.now(),  //  24
            createdBy = request.createdBy.toString(),  //  25
            version = 0,  //  26
            modified = LocalDateTime.now(),  //  27
            modifiedBy = null,  //  28
            deleted = false  //  29
        )
        pickExecutionIssueRepository.save(pickExecutionIssue)
        
        return MessageResponse(
            id = null,
            name = null,
            code = "SUCCESS",
            type = "CREATED",
            message = "Second scan issue recorded successfully",
            errorPosition = null
        )
    } catch (e: Exception) {
        println("=== ERROR in recordSecondScanIssue ===")
        e.printStackTrace()
        return MessageResponse(
            id = null,
            name = null,
            code = "ERROR",
            type = "EXCEPTION",
            message = "Error: ${e.message}",
            errorPosition = null
        )
    }
}
private fun generateIssueNoForJo(): String {
    val now = LocalDateTime.now()
    val yearMonth = now.format(java.time.format.DateTimeFormatter.ofPattern("yyMM"))
    
    // 查询当月最新的 issueNo
    val latestIssueNo = pickExecutionIssueRepository.findLatestIssueNoByYearMonth(yearMonth)
    
    // 计算下一个序列号
    val nextSequence = if (latestIssueNo != null) {
        val parts = latestIssueNo.split("-")
        if (parts.size == 3) {
            val currentSequence = parts[2].toIntOrNull() ?: 0
            currentSequence + 1
        } else {
            1
        }
    } else {
        1
    }
    
    return "SKO-${yearMonth}-${nextSequence.toString().padStart(3, '0')}"
}
open fun getCompletedJobOrderPickOrdersWithCompletedSecondScan(): List<Map<String, Any?>> {
    println("=== getCompletedJobOrderPickOrdersWithCompletedSecondScan ===")

    return try {
        // 1. 先找出所有 COMPLETED 的 JO pick order（不再依赖 assignTo / userId）
        val completedPickOrders = pickOrderRepository
            .findAllByStatusIn(listOf(PickOrderStatus.COMPLETED))
            .filter { it.jobOrder != null }  // 只要有 jobOrder 的

        println("Found ${completedPickOrders.size} completed job order pick orders (all users)")

        // 2. 只保留「所有 item 的 second scan 都完成」的 pick order
        val completedJobOrderPickOrders = completedPickOrders.mapNotNull { pickOrder ->
            val jobOrder = pickOrder.jobOrder
            if (jobOrder != null) {
                val joPickOrders = findByPickOrderId(pickOrder.id!!)
                val allSecondScanCompleted = joPickOrders.isNotEmpty() &&
                        joPickOrders.all { it.matchStatus == JoPickOrderStatus.completed }

                if (allSecondScanCompleted) {
                    mapOf(
                        "id" to pickOrder.id,
                        "pickOrderId" to pickOrder.id,
                        "pickOrderCode" to pickOrder.code,
                        "pickOrderConsoCode" to pickOrder.consoCode,
                        "pickOrderTargetDate" to pickOrder.targetDate,
                        "pickOrderStatus" to pickOrder.status,
                        // ✅ 完成时间改用 jobOrder.planEnd
                        "completedDate" to jobOrder.planEnd,
                        "jobOrderId" to jobOrder.id,
                        "jobOrderCode" to jobOrder.code,
                        "jobOrderName" to jobOrder.bom?.name,
                        "reqQty" to jobOrder.reqQty,
                        "uom" to jobOrder.bom?.uom?.code,
                        "planStart" to jobOrder.planStart,
                        "planEnd" to jobOrder.planEnd,
                        "secondScanCompleted" to true,
                        "totalItems" to joPickOrders.size,
                        "completedItems" to joPickOrders.count { it.matchStatus == JoPickOrderStatus.completed }
                    )
                } else {
                    println("❌ Pick order ${pickOrder.id} has incomplete second scan, skipping.")
                    null
                }
            } else {
                println("❌ Pick order ${pickOrder.id} has no job order, skipping.")
                null
            }
        }

        println("Returning ${completedJobOrderPickOrders.size} completed job order pick orders with completed second scan")
        completedJobOrderPickOrders
    } catch (e: Exception) {
        println("❌ Error in getCompletedJobOrderPickOrdersWithCompletedSecondScan: ${e.message}")
        e.printStackTrace()
        emptyList()
    }
}
open fun getJobOrderListForPrintQrCode(date: LocalDate): List<JobOrderListForPrintQrCodeResponse> {
    println("=== getJobOrderListForPrintQrCode ===")
    println("Filtering by receiptDate: $date")
    return try {
        // 1. 根据 receiptDate 日期过滤 StockInLine（在数据库层面过滤）
        val allStockInLines = stockInLineRepository.findByReceiptDateAndDeletedFalse(date)
        println("📦 Found ${allStockInLines.size} StockInLines with receiptDate = $date")
        println("📦 All StockInLines: ${allStockInLines.map { it.id }}, ${allStockInLines.map { it.jobOrder?.id }}")
        println("📦 All StockInLines: ${allStockInLines.map { it.status }}, ${allStockInLines.map { it.jobOrder?.bom?.description }}")
        
        // 2. 进一步过滤：status = "received" 或 "completed"，且 BOM type = "WIP" 或 "FG"
        val stockInLines = allStockInLines.filter {
            it.jobOrder != null
            //it.status == "received" &&  // 只显示 QC 完成但尚未 putaway 的
          //  it.jobOrder?.bom?.description in listOf("WIP", "FG")
        }
        println("✅ After filtering: ${stockInLines.size} StockInLines")

        // 3. 获取相关的 JobOrder IDs
        val jobOrderIds = stockInLines.mapNotNull { it.jobOrder?.id }.distinct()
        println("📋 JobOrder IDs to check: $jobOrderIds")

        // 4. 批量加载 JobOrders（只加载需要的，且状态为 COMPLETED）
        val jobOrders = if (jobOrderIds.isNotEmpty()) {
            jobOrderRepository.findAllById(jobOrderIds)
                .filter { it.status == JobOrderStatus.COMPLETED || it.status == JobOrderStatus.STORING }
        } else {
            emptyList()
        }
        println("✅ Completed JobOrders: ${jobOrders.map { "${it.id}(${it.status})" }}")

        // 5. 构建响应
        val result = mutableListOf<JobOrderListForPrintQrCodeResponse>()

        for (stockInLine in stockInLines) {
            val jobOrder = jobOrders.find { it.id == stockInLine.jobOrder?.id }
            if (jobOrder != null && jobOrder.bom?.description in listOf("WIP", "FG")) {
                // Check if this job order is already in the result (to avoid duplicates)
                val existing = result.find {
                    it.stockInLineId == stockInLine.id
                }

                if (existing == null) {
                    val response = JobOrderListForPrintQrCodeResponse(
                        id = stockInLine.id!!,
                        code = jobOrder.code ?: "JO-${jobOrder.id}",
                        name = jobOrder.bom?.name ?: "",
                        reqQty = jobOrder.reqQty ?: BigDecimal.ZERO,
                        stockInLineId = stockInLine.id!!,
                        stockInLineQty = stockInLine.acceptedQty?.toDouble() ?: 0.0,
                        stockInLineStatus = stockInLine.status ?: "received",
                        finihedTime = jobOrder.planEnd ?: jobOrder.modified ?: LocalDateTime.now()
                    )
                    result.add(response)
                    println("✅ Added StockInLine ${stockInLine.id} (JobOrder ${jobOrder.id}, BOM: ${jobOrder.bom?.description})")
                }
            } else {
                println("❌ StockInLine ${stockInLine.id}: JobOrder ${stockInLine.jobOrder?.id} not found or BOM type not WIP/FG")
            }
        }
        
        println("📊 Final result: ${result.size} items")
        result
    } catch (e: Exception) {
        println("❌ Error in getJobOrderListForPrintQrCode: ${e.message}")
        e.printStackTrace()
        emptyList()
    }
}

open fun getCompletedJobOrderPickOrderLotDetails(pickOrderId: Long): List<Map<String, Any?>> {
    println("=== getCompletedJobOrderPickOrderLotDetails ===")
    println("pickOrderId: $pickOrderId")
    
    return try {
        val sql = """
            SELECT 
                -- Pick Order Information
                po.id as pickOrderId,
                po.code as pickOrderCode,
                po.consoCode as pickOrderConsoCode,
                DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
                po.status as pickOrderStatus,
                
                -- Job Order Information
                jo.id as jobOrderId,
                jo.code as jobOrderCode,
                DATE_FORMAT(jo.planStart, '%Y/%m/%d') as jobOrderPlanStart,
                jo.reqQty as jobOrderReqQty,
                
                -- Finished Goods Information  
                fg.code as fgCode,
                fg.name as fgName,
                
                b.outputQtyUom as unit,
                b.itemId as bomItemId,
                iu.uomId as bomItemUomId,
                
                uc_bom.udfudesc as uomConversionDesc,
                
                -- Pick Order Line Information
                pol.id as pickOrderLineId,
                pol.qty as pickOrderLineRequiredQty,
                pol.status as pickOrderLineStatus,
                
                -- Item Information
                i.id as itemId,
                i.code as itemCode,
                i.name as itemName,
                uc.code as uomCode,
                uc.udfudesc as uomDesc,
                uc.udfShortDesc as uomShortDesc,
                
                -- Lot Information
                ill.id as lotId,
                il.lotNo,
                DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
                w.name as location,
                COALESCE(uc.udfudesc, 'N/A') as stockUnit,   
                
                -- Router Information
                w.id as routerId,
                w.order as routerIndex,
                w.code as routerRoute,
                w.code as routerArea,
                
                -- Set quantities to NULL for rejected lots
                CASE 
                    WHEN sol.status = 'rejected' THEN NULL
                    ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
                END as availableQty,
                
                -- Required quantity for this lot
                COALESCE(spl.qty, 0) as requiredQty,
                
                -- Actual picked quantity
                COALESCE(sol.qty, 0) as actualPickQty,
                
                -- Suggested pick lot information
                spl.id as suggestedPickLotId,
                ill.status as lotStatus,
                
                -- Stock out line information
                sol.id as stockOutLineId,
                sol.status as stockOutLineStatus,
                COALESCE(sol.qty, 0) as stockOutLineQty,
                
                -- Additional detailed fields
                COALESCE(ill.inQty, 0) as inQty, 
                COALESCE(ill.outQty, 0) as outQty,
                COALESCE(ill.holdQty, 0) as holdQty,
                
                -- Calculate total picked quantity by ALL pick orders for this lot
                COALESCE((
                    SELECT SUM(sol_all.qty) 
                    FROM fpsmsdb.stock_out_line sol_all 
                    WHERE sol_all.inventoryLotLineId = ill.id 
                    AND sol_all.deleted = false
                    AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed')
                ), 0) as totalPickedByAllPickOrders,
                
                -- Calculate remaining available quantity correctly
                (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
                
                -- Lot availability status
                CASE 
                    WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
                    WHEN sol.status = 'rejected' THEN 'rejected'
                    WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
                    WHEN ill.status = 'unavailable' THEN 'status_unavailable'
                    ELSE 'available'
                END as lotAvailability,
                
                -- Processing status
                CASE 
                    WHEN sol.status = 'completed' THEN 'completed'
                    WHEN sol.status = 'rejected' THEN 'rejected'
                    WHEN sol.status = 'created' THEN 'pending'
                    ELSE 'pending'
                END as processingStatus,
                
                -- JoPickOrder second scan status
                jpo.match_status as match_status,
                jpo.match_by as match_by,
                jpo.match_qty as match_qty
                
            FROM fpsmsdb.pick_order po
            JOIN fpsmsdb.job_order jo ON jo.id = po.joId
            LEFT JOIN fpsmsdb.bom b ON b.id = jo.bomId          
            LEFT JOIN fpsmsdb.items fg ON fg.id = b.itemId
            JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
            JOIN fpsmsdb.items i ON i.id = pol.itemId
            LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
            LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
            LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id

            LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
            LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
            -- no-lot 情況：ill.id 為 null 時，要匹配 sol.inventoryLotLineId IS NULL
            LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
                AND (
                    sol.inventoryLotLineId = ill.id
                    OR (sol.inventoryLotLineId IS NULL AND ill.id IS NULL)
                )
            LEFT JOIN fpsmsdb.jo_pick_order jpo ON jpo.pick_order_id = po.id AND jpo.item_id = pol.itemId
            LEFT JOIN fpsmsdb.item_uom iu ON iu.itemId = b.itemId AND iu.stockUnit = 1
            LEFT JOIN fpsmsdb.uom_conversion uc_fg ON uc_fg.id = iu.uomId
            LEFT JOIN fpsmsdb.uom_conversion uc_bom ON uc_bom.id = iu.uomId
            WHERE po.deleted = false
            AND po.id = :pickOrderId
            AND pol.deleted = false
            AND po.status = 'COMPLETED'
            -- ill / il 可能為 null（no-lot），避免把 no-lot 記錄過濾掉
            AND (ill.id IS NULL OR ill.deleted = false)
            AND (il.id IS NULL OR il.deleted = false)
            AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
            ORDER BY 
                CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END,
                COALESCE(w.`order`, 999999) ASC, 
                po.code ASC,
                i.code ASC,
                il.expiryDate ASC, 
                il.lotNo ASC
        """.trimIndent()
        
       // println("    Executing SQL for Job Order pick order lot details: $sql")
        println("    With parameters: pickOrderId = $pickOrderId")
        
        val results = jdbcDao.queryForList(sql, mapOf("pickOrderId" to pickOrderId))
        
        println("Found ${results.size} lot details for pick order $pickOrderId")
        results
    } catch (e: Exception) {
        println("❌ Error in getCompletedJobOrderPickOrderLotDetails: ${e.message}")
        e.printStackTrace()
        emptyList()
    }
}
// ... existing code ...
/*
open fun getCompletedJobOrderPickOrders(): List<Map<String, Any?>> {
    println("=== getCompletedJobOrderPickOrders (no user filter) ===")

    return try {
        // 1. 找出所有 COMPLETED 状态、且有关联 JobOrder 的 pick order（不再按 assignTo 过滤）
        val allCompletedPickOrders = pickOrderRepository
            .findAllByStatusIn(listOf(PickOrderStatus.COMPLETED))
            .filter { it.jobOrder != null }

            println("Found ${allCompletedPickOrders.size} completed job order pick orders")

            val pickOrderIds = allCompletedPickOrders.mapNotNull { it.id }.distinct()
            val joByPickOrderId: Map<Long, List<JoPickOrder>> =
                if (pickOrderIds.isNotEmpty()) {
                    joPickOrderRepository.findByPickOrderIdIn(pickOrderIds)
                        .filterNot { it.deleted }
                        .groupBy { it.pickOrderId ?: 0L }
                        .filterKeys { it != 0L }
                } else {
                    emptyMap()
                }
    
            val completedJobOrderPickOrders = allCompletedPickOrders.mapNotNull { pickOrder ->
                val jobOrder = pickOrder.jobOrder
                if (jobOrder != null) {
                    val poId = pickOrder.id ?: return@mapNotNull null
                    val joPickOrders = joByPickOrderId[poId].orEmpty()
    
                    val secondScanCompleted = joPickOrders.isNotEmpty() &&
                        joPickOrders.all { it.matchStatus == JoPickOrderStatus.completed }
    
                    mapOf(
                        "id" to pickOrder.id,
                        "pickOrderId" to pickOrder.id,
                        "pickOrderCode" to pickOrder.code,
                        "pickOrderConsoCode" to pickOrder.consoCode,
                        "pickOrderTargetDate" to pickOrder.targetDate?.let {
                            "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}"
                        },
                        "pickOrderStatus" to pickOrder.status,
                        "completedDate" to jobOrder.planEnd,
                        "jobOrderId" to jobOrder.id,
                        "jobOrderCode" to jobOrder.code,
                        "jobOrderName" to jobOrder.bom?.name,
                        "reqQty" to jobOrder.reqQty,
                        "uom" to jobOrder.bom?.uom?.code,
                        "planStart" to jobOrder.planStart,
                        "planEnd" to jobOrder.planEnd,
                        "secondScanCompleted" to secondScanCompleted,
                        "totalItems" to joPickOrders.size,
                        "completedItems" to joPickOrders.count { it.matchStatus == JoPickOrderStatus.completed },
                    )
                } else {
                    println("❌ Pick order ${pickOrder.id} has no job order, skipping.")
                    null
                }
            }

        println("Returning ${completedJobOrderPickOrders.size} completed job order pick orders")
        completedJobOrderPickOrders
    } catch (e: Exception) {
        println("❌ Error in getCompletedJobOrderPickOrders: ${e.message}")
        e.printStackTrace()
        emptyList()
    }
}
*/
open fun getCompletedJobOrderPickOrders(completedDate: LocalDate?): List<Map<String, Any?>> {
    
        println("=== getCompletedJobOrderPickOrders (no user filter) ===")

        return try {
            // 1. 找出所有 COMPLETED 状态、且有关联 JobOrder 的 pick order（不再按 assignTo 过滤）
            val allCompletedPickOrders =
                if (completedDate != null) {
                    val from = completedDate.atStartOfDay()
                    val toExclusive = completedDate.plusDays(1).atStartOfDay()
                    /* 
                    pickOrderRepository.findAllCompletedWithJobOrderPlanEndOnDay(
                        PickOrderStatus.COMPLETED,
                        from,
                        toExclusive,
                    )
                     */
                    pickOrderRepository.findAllCompletedWithJobOrderPlanStartOnDay(
                        PickOrderStatus.COMPLETED,
                        from,
                        toExclusive,
                    )
                } else {
                    pickOrderRepository
                        .findAllByStatusIn(listOf(PickOrderStatus.COMPLETED))
                        .filter { it.jobOrder != null }
                }

            println("Found ${allCompletedPickOrders.size} completed job order pick orders")

            val pickOrderIds = allCompletedPickOrders.mapNotNull { it.id }.distinct()
            val joByPickOrderId: Map<Long, List<JoPickOrder>> =
                if (pickOrderIds.isNotEmpty()) {
                    joPickOrderRepository.findByPickOrderIdIn(pickOrderIds)
                        .filterNot { it.deleted }
                        .groupBy { it.pickOrderId ?: 0L }
                        .filterKeys { it != 0L }
                } else {
                    emptyMap()
                }

            val completedJobOrderPickOrders = allCompletedPickOrders.mapNotNull { pickOrder ->
                val jobOrder = pickOrder.jobOrder
                if (jobOrder != null) {
                    val poId = pickOrder.id ?: return@mapNotNull null
                    val joPickOrders = joByPickOrderId[poId].orEmpty()

                    val secondScanCompleted = joPickOrders.isNotEmpty() &&
                            joPickOrders.all { it.matchStatus == JoPickOrderStatus.completed }

                    mapOf(
                        "id" to pickOrder.id,
                        "pickOrderId" to pickOrder.id,
                        "pickOrderCode" to pickOrder.code,
                        "pickOrderConsoCode" to pickOrder.consoCode,
                        "pickOrderTargetDate" to pickOrder.targetDate?.let {
                            "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}"
                        },
                        "pickOrderStatus" to pickOrder.status,
                        "completedDate" to jobOrder.planStart,
                        "jobOrderId" to jobOrder.id,
                        "jobOrderCode" to jobOrder.code,
                        "jobOrderName" to jobOrder.bom?.name,
                        "reqQty" to jobOrder.reqQty,
                        "uom" to jobOrder.bom?.uom?.code,
                        "planStart" to jobOrder.planStart,
                        "planEnd" to jobOrder.planEnd,
                        "secondScanCompleted" to secondScanCompleted,
                        "totalItems" to joPickOrders.size,
                        "completedItems" to joPickOrders.count { it.matchStatus == JoPickOrderStatus.completed },
                    )
                } else {
                    println("❌ Pick order ${pickOrder.id} has no job order, skipping.")
                    null
                }
            }

            println("Returning ${completedJobOrderPickOrders.size} completed job order pick orders")
            completedJobOrderPickOrders
        } catch (e: Exception) {
            println("❌ Error in getCompletedJobOrderPickOrders: ${e.message}")
            e.printStackTrace()
            emptyList()
        }
    }
    open fun getJobOrderPickOrders(date: LocalDate?, status: PickOrderStatus?): List<Map<String, Any?>> {
        println("=== getJobOrderPickOrders ===")
        println("date: $date, status: $status")
    
        return try {
            val allPickOrders =
                when {
                    date != null -> {
                        val from = date.atStartOfDay()
                        val toExclusive = date.plusDays(1).atStartOfDay()
                        pickOrderRepository.findByJobOrderPlanStartOnDayAndOptionalStatus(from, toExclusive, status)
                    }
                    status != null -> {
                        pickOrderRepository.findAllByStatusAndDeletedFalse(status)
                            .filter { it.jobOrder != null && it.jobOrder?.isHidden != true }
                    }
                    else -> {
                        pickOrderRepository.findAllByStatusIn(PickOrderStatus.entries.toList())
                            .filter { !it.deleted && it.jobOrder != null && it.jobOrder?.isHidden != true }
                    }
                }
    
            val pickOrderIds = allPickOrders.mapNotNull { it.id }.distinct()
            val joByPickOrderId: Map<Long, List<JoPickOrder>> =
                if (pickOrderIds.isNotEmpty()) {
                    joPickOrderRepository.findByPickOrderIdIn(pickOrderIds)
                        .filterNot { it.deleted }
                        .groupBy { it.pickOrderId ?: 0L }
                        .filterKeys { it != 0L }
                } else {
                    emptyMap()
                }
    
            allPickOrders.mapNotNull { pickOrder ->
                val jobOrder = pickOrder.jobOrder ?: return@mapNotNull null
                val poId = pickOrder.id ?: return@mapNotNull null
                val joPickOrders = joByPickOrderId[poId].orEmpty()
    
                val secondScanCompleted = joPickOrders.isNotEmpty() &&
                    joPickOrders.all { it.matchStatus == com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus.completed }
    
                mapOf(
                    "id" to pickOrder.id,
                    "pickOrderId" to pickOrder.id,
                    "pickOrderCode" to pickOrder.code,
                    "pickOrderConsoCode" to pickOrder.consoCode,
                    "pickOrderTargetDate" to pickOrder.targetDate?.let {
                        "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}"
                    },
                    "pickOrderStatus" to pickOrder.status,
                    "completedDate" to jobOrder.planStart,
                    "jobOrderId" to jobOrder.id,
                    "jobOrderCode" to jobOrder.code,
                    "jobOrderName" to jobOrder.bom?.name,
                    "reqQty" to jobOrder.reqQty,
                    "uom" to jobOrder.bom?.uom?.code,
                    "planStart" to jobOrder.planStart,
                    "planEnd" to jobOrder.planEnd,
                    "secondScanCompleted" to secondScanCompleted,
                    "totalItems" to joPickOrders.size,
                    "completedItems" to joPickOrders.count {
                        it.matchStatus == com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus.completed
                    },
                )
            }
        } catch (e: Exception) {
            println("❌ Error in getJobOrderPickOrders: ${e.message}")
            e.printStackTrace()
            emptyList()
        }
    }
    // this is benson need to correct function, to add search by store Id and else part
    open fun getJobOrderPickOrderLotDetails(
        pickOrderId: Long,
        status: String? = null, // 可選，不傳=全部狀態
    ): List<Map<String, Any?>> {
        println("=== getJobOrderPickOrderLotDetails (all status) ===")
        println("pickOrderId: $pickOrderId, status: $status")
    
        return try {
            val sql = """
                SELECT 
                    -- Pick Order Information
                    po.id as pickOrderId,
                    po.code as pickOrderCode,
                    po.consoCode as pickOrderConsoCode,
                    DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
                    po.status as pickOrderStatus,
                    
                    -- Job Order Information
                    jo.id as jobOrderId,
                    jo.code as jobOrderCode,
                    DATE_FORMAT(jo.planStart, '%Y/%m/%d') as jobOrderPlanStart,
                    jo.reqQty as jobOrderReqQty,
                    
                    -- Finished Goods Information
                    fg.code as fgCode,
                    fg.name as fgName,
                    
                    b.outputQtyUom as unit,
                    b.itemId as bomItemId,
                    iu.uomId as bomItemUomId,
                    uc_bom.udfudesc as uomConversionDesc,
                    
                    -- Pick Order Line Information
                    pol.id as pickOrderLineId,
                    pol.qty as pickOrderLineRequiredQty,
                    pol.status as pickOrderLineStatus,
                    
                    -- Item Information
                    i.id as itemId,
                    i.code as itemCode,
                    i.name as itemName,
                    uc.code as uomCode,
                    uc.udfudesc as uomDesc,
                    uc.udfShortDesc as uomShortDesc,
                    
                    -- Lot Information
                    ill.id as lotId,
                    il.lotNo,
                    DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
                    w.name as location,
                    COALESCE(uc.udfudesc, 'N/A') as stockUnit,
                    
                    -- Router Information
                    w.id as routerId,
                    w.`order` as routerIndex,
                    w.code as routerRoute,
                    w.code as routerArea,
                    
                    -- Set quantities to NULL for rejected lots
                    CASE 
                        WHEN sol.status = 'rejected' THEN NULL
                        ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
                    END as availableQty,
                    
                    -- Required quantity for this lot
                    COALESCE(spl.qty, 0) as requiredQty,
                    
                    -- Actual picked quantity
                    COALESCE(sol.qty, 0) as actualPickQty,
                    
                    -- Suggested pick lot information
                    spl.id as suggestedPickLotId,
                    ill.status as lotStatus,
                    
                    -- Stock out line information
                    sol.id as stockOutLineId,
                    sol.status as stockOutLineStatus,
                    COALESCE(sol.qty, 0) as stockOutLineQty,
                    
                    -- Additional detailed fields
                    COALESCE(ill.inQty, 0) as inQty, 
                    COALESCE(ill.outQty, 0) as outQty,
                    COALESCE(ill.holdQty, 0) as holdQty,
                    
                    -- Calculate total picked quantity by ALL pick orders for this lot
                    COALESCE((
                        SELECT SUM(sol_all.qty) 
                        FROM fpsmsdb.stock_out_line sol_all 
                        WHERE sol_all.inventoryLotLineId = ill.id 
                          AND sol_all.deleted = false
                          AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed', 'picking')
                    ), 0) as totalPickedByAllPickOrders,
                    
                    -- Calculate remaining available quantity correctly
                    (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
                    
                    -- Lot availability status
                    CASE 
                        WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
                        WHEN sol.status = 'rejected' THEN 'rejected'
                        WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
                        WHEN ill.status = 'unavailable' THEN 'status_unavailable'
                        ELSE 'available'
                    END as lotAvailability,
                    
                    -- Processing status (all-status friendly)
                    CASE 
                        WHEN sol.status = 'completed' THEN 'completed'
                        WHEN sol.status = 'rejected' THEN 'rejected'
                        WHEN sol.status IN ('checked', 'partially_completed', 'picking') THEN 'in_progress'
                        WHEN sol.status IN ('created', 'pending') THEN 'pending'
                        ELSE COALESCE(sol.status, 'pending')
                    END as processingStatus,
                    
                    -- JoPickOrder second scan status
                    jpo.match_status as match_status,
                    jpo.match_by as match_by,
                    jpo.match_qty as match_qty
                    
                FROM fpsmsdb.pick_order po
                JOIN fpsmsdb.job_order jo ON jo.id = po.joId
                LEFT JOIN fpsmsdb.bom b ON b.id = jo.bomId
                LEFT JOIN fpsmsdb.items fg ON fg.id = b.itemId
                JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
                JOIN fpsmsdb.items i ON i.id = pol.itemId
                LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
                LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
                LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id
                LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
                LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
                -- no-lot：ill.id 為 null 時，匹配 sol.inventoryLotLineId IS NULL
                LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
                    AND (
                        sol.inventoryLotLineId = ill.id
                        OR (sol.inventoryLotLineId IS NULL AND ill.id IS NULL)
                    )
                LEFT JOIN fpsmsdb.jo_pick_order jpo ON jpo.pick_order_id = po.id AND jpo.item_id = pol.itemId
                LEFT JOIN fpsmsdb.item_uom iu ON iu.itemId = b.itemId AND iu.stockUnit = 1
                LEFT JOIN fpsmsdb.uom_conversion uc_fg ON uc_fg.id = iu.uomId
                LEFT JOIN fpsmsdb.uom_conversion uc_bom ON uc_bom.id = iu.uomId
                WHERE po.deleted = false
                  AND po.id = :pickOrderId
                  AND pol.deleted = false
                  AND (:status IS NULL OR po.status = :status)
                  -- ill / il 可能為 null（no-lot），避免過濾掉 no-lot 記錄
                  AND (ill.id IS NULL OR ill.deleted = false)
                  AND (il.id IS NULL OR il.deleted = false)
                  AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
                ORDER BY 
                    CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END,
                    COALESCE(w.`order`, 999999) ASC, 
                    po.code ASC,
                    i.code ASC,
                    il.expiryDate ASC, 
                    il.lotNo ASC
            """.trimIndent()
    
            val params = mapOf(
                "pickOrderId" to pickOrderId,
                "status" to status?.trim()?.takeIf { it.isNotEmpty() }, // null = all status
            )
    
            println("With parameters: $params")
            val results = jdbcDao.queryForList(sql, params)
            println("Found ${results.size} lot details for pickOrderId=$pickOrderId")
            results
        } catch (e: Exception) {
            println("Error in getJobOrderPickOrderLotDetails: ${e.message}")
            e.printStackTrace()
            emptyList()
        }
    }

    open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Long): List<Map<String, Any?>> {
        println("=== getCompletedJobOrderPickOrderLotDetailsForCompletedPick ===")
        println("pickOrderId: $pickOrderId")

        return try {
            val sql = """
            SELECT 
                -- Pick Order Information
                po.id as pickOrderId,
                po.code as pickOrderCode,
                po.consoCode as pickOrderConsoCode,
                DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
                po.status as pickOrderStatus,
                
                -- Job Order Information
                jo.id as jobOrderId,
                jo.code as jobOrderCode,
                
                -- Pick Order Line Information
                pol.id as pickOrderLineId,
                pol.qty as pickOrderLineRequiredQty,
                pol.status as pickOrderLineStatus,
                
                -- Item Information
                i.id as itemId,
                i.code as itemCode,
                i.name as itemName,
                uc.code as uomCode,
                uc.udfudesc as uomDesc,
                uc.udfShortDesc as uomShortDesc,
                
                -- Lot Information
                ill.id as lotId,
                il.lotNo,
                DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
                w.name as location,
                COALESCE(uc.udfudesc, 'N/A') as stockUnit,   
                
                -- Router Information
                w.id as routerId,
                w.order as routerIndex,
                w.code as routerRoute,
                w.code as routerArea,
                
                -- Set quantities to NULL for rejected lots
                CASE 
                    WHEN sol.status = 'rejected' THEN NULL
                    ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
                END as availableQty,
                
                -- Required quantity for this lot
                COALESCE(spl.qty, 0) as requiredQty,
                
                -- Actual picked quantity
                COALESCE(sol.qty, 0) as actualPickQty,
                
                -- Suggested pick lot information
                spl.id as suggestedPickLotId,
                ill.status as lotStatus,
                
                -- Stock out line information
                sol.id as stockOutLineId,
                sol.status as stockOutLineStatus,
                COALESCE(sol.qty, 0) as stockOutLineQty,
                
                -- Additional detailed fields
                COALESCE(ill.inQty, 0) as inQty, 
                COALESCE(ill.outQty, 0) as outQty,
                COALESCE(ill.holdQty, 0) as holdQty,
                
                -- Calculate total picked quantity by ALL pick orders for this lot
                COALESCE((
                    SELECT SUM(sol_all.qty) 
                    FROM fpsmsdb.stock_out_line sol_all 
                    WHERE sol_all.inventoryLotLineId = ill.id 
                    AND sol_all.deleted = false
                    AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed')
                ), 0) as totalPickedByAllPickOrders,
                
                -- Calculate remaining available quantity correctly
                (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
                
                -- Lot availability status
                CASE 
                    WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
                    WHEN sol.status = 'rejected' THEN 'rejected'
                    WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
                    WHEN ill.status = 'unavailable' THEN 'status_unavailable'
                    ELSE 'available'
                END as lotAvailability,
                
                -- Processing status
                CASE 
                    WHEN sol.status = 'completed' THEN 'completed'
                    WHEN sol.status = 'rejected' THEN 'rejected'
                    WHEN sol.status = 'created' THEN 'pending'
                    ELSE 'pending'
                END as processingStatus,
                
                -- JoPickOrder second scan status
                jpo.match_status as match_status,
                jpo.match_by as match_by,
                jpo.match_qty as match_qty
                
            FROM fpsmsdb.pick_order po
            JOIN fpsmsdb.job_order jo ON jo.id = po.joId
            JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
            JOIN fpsmsdb.items i ON i.id = pol.itemId
            LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
            LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
            LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id

            LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
            LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
            -- no-lot 情況：ill.id 為 null 時，要匹配 sol.inventoryLotLineId IS NULL 的记录
            LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
                AND (
                    sol.inventoryLotLineId = ill.id
                    OR (sol.inventoryLotLineId IS NULL AND ill.id IS NULL)
                )
            LEFT JOIN fpsmsdb.jo_pick_order jpo ON jpo.pick_order_id = po.id AND jpo.item_id = pol.itemId

            WHERE po.deleted = false
            AND po.id = :pickOrderId
            AND pol.deleted = false
            AND po.status = 'COMPLETED'
            -- ill / il 可能为 null（no-lot），避免把 no-lot 记录过滤掉
            AND (ill.id IS NULL OR ill.deleted = false)
            AND (il.id IS NULL OR il.deleted = false)
            AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
            ORDER BY 
                CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END,
                COALESCE(w.`order`, 999999) ASC, 
                po.code ASC,
                i.code ASC,
                il.expiryDate ASC, 
                il.lotNo ASC
        """.trimIndent()

            //  println("    Executing SQL for completed pick order lot details: $sql")
            println("    With parameters: pickOrderId = $pickOrderId")

            val results = jdbcDao.queryForList(sql, mapOf("pickOrderId" to pickOrderId))

            println("Found ${results.size} lot details for completed pick order $pickOrderId")
            results
        } catch (e: Exception) {
            println("❌ Error in getCompletedJobOrderPickOrderLotDetailsForCompletedPick: ${e.message}")
            e.printStackTrace()
            emptyList()
        }
    }

    private fun normalizeFloor(raw: String): String {
        if (raw.isBlank()) return raw
        val cleaned = raw.trim().uppercase()
        val num = cleaned.replace(Regex("[^0-9]"), "")
        return if (num.isNotEmpty()) "${num}F" else cleaned
    }

    open fun getAllJoPickOrders(
        bomType: String?,
        floor: String?,
        jobOrderCode: String? = null,
        pickOrderCode: String? = null,
        itemName: String? = null,
        bomDescription: String? = null,
        planStart: LocalDate? = null,
    ): List<AllJoPickOrderResponse> {
        println("=== getAllJoPickOrders ===")
        val wallStartNs = System.nanoTime()
        val timing = linkedMapOf<String, Long>()
        fun <T> timed(name: String, block: () -> T): T {
            val t0 = System.nanoTime()
            val v = block()
            timing[name] = ((System.nanoTime() - t0) / 1_000_000)
            return v
        }

        return try {
            val releasedPickOrders = timed("loadReleasedPickOrdersMs") {
                pickOrderRepository.findAllReleasedJoWorkbenchPickOrders(
                    status = PickOrderStatus.RELEASED,
                    completedStatus = JobOrderStatus.COMPLETED,
                )
            }

           // println("Found ${releasedPickOrders.size} released job order pick orders")
            val pickOrderIds = releasedPickOrders.mapNotNull { it.id }

            val normalizedFloorFilter = floor
                ?.let { normalizeFloor(it) }
                ?.takeIf { it.isNotBlank() && it != "ALL" }

            // 2. 批量查询每个 pick order 的按楼层统计（若没有则跳过）
            val floorCountsByPickOrderId: Map<Long, List<Map<String, Any?>>> = timed("queryFloorCountsMs") {
                if (pickOrderIds.isNotEmpty()) {
                    val sql = """
                    SELECT 
                        po.id AS pickOrderId,
                        COALESCE(NULLIF(TRIM(w.store_id), ''), SUBSTRING_INDEX(w.code, '-', 1)) AS floorKey,
                        COUNT(DISTINCT pol.id) AS totalCount,
                        COUNT(DISTINCT CASE 
                            WHEN LOWER(sol.status) = 'completed' THEN pol.id
                            WHEN pol.status = 'COMPLETED' THEN pol.id
                        END) AS finishedCount
                    FROM fpsmsdb.pick_order po
                    JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
                    LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId AND spl.deleted = false
                    LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id AND ill.deleted = false
                    LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
                    LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
                    WHERE po.deleted = false
                    AND po.status = 'RELEASED'
                    AND po.joId IS NOT NULL
                    AND po.id IN (${pickOrderIds.joinToString(",")})
                    AND w.id IS NOT NULL
                    GROUP BY po.id, floorKey
                """.trimIndent()
                    jdbcDao.queryForList(sql, emptyMap<String, Any?>())
                        .groupBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L }
                } else {
                    emptyMap()
                }
            }

            // 2b. no-lot counts (lines with stock_out_line.inventoryLotLineId IS NULL)
            val noLotCountsByPickOrderId: Map<Long, Map<String, Any?>> = timed("queryNoLotCountsMs") {
                if (pickOrderIds.isNotEmpty()) {
                    val sql = """
                    SELECT
                        po.id AS pickOrderId,
                        COUNT(DISTINCT pol.id) AS totalCount,
                        COUNT(DISTINCT CASE 
                            WHEN LOWER(sol.status) = 'completed' THEN pol.id
                            WHEN pol.status = 'COMPLETED' THEN pol.id
                        END) AS finishedCount
                    FROM fpsmsdb.pick_order po
                    JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
                    JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
                    WHERE po.deleted = false
                      AND po.status = 'RELEASED'
                      AND po.joId IS NOT NULL
                      AND po.id IN (${pickOrderIds.joinToString(",")})
                      AND sol.inventoryLotLineId IS NULL
                    GROUP BY po.id
                """.trimIndent()
                    jdbcDao.queryForList(sql, emptyMap<String, Any?>())
                        .associateBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L }
                } else emptyMap()
            }

            // 2c. suggested fail (rejected) counts per pick order
            val suggestedFailCountByPickOrderId: Map<Long, Int> = timed("querySuggestedFailCountsMs") {
                if (pickOrderIds.isNotEmpty()) {
                    val sql = """
                    SELECT
                        po.id AS pickOrderId,
                        COUNT(DISTINCT sol.id) AS rejectedCount
                    FROM fpsmsdb.pick_order po
                    JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
                    JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
                    WHERE po.deleted = false
                      AND po.status = 'RELEASED'
                      AND po.joId IS NOT NULL
                      AND po.id IN (${pickOrderIds.joinToString(",")})
                      AND LOWER(sol.status) = 'rejected'
                    GROUP BY po.id
                """.trimIndent()
                    jdbcDao.queryForList(sql, emptyMap<String, Any?>()).associate { row ->
                        val id = (row["pickOrderId"] as? Number)?.toLong() ?: 0L
                        val cnt = (row["rejectedCount"] as? Number)?.toInt() ?: 0
                        id to cnt
                    }
                } else emptyMap()
            }

            val linesByPickOrderId = timed("loadPickOrderLinesMs") {
                if (pickOrderIds.isNotEmpty()) {
                    pickOrderLineRepository.findAllByPickOrderIdInAndDeletedFalse(pickOrderIds)
                        .mapNotNull { pol ->
                            val pid = pol.pickOrder?.id ?: return@mapNotNull null
                            pid to pol
                        }
                        .groupBy({ it.first }, { it.second })
                } else {
                    emptyMap()
                }
            }

            val jobOrderIdsForStockIn = releasedPickOrders.mapNotNull { it.jobOrder?.id }.distinct()
            val lotNoByJobOrderId = timed("loadStockInLinesMs") {
                if (jobOrderIdsForStockIn.isNotEmpty()) {
                    stockInLineRepository.findFirstLotNoByJobOrderIds(jobOrderIdsForStockIn)
                        .associate { it.jobOrderId to it.lotNo }
                } else {
                    emptyMap()
                }
            }

            val jobTypeIds = releasedPickOrders.mapNotNull { it.jobOrder?.jobTypeId }.distinct()
            val jobTypesById = timed("loadJobTypesMs") {
                if (jobTypeIds.isNotEmpty()) {
                    jobTypeRepository.findAllById(jobTypeIds).associateBy { it.id }
                } else {
                    emptyMap()
                }
            }
            val jobOrderPickOrders = timed("buildResponseMs") {
                releasedPickOrders.mapNotNull { pickOrder ->
                    //  println("Processing pick order: ${pickOrder.id}, code: ${pickOrder.code}")
                    val jobOrder = pickOrder.jobOrder
                    if (jobOrder == null) {
                        println("❌ Pick order ${pickOrder.id} has no job order")
                        return@mapNotNull null
                    }
                    if (jobOrder.isHidden == true) {
                        return@mapNotNull null
                    }
                    if (jobOrder.status == JobOrderStatus.COMPLETED) {
                        return@mapNotNull null
                    }

                    //println("Job order found: ${jobOrder.id}, code: ${jobOrder.code}")

                    val bom = jobOrder.bom

                    // 按 bom.type 过滤：null / blank 表示不过滤（全部）
                    val normalizedType = bomType
                        ?.trim()
                        ?.lowercase()
                        ?.takeIf { it.isNotBlank() && it != "all" }
                    if (normalizedType != null) {
                        val currentType = bom?.type?.trim()?.lowercase()
                        if (currentType != normalizedType) return@mapNotNull null
                    }
                    // println("BOM found: ${bom?.id}")

                    val item = bom?.item
                    if (item == null) {
                        println("❌ BOM ${bom?.id} has no item")
                        return@mapNotNull null
                    }

                    //println("Item found: ${item.id}, name: ${item.name}")

                    val uom = bom.outputQtyUom
                    if (uom == null) {
                        println("❌ BOM ${bom.id} has no uom")
                        return@mapNotNull null
                    }

                    // println("UOM found: ${uom.id}, code: ${uom.code}")

                    val poId = pickOrder.id ?: return@mapNotNull null
                    val pickOrderLines = linesByPickOrderId[poId].orEmpty()
                    val finishedLines = pickOrderLines.count { it.status == PickOrderLineStatus.COMPLETED }
                    val jobOrderType = jobOrder.jobTypeId?.let { jobTypesById[it] }
                    val joId = jobOrder.id ?: return@mapNotNull null
                    val lotNo = lotNoByJobOrderId[joId]
                    // println("✅ Building response for pick order ${pickOrder.id}")
                    val floorPickCounts = floorCountsByPickOrderId[pickOrder.id]?.map { row ->
                        com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto(
                            floor = normalizeFloor((row["floorKey"] as? String).orEmpty()),
                            finishedCount = (row["finishedCount"] as? Number)?.toInt() ?: 0,
                            totalCount = (row["totalCount"] as? Number)?.toInt() ?: 0
                        )
                    }.orEmpty()

                    val noLotRow = noLotCountsByPickOrderId[pickOrder.id]
                    val noLotPickCount = if (noLotRow != null) {
                        com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto(
                            floor = "NO_LOT",
                            finishedCount = (noLotRow["finishedCount"] as? Number)?.toInt() ?: 0,
                            totalCount = (noLotRow["totalCount"] as? Number)?.toInt() ?: 0
                        )
                    } else null

                    // Backend filtering by floor:
                    // - When selecting a specific floor (2F/3F/4F), show ONLY remaining on that floor.
                    // - When selecting NO_LOT, show ONLY remaining for no-lot items.
                    if (normalizedFloorFilter != null) {
                        if (normalizedFloorFilter == "NO_LOT") {
                            val hasNoLotRemaining = (noLotPickCount?.let { it.totalCount - it.finishedCount } ?: 0) > 0
                            if (!hasNoLotRemaining) return@mapNotNull null
                        } else {
                            val hasRemainingOnFloor = floorPickCounts.any { c ->
                                c.floor == normalizedFloorFilter && (c.totalCount - c.finishedCount) > 0
                            }
                            if (!hasRemainingOnFloor) return@mapNotNull null
                        }
                    }

                    val suggestedFailCount = suggestedFailCountByPickOrderId[pickOrder.id] ?: 0
                    AllJoPickOrderResponse(
                        id = pickOrder.id ?: 0L,
                        pickOrderId = pickOrder.id,
                        pickOrderCode = pickOrder.code,
                        jobOrderId = jobOrder.id,
                        jobOrderCode = jobOrder.code,
                        jobOrderTypeId = jobOrder.jobTypeId,
                        jobOrderType = jobOrderType?.name,
                        itemId = item.id ?: 0L,
                        itemName = item.name ?: "",
                        bomDescription = bom?.description,
                        reqQty = jobOrder.reqQty ?: BigDecimal.ZERO,
                        //uomId = bom.outputQtyUom?.id : 0L,
                        uomId = 0,
                        uomName = bom?.outputQtyUom ?: "",
                        lotNo = lotNo,
                        planStart = jobOrder.planStart,
                        jobOrderStatus = jobOrder.status?.value ?: "",
                        finishedPickOLineCount = finishedLines,
                        floorPickCounts = floorPickCounts,
                        noLotPickCount = noLotPickCount,
                        suggestedFailCount = suggestedFailCount
                    )
                }
            }
            val sorted = timed("sortResponseMs") {
                jobOrderPickOrders.sortedByDescending { it.id }
            }
            val normalizedJobOrderCode = jobOrderCode?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
            val normalizedPickOrderCode = pickOrderCode?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
            val normalizedItemName = itemName?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
            val normalizedBomDescription = bomDescription?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
            val dateFilter = planStart
            val filtered = timed("applySearchFilterMs") {
                sorted.filter { row ->
                    val jobCodeMatch = normalizedJobOrderCode == null ||
                        (row.jobOrderCode?.lowercase()?.contains(normalizedJobOrderCode) == true)
                    val pickCodeMatch = normalizedPickOrderCode == null ||
                        (row.pickOrderCode?.lowercase()?.contains(normalizedPickOrderCode) == true)
                    val itemNameMatch = normalizedItemName == null ||
                        row.itemName.lowercase().contains(normalizedItemName)
                    val bomDescriptionMatch = normalizedBomDescription == null ||
                        (row.bomDescription?.lowercase()?.contains(normalizedBomDescription) == true)
                    val planStartMatch = dateFilter == null || row.planStart?.toLocalDate() == dateFilter
                    jobCodeMatch && pickCodeMatch && itemNameMatch && bomDescriptionMatch && planStartMatch
                }
            }
            val totalMs = (System.nanoTime() - wallStartNs) / 1_000_000
            println(
                "JO_ALL_PICK_ORDERS_TIMING totalMs=$totalMs released=${releasedPickOrders.size} " +
                    timing.entries.joinToString(" ") { "${it.key}=${it.value}" },
            )
            //  println("Returning ${jobOrderPickOrders.size} released job order pick orders")
            filtered
        } catch (e: Exception) {
            val totalMs = (System.nanoTime() - wallStartNs) / 1_000_000
            println(
                "JO_ALL_PICK_ORDERS_TIMING totalMs=$totalMs (error) " +
                    timing.entries.joinToString(" ") { "${it.key}=${it.value}" },
            )
            println("❌ Error in getAllJoPickOrders: ${e.message}")
            e.printStackTrace()
            emptyList()
        }
    }

    open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLotsHierarchicalResponse {
        println("=== getJobOrderLotsHierarchicalByPickOrderId ===")
        println("pickOrderId: $pickOrderId")

        return try {
            val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
            if (pickOrder == null || pickOrder.deleted == true) {
                println("❌ Pick order $pickOrderId not found or deleted")
                return JobOrderLotsHierarchicalResponse(
                    pickOrder = PickOrderInfoResponse(
                        id = null,
                        code = null,
                        consoCode = null,
                        targetDate = null,
                        type = null,
                        status = null,
                        assignTo = null,
                        jobOrder = JobOrderBasicInfoResponse(0, "", "")
                    ),
                    pickOrderLines = emptyList()
                )
            }

            val jobOrder = pickOrder.jobOrder ?: return JobOrderLotsHierarchicalResponse(
                pickOrder = PickOrderInfoResponse(
                    id = null,
                    code = null,
                    consoCode = null,
                    targetDate = null,
                    type = null,
                    status = null,
                    assignTo = null,
                    jobOrder = JobOrderBasicInfoResponse(0, "", "")
                ),
                pickOrderLines = emptyList()
            )

            // ✅ 添加数据获取逻辑（从原始 Map 版本复制）
            // 获取 pick order lines
            val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!)
                .filter { it.deleted == false }

            // Pre-calculate total available qty per item (for UI display).
            // Note: we align with pick suggestion logic: AVAILABLE status, not expired, remaining > 0.
            val itemIds = pickOrderLines.mapNotNull { it.item?.id }.distinct()
            val today = LocalDate.now()
            val totalAvailableQtyByItemId: Map<Long, Double> = if (itemIds.isNotEmpty()) {
                inventoryLotLineRepository.findAllByItemIdIn(itemIds)
                    .asSequence()
                    .filter { it.deleted == false }
                    .filter { it.status == InventoryLotLineStatus.AVAILABLE } // entity enum
                    .filter { ill ->
                        val exp = ill.inventoryLot?.expiryDate
                        exp == null || !exp.isBefore(today)
                    }
                    .mapNotNull { ill ->
                        val iid = ill.inventoryLot?.item?.id ?: return@mapNotNull null
                        val remaining = (ill.inQty ?: BigDecimal.ZERO)
                            .minus(ill.outQty ?: BigDecimal.ZERO)
                            .minus(ill.holdQty ?: BigDecimal.ZERO)
                        if (remaining > BigDecimal.ZERO) iid to remaining else null
                    }
                    .groupBy({ it.first }, { it.second })
                    .mapValues { (_, qtys) -> qtys.fold(BigDecimal.ZERO) { acc, q -> acc + q }.toDouble() }
            } else {
                emptyMap()
            }

            // 获取所有 pick order line IDs
            val pickOrderLineIds = pickOrderLines.map { it.id!! }

            // 获取 suggested pick lots
            val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) {
                suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds)
                    .filter { it.deleted == false }
            } else {
                emptyList()
            }

            // 获取所有 inventory lot line IDs
            val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id }

            // 获取 inventory lot lines
            val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) {
                inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds)
                    .filter { it.deleted == false }
            } else {
                emptyList()
            }

            // 获取 inventory lots
            val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct()
            val inventoryLots = if (inventoryLotIds.isNotEmpty()) {
                inventoryLotRepository.findAllByIdIn(inventoryLotIds)
                    .filter { it.deleted == false }
            } else {
                emptyList()
            }

            // 获取 stock out lines
            val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) {
                pickOrderLineIds.flatMap { polId ->
                    inventoryLotLineIds.flatMap { illId ->
                        stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
                    }
                }
            } else {
                emptyList()
            }

            // 取得所有 stock out line（含無 lot 情況）
            val stockOutLinesByPickOrderLine = pickOrderLineIds.associateWith { polId ->
                stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId)
            }

            // stockouts 可能包含不在 suggestedPickLots 內的 inventoryLotLineId，需補齊以便計算 location/availableQty
            val stockOutInventoryLotLineIds = stockOutLinesByPickOrderLine.values
                .flatten()
                .mapNotNull { it.inventoryLotLineId }
                .distinct()

            val stockOutInventoryLotLines = if (stockOutInventoryLotLineIds.isNotEmpty()) {
                inventoryLotLineRepository.findAllByIdIn(stockOutInventoryLotLineIds)
                    .filter { it.deleted == false }
            } else {
                emptyList()
            }

            val inventoryLotLineById = (inventoryLotLines + stockOutInventoryLotLines)
                .filter { it.id != null }
                .associateBy { it.id!! }

            // 获取 stock in lines 通过 inventoryLotLineId（用于填充 stockInLineId）
            val stockInLinesByInventoryLotLineId = if (inventoryLotLineIds.isNotEmpty()) {
                // ✅ 修复：直接使用已加载的 inventoryLotLines 实体获取 stockInLineId
                inventoryLotLines.associateBy { it.id!! }
                    .mapNotNull { (illId, ill) ->
                        // 通过关系链：InventoryLotLine -> InventoryLot -> StockInLine -> id
                        ill.inventoryLot?.stockInLine?.id?.let { stockInLineId ->
                            illId to stockInLineId
                        }
                    }
                    .toMap()
            } else {
                emptyMap()
            }

            // 获取 jo_pick_order 记录
            val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!)

            // 构建 pick order info
            val pickOrderInfo = PickOrderInfoResponse(
                id = pickOrder.id,
                code = pickOrder.code,
                consoCode = pickOrder.consoCode,
                targetDate = pickOrder.targetDate?.let {
                    "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                },
                type = pickOrder.type?.value,
                status = pickOrder.status?.value,
                assignTo = pickOrder.assignTo?.id,
                jobOrder = JobOrderBasicInfoResponse(
                    id = jobOrder.id!!,
                    code = jobOrder.code ?: "",
                    name = "Job Order ${jobOrder.code}"
                )
            )

            // 构建 pick order lines with lots
            val pickOrderLinesResult = pickOrderLines.map { pol ->
                val item = pol.item
                val uom = pol.uom
                val lineId = pol.id!!
                val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id }
                val jpo = joPickOrders.firstOrNull { it.itemId == item?.id }
                val handlerName = jpo?.handledBy?.let { uid ->
                    userService.find(uid).orElse(null)?.name
                }
                // 构建 lots 数据
                val lots = lineSuggestedLots.mapNotNull { spl ->
                    val ill = spl.suggestedLotLine
                    if (ill == null || ill.deleted == true) return@mapNotNull null

                    val il = ill.inventoryLot
                    if (il == null || il.deleted == true) return@mapNotNull null

                    val warehouse = ill.warehouse
                    val sol = stockOutLines.firstOrNull {
                        it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id
                    }
                    val jpo = joPickOrders.firstOrNull { it.itemId == item?.id }
                    val handlerName = jpo?.handledBy?.let { uid ->
                        userService.find(uid).orElse(null)?.name
                    }
                    println("handlerName: $handlerName")
                    val availableQty = if (sol?.status == "rejected") {
                        null
                    } else {
                        (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty
                            ?: BigDecimal.ZERO)
                    }

                    val lotAvailability = when {
                        il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired"
                        sol?.status == "rejected" -> "rejected"
                        ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable"
                        availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock"
                        else -> "available"
                    }

                    val processingStatus = when (sol?.status) {
                        "completed" -> "completed"
                        "rejected" -> "rejected"
                        "created" -> "pending"
                        else -> "pending"
                    }

                    // 获取 stockInLineId
                    val stockInLineId = ill.id?.let { illId ->
                        stockInLinesByInventoryLotLineId[illId]
                    }

                    LotDetailResponse(
                        lotId = ill.id,
                        lotNo = il.lotNo,
                        expiryDate = il.expiryDate?.let {
                            "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                        },
                        location = warehouse?.code,
                        availableQty = availableQty?.toDouble(),
                        requiredQty = spl.qty?.toDouble() ?: 0.0,
                        actualPickQty = sol?.qty ?: 0.0,
                        processingStatus = processingStatus,
                        lotAvailability = lotAvailability,
                        pickOrderId = pickOrder.id,
                        pickOrderCode = pickOrder.code,
                        pickOrderConsoCode = pickOrder.consoCode,
                        pickOrderLineId = pol.id,
                        stockOutLineId = sol?.id,
                        stockInLineId = stockInLineId,
                        suggestedPickLotId = spl.id,
                        stockOutLineQty = sol?.qty ?: 0.0,
                        stockOutLineStatus = sol?.status,
                        routerIndex = warehouse?.order?.toString(),
                        routerArea = warehouse?.code,
                        routerRoute = warehouse?.code,
                        uomShortDesc = uom?.udfShortDesc,
                        matchStatus = jpo?.matchStatus?.value,
                        matchBy = jpo?.matchBy,
                        matchQty = jpo?.matchQty?.toDouble()
                    )
                }

                // 构建 stockouts 数据：用于无 suggested lot / noLot 场景也能显示并闭环（submit 0）
                val stockouts = (stockOutLinesByPickOrderLine[lineId] ?: emptyList()).map { sol ->
                    val illId = sol.inventoryLotLineId
                    val ill = if (illId != null) inventoryLotLineById[illId] else null
                    val lot = ill?.inventoryLot
                    val warehouse = ill?.warehouse
                    val availableQty = if (sol.status == "rejected") {
                        null
                    } else if (ill == null || ill.deleted == true) {
                        null
                    } else {
                        (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty
                            ?: BigDecimal.ZERO)
                    }

                    StockOutLineDetailResponse(
                        id = sol.id,
                        status = sol.status,
                        qty = sol.qty.toDouble(),
                        lotId = illId,
                        lotNo = sol.lotNo ?: lot?.lotNo,
                        location = warehouse?.code,
                        availableQty = availableQty?.toDouble(),
                        noLot = (illId == null)
                    )
                }

                PickOrderLineWithLotsResponse(
                    id = pol.id!!,
                    itemId = item?.id,
                    itemCode = item?.code,
                    itemName = item?.name,
                    requiredQty = pol.qty?.toDouble(),
                    totalAvailableQty = item?.id?.let { totalAvailableQtyByItemId[it] },
                    uomCode = uom?.code,
                    uomDesc = uom?.udfudesc,
                    status = pol.status?.value,
                    lots = lots,
                    stockouts = stockouts,
                    handler = handlerName
                )
            }

            return JobOrderLotsHierarchicalResponse(
                pickOrder = pickOrderInfo,
                pickOrderLines = pickOrderLinesResult
            )

        } catch (e: Exception) {
            println("❌ Error in getJobOrderLotsHierarchicalByPickOrderId: ${e.message}")
            e.printStackTrace()
            return JobOrderLotsHierarchicalResponse(
                pickOrder = PickOrderInfoResponse(
                    id = null,
                    code = null,
                    consoCode = null,
                    targetDate = null,
                    type = null,
                    status = null,
                    assignTo = null,
                    jobOrder = JobOrderBasicInfoResponse(0, "", "")
                ),
                pickOrderLines = emptyList()
            )
        }
    }

    open fun updateHandledByForItem(pickOrderId: Long, itemId: Long, userId: Long): MessageResponse {
        val joPickOrderOpt = joPickOrderRepository.findByPickOrderIdAndItemId(pickOrderId, itemId)

        if (joPickOrderOpt.isEmpty) {
            println("⚠️ JoPickOrder not found for pickOrderId: $pickOrderId, itemId: $itemId")
            return MessageResponse(
                id = 0,
                code = "404",
                name = "Not Found",
                type = "error",
                message = "JoPickOrder not found",
                errorPosition = "operator"
            )
        }

        val joPickOrder = joPickOrderOpt.get()
        if (userId != null && joPickOrder.handledBy != null) {
            val existingOperatorId = joPickOrder.handledBy
            val newOperatorId = userId

            // 如果不是同一个用户，拒绝更新
            if (existingOperatorId != null && existingOperatorId != newOperatorId) {
                return MessageResponse(
                    id = joPickOrder.id,
                    code = "409",
                    name = "Conflict",
                    type = "error",
                    message = "This pick order is already assigned to another user",
                    errorPosition = "operator"
                )
            }
        }
        // Update the displayed operator: jo_pick_order.handled_by
        joPickOrder.handledBy = userId
        joPickOrderRepository.save(joPickOrder)
        // Don't update other fields - only handledBy

        return MessageResponse(
            id = joPickOrder.id,
            code = null,
            name = null,
            type = null,
            message = "Pick order handled by updated",
            errorPosition = null
        )
    }

open fun updateRecordHandledByForItem(pickOrderId: Long, itemId: Long, userId: Long): MessageResponse {
    val joPickOrderRecordOpt = joPickOrderRecordRepository.findByPickOrderIdAndItemId(pickOrderId, itemId)
    
    if (joPickOrderRecordOpt.isEmpty) {
        println("⚠️ JoPickOrderRecord not found for pickOrderId: $pickOrderId, itemId: $itemId")
        return MessageResponse(
            id = 0,
            code = "404",
            name = "Not Found",
            type = "error",
            message = "JoPickOrderRecord not found",
            errorPosition = "operator"
        )
    }

    val joPickOrderRecord = joPickOrderRecordOpt.get()
    if (userId != null && joPickOrderRecord.handledBy != null) {
        val existingOperatorId = joPickOrderRecord.handledBy
        val newOperatorId = userId
        
        // 如果不是同一个用户，拒绝更新
        if (existingOperatorId != null && existingOperatorId != newOperatorId) {
            return MessageResponse(
                id = joPickOrderRecord.id,
                code = "409",
                name = "Conflict",
                type = "error",
                message = "This pick order is already assigned to another user",
                errorPosition = "operator"
            )
        }
    }
    joPickOrderRecord.handledBy = userId
    joPickOrderRecordRepository.save(joPickOrderRecord)
    // Don't update other fields - only handledBy
    
    return MessageResponse(
        id = joPickOrderRecord.id,
        code = null,
        name = null,
        type = null,
        message = "Pick order record handled by updated",
        errorPosition = null
    )
}
@Transactional(rollbackFor = [Exception::class])
open fun deleteJoPickOrderJobOrderProductProcessPickOrder(jobOrderId: Long): MessageResponse {
    println("=== deleteJoPickOrderJobOrderProductProcessPickOrder ===")
    println("jobOrderId: $jobOrderId")

    // 1. 只允许删除 PLANNING 的工单
    val jobOrder = jobOrderRepository.findById(jobOrderId)
        .orElseThrow { NoSuchElementException("JobOrder $jobOrderId not found") }

    if (jobOrder.status != JobOrderStatus.PLANNING) {
        return null as MessageResponse;
    }

    // 2. Product Process & Product Process Line
    val productProcesses = productProcessRepository.findByJobOrder_Id(jobOrderId)
    val productProcessIds = productProcesses.mapNotNull { it.id }

    if (productProcessIds.isNotEmpty()) {
        val productProcessLines =
            productProcessLineRepository.findByProductProcess_IdIn(productProcessIds)
        if (productProcessLines.isNotEmpty()) {
            productProcessLineRepository.deleteAll(productProcessLines)
        }
        productProcessRepository.deleteAll(productProcesses)
    }

    // 3. PickOrder / PickOrderLine / JoPickOrder / JoPickOrderRecord
    val pickOrders = pickOrderRepository.findAllByJobOrder_Id(jobOrderId)
    if (pickOrders.isNotEmpty()) {
        val pickOrderIds = pickOrders.mapNotNull { it.id }

        // 3.1 所有 pick order line
        val pickOrderLines = pickOrderIds.flatMap { poId ->
            pickOrderLineRepository.findAllByPickOrderId(poId)
        }
        val pickOrderLineIds = pickOrderLines.mapNotNull { it.id }

        // 3.2 jo_pick_order & jo_pick_order_record
        pickOrderIds.forEach { poId ->
            val joPickOrders = joPickOrderRepository.findByPickOrderId(poId)
            if (joPickOrders.isNotEmpty()) {
                joPickOrderRepository.deleteAll(joPickOrders)
            }
            val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(poId)
            if (joPickOrderRecords.isNotEmpty()) {
                joPickOrderRecordRepository.deleteAll(joPickOrderRecords)
            }
        }

        // 4. SuggestedPickLot & 释放 inventoryLotLine.holdQty
        if (pickOrderLineIds.isNotEmpty()) {
            val suggestedPickLots =
                suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds)
            if (suggestedPickLots.isNotEmpty()) {
                val lotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id }.distinct()
                if (lotLineIds.isNotEmpty()) {
                    val lotLines = inventoryLotLineRepository.findAllByIdIn(lotLineIds)
                    // 把当初 hold 住的 qty 减回去（releaseJobOrder 里是直接 + qty，这里反向 - qty）
                    lotLines.forEach { ill ->
                        val totalQtyToRelease = suggestedPickLots
                            .filter { it.suggestedLotLine?.id == ill.id }
                            .mapNotNull { it.qty }
                            .fold(BigDecimal.ZERO) { acc, q -> acc + q }
                        if (totalQtyToRelease > BigDecimal.ZERO) {
                            val current = ill.holdQty ?: BigDecimal.ZERO
                            ill.holdQty = current - totalQtyToRelease
                        }
                    }
                    inventoryLotLineRepository.saveAll(lotLines)
                }
                suggestPickLotRepository.deleteAll(suggestedPickLots)
            }
        }

        // 5. StockOut & StockOutLine（按 consoPickOrderCode 关联）
        pickOrders
            .mapNotNull { it.consoCode }
            .mapNotNull { stockOutRepository.findByConsoPickOrderCode(it).orElse(null) }
            .forEach { stockOut ->
                val lines = stockOutLineRepository.findAllByStockOutId(stockOut.id!!)
                if (lines.isNotEmpty()) {
                    stockOutLineRepository.deleteAll(lines)
                }
                stockOutRepository.delete(stockOut)
            }

        // 6. 最后删 PickOrderLine & PickOrder
        if (pickOrderLines.isNotEmpty()) {
            pickOrderLineRepository.deleteAll(pickOrderLines)
        }
        pickOrderRepository.deleteAll(pickOrders)
    }
    val jobOrderProcesses = jobOrderProcessRepository.findAllByJo_Id(jobOrderId)
    if (jobOrderProcesses.isNotEmpty()) {
        jobOrderProcessRepository.deleteAll(jobOrderProcesses)
    }
    // 7. 最后删除 JobOrder 本身
    jobOrderRepository.delete(jobOrder)

    return MessageResponse(
        id = jobOrder.id,
        code = jobOrder.code,
        name = jobOrder.bom?.name,
        type = null,
        message = "Job Order deleted",
        errorPosition = null
    )
}
open fun getMaterialPickStatus(date: String?): List<MaterialPickStatusItem> {
    // Parse date filter if provided
    val filterDate = date?.let {
        try {
            LocalDate.parse(it)
        } catch (e: Exception) {
            null
        }
    }
    
    // Get all joPickOrders
    val joPickOrders = joPickOrderRepository.findAll()
    if (joPickOrders.isEmpty()) return emptyList()

    // Batch load related Job Orders once to avoid repeated findById calls
    val jobOrderIds = joPickOrders.mapNotNull { it.jobOrderId }.distinct()
    val jobOrdersById = if (jobOrderIds.isEmpty()) {
        emptyMap()
    } else {
        jobOrderRepository.findAllById(jobOrderIds).associateBy { it.id }
    }

    // Filter by date/status in-memory using preloaded Job Orders
    val groupedByJobOrder = joPickOrders
        .filter { joPickOrder ->
            val jobOrder = joPickOrder.jobOrderId?.let { jobOrdersById[it] } ?: return@filter false
            val matchesDate = filterDate == null || jobOrder.planStart?.toLocalDate() == filterDate
            val notCompleted = jobOrder.status != JobOrderStatus.COMPLETED
            matchesDate && notCompleted
        }
        .groupBy { it.jobOrderId }

    val allPickOrderIds = groupedByJobOrder.values
        .flatten()
        .mapNotNull { it.pickOrderId }
        .distinct()

    val pickOrdersById = if (allPickOrderIds.isEmpty()) {
        emptyMap()
    } else {
        pickOrderRepository.findAllById(allPickOrderIds).associateBy { it.id }
    }

    val allPickOrderLines = if (allPickOrderIds.isEmpty()) {
        emptyList()
    } else {
        pickOrderLineRepository.findAllByPickOrderIdInAndDeletedFalse(allPickOrderIds)
    }
    val pickOrderLinesByPickOrderId = allPickOrderLines.groupBy { it.pickOrder?.id }

    val pickOrderLineIds = allPickOrderLines.mapNotNull { it.id }.distinct()
    val allStockOutLines = if (pickOrderLineIds.isEmpty()) {
        emptyList()
    } else {
        stockOutLineRepository.findAllByPickOrderLineIdInAndDeletedFalse(pickOrderLineIds)
    }
    val stockOutLinesByPickOrderLineId = allStockOutLines.groupBy { it.pickOrderLine?.id }

    val allIssues = if (allPickOrderIds.isEmpty()) {
        emptyList()
    } else {
        pickExecutionIssueRepository.findAllByPickOrderIdInAndDeletedFalse(allPickOrderIds)
    }
    val issuesByPickOrderId = allIssues.groupBy { it.pickOrderId }
    
    
    // Map each job order group to a single MaterialPickStatusItem
    return groupedByJobOrder.mapNotNull { (jobOrderId, joPickOrdersForJob) ->
        if (jobOrderId == null) return@mapNotNull null
        
        val jobOrder = jobOrdersById[jobOrderId] ?: return@mapNotNull null
        
        // Get BOM item (finished good/semi-finished product), not BOM Material item
        val bomItem = jobOrder.bom?.item
        if (bomItem == null) return@mapNotNull null
        
        // Get all pick orders for this job order
        val pickOrderIds = joPickOrdersForJob.mapNotNull { it.pickOrderId }.distinct()
        
        // Aggregate data from all pick orders for this job order
        val allPickOrderLinesForJob = pickOrderIds.flatMap { poId ->
            pickOrderLinesByPickOrderId[poId].orEmpty()
        }
        
        // Get all stock out lines for all pick orders of this job order
        val allStockOutLinesForJob = allPickOrderLinesForJob.flatMap { pol ->
            val polId = pol.id ?: return@flatMap emptyList()
            stockOutLinesByPickOrderLineId[polId].orEmpty()
        }
        
        // ✅ 修复：startTime = 第一个 item 开始提料的时间
        // 只考虑已经开始的 items（status 不是 pending），取最早的 startTime
        val pickStartTime = allStockOutLinesForJob
            .filter { it.status != null && it.status != "pending" }
            .mapNotNull { it.startTime }
            .minOrNull()
        
        // Count total items to pick (number of distinct pick order lines)
        val numberOfItemsToPick = allPickOrderLinesForJob.size
        
        // 已结束行数：有至少一条 completed，或整行全是 rejected 都算「已结束」
        val finishedItemsCount = allPickOrderLinesForJob.count { pol ->
            val stockOutLinesForPol = allStockOutLinesForJob.filter {
                it.pickOrderLine?.id == pol.id
            }
            stockOutLinesForPol.any { it.status == "completed" } ||
                (stockOutLinesForPol.isNotEmpty() && stockOutLinesForPol.all { it.status == "rejected" })
        }
        
        // 只有当所有 items 都已结束（完成或全部拒绝）时，才返回 endTime
        val pickEndTime = if (finishedItemsCount == numberOfItemsToPick && numberOfItemsToPick > 0) {
            val completedEndTime = allStockOutLinesForJob
                .filter { it.status == "completed" }
                .mapNotNull { it.endTime }
                .maxOrNull()
            // 若没有任何 completed 的 endTime（例如全部 rejected），用 pick order 的 completeDate 作为结束时间
            completedEndTime ?: pickOrderIds.mapNotNull { poId ->
                pickOrdersById[poId]?.completeDate
            }.maxOrNull()
        } else {
            null
        }
        
        // Count total items to pick (number of distinct pick order lines)
        //val numberOfItemsToPick = allPickOrderLines.size
        
        // Count total items with issues from all pick orders
        val numberOfItemsWithIssue = pickOrderIds.sumOf { poId ->
            issuesByPickOrderId[poId]?.size ?: 0
        }
        
        // Get job order quantity and UOM
        val jobOrderQty = jobOrder.reqQty?.toDouble()
        val uom = jobOrder.bom?.uom?.code
        
        // Determine pick status - check if all pick orders are completed
        val pickOrders = pickOrderIds.mapNotNull { poId -> pickOrdersById[poId] }
        val pickStatus = when {
            pickOrders.isEmpty() -> null
            pickOrders.all { it.status == com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.COMPLETED } -> "completed"
            pickOrders.any { it.status == com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED } -> "released"
            else -> "pending"
        }
        
        // Use the first pick order code (or combine if multiple)
        val pickOrderCode = pickOrderIds.firstOrNull()?.let { poId -> pickOrdersById[poId]?.code }
        
        MaterialPickStatusItem(
            id = jobOrderId, // Use jobOrderId as id
            pickOrderId = pickOrderIds.firstOrNull(), // First pick order ID
            pickOrderCode = pickOrderCode,
            jobOrderId = jobOrderId,
            jobOrderCode = jobOrder.code,
            itemId = bomItem.id, // BOM item ID (finished good/semi-finished)
            itemCode = bomItem.code, // BOM item code
            itemName = bomItem.name, // BOM item name (finished good/semi-finished)
            jobOrderQty = jobOrderQty,
            uom = uom,
            pickStartTime = pickStartTime,
            pickEndTime = pickEndTime,
            numberOfItemsToPick = numberOfItemsToPick,
            numberOfItemsWithIssue = numberOfItemsWithIssue,
            pickStatus = pickStatus
        )
    }
}
}