package com.ffii.fpsms.modules.pickOrder.service

import com.ffii.core.response.RecordsRes
import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.common.SecurityUtils
import com.ffii.fpsms.modules.common.CodeGenerator
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.entity.UomConversionRepository
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.pickOrder.entity.PickOrder
import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus
import java.time.LocalTime
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderInfo
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderGroup
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderGroupRepository
import com.ffii.fpsms.modules.pickOrder.web.models.*
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockOut
import com.ffii.fpsms.modules.stock.entity.StockOutLine
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.stock.entity.projection.CurrentInventoryItemInfo
import com.ffii.fpsms.modules.stock.service.InventoryLotLineService
import com.ffii.fpsms.modules.stock.service.InventoryService
import com.ffii.fpsms.modules.stock.service.StockOutLineService
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService
import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus
import com.ffii.fpsms.modules.stock.web.model.StockOutStatus
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPoRequest
import com.ffii.fpsms.modules.user.entity.UserRepository
import com.ffii.fpsms.modules.user.service.UserService
import org.springframework.data.domain.PageRequest
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import java.io.Serializable
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderGroupInfo
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository
import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository
import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository
import com.ffii.fpsms.modules.pickOrder.web.models.LotSubstitutionConfirmRequest
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecordRepository
import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLine
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository //  添加这行
import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo //  添加这行
import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot
@Service    
open class PickOrderService(
    private val jdbcDao: JdbcDao,
    private val pickOrderRepository: PickOrderRepository,
    private val pickOrderLineRepository: PickOrderLineRepository,
    private val suggestedPickLotService: SuggestedPickLotService,
    private val userService: UserService,
    private val stockOutLIneRepository: StockOutLIneRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val pickOrderGroupRepository: PickOrderGroupRepository,
    private val inventoryLotLineService: InventoryLotLineService,
    private val suggestPickLotRepository: SuggestPickLotRepository,
    private val inventoryService: InventoryService,
    private val stockOutRepository: StockOutRepository,
    private val itemsRepository: ItemsRepository,
    private val uomConversionRepository: UomConversionRepository,
    private val jobOrderRepository: JobOrderRepository,
    private val itemUomService: ItemUomService,
    private val deliveryOrderRepository: DeliveryOrderRepository, 
    private val truckRepository: TruckRepository,
    private val doPickOrderService: DoPickOrderService,
    private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository,
    private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
    private val doPickOrderRepository: DoPickOrderRepository,
    private val userRepository: UserRepository,
    private val joPickOrderRepository: JoPickOrderRepository,          //  添加这行
    private val joPickOrderRecordRepository: JoPickOrderRecordRepository,
    private val doPickOrderLineRepository: DoPickOrderLineRepository,
    private val inventoryLotRepository: InventoryLotRepository,

    
) : AbstractBaseEntityService<PickOrder, Long, PickOrderRepository>(jdbcDao, pickOrderRepository) {
    open fun create(request: SavePickOrderRequest): MessageResponse {
        val code = assignPickCode()
        val jo = request.joId?.let { jobOrderRepository.findById(it).getOrNull() }
        val deliveryOrder = request.doId?.let { deliveryOrderRepository.findById(it).getOrNull() }
        val pickOrder = PickOrder().apply {
            this.code = code
            this.jobOrder = jo
            this.deliveryOrder = deliveryOrder
            this.targetDate = request.targetDate.atStartOfDay()
            this.type = request.type
            this.status = PickOrderStatus.PENDING
            this.totalLines = request.pickOrderLine.size
            this.submittedLines = 0
        }
        val savedPickOrder = saveAndFlush(pickOrder)
        val polEntries = request.pickOrderLine.map {
//            val item = itemsRepository.findById(it.itemId).orElseThrow()
            val uom = it.uom?.let { uomCode -> uomConversionRepository.findByCodeAndDeletedFalse(uomCode) }
                ?: it.uomId?.let { uomId -> uomConversionRepository.findById(uomId).orElseThrow() }
            val item = it.itemId?.let { itemId -> itemsRepository.findById(itemId).orElseThrow() }
            PickOrderLine().apply {
                this.pickOrder = savedPickOrder
                this.item = item
                this.qty = it.qty
                this.uom = uom
                this.status = PickOrderLineStatus.PENDING
            }
        }
        pickOrderLineRepository.saveAll(polEntries)
        return MessageResponse(
            id = savedPickOrder.id,
            name = savedPickOrder.code,
            code = savedPickOrder.code,
            type = savedPickOrder.type!!.value,
            message = "success",
            errorPosition = "",
        )
    }

    open fun localDateTimeParse(dateTime: String?, pattern: String? = "YYYY-MM-DD hh:mm:ss"): LocalDateTime? {
        try {
            val formatter = DateTimeFormatter.ofPattern(pattern!!)
            return LocalDateTime.parse(dateTime, formatter)
        } catch (e: Exception) {
            return null
        }
    }

    open fun allPickOrders(): List<PickOrderInfo> {
        return pickOrderRepository.findPickOrderInfoByDeletedIsFalse()
    }

    open fun allPickOrdersByPage(request: SearchPickOrderRequest): RecordsRes<PickOrderInfo> {
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);

        val response = pickOrderRepository.findPickOrderInfoByConditionsAndPageable(
            code = request.code ?: "all",
            targetDateFrom = localDateTimeParse(request.targetDateFrom),
            targetDateTo = localDateTimeParse(request.targetDateTo),
            type = request.type ?: "all",
            status = request.status ?: "all",
            itemName = request.itemName ?: "all",
            assignTo = request.assignTo,
            pageable = pageable
        )

        val records = response.content
        val total = response.totalElements
        return RecordsRes<PickOrderInfo>(records, total.toInt())
    }


    open fun getPickOrdersWithStockBalanceByPage(request: SearchPickOrderRequest): RecordsRes<GetPickOrderInfo> {
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);

        // First get the basic pick order info for pagination
        val response = pickOrderRepository.findPickOrderInfoByConditionsAndPageable(
            code = request.code ?: "all",
            targetDateFrom = localDateTimeParse(request.targetDateFrom),
            targetDateTo = localDateTimeParse(request.targetDateTo),
            type = request.type ?: "all",
            status = request.status ?: "all",
            itemName = request.itemName ?: "all",
            assignTo = request.assignTo,
            pageable = pageable
        )

        val records = response.content
        val pickOrderIds = records.mapNotNull { it.id }

        if (pickOrderIds.isEmpty()) {
            return RecordsRes(emptyList(), 0)
        }

        // Get full pick orders with relationships
        val fullPickOrders = pickOrderRepository.findAllByIdIn(pickOrderIds)
        val groupsByPickOrderId = pickOrderGroupRepository.findByPickOrderIdInAndDeletedIsFalse(pickOrderIds)
            .groupBy { it.pickOrderId }
        // Get all item IDs
        val itemIds = fullPickOrders
            .flatMap { it.pickOrderLines }
            .mapNotNull { it.item?.id }
            .distinct()

        // Get inventory data
        val today = LocalDate.now()
        val zero = BigDecimal.ZERO

        val inventories = if (itemIds.isNotEmpty()) {
            inventoryLotLineService
                .allInventoryLotLinesByItemIdIn(itemIds)
                .filter { it.status == InventoryLotLineStatus.AVAILABLE.value }
                .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero }
                .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) }
                .groupBy { it.item?.id }
        } else {
            emptyMap()
        }

        // Build response (removed suggestions)
        val pickOrderInfos = fullPickOrders.map { po ->
            val pickOrderLineInfos = po.pickOrderLines.map { pol ->
                val inventory = pol.item?.id?.let { inventories[it] }

                GetPickOrderLineInfo(
                    id = pol.id,
                    itemId = pol.item?.id,
                    itemCode = pol.item?.code,
                    itemName = pol.item?.name,
                    availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) },
                    requiredQty = pol.qty,
                    uomCode = pol.uom?.code,
                    uomDesc = pol.uom?.udfudesc,
                    suggestedList = emptyList() // Empty list since you don't need suggestions
                )
            }

            val groupName = po.id?.let { pickOrderId ->
                groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name
            } ?: "No Group"
            GetPickOrderInfo(
                id = po.id,
                code = po.code,
                consoCode = po.consoCode,
                targetDate = po.targetDate,
                type = po.type?.value,
                status = po.status?.value,
                assignTo = po.assignTo?.id,
                groupName = groupName,
                pickOrderLines = pickOrderLineInfos
            )
        }

        val total = response.totalElements
        return RecordsRes(pickOrderInfos, total.toInt())
    }

    open fun assignPickOrders(pickOrderIds: List<Long>, assignTo: Long): MessageResponse {
        try {
            val pickOrders = pickOrderRepository.findAllById(pickOrderIds)
            val user = userService.find(assignTo).orElse(null) // 获取用户对象

            pickOrders.forEach { pickOrder ->
                pickOrder.assignTo = user  // 使用 User 对象而不是 Long
                pickOrder.status = PickOrderStatus.ASSIGNED  // 使用枚举值
            }

            pickOrderRepository.saveAll(pickOrders)

            return MessageResponse(
                id = null,
                name = "Pick orders assigned successfully",
                code = "SUCCESS",
                type = "pickorder",
                message = "Pick orders assigned successfully",
                errorPosition = null
            )
        } catch (e: Exception) {
            return MessageResponse(
                id = null,
                name = "Failed to assign pick orders",
                code = "ERROR",
                type = "pickorder",
                message = "Failed to assign pick orders: ${e.message}",
                errorPosition = null
            )
        }
    }

    open fun releasePickOrders(pickOrderIds: List<Long>, assignTo: Long): MessageResponse {
        try {
            val pickOrders = pickOrderRepository.findAllById(pickOrderIds)
            val user = userService.find(assignTo).orElse(null) // 获取用户对象

            pickOrders.forEach { pickOrder ->
                pickOrder.assignTo = user  // 使用 User 对象而不是 Long
                pickOrder.status = PickOrderStatus.RELEASED  // 使用枚举值
            }

            pickOrderRepository.saveAll(pickOrders)

            return MessageResponse(
                id = null,
                name = "Pick orders released successfully",
                code = "SUCCESS",
                type = "pickorder",
                message = "Pick orders released successfully",
                errorPosition = null
            )
        } catch (e: Exception) {
            return MessageResponse(
                id = null,
                name = "Failed to release pick orders",
                code = "ERROR",
                type = "pickorder",
                message = "Failed to release pick orders: ${e.message}",
                errorPosition = null
            )
        }
    }


    open fun getConsoPickOrderList(args: MutableMap<String, Any>): List<Map<String, Any>> {
        val sql = StringBuilder(
            "select"
                    + " po.consoCode, "
                    + " po.releasedDate, "
                    + " po.status, "
                    + " po.assignTo "
                    + " from pick_order po "
                    + " where po.deleted = false "
                    + " and po.consoCode is not null "
        )
        if (args.containsKey("consoCode")) {
            sql.append(" AND po.consoCode like :consoCode ");
        }
        if (args.containsKey("status")) {
            sql.append(" AND po.status = :status ");
        }
        if (args.containsKey("assignTo")) {
            sql.append(" AND po.assignTo = :assignTo ");
        }
        sql.append(" group by po.consoCode, po.releasedDate, po.status, po.assignTo ")
        return jdbcDao.queryForList(sql.toString(), args);
    }

    open fun assignPickCode(): String {
        val suffixFormat = "%03d"
        val pattern = "yyMMdd"
        val formatter = DateTimeFormatter.ofPattern(pattern)

        val prefix = "PI"
        val midfix = LocalDate.now().format(formatter)
        val suffix = String.format(suffixFormat, 1)

        val latestCode = pickOrderRepository.findLatestCodeByPrefix("${prefix}-${midfix}")
        if (latestCode != null) {
            val splitLatestCode = latestCode.split("-")
            if (splitLatestCode.size > 2) {
                val latestNo = splitLatestCode[2].toInt()
                return listOf<String>(prefix, midfix, String.format(suffixFormat, latestNo + 1)).joinToString("-")
            }
        }

        return listOf<String>(prefix, midfix, suffix).joinToString("-")
    }

    // Consolidating Pick Orders
    open fun assignConsoCode(): String {
        val suffixFormat = "%03d"
        val pattern = "yyyyMMdd"
        val formatter = DateTimeFormatter.ofPattern(pattern)

        val prefix = "PICK"
        val midfix = LocalDate.now().format(formatter)
        val suffix = String.format(suffixFormat, 1)

        val latestConsoCode = pickOrderRepository.findLatestConsoCodeByPrefix("${prefix}-${midfix}")
        if (latestConsoCode != null) {
            val splitLatestConsoCode = latestConsoCode.split("-")
            if (splitLatestConsoCode.size > 2) {
                val latestNo = splitLatestConsoCode[2].toInt()
                return listOf<String>(prefix, midfix, String.format(suffixFormat, latestNo + 1)).joinToString("-")
            }
        }

        return listOf<String>(prefix, midfix, suffix).joinToString("-")
    }

    open fun consoPickOrders(request: ConsoPickOrderRequest): List<PickOrderInfo> {
        val newConsoCode = assignConsoCode()
        val pickOrders = pickOrderRepository.findAllByIdInAndStatus(request.ids, PickOrderStatus.PENDING)
        pickOrders.forEach {
            it.consoCode = newConsoCode
            it.status = PickOrderStatus.CONSOLIDATED
        }
        println(newConsoCode)
        println(pickOrders)

        val savedPickOrders = pickOrderRepository.saveAll(pickOrders)
        val savedPickOrderInfos = savedPickOrders.map { po -> po.id as Serializable }
            .let { pickOrderRepository.findPickOrderInfoByIdIn(it) }

        return savedPickOrderInfos
    }

    open fun deconsoPickOrders(request: ConsoPickOrderRequest): List<PickOrderInfo> {
        val pickOrders = pickOrderRepository.findAllByIdInAndStatus(request.ids, PickOrderStatus.CONSOLIDATED)
        pickOrders.forEach {
            it.consoCode = null
            it.status = PickOrderStatus.PENDING
        }

        val updatedPickOrders = pickOrderRepository.saveAll(pickOrders)
        val updatedPickOrderInfos = updatedPickOrders.map { po -> po.id as Serializable }
            .let { pickOrderRepository.findPickOrderInfoByIdIn(it) }

        return updatedPickOrderInfos
    }

    open fun getPickOrderLine(args: MutableMap<String, Any>): List<Map<String, Any>> {
        if (!args.containsKey("consoCode") || args["consoCode"] == null) {
            throw IllegalArgumentException("consoCode must not be null")
        }
        val sql = StringBuilder(
            "select"
                    + " po.status as poStatus,"
                    + " pol.id, "
                    + " i.name as itemName, "
                    + " pol.qty as qty, "
                    + " max(uc.code) as uom, "
                    + " group_concat(ill.id) as lotLineId, "
                    + " group_concat(w.name) as warehouse, "
                    + " group_concat(il.lotNo) as suggestedLotNo, "
                    + " pol.status "
                    + " from pick_order po "
                    + " left join pick_order_line pol on pol.poId = po.id "
                    + " left join items i on i.id = pol.itemId "
                    + " left join suggested_pick_lot spl on spl.pickOrderLineId = pol.id "
                    + " left join inventory_lot_line ill on ill.id = spl.suggestedLotLineId "
                    + " left join warehouse w on w.id = ill.warehouseId "
                    + " left join inventory_lot il on il.id = ill.inventoryLotId "
                    + " left join uom_conversion uc on uc.id = pol.uomId "
                    + " where po.deleted = false "
                    + " and po.consoCode = :consoCode "
                    + " group by po.status, pol.id, i.name, pol.qty, pol.status "
        )
        return jdbcDao.queryForList(sql.toString(), args);
    }

    // TODO: Add actual pick lots
    open fun consoPickOrderDetail(consoCode: String): ConsoPickOrderResponse {
        val zero = BigDecimal.ZERO
        val one = BigDecimal.ONE

        // pick orders
        val pos = pickOrderRepository.findAllByConsoCode(consoCode)

        // Suggestions for Pick Order
//        val suggestions =
//            suggestedPickLotService.suggestionForPickOrders(SuggestedPickLotForPoRequest(pickOrders = pos))
//        val suggestedList = suggestions.suggestedList

        // Mapping: PickOrder -> PickOrderInConso
        val finalPos = pos.map { po ->
            val pols = po.pickOrderLines

            val finalPols = pols.map { pol ->
                val item = pol.item
                val uom = pol.uom

                // Check If already have suggestion
                val suggestedPickLots = pol.suggestedPickLots
//                if (suggestedPickLots.isEmpty()) {
//                    suggestedPickLots = suggestedList.filter { it.pickOrderLine?.id == pol.id }.toMutableList()
//                }

                // Mapping: SuggestedPickLot -> SuggestPickLotInConso
                val finalSuggestedPickLots = suggestedPickLots.map {
                    val inventoryLotLine = it.suggestedLotLine
                    val salesUnit = inventoryLotLine?.inventoryLot?.item?.id?.let { _itemId ->
                        itemUomService.findSalesUnitByItemId(_itemId)
                    }
                    val ratio = BigDecimal.ONE //(salesUnit?.ratioN ?: zero).divide(salesUnit?.ratioD ?: one)

                    val remainingQty = (inventoryLotLine?.inQty ?: zero)
                        .minus(inventoryLotLine?.outQty ?: zero)
                        .minus(inventoryLotLine?.holdQty ?: zero)
                    val stockUom = inventoryLotLine?.stockUom?.uom

                    // Mapping: InventoryLotLine -> InventoryLotLineInConso
                    val finalInventoryLotLine = InventoryLotLineInConso(
                        id = inventoryLotLine?.id,
                        lotNo = inventoryLotLine?.inventoryLot?.lotNo,
                        inQty = (inventoryLotLine?.inQty ?: zero).multiply(ratio),
                        outQty = (inventoryLotLine?.outQty ?: zero).multiply(ratio),
                        holdQty = (inventoryLotLine?.holdQty ?: zero).multiply(ratio),
                        remainingQty = remainingQty.multiply(ratio),
                        stockUom = IdCodeDesc(stockUom?.id, stockUom?.code, stockUom?.udfudesc),
                        status = inventoryLotLine?.status?.value,
                        remarks = inventoryLotLine?.remarks
                    )

                    // Return
                    SuggestedPickLotInConso(
                        id = it.id,
                        type = it.type?.value,
                        qty = it.qty,
                        inventoryLotLine = finalInventoryLotLine,
                        pickSuggested = it.pickSuggested
                    )
                }


                // Return
                PickOrderLineInConso(
                    id = pol.id,
                    item = IdCodeName(item?.id, item?.code, item?.name),
                    qty = pol.qty,
                    uom = IdCodeDesc(uom?.id, uom?.code, uom?.udfudesc),
                    suggestedPickLots = finalSuggestedPickLots,
                    status = pol.status?.value,
                    actualPickLots = mutableListOf()
                )
            }

            val releasedBy = po.releasedBy
            val assignTo = po.assignTo

            // Return
            PickOrderInConso(
                id = po.id,
                code = po.code,
                targetDate = po.targetDate,
                completeDate = po.completeDate,
                releasedDate = po.releasedDate,
                releasedBy = IdName(releasedBy?.id, releasedBy?.name),
                assignTo = IdName(assignTo?.id, assignTo?.name),
                pickOrderLines = finalPols,
                type = po.type?.value,
                status = po.status?.value,
            )
        }

        // Items
        val finalItems = finalPos
            .flatMap { it.pickOrderLines }
            .groupBy { it.item.id }
            .map { (_itemId, _line) ->
                val itemSuggestions = _line
                    .flatMap { it.suggestedPickLots }
                    .groupBy { it.inventoryLotLine.id to it.pickSuggested }
                    .map { (_key, _lot) ->
                        SuggestedPickLotInConso(
                            id = _key.first,
                            type = _lot.first().type,
                            inventoryLotLine = _lot.first().inventoryLotLine,
                            qty = _lot.fold(zero) { sum, item -> sum + (item.qty ?: zero) },
                            pickSuggested = _key.second
                        )
                    }

                ItemInConso(
                    id = _line.first().item.id,
                    code = _line.first().item.code,
                    name = _line.first().item.name,
                    qty = _line.fold(zero) { sum, item -> sum + (item.qty ?: zero) },
                    suggestPickLots = itemSuggestions,
                    actualPickLots = mutableListOf()
                )
            }

        val response = ConsoPickOrderResponse(
            consoCode = consoCode,
            pickOrders = finalPos,
            items = finalItems
        )

        return response
    }

    open fun releaseConsoPickOrderInfo(consoCode: String): ReleasePickOrderInfoResponse {
        val today = LocalDate.now()
        val zero = BigDecimal.ZERO
        val one = BigDecimal.ONE
        val pos = pickOrderRepository.findAllByConsoCodeAndStatus(consoCode, PickOrderStatus.CONSOLIDATED)
        println(pos)
        // Get Inventory Data
        val requiredItems = pos
            .flatMap { it.pickOrderLines }
            .groupBy { it.item?.id }
            .map { (key, value) ->
                key to object : CurrentInventoryItemInfo {
                    override val id: Long? = value[0].item?.id
                    override val code: String? = value[0].item?.code
                    override val name: String? = value[0].item?.name
                    override val uomDesc: String? = value[0].uom?.udfudesc
                    override var availableQty: BigDecimal? = zero
                    override val requiredQty: BigDecimal = value.sumOf { it.qty ?: zero }

                }
            } // itemId - requiredQty

        val itemIds = requiredItems.mapNotNull { it.first }
//        val inventories = inventoryLotLineRepository.findCurrentInventoryByItems(itemIds)
//        val inventories = inventoryService.allInventoriesByItemIds(itemIds)
        val inventories = inventoryLotLineService
            .allInventoryLotLinesByItemIdIn(itemIds)
            .filter { it.status == InventoryLotLineStatus.AVAILABLE.value }
            .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero }
            .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) }
            .sortedBy { it.expiryDate }
            .groupBy { it.item?.id }

        // Pick Orders
        val releasePickOrderInfos = pos
            .map { po ->
                val releasePickOrderLineInfos = po.pickOrderLines.map { pol ->
//                    if (pol.item?.id != null && pol.item!!.id!! > 0) {
                    val inventory = pol.item?.id?.let { inventories[it] }
                    val itemUom = pol.item?.id?.let { itemUomService.findSalesUnitByItemId(it) }
//                    val inventory = inventories.find { it.itemId == pol.item?.id }

                    // Return
                    ReleasePickOrderLineInfo(
                        id = pol.id,
                        itemId = pol.item?.id,
                        itemCode = pol.item?.code,
                        itemName = pol.item?.name,
//                            availableQty = inventory?.availableQty,
                        availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) },
//                        availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) * (itemUom?.ratioN ?: one) * (itemUom?.ratioD ?: one) },
                        requiredQty = pol.qty,
                        uomCode = pol.uom?.code,
                        uomDesc = pol.uom?.udfudesc,
                    )
//                    }
                }

                // Return
                ReleasePickOrderInfo(
                    id = po.id,
                    code = po.code,
                    targetDate = po.targetDate,
                    type = po.type?.value,
                    status = po.status?.value,
                    pickOrderLines = releasePickOrderLineInfos
                )
            }

        // Items
        val currentInventoryInfos = requiredItems.map { item ->
//            val inventory = inventories
//                .find { it.itemId == item.first }

            val inventory = item.first?.let { inventories[it] }
            val itemUom = item.first?.let { itemUomService.findSalesUnitByItemId(it) }

            item.second.let {
//                it.availableQty = inventory?.availableQty

                it.availableQty = inventory?.sumOf { i -> i.availableQty ?: zero }
//                it.availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) * (itemUom?.ratioN ?: one) * (itemUom?.ratioD ?: one) }

                // return
                it
            }
        }

        return ReleasePickOrderInfoResponse(
            consoCode = consoCode,
            pickOrders = releasePickOrderInfos,
            items = currentInventoryInfos,
        )
    }

    // TODO:
    open fun getPickOrdersInfo(ids: List<Long>): GetPickOrderInfoResponse {
        val today = LocalDate.now()
        val zero = BigDecimal.ZERO
        val one = BigDecimal.ONE
        val pos = pickOrderRepository.findAllByIdIn(ids)
        println(pos)
        val groupsByPickOrderId = pickOrderGroupRepository.findByPickOrderIdInAndDeletedIsFalse(ids)
            .groupBy { it.pickOrderId }

        // Get Inventory Data
        val requiredItems = pos
            .flatMap { it.pickOrderLines }
            .groupBy { it.item?.id }
            .map { (key, value) ->
                key to object : CurrentInventoryItemInfo {
                    override val id: Long? = value[0].item?.id
                    override val code: String? = value[0].item?.code
                    override val name: String? = value[0].item?.name
                    override val uomDesc: String? = value[0].uom?.udfudesc
                    override var availableQty: BigDecimal? = zero
                    override val requiredQty: BigDecimal = value.sumOf { it.qty ?: zero }
                }
            } // itemId - requiredQty

        val itemIds = requiredItems.mapNotNull { it.first }
        val inventories = inventoryLotLineService
            .allInventoryLotLinesByItemIdIn(itemIds)
            .filter { it.status == InventoryLotLineStatus.AVAILABLE.value }
            .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero }
            .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) }
            .sortedBy { it.expiryDate }
            .groupBy { it.item?.id }

        val suggestions =
            suggestedPickLotService.suggestionForPickOrders(SuggestedPickLotForPoRequest(pickOrders = pos))

        //  Get stock out line data for each pick order line
        val pickOrderLineIds = pos.flatMap { it.pickOrderLines }.mapNotNull { it.id }
        val stockOutLinesByPickOrderLineId = if (pickOrderLineIds.isNotEmpty()) {
            pickOrderLineIds.associateWith { pickOrderLineId ->
                stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
            }
        } else {
            emptyMap()
        }

        //  Pre-calculate available quantities for each item ONCE
        val itemAvailableQtyMap = mutableMapOf<Long, BigDecimal>()
        itemIds.forEach { itemId ->
            val inventory = inventories[itemId]
            val totalAvailableQty = inventory?.sumOf { i ->
                val inQty = i.inQty ?: zero
                val outQty = i.outQty ?: zero
                val holdQty = i.holdQty ?: zero
                inQty.minus(outQty).minus(holdQty)
            } ?: zero
            itemAvailableQtyMap[itemId] = totalAvailableQty
        }

        val releasePickOrderLineInfos = pos
        .map { po ->
            val releasePickOrderLineInfos = po.pickOrderLines.map { pol ->
                val itemId = pol.item?.id
                val availableQty = itemId?.let { itemAvailableQtyMap[it] } ?: zero
    
                // 取得這一行的所有 stock_out_line
                val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList()
    
                // Debug: 已拣数量
                println("=== PICKED QTY DEBUG: Line ${pol.id} ===")
                println("Stock Out Lines: ${stockOutLines.map { "${it.id}(status=${it.status}, qty=${it.qty})" }}")
    
                val totalPickedQty = stockOutLines.sumOf { it.qty ?: zero }
    
                println("Total Picked Qty: $totalPickedQty")
                println("=== END DEBUG ===")
    
                // ✅ 無批次的 stock_out_line（inventoryLotLineId 為空）
                val noLotStockouts = stockOutLines
                    .filter { it.inventoryLotLineId == null }   // 注意：用 inventoryLotLineId
                    .map { sol ->
                        NoLotLineDto(
                            stockOutLineId = sol.id!!,
                            status = sol.status,
                            qty = sol.qty ?: zero,
                            created = null,   // 若實體沒有 created/modified 欄位，就先給 null
                            modified = null
                        )
                    }
    
                // 回傳行資訊
                GetPickOrderLineInfo(
                    id = pol.id,
                    itemId = pol.item?.id,
                    itemCode = pol.item?.code,
                    itemName = pol.item?.name,
                    availableQty = availableQty,
                    requiredQty = pol.qty,
                    uomCode = pol.uom?.code,
                    uomDesc = pol.uom?.udfudesc,
                    suggestedList = suggestions.suggestedList.filter { it.pickOrderLine?.id == pol.id },
                    pickedQty = totalPickedQty,
                    noLotLines = noLotStockouts        // ✅ 關鍵：把無批次行帶出去
                )
            }
    
            val groupName = po.id?.let { pickOrderId ->
                groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name
            } ?: "No Group"
    
            GetPickOrderInfo(
                id = po.id,
                code = po.code,
                consoCode = po.consoCode,
                targetDate = po.targetDate,
                type = po.type?.value,
                status = po.status?.value,
                assignTo = po.assignTo?.id,
                groupName = groupName,
                pickOrderLines = releasePickOrderLineInfos
            )
        }
        // Items
        val currentInventoryInfos = requiredItems.map { item ->
            val inventory = item.first?.let { inventories[it] }
            val itemUom = item.first?.let { itemUomService.findSalesUnitByItemId(it) }

            item.second.let {
                val convertedAvailableQty = inventory?.sumOf { i ->
                    val baseQty = (i.availableQty ?: zero)
                    baseQty
                }

                it.availableQty = convertedAvailableQty
                it
            }
        }
        val consoCode = pos.firstOrNull()?.consoCode
        return GetPickOrderInfoResponse(
            consoCode = consoCode,
            pickOrders = releasePickOrderLineInfos,
            items = currentInventoryInfos,
        )
    }

    open fun getAllPickOrdersInfo(userId: Long? = null): GetPickOrderInfoResponse {
        // 使用现有的查询方法获取所有 Pick Orders，然后在内存中过滤
        val allPickOrders = pickOrderRepository.findAll()
        val releasedPickOrderIds = allPickOrders
            .filter { it.status == PickOrderStatus.RELEASED }
            .filter { userId == null || it.assignTo?.id == userId } //  Fixed: now userId is a parameter
            .map { it.id!! }

        println("All released pick order IDs for user $userId: $releasedPickOrderIds")

        // 如果没有任何已发布的 Pick Orders，返回空结果
        if (releasedPickOrderIds.isEmpty()) {
            return GetPickOrderInfoResponse(
                consoCode = null,
                pickOrders = emptyList(),
                items = emptyList()
            )
        }

        // 重用现有的 getPickOrdersInfo 方法
        return getPickOrdersInfo(releasedPickOrderIds)
    }

    open fun getPickOrderLineLotDetails(pickOrderLineId: Long): List<PickOrderLineLotDetailResponse> {
        val today = LocalDate.now()
        val zero = BigDecimal.ZERO
    
        println("=== Debug: getPickOrderLineLotDetails (Repository-based) ===")
        println("pickOrderLineId: $pickOrderLineId")
        println("today: $today")
    
        // ✅ 1. 获取 PickOrderLine 以获取 UOM 信息
        val pickOrderLine = pickOrderLineRepository.findById(pickOrderLineId).orElse(null)
        if (pickOrderLine == null) {
            println("❌ PickOrderLine not found: $pickOrderLineId")
            return emptyList()
        }
    
        val uomDesc = pickOrderLine.uom?.udfudesc ?: "N/A"
    
        // ✅ 2. 获取所有相关的 SuggestedPickLot
        val suggestedPickLots = suggestPickLotRepository.findAllByPickOrderLineId(pickOrderLineId)
        val suggestedPickLotsByLotId = suggestedPickLots
            .mapNotNull { spl -> spl.suggestedLotLine?.id?.let { it to spl } }
            .toMap()
    
        // ✅ 3. 获取所有相关的 StockOutLine（包括有 lot 和没有 lot 的）
        val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
        val stockOutLinesByLotId = stockOutLines
            .filter { it.inventoryLotLineId != null }
            .mapNotNull { sol -> sol.inventoryLotLineId?.let { it to sol } }
            .toMap()
        
        // ✅ 4. 获取所有 no-lot 的 StockOutLine
        val noLotStockOutLines = stockOutLines.filter { it.inventoryLotLineId == null }
    
        // ✅ 5. 获取所有相关的 InventoryLotLine IDs
        val inventoryLotLineIds = (suggestedPickLots.mapNotNull { it.suggestedLotLine?.id } +
                stockOutLines.mapNotNull { it.inventoryLotLineId }).distinct()
    
        // ✅ 6. 批量加载 InventoryLotLine 实体
        val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) {
            inventoryLotLineRepository.findAllById(inventoryLotLineIds)
                .associateBy { it.id!! }
        } else {
            emptyMap()
        }
    
        // ✅ 7. 构建有 lot 的记录
        val lotDetails = inventoryLotLines.values.mapNotNull { ill ->
            val il = ill.inventoryLot
            val w = ill.warehouse
            val spl = ill.id?.let { suggestedPickLotsByLotId[it] }
            val sol = ill.id?.let { stockOutLinesByLotId[it] }
    
            // 计算可用数量
            val inQty = ill.inQty ?: zero
            val outQty = ill.outQty ?: zero
            val holdQty = ill.holdQty ?: zero
            val availableQty = inQty.minus(outQty).minus(holdQty)
    
            // 计算所有 pick orders 的总已拣数量
            val totalPickedByAllPickOrders = if (ill.id != null) {
                stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
                    .filter { it.inventoryLotLineId == ill.id }
                    .sumOf { it.qty ?: zero }
            } else {
                zero
            }
    
            // 判断是否过期
            val isExpired = il?.expiryDate?.let { it.isBefore(today) } == true
    
            // 计算 lotAvailability
            val lotAvailability = when {
                isExpired -> "expired"
                sol?.status == "rejected" -> "rejected"
                ill.status?.value == "unavailable" -> "status_unavailable"
                availableQty <= zero -> "insufficient_stock"
                else -> "available"
            }
    
            // 过滤：只返回未完成的或需要显示的
            val shouldInclude = sol == null || 
                sol.status != "completed" || 
                (sol.qty ?: zero) != (spl?.qty ?: zero)
    
            if (!shouldInclude) {
                return@mapNotNull null
            }
    
            PickOrderLineLotDetailResponse(
                lotId = ill.id,
                lotNo = il?.lotNo,
                expiryDate = il?.expiryDate,
                location = w?.code,
                stockInLineId = il?.stockInLine?.id,
                stockUnit = ill.stockUom?.uom?.udfudesc ?: uomDesc,
                availableQty = availableQty,
                requiredQty = spl?.qty ?: zero,
                inQty = inQty,
                outQty = outQty,
                holdQty = holdQty,
                actualPickQty = sol?.qty ?: zero,
                suggestedPickLotId = spl?.id,
                lotStatus = ill.status?.value,
                stockOutLineId = sol?.id,
                stockOutLineStatus = sol?.status,
                stockOutLineQty = sol?.qty,
                totalPickedByAllPickOrders = totalPickedByAllPickOrders,
                remainingAfterAllPickOrders = availableQty,
                lotAvailability = lotAvailability,
                noLot = false
            )
        }
    
        // ✅ 8. 构建 no-lot 的记录
        val noLotDetails = noLotStockOutLines.mapNotNull { sol ->
            // 过滤：只返回未完成的
            if (sol.status == "completed") {
                return@mapNotNull null
            }
    
            PickOrderLineLotDetailResponse(
                lotId = null,
                lotNo = null,
                expiryDate = null,
                location = null,
                stockInLineId = null,
                stockUnit = uomDesc,
                availableQty = null,
                requiredQty = zero,
                inQty = zero,
                outQty = zero,
                holdQty = zero,
                actualPickQty = sol.qty ?: zero,
                suggestedPickLotId = null,
                lotStatus = "unavailable",
                stockOutLineId = sol.id,
                stockOutLineStatus = sol.status,
                stockOutLineQty = sol.qty,
                totalPickedByAllPickOrders = zero,
                remainingAfterAllPickOrders = null,
                lotAvailability = "insufficient_stock",
                noLot = true
            )
        }
    
        // ✅ 9. 合并并排序
        val allDetails = (lotDetails + noLotDetails).sortedWith(
            compareBy<PickOrderLineLotDetailResponse>(
                { it.noLot }, // no-lot 行排在后面
                { it.stockOutLineStatus == "rejected" }, // rejected 排在前面
                { it.expiryDate ?: LocalDate.MAX }, // 按过期日期排序
                { it.lotNo ?: "" } // 按 lotNo 排序
            )
        )
    
        println("✅ Final result count: ${allDetails.size}")
        println("  - With lot: ${lotDetails.size}")
        println("  - No-lot: ${noLotDetails.size}")
    
        return allDetails
    }

    @Transactional(rollbackFor = [java.lang.Exception::class])
    open fun releaseConsoPickOrderAction(request: ReleaseConsoPickOrderRequest): ReleasePickOrderInfoResponse {
        val zero = BigDecimal.ZERO
        val releasedBy = SecurityUtils.getUser().getOrNull()
        val assignTo = request.assignTo?.let { userService.find(it) }?.getOrNull()
        val pos = pickOrderRepository.findAllByConsoCodeAndStatus(request.consoCode, PickOrderStatus.CONSOLIDATED)
        pos.forEach {
            it.apply {
                this.releasedBy = releasedBy
                status = PickOrderStatus.RELEASED
                this.assignTo = assignTo
            }
        }

        val suggestions =
            suggestedPickLotService.suggestionForPickOrders(SuggestedPickLotForPoRequest(pickOrders = pos))
        val currUser = SecurityUtils.getUser().orElseThrow()
        val stockOut = StockOut().apply {
            this.type = "job"
            this.consoPickOrderCode = request.consoCode
            this.status = StockOutStatus.PENDING.status
            this.handler = currUser.id
        }
        stockOutRepository.save(stockOut)
        suggestions.suggestedList.forEach {
            println("suggest ${it.suggestedLotLine?.id}")
            println("pick ${it.pickOrderLine?.id}")
        }
        suggestedPickLotService.saveAll(suggestions.suggestedList)

        val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
        pickOrderRepository.saveAll(pos)

        val inventoryLotLines =
            inventoryLotLineRepository.findAllByIdIn(saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id })

        logger.info(saveSuggestedPickLots)
        saveSuggestedPickLots.forEach { lot ->
//            val ratio = lot.suggestedLotLine!!.stockUom
            if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) {
                val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine)
                inventoryLotLines[lineIndex].holdQty =
                    (inventoryLotLines[lineIndex].holdQty ?: zero).plus(lot.qty ?: zero)
            }
        }
//        inventoryLotLines.forEach {  line ->
//            line.holdQty = (line.holdQty ?: zero)?.plus(saveSuggestedPickLots.find { it.suggestedLotLine == line }?.qty ?: zero)
//        }

        inventoryLotLineRepository.saveAll(inventoryLotLines)

        return releaseConsoPickOrderInfo(request.consoCode)
    }

    @Transactional(rollbackFor = [java.lang.Exception::class])
    open fun releaseAssignedPickOrders(pickOrderIds: List<Long>, assignTo: Long): MessageResponse {
        try {
            val zero = BigDecimal.ZERO
            val releasedBy = SecurityUtils.getUser().getOrNull()
            val user = userService.find(assignTo).orElse(null)

            val pickOrders = pickOrderRepository.findAllByIdInAndStatus(pickOrderIds, PickOrderStatus.ASSIGNED)

            if (pickOrders.isEmpty()) {
                return MessageResponse(
                    id = null,
                    name = "No assigned pick orders found",
                    code = "ERROR",
                    type = "pickorder",
                    message = "No assigned pick orders found with the provided IDs",
                    errorPosition = null
                )
            }

            // ← GENERATE CONSOCODE EARLY AND SAVE IMMEDIATELY
            val newConsoCode = assignConsoCode()
            val currUser = SecurityUtils.getUser().orElseThrow()

            // Create and save StockOut immediately to prevent duplicate consoCodes
            val stockOut = StockOut().apply {
                this.type = "job"
                this.consoPickOrderCode = newConsoCode
                this.status = StockOutStatus.PENDING.status
                this.handler = currUser.id
            }
            val savedStockOut = stockOutRepository.saveAndFlush(stockOut)  // ← FLUSH to commit immediately

            // Update pick orders
            pickOrders.forEach { pickOrder ->
                pickOrder.apply {
                    this.releasedBy = releasedBy
                    status = PickOrderStatus.RELEASED
                    this.assignTo = user
                    this.consoCode = newConsoCode  // ← Also assign consoCode to pick orders
                }
            }

            val suggestions = suggestedPickLotService.suggestionForPickOrders(
                SuggestedPickLotForPoRequest(pickOrders = pickOrders)
            )

            val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)

            pickOrderRepository.saveAll(pickOrders)

            val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
                saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
            )
            saveSuggestedPickLots.forEach { lot ->
                val lotLineId = lot.suggestedLotLine?.id
                if (lotLineId != null) {
                    val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine)
                    if (lineIndex >= 0) {
                        val currentHold = inventoryLotLines[lineIndex].holdQty ?: zero
                        val addHold = lot.qty ?: zero
                        inventoryLotLines[lineIndex].holdQty = currentHold.plus(addHold)
                    }
                }
            }
            logger.info("saveSuggestedPickLots: $saveSuggestedPickLots")
            inventoryLotLineRepository.saveAll(inventoryLotLines)
            var precreated = 0
            saveSuggestedPickLots.forEach { lot ->
                val polId = lot.pickOrderLine?.id
                val illId = lot.suggestedLotLine?.id
                if (polId != null && illId != null) {
                    try {
                        val existingLines =
                            stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
                                polId,
                                illId
                            )
                        if (existingLines.isEmpty()) {
                            val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null)
                            val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null)

                            if (pickOrderLine != null && inventoryLotLine != null) {
                                val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply {
                                    this.stockOut = savedStockOut
                                    this.pickOrderLine = pickOrderLine
                                    this.inventoryLotLine = inventoryLotLine
                                    this.item = pickOrderLine.item  //  Add the required item field
                                    this.status =
                                        com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status
                                    this.qty = 0.0
                                    this.type = "NOR"
                                }
                                stockOutLIneRepository.save(line)
                                precreated++
                            } else {
                                logger.warn("Could not find pickOrderLine (id=$polId) or inventoryLotLine (id=$illId)")
                            }
                        }
                    } catch (e: Exception) {
                        logger.warn("Failed to create stock out line for pickOrderLineId=$polId, inventoryLotLineId=$illId: ${e.message}")
                    }
                }
            }
            logger.info("Precreated $precreated stock out lines for suggested lots on release.")
            return MessageResponse(
                id = null,
                name = "Pick orders released successfully with inventory management",
                code = "SUCCESS",
                type = "pickorder",
                message = "Pick orders released successfully with inventory management",
                errorPosition = null,
                entity = mapOf("consoCode" to newConsoCode)  // ← Return the generated consoCode
            )

        } catch (e: Exception) {
            return MessageResponse(
                id = null,
                name = "Failed to release pick orders",
                code = "ERROR",
                type = "pickorder",
                message = "Failed to release pick orders: ${e.message}",
                errorPosition = null
            )
        }
    }

    @Throws(IOException::class)
    @Transactional
    open fun completeStockOut(consoCode: String): MessageResponse {
        println("=== DEBUG: completeStockOut ===")
        println("consoCode: $consoCode")

        val stockOut = stockOutRepository.findFirstByConsoPickOrderCodeOrderByIdDesc(consoCode)
    ?: throw NoSuchElementException("No stock_out found for consoCode: $consoCode")
        println("StockOut ID: ${stockOut.id}")

        //  FIXED: 直接通过 consoCode 查找相关的 stock out lines，而不依赖 stockOutId 关联
        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))
        println("=== DEBUG: Stock Out Lines for consoCode $consoCode ===")
        stockOutLinesResult.forEach { row ->
            println("StockOutLine ID: ${row["id"]}, stockOutId: ${row["stockOutId"]}, status: ${row["status"]}, qty: ${row["qty"]}, pickOrderLineId: ${row["pickOrderLineId"]}")
        }

        //  将结果转换为 StockOutLine 对象
        val stockOutLineIds = stockOutLinesResult.mapNotNull { row ->
            val id = row["id"]
            println("Raw ID value: $id (type: ${id?.javaClass?.simpleName})")
            val convertedId = when (id) {
                is Number -> id.toLong()
                is String -> id.toLongOrNull()
                else -> null
            }
            println("Converted ID: $convertedId")
            convertedId
        }

        val stockOutLines = if (stockOutLineIds.isNotEmpty()) {
            stockOutLIneRepository.findAllById(stockOutLineIds)
        } else {
            emptyList()
        }

        println("Total stock out lines for consoCode $consoCode: ${stockOutLines.size}")

        stockOutLines.forEach { line ->
            println("Stock out line ${line.id}: status=${line.status}, qty=${line.qty}")
        }

        val unfinishedLines = stockOutLines.filter { sol ->
            val status = sol.status?.trim()?.lowercase()
            val isComplete = status == "completed" || status == StockOutLineStatus.COMPLETE.status.lowercase()
            val isRejected = status == "rejected" || status == StockOutLineStatus.REJECTED.status.lowercase()
            val isPartiallyComplete = status == "partially_completed" || status == StockOutLineStatus.PARTIALLY_COMPLETE.status.lowercase()
        
            // 只要是“结束态”（你认可的状态），就不算 unfinished
            !(isComplete || isRejected || isPartiallyComplete)
        }

        println("Unfinished lines: ${unfinishedLines.size}")

        if (unfinishedLines.isEmpty()) {
            stockOut.apply {
                this.status = StockOutStatus.COMPLETE.status
            }
            val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

            //  NEW APPROACH: Update through relationship chain
            // 1. Get all pick order lines from completed stock out lines
            val completedPickOrderLineIds = stockOutLines.mapNotNull { it.pickOrderLine?.id }
            println("Completed pick order line IDs: $completedPickOrderLineIds")

            // 2. Get all pick order lines and update their status
            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")

                // 3. Get all unique pick order IDs from the lines
                val pickOrderIds = pickOrderLines.mapNotNull { it.pickOrder?.id }.distinct()
                println("Affected pick order IDs: $pickOrderIds")

                // 4. Check if each pick order is fully completed
                pickOrderIds.forEach { pickOrderId ->
                    val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
                    if (pickOrder != null) {
                        // Check if all lines of this pick order are completed
                        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()) {
                            // All lines are completed, update pick order status
                            pickOrder.status = PickOrderStatus.COMPLETED
                            pickOrder.completeDate = LocalDateTime.now()
                            pickOrderRepository.save(pickOrder)
                            println(" Updated pick order ${pickOrder.code} to COMPLETED status")

                            //  修改：通过 do_pick_order_line 查询（因为 do_pick_order.pick_order_id 可能为 null）
                            val doPickOrderLines =
                                doPickOrderLineRepository.findByPickOrderIdAndDeletedFalse(pickOrderId)
                            val doPickOrderIds = doPickOrderLines.mapNotNull { it.doPickOrderId }.distinct()

                            println("    DEBUG: Found ${doPickOrderLines.size} do_pick_order_line records for pick order $pickOrderId")
                            println("    DEBUG: Unique do_pick_order IDs: $doPickOrderIds")

                            if (doPickOrderIds.isEmpty()) {
                                println("ℹ️ INFO: No do_pick_order records found - skipping record copying")
                            } else {
                                var copied = 0
                                var deleted = 0

                                doPickOrderIds.forEach { doPickOrderId ->
                                    val dpo = doPickOrderRepository.findById(doPickOrderId).orElse(null)
                                    if (dpo == null) {
                                        println("  WARNING: do_pick_order $doPickOrderId not found, skipping")
                                        return@forEach
                                    }
                                
                                    println("    Processing do_pick_order ID: ${dpo.id}, ticket: ${dpo.ticketNo}")
                                
                                    //  新增：检查这个 do_pick_order 下是否还有其他未完成的 pick orders
                                    val allDoPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!)
                                    val allPickOrderIdsInDpo = allDoPickOrderLines.mapNotNull { it.pickOrderId }.distinct()
                                    
                                    println("    DEBUG: This do_pick_order has ${allPickOrderIdsInDpo.size} pick orders")
                                    
                                    //  检查每个 pick order 的状态
                                    val pickOrderStatuses = allPickOrderIdsInDpo.map { poId ->
                                        val po = pickOrderRepository.findById(poId).orElse(null)
                                        poId to po?.status
                                    }
                                    
                                    pickOrderStatuses.forEach { (poId, status) ->
                                        println("    DEBUG: Pick order $poId status: $status")
                                    }
                                    
                                    //  只有当所有 pick orders 都是 COMPLETED 状态时，才移动到 record 表
                                    val allPickOrdersCompleted = pickOrderStatuses.all { (_, status) -> 
                                        status == PickOrderStatus.COMPLETED 
                                    }
                                    
                                    if (!allPickOrdersCompleted) {
                                        println("⏳ Not all pick orders in this do_pick_order are completed, keeping do_pick_order alive")
                                        println("    DEBUG: Completed pick orders: ${pickOrderStatuses.count { it.second == PickOrderStatus.COMPLETED }}/${pickOrderStatuses.size}")
                                        return@forEach  //  跳过这个 do_pick_order，不删除它
                                    }
                                    
                                    println(" All pick orders in this do_pick_order are completed, moving to record table")
                                    // 2) 先复制 do_pick_order -> do_pick_order_record
                                    val savedHeader = saveDoPickOrderRecordWithDnRetry { deliveryNoteCode ->
                                        DoPickOrderRecord(
                                            recordId = dpo.id,
                                            storeId = dpo.storeId ?: "",
                                            ticketNo = dpo.ticketNo ?: "",
                                            ticketStatus = DoPickOrderStatus.completed,
                                            truckId = dpo.truckId,
                                            truckDepartureTime = dpo.truckDepartureTime,
                                            shopId = dpo.shopId,
                                            handledBy = dpo.handledBy,
                                            handlerName = dpo.handlerName,
                                            doOrderId = dpo.doOrderId,
                                            pickOrderCode = dpo.pickOrderCode,
                                            deliveryOrderCode = dpo.deliveryOrderCode,
                                            deliveryNoteCode = deliveryNoteCode,
                                            loadingSequence = dpo.loadingSequence,
                                            ticketReleaseTime = dpo.ticketReleaseTime,
                                            ticketCompleteDateTime = java.time.LocalDateTime.now(),
                                            truckLanceCode = dpo.truckLanceCode,
                                            shopCode = dpo.shopCode,
                                            shopName = dpo.shopName,
                                            requiredDeliveryDate = dpo.requiredDeliveryDate
                                        )
                                    }

                                    // 3) 复制行 do_pick_order_line -> do_pick_order_line_record
                                    val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!)
                                    val lineRecords = lines.map { l: DoPickOrderLine ->
                                        DoPickOrderLineRecord().apply {
                                            this.recordId=l.id
                                            this.doPickOrderId = savedHeader.recordId
                                            this.pickOrderId = l.pickOrderId
                                            this.doOrderId = l.doOrderId
                                            this.pickOrderCode = l.pickOrderCode
                                            this.deliveryOrderCode = l.deliveryOrderCode
                                            this.status = l.status
                                        }
                                    }
                                    if (lineRecords.isNotEmpty()) {
                                        doPickOrderLineRecordRepository.saveAll(lineRecords)
                                    }
                                    copied++

                                    // 4) 删除原行、原表
                                    if (lines.isNotEmpty()) doPickOrderLineRepository.deleteAll(lines)
                                    doPickOrderRepository.delete(dpo)
                                    deleted++

                                    // 5) 同步更新 delivery_order 状态为 completed
                                    dpo.doOrderId?.let { doId ->
                                        val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId)
                                        if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) {
                                            deliveryOrder.status = DeliveryOrderStatus.COMPLETED
                                            deliveryOrderRepository.save(deliveryOrder)
                                            println(" Updated delivery order $doId to COMPLETED")
                                        }
                                    }
                                }

                                println(" Copied $copied do_pick_order to record and deleted $deleted original(s)")
                            }
                            //  添加：直接使用 Repository 处理 JO pick order（避免循环依赖）
                            if (pickOrder.jobOrder != null) {
                                val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId)
                                joPickOrders.forEach {
                                    it.ticketCompleteTime = LocalDateTime.now() // 记录拣货完成时间
                                    // match_status 保持为 'pending'，等待二次扫描
                                }
                                joPickOrderRepository.saveAll(joPickOrders)

                                val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId)
                                joPickOrderRecords.forEach {
                                    it.ticketCompleteTime = LocalDateTime.now() // 记录拣货完成时间
                                    // match_status 保持为 'pending'，等待二次扫描
                                }
                                joPickOrderRecordRepository.saveAll(joPickOrderRecords)

                                println(" Set jo_pick_order ticketCompleteTime for pick order ${pickOrderId}, waiting for second scan")
                            }
                        }
                    }
                }
                // After updating pick order to COMPLETED status, add:

            }

            return MessageResponse(
                id = savedStockOut.id,
                name = savedStockOut.consoPickOrderCode ?: savedStockOut.deliveryOrderCode,
                code = savedStockOut.consoPickOrderCode ?: savedStockOut.deliveryOrderCode,
                type = savedStockOut.type,
                message = "completed",
                errorPosition = null,
            )
        } else {
            println("❌ Still have unfinished lines, cannot complete")
            return MessageResponse(
                id = stockOut.id,
                name = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode,
                code = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode,
                type = stockOut.type,
                message = "not completed",
                errorPosition = null,
            )
        }
    }

    /**
     * 強制完成 do_pick_order：僅將未完成的 pick_order / pick_order_line 標記為完成（不修改 stock_out_line 數量），
     * 並執行與正常完成相同的歸檔（do_pick_order → do_pick_order_record）與 delivery_order 狀態更新。
     * 僅適用於仍存在于 do_pick_order 表中的單據。
     */
    @Transactional(rollbackFor = [Exception::class])
    open fun forceCompleteDoPickOrder(doPickOrderId: Long): MessageResponse {
        val dpo = doPickOrderRepository.findById(doPickOrderId).orElse(null)
            ?: return MessageResponse(
                id = null,
                name = "Do pick order not found",
                code = "NOT_FOUND",
                type = "doPickOrder",
                message = "do_pick_order not found (may already be archived)",
                errorPosition = null,
            )
        if (dpo.ticketStatus == DoPickOrderStatus.completed) {
            return MessageResponse(
                id = dpo.id,
                name = dpo.ticketNo,
                code = "ALREADY_COMPLETED",
                type = "doPickOrder",
                message = "Do pick order already completed",
                errorPosition = null,
            )
        }
        if (dpo.ticketStatus !in listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released)) {
            return MessageResponse(
                id = dpo.id,
                name = dpo.ticketNo,
                code = "INVALID_STATUS",
                type = "doPickOrder",
                message = "Invalid ticket status for force complete",
                errorPosition = null,
            )
        }

        val dpoLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrderId)
        val pickOrderIds = dpoLines.mapNotNull { it.pickOrderId }.distinct()
        if (pickOrderIds.isEmpty()) {
            return MessageResponse(
                id = dpo.id,
                name = dpo.ticketNo,
                code = "NO_PICK_ORDERS",
                type = "doPickOrder",
                message = "No pick orders linked to this do_pick_order",
                errorPosition = null,
            )
        }

        for (pickOrderId in pickOrderIds) {
            val pols = pickOrderLineRepository.findByPickOrderId(pickOrderId).filter { !it.deleted }
            pols.forEach { pol ->
                pol.status = PickOrderLineStatus.COMPLETED
            }
            if (pols.isNotEmpty()) {
                pickOrderLineRepository.saveAll(pols)
            }
            val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: continue
            pickOrder.status = PickOrderStatus.COMPLETED
            pickOrder.completeDate = LocalDateTime.now()
            pickOrderRepository.save(pickOrder)

            if (pickOrder.jobOrder != null) {
                val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId)
                joPickOrders.forEach { it.ticketCompleteTime = LocalDateTime.now() }
                if (joPickOrders.isNotEmpty()) {
                    joPickOrderRepository.saveAll(joPickOrders)
                }
                val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId)
                joPickOrderRecords.forEach { it.ticketCompleteTime = LocalDateTime.now() }
                if (joPickOrderRecords.isNotEmpty()) {
                    joPickOrderRecordRepository.saveAll(joPickOrderRecords)
                }
            }
        }

        moveDoPickOrderToCompletedRecordAfterForce(dpo)

        return MessageResponse(
            id = dpo.id,
            code = "SUCCESS",
            name = dpo.ticketNo,
            type = "doPickOrder",
            message = "Force completed",
            errorPosition = null,
            entity = null,
        )
    }

    private fun moveDoPickOrderToCompletedRecordAfterForce(dpo: DoPickOrder) {
        val doOrderIdForDelivery = dpo.doOrderId
        val savedHeader = saveDoPickOrderRecordWithDnRetry { deliveryNoteCode ->
            DoPickOrderRecord(
                recordId = dpo.id,
                storeId = dpo.storeId ?: "",
                ticketNo = dpo.ticketNo ?: "",
                ticketStatus = DoPickOrderStatus.completed,
                truckId = dpo.truckId,
                truckDepartureTime = dpo.truckDepartureTime,
                pickOrderId = dpo.pickOrderId,
                doOrderId = dpo.doOrderId,
                ticketReleaseTime = dpo.ticketReleaseTime,
                shopId = dpo.shopId,
                handlerName = dpo.handlerName,
                handledBy = dpo.handledBy,
                ticketCompleteDateTime = LocalDateTime.now(),
                truckLanceCode = dpo.truckLanceCode,
                shopCode = dpo.shopCode,
                shopName = dpo.shopName,
                requiredDeliveryDate = dpo.requiredDeliveryDate,
                pickOrderCode = dpo.pickOrderCode,
                deliveryOrderCode = dpo.deliveryOrderCode,
                deliveryNoteCode = deliveryNoteCode,
                loadingSequence = dpo.loadingSequence,
            )
        }

        val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!)
        val lineRecords = lines.map { l: DoPickOrderLine ->
            DoPickOrderLineRecord().apply {
                this.recordId = l.id
                this.doPickOrderId = savedHeader.recordId
                this.pickOrderId = l.pickOrderId
                this.doOrderId = l.doOrderId
                this.pickOrderCode = l.pickOrderCode
                this.deliveryOrderCode = l.deliveryOrderCode
                this.status = l.status
            }
        }
        if (lineRecords.isNotEmpty()) {
            doPickOrderLineRecordRepository.saveAll(lineRecords)
        }
        if (lines.isNotEmpty()) {
            doPickOrderLineRepository.deleteAll(lines)
        }
        doPickOrderRepository.delete(dpo)

        doOrderIdForDelivery?.let { doId ->
            val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId)
            if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) {
                deliveryOrder.status = DeliveryOrderStatus.COMPLETED
                deliveryOrderRepository.save(deliveryOrder)
            }
        }
    }

    private fun saveDoPickOrderRecordWithDnRetry(
        maxAttempts: Int = 3,
        buildRecord: (String) -> DoPickOrderRecord
    ): DoPickOrderRecord {
        val prefix = "DN"
        repeat(maxAttempts) { attempt ->
            val midfix = LocalDate.now().format(CodeGenerator.DEFAULT_FORMATTER)
            val latestCode = doPickOrderRecordRepository.findLatestDeliveryNoteCodeByPrefix("$prefix-$midfix")
            val deliveryNoteCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode)
            val dpoRecord = buildRecord(deliveryNoteCode)
            try {
                return doPickOrderRecordRepository.save(dpoRecord)
            } catch (e: DataIntegrityViolationException) {
                if (attempt == maxAttempts - 1) {
                    throw e
                }
            }
        }
        throw IllegalStateException("Failed to generate unique delivery note code after $maxAttempts attempts")
    }
 /*
    @Transactional(rollbackFor = [java.lang.Exception::class])
   
    open fun checkAndCompletePickOrderByConsoCode(consoCode: String): MessageResponse {
        try {
         //   println("=== DEBUG: checkAndCompletePickOrderByConsoCode ===")
           // println("consoCode: $consoCode")

            val stockOut = stockOutRepository.findFirstByConsoPickOrderCodeOrderByIdDesc(consoCode)
            if (stockOut == null) {
                //println("❌ No stock_out found for consoCode: $consoCode")
                return MessageResponse(
                    id = null,
                    name = "Stock out not found",
                    code = "ERROR",
                    type = "pickorder",
                    message = "No stock out found for consoCode: $consoCode",
                    errorPosition = null
                )
            }

            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()
            }
            val unfinishedLines = stockOutLines.filter { sol ->
                val status = sol.status?.trim()?.lowercase()
            
                val isComplete =
                    status == "completed" || status == StockOutLineStatus.COMPLETE.status.lowercase()
                val isRejected =
                    status == "rejected" || status == StockOutLineStatus.REJECTED.status.lowercase()
                val isPartiallyComplete =
                    status == "partially_completed" || status == StockOutLineStatus.PARTIALLY_COMPLETE.status.lowercase()
            
                // 只要是你认可的“结束态”，就不算 unfinished
                !(isComplete || isRejected || isPartiallyComplete)
            }

           // println("📊 Stock out lines: ${stockOutLines.size}, Unfinished: ${unfinishedLines.size}")

            if (unfinishedLines.isEmpty()) {
                println(" All stock out lines completed, updating pick order statuses...")
                return completeStockOut(consoCode)
            } else {
                //println("⏳ Still have ${unfinishedLines.size} unfinished lines")
                return MessageResponse(
                    id = stockOut.id,
                    name = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode,
                    code = "NOT_COMPLETED",
                    type = stockOut.type,
                    message = "Pick order not completed yet, ${unfinishedLines.size} lines remaining",
                    errorPosition = null
                )
            }

        } catch (e: Exception) {
            println("❌ Error in checkAndCompletePickOrderByConsoCode: ${e.message}")
            e.printStackTrace()
            return MessageResponse(
                id = null,
                name = "Failed to check pick order completion",
                code = "ERROR",
                type = "pickorder",
                message = "Failed to check pick order completion: ${e.message}",
                errorPosition = null
            )
        }
    }
*/
@Transactional(readOnly = true)
open fun countUnfinishedLinesByConsoCode(consoCode: String): Int {
    val sql = """
        SELECT COUNT(1) AS unfinished_count
        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
          AND LOWER(TRIM(COALESCE(sol.status, ''))) NOT IN (
            'completed',
            'complete',
            'rejected',
            'partially_completed',
            'partially_complete'
          )
    """.trimIndent()
    val rows = jdbcDao.queryForList(sql, mapOf("consoCode" to consoCode))
    val countAny = rows.firstOrNull()?.get("unfinished_count")
    return when (countAny) {
        is Number -> countAny.toInt()
        is String -> countAny.toIntOrNull() ?: 0
        else -> 0
    }
}
@Transactional(rollbackFor = [Exception::class])
open fun checkAndCompletePickOrderByConsoCode(consoCode: String): MessageResponse {
    val unfinished = countUnfinishedLinesByConsoCode(consoCode)
    if (unfinished > 0) {
        return MessageResponse(
            id = null,
            name = consoCode,
            code = "NOT_COMPLETED",
            type = "pickorder",
            message = "Pick order not completed yet, $unfinished lines remaining",
            errorPosition = null
        )
    }
    return completeStockOut(consoCode)
}
    open fun createGroup(name: String, targetDate: LocalDate, pickOrderId: Long?): PickOrderGroup {
        val group = PickOrderGroup().apply {
            this.name = name
            this.targetDate = targetDate
            this.pickOrderId = pickOrderId
        }
        return pickOrderGroupRepository.save(group)
    }

    fun getGroupsByPickOrderId(pickOrderId: Long): List<PickOrderGroup> {
        return pickOrderGroupRepository.findByPickOrderIdAndDeletedIsFalse(pickOrderId)
    }



    open fun allPickOrdersGroup(): List<PickOrderGroupInfo> {
        return pickOrderGroupRepository.findAllByDeletedIsFalse()
    }

    open fun getLatestGroupNameAndCreate(): MessageResponse {
        // Use the working repository method like the old /latest endpoint
        val allGroups = allPickOrdersGroup()
        val nextGroupName = if (allGroups.isNotEmpty()) {
            // Sort by numeric part of the name (A001 -> 1, A006 -> 6)
            val latestName = allGroups
                .filter { it.name?.startsWith("A") == true }
                .maxByOrNull { group ->
                    group.name?.substring(1)?.toIntOrNull() ?: 0
                }?.name ?: "A000"

            // Generate next group name
            val currentNumber = latestName.substring(1).toIntOrNull() ?: 0
            val nextNumber = currentNumber + 1
            "A${nextNumber.toString().padStart(3, '0')}"
        } else {
            "A001"  // If no groups exist, return A001
        }

        // Use the new flexible createNewGroups method
        val request = SavePickOrderGroupRequest(
            names = listOf(nextGroupName),  // Create new group with the generated name
            targetDate = null,
            pickOrderId = null
        )

        return createNewGroups(request)
    }

    fun getLatestGroupName(): String {
        // Use allPickOrdersGroup() instead of the failing repository method
        val allGroups = allPickOrdersGroup()
        return if (allGroups.isNotEmpty()) {
            // Sort by numeric part of the name (A001 -> 1, A006 -> 6)
            val latestName = allGroups
                .filter { it.name?.startsWith("A") == true }
                .maxByOrNull { group ->
                    group.name?.substring(1)?.toIntOrNull() ?: 0
                }?.name ?: "A000"

            // Generate next group name
            val currentNumber = latestName.substring(1).toIntOrNull() ?: 0
            val nextNumber = currentNumber + 1
            "A${nextNumber.toString().padStart(3, '0')}"
        } else {
            "A001"  // If no groups exist, return A001
        }
    }

    open fun createNewGroups(request: SavePickOrderGroupRequest): MessageResponse {
        val updatedGroups = mutableListOf<PickOrderGroup>()
        val createdGroups = mutableListOf<PickOrderGroup>()

        // Case 1: Update existing groups by IDs
        request.groupIds?.forEach { groupId ->
            val group = pickOrderGroupRepository.findById(groupId).orElse(null)
            if (group != null) {
                group.targetDate = request.targetDate
                group.pickOrderId = request.pickOrderId
                val savedGroup = pickOrderGroupRepository.save(group)
                updatedGroups.add(savedGroup)
            }
        }

        // Case 2: Create new groups by names
        request.names?.forEach { name ->
            val group = PickOrderGroup().apply {
                this.name = name
                this.targetDate = request.targetDate
                this.pickOrderId = request.pickOrderId
            }
            val savedGroup = pickOrderGroupRepository.save(group)
            createdGroups.add(savedGroup)
        }

        val totalGroups = updatedGroups.size + createdGroups.size
        val allGroups = updatedGroups + createdGroups

        return MessageResponse(
            id = allGroups.firstOrNull()?.id,
            name = allGroups.map { it.name ?: "" }.joinToString(", "),
            code = "groups_processed",
            type = "pick_order_groups",
            message = "Updated ${updatedGroups.size} groups and created ${createdGroups.size} groups successfully",
            errorPosition = "",
        )
    }

    //  添加缺失的方法：从 pick order line ID 获取 pick order ID
    private fun getPickOrderIdFromPickOrderLineId(pickOrderLineId: Long): Long? {
        val sql = """
        SELECT po.id as pickOrderId
        FROM fpsmsdb.pick_order_line pol
        JOIN fpsmsdb.pick_order po ON po.id = pol.poId
        WHERE pol.id = :pickOrderLineId
        """.trimIndent()

        val result = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to pickOrderLineId))

        return if (result.isNotEmpty()) {
            (result[0]["pickOrderId"] as Number).toLong()
        } else {
            null
        }
    }

    //  添加检查是否需要 resuggest 的方法
    private fun checkIfNeedsResuggest(pickOrderLineId: Long): Boolean {
        println("    checkIfNeedsResuggest called with pickOrderLineId: $pickOrderLineId")

        //  首先执行一个调试查询来查看实际的数值
        val debugSql = """
        SELECT 
            spl.id as suggestedPickLotId,
            spl.qty as suggestedQty,
            ill.inQty,
            ill.outQty,
            ill.holdQty,
            (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as baseAvailableQty,
            sales_iu.ratioN,
            sales_iu.ratioD,
--            CASE 
--                WHEN sales_iu.ratioN IS NOT NULL AND sales_iu.ratioD IS NOT NULL AND sales_iu.ratioD != 0 
--                THEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) / (sales_iu.ratioN / sales_iu.ratioD)
--                ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
--            END as convertedAvailableQty
            COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0) as convertedAvailableQty
        FROM fpsmsdb.suggested_pick_lot spl
        JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId
        JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
        LEFT JOIN fpsmsdb.item_uom sales_iu ON sales_iu.itemId = il.itemId AND sales_iu.salesUnit = true AND sales_iu.deleted = false
        WHERE spl.pickOrderLineId = :pickOrderLineId
        """.trimIndent()

        println("    Debug SQL: $debugSql")
        val debugResult = jdbcDao.queryForList(debugSql, mapOf("pickOrderLineId" to pickOrderLineId))
        println("    Debug result:")
        debugResult.forEach { row ->
            println("  SuggestedPickLotId: ${row["suggestedPickLotId"]}")
            println("  SuggestedQty: ${row["suggestedQty"]}")
            println("  InQty: ${row["inQty"]}")
            println("  OutQty: ${row["outQty"]}")
            println("  HoldQty: ${row["holdQty"]}")
            println("  BaseAvailableQty: ${row["baseAvailableQty"]}")
            println("  RatioN: ${row["ratioN"]}")
            println("  RatioD: ${row["ratioD"]}")
            println("  ConvertedAvailableQty: ${row["convertedAvailableQty"]}")
            //  修复：正确处理 Number 类型转换
            val convertedAvailableQty = (row["convertedAvailableQty"] as Number).toDouble()
            val suggestedQty = (row["suggestedQty"] as Number).toDouble()
            println("  Is Insufficient: ${convertedAvailableQty < suggestedQty}")
            println("  ---")
        }

        val checkSql = """
        SELECT 
            COUNT(*) as totalSuggestions,
            COUNT(CASE WHEN ill.status != 'available' THEN 1 END) as unavailableLots,
            COUNT(CASE WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 1 END) as expiredLots,
            --  修复：使用销售单位进行比较
--            COUNT(CASE WHEN 
--                CASE 
--                    WHEN sales_iu.ratioN IS NOT NULL AND sales_iu.ratioD IS NOT NULL AND sales_iu.ratioD != 0 
--                    THEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) / (sales_iu.ratioN / sales_iu.ratioD)
--                    ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
--                END < spl.qty 
--            THEN 1 END) as insufficientStockLots,
            COUNT(CASE WHEN 
                (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) < spl.qty 
            THEN 1 END) as insufficientStockLots,
            COUNT(CASE WHEN sol.status IN ('rejected', 'lot-change', 'determine1') THEN 1 END) as problematicStockOutLines,
            --  检查单位不一致问题
            COUNT(CASE WHEN spl.qty > pol.qty * 100 THEN 1 END) as unitMismatchLots
        FROM fpsmsdb.suggested_pick_lot spl
        JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId
        JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
        JOIN fpsmsdb.pick_order_line pol ON pol.id = spl.pickOrderLineId
        --  添加销售单位转换
        LEFT JOIN fpsmsdb.item_uom sales_iu ON sales_iu.itemId = il.itemId AND sales_iu.salesUnit = true AND sales_iu.deleted = false
        LEFT JOIN fpsmsdb.stock_out_line sol ON sol.inventoryLotLineId = ill.id 
            AND sol.pickOrderLineId = spl.pickOrderLineId
        WHERE spl.pickOrderLineId = :pickOrderLineId
        """.trimIndent()

        //println("    Executing SQL: $checkSql")
      //  println("    With parameters: pickOrderLineId = $pickOrderLineId")

        val result = jdbcDao.queryForList(checkSql, mapOf("pickOrderLineId" to pickOrderLineId))

        println("    SQL result size: ${result.size}")

        if (result.isNotEmpty()) {
            val row = result[0]
            val totalSuggestions = (row["totalSuggestions"] as Number).toInt()
            val unavailableLots = (row["unavailableLots"] as Number).toInt()
            val expiredLots = (row["expiredLots"] as Number).toInt()
            val insufficientStockLots = (row["insufficientStockLots"] as Number).toInt()
            val problematicStockOutLines = (row["problematicStockOutLines"] as Number).toInt()
            val unitMismatchLots = (row["unitMismatchLots"] as Number).toInt()

            println("=== Resuggest Check ===")
            println("Total suggestions: $totalSuggestions")
            println("Unavailable lots: $unavailableLots")
            println("Expired lots: $expiredLots")
            println("Insufficient stock lots: $insufficientStockLots")
            println("Problematic stock out lines: $problematicStockOutLines")
            println("Unit mismatch lots: $unitMismatchLots")

            // 检查是否需要 resuggest 的条件
            val needsResuggest = (
                    unavailableLots > 0 ||
                            expiredLots > 0 ||
                            insufficientStockLots > 0 ||
                            problematicStockOutLines > 0 ||
                            unitMismatchLots > 0 ||  //  单位不一致检查
                            totalSuggestions == 0  // 没有建议也需要 resuggest
                    )

            println("Needs resuggest: $needsResuggest")
            return needsResuggest
        } else {
            println("    No results returned from SQL query")
        }

        return false
    }


    // Add this new method to PickOrderService.kt
    @Transactional(rollbackFor = [java.lang.Exception::class])
    open fun autoAssignAndReleasePickOrderByStoreAndTicket(
        storeId: String,
        ticketNo: String,
        userId: Long
    ): MessageResponse {
        try {
            println("=== DEBUG: autoAssignAndReleasePickOrderByStoreAndTicket ===")
            println("storeId: $storeId, ticketNo: $ticketNo, userId: $userId")

            val zero = BigDecimal.ZERO
            val releasedBy = SecurityUtils.getUser().getOrNull()
            val user = userService.find(userId).orElse(null)

            if (user == null) {
                return MessageResponse(
                    id = null,
                    name = "User not found",
                    code = "ERROR",
                    type = "pickorder",
                    message = "User with ID $userId not found",
                    errorPosition = null
                )
            }

            // Find the do_pick_order by store_id and ticket_no
            val sql = """
            SELECT DISTINCT dpo.pick_order_id AS pickOrderId
            FROM do_pick_order dpo
            WHERE dpo.deleted = false
              AND dpo.store_id = :storeId
              AND dpo.ticket_no = :ticketNo
              AND dpo.pick_order_id IS NOT NULL
        """.trimIndent()

            val idRows = jdbcDao.queryForList(sql, mapOf("storeId" to storeId, "ticketNo" to ticketNo))
            val pickOrderIdsByTicket = idRows.mapNotNull { row ->
                when (val id = row["pickOrderId"]) {
                    is Number -> id.toLong()
                    is String -> id.toLongOrNull()
                    else -> null
                }
            }.toSet()

            println("Candidate pickOrderIds by ticket $ticketNo: $pickOrderIdsByTicket")

            if (pickOrderIdsByTicket.isEmpty()) {
                return MessageResponse(
                    id = null,
                    name = "No pick orders found",
                    code = "NO_ORDERS",
                    type = "pickorder",
                    message = "No pick orders found for store $storeId with ticket $ticketNo",
                    errorPosition = null
                )
            }

            // Check if user already has pick orders in progress
            val userExistingOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
                user,
                listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED)
            ).filter { it.deliveryOrder != null }

            if (userExistingOrders.isNotEmpty()) {
                println("    DEBUG: User $userId already has ${userExistingOrders.size} pick orders in progress")
                return MessageResponse(
                    id = null,
                    name = "User already has pick orders",
                    code = "USER_BUSY",
                    type = "pickorder",
                    message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.",
                    errorPosition = null,
                    entity = mapOf(
                        "existingOrders" to userExistingOrders.map {
                            mapOf(
                                "id" to it.id,
                                "code" to it.code,
                                "status" to it.status?.value
                            )
                        }
                    )
                )
            }

            // Find available pick orders for the specific ticket
            val availablePickOrders = pickOrderRepository
                .findAll()
                .filter { it.id != null && pickOrderIdsByTicket.contains(it.id!!) }
                .filter { it.deliveryOrder != null }
                .filter { it.assignTo == null }
                .filter { it.status == PickOrderStatus.PENDING || it.status == PickOrderStatus.RELEASED }
                .sortedBy { it.targetDate }
                .take(1) // Take only one pick order

            if (availablePickOrders.isEmpty()) {
                return MessageResponse(
                    id = null,
                    name = "No available pick orders",
                    code = "NO_ORDERS",
                    type = "pickorder",
                    message = "No unassigned pick orders available for store $storeId with ticket $ticketNo",
                    errorPosition = null
                )
            }

            val selected = availablePickOrders.first()
            val currUser = SecurityUtils.getUser().orElseThrow()

            // If still PENDING, perform full release flow
            if (selected.status == PickOrderStatus.PENDING) {
                val newConsoCode = assignConsoCode()

                val stockOut = StockOut().apply {
                    this.type = "job"
                    this.consoPickOrderCode = newConsoCode
                    this.status = StockOutStatus.PENDING.status
                    this.handler = currUser.id
                }
                val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

                selected.apply {
                    this.releasedBy = releasedBy
                    status = PickOrderStatus.RELEASED
                    this.consoCode = newConsoCode
                }

                val suggestions = suggestedPickLotService.suggestionForPickOrders(
                    SuggestedPickLotForPoRequest(pickOrders = listOf(selected))
                )
                val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
                pickOrderRepository.saveAndFlush(selected)

                val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
                    saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
                )
                saveSuggestedPickLots.forEach { lot ->
                    val lotLineId = lot.suggestedLotLine?.id
                    if (lotLineId != null) {
                        val idx = inventoryLotLines.indexOf(lot.suggestedLotLine)
                        if (idx >= 0) {
                            val currentHold = inventoryLotLines[idx].holdQty ?: zero
                            val addHold = lot.qty ?: zero
                            inventoryLotLines[idx].holdQty = currentHold.plus(addHold)
                        }
                    }
                }
                inventoryLotLineRepository.saveAll(inventoryLotLines)

                // Pre-create stock out lines
                var precreated = 0
                saveSuggestedPickLots.forEach { lot ->
                    val polId = lot.pickOrderLine?.id
                    val illId = lot.suggestedLotLine?.id
                    if (polId != null && illId != null) {
                        val existing = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
                            polId,
                            illId
                        )
                        if (existing.isEmpty()) {
                            val pol = pickOrderLineRepository.findById(polId).orElse(null)
                            val ill = inventoryLotLineRepository.findById(illId).orElse(null)
                            if (pol != null && ill != null) {
                                val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply {
                                    this.stockOut = savedStockOut
                                    this.pickOrderLine = pol
                                    this.inventoryLotLine = ill
                                    this.item = pol.item
                                    this.status =
                                        com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status
                                    this.qty = 0.0
                                    this.type = "NOR"
                                }
                                stockOutLIneRepository.save(line)
                                precreated++
                            }
                        }
                    }
                }
                println("Precreated $precreated stock out lines for ticket $ticketNo")
            }

            // Assign user (both pending->released and already released)
            selected.assignTo = user
            pickOrderRepository.saveAndFlush(selected)

            // Update do_pick_order_record entries
            val existingRecords = doPickOrderRecordRepository.findByPickOrderId(selected.id!!)
            println("    DEBUG: Found ${existingRecords.size} existing DoPickOrderRecord entries for pick order ${selected.id}")

            if (existingRecords.isNotEmpty()) {
                existingRecords.forEach { record ->
                    record.handledBy = user.id
                    record.ticketStatus = DoPickOrderStatus.released
                    record.ticketReleaseTime = LocalDateTime.now()  //  添加这行
                    println("    DEBUG: Updating existing DoPickOrderRecord ID: ${record.id} - handledBy: ${user.id}, status: released")
                }
                doPickOrderRecordRepository.saveAll(existingRecords)
                println(" Updated ${existingRecords.size} existing DoPickOrderRecord entries")
            }

            doPickOrderService.updateHandledByForPickOrder(selected.id!!, user.id!!)
            println(" Updated DoPickOrder handledBy to user $userId for pick order ${selected.id}")

            return MessageResponse(
                id = null,
                name = "Pick order assigned by ticket",
                code = "SUCCESS",
                type = "pickorder",
                message = "Assigned to user $userId for store $storeId with ticket $ticketNo",
                errorPosition = null,
                entity = mapOf(
                    "pickOrderIds" to listOf(selected.id!!),
                    "storeId" to storeId,
                    "ticketNo" to ticketNo,
                    "status" to selected.status?.value
                )
            )

        } catch (e: Exception) {
            e.printStackTrace()
            return MessageResponse(
                id = null,
                name = "Failed to auto-assign by ticket",
                code = "ERROR",
                type = "pickorder",
                message = "Failed to auto-assign by ticket: ${e.message}",
                errorPosition = null
            )
        }
    }

    open fun getPickOrderDetailsOptimized(pickOrderIds: List<Long>): GetPickOrderInfoResponse {
        val today = LocalDate.now()
        val zero = BigDecimal.ZERO

        if (pickOrderIds.isEmpty()) {
            return GetPickOrderInfoResponse(
                consoCode = null,
                pickOrders = emptyList(),
                items = emptyList()
            )
        }

        val pickOrderIdsStr = pickOrderIds.joinToString(",")

        val sql = """
            SELECT 
                -- Pick Order Information
                po.id as pickOrderId,
                po.code as pickOrderCode,
            po.consoCode as pickOrderConsoCode,
                po.targetDate as pickOrderTargetDate,
                po.type as pickOrderType,
                po.status as pickOrderStatus,
                po.assignTo as pickOrderAssignTo,
                
                -- Pick Order Line Information
                pol.id as pickOrderLineId,
                pol.qty as pickOrderLineRequiredQty,
            pol.status as pickOrderLineStatus,
                
                -- Item Information
                i.id as itemId,
                i.code as itemCode,
                i.name as itemName,
                uc.code as uomCode,
                uc.udfudesc as uomDesc,
                
                --  Calculate total picked quantity from stock_out_line table
                COALESCE((
                    SELECT SUM(sol_picked.qty) 
                    FROM fpsmsdb.stock_out_line sol_picked 
                    WHERE sol_picked.pickOrderLineId = pol.id 
                    AND sol_picked.deleted = false
                    AND sol_picked.status IN ('completed', 'COMPLETE', 'partially_completed','rejected')
                ), 0) as totalPickedQty,
                
                --  Calculate available quantity from inventory
                COALESCE((
                    SELECT SUM(
                        COALESCE(inv.onHandQty, 0)
                        - COALESCE(inv.onHoldQty, 0)
                        - COALESCE(inv.unavailableQty, 0)
                    )
                    FROM fpsmsdb.inventory inv
                    JOIN fpsmsdb.item_uom iu
                        ON iu.itemId = inv.itemId
                        AND iu.uomId = inv.uomId
                        AND iu.baseUnit = 1
                        AND iu.deleted = false
                    WHERE inv.itemId = i.id
                    AND inv.deleted = false
            ), 0) as availableQty,
            
            --  Check if all stock out lines for this pick order line are completed
            CASE 
                WHEN EXISTS (
                    SELECT 1 
                    FROM fpsmsdb.stock_out_line sol_check 
                    WHERE sol_check.pickOrderLineId = pol.id 
                    AND sol_check.deleted = false
                    AND sol_check.status NOT IN ('completed', 'COMPLETE')
                ) THEN false
                WHEN EXISTS (
                    SELECT 1 
                    FROM fpsmsdb.stock_out_line sol_check 
                    WHERE sol_check.pickOrderLineId = pol.id 
                    AND sol_check.deleted = false
                ) THEN true
                ELSE false
            END as allLotsCompleted
                
            FROM fpsmsdb.pick_order po
            JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
            JOIN fpsmsdb.items i ON i.id = pol.itemId
            LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
            WHERE po.deleted = false 
            AND po.id IN ($pickOrderIdsStr)
            AND pol.deleted = false
            AND LOWER(po.status) = 'released'
        AND LOWER(po.type) NOT IN ('do', 'jo')  --  排除 do 和 jo 类型
        --  Only include lines that have incomplete stock out lines
        AND (
            NOT EXISTS (
                SELECT 1 
                FROM fpsmsdb.stock_out_line sol_all_complete
                WHERE sol_all_complete.pickOrderLineId = pol.id 
                AND sol_all_complete.deleted = false
            )
            OR EXISTS (
                SELECT 1 
                FROM fpsmsdb.stock_out_line sol_incomplete
                WHERE sol_incomplete.pickOrderLineId = pol.id 
                AND sol_incomplete.deleted = false
                AND sol_incomplete.status NOT IN ('completed', 'COMPLETE')
            )
        )
            
            ORDER BY 
                po.code ASC,
                i.code ASC
        """.trimIndent()

      // println("    Executing optimized SQL: $sql")

        val result = jdbcDao.queryForList(sql, emptyMap<String, Any>())

        println(" Optimized result count: ${result.size}")

        //  Transform the flat data into the expected nested structure
        val pickOrdersMap = mutableMapOf<Long, GetPickOrderInfo>()
        var consoCode: String? = null  //  用于存储 consoCode

        result.forEach { row ->
            val pickOrderId = (row["pickOrderId"] as Number).toLong()
            val pickOrderLineId = (row["pickOrderLineId"] as Number).toLong()

            //  获取 consoCode（所有行应该有相同的 consoCode）
            if (consoCode == null) {
                consoCode = row["pickOrderConsoCode"] as String?
            }

            // Create or get pick order
            if (!pickOrdersMap.containsKey(pickOrderId)) {
                pickOrdersMap[pickOrderId] = GetPickOrderInfo(
                    id = pickOrderId,
                    code = row["pickOrderCode"] as String,
                    consoCode = row["pickOrderConsoCode"] as String?,
                    targetDate = row["pickOrderTargetDate"] as LocalDateTime?, //  Convert to LocalDateTime
                    type = row["pickOrderType"] as String,
                    status = row["pickOrderStatus"] as String,
                    assignTo = (row["pickOrderAssignTo"] as Number?)?.toLong(),
                    groupName = "No Group",
                    pickOrderLines = mutableListOf()
                )
            }

            val pickOrder = pickOrdersMap[pickOrderId]!!

            // Create pick order line
            val pickOrderLine = GetPickOrderLineInfo(
                id = pickOrderLineId,
                itemId = (row["itemId"] as Number).toLong(),
                itemCode = row["itemCode"] as String,
                itemName = row["itemName"] as String,
                availableQty = (row["availableQty"] as Number).toDouble().toBigDecimal(), //  Convert to BigDecimal
                requiredQty = (row["pickOrderLineRequiredQty"] as Number).toDouble()
                    .toBigDecimal(), //  Convert to BigDecimal
                uomCode = row["uomCode"] as String?,
                uomDesc = row["uomDesc"] as String?,
                suggestedList = emptyList(),
                pickedQty = (row["totalPickedQty"] as Number).toDouble().toBigDecimal() //  Convert to BigDecimal
            )

            //  Fix the add method call
            (pickOrder.pickOrderLines as MutableList).add(pickOrderLine)
        }

        return GetPickOrderInfoResponse(
            consoCode = consoCode, //  使用获取到的 consoCode
            pickOrders = pickOrdersMap.values.toList(),
            items = emptyList()
        )
    }

    open fun getPickOrderDetailsOptimizedByUser(userId: Long): GetPickOrderInfoResponse {
        // Fetch pre-filtered IDs directly from DB to avoid full-table scan.
        val releasedPickOrderIds = pickOrderRepository.findReleasedIdsForPickExecution(
            userId,
            PickOrderStatus.RELEASED,
            listOf(PickOrderType.DELIVERY_ORDER, PickOrderType.JOB_ORDER)
        )

        if (releasedPickOrderIds.isEmpty()) {
            return GetPickOrderInfoResponse(
                consoCode = null,
                pickOrders = emptyList(),
                items = emptyList()
            )
        }

        // Use the existing optimized method
        return getPickOrderDetailsOptimized(releasedPickOrderIds)
    }


// ... existing code ...

    @Transactional(rollbackFor = [java.lang.Exception::class])
    open fun autoAssignAndReleasePickOrder(userId: Long): MessageResponse {
        try {
            println("=== DEBUG: autoAssignAndReleasePickOrder ===")
            println("userId: $userId")

            val zero = BigDecimal.ZERO
            val releasedBy = SecurityUtils.getUser().getOrNull()
            val user = userService.find(userId).orElse(null)

            if (user == null) {
                println("❌ User not found: $userId")
                return MessageResponse(
                    id = null,
                    name = "User not found",
                    code = "ERROR",
                    type = "pickorder",
                    message = "User with ID $userId not found",
                    errorPosition = null
                )
            }

            println(" User found: ${user.name} (ID: ${user.id})")

            // Check if user already has pending/released pick orders that are not completed
            val existingPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
                user,
                listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED)
            )

            println("📋 Found ${existingPickOrders.size} existing pick orders for user")

            // Filter out completed pick orders by checking stock_out status
            val activePickOrders = existingPickOrders.filter { pickOrder ->
                //  Fix: Store consoCode in local variable to avoid smart cast issues
                val consoCode = pickOrder.consoCode
                if (consoCode == null) {
                    println("    Checking pick order ${pickOrder.code} (consoCode: null)")
                    println("    No consoCode - considering as active")
                    true // No consoCode means not completed
                } else {
                    val stockOut = stockOutRepository.findFirstByConsoPickOrderCodeOrderByIdDesc(consoCode)
                    println(" Checking pick order ${pickOrder.code} (consoCode: $consoCode)")

                    if (stockOut == null) {
                        println("    No stock_out record found - considering as active")
                        true // No stock_out record means not completed
                    } else {
                        println("  📦 Stock_out status: ${stockOut.status}")
                        val isActive = stockOut.status != StockOutStatus.COMPLETE.status
                        println("  ${if (isActive) "🟡 Active" else " Completed"}")
                        isActive
                    }
                }
            }

            println("   Active pick orders: ${activePickOrders.size}")

            if (activePickOrders.isNotEmpty()) {
                // User already has active pick orders, return existing ones
                println("ℹ️ User already has active pick orders")
                return MessageResponse(
                    id = null,
                    name = "User already has active pick orders",
                    code = "EXISTS",
                    type = "pickorder",
                    message = "User already has ${activePickOrders.size} active pick order(s)",
                    errorPosition = null,
                    entity = mapOf(
                        "pickOrderIds" to activePickOrders.map { it.id!! },
                        "hasActiveOrders" to true
                    )
                )
            }

            // Find available pick orders (prioritize those with doId)
            val availablePickOrders = pickOrderRepository.findAllByStatusAndDeletedFalse(PickOrderStatus.PENDING)
                .filter { it.deliveryOrder != null } // Only pick orders with doId (delivery order)
                .sortedBy { it.targetDate } // Sort by target date (earliest first)
                .take(1) // Take only one pick order for now

            println("📋 Available pick orders with doId: ${availablePickOrders.size}")

            if (availablePickOrders.isEmpty()) {
                println("❌ No available pick orders with doId found")
                return MessageResponse(
                    id = null,
                    name = "No available pick orders",
                    code = "NO_ORDERS",
                    type = "pickorder",
                    message = "No pending pick orders with delivery orders available for assignment",
                    errorPosition = null,
                    entity = mapOf("hasActiveOrders" to false)
                )
            }

            println(" Found available pick order: ${availablePickOrders.first().code}")

            //  Add the complete assignment logic here
            val availablePickOrder = availablePickOrders.first()

            // Generate consoCode early and save immediately
            val newConsoCode = assignConsoCode()
            val currUser = SecurityUtils.getUser().orElseThrow()

            // Create and save StockOut immediately to prevent duplicate consoCodes
            val stockOut = StockOut().apply {
                this.type = "job"
                this.consoPickOrderCode = newConsoCode
                this.status = StockOutStatus.PENDING.status
                this.handler = currUser.id
            }
            val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

            // Assign and release the pick order
            availablePickOrder.apply {
                this.releasedBy = releasedBy
                status = PickOrderStatus.RELEASED
                this.assignTo = user
                this.consoCode = newConsoCode
            }

            val suggestions = suggestedPickLotService.suggestionForPickOrders(
                SuggestedPickLotForPoRequest(pickOrders = listOf(availablePickOrder))
            )

            val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)

            pickOrderRepository.saveAndFlush(availablePickOrder)

            val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
                saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
            )

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

            inventoryLotLineRepository.saveAll(inventoryLotLines)

            println(" Successfully assigned pick order ${availablePickOrder.code}")

            return MessageResponse(
                id = null,
                name = "Pick order automatically assigned and released",
                code = "SUCCESS",
                type = "pickorder",
                message = "Pick order ${availablePickOrder.code} assigned and released to user ${user.name}",
                errorPosition = null,
                entity = mapOf(
                    "consoCode" to newConsoCode,
                    "pickOrderIds" to listOf(availablePickOrder.id!!),
                    "hasActiveOrders" to false
                )
            )

        } catch (e: Exception) {
            println("❌ Error in autoAssignAndReleasePickOrder: ${e.message}")
            e.printStackTrace()
            return MessageResponse(
                id = null,
                name = "Failed to auto-assign pick order",
                code = "ERROR",
                type = "pickorder",
                message = "Failed to auto-assign pick order: ${e.message}",
                errorPosition = null
            )
        }
    }

    open fun checkPickOrderCompletion(userId: Long): MessageResponse {
        try {
            val user = userService.find(userId).orElse(null)

            if (user == null) {
                return MessageResponse(
                    id = null,
                    name = "User not found",
                    code = "ERROR",
                    type = "pickorder",
                    message = "User with ID $userId not found",
                    errorPosition = null
                )
            }

            // Get all pick orders assigned to this user
            val userPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
                user,
                listOf(PickOrderStatus.ASSIGNED, PickOrderStatus.RELEASED)
            )

            val completionStatus = userPickOrders.map { pickOrder ->
                //  Fix: Store consoCode in local variable to avoid smart cast issues
                val consoCode = pickOrder.consoCode
                val stockOut = if (consoCode != null) {
                    stockOutRepository.findByConsoPickOrderCode(consoCode).orElse(null)
                } else {
                    null
                }

                val stockOutLines = if (stockOut != null) {
                    stockOutLIneRepository.findAllByStockOutId(stockOut.id!!)
                } else {
                    emptyList()
                }

                val unfinishedLines = stockOutLines.filter { sol ->
                    val status = sol.status?.trim()?.lowercase()
                
                    val isComplete =
                        status == "completed" || status == StockOutLineStatus.COMPLETE.status.lowercase()
                    val isRejected =
                        status == "rejected" || status == StockOutLineStatus.REJECTED.status.lowercase()
                    val isPartiallyComplete =
                        status == "partially_completed" || status == StockOutLineStatus.PARTIALLY_COMPLETE.status.lowercase()
                
                    // 只要是结束态，就不算 unfinished
                    !(isComplete || isRejected || isPartiallyComplete)
                }
                println("📊 Stock out lines: ${stockOutLines.size}, Unfinished: ${unfinishedLines.size}")

                mapOf(
                    "pickOrderId" to pickOrder.id,
                    "pickOrderCode" to pickOrder.code,
                    "consoCode" to consoCode,
                    "isCompleted" to (unfinishedLines.isEmpty() && stockOut != null),
                    "stockOutStatus" to (stockOut?.status ?: "not_created"),
                    "totalLines" to stockOutLines.size,
                    "unfinishedLines" to unfinishedLines.size
                )
            }

            val completedOrders = completionStatus.filter { it["isCompleted"] == true }
            val hasCompletedOrders = completedOrders.isNotEmpty()

            return MessageResponse(
                id = null,
                name = "Pick order completion status checked",
                code = "SUCCESS",
                type = "pickorder",
                message = "Found ${completedOrders.size} completed orders out of ${userPickOrders.size} total",
                errorPosition = null,
                entity = mapOf(
                    "hasCompletedOrders" to hasCompletedOrders,
                    "completedOrders" to completedOrders,
                    "allOrders" to completionStatus
                )
            )

        } catch (e: Exception) {
            return MessageResponse(
                id = null,
                name = "Failed to check pick order completion",
                code = "ERROR",
                type = "pickorder",
                message = "Failed to check pick order completion: ${e.message}",
                errorPosition = null
            )
        }
    }

    open fun getAllPickOrderLotsWithDetailsWithAutoAssign(userId: Long): List<Map<String, Any>> {
        println("=== Debug: getAllPickOrderLotsWithDetailsWithAutoAssign ===")
        println("today: ${LocalDate.now()}")
        println("userId filter: $userId")

        //  First attempt auto-assignment for the user
        println("   Attempting auto-assignment for user $userId")
        val assignedPickOrderResponse = autoAssignAndReleasePickOrder(userId)

        // Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId)
        val user = userService.find(userId).orElse(null)
        if (user == null) {
            println("❌ User not found: $userId")
            return emptyList()
        }

        // Get all pick orders assigned to user with PENDING or RELEASED status that have doId
        val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
            user,
            listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED)

        ).filter { it.deliveryOrder != null } // Only pick orders with doId
        println("    DEBUG: All assigned pick orders: ${allAssignedPickOrders.size}")
        val pickOrderIds = allAssignedPickOrders.map { it.id!! }
        println("    DEBUG: All assigned pick orders: ${pickOrderIds.size}")
        println(" Pick order IDs to fetch: $pickOrderIds")

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

        //  Use SQL query approach similar to getPickOrderLineLotDetails for detailed lot information
        val pickOrderIdsStr = pickOrderIds.joinToString(",")

        val sql = """
            SELECT 
                -- Pick Order Information
                po.id as pickOrderId,
                po.code as pickOrderCode,
                po.consoCode as pickOrderConsoCode,
                DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
                po.type as pickOrderType,
                po.status as pickOrderStatus,
                po.assignTo as pickOrderAssignTo,
                
                -- Pick Order Line Information
                pol.id as pickOrderLineId,
                pol.qty as pickOrderLineRequiredQty,
                pol.status as pickOrderLineStatus,
                
                -- Item Information
                i.id as itemId,
                i.code as itemCode,
                i.name as itemName,
                uc.code as uomCode,
                uc.udfudesc as uomDesc,
                
                -- Lot Information (similar to lot-details endpoint)
                ill.id as lotId,
                il.lotNo,
                DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
                w.name as location,
                COALESCE(uc.udfudesc, 'N/A') as stockUnit,   
                
                --  FIXED: Set quantities to NULL for rejected lots
                CASE 
                    WHEN sol.status = 'rejected' THEN NULL
                    ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
                END as availableQty,
                
                -- Required quantity for this lot
                COALESCE(spl.qty, 0) as requiredQty,
                
                -- Actual picked quantity
                COALESCE(sol.qty, 0) as actualPickQty,
                
                -- Suggested pick lot information
                spl.id as suggestedPickLotId,
                ill.status as lotStatus,
                
                -- Stock out line information
                sol.id as stockOutLineId,
                sol.status as stockOutLineStatus,
                COALESCE(sol.qty, 0) as stockOutLineQty,
                
                -- Additional detailed fields from lot-details
                COALESCE(ill.inQty, 0) as inQty, 
                COALESCE(ill.outQty, 0) as outQty,
                COALESCE(ill.holdQty, 0) as holdQty,
                COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId,
                ill.inventoryLotId as debugInventoryLotId,
                
                -- Calculate total picked quantity by ALL pick orders for this lot
                COALESCE((
                    SELECT SUM(sol_all.qty) 
                    FROM fpsmsdb.stock_out_line sol_all 
                    WHERE sol_all.inventoryLotLineId = ill.id 
                    AND sol_all.deleted = false
                    AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed')
                ), 0) as totalPickedByAllPickOrders,
                
                -- Calculate remaining available quantity correctly
                (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
                
                -- Add detailed debug fields for lotAvailability calculation
                ill.status as debug_ill_status,
                (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) as debug_is_expired,
                sol.status as debug_sol_status,
                (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as debug_remaining_stock,
                (COALESCE(spl.qty, 0) - COALESCE(sol.qty, 0)) as debug_required_after_picked,
                
                -- Lot availability status (same logic as lot-details)
                CASE 
                    -- Check if lot is expired
                    WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
                    -- Check if lot has rejected stock out line for this pick order
                    WHEN sol.status = 'rejected' THEN 'rejected'
                    -- Check if lot is unavailable due to insufficient stock
                    WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
                    -- Check if lot status is unavailable
                    WHEN ill.status = 'unavailable' THEN 'status_unavailable'
                    -- Default to available
                    ELSE 'available'
                END as lotAvailability,
                
                -- Processing status
                CASE 
                    WHEN sol.status = 'completed' THEN 'completed'
                    WHEN sol.status = 'rejected' THEN 'rejected'
                    WHEN sol.status = 'created' THEN 'pending'
                    ELSE 'pending'
                END as processingStatus
                
            FROM fpsmsdb.pick_order po
            JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
            JOIN fpsmsdb.items i ON i.id = pol.itemId
            LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
            LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
            LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id
            LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
            LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
            LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
            WHERE po.deleted = false 
            AND po.id IN ($pickOrderIdsStr)
            AND pol.deleted = false
            AND po.status IN ('PENDING', 'RELEASED')
            AND po.assignTo = :userId
            AND ill.deleted = false
            AND il.deleted = false
            AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
            ORDER BY 
                CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, -- Show rejected lots first
                po.code ASC,
                i.code ASC,
                il.expiryDate ASC, 
                il.lotNo ASC
        """.trimIndent()

        //println("    Executing SQL for all pick order lots with details: $sql")
        println(" With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr")

        val results = jdbcDao.queryForList(sql, mapOf("userId" to userId))
        println(" Total result count: ${results.size}")

        // Filter out lots with null availableQty (rejected lots)
        val filteredResults = results.filter { row ->
            val availableQty = row["availableQty"]
            availableQty != null
        }

        println(" Filtered result count: ${filteredResults.size}")

        val enrichedResults = filteredResults
        return enrichedResults
    }

    open fun getFgPickOrders(): List<Map<String, Any?>> {
        try {
            println("    Starting getFgPickOrders method - using repository")

            // Use repository to find pick orders with type 'do'
            val pickOrders = pickOrderRepository.findAll().filter {
                it.type?.value == "do" &&
                        it.status?.value in listOf("assigned", "released", "picking")
            }

            //println("    Found ${pickOrders.size} FG pick orders using repository")

            val results = pickOrders.map { po ->
                val deliveryOrder = po.deliveryOrder
                val shop = deliveryOrder?.shop
                val supplier = deliveryOrder?.supplier

                //  Get truck information for this shop - handle Optional properly
                val truckInfo = if (shop?.id != null) {
                    try {
                        jdbcDao.queryForMap(
                            """
                            SELECT TruckLanceCode, DepartureTime 
                            FROM truck 
                            WHERE shopId = :shopId AND deleted = false 
                            LIMIT 1
                        """, mapOf("shopId" to shop.id)
                        ).orElse(null)
                    } catch (e: Exception) {
                        println("  No truck found for shop ${shop.id}: ${e.message}")
                        null
                    }
                } else null

                mapOf(
                    "pickOrderId" to (po.id ?: 0L),
                    "pickOrderCode" to (po.code ?: ""),
                    "pickOrderConsoCode" to (po.consoCode ?: ""),
                    "pickOrderTargetDate" to (po.targetDate?.toString() ?: ""),
                    "pickOrderStatus" to (po.status?.value ?: ""),
                    "deliveryOrderId" to (deliveryOrder?.id ?: 0L),
                    "deliveryNo" to (deliveryOrder?.code ?: ""),
                    "deliveryDate" to (deliveryOrder?.orderDate?.toString() ?: ""),
                    "shopId" to (shop?.id ?: 0L),
                    "shopCode" to (shop?.code ?: ""),
                    "shopName" to (shop?.name ?: ""),
                    "shopAddress" to buildShopAddress(shop),
                    "shopPoNo" to (supplier?.code ?: ""),
                    "numberOfCartons" to (po.pickOrderLines.size),
                    "TruckLanceCode" to (truckInfo?.get("TruckLanceCode") ?: ""), //  Add truck number
                    "DepartureTime" to (truckInfo?.get("DepartureTime") ?: ""), //  Add departure time
                    "qrCodeData" to (po.id ?: 0L)
                )
            }

            println(" FG Pick Orders result count: ${results.size}")
            return results

        } catch (e: Exception) {
            println("❌ Error in getFgPickOrders: ${e.message}")
            e.printStackTrace()
            return emptyList()
        }
    }

    open fun getFgPickOrdersByUserId(userId: Long): List<Map<String, Any?>> {
        try {
            println("    Starting getFgPickOrdersByUserId with userId: $userId")

            //  修复：从 do_pick_order_line 获取 pick order 信息
            val sql = """
                SELECT 
                    dpo.id as doPickOrderId,
                    dpo.store_id as storeId,
                    dpo.ticket_no as ticketNo,
                    dpo.TruckLanceCode as truckLanceCode,
                    dpo.truck_departure_time as DepartureTime,
                    dpo.ShopCode as shopCode,
                    dpo.ShopName as shopName,
                    s.id as shopId,
                    s.name as shopNameFromShop,
                    CONCAT_WS(', ', s.addr1, s.addr2, s.addr3, s.addr4, s.district) as shopAddress,
                    --  从 do_pick_order_line 获取所有关联的 pick orders 和 delivery orders
                    GROUP_CONCAT(DISTINCT dpol.pick_order_id ORDER BY dpol.pick_order_id) as pickOrderIds,
                    GROUP_CONCAT(DISTINCT dpol.pick_order_code ORDER BY dpol.pick_order_id SEPARATOR ', ') as pickOrderCodes,
                    GROUP_CONCAT(DISTINCT dpol.do_order_id ORDER BY dpol.do_order_id) as deliveryOrderIds,
                    GROUP_CONCAT(DISTINCT dpol.delivery_order_code ORDER BY dpol.do_order_id SEPARATOR ', ') as deliveryNos,
                    --  获取第一个 pick order 的详细信息（用于兼容性）
                    (SELECT po2.consoCode FROM pick_order po2 WHERE po2.id = MIN(dpol.pick_order_id) LIMIT 1) as pickOrderConsoCode,
                    (SELECT po2.targetDate FROM pick_order po2 WHERE po2.id = MIN(dpol.pick_order_id) LIMIT 1) as pickOrderTargetDate,
                    (SELECT po2.status FROM pick_order po2 WHERE po2.id = MIN(dpol.pick_order_id) LIMIT 1) as pickOrderStatus,
                    (SELECT do2.orderDate FROM delivery_order do2 WHERE do2.id = MIN(dpol.do_order_id) LIMIT 1) as deliveryDate,
                    COUNT(DISTINCT dpol.pick_order_id) as numberOfPickOrders,
                    (SELECT SUM(pol_count.line_count)
                     FROM (
                         SELECT po3.id, COUNT(*) as line_count
                         FROM pick_order po3
                         JOIN pick_order_line pol3 ON pol3.poId = po3.id AND pol3.deleted = false
                         WHERE po3.id IN (SELECT dpol2.pick_order_id FROM do_pick_order_line dpol2 WHERE dpol2.do_pick_order_id = dpo.id AND dpol2.deleted = 0)
                         GROUP BY po3.id
                     ) pol_count
                    ) as numberOfCartons
                FROM do_pick_order dpo
                INNER JOIN do_pick_order_line dpol ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0
                --  JOIN pick_order 以检查用户分配
                INNER JOIN pick_order po ON po.id = dpol.pick_order_id
                LEFT JOIN shop s ON s.id = dpo.shop_id
                WHERE po.assignTo = :userId
                AND po.type = 'do'
                AND po.status IN ('assigned', 'released', 'picking')
                AND po.deleted = false
                AND dpo.deleted = false
                GROUP BY dpo.id, dpo.store_id, dpo.ticket_no, dpo.TruckLanceCode, dpo.truck_departure_time, 
                         dpo.ShopCode, dpo.ShopName, s.id, s.name, s.addr1, s.addr2, s.addr3, s.addr4, s.district
                ORDER BY MIN(po.targetDate) DESC, MIN(po.code) ASC
            """.trimIndent()

           // println("    Executing SQL for FG pick orders by userId: $sql")
           // println("    With parameters: userId = $userId")

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

            if (results.isEmpty()) {
               // println("❌ No active FG pick orders found for user: $userId")
                return emptyList()
            }

          //  println("    Found ${results.size} active FG pick orders for user: $userId")

            //  添加调试信息
            results.forEachIndexed { index, row ->
                println("    DEBUG: Result $index:")
                println("  - doPickOrderId: ${row["doPickOrderId"]}")
                println("  - pickOrderIds: ${row["pickOrderIds"]}")
                println("  - numberOfPickOrders: ${row["numberOfPickOrders"]}")
                println("  - ticketNo: ${row["ticketNo"]}")
            }

            val formattedResults = results.map { row ->
                //  解析 pick order IDs 列表
                val pickOrderIdsStr = row["pickOrderIds"] as? String ?: ""
                val pickOrderIds = if (pickOrderIdsStr.isNotEmpty()) {
                    pickOrderIdsStr.split(",").mapNotNull { it.toLongOrNull() }
                } else {
                    emptyList()
                }

                mapOf(
                    "doPickOrderId" to (row["doPickOrderId"] ?: 0L),
                    "pickOrderIds" to pickOrderIds,  //  返回所有 pick order IDs
                    "pickOrderId" to (pickOrderIds.firstOrNull() ?: 0L),  //  兼容：返回第一个
                    "pickOrderCodes" to (row["pickOrderCodes"] ?: ""),
                    "pickOrderCode" to ((row["pickOrderCodes"] as? String)?.split(", ")?.firstOrNull() ?: ""),  //  兼容
                    "deliveryOrderIds" to (row["deliveryOrderIds"] as? String ?: "").split(",")
                        .mapNotNull { it.toLongOrNull() },
                    "deliveryNos" to (row["deliveryNos"] ?: ""),
                    "pickOrderConsoCode" to (row["pickOrderConsoCode"] ?: ""),
                    "pickOrderTargetDate" to (row["pickOrderTargetDate"]?.toString() ?: ""),
                    "pickOrderStatus" to (row["pickOrderStatus"] ?: ""),
                    "deliveryDate" to (row["deliveryDate"]?.toString() ?: ""),
                    "shopId" to (row["shopId"] ?: 0L),
                    "shopCode" to (row["shopCode"] ?: ""),
                    "shopName" to (row["shopName"] ?: row["shopNameFromShop"] ?: ""),
                    "shopAddress" to (row["shopAddress"] ?: ""),
                    "shopPoNo" to "",
                    "numberOfPickOrders" to (row["numberOfPickOrders"] ?: 0),  //  新增：pick order 数量
                    "numberOfCartons" to (row["numberOfCartons"] ?: 0),
                    "truckLanceCode" to (row["truckLanceCode"] ?: ""),
                    "DepartureTime" to (row["DepartureTime"]?.toString() ?: ""),
                    "ticketNo" to (row["ticketNo"] ?: ""),
                    "storeId" to (row["storeId"] ?: ""),
                    "qrCodeData" to (row["doPickOrderId"] ?: 0L)  //  改为 doPickOrderId
                )
            }

            println(" Built ${formattedResults.size} FG pick orders for user: $userId")
            return formattedResults

        } catch (e: Exception) {
            println("❌ Error in getFgPickOrdersByUserId: ${e.message}")
            e.printStackTrace()
            return emptyList()
        }
    }

    open fun getFgPickOrdersByPickOrderId(pickOrderId: Long): List<Map<String, Any?>> {
        try {
            println("    Starting getFgPickOrdersByPickOrderId method with pickOrderId: $pickOrderId")

            val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)

            if (pickOrder == null) {
                println("❌ Pick order not found with ID: $pickOrderId")
                return emptyList()
            }

            println("    Found pick order: ${pickOrder.code}, type: ${pickOrder.type?.value}, status: ${pickOrder.status?.value}")

            if (pickOrder.type?.value != "do") {
                println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}")
                return emptyList()
            }
            val allowedstatuses = listOf(
                "assigned", "released", "picking"
                //, "completed"
            )
            if (pickOrder.status?.value !in allowedstatuses) {
                println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}")
                return emptyList()
            }

            val deliveryOrder = pickOrder.deliveryOrder
            val shop = deliveryOrder?.shop
            val supplier = deliveryOrder?.supplier

            println(" Delivery order: ${deliveryOrder?.code}, Shop: ${shop?.name}, Supplier: ${supplier?.code}")
            println("    Shop ID: ${shop?.id}")

            //  Get truck information using repository with detailed debugging
            val truck = if (shop?.id != null) {
                try {
                    println("    Querying truck repository for shopId: ${shop.id}")

                    // Get all trucks for this shop
                    val trucksForShop = truckRepository.findByShopIdAndDeletedFalse(shop.id)
                    println("    Trucks for shop ${shop.id}: ${trucksForShop.size}")
                    trucksForShop.forEach { t ->
                        println("  - Truck ID: ${t.id}, truckLanceCode: ${t.truckLanceCode}, ShopId: ${t.shop?.id}, Deleted: ${t.deleted}")
                    }

                    // Take the first truck (or null if none found)
                    val selectedTruck = trucksForShop.firstOrNull()

                    if (selectedTruck != null) {
                        println(" Selected truck: ID=${selectedTruck.id}, truckLanceCode=${selectedTruck.truckLanceCode}, DepartureTime=${selectedTruck.departureTime}")
                    } else {
                        println("❌ No truck found for shopId ${shop.id}")
                    }

                    selectedTruck
                } catch (e: Exception) {
                    println("  Error querying truck repository for shop ${shop.id}: ${e.message}")
                    e.printStackTrace()
                    null
                }
            } else {
                println("  Shop ID is null")
                null
            }
            val ticketNo = try {
                val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
                val ticketNo = doPickOrders.firstOrNull()?.ticketNo ?: ""
                println("    Found ticket number: $ticketNo for pick order $pickOrderId")
                ticketNo
            } catch (e: Exception) {
                println("  Error getting ticket number for pick order $pickOrderId: ${e.message}")
                ""
            }
            val dpoStoreId = try {
                val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
                doPickOrders.firstOrNull()?.storeId ?: ""
            } catch (e: Exception) {
                println("  Error getting storeId for pick order $pickOrderId: ${e.message}")
                ""
            }
            val result = mapOf(
                "pickOrderId" to (pickOrder.id ?: 0L),
                "pickOrderCode" to (pickOrder.code ?: ""),
                "pickOrderConsoCode" to (pickOrder.consoCode ?: ""),
                "pickOrderTargetDate" to (pickOrder.targetDate?.toString() ?: ""),
                "pickOrderStatus" to (pickOrder.status?.value ?: ""),
                "deliveryOrderId" to (deliveryOrder?.id ?: 0L),
                "deliveryNo" to (deliveryOrder?.code ?: ""),
                "deliveryDate" to (deliveryOrder?.orderDate?.toString() ?: ""),
                "shopId" to (shop?.id ?: 0L),
                "shopCode" to (shop?.code ?: ""),
                "shopName" to (shop?.name ?: ""),
                "shopAddress" to buildShopAddress(shop),
                "shopPoNo" to (supplier?.code ?: ""),
                "numberOfCartons" to (pickOrder.pickOrderLines.size),
                "truckLanceCode" to (truck?.truckLanceCode ?: ""), //  Use entity property
                "DepartureTime" to (truck?.departureTime?.toString() ?: ""),
                "ticketNo" to ticketNo,
                "storeId" to dpoStoreId,
                "qrCodeData" to (pickOrder.id ?: 0L)
            )

            println(" FG Pick Orders by ID result count: 1")
            return listOf(result)

        } catch (e: Exception) {
            println("❌ Error in getFgPickOrdersByPickOrderId: ${e.message}")
            e.printStackTrace()
            return emptyList()
        }
    }

    open fun getnewFgPickOrdersByPickOrderId(pickOrderId: Long): List<Map<String, Any?>> {
        try {
            println("    Starting getnewFgPickOrdersByPickOrderId method with pickOrderId: $pickOrderId")

            val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)

            if (pickOrder == null) {
                println("❌ Pick order not found with ID: $pickOrderId")
                return emptyList()
            }

            println("    Found pick order: ${pickOrder.code}, type: ${pickOrder.type?.value}, status: ${pickOrder.status?.value}")

            if (pickOrder.type?.value != "do") {
                println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}")
                return emptyList()
            }
            val allowedstatuses = listOf(
                "assigned", "released", "picking", "completed"
                //, "completed"
            )
            if (pickOrder.status?.value !in allowedstatuses) {
                println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}")
                return emptyList()
            }

            val deliveryOrder = pickOrder.deliveryOrder
            val shop = deliveryOrder?.shop
            val supplier = deliveryOrder?.supplier

            println(" Delivery order: ${deliveryOrder?.code}, Shop: ${shop?.name}, Supplier: ${supplier?.code}")
            println("    Shop ID: ${shop?.id}")

            //  Get truck information using repository with detailed debugging
            val truck = if (shop?.id != null) {
                try {
                    println("    Querying truck repository for shopId: ${shop.id}")

                    // Get all trucks for this shop
                    val trucksForShop = truckRepository.findByShopIdAndDeletedFalse(shop.id)
                    println("    Trucks for shop ${shop.id}: ${trucksForShop.size}")
                    trucksForShop.forEach { t ->
                        println("  - Truck ID: ${t.id}, TruckLanceCode: ${t.truckLanceCode}, ShopId: ${t.shop?.id}, Deleted: ${t.deleted}")
                    }

                    // Take the first truck (or null if none found)
                    val selectedTruck = trucksForShop.firstOrNull()

                    if (selectedTruck != null) {
                        println(" Selected truck: ID=${selectedTruck.id}, TruckLanceCode=${selectedTruck.truckLanceCode}, DepartureTime=${selectedTruck.departureTime}")
                    } else {
                        println("❌ No truck found for shopId ${shop.id}")
                    }

                    selectedTruck
                } catch (e: Exception) {
                    println("  Error querying truck repository for shop ${shop.id}: ${e.message}")
                    e.printStackTrace()
                    null
                }
            } else {
                println("  Shop ID is null")
                null
            }
            val ticketNo = try {
                val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
                val ticketNo = doPickOrders.firstOrNull()?.ticketNo ?: ""
                println("    Found ticket number: $ticketNo for pick order $pickOrderId")
                ticketNo
            } catch (e: Exception) {
                println("  Error getting ticket number for pick order $pickOrderId: ${e.message}")
                ""
            }
            val dpoStoreId = try {
                val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
                doPickOrders.firstOrNull()?.storeId ?: ""
            } catch (e: Exception) {
                println("  Error getting storeId for pick order $pickOrderId: ${e.message}")
                ""
            }
            val result = mapOf(
                "pickOrderId" to (pickOrder.id ?: 0L),
                "pickOrderCode" to (pickOrder.code ?: ""),
                "pickOrderConsoCode" to (pickOrder.consoCode ?: ""),
                "pickOrderTargetDate" to (pickOrder.targetDate?.toString() ?: ""),
                "pickOrderStatus" to (pickOrder.status?.value ?: ""),
                "deliveryOrderId" to (deliveryOrder?.id ?: 0L),
                "deliveryNo" to (deliveryOrder?.code ?: ""),
                "deliveryDate" to (deliveryOrder?.orderDate?.toString() ?: ""),
                "shopId" to (shop?.id ?: 0L),
                "shopCode" to (shop?.code ?: ""),
                "shopName" to (shop?.name ?: ""),
                "shopAddress" to buildShopAddress(shop),
                "shopPoNo" to (supplier?.code ?: ""),
                "numberOfCartons" to (pickOrder.pickOrderLines.size),
                "TruckLanceCode" to (truck?.truckLanceCode ?: ""), //  Use entity property
                "DepartureTime" to (truck?.departureTime?.toString() ?: ""),
                "ticketNo" to ticketNo,
                "storeId" to dpoStoreId,
                "qrCodeData" to (pickOrder.id ?: 0L)
            )

            println(" FG Pick Orders by ID result count: 1")
            return listOf(result)

        } catch (e: Exception) {
            println("❌ Error in getFgPickOrdersByPickOrderId: ${e.message}")
            e.printStackTrace()
            return emptyList()
        }
    }

    private fun buildShopAddress(shop: com.ffii.fpsms.modules.master.entity.Shop?): String {
        if (shop == null) return ""

        val addressParts = mutableListOf<String>()

        shop.addr1?.let { if (it.isNotBlank()) addressParts.add(it) }
        shop.addr2?.let { if (it.isNotBlank()) addressParts.add(it) }
        shop.addr3?.let { if (it.isNotBlank()) addressParts.add(it) }
        shop.addr4?.let { if (it.isNotBlank()) addressParts.add(it) }
        shop.district?.let { if (it.isNotBlank()) addressParts.add(it) }

        return addressParts.joinToString(", ")
    }

    // Add this new method that doesn't auto-assign
    open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map<String, Any>> {
        println("=== Debug: getAllPickOrderLotsWithDetailsWithoutAutoAssign ===")
        println("today: ${LocalDate.now()}")
        println("userId filter: $userId")

        // Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId)
        val user = userService.find(userId).orElse(null)
        if (user == null) {
            println("❌ User not found: $userId")
            return emptyList()
        }

        // Get all pick orders assigned to user with PENDING or RELEASED status that have doId
        val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
            user,
            listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED)
        ).filter { it.deliveryOrder != null } // Only pick orders with doId
        println("    DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId")
        allAssignedPickOrders.forEach { po ->
            println("    DEBUG: Pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}")
        }
        //  NEW LOGIC: Filter based on assignment and status
        val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) {
            // Check if there are any RELEASED orders assigned to this user (active work)
            val assignedReleasedOrders = allAssignedPickOrders.filter {
                it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId
            }

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

        println("�� DEBUG: After assignment filtering, ${filteredPickOrders.size} pick orders remain")
        filteredPickOrders.forEach { po ->
            println("�� DEBUG: Final pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}")
        }

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

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

        // Use the same SQL query as the auto-assign version but without the auto-assignment
        val pickOrderIdsStr = pickOrderIds.joinToString(",")

        val sql = """
        SELECT 
            -- Pick Order Information
            po.id as pickOrderId,
            po.code as pickOrderCode,
            po.consoCode as pickOrderConsoCode,
            DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
            po.type as pickOrderType,
            po.status as pickOrderStatus,
            po.assignTo as pickOrderAssignTo,
            
            -- Pick Order Line Information
            pol.id as pickOrderLineId,
            pol.qty as pickOrderLineRequiredQty,
            pol.status as pickOrderLineStatus,
            
            -- Item Information
            i.id as itemId,
            i.code as itemCode,
            i.name as itemName,
            uc.code as uomCode,
            uc.udfudesc as uomDesc,
            uc.udfShortDesc as uomShortDesc,
            
            -- Lot Information (similar to lot-details endpoint)
            ill.id as lotId,
            il.lotNo,
            DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
            w.name as location,
            COALESCE(uc.udfudesc, 'N/A') as stockUnit,   
            
           -- Router Information
            w.id as routerId,
            w.`order` as routerIndex,
            w.code as routerRoute,
            w.code as routerArea,



            --  FIXED: Set quantities to NULL for rejected lots
            CASE 
            WHEN sol.status = 'rejected' THEN NULL
            ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
        END as availableQty,
            
            -- Required quantity for this lot
            COALESCE(spl.qty, 0) as requiredQty,
            
            -- Actual picked quantity
            COALESCE(sol.qty, 0) as actualPickQty,
            
            -- Suggested pick lot information
            spl.id as suggestedPickLotId,
            ill.status as lotStatus,
            
            -- Stock out line information
            sol.id as stockOutLineId,
            sol.status as stockOutLineStatus,
            COALESCE(sol.qty, 0) as stockOutLineQty,
            
            -- Additional detailed fields from lot-details
            COALESCE(ill.inQty, 0) as inQty, 
            COALESCE(ill.outQty, 0) as outQty,
            COALESCE(ill.holdQty, 0) as holdQty,
            COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId,
            ill.inventoryLotId as debugInventoryLotId,
            
            -- Calculate total picked quantity by ALL pick orders for this lot
            COALESCE((
                SELECT SUM(sol_all.qty) 
                FROM fpsmsdb.stock_out_line sol_all 
                WHERE sol_all.inventoryLotLineId = ill.id 
                AND sol_all.deleted = false
                AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed')
            ), 0) as totalPickedByAllPickOrders,
            
            -- Calculate remaining available quantity correctly
            (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
            
            -- Add detailed debug fields for lotAvailability calculation
            ill.status as debug_ill_status,
            (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) as debug_is_expired,
            sol.status as debug_sol_status,
            (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as debug_remaining_stock,
            (COALESCE(spl.qty, 0) - COALESCE(sol.qty, 0)) as debug_required_after_picked,
            
            -- Lot availability status (same logic as lot-details)
            CASE 
                -- Check if lot is expired
                WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
                -- Check if lot has rejected stock out line for this pick order
                WHEN sol.status = 'rejected' THEN 'rejected'
                -- Check if lot is unavailable due to insufficient stock
                WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
                -- Check if lot status is unavailable
                WHEN ill.status = 'unavailable' THEN 'status_unavailable'
                -- Default to available
                ELSE 'available'
            END as lotAvailability,
            
            -- Processing status
            CASE 
                WHEN sol.status = 'completed' THEN 'completed'
                WHEN sol.status = 'rejected' THEN 'rejected'
                WHEN sol.status = 'created' THEN 'pending'
                ELSE 'pending'
            END as processingStatus
            
        FROM fpsmsdb.pick_order po
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
JOIN fpsmsdb.items i ON i.id = pol.itemId
LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
WHERE po.deleted = false
AND po.id IN ($pickOrderIdsStr)
AND pol.deleted = false
AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED')
AND po.assignTo = :userId
AND ill.deleted = false
AND il.deleted = false
AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
ORDER BY 
    CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END,
    COALESCE(w.`order`, 999999) ASC,  --  改用 warehouse.order
    -- Add frontpart (floor) sorting: 1F > 2F/4F
    CASE 
        WHEN w.store_id = '1F' THEN 1
        WHEN w.store_id IN ('2F', '4F') THEN 2
        ELSE 3
    END ASC,
    po.code ASC,
    i.code ASC,
    il.expiryDate ASC, 
    il.lotNo ASC
    """.trimIndent()

        //println("    Executing SQL for all pick order lots with details (no auto-assign): $sql")
        println(" With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr")

        val results = jdbcDao.queryForList(sql, mapOf("userId" to userId))
        println(" Total result count: ${results.size}")

        // Filter out lots with null availableQty (rejected lots)
        val filteredResults = results.filter { row ->
            val availableQty = row["availableQty"]
            availableQty != null
        }

        println(" Filtered result count: ${filteredResults.size}")
        /*
    //  Add router information for each lot
    val enrichedResults = filteredResults.map { row ->
        val inventoryLotId = row["debugInventoryLotId"] as? Number
        
        // Get router information for this inventory lot
        val routerInfo = if (inventoryLotId != null) {
            try {
                val router = routerRepository.findFirstByInventoryLotIdAndDeletedFalse(inventoryLotId.toInt())
                if (router != null) {
                    mapOf(

                        "routerRoute" to (router.route ?: ""),

                        "routerItemCode" to (router.itemCode ?: 0),
                        "routerItemName" to (router.itemName ?: ""),


                    )
                } else {
                    mapOf(
                        "routerIndex" to 0,
                        "routerRoute" to "",
                        "routerArea" to "",
                        "routerItemCode" to 0,
                        "routerItemName" to "",
                        "routerUomId" to 0,
                        "routerNoofCarton" to 0
                    )
                }
            } catch (e: Exception) {
                println("  Error getting router info for inventoryLotId $inventoryLotId: ${e.message}")
                mapOf(
                    "routerIndex" to 0,
                    "routerRoute" to "",
                    "routerArea" to "",
                    "routerItemCode" to 0,
                    "routerItemName" to "",
                    "routerUomId" to 0,
                    "routerNoofCarton" to 0
                )
            }
        } else {
            mapOf(
                "routerIndex" to 0,
                "routerRoute" to "",
                "routerArea" to "",
                "routerItemCode" to 0,
                "routerItemName" to "",
                "routerUomId" to 0,
                "routerNoofCarton" to 0
            )
        }
        
        // Combine original row with router information
        row.toMutableMap().apply {
            putAll(routerInfo)
        }
    }
    */
        val enrichedResults = filteredResults
        return enrichedResults
    }
/* 
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
    //println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===")
   // println("userId filter: $userId")
    
    val user = userService.find(userId).orElse(null)
    if (user == null) {
        println("❌ User not found: $userId")
        return emptyMap()
    }
    
    //  Step 1: 获取 do_pick_order 基本信息
    val doPickOrderSql = """
    SELECT DISTINCT
        dpo.id as do_pick_order_id,
        dpo.ticket_no,
        dpo.store_id,
        dpo.TruckLanceCode,
        dpo.truck_departure_time,
        dpo.ShopCode,
        dpo.ShopName,
        dpo.ticket_status as doTicketStatus
    FROM fpsmsdb.do_pick_order dpo
    INNER JOIN fpsmsdb.do_pick_order_line dpol ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0
    INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id
    WHERE po.assignTo = :userId
    AND po.type = 'do'
    AND EXISTS (
        SELECT 1 
        FROM fpsmsdb.do_pick_order_line dpol2
        INNER JOIN fpsmsdb.pick_order po2 ON po2.id = dpol2.pick_order_id
        WHERE dpol2.do_pick_order_id = dpo.id
        AND dpol2.deleted = 0
        AND po2.status IN ('assigned', 'released', 'picking')
        AND po2.deleted = false
    )
    AND dpo.handled_by = :userId
  AND dpo.ticket_status IN ('released','picking')
    AND po.deleted = false
    AND dpo.deleted = false
    LIMIT 1
    """.trimIndent()
    
    val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null)
    if (doPickOrderInfo == null) {
        println("❌ No do_pick_order found for user $userId")
        return mapOf(
            "fgInfo" to null,
            "pickOrders" to emptyList<Any>()
        )
    }
    
    val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong()
    println("    Found do_pick_order ID: $doPickOrderId")
    val doTicketStatus = doPickOrderInfo["doTicketStatus"]
    //  Step 2: 获取该 do_pick_order 下的所有 pick orders
    val pickOrdersSql = """
        SELECT DISTINCT
            dpol.pick_order_id,
            dpol.pick_order_code,
            dpol.do_order_id,
            dpol.delivery_order_code,
            po.consoCode,
            po.status,
            DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate
        FROM fpsmsdb.do_pick_order_line dpol
        INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id
        WHERE dpol.do_pick_order_id = :doPickOrderId
        AND dpol.deleted = 0
        AND po.deleted = false
        ORDER BY dpol.pick_order_id
    """.trimIndent()
    
    val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderId" to doPickOrderId))
    println("    Found ${pickOrdersInfo.size} pick orders")
    
    //  Step 3: 为每个 pick order 获取 lines 和 lots
    val allPickOrderLines = mutableListOf<Map<String, Any?>>()
    val lineCountsPerPickOrder = mutableListOf<Int>()
    val pickOrderIds = mutableListOf<Long>()
    val pickOrderCodes = mutableListOf<String>()
    val doOrderIds = mutableListOf<Long>()
    val deliveryOrderCodes = mutableListOf<String>()
    
    pickOrdersInfo.forEach { poInfo ->
        val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong()
        val pickOrderCode = poInfo["pick_order_code"] as? String
        val doOrderId = (poInfo["do_order_id"] as? Number)?.toLong()
        val deliveryOrderCode = poInfo["delivery_order_code"] as? String
        
        // 收集 IDs 和 codes
        pickOrderId?.let { pickOrderIds.add(it) }
        pickOrderCode?.let { pickOrderCodes.add(it) }
        doOrderId?.let { doOrderIds.add(it) }
        deliveryOrderCode?.let { deliveryOrderCodes.add(it) }
        
        //  查询该 pick order 的所有 lines 和 lots
        val linesSql = """
        SELECT 
            po.id as pickOrderId,
            po.code as pickOrderCode,
            po.consoCode as pickOrderConsoCode,
            DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
            po.type as pickOrderType,
            po.status as pickOrderStatus,
            po.assignTo as pickOrderAssignTo,
            
            pol.id as pickOrderLineId,
            pol.qty as pickOrderLineRequiredQty,
            pol.status as pickOrderLineStatus,
            
            i.id as itemId,
            i.code as itemCode,
            i.name as itemName,
            i.item_Order as itemOrder,
            uc.code as uomCode,
            uc.udfudesc as uomDesc,
            uc.udfShortDesc as uomShortDesc,
            
            ill.id as lotId,
            il.lotNo,
            DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
            w.name as location,
            COALESCE(uc.udfudesc, 'N/A') as stockUnit,
            il.stockInLineId as stockInLineId,
            w.`order` as routerIndex,
            w.code as routerRoute,
            
            CASE 
                WHEN sol.status = 'rejected' THEN NULL
                ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
            END as availableQty,
            
            COALESCE(spl.qty, 0) as requiredQty,
            COALESCE(sol.qty, 0) as actualPickQty,
            spl.id as suggestedPickLotId,
            ill.status as lotStatus,
            sol.id as stockOutLineId,
            sol.status as stockOutLineStatus,
            COALESCE(sol.qty, 0) as stockOutLineQty,
            COALESCE(ill.inQty, 0) as inQty,
            COALESCE(ill.outQty, 0) as outQty,
            COALESCE(ill.holdQty, 0) as holdQty,
            
            CASE 
                WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
                WHEN sol.status = 'rejected' THEN 'rejected'
                WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
                WHEN ill.status = 'unavailable' THEN 'status_unavailable'
                ELSE 'available'
            END as lotAvailability,
            
                        CASE 
                WHEN sol.status = 'completed' THEN 'completed'
                WHEN sol.status = 'rejected' THEN 'rejected'
                WHEN sol.status = 'created' THEN 'pending'
                ELSE 'pending'
            END as processingStatus
        FROM fpsmsdb.pick_order po
        JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
        JOIN fpsmsdb.items i ON i.id = pol.itemId
        LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId

        LEFT JOIN (
            SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId
            FROM fpsmsdb.suggested_pick_lot spl
            UNION
            SELECT sol.pickOrderLineId, sol.inventoryLotLineId
            FROM fpsmsdb.stock_out_line sol
            WHERE sol.deleted = false
        ) ll ON ll.pickOrderLineId = pol.id

        LEFT JOIN fpsmsdb.suggested_pick_lot spl 
          ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId

        -- lots 用（保留原逻辑）
        LEFT JOIN fpsmsdb.stock_out_line sol 
          ON sol.pickOrderLineId = pol.id 
         AND ( (sol.inventoryLotLineId = ll.lotLineId)
            OR (sol.inventoryLotLineId IS NULL AND ll.lotLineId IS NULL) )
         AND sol.deleted = false

        LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false
        LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false
        LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId

        WHERE po.id = :pickOrderId
          AND po.deleted = false
        ORDER BY 
            COALESCE(w.`order`, 999999) ASC,
            pol.id ASC,
            il.lotNo ASC
        """.trimIndent()
        
        val linesResults = jdbcDao.queryForList(linesSql, mapOf("pickOrderId" to pickOrderId))
        println("    Pick order $pickOrderId has ${linesResults.size} line-lot records")
        println("DEBUG polIds in linesResults: " + linesResults.map { it["pickOrderLineId"] }.distinct())
println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["stockOutLineId"]?.let { _ -> it["pickOrderLineId"] } }.distinct())
        //  按 pickOrderLineId 分组
        val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() }
        
        val pickOrderLines = lineGroups.map { (lineId, lineRows) ->
            val firstLineRow = lineRows.firstOrNull()
            
            if (firstLineRow == null) {
                null
            } else {
                //  lots：仍然是“有批次”的明细（保留你当前的逻辑）
                val lots = if (lineRows.any { it["lotId"] != null }) {
                    lineRows.filter { it["lotId"] != null }.map { lotRow ->
                        mapOf(
                            "id" to lotRow["lotId"],
                            "lotNo" to lotRow["lotNo"],
                            "expiryDate" to lotRow["expiryDate"],
                            "location" to lotRow["location"],
                            "stockUnit" to lotRow["stockUnit"],
                            "availableQty" to lotRow["availableQty"],
                            "requiredQty" to lotRow["requiredQty"],
                            "actualPickQty" to lotRow["actualPickQty"],
                            "inQty" to lotRow["inQty"],
                            "outQty" to lotRow["outQty"],
                            "holdQty" to lotRow["holdQty"],
                            "lotStatus" to lotRow["lotStatus"],
                            "lotAvailability" to lotRow["lotAvailability"],
                            "processingStatus" to lotRow["processingStatus"],
                            "suggestedPickLotId" to lotRow["suggestedPickLotId"],
                            "stockOutLineId" to lotRow["stockOutLineId"],
                            "stockOutLineStatus" to lotRow["stockOutLineStatus"],
                            "stockOutLineQty" to lotRow["stockOutLineQty"],
                            "stockInLineId" to lotRow["stockInLineId"],
                            "router" to mapOf(
                                "id" to null,
                                "index" to lotRow["routerIndex"],
                                "route" to lotRow["routerRoute"],
                                "area" to lotRow["routerRoute"],
                                "itemCode" to lotRow["itemId"],
                                "itemName" to lotRow["itemName"],
                                "uomId" to lotRow["uomCode"],
                                "noofCarton" to lotRow["requiredQty"]
                            )
                        )
                    }
                } else {
                    emptyList()
                }

                // stockouts：包含该 pick order line 的所有出库行（包含 noLot）
                // 注意：之前用 sol_any 会导致行被重复 N 次（N=该行 stock_out_line 数量），进而前端合并 requiredQty 时被累加。
                // 这里直接用同一行上的 sol 字段构建 stockouts，避免笛卡尔积。
                val stockouts = lineRows
                    .filter { it["stockOutLineId"] != null }
                    .distinctBy { row -> row["stockOutLineId"] }
                    .map { row ->
                        val lotId = row["lotId"]
                        val noLot = (lotId == null)
                        mapOf(
                            "id" to row["stockOutLineId"],
                            "status" to row["stockOutLineStatus"],
                            "qty" to row["stockOutLineQty"],
                            "lotId" to lotId,
                            "lotNo" to (row["lotNo"] ?: ""),
                            "location" to (row["location"] ?: ""),
                            "availableQty" to row["availableQty"],
                            "stockInLineId" to row["stockInLineId"],
                            "noLot" to noLot
                        )
                    }

                mapOf(
                    "id" to lineId,
                    "requiredQty" to firstLineRow["pickOrderLineRequiredQty"],
                    "status" to firstLineRow["pickOrderLineStatus"],
                    "itemOrder" to firstLineRow["itemOrder"],
                    "item" to mapOf(
                        "id" to firstLineRow["itemId"],
                        "code" to firstLineRow["itemCode"],
                        "name" to firstLineRow["itemName"],
                        "uomCode" to firstLineRow["uomCode"],
                        "uomDesc" to firstLineRow["uomDesc"],
                        "uomShortDesc" to firstLineRow["uomShortDesc"],
                    ),
                    "lots" to lots,
                    //  新增字段
                    "stockouts" to stockouts
                )
            }
        }.filterNotNull()
        
        // 记录该 pick order 的行数
        lineCountsPerPickOrder.add(pickOrderLines.size)
        allPickOrderLines.addAll(pickOrderLines)
    }
        // 合并到总列表
        // DB 里 store_id 可能是 "2/F"、"4/F"，与字面 "2F" 不一致会导致分支走错
        val doStoreFloorKey = doPickOrderInfo["store_id"]?.toString()?.trim()?.uppercase(Locale.ROOT)
            ?.replace("/", "")
            ?.replace(" ", "")
            ?: ""
        when (doStoreFloorKey) {
            "2F" -> {
                // 2F：按 items.item_Order 排序（INT，NULL 置后）
                allPickOrderLines.sortWith(
                    compareBy(
                        { line ->
                            val v = line["itemOrder"] ?: line["itemorder"]
                            when (v) {
                                is Number -> v.toInt()
                                else -> 999999
                            }
                        },
                        { line -> (line["id"] as? Number)?.toLong() ?: Long.MAX_VALUE }
                    )
                )
            }
            "4F" -> {
                // 4F：旧逻辑 — 保持 SQL 顺序（COALESCE(w.`order`, 999999), pol.id, lotNo），不二次排序
            }
            else -> {
                allPickOrderLines.sortWith(
                    compareBy { line ->
                        val lots = line["lots"] as? List<Map<String, Any?>>
                        val firstLot = lots?.firstOrNull()
                        val router = firstLot?.get("router") as? Map<String, Any?>
                        val indexValue = router?.get("index")
                        when (indexValue) {
                            is Number -> indexValue.toInt()
                            is String -> {
                                val parts = indexValue.split("-")
                                if (parts.size > 1) {
                                    parts.last().toIntOrNull() ?: 999999
                                } else {
                                    indexValue.toIntOrNull() ?: 999999
                                }
                            }
                            else -> 999999
                        }
                    }
                )
            }
        }
    
    
    //  构建 FG 信息
    val fgInfo = mapOf(
        "doPickOrderId" to doPickOrderId,
        "ticketNo" to doPickOrderInfo["ticket_no"],
        "storeId" to doPickOrderInfo["store_id"],
        "shopCode" to doPickOrderInfo["ShopCode"],
        "shopName" to doPickOrderInfo["ShopName"],
        "truckLanceCode" to doPickOrderInfo["TruckLanceCode"],
        "departureTime" to doPickOrderInfo["truck_departure_time"]
    )
    val allConsoCodes = pickOrdersInfo.mapNotNull { it["consoCode"] as? String }.distinct()

    //  构建合并后的 pick order 对象
    val mergedPickOrder = if (pickOrdersInfo.isNotEmpty()) {
        val firstPickOrderInfo = pickOrdersInfo.first()
        mapOf(
            "pickOrderIds" to pickOrderIds,
            "pickOrderCodes" to pickOrderCodes,
            "doOrderIds" to doOrderIds,
            "deliveryOrderCodes" to deliveryOrderCodes,
            "lineCountsPerPickOrder" to lineCountsPerPickOrder,
            "consoCodes" to allConsoCodes,  
            "status" to doTicketStatus,
            "targetDate" to firstPickOrderInfo["targetDate"],
            "pickOrderLines" to allPickOrderLines
        )
    } else {
        null
    }
    
    return mapOf(
        "fgInfo" to fgInfo,
        "pickOrders" to listOfNotNull(mergedPickOrder)
    )
}
*/
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
    val user = userService.find(userId).orElse(null)
    if (user == null) {
        println("❌ User not found: $userId")
        return emptyMap()
    }
    // Step 1: 找到該 user 目前處理中的 do_pick_order（你原本就是 LIMIT 1）
    val doPickOrderSql = """
        SELECT DISTINCT
            dpo.id as do_pick_order_id,
            dpo.ticket_no,
            dpo.store_id,
            dpo.TruckLanceCode,
            dpo.truck_departure_time,
            dpo.ShopCode,
            dpo.ShopName,
            dpo.ticket_status as doTicketStatus
        FROM fpsmsdb.do_pick_order dpo
        INNER JOIN fpsmsdb.do_pick_order_line dpol ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0
        INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id
        WHERE po.assignTo = :userId
          AND po.type = 'do'
          AND EXISTS (
              SELECT 1
              FROM fpsmsdb.do_pick_order_line dpol2
              INNER JOIN fpsmsdb.pick_order po2 ON po2.id = dpol2.pick_order_id
              WHERE dpol2.do_pick_order_id = dpo.id
                AND dpol2.deleted = 0
                AND po2.status IN ('assigned', 'released', 'picking')
                AND po2.deleted = false
          )
          AND dpo.handled_by = :userId
          AND dpo.ticket_status IN ('released','picking')
          AND po.deleted = false
          AND dpo.deleted = false
        LIMIT 1
    """.trimIndent()
    val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null)
        ?: return mapOf("fgInfo" to null, "pickOrders" to emptyList<Any>())
    val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong()
        ?: return mapOf("fgInfo" to null, "pickOrders" to emptyList<Any>())
    val doTicketStatus = doPickOrderInfo["doTicketStatus"]
    // Step 2: 找到該 do_pick_order 底下所有 pick orders
    val pickOrdersSql = """
        SELECT DISTINCT
            dpol.pick_order_id,
            dpol.pick_order_code,
            dpol.do_order_id,
            dpol.delivery_order_code,
            po.consoCode,
            po.status,
            DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate
        FROM fpsmsdb.do_pick_order_line dpol
        INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id
        WHERE dpol.do_pick_order_id = :doPickOrderId
          AND dpol.deleted = 0
          AND po.deleted = false
        ORDER BY dpol.pick_order_id
    """.trimIndent()
    val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderId" to doPickOrderId))
    if (pickOrdersInfo.isEmpty()) {
        val fgInfo = mapOf(
            "doPickOrderId" to doPickOrderId,
            "ticketNo" to doPickOrderInfo["ticket_no"],
            "storeId" to doPickOrderInfo["store_id"],
            "shopCode" to doPickOrderInfo["ShopCode"],
            "shopName" to doPickOrderInfo["ShopName"],
            "truckLanceCode" to doPickOrderInfo["TruckLanceCode"],
            "departureTime" to doPickOrderInfo["truck_departure_time"],
        )
        return mapOf("fgInfo" to fgInfo, "pickOrders" to emptyList<Any>())
    }
    val pickOrderIds = pickOrdersInfo.mapNotNull { (it["pick_order_id"] as? Number)?.toLong() }.distinct()
    val pickOrderCodes = pickOrdersInfo.mapNotNull { it["pick_order_code"] as? String }
    val doOrderIds = pickOrdersInfo.mapNotNull { (it["do_order_id"] as? Number)?.toLong() }
    val deliveryOrderCodes = pickOrdersInfo.mapNotNull { it["delivery_order_code"] as? String }
    val allConsoCodes = pickOrdersInfo.mapNotNull { it["consoCode"] as? String }.distinct()
    // Step 3: 一次查完所有 pickOrder 的 line/lot/stockout 明細（避免 N+1）
    val linesSqlAll = """
        SELECT 
            po.id as pickOrderId,
            po.code as pickOrderCode,
            po.consoCode as pickOrderConsoCode,
            DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
            po.type as pickOrderType,
            po.status as pickOrderStatus,
            po.assignTo as pickOrderAssignTo,
            
            pol.id as pickOrderLineId,
            pol.qty as pickOrderLineRequiredQty,
            pol.status as pickOrderLineStatus,
            
            i.id as itemId,
            i.code as itemCode,
            i.name as itemName,
            i.item_Order as itemOrder,
            uc.code as uomCode,
            uc.udfudesc as uomDesc,
            uc.udfShortDesc as uomShortDesc,
            
            ill.id as lotId,
            il.lotNo,
            DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
            w.name as location,
            COALESCE(uc.udfudesc, 'N/A') as stockUnit,
            il.stockInLineId as stockInLineId,
            w.`order` as routerIndex,
            w.code as routerRoute,
            
            CASE 
                WHEN sol.status = 'rejected' THEN NULL
                ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
            END as availableQty,
            
            COALESCE(spl.qty, 0) as requiredQty,
            COALESCE(sol.qty, 0) as actualPickQty,
            spl.id as suggestedPickLotId,
            ill.status as lotStatus,
            sol.id as stockOutLineId,
            sol.status as stockOutLineStatus,
            COALESCE(sol.qty, 0) as stockOutLineQty,
            COALESCE(ill.inQty, 0) as inQty,
            COALESCE(ill.outQty, 0) as outQty,
            COALESCE(ill.holdQty, 0) as holdQty,
            
            CASE 
                WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
                WHEN sol.status = 'rejected' THEN 'rejected'
                WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
                WHEN ill.status = 'unavailable' THEN 'status_unavailable'
                ELSE 'available'
            END as lotAvailability,
            
            CASE 
                WHEN sol.status = 'completed' THEN 'completed'
                WHEN sol.status = 'rejected' THEN 'rejected'
                WHEN sol.status = 'created' THEN 'pending'
                ELSE 'pending'
            END as processingStatus
        FROM fpsmsdb.pick_order po
        JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
        JOIN fpsmsdb.items i ON i.id = pol.itemId
        LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
        LEFT JOIN (
            SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId
            FROM fpsmsdb.suggested_pick_lot spl
            UNION
            SELECT sol.pickOrderLineId, sol.inventoryLotLineId
            FROM fpsmsdb.stock_out_line sol
            WHERE sol.deleted = false
        ) ll ON ll.pickOrderLineId = pol.id
        LEFT JOIN fpsmsdb.suggested_pick_lot spl 
          ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId
        LEFT JOIN fpsmsdb.stock_out_line sol 
          ON sol.pickOrderLineId = pol.id 
         AND ( (sol.inventoryLotLineId = ll.lotLineId)
            OR (sol.inventoryLotLineId IS NULL AND ll.lotLineId IS NULL) )
         AND sol.deleted = false
        LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false
        LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false
        LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
        WHERE po.id IN (:pickOrderIds)
          AND po.deleted = false
        ORDER BY 
            po.id ASC,
            COALESCE(w.`order`, 999999) ASC,
            pol.id ASC,
            il.lotNo ASC
    """.trimIndent()
    val allLineRows = jdbcDao.queryForList(linesSqlAll, mapOf("pickOrderIds" to pickOrderIds))
    // Kotlin 端組裝：pickOrderId -> lineId -> rows
    val byPickOrderId = allLineRows.groupBy { (it["pickOrderId"] as? Number)?.toLong() }
    // 用來計算每個 pick order 的 lineCounts（保持你原本回傳欄位）
    val lineCountsPerPickOrder = mutableListOf<Int>()
    val allPickOrderLines = mutableListOf<Map<String, Any?>>()
    // 依照 Step2 的 pickOrdersInfo 順序處理（避免順序變動）
    pickOrdersInfo.forEach { poInfo ->
        val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() ?: return@forEach
        val rows = byPickOrderId[pickOrderId].orEmpty()
        val lineGroups = rows.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() }
        val pickOrderLines = lineGroups.mapNotNull { (lineId, lineRows) ->
            val first = lineRows.firstOrNull() ?: return@mapNotNull null
            val lots = if (lineRows.any { it["lotId"] != null }) {
                lineRows
                    .filter { it["lotId"] != null }
                    .map { lotRow ->
                        mapOf(
                            "id" to lotRow["lotId"],
                            "lotNo" to lotRow["lotNo"],
                            "expiryDate" to lotRow["expiryDate"],
                            "location" to lotRow["location"],
                            "stockUnit" to lotRow["stockUnit"],
                            "availableQty" to lotRow["availableQty"],
                            "requiredQty" to lotRow["requiredQty"],
                            "actualPickQty" to lotRow["actualPickQty"],
                            "inQty" to lotRow["inQty"],
                            "outQty" to lotRow["outQty"],
                            "holdQty" to lotRow["holdQty"],
                            "lotStatus" to lotRow["lotStatus"],
                            "lotAvailability" to lotRow["lotAvailability"],
                            "processingStatus" to lotRow["processingStatus"],
                            "suggestedPickLotId" to lotRow["suggestedPickLotId"],
                            "stockOutLineId" to lotRow["stockOutLineId"],
                            "stockOutLineStatus" to lotRow["stockOutLineStatus"],
                            "stockOutLineQty" to lotRow["stockOutLineQty"],
                            "stockInLineId" to lotRow["stockInLineId"],
                            "router" to mapOf(
                                "id" to null,
                                "index" to lotRow["routerIndex"],
                                "route" to lotRow["routerRoute"],
                                "area" to lotRow["routerRoute"],
                                "itemCode" to lotRow["itemId"],
                                "itemName" to lotRow["itemName"],
                                "uomId" to lotRow["uomCode"],
                                "noofCarton" to lotRow["requiredQty"]
                            )
                        )
                    }
            } else emptyList()
            val stockouts = lineRows
                .filter { it["stockOutLineId"] != null }
                .distinctBy { it["stockOutLineId"] }
                .map { row ->
                    val lotId = row["lotId"]
                    val noLot = (lotId == null)
                    mapOf(
                        "id" to row["stockOutLineId"],
                        "status" to row["stockOutLineStatus"],
                        "qty" to row["stockOutLineQty"],
                        "lotId" to lotId,
                        "lotNo" to (row["lotNo"] ?: ""),
                        "location" to (row["location"] ?: ""),
                        "availableQty" to row["availableQty"],
                        "stockInLineId" to row["stockInLineId"],
                        "noLot" to noLot
                    )
                }
            mapOf(
                "id" to lineId,
                "requiredQty" to first["pickOrderLineRequiredQty"],
                "status" to first["pickOrderLineStatus"],
                "itemOrder" to first["itemOrder"],
                "item" to mapOf(
                    "id" to first["itemId"],
                    "code" to first["itemCode"],
                    "name" to first["itemName"],
                    "uomCode" to first["uomCode"],
                    "uomDesc" to first["uomDesc"],
                    "uomShortDesc" to first["uomShortDesc"],
                ),
                "lots" to lots,
                "stockouts" to stockouts
            )
        }
        lineCountsPerPickOrder.add(pickOrderLines.size)
        allPickOrderLines.addAll(pickOrderLines)
    }
    // 保留你原本按 store 樓層的排序規則
    val doStoreFloorKey = doPickOrderInfo["store_id"]?.toString()?.trim()?.uppercase(Locale.ROOT)
        ?.replace("/", "")
        ?.replace(" ", "")
        ?: ""
    when (doStoreFloorKey) {
        "2F" -> {
            allPickOrderLines.sortWith(
                compareBy(
                    { line ->
                        val v = line["itemOrder"] ?: line["itemorder"]
                        when (v) {
                            is Number -> v.toInt()
                            else -> 999999
                        }
                    },
                    { line -> (line["id"] as? Number)?.toLong() ?: Long.MAX_VALUE }
                )
            )
        }
        "4F" -> {
            // 4F：保留 SQL 順序，不做二次排序
        }
        else -> {
            allPickOrderLines.sortWith(
                compareBy { line ->
                    val lots = line["lots"] as? List<Map<String, Any?>>
                    val firstLot = lots?.firstOrNull()
                    val router = firstLot?.get("router") as? Map<String, Any?>
                    val indexValue = router?.get("index")
                    when (indexValue) {
                        is Number -> indexValue.toInt()
                        is String -> indexValue.toIntOrNull() ?: 999999
                        else -> 999999
                    }
                }
            )
        }
    }
    val fgInfo = mapOf(
        "doPickOrderId" to doPickOrderId,
        "ticketNo" to doPickOrderInfo["ticket_no"],
        "storeId" to doPickOrderInfo["store_id"],
        "shopCode" to doPickOrderInfo["ShopCode"],
        "shopName" to doPickOrderInfo["ShopName"],
        "truckLanceCode" to doPickOrderInfo["TruckLanceCode"],
        "departureTime" to doPickOrderInfo["truck_departure_time"]
    )
    val mergedPickOrder = run {
        val firstPickOrderInfo = pickOrdersInfo.first()
        mapOf(
            "pickOrderIds" to pickOrderIds,
            "pickOrderCodes" to pickOrderCodes,
            "doOrderIds" to doOrderIds,
            "deliveryOrderCodes" to deliveryOrderCodes,
            "lineCountsPerPickOrder" to lineCountsPerPickOrder,
            "consoCodes" to allConsoCodes,
            "status" to doTicketStatus,
            "targetDate" to firstPickOrderInfo["targetDate"],
            "pickOrderLines" to allPickOrderLines
        )
    }
    return mapOf(
        "fgInfo" to fgInfo,
        "pickOrders" to listOf(mergedPickOrder)
    )
}

        // Fix the type issues in the getPickOrdersByDateAndStore method
        open fun getPickOrdersByDateAndStore(storeId: String): Map<String, Any?> {
            println("=== Debug: getPickOrdersByDateAndStore ===")
            println("storeId: $storeId")

            try {
                val today = LocalDate.now()
                println("Today's date: $today")

                // SQL query to get pick orders grouped by date and store
                // Get all available orders for this store
                val sql = """
            SELECT 
                dpo.store_id as storeId,
                DATE_FORMAT(dpo.created, '%Y-%m-%d') as orderDate,
                COUNT(DISTINCT po.id) as orderTotal,
                COUNT(DISTINCT CASE WHEN po.status = 'COMPLETED' THEN po.id END) as orderCompleted,
                GROUP_CONCAT(DISTINCT po.id ORDER BY po.id) as pickOrderIds,
                GROUP_CONCAT(DISTINCT po.code ORDER BY po.id SEPARATOR ',') as pickOrderCodes,
                -- Get do_pick_order details
                GROUP_CONCAT(
                    CONCAT(
                        dpo.id, ':', 
                        dpo.ticket_no, ':', 
                        dpo.pick_order_id
                    ) 
                    ORDER BY dpo.id SEPARATOR '|'
                ) as doPickOrderDetails
            FROM do_pick_order dpo
            JOIN pick_order po ON po.id = dpo.pick_order_id
            WHERE dpo.deleted = false
            AND po.deleted = false
            AND dpo.store_id = :storeId
            GROUP BY dpo.store_id, DATE_FORMAT(dpo.created, '%Y-%m-%d')
            ORDER BY orderDate ASC
        """.trimIndent()

                //println("    Executing SQL: $sql")
                println("    With parameters: storeId = $storeId")

                val results = jdbcDao.queryForList(sql, mapOf("storeId" to storeId))
                println(" Found ${results.size} records")

                if (results.isEmpty()) {
                    return mapOf(
                        "storeId" to storeId,
                        "orders" to emptyList<Map<String, Any?>>()
                    )
                }

                // Analyze each date's completion status
                val ordersByDate = results.map { row ->
                    val orderDate = LocalDate.parse(row["orderDate"] as String)
                    val orderTotal = (row["orderTotal"] as Number).toInt()
                    val orderCompleted = (row["orderCompleted"] as Number).toInt()
                    val isAllCompleted = orderTotal == orderCompleted && orderTotal > 0

                    mapOf(
                        "orderDate" to orderDate,
                        "orderTotal" to orderTotal,
                        "orderCompleted" to orderCompleted,
                        "isAllCompleted" to isAllCompleted,
                        "rawData" to row
                    )
                }

                println("📊 Orders by date analysis:")
                ordersByDate.forEach { order ->
                    println("  Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}, AllCompleted: ${order["isAllCompleted"]}")
                }

                // Simple logic: Find the first incomplete date and return it + next 2 days
                val ordersToReturn = mutableListOf<Map<String, Any?>>()

                // Find the first incomplete order date
                val firstIncompleteDate = ordersByDate
                    .firstOrNull { !(it["isAllCompleted"] as Boolean) }

                if (firstIncompleteDate != null) {
                    val startDate = firstIncompleteDate["orderDate"] as LocalDate
                    println("�� First incomplete date: $startDate")

                    // Get the incomplete date + next 2 days (total 3 days)
                    val targetDates = (0..2).map { days -> startDate.plusDays(days.toLong()) }
                    println("   Target dates to return: $targetDates")

                    // Only return dates that actually exist in the database
                    ordersToReturn.addAll(
                        ordersByDate.filter { order ->
                            val orderDate = order["orderDate"] as LocalDate
                            targetDates.contains(orderDate)
                        }.map { it["rawData"] as Map<String, Any?> }
                    )
                } else {
                    println("   All orders are completed, returning empty list")
                }

                // Transform results into the required structure
                val finalOrders = ordersToReturn.map { row ->
                    val doPickOrderDetails = (row["doPickOrderDetails"] as String?)?.split("|") ?: emptyList()

                    val doPickOrderList = doPickOrderDetails.map { detail ->
                        val parts = detail.split(":")
                        if (parts.size >= 3) {
                            mapOf<String, Any?>(
                                "doPickOrderId" to parts[0].toLongOrNull(),
                                "ticketNo" to parts[1],
                                "pickOrderId" to parts[2].toLongOrNull()
                            )
                        } else {
                            mapOf<String, Any?>(
                                "doPickOrderId" to null,
                                "ticketNo" to "",
                                "pickOrderId" to null
                            )
                        }
                    }

                    mapOf<String, Any?>(
                        "orderTotal" to row["orderTotal"],
                        "orderCompleted" to row["orderCompleted"],
                        "orderDate" to row["orderDate"],
                        "pickOrderIds" to ((row["pickOrderIds"] as String?)?.split(",")
                            ?.mapNotNull { it.toLongOrNull() } ?: emptyList<Long>()),
                        "pickOrderCodes" to ((row["pickOrderCodes"] as String?)?.split(",") ?: emptyList<String>()),
                        "doPickOrderDetails" to doPickOrderList
                    )
                }

                println(" Final orders to return: ${finalOrders.size}")
                finalOrders.forEach { order ->
                    println("  📅 Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}")
                }

                return mapOf<String, Any?>(
                    "storeId" to storeId,
                    "orders" to finalOrders
                )

            } catch (e: Exception) {
                println("❌ Error in getPickOrdersByDateAndStore: ${e.message}")
                e.printStackTrace()
                return mapOf<String, Any?>(
                    "storeId" to storeId,
                    "orders" to emptyList<Map<String, Any?>>(),
                    "error" to e.message
                )
            }
        }
        @Transactional(rollbackFor = [java.lang.Exception::class])
        open fun confirmLotSubstitution(req: LotSubstitutionConfirmRequest): MessageResponse {
            val zero = BigDecimal.ZERO
        
            // Validate pick order line
            val pol = pickOrderLineRepository.findById(req.pickOrderLineId).orElse(null)
                ?: return MessageResponse(
                    id = null, name = "Pick order line not found", code = "ERROR", type = "pickorder",
                    message = "Pick order line ${req.pickOrderLineId} not found", errorPosition = null
                )
        
            val polItemId = pol.item?.id
                ?: return MessageResponse(
                    id = null, name = "Item not found", code = "ERROR", type = "pickorder",
                    message = "Pick order line item is null", errorPosition = null
                )
        
            // Find new InventoryLotLine (from stockInLineId first, fallback lotNo)
            val newIll = resolveNewInventoryLotLine(req, polItemId)
                ?: return MessageResponse(
                    id = null, name = "New lot line not found", code = "ERROR", type = "pickorder",
                    message = "This lot is not yet putaway", errorPosition = null
                )

            val targetUnavailable = newIll.status == InventoryLotLineStatus.UNAVAILABLE
        
            
            // Item consistency check
            val newItemId = newIll.inventoryLot?.item?.id
            if (newItemId == null || newItemId != polItemId) {
                return MessageResponse(
                    id = null, name = "Item mismatch", code = "ERROR", type = "pickorder",
                    message = "New lot line item does not match pick order line item", errorPosition = null
                )
            }
        
            // Resolve SuggestedPickLot:
            // - If originalSuggestedPickLotId provided: use it
            // - Else (1:1 assumption): find by pickOrderLineId (optionally also by stockOutLineId if you add repository method)
            val spl = if (req.originalSuggestedPickLotId != null && req.originalSuggestedPickLotId > 0) {
                suggestPickLotRepository.findById(req.originalSuggestedPickLotId).orElse(null)
            } else {
                // 1:1 assumption fallback (you need a repository method; replace with your actual one)
                // e.g. suggestPickLotRepository.findFirstByPickOrderLineIdAndDeletedFalseOrderByIdDesc(req.pickOrderLineId)
                suggestPickLotRepository.findFirstByPickOrderLineId(req.pickOrderLineId)
            }
        
            if (spl == null) {
                return MessageResponse(
                    id = null, name = "Suggested pick lot not found", code = "ERROR", type = "pickorder",
                    message = "SuggestedPickLot not found for pickOrderLineId=${req.pickOrderLineId}", errorPosition = null
                )
            }
        
            val qtyToHold = spl.qty ?: zero
            if (qtyToHold.compareTo(zero) <= 0) {
                return MessageResponse(
                    id = null, name = "Invalid qty", code = "ERROR", type = "pickorder",
                    message = "SuggestedPickLot qty is invalid: $qtyToHold", errorPosition = null
                )
            }
        
            if (!targetUnavailable) {
                // Availability check on newIll BEFORE updates
                val inQty = newIll.inQty ?: zero
                val outQty = newIll.outQty ?: zero
                val holdQty = newIll.holdQty ?: zero
                //val issueQty = newIll.issueQty ?: zero
                val available = inQty.subtract(outQty).subtract(holdQty)//.subtract(issueQty)
            
                if (available.compareTo(qtyToHold) < 0) {
                    return MessageResponse(
                        id = null, name = "Insufficient lot qty", code = "REJECT", type = "pickorder",
                        message = "Reject switch lot: available=$available < required=$qtyToHold", errorPosition = null
                    )
                }
            }
        
            val oldIll = spl.suggestedLotLine
        
            // Load stock out line (if provided) to decide "bind vs split" and compute pickedQty
            val existingSol = if (req.stockOutLineId != null && req.stockOutLineId > 0) {
                stockOutLIneRepository.findById(req.stockOutLineId).orElse(null)
            } else null
            val pickedQty = existingSol?.qty?.let { numToBigDecimal(it as? Number) } ?: zero

            // Allow switch to UNAVAILABLE lot, but do NOT move hold/onHold and do NOT alter SOL status.
            if (targetUnavailable) {
                // ✅ Release hold from old suggested lot first (prevents stranded holds when user scans unavailable/expired lot).
                // Release only the remaining qty if some qty has been picked already.
                val qtyToRelease = qtyToHold.subtract(pickedQty).coerceAtLeast(zero)
                if (qtyToRelease > zero && oldIll != null && oldIll.id != null && oldIll.id != newIll.id) {
                    val oldHold = oldIll.holdQty ?: zero
                    val newOldHold = oldHold.subtract(qtyToRelease).coerceAtLeast(zero)
                    oldIll.holdQty = newOldHold
                    inventoryLotLineRepository.save(oldIll)
                }

                spl.suggestedLotLine = newIll
                suggestPickLotRepository.saveAndFlush(spl)

                if (req.stockOutLineId != null && req.stockOutLineId > 0) {
                    val sol = stockOutLIneRepository.findById(req.stockOutLineId).orElse(null)
                    if (sol != null) {
                        sol.inventoryLotLine = newIll
                        sol.item = pol.item
                        stockOutLIneRepository.saveAndFlush(sol)
                    }
                }

                val newLotNo = newIll.inventoryLot?.lotNo ?: req.newInventoryLotNo
                return MessageResponse(
                    id = null,
                    name = "Lot substitution confirmed (unavailable lot bound)",
                    code = "SUCCESS_UNAVAILABLE",
                    type = "pickorder",
                    message = "Switched to unavailable lot '$newLotNo' without hold movement and without stock out status update",
                    errorPosition = null
                )
            }
        

            // Switch lot rule:
            // - actual pick == 0: replace/bind (no new line)
            // - actual pick  > 0: split remaining qty into a NEW suggested pick lot + stock out line
            if (pickedQty.compareTo(zero) > 0) {
                val remaining = qtyToHold.subtract(pickedQty)
                if (remaining.compareTo(zero) <= 0) {
                    return MessageResponse(
                        id = null,
                        name = "No remaining qty",
                        code = "REJECT",
                        type = "pickorder",
                        message = "Reject switch lot: picked=$pickedQty already >= required=$qtyToHold",
                        errorPosition = null
                    )
                }

                // Move HOLD for remaining qty (old -> new)
                if (oldIll != null && oldIll.id != null && oldIll.id != newIll.id) {
                    val oldHold = oldIll.holdQty ?: zero
                    val newOldHold = oldHold.subtract(remaining)
                    oldIll.holdQty = if (newOldHold.compareTo(zero) < 0) zero else newOldHold
                    inventoryLotLineRepository.save(oldIll)

                    val newHold = (newIll.holdQty ?: zero).add(remaining)
                    newIll.holdQty = newHold
                    inventoryLotLineRepository.save(newIll)
                }
                if (oldIll == null) {
                    val newHold = (newIll.holdQty ?: zero).add(remaining)
                    newIll.holdQty = newHold
                    inventoryLotLineRepository.save(newIll)
                }

                // Lock current suggestion qty to the picked qty (picked part stays on oldIll)
                spl.qty = pickedQty
                suggestPickLotRepository.saveAndFlush(spl)

                // Create a NEW stock out line + suggestion for the remaining qty
                val stockOut: StockOut = if (existingSol != null) {
                    existingSol.stockOut ?: throw IllegalStateException("Existing StockOutLine has null stockOut")
                } else {
                    val consoCode = pol.pickOrder?.consoCode ?: ""
                    val existing = stockOutRepository.findByConsoPickOrderCode(consoCode).orElse(null)
                    if (existing != null) {
                        existing
                    } else {
                        val handlerId = pol.pickOrder?.assignTo?.id ?: SecurityUtils.getUser().orElse(null)?.id
                        require(handlerId != null) { "Cannot create StockOut: handlerId is null" }
                        val newStockOut = StockOut().apply {
                            this.consoPickOrderCode = consoCode
                            this.type = pol.pickOrder?.type?.value ?: ""
                            this.status = StockOutStatus.PENDING.status
                            this.handler = handlerId
                        }
                        stockOutRepository.save(newStockOut)
                    }
                }

                val newSol = StockOutLine().apply {
                    this.stockOut = stockOut
                    this.pickOrderLine = pol
                    this.item = pol.item
                    this.inventoryLotLine = newIll
                    this.qty = 0.0
                    this.status = StockOutLineStatus.CHECKED.status
                    this.startTime = LocalDateTime.now()
                    this.type = existingSol?.type ?: "Nor"
                }
                stockOutLIneRepository.saveAndFlush(newSol)

                val newSpl = SuggestedPickLot().apply {
                    this.type = spl.type
                    this.pickOrderLine = pol
                    this.suggestedLotLine = newIll
                    this.stockOutLine = newSol
                    this.qty = remaining
                    this.pickSuggested = spl.pickSuggested
                }
                suggestPickLotRepository.saveAndFlush(newSpl)

                val newLotNo = newIll.inventoryLot?.lotNo ?: req.newInventoryLotNo
                return MessageResponse(
                    id = null,
                    name = "Lot substitution confirmed (split line)",
                    code = "SUCCESS",
                    type = "pickorder",
                    message = "Picked=$pickedQty, created new line for remaining=$remaining on lotNo '$newLotNo'",
                    errorPosition = null
                )
            }
        
            // If oldIll exists and different: move hold old -> new
            if (oldIll != null && oldIll.id != null && oldIll.id != newIll.id) {
                val oldHold = oldIll.holdQty ?: zero
                val newOldHold = oldHold.subtract(qtyToHold)
                oldIll.holdQty = if (newOldHold.compareTo(zero) < 0) zero else newOldHold
                inventoryLotLineRepository.save(oldIll)
        
                val newHold = (newIll.holdQty ?: zero).add(qtyToHold)
                newIll.holdQty = newHold
                inventoryLotLineRepository.save(newIll)
            }
        
            // If first bind (oldIll == null): just hold on new
            if (oldIll == null) {
                val newHold = (newIll.holdQty ?: zero).add(qtyToHold)
                newIll.holdQty = newHold
                inventoryLotLineRepository.save(newIll)
            }
        
            // Point suggestion to new lot line
            spl.suggestedLotLine = newIll
            suggestPickLotRepository.save(spl)
        
            // Update stock out line if provided
            if (req.stockOutLineId != null && req.stockOutLineId > 0) {
                val sol = stockOutLIneRepository.findById(req.stockOutLineId).orElse(null)
                if (sol != null) {
                    sol.inventoryLotLine = newIll
                    sol.item = pol.item
                    stockOutLIneRepository.save(sol)
                }
            }
        
            val newLotNo = newIll.inventoryLot?.lotNo ?: req.newInventoryLotNo
            return MessageResponse(
                id = null,
                name = "Lot substitution confirmed",
                code = "SUCCESS",
                type = "pickorder",
                message = "Updated suggestion and stock out line to new lot line with lotNo '$newLotNo'",
                errorPosition = null
            )
        }
        
        private fun resolveNewInventoryLotLine(
            req: LotSubstitutionConfirmRequest,
            itemId: Long
        ): InventoryLotLine? {
            // Prefer stockInLineId
            if (req.newStockInLineId != null && req.newStockInLineId > 0) {
                val inventoryLot = inventoryLotRepository.findByStockInLineIdAndDeletedFalse(req.newStockInLineId)
                    ?: return null
        
                val lotLines = inventoryLotLineRepository.findAllByInventoryLotId(inventoryLot.id!!)
                    .filter { it.inventoryLot?.item?.id == itemId && !it.deleted }
        
                return lotLines.firstOrNull()
            }
        
            // Fallback lotNo (req.newInventoryLotNo is non-null String in your model)
            if (req.newInventoryLotNo.isNotBlank()) {
                return inventoryLotLineRepository
                    .findFirstByInventoryLotLotNoAndInventoryLotItemIdAndDeletedFalseOrderByIdDesc(
                        req.newInventoryLotNo,
                        itemId
                    )
            }
        
            return null
        }

        private fun mapCompletedDoPickOrders(
            baseRecords: List<DoPickOrderRecord>,
            request: GetCompletedDoPickOrdersRequest
        ): List<CompletedDoPickOrderResponse> {
            return try {
                val normalizedTargetDate = request.targetDate
                    ?.takeIf { it.isNotBlank() }
                    ?.replace("-", "")
                println("request.targetDate: ${request.targetDate}")
                println("request.shopName: ${request.shopName}")
                println("request.deliveryNoteCode: ${request.deliveryNoteCode}")
                val completedRecords = baseRecords
                    .filter { record ->
                        val matchTargetDate = normalizedTargetDate.isNullOrBlank() ||
                            record.requiredDeliveryDate
                                ?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
                                ?.contains(normalizedTargetDate, ignoreCase = true) == true
                        val matchShop = request.shopName.isNullOrBlank() ||
                            record.shopName?.contains(request.shopName, ignoreCase = true) == true
                        val matchDeliveryNoteCode = request.deliveryNoteCode.isNullOrBlank() ||
                            record.deliveryNoteCode?.contains(request.deliveryNoteCode, ignoreCase = true) == true
                        val matchTruck = request.truckLanceCode.isNullOrBlank() ||
                            record.truckLanceCode?.contains(request.truckLanceCode!!, ignoreCase = true) == true
                        matchTargetDate && matchShop && matchDeliveryNoteCode && matchTruck
                    }
                    .filter { (it.recordId ?: 0L) > 0 }

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

                val recordIds = completedRecords.mapNotNull { it.recordId }.distinct()
                val lineRecords = doPickOrderLineRecordRepository.findByDoPickOrderIdInAndDeletedFalse(recordIds)
                val lineRecordsByRecordId = lineRecords.groupBy { it.doPickOrderId }

                val filteredRecords = completedRecords.sortedByDescending { it.ticketCompleteDateTime }

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

                val allPickOrderIds = lineRecords.mapNotNull { it.pickOrderId }.distinct()
                val pickOrdersById = pickOrderRepository.findAllById(allPickOrderIds).associateBy { it.id!! }

                val allDeliveryOrderIds = lineRecords.mapNotNull { it.doOrderId }.distinct()
                val deliveryOrdersById = deliveryOrderRepository.findAllById(allDeliveryOrderIds).associateBy { it.id!! }

                filteredRecords.map { record ->
                    val lines = lineRecordsByRecordId[record.recordId] ?: emptyList()
                    val pickOrderIds = lines.mapNotNull { it.pickOrderId }.distinct()
                    val pickOrderCodes = lines.mapNotNull { it.pickOrderCode }.distinct()
                    val deliveryOrderIds = lines.mapNotNull { it.doOrderId }.distinct()
                    val deliveryNos = lines.mapNotNull { it.deliveryOrderCode }.distinct()

                    val numberOfCartons = record.cartonQty ?: 0
                    val completedDateStr = record.ticketCompleteDateTime
                        ?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))

                    val representativePickOrder = pickOrderIds.firstOrNull()?.let { pickOrdersById[it] }
                    val representativeDelivery = deliveryOrderIds.firstOrNull()?.let { deliveryOrdersById[it] }
                    val shopAddress = representativeDelivery?.shop?.let { buildShopAddress(it) } ?: ""
                    val deliveryDateStr = record.requiredDeliveryDate
                        ?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
                    CompletedDoPickOrderResponse(
                        id = record.id ?: 0L,
                        doPickOrderRecordId = record.id ?: 0L,
                        recordId = record.recordId ?: 0L,
                        ticketNo = record.ticketNo.orEmpty(),
                        storeId = record.storeId,
                        completedDate = completedDateStr,
                        shopCode = record.shopCode,
                        shopName = record.shopName,
                        truckLanceCode = record.truckLanceCode,
                        departureTime = record.truckDepartureTime,
                        deliveryNoteCode = record.deliveryNoteCode,
                        pickOrderIds = pickOrderIds,
                        pickOrderCodes = pickOrderCodes,
                        deliveryOrderIds = deliveryOrderIds,
                        deliveryNos = deliveryNos,
                        pickOrderConsoCode = representativePickOrder?.consoCode,
                        pickOrderStatus = representativePickOrder?.status?.value,
                        deliveryDate = deliveryDateStr,
                        numberOfCartons = numberOfCartons,
                        shopId = record.shopId ?: representativeDelivery?.shop?.id,
                        shopAddress = shopAddress,
                        fgPickOrders = pickOrderIds.map { poId ->
                            FgPickOrderSummary(
                                pickOrderId = poId,
                                deliveryOrderId = deliveryOrderIds.firstOrNull()
                            )
                        },
                        handlerName = record.handlerName,
                    )
                }
            } catch (e: Exception) {
                //println("❌ Error in mapCompletedDoPickOrders: ${e.message}")
                e.printStackTrace()
                emptyList()
            }
        }

        open fun getCompletedDoPickOrders(
            userId: Long,
            request: GetCompletedDoPickOrdersRequest
        ): List<CompletedDoPickOrderResponse> {
            val baseRecords = doPickOrderRecordRepository
                .findByHandledByAndTicketStatusAndDeletedFalse(userId, DoPickOrderStatus.completed)
            return mapCompletedDoPickOrders(baseRecords, request)
        }

        open fun getCompletedDoPickOrdersAll(
            request: GetCompletedDoPickOrdersRequest
        ): List<CompletedDoPickOrderResponse> {
            val baseRecords = doPickOrderRecordRepository
                .findByTicketStatusAndDeletedFalse(DoPickOrderStatus.completed)
            return mapCompletedDoPickOrders(baseRecords, request)
        }


    private fun numToBigDecimal(n: Number?): BigDecimal {
        return when (n) {
            null -> BigDecimal.ZERO
            is BigDecimal -> n
            else -> BigDecimal.valueOf(n.toDouble())
        }
    }
  
    open fun getLotDetailsByDoPickOrderRecordId3(doPickOrderRecordId: Long): LotDetailsByDoPickOrderRecordResponse {
     //   println("=== Debug: getLotDetailsByDoPickOrderRecordId3 (Repository-based) ===")
        //println("doPickOrderRecordId: $doPickOrderRecordId")
        
        val zero = BigDecimal.ZERO
        val today = LocalDate.now()
        
        // 1) 获取 DoPickOrderRecord
        val dpor = doPickOrderRecordRepository.findById(doPickOrderRecordId).orElse(null)
            ?: return LotDetailsByDoPickOrderRecordResponse(
                fgInfo = null,
                pickOrders = emptyList()
            )
        
        //println("    Found DoPickOrderRecord: id=${dpor.id}, ticketNo=${dpor.ticketNo}")
        
        // 2) 构建 fgInfo
        val fgInfo = FgInfoResponse(
            doPickOrderId = dpor.id,
            ticketNo = dpor.ticketNo,
            storeId = dpor.storeId,
            shopCode = dpor.shopCode,
            shopName = dpor.shopName,
            truckLanceCode = dpor.truckLanceCode,
            departureTime = dpor.truckDepartureTime
        )
        
        // 3) 通过 DoPickOrderLineRecord 获取 pickOrderIds
        val lineRecords = doPickOrderLineRecordRepository.findByDoPickOrderIdAndDeletedFalse(dpor.recordId!!)
       // println("    Found ${lineRecords.size} DoPickOrderLineRecord records")
        
        var pickOrderIds = lineRecords.mapNotNull { it.pickOrderId }.distinct()
        
        // 备用方案：如果 lineRecords 中没有 pickOrderId，尝试使用 DoPickOrderRecord.pickOrderId
        if (pickOrderIds.isEmpty() && dpor.pickOrderId != null) {
           // println("  WARNING: No pickOrderIds in lineRecords, using DoPickOrderRecord.pickOrderId=${dpor.pickOrderId}")
            pickOrderIds = listOf(dpor.pickOrderId!!)
        }
        
        if (pickOrderIds.isEmpty()) {
            //println("❌ ERROR: No pickOrderIds found for doPickOrderRecordId=$doPickOrderRecordId")
            return LotDetailsByDoPickOrderRecordResponse(
                fgInfo = fgInfo,
                pickOrders = emptyList()
            )
        }
        
       // println("    Using pickOrderIds: $pickOrderIds")
        
        // 4) 批量加载所有相关数据
        val pickOrders = pickOrderRepository.findAllById(pickOrderIds)
            .filter { !it.deleted }
        
        if (pickOrders.isEmpty()) {
           // println("❌ ERROR: No pick orders found for ids: $pickOrderIds")
            return LotDetailsByDoPickOrderRecordResponse(
                fgInfo = fgInfo,
                pickOrders = emptyList()
            )
        }
        
       // println("    Found ${pickOrders.size} pick orders")
        
        // 5) 收集所有 pickOrderLineIds
        val allPickOrderLineIds = pickOrders
            .flatMap { it.pickOrderLines }
            .filter { !it.deleted }
            .mapNotNull { it.id }
        
       // println("    Found ${allPickOrderLineIds.size} pick order lines")
        
        // 6) 批量加载 suggestions 和 stock out lines
        val allSuggestions = if (allPickOrderLineIds.isNotEmpty()) {
            suggestPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds)
        } else {
            emptyList()
        }
        
        val allStockOutLines = if (allPickOrderLineIds.isNotEmpty()) {
            allPickOrderLineIds.flatMap { lineId ->
                stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(lineId)
            }
        } else {
            emptyList()
        }
        
        // 7) 按 pickOrderLineId 分组
        val suggestionsByLineId = allSuggestions.groupBy { it.pickOrderLine?.id }
        val stockOutLinesByLineId = allStockOutLines.groupBy { it.pickOrderLineId }
        
        // 8) 批量加载所有 inventoryLotLineIds
        val allInventoryLotLineIds = allSuggestions
            .mapNotNull { it.suggestedLotLine?.id }
            .distinct()
        
        val inventoryLotLinesMap = if (allInventoryLotLineIds.isNotEmpty()) {
            inventoryLotLineRepository.findAllById(allInventoryLotLineIds)
                .associateBy { it.id!! }
        } else {
            emptyMap()
        }
        
        // 9) 构建响应
        val pickOrderDtos = pickOrders.map { po ->
            val lineDtos = po.pickOrderLines
                .filter { !it.deleted }
                .map { pol ->
                    val lineId = pol.id
                    val item = pol.item
                    val uom = pol.uom
                    
                    // 获取该 line 的 suggestions 和 stock out lines
                    val suggestions = lineId?.let { suggestionsByLineId[it] } ?: emptyList()
                    val stockOutLines = lineId?.let { stockOutLinesByLineId[it] } ?: emptyList()
                    
                    // 构建 lots（合并相同 lot 的多个 suggestions）
                    val lotMap = mutableMapOf<Long?, LotDetailResponse>()
                    
                    suggestions.forEach { spl ->
                        val ill = spl.suggestedLotLine
                        if (ill != null && ill.id != null) {
                            val illId = ill.id!!
                            val illEntity = inventoryLotLinesMap[illId] ?: ill
                            val il = illEntity.inventoryLot
                            val w = illEntity.warehouse
                            val isExpired = il?.expiryDate?.let { exp -> exp.isBefore(today) } == true
                            
                            val availableQty = (illEntity.inQty ?: zero)
                                .minus(illEntity.outQty ?: zero)
                                .minus(illEntity.holdQty ?: zero)
                            
                            // 查找对应的 stock out line
                            val stockOutLine = stockOutLines.find { sol ->
                                sol.inventoryLotLineId == illId
                            }
                            
                            // 计算 actualPickQty
                            val actualPickQty = stockOutLine?.qty?.let { numToBigDecimal(it as? Number) }
                            
                            if (lotMap.containsKey(illId)) {
                                // 合并 requiredQty
                                val existing = lotMap[illId]!!
                                val newRequiredQty = (existing.requiredQty ?: zero).plus(spl.qty ?: zero)
                                lotMap[illId] = existing.copy(requiredQty = newRequiredQty)
                            } else {
                                lotMap[illId] = LotDetailResponse(
                                    id = illId,
                                    lotNo = il?.lotNo,
                                    expiryDate = il?.expiryDate,
                                    location = w?.code,
                                    stockUnit = illEntity.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A",
                                    availableQty = availableQty,
                                    requiredQty = spl.qty,
                                    actualPickQty = actualPickQty,
                                    inQty = illEntity.inQty,
                                    outQty = illEntity.outQty,
                                    holdQty = illEntity.holdQty,
                                    lotStatus = illEntity.status?.value,
                                    lotAvailability = when {
                                        isExpired -> "expired"
                                        stockOutLine?.status == "rejected" -> "rejected"
                                        illEntity.status?.value == "unavailable" -> "status_unavailable"
                                        availableQty <= zero -> "insufficient_stock"
                                        else -> "available"
                                    },
                                    processingStatus = when {
                                        stockOutLine?.status == "completed" -> "completed"
                                        stockOutLine?.status == "rejected" -> "rejected"
                                        else -> "pending"
                                    },
                                    suggestedPickLotId = spl.id,
                                    stockOutLineId = stockOutLine?.id,
                                    stockOutLineStatus = stockOutLine?.status,
                                    stockOutLineQty = stockOutLine?.qty?.let { numToBigDecimal(it as? Number) },
                                    router = RouterInfoResponse(
                                        id = null,
                                        index = w?.order.toString(),
                                        route = w?.code,
                                        area = w?.code
                                    )
                                )
                            }
                        }
                    }
                    
                    val lots = lotMap.values.toList()
                    
                    // 构建 stockouts（包括没有 lot 的）
                    val stockouts = stockOutLines.map { sol ->
                        val illId = sol.inventoryLotLineId
                        val ill = illId?.let { inventoryLotLinesMap[it] }
                        val il = ill?.inventoryLot
                        val w = ill?.warehouse
                        val available = if (ill == null) null else
                            (ill.inQty ?: zero)
                                .minus(ill.outQty ?: zero)
                                .minus(ill.holdQty ?: zero)
                        
                        StockOutDetailResponse(
                            id = sol.id,
                            status = sol.status,
                            qty = sol.qty?.let { numToBigDecimal(it as? Number) },
                            lotId = ill?.id,
                            lotNo = il?.lotNo ?: "",
                            location = w?.code ?: "",
                            availableQty = available,
                            noLot = (ill == null)
                        )
                    }
                    
                    PickOrderLineDetailResponse(
                        id = lineId,
                        requiredQty = pol.qty,
                        status = pol.status?.value,
                        item = ItemInfoResponse(
                            id = item?.id,
                            code = item?.code,
                            name = item?.name,
                            uomCode = uom?.code,
                            uomDesc = uom?.udfudesc,
                            uomShortDesc = uom?.udfShortDesc
                        ),
                        lots = lots,
                        stockouts = stockouts
                    )
                }
            
            // ✅ 添加排序逻辑：按楼层（1F > 2F/4F）和 router index 排序
            val sortedLineDtos = lineDtos.sortedWith(compareBy(
                { line ->
                    // 提取楼层排序值：1F = 1, 2F/4F = 2, 其他 = 3
                    val firstLot = line.lots.firstOrNull()
                    val routerIndex = firstLot?.router?.index
                    val floorSortValue = when (routerIndex) {
                        is String -> {
                            val parts = routerIndex.split("-")
                            if (parts.isNotEmpty()) {
                                val floorPart = parts[0].uppercase() // "1F", "2F", "4F"
                                when (floorPart) {
                                    "1F" -> 1
                                    "2F", "4F" -> 2
                                    else -> 3
                                }
                            } else {
                                3
                            }
                        }
                        else -> 3
                    }
                    floorSortValue
                },
                { line ->
                    // 提取数字部分：格式为 "store_id-number" (如 "2F-004")
                    val firstLot = line.lots.firstOrNull()
                    val routerIndex = firstLot?.router?.index
                    when (routerIndex) {
                        is String -> {
                            val parts = routerIndex.split("-")
                            if (parts.size > 1) {
                                parts.last().toIntOrNull() ?: 999999
                            } else {
                                routerIndex.toIntOrNull() ?: 999999
                            }
                        }
                        else -> 999999
                    }
                }
            ))
            
            PickOrderDetailResponse(
                pickOrderId = po.id,
                pickOrderCode = po.code,
                doOrderId = po.deliveryOrder?.id,
                deliveryOrderCode = po.deliveryOrder?.code,
                consoCode = po.consoCode,
                status = po.status?.value,
                targetDate = po.targetDate?.toLocalDate(),
                pickOrderLines = sortedLineDtos  
            )
        }
        
        //println("✅ Successfully built response with ${pickOrderDtos.size} pick orders")
        
        return LotDetailsByDoPickOrderRecordResponse(
            fgInfo = fgInfo,
            pickOrders = pickOrderDtos
        )
    }

    }

