package com.ffii.fpsms.modules.deliveryOrder.service

import com.ffii.core.utils.PdfUtils
import org.springframework.context.annotation.Lazy
import com.ffii.core.utils.ZebraPrinterUtil
import com.ffii.fpsms.m18.entity.M18DataLogRepository
import com.ffii.fpsms.m18.service.M18DataLogService
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrder
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderResponse
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderStatusRequest
import com.ffii.fpsms.modules.master.service.CurrencyService
import com.ffii.fpsms.modules.master.service.ShopService
import com.ffii.fpsms.modules.user.entity.UserRepository
import com.ffii.fpsms.modules.user.service.UserService
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.deliveryOrder.web.models.DoDetailResponse
import com.ffii.fpsms.modules.deliveryOrder.web.models.DoDetailLineResponse
import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleaseDoRequest
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderRequest
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderLineRequest
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService
import java.time.LocalDate
import java.math.BigDecimal
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import java.time.LocalDateTime
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder
import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus
import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDeliveryNoteRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDeliveryNoteRequest
import com.ffii.fpsms.modules.master.service.PrinterService
import java.time.format.DateTimeFormatter
import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository
import java.time.LocalTime
import com.ffii.fpsms.modules.pickOrder.entity.Truck
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.entity.StockOut
import com.ffii.fpsms.modules.stock.entity.StockOutLine
import com.ffii.fpsms.modules.stock.web.model.StockOutStatus
import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPolRequest
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import net.sf.jasperreports.engine.JasperCompileManager
import net.sf.jasperreports.engine.JasperExportManager
import net.sf.jasperreports.engine.JasperPrint
import org.springframework.core.io.ClassPathResource
import java.io.File
import java.io.FileNotFoundException
import com.ffii.core.support.JdbcDao;
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDNLabelsRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDNLabelsRequest
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.service.InventoryLotService
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService  //  添加这行
import com.ffii.fpsms.modules.deliveryOrder.web.models.*
import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository //  添加
import com.ffii.core.utils.CanonPrinterUtil
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import kotlin.collections.emptyMap
import com.ffii.fpsms.modules.master.print.A4PrintDriverRegistry
import com.ffii.core.response.RecordsRes
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfoLite
import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfoLiteDto
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo
import java.util.Locale
@Service
open class DeliveryOrderService(
    private val deliveryOrderRepository: DeliveryOrderRepository,
    private val shopService: ShopService,
    private val currencyService: CurrencyService,
    private val m18DataLogService: M18DataLogService,
    private val m18DataLogRepository: M18DataLogRepository,
    private val userService: UserService,
    private val userRepository: UserRepository,
    private val pickOrderService: PickOrderService,
    @Lazy private val doPickOrderService: DoPickOrderService,
    private val truckRepository: TruckRepository,
    private val pickOrderRepository: PickOrderRepository,
    private val suggestedPickLotService: SuggestedPickLotService,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val stockOutRepository: StockOutRepository,
    private val stockOutLineRepository: StockOutLIneRepository,
    private val pickOrderLineRepository: PickOrderLineRepository,
    private val printerService: PrinterService,
    private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
    private val purchaseOrderRepository: PurchaseOrderRepository,
    private val inventoryLotService: InventoryLotService,
    private val suggestedPickLotRepository: SuggestPickLotRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val jdbcDao: JdbcDao,
    private val pickExecutionIssueRepository: PickExecutionIssueRepository,
    private val doPickOrderRepository: DoPickOrderRepository,
    private val doPickOrderLineRepository: DoPickOrderLineRepository,
    private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository,
    private val itemsRepository: ItemsRepository
) {
    open fun searchDoLiteByPage(
    code: String?,
    shopName: String?,
    status: String?,
    estimatedArrivalDate: LocalDateTime?,
    pageNum: Int?,
    pageSize: Int?,
    truckLanceCode: String?
): RecordsRes<DeliveryOrderInfoLiteDto> {

        val page = (pageNum ?: 1) - 1
        val size = pageSize ?: 10
        val statusEnum = status?.let { s -> DeliveryOrderStatus.entries.find { it.value == s } }

        val etaStart = estimatedArrivalDate
        val etaEnd = estimatedArrivalDate?.plusDays(1)

        val searchTruckLanceCode = truckLanceCode?.ifBlank { null }?.lowercase()

        if (searchTruckLanceCode != null) {
            println("DEBUG: Filtering by truckLanceCode: $searchTruckLanceCode")

            // ✅ 优化：先从 truck 表找到匹配的 truck，获取 Store_id 和 shopId
            val matchingTrucks = truckRepository.findAllByTruckLanceCodeContainingAndDeletedFalse(searchTruckLanceCode)
            println("DEBUG: Found ${matchingTrucks.size} matching trucks")

            // 收集所有匹配的 Store_id 和 shopId
            val matchingStoreIds = matchingTrucks.mapNotNull { it.storeId }.distinct()
            val matchingShopIds = matchingTrucks.mapNotNull { it.shop?.id }.distinct()

            println("DEBUG: Matching storeIds: $matchingStoreIds")
            println("DEBUG: Matching shopIds: ${matchingShopIds.size} shops")

            // 根据 Store_id 确定需要过滤的 supplier codes
            // Store_id = "2F" → supplier code != "P06B"
            // Store_id = "4F" → supplier code = "P06B"
            val supplierCodesToInclude = mutableSetOf<String?>()
            val supplierCodesToExclude = mutableSetOf<String?>()

            if (matchingStoreIds.contains("2F")) {
                // 2F 只关心 P07 / P06D
                supplierCodesToInclude.addAll(listOf("P07", "P06D"))
                // 同时排除 P06B，避免混在 2F 结果里
                supplierCodesToExclude.add("P06B")
            }
            if (matchingStoreIds.contains("4F")) {
                // 4F 只关心 P06B
                supplierCodesToInclude.add("P06B")
            }

            // 查询符合条件的 DeliveryOrder（根据 supplier code 和 shopId 预过滤）
            // 注意：这里需要在 Repository 层面添加 supplier code 过滤
            // 或者先查询所有，然后在代码层面过滤

            val allResult = deliveryOrderRepository.searchDoLitePage(
                code = code?.ifBlank { null },
                shopName = shopName?.ifBlank { null },
                status = statusEnum,
                etaStart = etaStart,
                etaEnd = etaEnd,
                pageable = PageRequest.of(0, 100000)
            )

            println("DEBUG: Total records from DB before filtering: ${allResult.totalElements}")

            // ✅ 优化1: 批量查询所有 DeliveryOrder 实体
            val deliveryOrderIds = allResult.content.mapNotNull { it.id }
            val deliveryOrdersMap = deliveryOrderRepository.findAllById(deliveryOrderIds)
                .associateBy { it.id }

            println("DEBUG: Loaded ${deliveryOrdersMap.size} delivery orders in batch")

            // ✅ 优化2: 预过滤 - 根据 supplier code 和 shopId 过滤
            val preFilteredContent = allResult.content.filter { info ->
                val deliveryOrder = deliveryOrdersMap[info.id]
                val supplierCode = deliveryOrder?.supplier?.code

                // 检查 supplier code 是否匹配
                val supplierMatches = when {
                    supplierCodesToExclude.contains(supplierCode) -> false
                    supplierCodesToInclude.isNotEmpty() && !supplierCodesToInclude.contains(supplierCode) -> false
                    else -> true
                }

                // 如果提供了 shopId 过滤，也检查 shopId
                val shopMatches = if (matchingShopIds.isNotEmpty()) {
                    deliveryOrder?.shop?.id in matchingShopIds
                } else {
                    true  // 如果没有匹配的 shopId，不过滤
                }

                supplierMatches && shopMatches
            }

            println("DEBUG: Pre-filtered records: ${preFilteredContent.size} (from ${allResult.content.size})")

            // ✅ 优化3: 收集所有需要查询的 shopId 和日期组合（只处理预过滤后的记录）
            val shopIdAndDatePairs = preFilteredContent.mapNotNull { info ->
                val deliveryOrder = deliveryOrdersMap[info.id]
                val shopId = deliveryOrder?.shop?.id
                val estimatedArrivalDate = info.estimatedArrivalDate
                if (shopId != null && estimatedArrivalDate != null) {
                    val targetDate = estimatedArrivalDate.toLocalDate()
                    val dayAbbr = getDayOfWeekAbbr(targetDate)
                    val supplierCode = deliveryOrder.supplier?.code
                    val preferredFloor = when (supplierCode) {
                        "P06B" -> "4F"
                        "P07", "P06D" -> "2F"
                        else -> "2F" // 或者改成 null / 其他默认值，看你业务需要
                    }
                    Triple(shopId, preferredFloor, dayAbbr)
                } else {
                    null
                }
            }.distinct()

            println("DEBUG: Unique shopId/floor/day combinations: ${shopIdAndDatePairs.size}")

            // ✅ 优化4: 批量查询所有需要的 Truck
            val truckCache = mutableMapOf<Triple<Long, String, String>, Truck?>()
            shopIdAndDatePairs.forEach { (shopId, preferredFloor, dayAbbr) ->
                val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, preferredFloor, dayAbbr)
                val matchedTruck = if (trucks.isEmpty()) {
                    truckRepository.findByShopIdAndDeletedFalse(shopId)
                        .filter { it.storeId == preferredFloor }
                        .minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                } else {
                    trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                }
                truckCache[Triple(shopId, preferredFloor, dayAbbr)] = matchedTruck
            }

            println("DEBUG: Cached ${truckCache.size} truck lookups")

            // 处理预过滤后的记录：计算 truckLanceCode 并过滤
            val processedRecords = preFilteredContent.map { info ->
                val deliveryOrder = deliveryOrdersMap[info.id]
                val supplierCode = deliveryOrder?.supplier?.code
                val preferredFloor = when (supplierCode) {
                    "P06B" -> "4F"
                    "P07", "P06D" -> "2F"
                    else -> null
                }
                val shop = deliveryOrder?.shop
                val shopId = shop?.id
                val estimatedArrivalDate = info.estimatedArrivalDate

                val calculatedTruckLanceCode =
                    if (deliveryOrder != null && shopId != null && estimatedArrivalDate != null) {
                        val targetDate = estimatedArrivalDate.toLocalDate()
                        val dayAbbr = getDayOfWeekAbbr(targetDate)

                        // 从缓存中获取 Truck
                        val matchedTruck = truckCache[Triple(shopId, preferredFloor, dayAbbr)]
                        matchedTruck?.truckLanceCode
                    } else {
                        null
                    }

                DeliveryOrderInfoLiteDto(
                    id = info.id,
                    code = info.code,
                    orderDate = info.orderDate,
                    estimatedArrivalDate = info.estimatedArrivalDate,
                    status = info.status,
                    shopName = info.shopName,
                    supplierName = info.supplierName,
                    shopAddress = info.shopAddress,
                    truckLanceCode = calculatedTruckLanceCode
                )
            }.filter { dto ->
                val dtoTruckLanceCode = dto.truckLanceCode?.lowercase() ?: ""
                dtoTruckLanceCode.contains(searchTruckLanceCode)
            }

            // 计算准确的总数（过滤后的总数）
            val totalCount = processedRecords.size
            println("DEBUG: Final filtered records count: $totalCount")

            // 对处理后的记录进行分页
            val startIndex = page * size
            val endIndex = minOf(startIndex + size, processedRecords.size)

            val paginatedRecords = if (startIndex < processedRecords.size) {
                processedRecords.subList(startIndex, endIndex)
            } else {
                emptyList()
            }
 
            return RecordsRes(paginatedRecords, totalCount)
        } else {
            // 未提供 truckLanceCode：在 DB 層依允許的供應商分頁，避免先取 10 筆再過濾導致每頁顯示少於 pageSize
            val allowedSupplierCodes = listOf("P06B", "P07", "P06D")
            val result = deliveryOrderRepository.searchDoLitePageWithSupplierCodes(
                code = code?.ifBlank { null },
                shopName = shopName?.ifBlank { null },
                status = statusEnum,
                etaStart = etaStart,
                etaEnd = etaEnd,
                allowedSupplierCodes = allowedSupplierCodes,
                pageable = PageRequest.of(page.coerceAtLeast(0), size),
            )

            val records = result.content.map { info ->
                val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(info.id)
                val supplierCode = deliveryOrder?.supplier?.code
                val preferredFloor = when (supplierCode) {
                    "P06B" -> "4F"
                    "P07", "P06D" -> "2F"
                    else -> "2F"
                }
                val shop = deliveryOrder?.shop
                val shopId = shop?.id
                val estimatedArrivalDate = info.estimatedArrivalDate

                val calculatedTruckLanceCode =
                    if (deliveryOrder != null && shopId != null && estimatedArrivalDate != null) {
                        val targetDate = estimatedArrivalDate.toLocalDate()
                        val dayAbbr = getDayOfWeekAbbr(targetDate)
                        val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, preferredFloor, dayAbbr)

                        val matchedTruck = if (trucks.isEmpty()) {
                            truckRepository.findByShopIdAndDeletedFalse(shopId)
                                .filter { it.storeId == preferredFloor }
                                .minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                        } else {
                            trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                        }

                        matchedTruck?.truckLanceCode
                    } else {
                        null
                    }

                DeliveryOrderInfoLiteDto(
                    id = info.id,
                    code = info.code,
                    orderDate = info.orderDate,
                    estimatedArrivalDate = info.estimatedArrivalDate,
                    status = info.status,
                    shopName = info.shopName,
                    supplierName = info.supplierName,
                    shopAddress = info.shopAddress,
                    truckLanceCode = calculatedTruckLanceCode,
                )
            }

            return RecordsRes(records, result.totalElements.toInt())
        }


    }

    /**
     * 僅回傳依店鋪／ETA 從 truck 排程**推算後** `truckLanceCode` 為 null 或空白的送貨單（畫面上對應「車線-X」）。
     * 與 [searchDoLiteByPage] 帶一般車線關鍵字分開，避免 `車線-X` 在 truck 表無 shopId 時走舊邏輯漏單。
     */
    open fun searchDoLiteUnassignedTruckByPage(
        code: String?,
        shopName: String?,
        status: String?,
        estimatedArrivalDate: LocalDateTime?,
        pageNum: Int?,
        pageSize: Int?,
    ): RecordsRes<DeliveryOrderInfoLiteDto> {
        val page = (pageNum ?: 1) - 1
        val size = pageSize ?: 10
        val statusEnum = status?.let { s -> DeliveryOrderStatus.entries.find { it.value == s } }
        val etaStart = estimatedArrivalDate
        val etaEnd = estimatedArrivalDate?.plusDays(1)
        val allowedSupplierCodes = listOf("P06B", "P07", "P06D")

        val allResult = deliveryOrderRepository.searchDoLitePageWithSupplierCodes(
            code = code?.ifBlank { null },
            shopName = shopName?.ifBlank { null },
            status = statusEnum,
            etaStart = etaStart,
            etaEnd = etaEnd,
            allowedSupplierCodes = allowedSupplierCodes,
            pageable = PageRequest.of(0, 100_000),
        )

        val deliveryOrderIds = allResult.content.mapNotNull { it.id }
        val deliveryOrdersMap = deliveryOrderRepository.findAllById(deliveryOrderIds).associateBy { it.id }

        val processedRecords = allResult.content.map { info ->
            val deliveryOrder = deliveryOrdersMap[info.id]
            val supplierCode = deliveryOrder?.supplier?.code
            val preferredFloor = when (supplierCode) {
                "P06B" -> "4F"
                "P07", "P06D" -> "2F"
                else -> "2F"
            }
            val shop = deliveryOrder?.shop
            val shopId = shop?.id
            val infoEta = info.estimatedArrivalDate

            val calculatedTruckLanceCode =
                if (deliveryOrder != null && shopId != null && infoEta != null) {
                    val targetDate = infoEta.toLocalDate()
                    val dayAbbr = getDayOfWeekAbbr(targetDate)
                    val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, preferredFloor, dayAbbr)
                    val matchedTruck = if (trucks.isEmpty()) {
                        truckRepository.findByShopIdAndDeletedFalse(shopId)
                            .filter { it.storeId == preferredFloor }
                            .minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                    } else {
                        trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                    }
                    matchedTruck?.truckLanceCode
                } else {
                    null
                }

            DeliveryOrderInfoLiteDto(
                id = info.id,
                code = info.code,
                orderDate = info.orderDate,
                estimatedArrivalDate = info.estimatedArrivalDate,
                status = info.status,
                shopName = info.shopName,
                supplierName = info.supplierName,
                shopAddress = info.shopAddress,
                truckLanceCode = calculatedTruckLanceCode,
            )
        }.filter { dto -> dto.truckLanceCode.isNullOrBlank() }

        val totalCount = processedRecords.size
        val startIndex = page * size
        val endIndex = minOf(startIndex + size, processedRecords.size)
        val paginatedRecords = if (startIndex < processedRecords.size) {
            processedRecords.subList(startIndex, endIndex)
        } else {
            emptyList()
        }
        return RecordsRes(paginatedRecords, totalCount)
    }

        open fun findByM18DataLogId(m18DataLogId: Long): DeliveryOrder? {
            return deliveryOrderRepository.findTopByM18DataLogIdOrderByModifiedDesc(m18DataLogId)
        }

        open fun getDoList(): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findDeliveryOrderInfoByDeletedIsFalse();
        }

        /*
    open fun getDetailedDo(id: Long) : DeliveryOrder? {
        return deliveryOrderRepository.findByIdAndDeletedIsFalse(id);
    }
    */
        open fun getDetailedDo(id: Long): DoDetailResponse? {
            val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(id) ?: return null

            return DoDetailResponse(
                id = deliveryOrder.id!!,
                code = deliveryOrder.code ?: "",
                supplierCode = deliveryOrder.supplier?.code,
                shopCode = deliveryOrder.shop?.code,
                shopName = deliveryOrder.shop?.name,
                currencyCode = deliveryOrder.currency?.code,
                orderDate = deliveryOrder.orderDate,
                estimatedArrivalDate = deliveryOrder.estimatedArrivalDate,
                completeDate = deliveryOrder.completeDate,
                status = deliveryOrder.status?.value,
                deliveryOrderLines = deliveryOrder.deliveryOrderLines.map { line ->
                    DoDetailLineResponse(
                        id = line.id!!,
                        itemNo = line.itemNo,
                        qty = line.qty,
                        price = line.price,
                        status = line.status?.value,
                        itemName = line.item?.name,
                        uomCode = line.uom?.code,
                        uom = line.uom?.udfudesc,
                        shortUom = line.uom?.udfShortDesc
                    )
                }
            )
        }

        open fun searchByCode(code: String?): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndDeletedIsFalse(code);
        }

        open fun searchByShopName(shopName: String): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByShopNameContainsAndDeletedIsFalse(shopName);
        }

        open fun searchByStatus(status: DeliveryOrderStatus): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByStatusAndDeletedIsFalse(status);
        }

        open fun searchBetweenOrderDate(start: LocalDateTime?, end: LocalDateTime?): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findByOrderDateBetweenAndDeletedIsFalse(start, end);
        }


        open fun getFullList(): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllBy();
        }

        open fun searchAll(
            code: String?,
            shopName: String?,
            status: DeliveryOrderStatus?,
            orderStartDate: LocalDateTime?,
            orderEndDate: LocalDateTime?,
            estArrStartDate: LocalDateTime?,
            estArrEndDate: LocalDateTime?
        ): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndOrderDateBetweenAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
                code,
                shopName,
                status,
                orderStartDate,
                orderEndDate,
                estArrStartDate,
                estArrEndDate
            );
        }

        open fun searchWithoutStatus(
            code: String?,
            shopName: String?,
            orderStartDate: LocalDateTime?,
            orderEndDate: LocalDateTime?,
            estArrStartDate: LocalDateTime?,
            estArrEndDate: LocalDateTime?
        ): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndOrderDateBetweenAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
                code,
                shopName,
                orderStartDate,
                orderEndDate,
                estArrStartDate,
                estArrEndDate
            );
        }

        open fun searchWithoutEstArrDate(
            code: String?,
            shopName: String?,
            status: DeliveryOrderStatus?,
            orderStartDate: LocalDateTime?,
            orderEndDate: LocalDateTime?
        ): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndOrderDateBetweenAndDeletedIsFalse(
                code,
                shopName,
                status,
                orderStartDate,
                orderEndDate
            );
        }

        /*
    open fun searchDoForList(
        code: String?,
        shopName: String?,
        status: String?,              // 前端传过来的字符串（pending/receiving/completed 或空）
        orderFrom: LocalDateTime?,
        orderTo: LocalDateTime?,
        etaFrom: LocalDateTime?,
        etaTo: LocalDateTime?,

    ): Page<DoSearchRow> {

        val page = deliveryOrderRepository.search(
            code = if (code.isNullOrBlank()) null else code,
            shopName = if (shopName.isNullOrBlank()) null else shopName,
            status = if (status.isNullOrBlank()) null else status,
            orderFrom = orderFrom,
            orderTo = orderTo,
            etaFrom = etaFrom,
            etaTo = etaTo,
            pageable = pageable
        )

        return page.map { d ->
            DoSearchRow(
                id = d.id!!,
                code = d.code,
                shopName = d.shop?.name,
                supplierName = d.supplier?.name,
                orderDate = d.orderDate,
                estimatedArrivalDate = d.estimatedArrivalDate,
                status = d.status?.value,
                deliveryOrderLineCount = d.deliveryOrderLines.size  // 只要数量
            )
        }
    }
    */
        open fun searchWithoutEstArrDateAndStatus(
            code: String?,
            shopName: String?,
            orderStartDate: LocalDateTime?,
            orderEndDate: LocalDateTime?
        ): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndOrderDateBetweenAndDeletedIsFalse(
                code,
                shopName,
                orderStartDate,
                orderEndDate
            );
        }

        open fun searchWithoutOrderDate(
            code: String?,
            shopName: String?,
            status: DeliveryOrderStatus?,
            estArrStartDate: LocalDateTime?,
            estArrEndDate: LocalDateTime?
        ): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
                code,
                shopName,
                status,
                estArrStartDate,
                estArrEndDate
            );
        }

        open fun searchWithoutOrderDateAndStatus(
            code: String?,
            shopName: String?,
            estArrStartDate: LocalDateTime?,
            estArrEndDate: LocalDateTime?
        ): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
                code,
                shopName,
                estArrStartDate,
                estArrEndDate
            );
        }

        open fun searchWithoutDate(
            code: String?,
            shopName: String?,
            status: DeliveryOrderStatus?
        ): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndDeletedIsFalse(
                code,
                shopName,
                status
            );
        }

        open fun searchCodeAndShopName(code: String?, shopName: String?): List<DeliveryOrderInfo> {
            return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndDeletedIsFalse(code, shopName);
        }

        open fun getWarehouseOrderByItemId(itemId: Long): Int? {
            val inventoryLots = inventoryLotService.findByItemId(itemId)
            if (inventoryLots.isNotEmpty()) {
                val inventoryLotId = inventoryLots.first().id?.toInt()
                return inventoryLotId?.let { lotId ->
                    // 查询 warehouse 的 order 字段
                    val sql = """
                    SELECT w.`order` as warehouseOrder
                    FROM fpsmsdb.inventory_lot_line ill
                    JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
                    WHERE ill.inventoryLotId = :lotId
                    AND ill.deleted = false
                    ORDER BY w.`order` ASC
                    LIMIT 1
                """.trimIndent()
                    val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId))
                    if (result.isNotEmpty()) {
                        val orderString = result.first()["warehouseOrder"] as? String
                        // Extract the last 3 digits from format like "2F-001"
                        orderString?.let { order ->
                            // Try to extract numeric part after the last "-"
                            val parts = order.split("-")
                            if (parts.isNotEmpty()) {
                                val lastPart = parts.last().trim()
                                // Take last 3 characters if they are all digits
                                if (lastPart.length >= 3) {
                                    val lastThreeDigits = lastPart.takeLast(3)
                                    if (lastThreeDigits.all { it.isDigit() }) {
                                        return lastThreeDigits.toIntOrNull()
                                    }
                                }
                                // Fallback: try to parse the last part as Int
                                lastPart.toIntOrNull()
                            } else {
                                // Fallback: try to extract any 3-digit sequence from the end
                                val lastThreeDigits = order.takeLast(3)
                                if (lastThreeDigits.all { it.isDigit() }) {
                                    lastThreeDigits.toIntOrNull()
                                } else {
                                    null
                                }
                            }
                        }
                    } else null
                }
            }
            return null
        }

        //  新增方法2：获取 warehouse 的 code 字段（用于显示路由）
        open fun getWarehouseCodeByItemId(itemId: Long): String? {
            val inventoryLots = inventoryLotService.findByItemId(itemId)
            if (inventoryLots.isNotEmpty()) {
                val inventoryLotId = inventoryLots.first().id?.toInt()
                return inventoryLotId?.let { lotId ->
                    val sql = """
                    SELECT w.code as warehouseCode
                    FROM fpsmsdb.inventory_lot_line ill
                    JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
                    WHERE ill.inventoryLotId = :lotId
                    AND ill.deleted = false
                    ORDER BY w.`order` ASC
                    LIMIT 1
                """.trimIndent()
                    val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId))
                    if (result.isNotEmpty()) {
                        result.first()["warehouseCode"] as String?
                    } else null
                }
            }
            return null
        }

    open fun updateDeliveryOrderStatus(request: SaveDeliveryOrderStatusRequest): SaveDeliveryOrderResponse {
        val deliveryOrder = checkNotNull(
            request.id?.let { deliveryOrderRepository.findById(it).getOrNull() }
        ) { "ID must be set" }

        val status = checkNotNull(
            request.status?.let { status -> DeliveryOrderStatus.entries.find { it.value == status } }
        ) { "Status must be set" }

        deliveryOrder.apply {
            this.status = status
        }

        val savedDeliveryOrder = deliveryOrderRepository.save(deliveryOrder).let {
            SaveDeliveryOrderResponse(
                id = it.id,
                code = it.code,
                supplierCode = it.supplier?.code,
                shopCode = it.shop?.code,
                currencyCode = it.currency?.code,
                orderDate = it.orderDate,
                estimatedArrivalDate = it.estimatedArrivalDate,
                completeDate = it.completeDate,
                status = it.status?.value
            )
        }

        return savedDeliveryOrder
    }

    @Throws(IOException::class)
    @Transactional
    open fun saveDeliveryOrder(request: SaveDeliveryOrderRequest): SaveDeliveryOrderResponse {
        val deliveryOrder =
            request.id?.let { deliveryOrderRepository.findById(it).getOrDefault(DeliveryOrder()) } ?: DeliveryOrder()
        val supplier = request.m18SupplierId?.let { shopService.findVendorByM18Id(it) }
            ?: request.supplierId?.let { shopService.findById(it) }
        val shop = request.m18ShopId?.let { shopService.findShopByM18Id(it) }
            ?: request.shopId?.let { shopService.findById(it) }
        val currency = request.m18CurrencyId?.let { currencyService.findByM18Id(it) }
            ?: request.currencyId?.let { currencyService.findById(it) }
        val status = request.status?.let { status -> DeliveryOrderStatus.entries.find { it.value == status } }
        val m18DataLog = request.m18DataLogId?.let { m18DataLogRepository.findById(it).getOrNull() }
        val handler = request.handlerId?.let { userRepository.findById(it).getOrNull() }

        //Need check duplicate?
        deliveryOrder.apply {
            code = request.code
            this.supplier = supplier
            this.shop = shop
            this.currency = currency
            orderDate = request.orderDate
            estimatedArrivalDate = request.estimatedArrivalDate
            completeDate = request.completeDate
            this.status = status
            this.m18DataLog = m18DataLog
            this.handler = handler
            m18BeId = request.m18BeId
            this.deleted = request.deleted
        }

        val savedDeliveryOrder = deliveryOrderRepository.saveAndFlush(deliveryOrder).let {
            SaveDeliveryOrderResponse(
                id = it.id,
                code = it.code,
                supplierCode = it.supplier?.code,
                shopCode = it.shop?.code,
                currencyCode = it.currency?.code,
                orderDate = it.orderDate,
                estimatedArrivalDate = it.estimatedArrivalDate,
                completeDate = it.completeDate,
                status = it.status?.value
            )
        }

        return savedDeliveryOrder
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse {
        println(" DEBUG: Starting releaseDeliveryOrder for DO ID: ${request.id}, User ID: ${request.userId}")

        val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
            ?: throw NoSuchElementException("Delivery Order not found")

        println(" DEBUG: Found delivery order - ID: ${deliveryOrder.id}, Shop: ${deliveryOrder.shop?.code}, Status: ${deliveryOrder.status}")

        deliveryOrder.apply {
            status = DeliveryOrderStatus.PENDING
        }
        deliveryOrderRepository.save(deliveryOrder)

        val pols = deliveryOrder.deliveryOrderLines
            .filter { it.deleted != true }   // 只跳过 deleted=true（deleted 为空时也当作未删）
            .map {
                SavePickOrderLineRequest(
                    itemId = it.item?.id,
                    qty = it.qty ?: BigDecimal.ZERO,
                    uomId = it.uom?.id,
                )
            }
        val po = SavePickOrderRequest(
            doId = deliveryOrder.id,
            type = PickOrderType.DELIVERY_ORDER,
            targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(),
            pickOrderLine = pols
        )

        val createdPickOrder = pickOrderService.create(po)
        println("    DEBUG: Created pick order - ID: ${createdPickOrder.id}")

        val consoCode = pickOrderService.assignConsoCode()
        val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)

        if (pickOrderEntity != null) {
            pickOrderEntity.consoCode = consoCode
            pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
            pickOrderRepository.saveAndFlush(pickOrderEntity)
            println(" DEBUG: Assigned consoCode $consoCode to pick order ${createdPickOrder.id}")

            //  Debug: Check pick order lines
            println(" DEBUG: Pick order has ${pickOrderEntity.pickOrderLines?.size ?: 0} pick order lines")
            pickOrderEntity.pickOrderLines?.forEach { line ->
                println("    DEBUG: Pick order line - Item ID: ${line.item?.id}, Qty: ${line.qty}")
            }


            val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
            val suggestions = suggestedPickLotService.suggestionForPickOrderLines(
                SuggestedPickLotForPolRequest(pickOrderLines = lines)
            )

            val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
            println("    DEBUG: Saved ${saveSuggestedPickLots.size} suggested pick lots")
            val insufficientCount = suggestions.suggestedList.count { it.suggestedLotLine == null }
            if (insufficientCount > 0) {
                println("⚠️ WARNING: $insufficientCount items have insufficient stock (issues auto-created)")
            }

            //  Hold inventory quantities
            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 ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO)
                    }
                }
            }
            inventoryLotLineRepository.saveAll(inventoryLotLines)

            //  Create stock out record and pre-create stock out lines
            val stockOut = StockOut().apply {
                this.type = "do"
                this.consoPickOrderCode = consoCode
                this.status = StockOutStatus.PENDING.status
                this.handler = request.userId
            }
            val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

            //  Pre-create stock out lines for suggested lots
            saveSuggestedPickLots.forEach { lot ->
                val polId = lot.pickOrderLine?.id
                val illId = lot.suggestedLotLine?.id
                if (polId != null && illId != null) {
                    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 = StockOutLine().apply {
                                this.stockOut = savedStockOut
                                this.pickOrderLine = pickOrderLine
                                this.inventoryLotLine = inventoryLotLine
                                this.item = pickOrderLine.item
                                this.status = StockOutLineStatus.PENDING.status
                                this.qty = 0.0
                                this.type = "NOR"
                            }
                            stockOutLineRepository.save(line)
                        }
                    }
                }
            }
        }

        //  CREATE do_pick_order_record entries
        val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
        val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"))

        println("    DEBUG: Target date: $targetDate, Date prefix: $datePrefix")
        
        //  新逻辑：根据 supplier code 决定楼层
        //  如果 supplier code 是 "P06B"，使用 4F，否则使用 2F
        val supplierCode = deliveryOrder.supplier?.code
        val preferredFloor = when (supplierCode) {
            "P06B" -> "4F"
            "P07", "P06D" -> "2F"
            else -> "2F" // 或者改成 null / 其他默认值，看你业务需要
        }

        println("    DEBUG: Supplier code: $supplierCode, Preferred floor: $preferredFloor")

        //  查找 truck
        val truck = deliveryOrder.shop?.id?.let { shopId ->
            println("    DEBUG: Looking for truck with shop ID: $shopId")
            val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
            println("    DEBUG: Found ${trucks.size} trucks for shop $shopId")

            val preferredStoreId = when (preferredFloor) {
                "2F" -> "2F"
                "4F" -> "4F"
                "3F" -> "3F"
                else -> "2F"
            }
            // 优先选择匹配的 truck
            val selectedTruck = if (trucks.size > 1) {
                trucks.find { it.storeId == preferredStoreId }
                    ?: trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
            } else {
                trucks.firstOrNull()
            }

            println("    DEBUG: Selected truck: ID=${selectedTruck?.id}, StoreId=${selectedTruck?.storeId}")
            selectedTruck
        }

        //  检查 truck 和 preferredFloor 是否匹配
        val truckStoreId = truck?.storeId
        val expectedStoreId = when (preferredFloor) {
            "2F" -> "2F"
            "4F" -> "4F"
            "3F" -> "3F"
            else -> "2F"
        }

        val defaultTruckId = 5577L
        val effectiveTruck = truck ?: truckRepository.findById(defaultTruckId).orElse(null)
        if (effectiveTruck == null) {
            val errorMsg = "No matching truck for preferredFloor ($preferredFloor) and default truck $defaultTruckId not found. Skipping DO ${deliveryOrder.id}."
            println("⚠️ $errorMsg")
            throw IllegalStateException(errorMsg)
        }

        println(" DEBUG: Truck matches preferred floor - Truck Store: ${effectiveTruck.storeId}, Preferred: $preferredFloor")

        val usedDefaultTruck = (truck == null)
        val storeId: String? = if (usedDefaultTruck) null else "$preferredFloor/F"
        val loadingSequence = effectiveTruck.loadingSequence ?: 999

        println("    DEBUG: Creating DoPickOrder - Floor: $preferredFloor, Store: $storeId, Truck: ${effectiveTruck.id}")

        val doPickOrder = DoPickOrder(
            storeId = storeId,
            ticketNo = "TEMP-${System.currentTimeMillis()}",
            ticketStatus = DoPickOrderStatus.pending,
            truckId = effectiveTruck.id,
            pickOrderId = createdPickOrder.id,
            doOrderId = deliveryOrder.id,
            ticketReleaseTime = null,
            shopId = deliveryOrder.shop?.id,
            handlerName = null,
            handledBy = null,
            ticketCompleteDateTime = null,
            truckDepartureTime = effectiveTruck.departureTime,
            truckLanceCode = effectiveTruck.truckLanceCode,
            shopCode = deliveryOrder.shop?.code,
            shopName = deliveryOrder.shop?.name,
            requiredDeliveryDate = targetDate,
            createdBy = null,
            modifiedBy = null,
            pickOrderCode = createdPickOrder.code,
            deliveryOrderCode = deliveryOrder.code,
            loadingSequence = loadingSequence,
            releaseType = null
        )

        val savedDoPickOrder = doPickOrderService.save(doPickOrder)
        println("    DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}")

        return MessageResponse(
            id = deliveryOrder.id,
            code = deliveryOrder.code,
            name = deliveryOrder.shop?.name,
            type = null,
            message = null,
            errorPosition = null,
            entity = mapOf("status" to deliveryOrder.status?.value)
        )
    }

    open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String {
        try {
            val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId)
            val pickOrderLineIds = pickOrderLines.mapNotNull { it.id }
            val suggestedPickLots = suggestedPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds)

            val lotNumbers = suggestedPickLots
                .filter { it.pickOrderLine?.item?.id == itemId }
                .mapNotNull { it.suggestedLotLine?.inventoryLot?.lotNo }
                .distinct()

            return lotNumbers.joinToString(", ")
        } catch (e: Exception) {
            println("Error getting lot numbers for item $itemId in pick order $pickOrderId: ${e.message}")
            return ""
        }
    }


    // Delivery Note
    @Throws(IOException::class)
    @Transactional
    open fun exportDeliveryNote(request: ExportDeliveryNoteRequest): Map<String, Any> {

        // INPUT STREAM SETUP
        val DELIVERYNOTE_PDF = "DeliveryNote/DeliveryNotePDF.jrxml"
        val resource = ClassPathResource(DELIVERYNOTE_PDF)
        if (!resource.exists()) {
            throw FileNotFoundException("Report file not found: $DELIVERYNOTE_PDF")
        }

        val inputStream = resource.inputStream
        val deliveryNote = JasperCompileManager.compileReport(inputStream)

        val fields = mutableListOf<MutableMap<String, Any>>()
        val params = mutableMapOf<String, Any>()

        // DRAFT & RECORD SEPARATION
        if (request.isDraft) {
            return exportDeliveryNoteFromDraft(request, deliveryNote, fields, params)
        } else {
            return exportDeliveryNoteFromRecord(request, deliveryNote, fields, params)
        }
    }

    // DRAFT
    private fun exportDeliveryNoteFromDraft(
        request: ExportDeliveryNoteRequest,
        deliveryNote: net.sf.jasperreports.engine.JasperReport,
        fields: MutableList<MutableMap<String, Any>>,
        params: MutableMap<String, Any>
    ): Map<String, Any> {
        val doPickOrder = doPickOrderRepository.findById(request.doPickOrderId).orElseThrow {
            NoSuchElementException("DoPickOrder not found with ID: ${request.doPickOrderId}")
        }

        val pickOrderIds = if (doPickOrder.pickOrderId != null) {
            // SINGLE DELIVERY ORDER
            listOf(doPickOrder.pickOrderId!!)
        } else {
            // MERGED DELIVERY ORDERS (DOPICKORDERLINE)
            val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!)
            val orderIds = doPickOrderLines.mapNotNull { it.pickOrderId }.distinct()

            if (orderIds.isEmpty()) {
                throw IllegalStateException("DoPickOrder ${request.doPickOrderId} has no associated pick orders")
            }
            orderIds
        }

        val deliveryOrderIds = if (doPickOrder.doOrderId != null) {
            // SINGLE DELIVERY ORDER
            listOf(doPickOrder.doOrderId!!)
        } else {
            // MERGED DELIVERY ORDERS (DOPICKORDERLINE)
            val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!)
            val orderIds = doPickOrderLines.mapNotNull { it.doOrderId }.distinct()

            if (orderIds.isEmpty()) {
                throw IllegalStateException("DoPickOrder ${request.doPickOrderId} has no associated delivery orders")
            }
            orderIds
        }

        val deliveryNoteInfo = deliveryOrderIds.flatMap { deliveryOrderId ->
            deliveryOrderRepository.findDeliveryOrderInfoById(deliveryOrderId)
        }.toMutableList()

        if (deliveryNoteInfo.isEmpty()) {
            throw NoSuchElementException("Delivery orders not found for IDs: $deliveryOrderIds")
        }

        val pickOrderId = pickOrderIds.first()
        val truckNo = doPickOrder.truckLanceCode ?: ""
        val selectedPickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)

        val allLines = deliveryNoteInfo.flatMap { info ->
            info.deliveryOrderLines.map { line -> line }
        }

        val sortedLines = allLines.sortedBy { line ->
            line.itemId?.let { itemId ->
                getWarehouseOrderByItemId(itemId)
            } ?: Int.MAX_VALUE
        }
        val uniqueItemIds = sortedLines.mapNotNull { it.itemId }.distinct()
        val itemsMap = if (uniqueItemIds.isNotEmpty()) {
            itemsRepository.findAllById(uniqueItemIds).associateBy { it.id }
        } else {
            emptyMap()
        }

        sortedLines.forEach { line ->
            val field = mutableMapOf<String, Any>()

            field["sequenceNumber"] = (fields.size + 1).toString()
            field["itemNo"] = line.itemNo
            field["itemName"] = line.itemName ?: ""
            field["uom"] = line.uom ?: ""
            field["qty"] = line.qty.toString()
            field["shortName"] = line.uomShortDesc ?: ""

            val route = line.itemId?.let { itemId ->
                getWarehouseCodeByItemId(itemId)
            } ?: "-"
            field["route"] = route

            //USE SUGGESTED PICK LOTS
            val lotNo = line.itemId?.let { itemId ->
                pickOrderIds.mapNotNull { pickOrderId ->
                    val lots = getLotNumbersForPickOrderByItemId(itemId, pickOrderId)
                    lots.ifBlank { null }
                }.distinct().joinToString(", ")
            }?.ifBlank { "沒有庫存" } ?: "沒有庫存"
            field["lotNo"] = lotNo

            val signOff = line.itemId?.let { itemId ->
                val item = itemsMap[itemId]
                if (item?.isEgg == true) {
                    "簽署: __________"
                } else {
                    ""
                }
            } ?: ""
            field["signOff"] = signOff

            fields.add(field)
        }

        params["dnTitle"] = "送貨單(初稿)"
        params["colQty"] = "所需數量"
        params["totalCartonTitle"] = ""
        params["deliveryNoteCodeTitle"] = ""
        params["deliveryNoteCode"] = ""

        params["numOfCarton"] = request.numOfCarton.toString()
        if (params["numOfCarton"] == "0") {
            params["numOfCarton"] = ""
        }

        params["shopName"] = doPickOrder.shopName ?: deliveryNoteInfo[0].shopName ?: ""
        params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: ""
        params["deliveryDate"] =
            deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: ""
        params["truckNo"] = truckNo
        params["ShopPurchaseOrderNo"] = doPickOrder.deliveryOrderCode ?: deliveryNoteInfo.joinToString(", ") { it.code }
        params["FGPickOrderNo"] = doPickOrder.pickOrderCode ?: selectedPickOrder?.code ?: ""
        params["loadingSequence"] = doPickOrder.loadingSequence?.let {
            "裝載順序：$it"
        } ?: ""

        return mapOf(
            "report" to PdfUtils.fillReport(deliveryNote, fields, params),
            "filename" to deliveryNoteInfo.joinToString("_") { it.code }
        )
    }

    private fun exportDeliveryNoteFromRecord(
        request: ExportDeliveryNoteRequest,
        deliveryNote: net.sf.jasperreports.engine.JasperReport,
        fields: MutableList<MutableMap<String, Any>>,
        params: MutableMap<String, Any>
    ): Map<String, Any> {
    
        val doPickOrderRecord = doPickOrderRecordRepository.findById(request.doPickOrderId).orElseThrow {
            NoSuchElementException("DoPickOrderRecord not found with ID: ${request.doPickOrderId}")
        }
    
        val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderId(doPickOrderRecord.recordId)
    
        val pickOrderIds = doPickOrderLineRecords.mapNotNull { it.pickOrderId }.distinct()
        if (pickOrderIds.isEmpty()) {
            throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated pick orders")
        }
    
        val deliveryOrderIds = doPickOrderLineRecords.mapNotNull { it.doOrderId }.distinct()
        if (deliveryOrderIds.isEmpty()) {
            throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated delivery orders")
        }
    
        val deliveryNoteInfo = deliveryOrderIds.flatMap { deliveryOrderId ->
            deliveryOrderRepository.findDeliveryOrderInfoById(deliveryOrderId)
        }.toMutableList()
    
        val pickOrderId = pickOrderIds.first()
        val truckNo = doPickOrderRecord.truckLanceCode ?: ""
        val selectedPickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)
    
        val allLines = deliveryNoteInfo.flatMap { info ->
            info.deliveryOrderLines.map { line -> line }
        }
    
        val pickOrderLines = pickOrderIds.flatMap { pid ->
            pickOrderLineRepository.findAllByPickOrderId(pid)
        }
    
        val pickOrderLineIdsByItemId = pickOrderLines
            .groupBy { it.item?.id }
            .mapValues { (_, lines) -> lines.mapNotNull { it.id } }
    
        val allPickOrderLineIds = pickOrderLines.mapNotNull { it.id }
        val stockOutLinesByPickOrderLineId: Map<Long, List<StockOutLineInfo>> =
            if (allPickOrderLineIds.isNotEmpty()) {
                allPickOrderLineIds.associateWith { polId ->
                    stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId)
                }
            } else {
                emptyMap()
            }
    
        val doStoreFloorKey = doPickOrderRecord.storeId?.toString()?.trim()?.uppercase(Locale.ROOT)
            ?.replace("/", "")
            ?.replace(" ", "")
            ?: ""
    
        val uniqueItemIdsForSort = allLines.mapNotNull { it.itemId }.distinct()
        val itemsById = if (uniqueItemIdsForSort.isNotEmpty()) {
            itemsRepository.findAllById(uniqueItemIdsForSort).associateBy { it.id!! }
        } else {
            emptyMap()
        }
    
        val sortedLines = when (doStoreFloorKey) {
            "2F" -> allLines.sortedWith(
                compareBy(
                    { line -> itemsById[line.itemId]?.item_Order ?: Int.MAX_VALUE },
                    { line -> line.itemNo },
                ),
            )
            "4F" -> allLines.sortedBy { line ->
                line.itemId?.let { getWarehouseOrderByItemId(it) } ?: Int.MAX_VALUE
            }
            else -> allLines.sortedBy { line ->
                line.itemId?.let { getWarehouseOrderByItemId(it) } ?: Int.MAX_VALUE
            }
        }
    
        val allIllIds = stockOutLinesByPickOrderLineId.values
            .flatMap { lines -> lines.mapNotNull { it.inventoryLotLineId } }
            .distinct()
        val illById: Map<Long, InventoryLotLine> = if (allIllIds.isNotEmpty()) {
            inventoryLotLineRepository.findAllById(allIllIds).associateBy { it.id!! }
        } else {
            emptyMap()
        }
    
        val itemsMap = itemsById
    
        sortedLines.forEach { line ->
            val field = mutableMapOf<String, Any>()
    
            field["sequenceNumber"] = (fields.size + 1).toString()
            field["itemNo"] = line.itemNo
            field["itemName"] = line.itemName ?: ""
            field["uom"] = line.uom ?: ""
    
            val actualPickQty = if (line.itemId != null) {
                val pickOrderLineIdsForItem = pickOrderLineIdsByItemId[line.itemId] ?: emptyList()
                val totalQty = pickOrderLineIdsForItem.sumOf { polId ->
                    stockOutLinesByPickOrderLineId[polId].orEmpty().sumOf { it.qty }
                }
                totalQty.toString()
            } else {
                line.qty.toString()
            }
            field["qty"] = actualPickQty
    
            field["shortName"] = line.uomShortDesc ?: ""
    
            val route = line.itemId?.let { itemId ->
                routeFromStockOutsForItem(
                    itemId,
                    pickOrderLineIdsByItemId,
                    stockOutLinesByPickOrderLineId,
                    illById,
                ).takeIf { it != "-" } ?: getWarehouseCodeByItemId(itemId)
            } ?: "-"
            field["route"] = route
    
            val lotNo = line.itemId?.let { itemId ->
                val pickOrderLineIdsForItem = pickOrderLineIdsByItemId[itemId] ?: emptyList()
                val lotNumbers = pickOrderLineIdsForItem.flatMap { polId ->
                    stockOutLinesByPickOrderLineId[polId].orEmpty().mapNotNull { it.lotNo }
                }.distinct().joinToString(", ")
    
                lotNumbers.ifBlank { "沒有庫存" }
            } ?: "沒有庫存"
    
            field["lotNo"] = lotNo
    
            val signOff = line.itemId?.let { itemId ->
                val item = itemsMap[itemId]
                if (item?.isEgg == true) {
                    "簽署: __________"
                } else {
                    ""
                }
            } ?: ""
            field["signOff"] = signOff
    
            fields.add(field)
        }
    
        params["dnTitle"] = "送貨單"
        params["colQty"] = "已提數量"
        params["totalCartonTitle"] = "總箱數："
        params["deliveryNoteCodeTitle"] = "送貨單編號："
        params["deliveryNoteCode"] = doPickOrderRecord.deliveryNoteCode ?: ""
    
        params["numOfCarton"] = request.numOfCarton.toString()
        if (params["numOfCarton"] == "0") {
            params["numOfCarton"] = ""
        }
    
        params["shopName"] = doPickOrderRecord.shopName ?: deliveryNoteInfo[0].shopName ?: ""
        params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: ""
        params["deliveryDate"] =
            deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: ""
        params["truckNo"] = truckNo
        params["ShopPurchaseOrderNo"] =
            doPickOrderRecord.deliveryOrderCode ?: deliveryNoteInfo.joinToString(", ") { it.code }
        params["FGPickOrderNo"] = doPickOrderRecord.pickOrderCode ?: selectedPickOrder?.code ?: ""
        params["loadingSequence"] = doPickOrderRecord.loadingSequence?.let {
            "裝載順序：$it"
        } ?: ""
    
        return mapOf(
            "report" to PdfUtils.fillReport(deliveryNote, fields, params),
            "filename" to deliveryNoteInfo.joinToString("_") { it.code }
        )
    }
    
    private fun routeFromStockOutsForItem(
        itemId: Long,
        pickOrderLineIdsByItemId: Map<Long?, List<Long>>,
        stockOutLinesByPickOrderLineId: Map<Long, List<StockOutLineInfo>>,
        illById: Map<Long, InventoryLotLine>,
    ): String {
        val polIds = pickOrderLineIdsByItemId[itemId] ?: return "-"
        val codes = linkedSetOf<String>()
        for (polId in polIds) {
            for (sol in stockOutLinesByPickOrderLineId[polId].orEmpty()) {
                val illId = sol.inventoryLotLineId ?: continue
                val ill = illById[illId] ?: continue
                ill.warehouse?.code?.takeIf { it.isNotBlank() }?.let { codes.add(it) }
            }
        }
        return if (codes.isEmpty()) "-" else codes.joinToString(", ")
    }
    //Print Delivery Note
    @Transactional
    open fun printDeliveryNote(request: PrintDeliveryNoteRequest) {
        val printer =
            printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer")

        val pdf = exportDeliveryNote(
            ExportDeliveryNoteRequest(
                doPickOrderId = request.doPickOrderId,
                numOfCarton = request.numOfCarton,
                isDraft = request.isDraft
            )
        )

        val jasperPrint = pdf["report"] as JasperPrint

        val tempPdfFile = File.createTempFile("print_job_", ".pdf")

        try {
            JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath)

            val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty

            printer.ip?.let { ip ->
                val port = printer.port ?: 9100
                val driver = A4PrintDriverRegistry.getDriver(printer.brand)
                driver.print(tempPdfFile, ip, port, printQty)
            }
            updateRecordCartonQty(request.doPickOrderId, request.numOfCarton)
        } finally {
            //tempPdfFile.delete()
        }

    }

    //Carton Labels
    open fun exportDNLabels(request: ExportDNLabelsRequest): Map<String, Any> {
        val DNLABELS_PDF = "DeliveryNote/DeliveryNoteCartonLabelsPDF.jrxml"
        val resource = ClassPathResource(DNLABELS_PDF)
        if (!resource.exists()) {
            throw FileNotFoundException("Label file not found: $DNLABELS_PDF")
        }

        val doPickOrderRecord = doPickOrderRecordRepository.findById(request.doPickOrderId).orElseThrow {
            NoSuchElementException("DoPickOrderRecord not found with ID: ${request.doPickOrderId}")
        }

        val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderId(doPickOrderRecord.recordId)

        val deliveryOrderIds = doPickOrderLineRecords.mapNotNull { it.doOrderId }.distinct()
        if (deliveryOrderIds.isEmpty()) {
            throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated delivery orders")
        }

        val inputStream = resource.inputStream
        val cartonLabel = JasperCompileManager.compileReport(inputStream)
        val cartonLabelInfo = deliveryOrderIds.flatMap { deliveryOrderId ->
            deliveryOrderRepository.findDeliveryOrderInfoById(deliveryOrderId)
        }.toMutableList()

        val params = mutableMapOf<String, Any>()
        val fields = mutableListOf<MutableMap<String, Any>>()

        for (info in cartonLabelInfo) {
            val field = mutableMapOf<String, Any>()
        }

        if (cartonLabelInfo.size > 1) {

        }

        params["shopPurchaseOrderNo"] = if (deliveryOrderIds.size > 0) {
            "請查閲送貨單（採購單共${deliveryOrderIds.size}張）"
        } else {
            doPickOrderRecord.deliveryOrderCode ?: cartonLabelInfo[0].code
        }
        params["deliveryNoteCode"] = doPickOrderRecord.deliveryNoteCode ?: ""
        params["shopAddress"] = cartonLabelInfo[0].shopAddress ?: ""
        val rawShopLabel = doPickOrderRecord.shopName ?: cartonLabelInfo[0].shopName ?: ""
        val parsedShopLabel = parseShopLabelForCartonLabel(rawShopLabel)
        params["shopCode"] = parsedShopLabel.shopCode
        params["shopCodeAbbr"] = parsedShopLabel.shopCodeAbbr
        params["shopName"] = parsedShopLabel.shopNameForLabel
        params["truckNo"] = doPickOrderRecord.truckLanceCode ?: ""

        for (cartonNumber in 1..request.numOfCarton) {
            val field = mutableMapOf<String, Any>()
            fields.add(field)
        }

        return mapOf(
            "report" to PdfUtils.fillReport(cartonLabel, fields, params),
            "filename" to "${cartonLabelInfo[0].code}_carton_labels"
        )
    }

    private data class ParsedShopLabelForCartonLabel(
        val shopCode: String,
        val shopCodeAbbr: String,
        val shopNameForLabel: String
    )

    private fun parseShopLabelForCartonLabel(rawInput: String): ParsedShopLabelForCartonLabel {
        // Fixed input format: shopCode - shopName1-shopName2
        val raw = rawInput.trim()

        val (shopCodePartRaw, restPart) = raw.split(" - ", limit = 2).let { parts ->
            (parts.getOrNull(0)?.trim().orEmpty()) to (parts.getOrNull(1)?.trim().orEmpty())
        }

        val shopCode = shopCodePartRaw.let { code ->
            val trimmed = code.trim()
            if (trimmed.length > 5) trimmed.substring(0, 5) else trimmed
        }

        val (shopName1, shopName2) = restPart.split("-", limit = 2).let { parts ->
            (parts.getOrNull(0)?.trim().orEmpty()) to (parts.getOrNull(1)?.trim().orEmpty())
        }

        val shopNameForLabel = if (shopName2.isNotBlank()) {
            "$shopName1\n$shopName2"
        } else {
            shopName1
        }

        val shopCodeAbbr = if (shopCode.length >= 2) shopCode.substring(0, 2) else shopCode

        return ParsedShopLabelForCartonLabel(
            shopCode = shopCode,
            shopCodeAbbr = shopCodeAbbr,
            shopNameForLabel = shopNameForLabel
        )
    }


    //Print Carton Labels
    @Transactional
    open fun printDNLabels(request: PrintDNLabelsRequest) {
        val printer =
            printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer")

        val pdf = exportDNLabels(
            ExportDNLabelsRequest(
                doPickOrderId = request.doPickOrderId,
                numOfCarton = request.numOfCarton
            )
        )

        val jasperPrint = pdf["report"] as JasperPrint

        val tempPdfFile = File.createTempFile("print_job_", ".pdf")

        try {
            JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath)


            val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty
            printer.ip?.let { ip ->
                printer.port?.let { port ->
                    ZebraPrinterUtil.printPdfToZebra(
                        tempPdfFile,
                        ip,
                        port,
                        printQty,
                        ZebraPrinterUtil.PrintDirection.ROTATED,
                        printer.dpi
                    )
                }
            }
            updateRecordCartonQty(request.doPickOrderId, request.numOfCarton)

            println("Test PDF saved to: ${tempPdfFile.absolutePath}")

        } finally {
            //tempPdfFile.delete()
        }

    }

    private fun updateRecordCartonQty(doPickOrderRecordId: Long, cartonQty: Int) {
        if (cartonQty <= 0) return
        val record = doPickOrderRecordRepository.findById(doPickOrderRecordId).orElse(null) ?: return
        record.cartonQty = cartonQty
        doPickOrderRecordRepository.save(record)
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDoResult {
        println("    DEBUG: Starting releaseDeliveryOrderWithoutTicket for DO ID: ${request.id}")

        val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
            ?: throw NoSuchElementException("Delivery Order not found")

        //  检查状态，跳过已完成或已发布的DO
        if (deliveryOrder.status == DeliveryOrderStatus.COMPLETED || deliveryOrder.status == DeliveryOrderStatus.RECEIVING) {
            throw IllegalStateException("Delivery Order ${deliveryOrder.id} is already ${deliveryOrder.status?.value}, skipping release")
        }

        //  更新状态为released (使用RECEIVING表示已发布)
        deliveryOrder.apply {
            status = DeliveryOrderStatus.RECEIVING  // 使用RECEIVING表示已发布状态
        }
        deliveryOrderRepository.save(deliveryOrder)

        // 创建 pick order
        val pols = deliveryOrder.deliveryOrderLines
        .filter { it.deleted != true }   // 只跳过 deleted=true（deleted 为空时也当作未删）
        .map {
            SavePickOrderLineRequest(
                itemId = it.item?.id,
                qty = it.qty ?: BigDecimal.ZERO,
                uomId = it.uom?.id,
            )
        }
        val po = SavePickOrderRequest(
            doId = deliveryOrder.id,
            type = PickOrderType.DELIVERY_ORDER,
            targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(),
            pickOrderLine = pols
        )

        val createdPickOrder = pickOrderService.create(po)
        val consoCode = pickOrderService.assignConsoCode()
        val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)

        if (pickOrderEntity != null) {
            pickOrderEntity.consoCode = consoCode
            pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
            pickOrderRepository.saveAndFlush(pickOrderEntity)
              // 创建 stock out
              val stockOut = StockOut().apply {
                this.type = "do"
                this.consoPickOrderCode = consoCode
                this.status = StockOutStatus.PENDING.status
                this.handler = request.userId
            }
            val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

            // 创建 suggestions 和 hold inventory
            val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
            val suggestions = suggestedPickLotService.suggestionForPickOrderLines(
                SuggestedPickLotForPolRequest(pickOrderLines = lines)
            )

            val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
            val insufficientCount = suggestions.suggestedList.count { it.suggestedLotLine == null }
            if (insufficientCount > 0) {
                println(" WARNING: $insufficientCount items have insufficient stock (issues auto-created)")
            }
            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 ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO)
                    }
                }
            }
            inventoryLotLineRepository.saveAll(inventoryLotLines)
            val pickOrderLineMap = lines.associateBy { it.id }
            val inventoryLotLineMap = inventoryLotLines.associateBy { it.id }
          
            // No-lot (insufficient stock) lines are created in suggestedPickLotService.saveAll → createStockOutLineForSuggestion; skip here to avoid duplicates.
            saveSuggestedPickLots.forEach { lot ->
                if (lot.suggestedLotLine == null) return@forEach
                val polId = lot.pickOrderLine?.id
                val illId = lot.suggestedLotLine?.id
                if (polId != null) {
                    val pickOrderLine = pickOrderLineMap[polId]
val inventoryLotLine = illId?.let { inventoryLotLineMap[it] }

                    if (pickOrderLine != null) {
                        val line = StockOutLine().apply {
                            this.stockOut = savedStockOut
                            this.pickOrderLine = pickOrderLine
                            this.inventoryLotLine = inventoryLotLine  // 可能为 null
                            this.item = pickOrderLine.item
                            //  修复：根据是否有 inventoryLotLine 设置状态
                            this.status = if (inventoryLotLine == null) {
                                StockOutLineStatus.PARTIALLY_COMPLETE.status  // 没有库存批次时使用 PARTIALLY_COMPLETE
                            } else {
                                StockOutLineStatus.PENDING.status  // 有正常库存批次时使用 PENDING
                            }
                            this.qty = 0.0
                            this.type = "NOR"
                        }
                        stockOutLineRepository.save(line)
                    }
                }
            }
        }

        //  新逻辑：根据 supplier code 决定楼层
        //  如果 supplier code 是 "P06B"，使用 4F，否则使用 2F
        val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
        val supplierCode = deliveryOrder.supplier?.code
        val preferredFloor = when (supplierCode) {
            "P06B" -> "4F"
            "P07", "P06D" -> "2F"
            else -> "2F" // 或者改成 null / 其他默认值，看你业务需要
        }

        println("    DEBUG: Floor calculation for DO ${deliveryOrder.id}")
        println("   - Supplier code: $supplierCode")
        println("   - Preferred floor: $preferredFloor")

        val truck = deliveryOrder.shop?.id?.let { shopId ->
            val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
            val preferredStoreId = when (preferredFloor) {
                "2F" -> "2F"
                "4F" -> "4F"
                "3F" -> "3F"
                else -> "2F"
            }

            val matchedTrucks = trucks.filter { it.storeId == preferredStoreId }

            if (matchedTrucks.isEmpty()) {
                null
            } else {
                if (preferredStoreId == "4F" && matchedTrucks.size > 1) {
                    deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate ->
                        val targetDate = estimatedArrivalDate.toLocalDate()
                        val dayOfWeek = targetDate.dayOfWeek

                        val dayAbbr = when (dayOfWeek) {
                            java.time.DayOfWeek.MONDAY -> "Mon"
                            java.time.DayOfWeek.TUESDAY -> "Tue"
                            java.time.DayOfWeek.WEDNESDAY -> "Wed"
                            java.time.DayOfWeek.THURSDAY -> "Thu"
                            java.time.DayOfWeek.FRIDAY -> "Fri"
                            java.time.DayOfWeek.SATURDAY -> "Sat"
                            java.time.DayOfWeek.SUNDAY -> "Sun"
                        }

                        println("    DEBUG: DO ${deliveryOrder.id} - Target date: $targetDate ($dayAbbr), Shop: $shopId")
                        println("    DEBUG: Found ${matchedTrucks.size} matched 4F trucks")

                        val dayMatchedTrucks = matchedTrucks.filter {
                            it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true
                        }

                        println("    DEBUG: Found ${dayMatchedTrucks.size} trucks matching $dayAbbr")
                        dayMatchedTrucks.forEach { t ->
                            println("   - Truck ID=${t.id}, Code=${t.truckLanceCode}, Time=${t.departureTime}")
                        }

                        if (dayMatchedTrucks.isNotEmpty()) {
                            val selected = dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                            println("✅ DEBUG: Selected truck matching $dayAbbr - ID=${selected?.id}, Code=${selected?.truckLanceCode}")
                            selected
                        } else {
                            println("⚠️ WARNING: No truck matching $dayAbbr, using first available truck")
                            matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                        }
                    } ?: run {
                        matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                    }
                } else {
                    matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                }
            }

        }
        val defaultTruckId = 5577L
        val effectiveTruck = truck ?: truckRepository.findById(defaultTruckId).orElse(null) 
        val usedDefaultTruck = (truck == null)
        if (effectiveTruck == null) {
            val errorMsg = "No matching truck for preferredFloor ($preferredFloor) and default truck $defaultTruckId not found. Skipping DO ${deliveryOrder.id}."
            println("⚠️ $errorMsg")
            throw IllegalStateException(errorMsg)
        }

        println(" DEBUG: Matched truck - ID=${effectiveTruck.id}, Store=${effectiveTruck.storeId}, Floor=$preferredFloor")

        return ReleaseDoResult(
            deliveryOrderId = deliveryOrder.id!!,
            deliveryOrderCode = deliveryOrder.code,
            pickOrderId = createdPickOrder.id!!,
            pickOrderCode = pickOrderEntity?.code,
            shopId = deliveryOrder.shop?.id,
            shopCode = deliveryOrder.shop?.code,
            shopName = deliveryOrder.shop?.name,
            estimatedArrivalDate = targetDate,
            preferredFloor = preferredFloor,
            truckId = effectiveTruck.id,
            truckDepartureTime = effectiveTruck.departureTime,
            truckLanceCode = effectiveTruck.truckLanceCode,
            loadingSequence = effectiveTruck.loadingSequence,
            usedDefaultTruck = usedDefaultTruck

        )
    }

    /**
     * Workbench NO-HOLD release:
     * - Create pick_order + stock_out
     * - Do NOT create suggested_pick_lot / stock_out_line
     * - Do NOT update inventory_lot_line.holdQty
     *
     * Downstream should be handled by workbench services (no-hold suggestion + stock_out_line creation).
     */
    @Transactional(rollbackFor = [Exception::class])
    open fun releaseDeliveryOrderWithoutTicketNoHold(request: ReleaseDoRequest): ReleaseDoResult {
        println("    DEBUG: Starting releaseDeliveryOrderWithoutTicketNoHold for DO ID: ${request.id}")

        val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
            ?: throw NoSuchElementException("Delivery Order not found")

        if (deliveryOrder.status == DeliveryOrderStatus.COMPLETED || deliveryOrder.status == DeliveryOrderStatus.RECEIVING) {
            throw IllegalStateException("Delivery Order ${deliveryOrder.id} is already ${deliveryOrder.status?.value}, skipping release")
        }

        // Mark released (same semantics as normal release)
        deliveryOrder.status = DeliveryOrderStatus.RECEIVING
        deliveryOrderRepository.save(deliveryOrder)

        // Create pick order (same as normal release)
        val pols = deliveryOrder.deliveryOrderLines
            .filter { it.deleted != true }
            .map {
                SavePickOrderLineRequest(
                    itemId = it.item?.id,
                    qty = it.qty ?: BigDecimal.ZERO,
                    uomId = it.uom?.id,
                )
            }
        val po = SavePickOrderRequest(
            doId = deliveryOrder.id,
            type = PickOrderType.DELIVERY_ORDER,
            targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(),
            pickOrderLine = pols
        )

        val createdPickOrder = pickOrderService.create(po)
        val consoCode = pickOrderService.assignConsoCode()
        val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)

        if (pickOrderEntity != null) {
            pickOrderEntity.consoCode = consoCode
            pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
            pickOrderRepository.saveAndFlush(pickOrderEntity)

            // Create stock out header only; stock_out_line is created by workbench service
            val stockOut = StockOut().apply {
                this.type = "do"
                this.consoPickOrderCode = consoCode
                this.status = StockOutStatus.PENDING.status
                this.handler = request.userId
            }
            stockOutRepository.saveAndFlush(stockOut)
        }

        // Truck selection (reuse normal logic)
        val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
        val supplierCode = deliveryOrder.supplier?.code
        val preferredFloor = when (supplierCode) {
            "P06B" -> "4F"
            "P07", "P06D" -> "2F"
            else -> "2F"
        }

        val truck = deliveryOrder.shop?.id?.let { shopId ->
            val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
            val preferredStoreId = when (preferredFloor) {
                "2F" -> "2F"
                "4F" -> "4F"
                "3F" -> "3F"
                else -> "2F"
            }

            val matchedTrucks = trucks.filter { it.storeId == preferredStoreId }
            if (matchedTrucks.isEmpty()) {
                null
            } else {
                if (preferredStoreId == "4F" && matchedTrucks.size > 1) {
                    deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate ->
                        val targetDate2 = estimatedArrivalDate.toLocalDate()
                        val dayAbbr = when (targetDate2.dayOfWeek) {
                            java.time.DayOfWeek.MONDAY -> "Mon"
                            java.time.DayOfWeek.TUESDAY -> "Tue"
                            java.time.DayOfWeek.WEDNESDAY -> "Wed"
                            java.time.DayOfWeek.THURSDAY -> "Thu"
                            java.time.DayOfWeek.FRIDAY -> "Fri"
                            java.time.DayOfWeek.SATURDAY -> "Sat"
                            java.time.DayOfWeek.SUNDAY -> "Sun"
                        }

                        val dayMatchedTrucks = matchedTrucks.filter {
                            it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true
                        }

                        if (dayMatchedTrucks.isNotEmpty()) {
                            dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                        } else {
                            matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                        }
                    } ?: run {
                        matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                    }
                } else {
                    matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                }
            }
        }

        val defaultTruckId = 5577L
        val effectiveTruck = truck ?: truckRepository.findById(defaultTruckId).orElse(null)
        val usedDefaultTruck = (truck == null)
        if (effectiveTruck == null) {
            val errorMsg = "No matching truck for preferredFloor ($preferredFloor) and default truck $defaultTruckId not found. Skipping DO ${deliveryOrder.id}."
            throw IllegalStateException(errorMsg)
        }

        return ReleaseDoResult(
            deliveryOrderId = deliveryOrder.id!!,
            deliveryOrderCode = deliveryOrder.code,
            pickOrderId = createdPickOrder.id!!,
            pickOrderCode = pickOrderEntity?.code,
            shopId = deliveryOrder.shop?.id,
            shopCode = deliveryOrder.shop?.code,
            shopName = deliveryOrder.shop?.name,
            estimatedArrivalDate = targetDate,
            preferredFloor = preferredFloor,
            truckId = effectiveTruck.id,
            truckDepartureTime = effectiveTruck.departureTime,
            truckLanceCode = effectiveTruck.truckLanceCode,
            loadingSequence = effectiveTruck.loadingSequence,
            usedDefaultTruck = usedDefaultTruck
        )
    }

    private fun getDayOfWeekAbbr(date: LocalDate): String =
        when (date.dayOfWeek) {
            java.time.DayOfWeek.MONDAY -> "Mon"
            java.time.DayOfWeek.TUESDAY -> "Tue"
            java.time.DayOfWeek.WEDNESDAY -> "Wed"
            java.time.DayOfWeek.THURSDAY -> "Thu"
            java.time.DayOfWeek.FRIDAY -> "Fri"
            java.time.DayOfWeek.SATURDAY -> "Sat"
            java.time.DayOfWeek.SUNDAY -> "Sun"
        }

    open fun check4FTruckAvailabilityForDoList(doIds: List<Long>): Check4FTruckBatchResponse {
        val problems = mutableListOf<ProblemDoDto>()

        doIds.forEach { doId ->
            val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId) ?: return@forEach
            val shopId = deliveryOrder.shop?.id ?: return@forEach
            val eta = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: return@forEach

            // 1) Check if this DO is a 4F order (your “all items on 4F, no 2F” rule)
            val sql = """
            SELECT 
                COUNT(DISTINCT CASE WHEN i.store_id = '4F' THEN dol.itemId END) AS count_4f,
                COUNT(DISTINCT CASE WHEN i.store_id = '2F' THEN dol.itemId END) AS count_2f,
                COUNT(DISTINCT dol.itemId) AS total_items
            FROM fpsmsdb.delivery_order_line dol
            INNER JOIN fpsmsdb.items i ON i.id = dol.itemId AND i.deleted = 0
            WHERE dol.deliveryOrderId = :doId AND dol.deleted = 0
        """.trimIndent()

            val res = jdbcDao.queryForList(sql, mapOf("doId" to doId)).firstOrNull() ?: return@forEach
            val count4F = (res["count_4f"] as? Number)?.toInt() ?: 0
            val count2F = (res["count_2f"] as? Number)?.toInt() ?: 0
            val totalItems = (res["total_items"] as? Number)?.toInt() ?: 0

            val is4FOrder = (totalItems > 0 && count4F == totalItems && count2F == 0)
            if (!is4FOrder) {
                // Not a 4F order → no problem, skip
                return@forEach
            }

            // 2) Find 4F trucks for that shop on that weekday
            val dayAbbr = getDayOfWeekAbbr(eta)
            val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, "4F", dayAbbr)

            if (trucks.isEmpty()) {
                // Problem DO: 4F order but no matching 4F truck that day
                val availableTrucksAll4F = truckRepository.findByShopIdAndDeletedFalse(shopId)
                    .filter { it.storeId == "4F" }

                val truckDtos = availableTrucksAll4F.map { t ->
                    TruckInfoDto(
                        id = t.id,
                        truckLanceCode = t.truckLanceCode,
                        departureTime = t.departureTime?.toString(),
                        storeId = t.storeId,
                        shopCode = t.shopCode,
                        shopName = t.shopName
                    )
                }

                problems += ProblemDoDto(
                    deliveryOrderId = deliveryOrder.id!!,
                    deliveryOrderCode = deliveryOrder.code,
                    targetDate = eta,
                    availableTrucks = truckDtos
                )
            }
        }

        return Check4FTruckBatchResponse(
            hasProblem = problems.isNotEmpty(),
            problems = problems
        )
    }
}
    