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 java.time.LocalDate

import java.time.LocalDateTime
import com.ffii.core.support.JdbcDao
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

@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
) {
    
    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): 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
            it.matchStatus = JoPickOrderStatus.pending // 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 new 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): Map<String, Any?> {
        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")
            return emptyMap()
        }
        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")
        
        val visiblePickOrders = allAssignedPickOrders
        
        // Filter based on assignment and status
        val filteredPickOrders = if (visiblePickOrders.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 mapOf(
                "pickOrder" to null as Any?,
                "pickOrderLines" to emptyList<Map<String, Any>>() as Any?
            )
        }
        
        // ✅ 修复：简化 SQL 查询，移除不存在的字段
        val pickOrderIdsStr = pickOrderIds.joinToString(",")
        
        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.type as pickOrderType,
            po.status as pickOrderStatus,
            po.assignTo as pickOrderAssignTo,
            
            -- 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,   
            
            -- ✅ Simplified Router Information using warehouse
            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,
            COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId,
            ill.inventoryLotId as debugInventoryLotId,
            
            -- 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,
            jpo.ticketReleaseTime as ticketReleaseTime,
            jpo.ticketCompleteTime as ticketCompleteTime
            
        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  -- ✅ 直接关联 warehouse
        LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
        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 IN ($pickOrderIdsStr)
        AND pol.deleted = false
        AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED')
        AND po.assignTo = :userId
        AND ill.deleted = false
        AND 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,  -- ✅ 使用 warehouse.order 排序
            po.code ASC,
            i.code ASC,
            il.expiryDate ASC, 
            il.lotNo ASC
    """.trimIndent()
        
        println("🔍 Executing SQL for job order hierarchical structure: $sql")
        println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr")
        
        // ✅ 修复：直接执行 SQL 查询并构建分层结构
        try {
            val results = jdbcDao.queryForList(sql, mapOf("userId" to userId))
            
            if (results.isEmpty()) {
                return mapOf(
                    "pickOrder" to null as Any?,
                    "pickOrderLines" to emptyList<Map<String, Any>>() as Any?
                )
            }
            
            // 构建分层结构
            val pickOrderInfo = results.firstOrNull()?.let { firstRow: Map<String, Any?> ->
                mapOf(
                    "id" to firstRow["pickOrderId"],
                    "code" to firstRow["pickOrderCode"],
                    "consoCode" to firstRow["pickOrderConsoCode"],
                    "targetDate" to firstRow["pickOrderTargetDate"],
                    "type" to firstRow["pickOrderType"],
                    "status" to firstRow["pickOrderStatus"],
                    "assignTo" to firstRow["pickOrderAssignTo"],
                    "jobOrder" to mapOf(
                        "id" to firstRow["jobOrderId"],
                        "code" to firstRow["jobOrderCode"],
                        "name" to "Job Order ${firstRow["jobOrderCode"]}" // ✅ 使用生成的名称
                    )
                )
            }
            
            // 按 pick order line 分组
            val groupedByLine = results.groupBy { row: Map<String, Any?> -> row["pickOrderLineId"] }
            val pickOrderLines = groupedByLine.map { (lineId: Any?, lineRows: List<Map<String, Any?>>) ->
                val firstLineRow = lineRows.first()
                mapOf(
                    "id" to lineId,
                    "itemId" to firstLineRow["itemId"],
                    "itemCode" to firstLineRow["itemCode"],
                    "itemName" to firstLineRow["itemName"],
                    "requiredQty" to firstLineRow["pickOrderLineRequiredQty"],
                    "uomCode" to firstLineRow["uomCode"],
                    "uomDesc" to firstLineRow["uomDesc"],
                    "lots" to lineRows.map { row: Map<String, Any?> ->
                        mapOf(
                            "lotId" to row["lotId"],
                            "lotNo" to row["lotNo"],
                            "expiryDate" to row["expiryDate"],
                            "location" to row["location"],
                            "availableQty" to row["availableQty"],
                            "requiredQty" to row["requiredQty"],
                            "actualPickQty" to row["actualPickQty"],
                            "processingStatus" to row["processingStatus"],
                            "lotAvailability" to row["lotAvailability"],
                            "pickOrderId" to row["pickOrderId"],
                            "pickOrderCode" to row["pickOrderCode"],
                            "pickOrderConsoCode" to row["pickOrderConsoCode"],
                            "pickOrderLineId" to row["pickOrderLineId"],
                            "stockOutLineId" to row["stockOutLineId"],
                            "suggestedPickLotId" to row["suggestedPickLotId"],
                            "stockOutLineQty" to row["stockOutLineQty"], 
                            "stockOutLineStatus" to row["stockOutLineStatus"],
                            "routerIndex" to row["routerIndex"],
                            "routerArea" to row["routerArea"],
                            "routerRoute" to row["routerRoute"],
                            "uomShortDesc" to row["uomShortDesc"],
                            "matchStatus" to row["match_status"],
                            "matchBy" to row["match_by"],
                            "matchQty" to row["match_qty"]
                        )
                    }
                )
            }
            
            return mapOf(
                "pickOrder" to pickOrderInfo as Any?,
                "pickOrderLines" to pickOrderLines as Any?
            )
            
        } catch (e: Exception) {
            println("❌ Error executing Job Order SQL: ${e.message}")
            e.printStackTrace()
            return mapOf(
                "pickOrder" to null as Any?,
                "pickOrderLines" to emptyList<Map<String, Any>>() as Any?
            )
        }
    }

    // Get completed job order pick orders (for second tab)
    // ✅ Fix the getCompletedJobOrderLotsHierarchical method
open fun getCompletedJobOrderLotsHierarchical(userId: Long): Map<String, Any?> {
    println("=== Debug: getCompletedJobOrderLotsHierarchical ===")
    println("today: ${LocalDate.now()}")
    println("userId filter: $userId")
    
    // Get all COMPLETED pick orders assigned to the user with joId
    val user = userService.find(userId).orElse(null)
    if (user == null) {
        println("❌ User not found: $userId")
        return emptyMap()
    }
    
    // Get all COMPLETED pick orders assigned to user that have joId
            val completedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn(
                userId,
                listOf(PickOrderStatus.COMPLETED)
    ).filter { it.jobOrder != null } // Only pick orders with joId

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

    val visiblePickOrders = completedPickOrders
    
    if (visiblePickOrders.isEmpty()) {
        println("🔍 DEBUG: No visible completed job orders found")
        return mapOf(
            "pickOrder" to null as Any?,
            "pickOrderLines" to emptyList<Map<String, Any>>() as Any?
        )
    }
    
    // For completed orders, show the latest one
    val latestCompletedOrder = visiblePickOrders.maxByOrNull { 
        it.completeDate ?: it.modified ?: LocalDateTime.MIN 
    }
    
    if (latestCompletedOrder == null) {
        println("🔍 DEBUG: No latest completed order found")
        return mapOf(
            "pickOrder" to null as Any?,
            "pickOrderLines" to emptyList<Map<String, Any>>() as Any?
        )
    }
    
    println("🔍 DEBUG: Using latest completed order: ${latestCompletedOrder.code}")
    
    // ✅ Use the same SQL query approach as getAllJobOrderLotsWithDetailsHierarchical
    val pickOrderIds = listOf(latestCompletedOrder.id!!)
    val pickOrderIdsStr = pickOrderIds.joinToString(",")
    
    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,
            'jo' as pickOrderType,
            po.status as pickOrderStatus,
            po.assignTo as pickOrderAssignTo,
            
            -- 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.`order` as routerIndex,
            w.code as routerRoute,
            w.code as routerArea,
            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,
            COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId,
            ill.inventoryLotId as debugInventoryLotId,
            
            -- 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

        LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
        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 IN ($pickOrderIdsStr)
        AND pol.deleted = false
        AND po.status = 'COMPLETED'
        AND po.assignTo = :userId
        AND ill.deleted = false
        AND il.deleted = false
        AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
        AND (jpo.match_status = 'pending' OR jpo.match_status = 'scanned')
        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 job order hierarchical structure: $sql")
    println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr")
    
    // ✅ Execute SQL query and build hierarchical structure
    try {
        val results = jdbcDao.queryForList(sql, mapOf("userId" to userId))
        
        if (results.isEmpty()) {
            return mapOf(
                "pickOrder" to null as Any?,
                "pickOrderLines" to emptyList<Map<String, Any>>() as Any?
            )
        }
        
        // Build hierarchical structure
        val pickOrderInfo = results.firstOrNull()?.let { firstRow: Map<String, Any?> ->
                        mapOf(
                "id" to firstRow["pickOrderId"],
                "code" to firstRow["pickOrderCode"],
                "consoCode" to firstRow["pickOrderConsoCode"],
                "targetDate" to firstRow["pickOrderTargetDate"],
                "type" to firstRow["pickOrderType"],
                "status" to firstRow["pickOrderStatus"],
                "assignTo" to firstRow["pickOrderAssignTo"],
                "jobOrder" to mapOf(
                    "id" to firstRow["jobOrderId"],
                    "code" to firstRow["jobOrderCode"],
                    "name" to "Job Order ${firstRow["jobOrderCode"]}"
                )
            )
        }
        
        // Group by pick order line
        val groupedByLine = results.groupBy { row: Map<String, Any?> -> row["pickOrderLineId"] }
        val pickOrderLines = groupedByLine.map { (lineId: Any?, lineRows: List<Map<String, Any?>>) ->
            val firstLineRow = lineRows.first()
            mapOf(
                "id" to lineId,
                "itemId" to firstLineRow["itemId"],
                "itemCode" to firstLineRow["itemCode"],
                "itemName" to firstLineRow["itemName"],
                "requiredQty" to firstLineRow["pickOrderLineRequiredQty"],
                "uomCode" to firstLineRow["uomCode"],
                "uomDesc" to firstLineRow["uomDesc"],
                "lots" to lineRows.map { row: Map<String, Any?> ->
                    mapOf(
                        "lotId" to row["lotId"],
                        "lotNo" to row["lotNo"],
                        "expiryDate" to row["expiryDate"],
                        "location" to row["location"],
                        "availableQty" to row["availableQty"],
                        "requiredQty" to row["requiredQty"],
                        "actualPickQty" to row["actualPickQty"],
                        "processingStatus" to row["processingStatus"],
                        "lotAvailability" to row["lotAvailability"],
                        "pickOrderId" to row["pickOrderId"],
                        "pickOrderCode" to row["pickOrderCode"],
                        "suggestedPickLotId" to row["suggestedPickLotId"],
                        "stockOutLineQty" to row["stockOutLineQty"],
                        "pickOrderConsoCode" to row["pickOrderConsoCode"],
                        "pickOrderLineId" to row["pickOrderLineId"],
                        "stockOutLineId" to row["stockOutLineId"],
                        "stockOutLineStatus" to row["stockOutLineStatus"],
                        "routerIndex" to row["routerIndex"],
                        "routerArea" to row["routerArea"],
                        "routerRoute" to row["routerRoute"],
                        "uomShortDesc" to row["uomShortDesc"],
                        "matchStatus" to row["match_status"],
                        "matchBy" to row["match_by"],
                        "matchQty" to row["match_qty"]
                    )
                }
            )
        }
        
        return mapOf(
            "pickOrder" to pickOrderInfo as Any?,
            "pickOrderLines" to pickOrderLines as Any?
        )
        
        } catch (e: Exception) {
        println("❌ Error executing completed Job Order SQL: ${e.message}")
            e.printStackTrace()
        return mapOf(
            "pickOrder" to null as Any?,
            "pickOrderLines" to emptyList<Map<String, Any>>() as Any?
        )
        }
    }

    // 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 recordDetails = completedRecords.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)
        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
        )
    }
    }
    // ✅ 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
        
        // ✅ 添加：如果 ticketCompleteTime 还没设置，现在设置（通常已经在拣货完成时设置了）
        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(userId: Long): List<Map<String, Any?>> {
    println("=== getCompletedJobOrderPickOrdersWithCompletedSecondScan ===")
    println("userId: $userId")
    
    return try {
        // Get all completed pick orders assigned to user that have joId
        val completedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn(
            userId,
            listOf(PickOrderStatus.COMPLETED)
        ).filter { it.jobOrder != null } // Only pick orders with joId

        println("Found ${completedPickOrders.size} completed job order pick orders for user $userId")

        val completedJobOrderPickOrders = completedPickOrders.mapNotNull { pickOrder ->
            val jobOrder = pickOrder.jobOrder
            if (jobOrder != null) {
                // Check if all items in this pick order have completed second scan
                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?.let { 
                            "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                        },
                        "pickOrderStatus" to pickOrder.status,
                        "completedDate" to pickOrder.completeDate?.let { 
                            "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                        },
                        "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 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
            LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
            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'
            AND ill.deleted = false
            AND 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(userId: Long): List<Map<String, Any?>> {
        println("=== getCompletedJobOrderPickOrders ===")
        println("userId: $userId")

        return try {
            // Get all completed pick orders assigned to user that have joId
            val completedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn(
                userId,
                listOf(PickOrderStatus.COMPLETED)
            ).filter { it.jobOrder != null } // Only pick orders with joId

            println("Found ${completedPickOrders.size} completed job order pick orders for user $userId")

            val completedJobOrderPickOrders = completedPickOrders.mapNotNull { pickOrder ->
                val jobOrder = pickOrder.jobOrder
                if (jobOrder != null) {
                    // Get JoPickOrder records for this pick order
                    val joPickOrders = findByPickOrderId(pickOrder.id!!)

                    // Calculate second scan completion status
                    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}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                        },
                        "pickOrderStatus" to pickOrder.status,
                        "completedDate" to pickOrder.completeDate?.let {
                            "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
                        },
                        "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()
        }
    }

// ... rest of the code ...

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
            LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
            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'
            AND ill.deleted = false
            AND 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()
    }
}
}
