// FPSMS-backend/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
package com.ffii.fpsms.modules.pickOrder.service

import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue
import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLine
import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory //  添加这行
import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.service.StockOutLineService

import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.annotation.Propagation
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecordRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum
import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus
import com.ffii.fpsms.modules.stock.web.model.StockOutRequest
import com.ffii.fpsms.modules.stock.web.model.StockOutStatus
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus
import com.ffii.fpsms.modules.pickOrder.web.models.*
import com.ffii.fpsms.modules.common.SecurityUtils
import com.ffii.fpsms.modules.stock.entity.StockOut
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.entity.ItemUomRespository
import com.ffii.fpsms.modules.common.CodeGenerator
import com.ffii.fpsms.modules.stock.web.model.BatchStockOutResult
import com.ffii.fpsms.modules.stock.entity.StockLedger
import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository
import org.springframework.beans.factory.annotation.Value
import com.ffii.fpsms.modules.stock.web.model.BatchStockOutRequest
import com.ffii.fpsms.modules.stock.web.model.BatchStockOutLineRequest
@Service
open class PickExecutionIssueService(
    private val pickExecutionIssueRepository: PickExecutionIssueRepository,
    private val stockOutLineRepository: StockOutLIneRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val inventoryRepository: InventoryRepository,
    private val suggestedPickLotService: SuggestedPickLotService,
    private val pickOrderRepository: PickOrderRepository,
    private val jdbcDao: JdbcDao,
    private val stockOutRepository: StockOutRepository,
    private val stockOutLineService: StockOutLineService,
    private val pickOrderLineRepository: PickOrderLineRepository,
    private val doPickOrderService: DoPickOrderService,
    private val joPickOrderRepository: JoPickOrderRepository,
    private val joPickOrderRecordRepository: JoPickOrderRecordRepository,
    private val itemsRepository: ItemsRepository,
    private val itemUomRespository: ItemUomRespository,
    private val stockLedgerRepository: StockLedgerRepository,
    private val deliveryOrderRepository: DeliveryOrderRepository,
    private val suggestedPickLotRepository: SuggestPickLotRepository,
    private val doPickOrderRepository: DoPickOrderRepository

) {
    // 配置属性：是否在批次拒绝时自动重新建议批次（默认为 false，不自动重新建议）
    @Value("\${pick.execution.auto-resuggest-on-rejection:false}")
    private val autoResuggestOnLotRejection: Boolean = false

    open fun recordPickExecutionIssue(request: PickExecutionIssueRequest): MessageResponse {
        try {
            println("=== recordPickExecutionIssue: START ===")
            println("Request details:")
            println("  pickOrderId: ${request.pickOrderId}")
            println("  pickOrderLineId: ${request.pickOrderLineId}")
            println("  itemId: ${request.itemId}")
            println("  itemCode: ${request.itemCode}")
            println("  lotId: ${request.lotId}")
            println("  lotNo: ${request.lotNo}")
            println("  requiredQty: ${request.requiredQty}")
            println("  actualPickQty: ${request.actualPickQty}")
            println("  missQty: ${request.missQty}")
            println("  badItemQty: ${request.badItemQty}")
            println("  badReason: ${request.badReason}")
            println("  issueCategory: ${request.issueCategory}")
            println("========================================")
            
            // 1. 解析 lot：
            // request.lotId 在前端目前传的是 inventory_lot_line.id（用于 SOL 关联/计算 bookQty 等）
            // 但 pick_execution_issue.lot_id 在 DB 上外键指向 inventory_lot.id
            val inventoryLotLine = request.lotId?.let {
                inventoryLotLineRepository.findById(it).orElse(null)
            }
            val inventoryLotIdForIssue = inventoryLotLine?.inventoryLot?.id

            // 2. 检查是否已经存在相同的 pick execution issue 记录（以 inventory_lot.id 去重）
            val existingIssues = pickExecutionIssueRepository.findByPickOrderLineIdAndLotIdAndDeletedFalse(
                request.pickOrderLineId,
                inventoryLotIdForIssue ?: 0L
            )
            
            println("Checking for existing issues...")
            println("  Found ${existingIssues.size} existing issues")
            existingIssues.forEachIndexed { index, issue ->
                println("    Existing[$index]: id=${issue.id}, issueNo=${issue.issueNo}, handleStatus=${issue.handleStatus}, issueQty=${issue.issueQty}")
            }
            
            if (existingIssues.isNotEmpty()) {
                println("❌ Duplicate issue found - returning DUPLICATE error")
                return MessageResponse(
                    id = null,
                    name = "Pick execution issue already exists",
                    code = "DUPLICATE",
                    type = "pick_execution_issue",
                    message = "A pick execution issue for this lot has already been recorded",
                    errorPosition = null
                )
            }
            
            val pickOrder = pickOrderRepository.findById(request.pickOrderId).orElse(null)
            println("Pick order: id=${pickOrder?.id}, code=${pickOrder?.code}, type=${pickOrder?.type?.value}")
            
            // 3. 计算账面数量 (bookQty)（用 inventory_lot_line 快照）
            println("Inventory lot line: id=${inventoryLotLine?.id}, lotNo=${inventoryLotLine?.inventoryLot?.lotNo}, inventoryLotId=${inventoryLotIdForIssue}")
            
            // 计算账面数量（创建 issue 时的快照）
            val bookQty = if (inventoryLotLine != null) {
                val inQty = inventoryLotLine.inQty ?: BigDecimal.ZERO
                val outQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
                val calculated = inQty.subtract(outQty)  // bookQty = inQty - outQty
                println("  BookQty calculation: inQty=$inQty, outQty=$outQty, bookQty=$calculated")
                calculated
            } else {
                println("  No inventory lot line found, bookQty=0")
                BigDecimal.ZERO
            }
            
            // 4. 获取数量值
            val requiredQty = request.requiredQty ?: BigDecimal.ZERO
            val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO
            val missQty = request.missQty ?: BigDecimal.ZERO
            val badItemQty = request.badItemQty ?: BigDecimal.ZERO
            val badReason = request.badReason ?: "quantity_problem" 
            val relatedStockOutLines = stockOutLineRepository
                .findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
                    request.pickOrderLineId,
                    request.lotId ?: 0L
                )

            val currentStatus = relatedStockOutLines.firstOrNull()?.status ?: ""
            /*
            if (currentStatus.equals("pending", ignoreCase = true)
                && actualPickQty > BigDecimal.ZERO
                && missQty == BigDecimal.ZERO
                && badItemQty == BigDecimal.ZERO
            ) {
                return MessageResponse(
                    id = null,
                    name = "Invalid issue for pending stock out line",
                    code = "ERROR",
                    type = "pick_execution_issue",
                    message = "Cannot submit only actual pick qty when stock out line is pending. Please rescan the lot or use normal pick flow.",
                    errorPosition = null
                )
            }
            */
            val lotRemainAvailable = bookQty        // 当前 lot 剩余
            val maxAllowed = requiredQty + lotRemainAvailable
          
            if (actualPickQty > maxAllowed) {
                return MessageResponse(
                    id = null,
                    name = "Actual pick qty too large",
                    code = "ERROR",
                    type = "pick_execution_issue",
                    message = "Actual pick qty cannot exceed required qty plus lot remaining available.",
                    errorPosition = null
                )
            }

            // 4.1 调整 inventory_lot_line.holdQty（以 actualPickQty-requiredQty 为差额）
            // 例：
            // required=8, actual=6 => delta=-2 => holdQty -= 2（释放预留）
            // required=8, actual=10 => delta=+2 => holdQty += 2（补预留）
            if (inventoryLotLine != null && request.lotId != null) {
                val deltaHold = actualPickQty.subtract(requiredQty)
                if (deltaHold.compareTo(BigDecimal.ZERO) != 0) {
                    val latestLotLine = inventoryLotLineRepository.findById(request.lotId).orElse(null)
                        ?: throw IllegalArgumentException("Inventory lot line not found: ${request.lotId}")

                    val currentHold = latestLotLine.holdQty ?: BigDecimal.ZERO
                    val currentOut = latestLotLine.outQty ?: BigDecimal.ZERO
                    val currentIn = latestLotLine.inQty ?: BigDecimal.ZERO

                    val newHold = currentHold.add(deltaHold)
                    if (newHold < BigDecimal.ZERO) {
                        return MessageResponse(
                            id = null,
                            name = "Invalid hold quantity adjustment",
                            code = "ERROR",
                            type = "pick_execution_issue",
                            message = "Cannot adjust holdQty by $deltaHold. Current holdQty=$currentHold, requiredQty=$requiredQty, actualPickQty=$actualPickQty",
                            errorPosition = null
                        )
                    }

                    // deltaHold > 0 时检查剩余是否足够（in - out - hold）
                    if (deltaHold > BigDecimal.ZERO) {
                        val remaining = currentIn.subtract(currentOut).subtract(currentHold)
                        if (deltaHold > remaining) {
                            return MessageResponse(
                                id = null,
                                name = "Insufficient remaining quantity",
                                code = "ERROR",
                                type = "pick_execution_issue",
                                message = "Cannot reserve additional $deltaHold. Remaining=$remaining (in=$currentIn, out=$currentOut, hold=$currentHold)",
                                errorPosition = null
                            )
                        }
                    }

                    latestLotLine.holdQty = newHold
                    latestLotLine.modified = LocalDateTime.now()
                    latestLotLine.modifiedBy = "system"
                    inventoryLotLineRepository.saveAndFlush(latestLotLine)
                    println("✅ Adjusted inventory_lot_line ${request.lotId} holdQty: $currentHold -> $newHold (delta=$deltaHold)")
                }
            }
            println("=== Quantity Summary ===")
            println("  Required Qty: $requiredQty")
            println("  Actual Pick Qty: $actualPickQty")
            println("  Miss Qty: $missQty")
            println("  Bad Item Qty: $badItemQty")
            println("  Bad Reason: $badReason")
            println("  Book Qty: $bookQty")
            
            // 5. 计算 issueQty（实际的问题数量）
            val issueQty = when {
                // Bad item 或 bad package：一律用用户输入的 bad 数量，不用 bookQty - actualPickQty
                badItemQty > BigDecimal.ZERO -> {
                    println("  Bad item/package: issueQty = badItemQty = $badItemQty")
                    badItemQty
                }
                // 仅 miss/少拣：用短少数量
                actualPickQty < requiredQty -> {
                    val calculatedIssueQty = bookQty.subtract(actualPickQty)
                    println("  Miss/short: issueQty = bookQty - actualPickQty = $bookQty - $actualPickQty = $calculatedIssueQty")
                    if (missQty > BigDecimal.ZERO && missQty > calculatedIssueQty) {
                        println("⚠️ Warning: User reported missQty ($missQty) exceeds calculated issueQty ($calculatedIssueQty)")
                        println("   BookQty: $bookQty, ActualPickQty: $actualPickQty")
                    }
                    calculatedIssueQty
                }
                else -> {
                    println("  Default: issueQty = 0")
                    BigDecimal.ZERO
                }
            }
            
            println("=== Final IssueQty Calculation ===")
            println("  Calculated IssueQty: $issueQty")
            println("================================================")
            println("=== Processing Logic Selection ===")

            // 6. 创建 pick execution issue 记录
            val issueNo = generateIssueNo()
            println("Generated issue number: $issueNo")
            val lotNoForIssue = request.lotNo ?: inventoryLotLine?.inventoryLot?.lotNo
            
            val pickExecutionIssue = PickExecutionIssue(
                id = null,
                pickOrderId = request.pickOrderId,
                pickOrderCode = request.pickOrderCode,
                pickOrderCreateDate = request.pickOrderCreateDate,
                pickExecutionDate = request.pickExecutionDate ?: LocalDate.now(),
                pickOrderLineId = request.pickOrderLineId,
                issueNo = issueNo,
                joPickOrderId = pickOrder?.jobOrder?.id,
                doPickOrderId = if (pickOrder?.type?.value == "do") pickOrder.id else null,
                issueCategory = IssueCategory.valueOf(
                    request.issueCategory ?: "lot_issue"
                ),
                itemId = request.itemId,
                itemCode = request.itemCode,
                itemDescription = request.itemDescription,
                lotId = inventoryLotIdForIssue,
                lotNo = lotNoForIssue,
                storeLocation = request.storeLocation,
                requiredQty = request.requiredQty,
                actualPickQty = request.actualPickQty,
                missQty = request.missQty,
                badItemQty = request.badItemQty,
                bookQty = bookQty,
                issueQty = issueQty,
                issueRemark = request.issueRemark,
                pickerName = request.pickerName,
                handleStatus = HandleStatus.pending,
                handleDate = null,
                handledBy = request.handledBy,
                created = LocalDateTime.now(),
                createdBy = "system",
                version = 0,
                modified = LocalDateTime.now(),
                modifiedBy = "system",
                deleted = false
            )
        
            println("Creating pick execution issue record...")
            val savedIssue = pickExecutionIssueRepository.save(pickExecutionIssue)
            println("✅ Issue record created successfully!")
            println("  Issue ID: ${savedIssue.id}")
            println("  Issue No: ${savedIssue.issueNo}")
            println("  Handle Status: ${savedIssue.handleStatus}")
            println("  Issue Qty: ${savedIssue.issueQty}")
        
            // 7. NEW: Update inventory_lot_line.issueQty（仍然用 lotLineId）
            if (request.lotId != null && inventoryLotLine != null) {
                println("Updating inventory_lot_line.issueQty...")
                // ✅ 修改：如果只有 missQty，不更新 issueQty
                val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO
                val missQty = request.missQty ?: BigDecimal.ZERO
                val badItemQty = request.badItemQty ?: BigDecimal.ZERO
                
                val isMissItemOnly = actualPickQty == BigDecimal.ZERO 
                        && missQty > BigDecimal.ZERO 
                        && badItemQty == BigDecimal.ZERO
                val hasMissItemWithPartialPick = missQty > BigDecimal.ZERO 
                        && actualPickQty > BigDecimal.ZERO
                
                println("  isMissItemOnly: $isMissItemOnly")
                println("  hasMissItemWithPartialPick: $hasMissItemWithPartialPick")
                
                if (!isMissItemOnly && !hasMissItemWithPartialPick) {
                    // Bad item only（含 package_problem）：lot.issueQty = 本次输入数量（= bad_item_qty / issue_qty），不累加
                    val isBadItemOnly = badItemQty > BigDecimal.ZERO && missQty == BigDecimal.ZERO
                    if (isBadItemOnly) {
                        inventoryLotLine.issueQty = issueQty  // = badItemQty，与 pick_execution_issue.bad_item_qty / issue_qty 一致
                        inventoryLotLine.modified = LocalDateTime.now()
                        inventoryLotLine.modifiedBy = "system"
                        inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
                        println("✅ Updated inventory_lot_line ${request.lotId} issueQty = input qty only: $issueQty (bad item only / package problem)")
                    } else {
                        val currentIssueQty = inventoryLotLine.issueQty ?: BigDecimal.ZERO
                        val newIssueQty = currentIssueQty.add(issueQty)
                        inventoryLotLine.issueQty = newIssueQty
                        inventoryLotLine.modified = LocalDateTime.now()
                        inventoryLotLine.modifiedBy = "system"
                        inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
                        println("✅ Updated inventory_lot_line ${request.lotId} issueQty: $currentIssueQty -> $newIssueQty")
                    }
                } else {
                    println("⏭️ Skipped updating issueQty for miss item (lot ${request.lotId})")
                }
            }
            
            // 7. 按规则：issue form 只记录问题 +（可选）把 SOL 标记为 checked
            val stockOutLines = stockOutLineRepository
                .findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
                    request.pickOrderLineId,
                    request.lotId ?: 0L
                )
            stockOutLines.forEach { sol ->
                sol.status = "checked"
                sol.modified = LocalDateTime.now()
                sol.modifiedBy = "system"
                stockOutLineRepository.save(sol)
            }
            stockOutLineRepository.flush()
        
            println("=== recordPickExecutionIssue: SUCCESS ===")
            println("Issue ID: ${savedIssue.id}, Issue No: ${savedIssue.issueNo}")
            return MessageResponse(
                id = savedIssue.id,
                name = "Pick execution issue recorded successfully",
                code = "SUCCESS",
                type = "pick_execution_issue",
                message = "Pick execution issue recorded successfully",
                errorPosition = null
            )
        
        } catch (e: Exception) {
            println("=== recordPickExecutionIssue: ERROR ===")
            println("Error: ${e.message}")
            e.printStackTrace()
            return MessageResponse(
                id = null,
                name = "Failed to record pick execution issue",
                code = "ERROR",
                type = "pick_execution_issue",
                message = "Error: ${e.message}",
                errorPosition = null
            )
        }
    }

    /**
     * Jo / 无异常量场景：不写入 pick_execution_issue、不更新 inventory_lot_line.issue_qty。
     * 仅 (1) 按 actualPickQty - requiredQty 调整 holdQty；(5) 将对应 stock_out_line 标为 checked（不改 qty）。
     * 可重复调用，避免 DUPLICATE。
     */
    open fun applyPickHoldAndMarkSolChecked(request: PickExecutionIssueRequest): MessageResponse {
        try {
            println("=== applyPickHoldAndMarkSolChecked: START ===")
            val missQty = request.missQty ?: BigDecimal.ZERO
            val badItemQty = request.badItemQty ?: BigDecimal.ZERO
            if (missQty.compareTo(BigDecimal.ZERO) > 0 || badItemQty.compareTo(BigDecimal.ZERO) > 0) {
                return MessageResponse(
                    id = null,
                    name = "Invalid request for hold-only API",
                    code = "ERROR",
                    type = "pick_execution_adjustment",
                    message = "This endpoint accepts only actual pick adjustment (miss and bad quantities must be zero). Use /recordIssue for issues.",
                    errorPosition = null
                )
            }
            if (request.lotId == null) {
                return MessageResponse(
                    id = null,
                    name = "lotId required",
                    code = "ERROR",
                    type = "pick_execution_adjustment",
                    message = "inventory lot line id (lotId) is required",
                    errorPosition = null
                )
            }

            val inventoryLotLine = inventoryLotLineRepository.findById(request.lotId).orElse(null)
            val bookQty = if (inventoryLotLine != null) {
                val inQty = inventoryLotLine.inQty ?: BigDecimal.ZERO
                val outQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
                inQty.subtract(outQty)
            } else {
                BigDecimal.ZERO
            }

            val requiredQty = request.requiredQty ?: BigDecimal.ZERO
            val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO
            val lotRemainAvailable = bookQty
            val maxAllowed = requiredQty.add(lotRemainAvailable)

            if (actualPickQty > maxAllowed) {
                return MessageResponse(
                    id = null,
                    name = "Actual pick qty too large",
                    code = "ERROR",
                    type = "pick_execution_adjustment",
                    message = "Actual pick qty cannot exceed required qty plus lot remaining available.",
                    errorPosition = null
                )
            }

            if (inventoryLotLine != null) {
                val deltaHold = actualPickQty.subtract(requiredQty)
                if (deltaHold.compareTo(BigDecimal.ZERO) != 0) {
                    val latestLotLine = inventoryLotLineRepository.findById(request.lotId).orElse(null)
                        ?: throw IllegalArgumentException("Inventory lot line not found: ${request.lotId}")

                    val currentHold = latestLotLine.holdQty ?: BigDecimal.ZERO
                    val currentOut = latestLotLine.outQty ?: BigDecimal.ZERO
                    val currentIn = latestLotLine.inQty ?: BigDecimal.ZERO

                    val newHold = currentHold.add(deltaHold)
                    if (newHold < BigDecimal.ZERO) {
                        return MessageResponse(
                            id = null,
                            name = "Invalid hold quantity adjustment",
                            code = "ERROR",
                            type = "pick_execution_adjustment",
                            message = "Cannot adjust holdQty by $deltaHold. Current holdQty=$currentHold, requiredQty=$requiredQty, actualPickQty=$actualPickQty",
                            errorPosition = null
                        )
                    }

                    if (deltaHold > BigDecimal.ZERO) {
                        val remaining = currentIn.subtract(currentOut).subtract(currentHold)
                        if (deltaHold > remaining) {
                            return MessageResponse(
                                id = null,
                                name = "Insufficient remaining quantity",
                                code = "ERROR",
                                type = "pick_execution_adjustment",
                                message = "Cannot reserve additional $deltaHold. Remaining=$remaining (in=$currentIn, out=$currentOut, hold=$currentHold)",
                                errorPosition = null
                            )
                        }
                    }

                    latestLotLine.holdQty = newHold
                    latestLotLine.modified = LocalDateTime.now()
                    latestLotLine.modifiedBy = "system"
                    inventoryLotLineRepository.saveAndFlush(latestLotLine)
                    println("✅ [hold-only] Adjusted inventory_lot_line ${request.lotId} holdQty: $currentHold -> $newHold (delta=$deltaHold)")
                }
            }

            val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
                request.pickOrderLineId,
                request.lotId
            )
            stockOutLines.forEach { sol ->
                sol.status = "checked"
                sol.modified = LocalDateTime.now()
                sol.modifiedBy = "system"
                stockOutLineRepository.save(sol)
            }
            stockOutLineRepository.flush()

            println("=== applyPickHoldAndMarkSolChecked: SUCCESS (${stockOutLines.size} SOL checked) ===")
            return MessageResponse(
                id = stockOutLines.firstOrNull()?.id,
                name = "Pick hold adjusted and lines marked checked",
                code = "SUCCESS",
                type = "pick_execution_adjustment",
                message = "Pick hold adjusted and stock out lines marked checked",
                errorPosition = null
            )
        } catch (e: Exception) {
            println("=== applyPickHoldAndMarkSolChecked: ERROR === ${e.message}")
            e.printStackTrace()
            return MessageResponse(
                id = null,
                name = "Failed to apply pick hold adjustment",
                code = "ERROR",
                type = "pick_execution_adjustment",
                message = "Error: ${e.message}",
                errorPosition = null
            )
        }
    }

    private fun handleAllZeroMarkCompleted(request: PickExecutionIssueRequest) {
        val stockOutLines = stockOutLineRepository
            .findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
                request.pickOrderLineId,
                request.lotId ?: 0L
            )
    
        stockOutLines.forEach { sol ->
            // issue form 不完结，只标记 checked，让 submit/batch submit 决定 completed（允许 0）
            sol.status = "checked"
            sol.modified = LocalDateTime.now()
            sol.modifiedBy = "system"
            stockOutLineRepository.save(sol)
            println("All-zero case: mark stock out line ${sol.id} as checked (qty kept as ${sol.qty})")
        }
        stockOutLineRepository.flush()
    }
    private fun generateIssueNo(): 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) {
            // 从最新的 issueNo 中提取序列号
            // 例如: SKO-2510-001 -> 001
            val parts = latestIssueNo.split("-")
            if (parts.size == 3) {
                val currentSequence = parts[2].toIntOrNull() ?: 0
                currentSequence + 1
            } else {
                1
            }
        } else {
            1
        }
        
        // 格式化为 SKO-YYMM-001
        return "SKO-${yearMonth}-${nextSequence.toString().padStart(3, '0')}"
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun checkAndCompletePickOrder(consoCode: String) {
    println("=== DEBUG: checkAndCompletePickOrder ===")
    println("consoCode: $consoCode")
    
    // 1. 查找 StockOut
    val stockOut = stockOutRepository.findFirstByConsoPickOrderCodeOrderByIdDesc(consoCode)
    if (stockOut == null) {
        println("❌ No stock_out found for consoCode: $consoCode")
        return
    }
    
    // 2. 查找所有相关的 stock out lines
    val stockOutLinesSql = """
        SELECT sol.*
        FROM stock_out_line sol
        JOIN pick_order_line pol ON pol.id = sol.pickOrderLineId
        JOIN pick_order po ON po.id = pol.poId
        WHERE po.consoCode = :consoCode
        AND sol.deleted = false
    """.trimIndent()
    
    val stockOutLinesResult = jdbcDao.queryForList(stockOutLinesSql, mapOf("consoCode" to consoCode))
    val stockOutLineIds = stockOutLinesResult.mapNotNull { row ->
        when (val id = row["id"]) {
            is Number -> id.toLong()
            is String -> id.toLongOrNull()
            else -> null
        }
    }
    
    val stockOutLines = if (stockOutLineIds.isNotEmpty()) {
        stockOutLineRepository.findAllById(stockOutLineIds)
    } else {
        emptyList()
    }
    
    println("Total stock out lines for consoCode $consoCode: ${stockOutLines.size}")
    
    // 3. 检查是否有未完成的行
    val unfinishedLines = stockOutLines.filter {
        it.status != StockOutLineStatus.COMPLETE.status
                && it.status != StockOutLineStatus.REJECTED.status
                && it.status != StockOutLineStatus.PARTIALLY_COMPLETE.status
    }
    
    println("📊 Stock out lines: ${stockOutLines.size}, Unfinished: ${unfinishedLines.size}")
    
    // 4. 如果所有行都完成或被拒绝，则完成 pick order
    if (unfinishedLines.isEmpty()) {
        println(" All stock out lines completed or rejected, completing pick order...")
        
        // 4.1 更新 StockOut 状态
        stockOut.status = StockOutStatus.COMPLETE.status
        stockOutRepository.saveAndFlush(stockOut)
        
        // 4.2 更新所有 pick order lines 状态
        val completedPickOrderLineIds = stockOutLines.mapNotNull { it.pickOrderLine?.id }
        println("Completed pick order line IDs: $completedPickOrderLineIds")
        
        if (completedPickOrderLineIds.isNotEmpty()) {
            val pickOrderLines = pickOrderLineRepository.findAllById(completedPickOrderLineIds)
            pickOrderLines.forEach { line ->
                line.status = PickOrderLineStatus.COMPLETED
                println("Updated pick order line ${line.id} to COMPLETED")
            }
            pickOrderLineRepository.saveAll(pickOrderLines)
            println(" Updated ${pickOrderLines.size} pick order lines to COMPLETED status")
            
            // 4.3 获取所有受影响的 pick orders
            val pickOrderIds = pickOrderLines.mapNotNull { it.pickOrder?.id }.distinct()
            println("Affected pick order IDs: $pickOrderIds")
            
            // 4.4 检查每个 pick order 是否完全完成
            pickOrderIds.forEach { pickOrderId ->
                val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
                if (pickOrder != null) {
                    // 检查这个 pick order 的所有行是否都完成了
                    val allLines = pickOrder.pickOrderLines
                    val completedLines = allLines.filter { it.status == PickOrderLineStatus.COMPLETED }
                    
                    println("Pick order ${pickOrder.code}: ${completedLines.size}/${allLines.size} lines completed")
                    
                    if (completedLines.size == allLines.size && allLines.isNotEmpty()) {
                        // 所有行都完成了，更新 pick order 状态
                        pickOrder.status = PickOrderStatus.COMPLETED
                        pickOrder.completeDate = LocalDateTime.now()
                        pickOrderRepository.save(pickOrder)
                        println(" Updated pick order ${pickOrder.code} to COMPLETED status")
                        
                        // 4.5 处理 DO pick order 相关记录
                        try {
                            val removedCount = doPickOrderService.removeDoPickOrdersForPickOrder(pickOrderId)
                            println(" Removed $removedCount do_pick_order records for completed pick order ${pickOrderId}")
                            
                            doPickOrderService.completeDoPickOrderRecordsForPickOrder(pickOrderId)
                            println(" Updated do_pick_order_record status to COMPLETED for pick order ${pickOrderId}")
                        } catch (e: Exception) {
                            println("⚠️ Error updating DO pick order records: ${e.message}")
                        }
                        
                        // 4.6 处理 JO pick order 相关记录
                        if (pickOrder.jobOrder != null) {
                            try {
                                val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId)
                                joPickOrders.forEach { 
                                    it.ticketCompleteTime = LocalDateTime.now()
                                }
                                joPickOrderRepository.saveAll(joPickOrders)
                                
                                val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId)
                                joPickOrderRecords.forEach { 
                                    it.ticketCompleteTime = LocalDateTime.now()
                                }
                                joPickOrderRecordRepository.saveAll(joPickOrderRecords)
                                
                                println(" Set jo_pick_order ticketCompleteTime for pick order ${pickOrderId}")
                            } catch (e: Exception) {
                                println("⚠️ Error updating JO pick order records: ${e.message}")
                            }
                        }
                    }
                }
            }
        }
        
        println("🎉 Pick order completed successfully!")
    } else {
        println("⏳ Still have ${unfinishedLines.size} unfinished lines, pick order not completed yet")
    }
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun checkAndCompletePickOrderByPickOrderId(pickOrderId: Long) {
    println("=== DEBUG: checkAndCompletePickOrderByPickOrderId ===")
    println("pickOrderId: $pickOrderId")

    val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
    if (pickOrder == null) {
        println("❌ No pick_order found for id: $pickOrderId")
        return
    }

    // 1. 找出这个 pick order 下所有的 pick order line
    val pickOrderLineIds = pickOrder.pickOrderLines.mapNotNull { it.id }
    if (pickOrderLineIds.isEmpty()) {
        println("⚠️ Pick order ${pickOrder.code} has no lines, skip completion check")
        return
    }

    // 2. 找出这些行对应的所有 stock_out_line
    val stockOutLines = stockOutLineRepository.findAllByPickOrderLineIdInAndDeletedFalse(pickOrderLineIds)
    println("Total stock out lines for pickOrder ${pickOrder.code}: ${stockOutLines.size}")

    // 3. 计算未完成的行（既不是 COMPLETE 也不是 REJECTED）
    val unfinishedLines = stockOutLines.filter {
        it.status != StockOutLineStatus.COMPLETE.status &&
            it.status != StockOutLineStatus.REJECTED.status
    }
    println("📊 Stock out lines: ${stockOutLines.size}, Unfinished: ${unfinishedLines.size}")

    // 4. 如果还有未完成的，不做完结
    if (unfinishedLines.isNotEmpty()) {
        println("⏳ Still have ${unfinishedLines.size} unfinished lines for pickOrder ${pickOrder.code}, not completed yet")
        return
    }

    // 5. 所有行都 COMPLETE/REJECTED → 完成相关 stock_out + pick_order_line + pick_order
    println("✅ All stock out lines completed or rejected for pickOrder ${pickOrder.code}, completing pick order...")

    // 5.1 完成相关的 stock_out（可能有多个）
    val stockOuts = stockOutLines.mapNotNull { it.stockOut }.distinct()
    stockOuts.forEach { so ->
        so.status = StockOutStatus.COMPLETE.status
        stockOutRepository.saveAndFlush(so)
        println("  - Updated stock_out ${so.id} to COMPLETE")
    }

    // 5.2 更新所有 pick_order_line 状态为 COMPLETED
    pickOrder.pickOrderLines.forEach { line ->
        line.status = PickOrderLineStatus.COMPLETED
        println("  - Updated pick_order_line ${line.id} to COMPLETED")
    }
    pickOrderLineRepository.saveAll(pickOrder.pickOrderLines)

    // 5.3 更新 pick_order 本身状态
    pickOrder.status = PickOrderStatus.COMPLETED
    pickOrder.completeDate = LocalDateTime.now()
    pickOrderRepository.save(pickOrder)
    println("  - Updated pick_order ${pickOrder.code} to COMPLETED")

    // 5.4 DO 相关处理
    try {
        // 先获取 doOrderIds，因为 removeDoPickOrdersForPickOrder 会删除记录
        val doPickOrders = doPickOrderService.findByPickOrderId(pickOrderId)
        val doOrderIds = doPickOrders.mapNotNull { it.doOrderId }.distinct()
        
        val removedCount = doPickOrderService.removeDoPickOrdersForPickOrder(pickOrderId)
        println("  - Removed $removedCount do_pick_order records for completed pick order $pickOrderId")

        doPickOrderService.completeDoPickOrderRecordsForPickOrder(pickOrderId)
        println("  - Updated do_pick_order_record status to COMPLETED for pick order $pickOrderId")
        
        // ✅ 5.4.1 检查并更新 DeliveryOrder 状态
        // 如果该 DO 的所有 pick orders 都已完成，则更新 DeliveryOrder 状态
        doOrderIds.forEach { doOrderId ->
            try {
                // 使用 SQL 查询检查该 DO 的所有 pick orders 是否都已完成
                val sql = """
                    SELECT COUNT(DISTINCT po.id) as total_pick_orders,
                           COUNT(DISTINCT CASE WHEN po.status = 'COMPLETED' THEN po.id END) as completed_pick_orders
                    FROM pick_order po
                    JOIN do_pick_order_record dpor ON dpor.pickOrderId = po.id
                    WHERE dpor.doOrderId = :doOrderId
                    AND po.deleted = false
                """.trimIndent()
                
                val result = jdbcDao.queryForList(sql, mapOf("doOrderId" to doOrderId))
                if (result.isNotEmpty()) {
                    val row = result[0]
                    val totalPickOrders = (row["total_pick_orders"] as? Number)?.toInt() ?: 0
                    val completedPickOrders = (row["completed_pick_orders"] as? Number)?.toInt() ?: 0
                    
                    println("  - DO $doOrderId: $completedPickOrders/$totalPickOrders pick orders completed")
                    
                    if (totalPickOrders > 0 && totalPickOrders == completedPickOrders) {
                        val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doOrderId)
                        if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) {
                            deliveryOrder.status = DeliveryOrderStatus.COMPLETED
                            deliveryOrderRepository.save(deliveryOrder)
                            println("  - ✅ Updated delivery order $doOrderId to COMPLETED")
                        }
                    }
                }
            } catch (e: Exception) {
                println("⚠️ Error checking DeliveryOrder completion for doOrderId $doOrderId: ${e.message}")
                e.printStackTrace()
            }
        }
    } catch (e: Exception) {
        println("⚠️ Error updating DO pick order records: ${e.message}")
        e.printStackTrace()
    }

    // 5.5 JO 相关处理（复用 consoCode 版本里的逻辑）
    if (pickOrder.jobOrder != null) {
        try {
            val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId)
            joPickOrders.forEach {
                it.ticketCompleteTime = LocalDateTime.now()
            }
            joPickOrderRepository.saveAll(joPickOrders)

            val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId)
            joPickOrderRecords.forEach {
                it.ticketCompleteTime = LocalDateTime.now()
            }
            joPickOrderRecordRepository.saveAll(joPickOrderRecords)

            println("  - Set jo_pick_order ticketCompleteTime for pick order $pickOrderId")
        } catch (e: Exception) {
            println("⚠️ Error updating JO pick order records: ${e.message}")
            e.printStackTrace()
        }
    }

    println("🎉 Pick order ${pickOrder.code} completed successfully (by pickOrderId, with DO/JO)!")
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, actualPickQty: BigDecimal, missQty: BigDecimal) {
    println("=== HANDLING MISS ITEM WITH PARTIAL PICK (NEW LOGIC: DON'T REJECT LOT) ===")
    println("Actual Pick Qty: ${actualPickQty}")
    println("Miss Qty: ${missQty}")
    
    val lotId = request.lotId ?: return
    val itemId = request.itemId ?: return
    val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
    
    // ✅ 修复：在更新 lot line 之前获取 inventory 的 onHandQty
    val inventoryBeforeUpdate = inventoryRepository.findByItemId(itemId).orElse(null)
    val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble()
    
    if (inventoryLotLine != null) {
        // ✅ 修改：更新 outQty 为 actualPickQty
        val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
        val newOutQty = currentOutQty.add(actualPickQty)
        inventoryLotLine.outQty = newOutQty
        
        // ✅ 修改：释放 holdQty（减少 requiredQty）
        val currentHoldQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO
        val requiredQty = request.requiredQty ?: BigDecimal.ZERO
        val newHoldQty = (currentHoldQty - requiredQty).coerceAtLeast(BigDecimal.ZERO)
        inventoryLotLine.holdQty = newHoldQty
        
        inventoryLotLine.modified = LocalDateTime.now()
        inventoryLotLine.modifiedBy = "system"
        inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
        
        println("Miss item with partial pick: Updated lot ${lotId}")
        println("  - Updated outQty: ${currentOutQty} -> ${newOutQty} (actualPickQty: ${actualPickQty})")
        println("  - Released holdQty: ${currentHoldQty} -> ${newHoldQty} (released: ${requiredQty})")
        println("  - Did NOT set status to UNAVAILABLE")
    }
    
    // ✅ 修改：不更新 unavailableQty（因为不 reject lot）
    
    // ✅ 按规则：issue form 不负责完结/数量提交，只记录问题 +（可选）把 SOL 标记为 checked
    val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
        request.pickOrderLineId,
        request.lotId ?: 0L
    )
    
    stockOutLines.forEach { stockOutLine ->
        stockOutLine.status = "checked"
        stockOutLine.modified = LocalDateTime.now()
        stockOutLine.modifiedBy = "system"
        stockOutLineRepository.saveAndFlush(stockOutLine)
        println("Issue form: marked stock out line ${stockOutLine.id} as checked (no completion)")
    }
    
    // ✅ 修复：检查 pick order line 是否应该标记为完成
    if (request.pickOrderLineId != null) {
        try {
            checkPickOrderLineCompletion(request.pickOrderLineId)
            println("Miss item with partial pick: Checked pick order line ${request.pickOrderLineId} completion status")
        } catch (e: Exception) {
            println("⚠️ Error checking pick order line completion: ${e.message}")
        }
    }
    
    // ✅ 修改：不重新建议拣货批次（因为 lot 仍然可用）
    println("Miss item with partial pick: Did NOT resuggest pick order (lot remains available)")
}

//  修复：Miss item 处理逻辑
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigDecimal) {
    println("=== HANDLING MISS ITEM ONLY (NEW LOGIC: DON'T REJECT LOT) ===")
    println("Miss Qty: ${missQty}")
    
    val lotId = request.lotId ?: return
    val itemId = request.itemId ?: return
    val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
    
    // ✅ 修复：在更新 lot line 之前获取 inventory 的 onHandQty
    val inventoryBeforeUpdate = inventoryRepository.findByItemId(itemId).orElse(null)
    val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble()
    
    if (inventoryLotLine != null) {
        // ✅ 修改：释放 holdQty（减少 holdQty）
        val currentHoldQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO
        val requiredQty = request.requiredQty ?: BigDecimal.ZERO
        // 释放 holdQty：减少 requiredQty（因为已经处理完了）
        val newHoldQty = (currentHoldQty - requiredQty).coerceAtLeast(BigDecimal.ZERO)
        inventoryLotLine.holdQty = newHoldQty
        
        // ✅ 修改：如果有 actualPickQty，更新 outQty
        val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO
        if (actualPickQty > BigDecimal.ZERO) {
            val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
            val newOutQty = currentOutQty.add(actualPickQty)
            inventoryLotLine.outQty = newOutQty
            println("  - Updated outQty: ${currentOutQty} -> ${newOutQty} (actualPickQty: ${actualPickQty})")
        }
        
        inventoryLotLine.modified = LocalDateTime.now()
        inventoryLotLine.modifiedBy = "system"
        inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
        
        println("Miss item only: Updated lot ${lotId}")
        println("  - Released holdQty: ${currentHoldQty} -> ${newHoldQty} (released: ${requiredQty})")
        println("  - Did NOT set status to UNAVAILABLE")
    }
    
    // ✅ 修改：不更新 unavailableQty（因为不 reject lot）
    
    // ✅ 修改：不 reject stock_out_line，根据 actualPickQty 设置状态
    val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
        request.pickOrderLineId,
        request.lotId ?: 0L
    )
    
    stockOutLines.forEach { stockOutLine ->
        val requiredQty = request.requiredQty?.toDouble() ?: 0.0
        val actualPickQtyDouble = request.actualPickQty?.toDouble() ?: 0.0
        
        // 设置状态：如果 actualPickQty >= requiredQty，则为 completed，否则为 completed
        val newStatus = if (actualPickQtyDouble >= requiredQty) {
            "completed"
        } else {
            "completed"
        }
        
        stockOutLine.status = newStatus
        stockOutLine.qty = actualPickQtyDouble
        stockOutLine.modified = LocalDateTime.now()
        stockOutLine.modifiedBy = "system"
        val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine)
        
        println("Updated stock out line ${stockOutLine.id} status to: ${newStatus} (NOT rejected)")
        
        // ✅ 修复：创建 stock_ledger 记录（如果有 actualPickQty），使用更新前的 onHandQty 计算 balance
        if (actualPickQtyDouble > 0) {
            val balance = onHandQtyBeforeUpdate - actualPickQtyDouble
            createStockLedgerForStockOut(savedStockOutLine, "Nor", balance)
        }
    }

    // ✅ 修复：检查 pick order line 是否应该标记为完成
    if (request.pickOrderLineId != null) {
        try {
            checkPickOrderLineCompletion(request.pickOrderLineId)
            println("Miss item only: Checked pick order line ${request.pickOrderLineId} completion status")
        } catch (e: Exception) {
            println("⚠️ Error checking pick order line completion: ${e.message}")
            e.printStackTrace()
        }
    }
    
    // ✅ 修改：不重新建议拣货批次（因为 lot 仍然可用）
    println("Miss item: Did NOT resuggest pick order (lot remains available)")
}

//  修复：Bad item 处理逻辑
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleBadItemOnly(request: PickExecutionIssueRequest, badItemQty: BigDecimal) {
    println("=== HANDLING BAD ITEM ONLY (FIXED LOGIC) ===")
    println("Bad Item Qty: ${badItemQty}")
    
    val lotId = request.lotId ?: return
    val itemId = request.itemId ?: return
    val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
    
    if (inventoryLotLine != null) {
        //  修复：Bad item 不减少 inQty，而是标记为 unavailable
        // 因为 bad item 意味着这些物品质量有问题，不能使用
        inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE
        inventoryLotLine.modified = LocalDateTime.now()
        inventoryLotLine.modifiedBy = "system"
        inventoryLotLineRepository.save(inventoryLotLine)
        
        println("Bad item only: Set lot ${lotId} status to UNAVAILABLE")
        
        // ✅ NEW: Reject ALL stockOutLines using this lot across ALL pick orders
        val allStockOutLinesForLot = stockOutLineRepository
            .findAllByInventoryLotLineIdInAndNotCompletedOrRejected(listOf(lotId))
        
        allStockOutLinesForLot.forEach { sol ->
            sol.status = StockOutLineStatus.REJECTED.status
            sol.modified = LocalDateTime.now()
            sol.modifiedBy = "system"
            println("  - Rejecting stock out line ${sol.id} (pickOrderLine: ${sol.pickOrderLine?.id}) for unavailable lot $lotId")
        }
        
        if (allStockOutLinesForLot.isNotEmpty()) {
            stockOutLineRepository.saveAllAndFlush(allStockOutLinesForLot)
            println("✅ Rejected ${allStockOutLinesForLot.size} stockOutLines for unavailable lot $lotId (across all pick orders)")
        }
    }
    
    //  修复：更新 inventory 表的 unavailableQty
    updateInventoryUnavailableQty(itemId, badItemQty)
    
    //  修复：更新 stock_out_line 状态为 rejected
    updateStockOutLineStatus(request, "rejected")
    
    // ✅ 优化：根据配置决定是否重新建议拣货批次（默认不自动重新建议）
    if (autoResuggestOnLotRejection) {
        try {
            resuggestPickOrder(request.pickOrderId)
            println("Resuggested pick order for bad item qty: ${badItemQty}")
        } catch (e: Exception) {
            println("Error during resuggest in handleBadItemOnly: ${e.message}")
        }
    } else {
        println("Skipped resuggest for bad item (autoResuggestOnLotRejection=false). User can scan another lot to switch.")
    }
}

//  修复：Both miss and bad item 处理逻辑
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty: BigDecimal, badItemQty: BigDecimal) {
    println("=== HANDLING BOTH MISS AND BAD ITEM (FIXED LOGIC) ===")
    println("Miss Qty: ${missQty}, Bad Item Qty: ${badItemQty}")
    
    val lotId = request.lotId ?: return
    val itemId = request.itemId ?: return
    val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
    val totalUnavailableQty = missQty.add(badItemQty)
    
    if (inventoryLotLine != null) {
        //  修复：Miss + Bad item 不减少 inQty，而是标记为 unavailable
        inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE
        inventoryLotLine.modified = LocalDateTime.now()
        inventoryLotLine.modifiedBy = "system"
        inventoryLotLineRepository.save(inventoryLotLine)
        
        println("Both miss and bad item: Set lot ${lotId} status to UNAVAILABLE")
        println("  - Miss Qty: ${missQty}")
        println("  - Bad Item Qty: ${badItemQty}")
        println("  - Total Unavailable Qty: ${totalUnavailableQty}")
        
        // ✅ NEW: Reject ALL stockOutLines using this lot across ALL pick orders
        val allStockOutLinesForLot = stockOutLineRepository
            .findAllByInventoryLotLineIdInAndNotCompletedOrRejected(listOf(lotId))
        
        allStockOutLinesForLot.forEach { sol ->
            sol.status = StockOutLineStatus.REJECTED.status
            sol.modified = LocalDateTime.now()
            sol.modifiedBy = "system"
            println("  - Rejecting stock out line ${sol.id} (pickOrderLine: ${sol.pickOrderLine?.id}) for unavailable lot $lotId")
        }
        
        if (allStockOutLinesForLot.isNotEmpty()) {
            stockOutLineRepository.saveAllAndFlush(allStockOutLinesForLot)
            println("✅ Rejected ${allStockOutLinesForLot.size} stockOutLines for unavailable lot $lotId (across all pick orders)")
        }
    }
    
    //  修复：更新 inventory 表的 unavailableQty
    updateInventoryUnavailableQty(itemId, totalUnavailableQty)
    
    //  修复：更新 stock_out_line 状态为 rejected
    updateStockOutLineStatus(request, "rejected")
    
    // ✅ 优化：根据配置决定是否重新建议拣货批次（默认不自动重新建议）
    if (autoResuggestOnLotRejection) {
        try {
            resuggestPickOrder(request.pickOrderId)
            println("Resuggested pick order for both miss qty: ${missQty} and bad item qty: ${badItemQty}")
        } catch (e: Exception) {
            println("Error during resuggest in handleBothMissAndBadItem: ${e.message}")
        }
    } else {
        println("Skipped resuggest for both miss and bad item (autoResuggestOnLotRejection=false). User can scan another lot to switch.")
    }
}

    //  修复：正常拣货处理逻辑
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
    private fun handleNormalPick(request: PickExecutionIssueRequest, actualPickQty: BigDecimal) {
        println("=== HANDLING NORMAL PICK ===")
        
        val lotId = request.lotId ?: return
        val itemId = request.itemId ?: return
        
        // ✅ 修复：在更新 lot line 之前获取 inventory 的 onHandQty
        val inventoryBeforeUpdate = inventoryRepository.findByItemId(itemId).orElse(null)
        val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble()
        
        // ✅ 修复：更新 stock_out_line，但不要累积 qty
        val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
            request.pickOrderLineId,
            request.lotId ?: 0L
        )
        
        stockOutLines.forEach { stockOutLine ->
            // ✅ 修复：直接设置 qty 为 actualPickQty，不要累积
            val requiredQty = request.requiredQty?.toDouble() ?: 0.0
            val actualPickQtyDouble = actualPickQty.toDouble()
            val newStatus = if (actualPickQtyDouble >= requiredQty) {
                "completed"
            } else {
                "partially_completed"
            }
            
            stockOutLine.status = newStatus
            stockOutLine.qty = actualPickQtyDouble // ✅ 直接设置，不累积
            stockOutLine.modified = LocalDateTime.now()
            stockOutLine.modifiedBy = "system"
            val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine)  // 使用 saveAndFlush
            
            println("Updated stock out line ${stockOutLine.id}: status=${newStatus}, qty=${actualPickQtyDouble}")
            
            // ✅ 修复：为正常拣货创建 stock_ledger 记录，使用更新前的 onHandQty 计算 balance
            val balance = onHandQtyBeforeUpdate - actualPickQtyDouble
            createStockLedgerForStockOut(savedStockOutLine, "Nor", balance)
        }
    
        // ✅ 修复：更新 inventory_lot_line 的 outQty
        val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
        if (inventoryLotLine != null) {
            // ✅ 修复：计算新的 outQty，考虑之前的 outQty
            val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
            val previousPickedQty = currentOutQty.minus(actualPickQty) // 计算之前已拣的数量
            val newOutQty = previousPickedQty.add(actualPickQty) // 更新为新的总拣货数量
            
            inventoryLotLine.outQty = newOutQty
            inventoryLotLine.modified = LocalDateTime.now()
            inventoryLotLine.modifiedBy = "system"
            inventoryLotLineRepository.saveAndFlush(inventoryLotLine)  // 使用 saveAndFlush 确保触发器执行
            
            println("Updated inventory lot line ${lotId} outQty: ${currentOutQty} -> ${newOutQty}")
        }
    }

    //  新方法：统一更新 inventory 表的 unavailableQty
    private fun updateInventoryUnavailableQty(itemId: Long, unavailableQty: BigDecimal) {
        try {
            println("=== INVENTORY UNAVAILABLE QTY UPDATE (TRIGGER HANDLED) ===")
            println("Item ID: ${itemId}")
            println("Expected unavailableQty to be added by trigger: ${unavailableQty}")
            println("Note: Database trigger will handle unavailableQty calculation")
            println("=========================================================")
        } catch (e: Exception) {
            println("Error in updateInventoryUnavailableQty: ${e.message}")
        }
    }

    private fun updateStockOutLineStatus(request: PickExecutionIssueRequest, status: String) {
        val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
            request.pickOrderLineId,
            request.lotId ?: 0L
        )
        
        stockOutLines.forEach { stockOutLine ->
            stockOutLine.status = status
            
            //  FIX: Update qty to actualPickQty before setting status to rejected
            if (status == "rejected" && request.actualPickQty != null) {
                stockOutLine.qty = request.actualPickQty.toDouble()
                println("Updated stock out line ${stockOutLine.id} qty to: ${request.actualPickQty}")
            }
            
            stockOutLine.modified = LocalDateTime.now()
            stockOutLine.modifiedBy = "system"
            stockOutLineRepository.save(stockOutLine)
            
            println("Updated stock out line ${stockOutLine.id} status to: ${status}")
        }
        try {
            stockOutLineRepository.flush()
            
            // ✅ FIX: 直接在这里实现检查逻辑，避免调用 stockOutLineService 导致的 NullPointerException
            val unfinishedLines = stockOutLineRepository
                .findAllByPickOrderLineIdAndDeletedFalse(request.pickOrderLineId)
                .filter {
                    it.status != StockOutLineStatus.COMPLETE.status &&
                        it.status != StockOutLineStatus.REJECTED.status
                }
            
            if (unfinishedLines.isEmpty()) {
                // 所有 stock out lines 都已完成或被拒绝，更新 pick order line 状态为 COMPLETED
                val pickOrderLine = pickOrderLineRepository.findById(request.pickOrderLineId).orElse(null)
                if (pickOrderLine != null) {
                    pickOrderLine.status = PickOrderLineStatus.COMPLETED
                    pickOrderLineRepository.save(pickOrderLine)
                    println("✅ Updated pick order line ${request.pickOrderLineId} to COMPLETED status")
                }
            } else {
                println("⏳ Pick order line ${request.pickOrderLineId} still has ${unfinishedLines.size} unfinished stock out lines")
            }
        } catch (e: Exception) {
            println("⚠️ Error checking pick order line completion: ${e.message}")
            e.printStackTrace()
            // 不中断主流程，只记录错误
        }
    }
    //  修复：使用 REQUIRES_NEW 传播级别，避免事务冲突
    open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
        try {
            val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow()
            
            println("=== RESUGGEST DEBUG START ===")
            println("Pick Order ID: $pickOrderId")
            println("Pick Order Code: ${pickOrder.code}")
            println("Pick Order Status: ${pickOrder.status}")
            
            //  NEW: Get ALL pick orders for the same items
            val itemIds = pickOrder.pickOrderLines.mapNotNull { it.item?.id }.distinct()
            println("Item IDs in current pick order: $itemIds")
    
            // 收集所有競爭 pick order，並按 id 去重
            // ✅ 优化：只考虑活跃的 pick orders（排除已完成和已取消的）
            val allCompetingPickOrders = itemIds.flatMap { itemId ->
                val competingOrders = pickOrderLineRepository.findAllPickOrdersByItemId(itemId)
                    .filter { 
                        it.id != pickOrderId && // Exclude current pick order
                        it.status != PickOrderStatus.COMPLETED
                    }
                // ✅ 优化：只在有 rejected 或 unavailable lots 时才打印详细信息
                if (competingOrders.isNotEmpty()) {
                    println("Found ${competingOrders.size} active competing pick orders for item $itemId")
                }
                competingOrders
            }.distinctBy { it.id }
    
            // basePickOrders 也做一次去重，避免後面 filter 產生大量重複記錄
            val basePickOrders = (listOf(pickOrder) + allCompetingPickOrders).distinctBy { it.id }
    
            val allPickOrderLineIdsToCheck = basePickOrders
                .flatMap { it.pickOrderLines }
                .mapNotNull { it.id }
                .distinct()
            
            println("Checking ${allPickOrderLineIdsToCheck.size} pick order lines for rejected stock out lines")
            
            //  OPTIMIZATION 3: 批量查询所有 stock out lines（一次查询代替 N*M 次查询）
            val allStockOutLines = if (allPickOrderLineIdsToCheck.isNotEmpty()) {
                stockOutLineRepository.findAllByPickOrderLineIdInAndDeletedFalse(allPickOrderLineIdsToCheck)
            } else {
                emptyList()
            }
            
            println("Found ${allStockOutLines.size} stock out lines in batch query")
    
            //  OPTIMIZATION 3: 按 pickOrderLineId 分组，便于后续查找
            val stockOutLinesByPickOrderLineId = allStockOutLines
                .groupBy { it.pickOrderLine?.id }
            
            //  NEW: 預先查出 basePickOrders 所有行對應的 suggestions，用來判斷是否用了 UNAVAILABLE lot
            val allSuggestionsForBaseOrders = if (allPickOrderLineIdsToCheck.isNotEmpty()) {
                suggestedPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIdsToCheck)
            } else {
                emptyList()
            }
    
            val suggestionsByPickOrderLineId = allSuggestionsForBaseOrders.groupBy { it.pickOrderLine?.id }
            val pickOrderIdsWithChanges = mutableSetOf<Long>()
            
            //  FIX: 重算條件 = 有 rejected 行，或者 有使用 UNAVAILABLE lot 的 suggestion
            val allPickOrdersToResuggest = basePickOrders.filter { po ->
                val hasRejected = po.pickOrderLines.any { pol ->
                    val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList()
                    val hasRejectedStockOutLine = stockOutLines.any {
                        it.status?.equals("rejected", ignoreCase = true) == true
                    }
    
                    if (hasRejectedStockOutLine) {
                        println("Pick Order ${po.code} has rejected stock out lines - will resuggest")
                        stockOutLines
                            .filter { it.status?.equals("rejected", ignoreCase = true) == true }
                            .forEach { sol ->
                                println("  - Rejected stock out line: ${sol.id} (lot: ${sol.inventoryLotLine?.id}, qty: ${sol.qty})")
                            }
                    }
    
                    hasRejectedStockOutLine
                }
    
                val hasUnavailableLot = po.pickOrderLines.any { pol ->
                    val suggestions = suggestionsByPickOrderLineId[pol.id] ?: emptyList()
                    val usedUnavailable = suggestions.any {
                        it.suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE
                    }
                    if (usedUnavailable) {
                        println("Pick Order ${po.code} has suggestions using UNAVAILABLE lots - will resuggest")
                    }
                    usedUnavailable
                }
    
                hasRejected || hasUnavailableLot
            }.distinctBy { it.id } 
    
            println("=== RESUGGEST DEBUG ===")
            println("Original pick orders: ${basePickOrders.size}")
            println("Filtered pick orders to resuggest: ${allPickOrdersToResuggest.size}")
            println("Pick orders being resuggested: ${allPickOrdersToResuggest.map { "${it.code}(${it.status})" }}")
            
            //  FIX: Only resuggest if there are actually pick orders with rejected lots
            if (allPickOrdersToResuggest.isEmpty()) {
                println("No pick orders need resuggesting - no rejected lots found")
                return MessageResponse(
                    id = pickOrderId,
                    name = "No resuggest needed",
                    code = "SUCCESS",
                    type = "resuggest",
                    message = "No pick orders have rejected lots, no resuggest needed",
                    errorPosition = null
                )
            }
            
            //  FIX: Get all pick order line IDs for the orders to resuggest
            val allPickOrderLineIds = allPickOrdersToResuggest
                .flatMap { it.pickOrderLines }
                .mapNotNull { it.id }
            
            println("All pick order line IDs to resuggest: ${allPickOrderLineIds.size} lines")
            
            //  FIX: Get all existing suggestions for these pick order lines
            val allSuggestions = suggestedPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds)
            println("Found ${allSuggestions.size} existing suggestions")
            
            //  OPTIMIZATION 3: 批量查询所有相关的 stock out lines（用于检查 rejected 状态）
            val allSuggestionStockOutLines = if (allSuggestions.isNotEmpty()) {
                val suggestionLineIds = allSuggestions.mapNotNull { it.pickOrderLine?.id }.distinct()
                val suggestionLotIds = allSuggestions.mapNotNull { it.suggestedLotLine?.id }.distinct()
                
                //  OPTIMIZATION 3: 使用批量查询方法（一次查询代替 N*M 次查询）
                if (suggestionLineIds.isNotEmpty() && suggestionLotIds.isNotEmpty()) {
                    stockOutLineRepository.findAllByPickOrderLineIdInAndInventoryLotLineIdInAndDeletedFalse(
                        suggestionLineIds,
                        suggestionLotIds
                    )
                } else {
                    emptyList()
                }
            } else {
                emptyList()
            }
            
            println("Found ${allSuggestionStockOutLines.size} stock out lines for suggestions")
            
            //  OPTIMIZATION 3: 按 (pickOrderLineId, inventoryLotLineId) 分组
            val stockOutLinesByLineAndLot = allSuggestionStockOutLines
                .groupBy { 
                    val lineId = it.pickOrderLine?.id
                    val lotId = it.inventoryLotLine?.id
                    lineId to lotId
                }
            
            //  FIX: Separate suggestions to keep (those WITHOUT rejected stock out lines) and delete
            val suggestionsToKeep = allSuggestions.filter { suggestion ->
                val pickOrderLineId = suggestion.pickOrderLine?.id
                val suggestedLotLine = suggestion.suggestedLotLine
                val suggestedLotLineId = suggestedLotLine?.id
    
                // ✅ 如果 lot 已经是 UNAVAILABLE，强制不保留（一定要重新建议）
                val isLotUnavailable = suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE
                if (isLotUnavailable) {
                    println("⏭️ Suggestion ${suggestion.id} uses UNAVAILABLE lot ${suggestedLotLine?.id}, will be deleted")
                    return@filter false
                }
    
                if (pickOrderLineId != null && suggestedLotLineId != null) {
                    val stockOutLines = stockOutLinesByLineAndLot[pickOrderLineId to suggestedLotLineId] ?: emptyList()
                    !stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true }
                } else {
                    true
                }
            }
    
            //  只删除有 rejected stock out lines 的 suggestions，或者使用 UNAVAILABLE lot 的 suggestions
            val suggestionsToDelete = allSuggestions.filter { suggestion ->
                val pickOrderLineId = suggestion.pickOrderLine?.id
                val suggestedLotLine = suggestion.suggestedLotLine
                val suggestedLotLineId = suggestedLotLine?.id
    
                val isLotUnavailable = suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE
    
                if (pickOrderLineId != null && suggestedLotLineId != null) {
                    val stockOutLines = stockOutLinesByLineAndLot[pickOrderLineId to suggestedLotLineId] ?: emptyList()
                    val hasRejected = stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true }
    
                    // ✅ 逻辑：只要 (lot UNAVAILABLE) 或 (有 rejected 的 stock_out_line)，就删掉这条 suggestion
                    if (isLotUnavailable || hasRejected) {
                        println("🗑 Deleting suggestion ${suggestion.id} for lot $suggestedLotLineId, " +
                                "unavailable=$isLotUnavailable, hasRejected=$hasRejected")
                        true
                    } else {
                        false
                    }
                } else {
                    // lotId == null 的老逻辑保留
                    suggestedLotLineId == null
                }
            }
            
            println("Suggestions to keep: ${suggestionsToKeep.size}")
            println("Suggestions to delete: ${suggestionsToDelete.size}")
            
            // ✅ FIX: 更新使用 UNAVAILABLE lot 的 stockOutLine 状态为 rejected
            val unavailableLotIds = suggestionsToDelete
                .filter { it.suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE }
                .mapNotNull { it.suggestedLotLine?.id }
                .distinct()
            
            if (unavailableLotIds.isNotEmpty()) {
                println("=== Updating stock out lines for UNAVAILABLE lots ===")
                println("UNAVAILABLE lot IDs: $unavailableLotIds")
                
                // ✅ FIX: Find ALL stockOutLines using unavailable lots (not just those with suggestions)
                val stockOutLinesToReject = stockOutLineRepository
                    .findAllByInventoryLotLineIdInAndNotCompletedOrRejected(unavailableLotIds)
                    .filter { sol ->
                        sol.status != StockOutLineStatus.REJECTED.status &&
                        sol.status != StockOutLineStatus.COMPLETE.status
                    }
                
                println("Found ${stockOutLinesToReject.size} stock out lines to reject for UNAVAILABLE lots (across all pick orders)")
                
                stockOutLinesToReject.forEach { sol ->
                    val oldStatus = sol.status
                    sol.status = StockOutLineStatus.REJECTED.status
                    sol.modified = LocalDateTime.now()
                    sol.modifiedBy = "system"
                    println("  - Updated stock out line ${sol.id} (lot: ${sol.inventoryLotLine?.id}, pickOrderLine: ${sol.pickOrderLine?.id}) status: $oldStatus -> rejected")
                }
                
                if (stockOutLinesToReject.isNotEmpty()) {
                    stockOutLineRepository.saveAll(stockOutLinesToReject)
                    stockOutLineRepository.flush()
                    println("✅ Updated ${stockOutLinesToReject.size} stock out lines to REJECTED status")
                }
            }
            
            //  FIX: Clear holdQty ONLY for lots that have rejected stock out lines
            val rejectedLotIds = suggestionsToDelete.mapNotNull { it.suggestedLotLine?.id }.distinct()
            println("Rejected lot IDs (clearing holdQty only): $rejectedLotIds")
            
            //  OPTIMIZATION 3: 批量加载和更新 lots
            val rejectedLots = if (rejectedLotIds.isNotEmpty()) {
                inventoryLotLineRepository.findAllById(rejectedLotIds)
            } else {
                emptyList()
            }
            
            rejectedLots.forEach { lot ->
                val originalHoldQty = lot.holdQty ?: BigDecimal.ZERO
                lot.holdQty = BigDecimal.ZERO
                println("Cleared holdQty for rejected lot ${lot.id}: $originalHoldQty -> 0")
            }
            
            if (rejectedLots.isNotEmpty()) {
                inventoryLotLineRepository.saveAll(rejectedLots)
                println("Batch cleared holdQty for ${rejectedLots.size} rejected lots")
            }
    
            // Delete old suggestions
            if (suggestionsToDelete.isNotEmpty()) {
                suggestedPickLotRepository.deleteAll(suggestionsToDelete)
                println("Deleted ${suggestionsToDelete.size} old suggestions")
            }
    
            // ✅ 优化：只调用一次 SuggestedPickLotService.resuggestPickOrder
            // SuggestedPickLotService 会处理所有相关的 pick orders，避免重复处理
            // 注意：PickExecutionIssueService 已经处理了 stockOutLine rejection，
            // 所以 SuggestedPickLotService 只需要重新生成 suggestions
            try {
                suggestedPickLotService.resuggestPickOrder(pickOrderId)
                println("Resuggested pick orders via SuggestedPickLotService")
            } catch (e: Exception) {
                println("Error resuggesting pick orders: ${e.message}")
                e.printStackTrace()
            }
            
            return MessageResponse(
                id = pickOrderId,
                name = "Pick order resuggested successfully",
                code = "SUCCESS",
                type = "resuggest",
                message = "Resuggested ${allPickOrdersToResuggest.size} pick orders",
                errorPosition = null
            )
        } catch (e: Exception) {
            println("Error in resuggestPickOrder: ${e.message}")
            e.printStackTrace()
            return MessageResponse(
                id = null,
                name = "Failed to resuggest pick order",
                code = "ERROR",
                type = "resuggest",
                message = "Error: ${e.message}",
                errorPosition = null
            )
        }
    }

    open fun getPickExecutionIssuesByPickOrder(pickOrderId: Long): List<PickExecutionIssue> {
        return pickExecutionIssueRepository.findByPickOrderIdAndDeletedFalse(pickOrderId)
    }

    open fun getPickExecutionIssuesByPickOrderLine(pickOrderLineId: Long): List<PickExecutionIssue> {
        return pickExecutionIssueRepository.findByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
    }

    open fun getBadItemList(status: PickExecutionIssueEnum? = null): List<PickExecutionIssue>{
        if(status == null){
            return pickExecutionIssueRepository.getBadItemList();
        }else{
            val statuses = listOf(status);
            return pickExecutionIssueRepository.getBadItemList_statusIn(statuses);
        }
    }

    open fun getAllPickExecutionIssues(type: String? = null): List<PickExecutionIssue> {
        println("=== getAllPickExecutionIssues Debug ===")
        println("Requested type: $type")
        
        val result = when (type?.lowercase()) {
            "jo" -> {
                // Case 1: 只返回 Job Order 的 issues
                val joIssues = pickExecutionIssueRepository.findJoIssues()
                println(" Finding JO issues: ${joIssues.size} records")
                joIssues.forEach { 
                    println("  - ID=${it.id}, joPickOrderId=${it.joPickOrderId}, doPickOrderId=${it.doPickOrderId}") 
                }
                joIssues
            }
            "do" -> {
                // Case 2: 只返回 Delivery Order 的 issues
                val doIssues = pickExecutionIssueRepository.findDoIssues()
                println(" Finding DO issues: ${doIssues.size} records")
                doIssues.forEach { 
                    println("  - ID=${it.id}, joPickOrderId=${it.joPickOrderId}, doPickOrderId=${it.doPickOrderId}") 
                }
                doIssues
            }
            "material" -> {
                // Case 3: 只返回 Material 的 issues (普通 pick order)
                val materialIssues = pickExecutionIssueRepository.findMaterialIssues()
                println(" Finding Material issues: ${materialIssues.size} records")
                materialIssues.forEach { 
                    println("  - ID=${it.id}, joPickOrderId=${it.joPickOrderId}, doPickOrderId=${it.doPickOrderId}") 
                }
                materialIssues
            }
            else -> {
                // Case 4: 返回所有 issues
                val allIssues = pickExecutionIssueRepository.findByDeletedFalse()
                println(" Finding ALL issues: ${allIssues.size} records")
                allIssues
            }
        }
        
        println("=== End Debug ===")
        return result
    }
    /**
 * 为 batch release 失败的 delivery order 创建 issue 记录
 */
@Transactional(rollbackFor = [Exception::class])
open fun createBatchReleaseIssue(
    deliveryOrderId: Long,
    errorMessage: String,
    issueCategory: IssueCategory = IssueCategory.match_issue
): Int {
    try {
        println("=== Creating batch release issue ===")
        println("Delivery Order ID: $deliveryOrderId")
        println("Error Message: $errorMessage")
        println("Issue Category: $issueCategory")
        
        // 查询 delivery order
        val deliveryOrderSql = """
            SELECT do.id, do.code, do.estimatedArrivalDate
            FROM fpsmsdb.delivery_order do
            WHERE do.id = :deliveryOrderId
            AND do.deleted = 0
        """.trimIndent()
        
        val deliveryOrder = jdbcDao.queryForMap(
            deliveryOrderSql,
            mapOf("deliveryOrderId" to deliveryOrderId)
        ).orElse(null)
        
        if (deliveryOrder == null) {
            println("❌ Delivery Order not found: $deliveryOrderId")
            return 0
        }
        
        // 查询 delivery order lines
        val linesSql = """
            SELECT dol.id, dol.itemId, i.code as itemCode, i.name as itemName, dol.qty
            FROM fpsmsdb.delivery_order_line dol
            INNER JOIN fpsmsdb.items i ON i.id = dol.itemId
            WHERE dol.deliveryOrderId = :deliveryOrderId
            AND dol.deleted = 0
        """.trimIndent()
        
        val lines = jdbcDao.queryForList(linesSql, mapOf("deliveryOrderId" to deliveryOrderId))
        
        if (lines.isEmpty()) {
            println("⚠️ No delivery order lines found for DO: $deliveryOrderId")
            return 0
        }
        
        // 生成 issue number
        val issueNo = generateIssueNo()
        var createdCount = 0
        
        // 为每个 line 创建 issue 记录
        lines.forEach { line ->
            val itemId = (line["itemId"] as? Number)?.toLong() ?: 0L
            val itemCode = line["itemCode"] as? String
            val itemName = line["itemName"] as? String
            val qty = (line["qty"] as? Number)?.let { BigDecimal(it.toString()) } ?: BigDecimal.ZERO
            
            val issue = PickExecutionIssue(
                id = null,
                pickOrderId = null,  // batch release 失败时可能还没有 pick order
                pickOrderCode = deliveryOrder["code"] as? String ?: "",
                pickOrderCreateDate = LocalDate.now(),
                pickExecutionDate = LocalDate.now(),
                pickOrderLineId = null,  // batch release 失败时可能还没有 pick order line
                issueNo = issueNo,
                joPickOrderId = null,
                doPickOrderId = null,
                issueCategory = issueCategory,
                itemId = itemId,
                itemCode = itemCode,
                itemDescription = itemName,
                lotId = null,
                lotNo = null,
                storeLocation = null,
                requiredQty = qty,
                actualPickQty = BigDecimal.ZERO,
                missQty = qty,
                badItemQty = BigDecimal.ZERO,
                issueRemark = "Batch release failed: $errorMessage",
                pickerName = null,
                handleStatus = HandleStatus.pending,
                handleDate = null,
                handledBy = null,
                created = LocalDateTime.now(),
                createdBy = "SYSTEM",
                version = 0,
                modified = LocalDateTime.now(),
                modifiedBy = "SYSTEM",
                deleted = false
            )
            
            pickExecutionIssueRepository.save(issue)
            createdCount++
        }
        
        println(" Created $createdCount ${issueCategory.name} issues for DO $deliveryOrderId")
        return createdCount
        
    } catch (e: Exception) {
        println("❌ Failed to create batch release issue: ${e.message}")
        e.printStackTrace()
        return 0
    }
}

open fun getMissItemList(issueCategory: String = "lot_issue"): List<StockIssueResponse> {
    val category = try {
        IssueCategory.valueOf(issueCategory)
    } catch (e: Exception) {
        IssueCategory.lot_issue
    }
    
    // Get all miss item issues
    val issues = pickExecutionIssueRepository.findMissItemList(category)
        .filter { it.handleStatus != HandleStatus.completed }
    
    // Group by lotId and itemId (same lot can have different items)
    val groupedByLot = issues
        .filter { it.lotId != null } // Only include issues with lotId
        .groupBy { Pair(it.lotId, it.itemId) }
    
    return groupedByLot.map { (lotItemPair, issueList) ->
        val lotId = lotItemPair.first
        val itemId = lotItemPair.second
        
        // Get the first issue for non-aggregated fields
        val firstIssue = issueList.first()
        val (_, uomDesc) = resolveLotUomInfo(lotId)
        // Sum missQty for this lot (as requested: sum of missQty, not issueQty)
        val totalMissQty = issueList.sumOf { it.missQty ?: BigDecimal.ZERO }
        
        StockIssueResponse(
            id = firstIssue.id ?: 0L, // Use first issue's id
            itemId = itemId,
            itemCode = firstIssue.itemCode,
            itemDescription = firstIssue.itemDescription,
            lotId = lotId,
            lotNo = firstIssue.lotNo,
            storeLocation = firstIssue.storeLocation,
            requiredQty = issueList.sumOf { it.requiredQty ?: BigDecimal.ZERO },
            actualPickQty = issueList.sumOf { it.actualPickQty ?: BigDecimal.ZERO },
            missQty = totalMissQty, // Sum of missQty
            badItemQty = issueList.sumOf { it.badItemQty ?: BigDecimal.ZERO },
            bookQty = firstIssue.bookQty ?: BigDecimal.ZERO,
            issueQty = totalMissQty, // For miss items, issueQty = sum of missQty
            issueRemark = issueList.mapNotNull { it.issueRemark }.distinct().joinToString("; "),
            pickerName = firstIssue.pickerName,
            handleStatus = firstIssue.handleStatus.name,
            handleDate = firstIssue.handleDate,
            handledBy = firstIssue.handledBy,
            uomDesc = uomDesc,

        )
    }
}

open fun getBadItemList(issueCategory: String = "lot_issue"): List<StockIssueResponse> {
    val category = try {
        IssueCategory.valueOf(issueCategory)
    } catch (e: Exception) {
        IssueCategory.lot_issue
    }
    
    // Get all bad item issues
    val issues = pickExecutionIssueRepository.findBadItemListByCategory(category)
        .filter { it.handleStatus != HandleStatus.completed }
    
    // Group by lotId and itemId (same lot can have different items)
    val groupedByLot = issues
        .filter { it.lotId != null } // Only include issues with lotId
        .groupBy { Pair(it.lotId, it.itemId) }
    
    return groupedByLot.map { (lotItemPair, issueList) ->
        val lotId = lotItemPair.first
        val itemId = lotItemPair.second
        
        // Get the first issue for non-aggregated fields
        val firstIssue = issueList.first()
        val (_, uomDesc) = resolveLotUomInfo(lotId)
        // Sum issueQty for this lot (as requested: sum of issueQty)
        val totalIssueQty = issueList.sumOf { it.issueQty ?: BigDecimal.ZERO }
        
        StockIssueResponse(
            id = firstIssue.id ?: 0L, // Use first issue's id
            itemId = itemId,
            itemCode = firstIssue.itemCode,
            itemDescription = firstIssue.itemDescription,
            lotId = lotId,
            lotNo = firstIssue.lotNo,
            storeLocation = firstIssue.storeLocation,
            requiredQty = issueList.sumOf { it.requiredQty ?: BigDecimal.ZERO },
            actualPickQty = issueList.sumOf { it.actualPickQty ?: BigDecimal.ZERO },
            missQty = issueList.sumOf { it.missQty ?: BigDecimal.ZERO },
            badItemQty = issueList.sumOf { it.badItemQty ?: BigDecimal.ZERO },
            bookQty = firstIssue.bookQty ?: BigDecimal.ZERO,
            issueQty = totalIssueQty, // For bad items, issueQty = sum of issueQty
            issueRemark = issueList.mapNotNull { it.issueRemark }.distinct().joinToString("; "),
            pickerName = firstIssue.pickerName,
            handleStatus = firstIssue.handleStatus.name,
            handleDate = firstIssue.handleDate,
            handledBy = firstIssue.handledBy,
            uomDesc = uomDesc,
        )
    }
}

open fun getBadItemOnlyList(): List<PickExecutionIssue> {
    return pickExecutionIssueRepository.findBadItemOnlyList(IssueCategory.lot_issue)
}

open fun getExpiryItemList(
    expiryDate: LocalDate? = null,
    itemCode: String? = null,
    itemName: String? = null,
): List<ExpiryItemResponse> {
    val today = LocalDate.now()
    val normalizedItemCode = itemCode?.trim()?.takeIf { it.isNotEmpty() }
    val normalizedItemName = itemName?.trim()?.takeIf { it.isNotEmpty() }
    val lotLines = inventoryLotLineRepository.findExpiredItems(
        today = today,
        expiryDate = expiryDate,
        itemCode = normalizedItemCode,
        itemName = normalizedItemName,
    )
    
    return lotLines.map { lotLine ->
        val lot = lotLine.inventoryLot
        val item = lot?.item // Get item from inventoryLot
        ExpiryItemResponse(
            id = lotLine.id ?: 0L,
            itemId = item?.id ?: 0L,
            itemCode = item?.code ?: "",
            itemDescription = item?.name,
            lotId = lot?.id ?: 0L,
            lotNo = lot?.lotNo,
            storeLocation = lotLine.warehouse?.code, // Construct from warehouse
            expiryDate = lot?.expiryDate,
            remainingQty = (lotLine.inQty ?: BigDecimal.ZERO).subtract(lotLine.outQty ?: BigDecimal.ZERO)
        )
    }
}

@Transactional(rollbackFor = [Exception::class])
open fun submitMissItem(request: SubmitIssueRequest): MessageResponse {
    try {
        val issue = pickExecutionIssueRepository.findById(request.issueId).orElse(null)
            ?: return MessageResponse(
                id = null,
                name = "Error",
                code = "NOT_FOUND",
                type = "stock_issue",
                message = "Issue not found",
                errorPosition = null
            )
        
        // 修改：使用 issueQty 而不是 missQty
        val issueQty = issue.issueQty ?: BigDecimal.ZERO
        if (issueQty <= BigDecimal.ZERO || issue.deleted) {
            return MessageResponse(
                id = null,
                name = "Error",
                code = "INVALID",
                type = "stock_issue",
                message = "Invalid issue or no issue quantity",
                errorPosition = null
            )
        }
        
        val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L
        
        val item = itemsRepository.findById(issue.itemId).orElse(null)
            ?: return MessageResponse(
                id = null,
                name = "Error",
                code = "ITEM_NOT_FOUND",
                type = "stock_issue",
                message = "Item not found",
                errorPosition = null
            )
        
        val lotLineId = issue.lotId
            ?: throw IllegalArgumentException("Issue ${issue.id} has no lot line id")
        val savedStockOutLine = stockOutLineService.createStockOut(
            StockOutRequest(
                inventoryLotLineId = lotLineId,
                qty = issueQty.toDouble(),
                type = "Miss"
            )
        )
        markIssueHandled(issue, handler)
        return MessageResponse(
            id = savedStockOutLine.stockOut?.id,
            name = "Success",
            code = "SUCCESS",
            type = "stock_issue",
            message = "Successfully submitted miss item",
            errorPosition = null
        )
    } catch (e: Exception) {
        return MessageResponse(
            id = null,
            name = "Error",
            code = "ERROR",
            type = "stock_issue",
            message = "Failed to submit miss item: ${e.message}",
            errorPosition = null
        )
    }
}
@Transactional(rollbackFor = [Exception::class])
open fun submitBadItem(request: SubmitIssueRequest): MessageResponse {
    try {
        val issue = pickExecutionIssueRepository.findById(request.issueId).orElse(null)
            ?: return MessageResponse(
                id = null,
                name = "Error",
                code = "NOT_FOUND",
                type = "stock_issue",
                message = "Issue not found",
                errorPosition = null
            )
        
        // 修改：使用 issueQty 而不是 badItemQty
        val issueQty = issue.issueQty ?: BigDecimal.ZERO
        if (issueQty <= BigDecimal.ZERO || issue.deleted) {
            return MessageResponse(
                id = null,
                name = "Error",
                code = "INVALID",
                type = "stock_issue",
                message = "Invalid issue or no issue quantity",
                errorPosition = null
            )
        }
        
        val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L
        
        val item = itemsRepository.findById(issue.itemId).orElse(null)
            ?: return MessageResponse(
                id = null,
                name = "Error",
                code = "ITEM_NOT_FOUND",
                type = "stock_issue",
                message = "Item not found",
                errorPosition = null
            )
        
        val lotLineId = issue.lotId
            ?: throw IllegalArgumentException("Issue ${issue.id} has no lot line id")
        val savedStockOutLine = stockOutLineService.createStockOut(
            StockOutRequest(
                inventoryLotLineId = lotLineId,
                qty = issueQty.toDouble(),
                type = "Bad"
            )
        )
        
        // ✅ 修复：Bad item 的 issueQty 应该重置为 0，而不是累加/减去
        // 先重置 issueQty 为 0
        if (issue.lotId != null) {
            val lotLineForReset = inventoryLotLineRepository.findById(issue.lotId).orElse(null)
            if (lotLineForReset != null) {
                lotLineForReset.issueQty = BigDecimal.ZERO
                inventoryLotLineRepository.saveAndFlush(lotLineForReset)
                println("✅ Reset issueQty to 0 for lot ${issue.lotId} before bad item submission")
            }
        }
        
        markIssueHandled(issue, handler)
        return MessageResponse(
            id = savedStockOutLine.stockOut?.id,
            name = "Success",
            code = "SUCCESS",
            type = "stock_issue",
            message = "Successfully submitted bad item",
            errorPosition = null
        )
    } catch (e: Exception) {
        return MessageResponse(
            id = null,
            name = "Error",
            code = "ERROR",
            type = "stock_issue",
            message = "Failed to submit bad item: ${e.message}",
            errorPosition = null
        )
    }
}

@Transactional(rollbackFor = [Exception::class])
open fun submitExpiryItem(request: SubmitExpiryRequest): MessageResponse {
    try {
        println("=== submitExpiryItem START ===")
        println("Request lotLineId=${request.lotLineId}, handler=${request.handler}")
        val lotLine = inventoryLotLineRepository.findById(request.lotLineId).orElse(null)
            ?: return MessageResponse(
                id = null,
                name = "Error",
                code = "NOT_FOUND",
                type = "stock_issue",
                message = "Lot line not found",
                errorPosition = null
            )
        
        val lot = lotLine.inventoryLot
        val today = LocalDate.now()
        
        if (lot?.expiryDate == null || !lot.expiryDate!!.isBefore(today)) {
            return MessageResponse(
                id = null,
                name = "Error",
                code = "NOT_EXPIRED",
                type = "stock_issue",
                message = "Item is not expired",
                errorPosition = null
            )
        }
        
        val inQty = lotLine.inQty ?: BigDecimal.ZERO
        val outQty = lotLine.outQty ?: BigDecimal.ZERO
        val remainingQty = inQty.subtract(outQty)
        println("LotLine=${lotLine.id}, itemId=${lot?.item?.id}, inQty=$inQty, outQty=$outQty, remainingQty=$remainingQty")
        
        if (remainingQty <= BigDecimal.ZERO) {
            return MessageResponse(
                id = null,
                name = "Error",
                code = "NO_REMAINING",
                type = "stock_issue",
                message = "No remaining quantity",
                errorPosition = null
            )
        }
        
        val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L
        
        val item = lot.item
            ?: return MessageResponse(
                id = null,
                name = "Error",
                code = "ITEM_NOT_FOUND",
                type = "stock_issue",
                message = "Item not found",
                errorPosition = null
            )
        
        val savedStockOutLine = stockOutLineService.createStockOut(
            StockOutRequest(
                inventoryLotLineId = lotLine.id!!,
                qty = remainingQty.toDouble(),
                type = "Expiry"
            )
        )
        println("submitExpiryItem createStockOut SUCCESS: stockOutLineId=${savedStockOutLine.id}, qty=$remainingQty")
        return MessageResponse(
            id = savedStockOutLine.stockOut?.id,
            name = "Success",
            code = "SUCCESS",
            type = "stock_issue",
            message = "Successfully submitted expiry item",
            errorPosition = null
        )
    } catch (e: Exception) {
        println("submitExpiryItem ERROR: ${e.message}")
        e.printStackTrace()
        return MessageResponse(
            id = null,
            name = "Error",
            code = "ERROR",
            type = "stock_issue",
            message = "Failed to submit expiry item: ${e.message}",
            errorPosition = null
        )
    }
}

@Transactional(rollbackFor = [Exception::class])
open fun batchSubmitMissItem(request: BatchSubmitIssueRequest): MessageResponse {
    try {
        // 修改：检查 issueQty 而不是 missQty
        val issues = pickExecutionIssueRepository.findAllById(request.issueIds)
            .filter { (it.issueQty ?: BigDecimal.ZERO) > BigDecimal.ZERO && !it.deleted }
        
        if (issues.isEmpty()) {
            return MessageResponse(
                id = null,
                name = "Error",
                code = "EMPTY",
                type = "stock_issue",
                message = "No valid issues to submit",
                errorPosition = null
            )
        }
        
        val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L
        
        issues.forEach { issue ->
            val issueQty = issue.issueQty ?: BigDecimal.ZERO
            
            val lotLineId = issue.lotId
                ?: throw IllegalArgumentException("Issue ${issue.id} has no lot line id")
            stockOutLineService.createStockOut(
                StockOutRequest(
                    inventoryLotLineId = lotLineId,
                    qty = issueQty.toDouble(),
                    type = "Miss"
                )
            )
            markIssueHandled(issue, handler)
        }
        
        return MessageResponse(
            id = null,
            name = "Success",
            code = "SUCCESS",
            type = "stock_issue",
            message = "Successfully submitted ${issues.size} miss item(s)",
            errorPosition = null
        )
    } catch (e: Exception) {
        return MessageResponse(
            id = null,
            name = "Error",
            code = "ERROR",
            type = "stock_issue",
            message = "Failed to submit miss items: ${e.message}",
            errorPosition = null
        )
    }
}

@Transactional(rollbackFor = [Exception::class])
open fun batchSubmitBadItem(request: BatchSubmitIssueRequest): MessageResponse {
    try {
        // 修改：检查 issueQty 而不是 badItemQty
        val issues = pickExecutionIssueRepository.findAllById(request.issueIds)
            .filter { (it.issueQty ?: BigDecimal.ZERO) > BigDecimal.ZERO && !it.deleted }
        
        if (issues.isEmpty()) {
            return MessageResponse(
                id = null,
                name = "Error",
                code = "EMPTY",
                type = "stock_issue",
                message = "No valid issues to submit",
                errorPosition = null
            )
        }
        
        val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L
        
        issues.forEach { issue ->
            val issueQty = issue.issueQty ?: BigDecimal.ZERO
            
            val lotLineId = issue.lotId
                ?: throw IllegalArgumentException("Issue ${issue.id} has no lot line id")
            stockOutLineService.createStockOut(
                StockOutRequest(
                    inventoryLotLineId = lotLineId,
                    qty = issueQty.toDouble(),
                    type = "Bad"
                )
            )
            // ✅ 修复：Bad item 的 issueQty 应该重置为 0，而不是累加/减去
            // 先重置 issueQty 为 0
            if (issue.lotId != null) {
                val lotLineForReset = inventoryLotLineRepository.findById(issue.lotId).orElse(null)
                if (lotLineForReset != null) {
                    lotLineForReset.issueQty = BigDecimal.ZERO
                    inventoryLotLineRepository.saveAndFlush(lotLineForReset)
                    println("✅ Reset issueQty to 0 for lot ${issue.lotId} before bad item batch submission")
                }
            }
            
            markIssueHandled(issue, handler)
        }
        
        return MessageResponse(
            id = null,
            name = "Success",
            code = "SUCCESS",
            type = "stock_issue",
            message = "Successfully submitted ${issues.size} bad item(s)",
            errorPosition = null
        )
    } catch (e: Exception) {
        return MessageResponse(
            id = null,
            name = "Error",
            code = "ERROR",
            type = "stock_issue",
            message = "Failed to submit bad items: ${e.message}",
            errorPosition = null
        )
    }
}
// Fix batchSubmitExpiryItem method (around line 945):
@Transactional(rollbackFor = [Exception::class])
open fun batchSubmitExpiryItem(request: BatchSubmitExpiryRequest): MessageResponse {
    try {
        val lotLines = inventoryLotLineRepository.findAllById(request.lotLineIds)
            .filter {
                val lot = it.inventoryLot
                val today = LocalDate.now()
                lot?.expiryDate != null &&
                lot.expiryDate!!.isBefore(today) &&
                (it.inQty ?: BigDecimal.ZERO) != (it.outQty ?: BigDecimal.ZERO)
            }

        if (lotLines.isEmpty()) {
            return MessageResponse(
                id = null, name = "Error", code = "EMPTY", type = "stock_issue",
                message = "No valid expiry items to submit", errorPosition = null
            )
        }

        val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L
        val batchReq = BatchStockOutRequest(
            type = "Expiry",
            handler = handler,
            lines = lotLines.map { ll ->
                val remaining = (ll.inQty ?: BigDecimal.ZERO).subtract(ll.outQty ?: BigDecimal.ZERO)
                BatchStockOutLineRequest(
                    inventoryLotLineId = ll.id!!,
                    qty = remaining.toDouble()
                )
            }
        )

        val result = stockOutLineService.createStockOutBatch(batchReq)

        return MessageResponse(
            id = result.stockOutId,
            name = "Success",
            code = "SUCCESS",
            type = "stock_issue",
            message = "Successfully submitted ${result.processedCount} expiry item(s), skipped ${result.skippedCount}",
            errorPosition = null
        )
    } catch (e: Exception) {
        return MessageResponse(
            id = null, name = "Error", code = "ERROR", type = "stock_issue",
            message = "Failed to submit expiry items: ${e.message}", errorPosition = null
        )
    }
}


private fun markIssueHandled(issue: PickExecutionIssue, handler: Long?) {
    pickExecutionIssueRepository.updateHandleStatus(
        issueId = issue.id ?: 0L,
        handleStatus = HandleStatus.completed,
        handleDate = LocalDate.now(),
        handledBy = handler
    )
}

private fun createIssueStockOutHeader(type: String, remarks: String?, handler: Long?): StockOut {
    val prefix = "ISSUE"
    val midfix = CodeGenerator.DEFAULT_MIDFIX
    val latestCode = stockOutRepository.findAll().mapNotNull { it.consoPickOrderCode }
        .filter { it.startsWith("$prefix-$midfix") }
        .maxOrNull()
    val consoCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode)
    
    val stockOut = StockOut().apply {
        this.type = type
        this.consoPickOrderCode = consoCode
        this.status = StockOutStatus.PENDING.status
        this.handler = handler
        this.remarks = remarks
    }
    return stockOutRepository.save(stockOut)
}

private fun updateLotLineAfterIssue(lotLineId: Long, qty: BigDecimal, isMissItem: Boolean = false) {
    val lotLine = inventoryLotLineRepository.findById(lotLineId).orElse(null)
    if (lotLine != null) {
        val currentOutQty = lotLine.outQty ?: BigDecimal.ZERO
        val newOutQty = currentOutQty.add(qty)
        lotLine.outQty = newOutQty
        
        // ✅ 修复：Bad item 的 issueQty 应该重置为 0，而不是减去
        // 注意：issueQty 应该在 submitBadItem 中重置，这里不再处理
        // if (!isMissItem) {
        //     val currentIssueQty = lotLine.issueQty ?: BigDecimal.ZERO
        //     lotLine.issueQty = currentIssueQty.subtract(qty)
        // }
        
        // If outQty != inQty, set status to AVAILABLE
        val inQty = lotLine.inQty ?: BigDecimal.ZERO
        if (newOutQty != inQty) {
            lotLine.status = InventoryLotLineStatus.AVAILABLE
        }
        
        lotLine.modified = LocalDateTime.now()
        lotLine.modifiedBy = "system"
        // 修复：使用 saveAndFlush 确保立即提交到数据库，触发触发器
        inventoryLotLineRepository.saveAndFlush(lotLine)
        updateInventoryAfterLotLineChange(lotLine)
    }
}
private fun updateInventoryAfterLotLineChange(lotLine: InventoryLotLine) {
    try {
        val item = lotLine.inventoryLot?.item ?: return
        val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return
        
        // 使用 SQL 查询计算所有相关 lot lines 的总和
        val sql = """
            SELECT 
                SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0)) as totalOnHandQty,
                SUM(COALESCE(ill.holdQty, 0)) as totalOnHoldQty,
                SUM(CASE 
                    WHEN ill.status = 'unavailable' 
                    THEN COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)
                    ELSE 0 
                END) as totalUnavailableQty
            FROM inventory_lot_line ill
            INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId
            WHERE il.itemId = :itemId
            AND ill.deleted = 0
        """.trimIndent()
        
        val result = jdbcDao.queryForMap(sql, mapOf("itemId" to item.id!!)).orElse(null)
        
        if (result != null) {
            val totalOnHandQty = (result["totalOnHandQty"] as? Number)?.let { 
                BigDecimal(it.toString()) 
            } ?: BigDecimal.ZERO
            
            val totalOnHoldQty = (result["totalOnHoldQty"] as? Number)?.let { 
                BigDecimal(it.toString()) 
            } ?: BigDecimal.ZERO
            
            val totalUnavailableQty = (result["totalUnavailableQty"] as? Number)?.let { 
                BigDecimal(it.toString()) 
            } ?: BigDecimal.ZERO
            
            // 更新 inventory
            inventory.onHandQty = totalOnHandQty
            inventory.onHoldQty = totalOnHoldQty
            inventory.unavailableQty = totalUnavailableQty
            inventory.status = if (totalOnHandQty.subtract(totalOnHoldQty).subtract(totalUnavailableQty) > BigDecimal.ZERO) {
                "available"
            } else {
                "unavailable"
            }
            inventory.modified = LocalDateTime.now()
            inventory.modifiedBy = "system"
            
            inventoryRepository.saveAndFlush(inventory)
            
            println("=== MANUALLY UPDATED INVENTORY ===")
            println("Item ID: ${item.id}")
            println("Total OnHandQty: ${totalOnHandQty}")
            println("Total OnHoldQty: ${totalOnHoldQty}")
            println("Total UnavailableQty: ${totalUnavailableQty}")
            println("Status: ${inventory.status}")
            println("==================================")
        }
        
    } catch (e: Exception) {
        println("Error updating inventory manually: ${e.message}")
        e.printStackTrace()
    }
}
private fun createStockLedgerForStockOut(
    stockOutLine: StockOutLine, 
    ledgerType: String? = null,
    balance: Double? = null  // 新增：可选的 balance 参数
) {
    val item = stockOutLine.item ?: return
    
    val outQty = stockOutLine.qty?.toDouble() ?: 0.0
    
    // ✅ 修复：如果传入了 balance，直接使用；否则重新查询 inventory
    val finalBalance = if (balance != null) {
        if (balance < 0) 0.0 else balance
    } else {
        // 重新查询 inventory 以获取触发器更新后的最新 onHandQty
        val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return
        val currentOnHandQty = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
        val newBalance = currentOnHandQty - outQty
        if (newBalance < 0) 0.0 else newBalance
    }
    
    // 使用传入的 ledgerType，如果没有则使用 stockOutLine.type
    val ledgerTypeToUse = ledgerType ?: stockOutLine.type ?: "Nor"
    
    val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return
    
    val stockLedger = StockLedger().apply {
        this.stockOutLine = stockOutLine
        this.inventory = inventory
        this.inQty = null
        this.outQty = outQty
        this.balance = finalBalance
        this.type = ledgerTypeToUse
        this.itemId = item.id
        this.itemCode = item.code
        this.uomId = inventory.uom?.id
            ?: itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(item.id!!)?.uom?.id
        this.date = LocalDate.now()
    }
    
    stockLedgerRepository.saveAndFlush(stockLedger)
    
    println("=== CREATED STOCK LEDGER ===")
    println("Type: ${ledgerTypeToUse}")
    println("OutQty: ${outQty}")
    println("Balance: ${finalBalance}")
    println("===========================")
}



@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleBadItemPackageProblem(request: PickExecutionIssueRequest, badItemQty: BigDecimal) {
    println("=== HANDLING BAD ITEM PACKAGE PROBLEM (DON'T REJECT LOT) ===")
    println("Bad Item Qty: ${badItemQty}")
    
    val lotId = request.lotId ?: return
    val itemId = request.itemId ?: return
    val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
    
    if (inventoryLotLine != null) {
        // Package problem: Don't mark as unavailable, just record the issue
        // Update outQty for actual picked items if any
        if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
            val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
            val newOutQty = currentOutQty.add(request.actualPickQty)
            inventoryLotLine.outQty = newOutQty
            println("Package problem: Updated outQty: ${currentOutQty} -> ${newOutQty}")
        }
        
        // IssueQty is already updated in recordPickExecutionIssue
        inventoryLotLine.modified = LocalDateTime.now()
        inventoryLotLine.modifiedBy = "system"
        inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
        
        println("Package problem: Updated lot ${lotId}, but did NOT mark as unavailable")
    }
    
    // Update inventory unavailableQty (still need to track unavailable quantity)
    updateInventoryUnavailableQty(itemId, badItemQty)
    
    // DON'T reject stock_out_line for package problem
    val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
        request.pickOrderLineId,
        request.lotId ?: 0L
    )
    
    stockOutLines.forEach { stockOutLine ->
        if (request.actualPickQty != null) {
            stockOutLine.qty = request.actualPickQty.toDouble()
        }
        
        // Determine status based on actual pick qty vs required qty
        val requiredQty = request.requiredQty?.toDouble() ?: 0.0
        val actualPickQtyDouble = request.actualPickQty?.toDouble() ?: 0.0
        val newStatus = if (actualPickQtyDouble >= requiredQty) {
            "completed"
        } else {
            "partially_completed"
        }
        stockOutLine.status = newStatus
        
        stockOutLine.modified = LocalDateTime.now()
        stockOutLine.modifiedBy = "system"
        val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine)
        
        // Create stock ledger for actual picked items
        if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
            createStockLedgerForStockOut(savedStockOutLine, "Nor")
        }
        
        println("Package problem: Updated stock out line ${stockOutLine.id} status to: ${newStatus} (NOT rejected)")
    }
    try {
        // 确保所有更改都已刷新到数据库
        stockOutLineRepository.flush()
        
        // 直接查询并检查该 pick order line 下的所有 stock out lines
        val allStockOutLines = stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(request.pickOrderLineId)
        val unfinishedLines = allStockOutLines.filter {
            it.status != StockOutLineStatus.COMPLETE.status
                    && it.status != StockOutLineStatus.REJECTED.status
        }
        
        println("Package problem: Checking pick order line ${request.pickOrderLineId}")
        println("  Total stock out lines: ${allStockOutLines.size}")
        println("  Unfinished lines: ${unfinishedLines.size}")
        unfinishedLines.forEach { line ->
            println("    - Line ${line.id}: status=${line.status}")
        }
        
        if (unfinishedLines.isEmpty() && allStockOutLines.isNotEmpty()) {
            val pickOrderLine = pickOrderLineRepository.findById(request.pickOrderLineId).orElse(null)
            if (pickOrderLine != null) {
                pickOrderLine.status = PickOrderLineStatus.COMPLETED
                pickOrderLineRepository.saveAndFlush(pickOrderLine)
                println("✅ Package problem: Updated pick order line ${request.pickOrderLineId} status to COMPLETED")
            } else {
                println("⚠️ Package problem: Pick order line ${request.pickOrderLineId} not found")
            }
        } else {
            println("⚠️ Package problem: Pick order line ${request.pickOrderLineId} still has ${unfinishedLines.size} unfinished lines")
        }
    } catch (e: Exception) {
        println("⚠️ Error checking pick order line completion: ${e.message}")
        e.printStackTrace()
    }
    // Don't resuggest for package problem (lot is still available)
    println("Package problem: Did NOT resuggest pick order (lot remains available)")
}

// NEW: Handle both miss and bad item with package problem
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleBothMissAndBadItemPackageProblem(request: PickExecutionIssueRequest, missQty: BigDecimal, badItemQty: BigDecimal) {
    println("=== HANDLING BOTH MISS AND BAD ITEM PACKAGE PROBLEM ===")
    println("Miss Qty: ${missQty}, Bad Item Qty: ${badItemQty}")
    
    val lotId = request.lotId ?: return
    val itemId = request.itemId ?: return
    val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
    
    if (inventoryLotLine != null) {
        // Update outQty for actual picked items
        if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
            val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
            val newOutQty = currentOutQty.add(request.actualPickQty)
            inventoryLotLine.outQty = newOutQty
            println("Both miss and bad (package): Updated outQty: ${currentOutQty} -> ${newOutQty}")
        }
        
        // Miss item: mark as unavailable
        if (missQty > BigDecimal.ZERO) {
            inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE
            println("Both miss and bad (package): Set status to UNAVAILABLE due to missQty: ${missQty}")
        }
        
        // IssueQty is already updated in recordPickExecutionIssue
        inventoryLotLine.modified = LocalDateTime.now()
        inventoryLotLine.modifiedBy = "system"
        inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
    }
    
    // Update inventory unavailableQty (miss qty + bad qty)
    val totalUnavailableQty = missQty.add(badItemQty)
    updateInventoryUnavailableQty(itemId, totalUnavailableQty)
    
    // Update stock_out_line: reject only if missQty > 0, otherwise partial/completed
    val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
        request.pickOrderLineId,
        request.lotId ?: 0L
    )
    
    stockOutLines.forEach { stockOutLine ->
        if (request.actualPickQty != null) {
            stockOutLine.qty = request.actualPickQty.toDouble()
        }
        
        val requiredQty = request.requiredQty?.toDouble() ?: 0.0
        val actualPickQtyDouble = request.actualPickQty?.toDouble() ?: 0.0
        
        // If missQty > 0, reject; otherwise check completion status
        val newStatus = if (missQty > BigDecimal.ZERO) {
            "rejected"
        } else if (actualPickQtyDouble >= requiredQty) {
            "completed"
        } else {
            "partially_completed"
        }
        
        stockOutLine.status = newStatus
        stockOutLine.modified = LocalDateTime.now()
        stockOutLine.modifiedBy = "system"
        val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine)
        
        if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
            createStockLedgerForStockOut(savedStockOutLine, "Nor")
        }
        
        println("Both miss and bad (package): Updated stock out line ${stockOutLine.id} status to: ${newStatus}")
    }
    
    // Resuggest only for miss qty
    if (missQty > BigDecimal.ZERO) {
        try {
            resuggestPickOrder(request.pickOrderId)
            println("Resuggested pick order for miss qty: ${missQty}")
        } catch (e: Exception) {
            println("Error during resuggest: ${e.message}")
        }
    } else {
        println("Both miss and bad (package): Did NOT resuggest (only bad item with package problem)")
    }
}
open fun getLotIssueDetails(lotId: Long, itemId: Long, issueType: String): LotIssueDetailResponse {
    val issues = if (issueType == "miss") {
        pickExecutionIssueRepository.findMissItemList(IssueCategory.lot_issue)
            .filter { 
                it.lotId == lotId && 
                it.itemId == itemId && 
                it.handleStatus != HandleStatus.completed 
            }
    } else {
        pickExecutionIssueRepository.findBadItemListByCategory(IssueCategory.lot_issue)
            .filter { 
                it.lotId == lotId && 
                it.itemId == itemId && 
                it.handleStatus != HandleStatus.completed 
            }
    }
    
    if (issues.isEmpty()) {
        throw IllegalArgumentException("No issues found for lot $lotId and item $itemId")
    }
    
    val firstIssue = issues.first()
    val lotLine = inventoryLotLineRepository.findById(firstIssue.lotId!!).orElse(null)
    val inQty = lotLine?.inQty ?: BigDecimal.ZERO
    val outQty = lotLine?.outQty ?: BigDecimal.ZERO
    val bookQty = inQty.subtract(outQty)

    val uom = lotLine?.stockUom?.uom
    val uomCode = uom?.code
    val uomDesc = uom?.udfudesc
    // Get DO/JO order codes
    val issueDetails = issues.map { issue ->
        var doOrderCode: String? = null
        var joOrderCode: String? = null
        
        // Get DO order code
        if (issue.doPickOrderId != null) {
            val doPickOrder = doPickOrderRepository.findById(issue.doPickOrderId).orElse(null)
            doOrderCode = doPickOrder?.deliveryOrderCode
        }
        
        // Get JO order code
        if (issue.joPickOrderId != null) {
            val joPickOrder = joPickOrderRepository.findById(issue.joPickOrderId).orElse(null)
            joOrderCode = joPickOrder?.jobOrderCode  // Use jobOrderCode directly instead of jobOrder?.code
        }
        
        IssueDetailItem(
            issueId = issue.id ?: 0L,
            pickerName = issue.pickerName,
            missQty = issue.missQty,
            issueQty = issue.issueQty,
            pickOrderCode = issue.pickOrderCode,
            doOrderCode = doOrderCode,
            joOrderCode = joOrderCode,
            issueRemark = issue.issueRemark
        )
    }
    
    return LotIssueDetailResponse(
        lotId = firstIssue.lotId,
        lotNo = firstIssue.lotNo,
        itemId = firstIssue.itemId,
        itemCode = firstIssue.itemCode,
        itemDescription = firstIssue.itemDescription,
        storeLocation = firstIssue.storeLocation,
        issues = issueDetails,
        bookQty = bookQty,

        uomDesc = uomDesc,
    )
}

open fun submitIssueWithQty(request: SubmitIssueWithQtyRequest): MessageResponse {
    try {
        println("=== submitIssueWithQty: START ===")
        println("Request: lotId=${request.lotId}, itemId=${request.itemId}, issueType=${request.issueType}, submitQty=${request.submitQty}, handler=${request.handler}")
        
        // Find all issues for this lot and item
        val issues = if (request.issueType == "miss") {
            pickExecutionIssueRepository.findMissItemList(IssueCategory.lot_issue)
                .filter { 
                    it.lotId == request.lotId && 
                    it.itemId == request.itemId && 
                    it.handleStatus != HandleStatus.completed 
                }
        } else {
            pickExecutionIssueRepository.findBadItemListByCategory(IssueCategory.lot_issue)
                .filter { 
                    it.lotId == request.lotId && 
                    it.itemId == request.itemId && 
                    it.handleStatus != HandleStatus.completed 
                }
        }
        
        println("Found ${issues.size} issues to process")
        issues.forEachIndexed { index, issue ->
            println("  Issue[$index]: id=${issue.id}, issueQty=${issue.issueQty}, handleStatus=${issue.handleStatus}")
        }
        
        if (issues.isEmpty()) {
            println("❌ No issues found for this lot")
            return MessageResponse(
                id = null,
                name = "Error",
                code = "NOT_FOUND",
                type = "stock_issue",
                message = "No issues found for this lot",
                errorPosition = null
            )
        }
        val firstIssue = issues.first()
        val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L
        val isMissItem = request.issueType == "miss"
        
        // Use custom quantity instead of sum
        val submitQty = request.submitQty
        
        // ✅ 修改：允许提交0，0表示"标记为已处理但无需出库"
        if (submitQty < BigDecimal.ZERO) {
            println("❌ Submit quantity cannot be negative: $submitQty")
            return MessageResponse(
                id = null,
                name = "Error",
                code = "INVALID",
                type = "stock_issue",
                message = "Submit quantity cannot be negative",
                errorPosition = null
            )
        }
        
        // ✅ 新增：如果提交数量为0，只标记issue为已处理，不创建stock_out_line
        if (submitQty == BigDecimal.ZERO) {
            println("ℹ️ Submit quantity is 0 - marking issues as handled without creating stock out")
            val isMissItem = request.issueType == "miss"

            // Bad + 有 lotId：重置 lot（issueQty=0, status=AVAILABLE），并释放该 lot 下 rejected 的 stock out line
            if (!isMissItem && request.lotId != null) {
                val lotLine = inventoryLotLineRepository.findById(request.lotId).orElse(null)
                if (lotLine != null) {
                    lotLine.issueQty = BigDecimal.ZERO
                    lotLine.status = InventoryLotLineStatus.AVAILABLE
                    lotLine.modified = LocalDateTime.now()
                    lotLine.modifiedBy = "system"
                    inventoryLotLineRepository.saveAndFlush(lotLine)
                    updateInventoryAfterLotLineChange(lotLine)
                    println("✅ Reset lot ${request.lotId}: issueQty=0, status=AVAILABLE")
                }
                val rejectedLines = stockOutLineRepository
                    .findByInventoryLotLineIdAndStatusAndDeletedFalse(
                        request.lotId,
                        StockOutLineStatus.REJECTED.status
                    )
                if (rejectedLines.isNotEmpty()) {
                    rejectedLines.forEach { sol ->
                        sol.status = "pending"
                        sol.modified = LocalDateTime.now()
                        sol.modifiedBy = "system"
                    }
                    stockOutLineRepository.saveAllAndFlush(rejectedLines)
                    println("✅ Released ${rejectedLines.size} rejected stock out line(s) for lot ${request.lotId} to pending")
                }
            }

            issues.forEach { issue ->
                println("  Marking issue ${issue.id} as handled by handler $handler")
                markIssueHandled(issue, handler)
            }
            println("✅ All issues marked as handled (no stock out created)")
            return MessageResponse(
                id = null,
                name = "Success",
                code = "SUCCESS",
                type = "stock_issue",
                message = "Issues marked as handled (no stock out - quantity is 0)",
                errorPosition = null
            )
        }
        
        // ✅ 修复：在创建和保存 stockOutLine 之前获取 inventory 的当前 onHandQty
        val inventoryBeforeUpdate = inventoryRepository.findByItemId(request.itemId).orElse(null)
        val onHandQtyBeforeUpdate = (inventoryBeforeUpdate?.onHandQty ?: BigDecimal.ZERO).toDouble()
        
        println("=== submitIssueWithQty: Before update ===")
        println("Item ID: ${request.itemId}")
        println("Submit Qty: ${submitQty}")
        println("OnHandQty Before Update: ${onHandQtyBeforeUpdate}")
        println("==========================================")
        
        val lotLine = request.lotId.let {
            inventoryLotLineRepository.findById(it).orElse(null)
        }
        println("Lot line: id=${lotLine?.id}, lotNo=${lotLine?.inventoryLot?.lotNo}")
        
        val item = itemsRepository.findById(request.itemId).orElse(null)
            ?: return MessageResponse(
                id = null,
                name = "Error",
                code = "ITEM_NOT_FOUND",
                type = "stock_issue",
                message = "Item not found",
                errorPosition = null
            )
        println("Item: id=${item.id}, code=${item.code}")
        
        val savedStockOutLine = stockOutLineService.createStockOut(
            StockOutRequest(
                inventoryLotLineId = request.lotId,
                qty = submitQty.toDouble(),
                type = if (isMissItem) "Miss" else "Bad"
            )
        )
        println("Created stock out line via createStockOut: id=${savedStockOutLine.id}, qty=${savedStockOutLine.qty}, status=${savedStockOutLine.status}")
        
        if (!isMissItem && request.lotId != null) {
            val rejectedLines = stockOutLineRepository
                .findByInventoryLotLineIdAndStatusAndDeletedFalse(
                    request.lotId,
                    StockOutLineStatus.REJECTED.status
                )
            if (rejectedLines.isNotEmpty()) {
                rejectedLines.forEach { sol ->
                    sol.status = "pending"  // 或 StockOutLineStatus.PENDING.status（若有该枚举）
                    sol.modified = LocalDateTime.now()
                    sol.modifiedBy = "system"
                }
                stockOutLineRepository.saveAllAndFlush(rejectedLines)
                println("✅ Released ${rejectedLines.size} rejected stock out line(s) for lot ${request.lotId} to pending")
            }
        }
        
        // Mark all issues as handled
        issues.forEach { issue ->
            println("  Marking issue ${issue.id} as handled by handler $handler")
            markIssueHandled(issue, handler)
        }
        
        println("=== submitIssueWithQty: SUCCESS ===")
        return MessageResponse(
            id = savedStockOutLine.stockOut?.id,
            name = "Success",
            code = "SUCCESS",
            type = "stock_issue",
            message = "Successfully submitted ${request.issueType} item with quantity $submitQty",
            errorPosition = null
        )
    } catch (e: Exception) {
        println("=== submitIssueWithQty: ERROR ===")
        println("Error: ${e.message}")
        e.printStackTrace()
        return MessageResponse(
            id = null,
            name = "Error",
            code = "ERROR",
            type = "stock_issue",
            message = "Failed to submit: ${e.message}",
            errorPosition = null
        )
    }
}

// New: Create stock ledger with explicit balance (to avoid double subtraction)
private fun createStockLedgerForStockOutWithBalance(
    stockOutLine: StockOutLine, 
    balance: Double,
    ledgerType: String? = null
) {
    val item = stockOutLine.item ?: return
    
    val outQty = stockOutLine.qty?.toDouble() ?: 0.0
    
    // Use the provided balance instead of calculating from inventory
    val finalBalance = if (balance < 0) 0.0 else balance
    
    // Use传入的 ledgerType，如果没有则使用 stockOutLine.type
    val ledgerTypeToUse = ledgerType ?: stockOutLine.type ?: "Nor"
    
    val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return
    
    val stockLedger = StockLedger().apply {
        this.stockOutLine = stockOutLine
        this.inventory = inventory
        this.inQty = null
        this.outQty = outQty
        this.balance = finalBalance
        this.type = ledgerTypeToUse
        this.itemId = item.id
        this.itemCode = item.code
        this.uomId = inventory.uom?.id
            ?: itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(item.id!!)?.uom?.id
        this.date = LocalDate.now()
    }
    
    stockLedgerRepository.saveAndFlush(stockLedger)
    
    println("=== CREATED STOCK LEDGER ===")
    println("Type: ${ledgerTypeToUse}")
    println("OutQty: ${outQty}")
    println("Balance: ${finalBalance}")
    println("===========================")
}

// ✅ 修复：直接在 PickExecutionIssueService 中实现检查逻辑，避免循环依赖
private fun checkPickOrderLineCompletion(pickOrderLineId: Long) {
    val allStockOutLines = stockOutLineRepository
        .findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
    
    println("=== checkPickOrderLineCompletion for pickOrderLineId: $pickOrderLineId ===")
    println("Total stock out lines: ${allStockOutLines.size}")
    allStockOutLines.forEach { sol ->
        println("  StockOutLine ${sol.id}: status=${sol.status}, qty=${sol.qty}")
    }
    
    val unfinishedLine = allStockOutLines.filter {
        val status = it.status?.trim()?.lowercase()
        val isComplete = status == StockOutLineStatus.COMPLETE.status.lowercase()
        val isRejected = status == StockOutLineStatus.REJECTED.status.lowercase()
        val isPartiallyComplete = status == StockOutLineStatus.PARTIALLY_COMPLETE.status.lowercase()
        
        // Check if partially_completed should be considered as finished
        // (if total picked qty meets required qty)
        val shouldConsiderPartiallyCompleteAsFinished = if (isPartiallyComplete) {
            val pickOrderLine = pickOrderLineRepository.findById(pickOrderLineId).orElse(null)
            if (pickOrderLine != null && pickOrderLine.qty != null) {
                val totalPickedQty = allStockOutLines
                    .filter { sol -> 
                        val solStatus = sol.status?.trim()?.lowercase()
                        solStatus == "completed" || solStatus == "partially_completed"
                    }
                    .fold(BigDecimal.ZERO) { acc, sol -> 
                        val qtyValue = sol.qty ?: 0.0
                        acc + BigDecimal(qtyValue.toString())
                    }
                totalPickedQty >= pickOrderLine.qty
            } else {
                false
            }
        } else {
            false
        }
        
        !isComplete && !isRejected && !shouldConsiderPartiallyCompleteAsFinished
    }
    
    println("Unfinished lines: ${unfinishedLine.size}")
    if (unfinishedLine.isNotEmpty()) {
        unfinishedLine.forEach { sol ->
            println("  - StockOutLine ${sol.id}: status=${sol.status}, qty=${sol.qty}")
        }
    }
    
    if (unfinishedLine.isEmpty()) {
        // set pick order line status to complete
        val pol = pickOrderLineRepository.findById(pickOrderLineId).orElseThrow()
        val previousStatus = pol.status
        pickOrderLineRepository.save(
            pol.apply {
                this.status = PickOrderLineStatus.COMPLETED
            }
        )
        println("✅ Updated pick order line $pickOrderLineId from $previousStatus to COMPLETED")
    } else {
        println("⏳ Pick order line $pickOrderLineId not completed yet - has ${unfinishedLine.size} unfinished stock out lines")
    }
}
private fun resolveLotUomInfo(lotId: Long?): Pair<String?, String?> {
    if (lotId == null) return Pair(null, null)

    val lotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
        ?: return Pair(null, null)

    val uom = lotLine.stockUom?.uom  // ItemUom -> UomConversion
    val uomCode = uom?.code
    val uomDesc = uom?.udfudesc      // 👈 你要的 udfudesc

    return Pair(uomCode, uomDesc)
}
}