// StockTakeRecordService.kt
package com.ffii.fpsms.modules.stock.service

import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockTakeRepository
import com.ffii.fpsms.modules.stock.entity.StockTakeRecordRepository
import com.ffii.fpsms.modules.stock.entity.StockTakeRecord
import com.ffii.fpsms.modules.stock.enums.StockTakeStatus
import com.ffii.fpsms.modules.stock.web.model.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
import java.math.BigDecimal
import com.ffii.fpsms.modules.user.entity.UserRepository
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import com.ffii.core.response.RecordsRes
import com.ffii.fpsms.modules.stock.service.InventoryLotLineService
import com.ffii.fpsms.modules.stock.entity.StockTakeLine
import com.ffii.fpsms.modules.stock.entity.StockTakeLineRepository
import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatus
import com.ffii.fpsms.modules.stock.entity.StockOut
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLine
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.entity.StockIn
import com.ffii.fpsms.modules.stock.entity.StockInRepository
import com.ffii.fpsms.modules.stock.entity.StockInLine
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLot
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import jakarta.persistence.EntityManager
import jakarta.persistence.PersistenceContext
import org.hibernate.Session
import org.hibernate.jdbc.Work
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet
import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository
import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.entity.StockLedger
import com.ffii.fpsms.modules.master.entity.UomConversionRepository
import java.time.LocalDate
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import java.math.RoundingMode
import com.ffii.fpsms.modules.stock.entity.StockTake
import com.ffii.fpsms.modules.master.entity.ItemsRepository
@Service
open class StockTakeRecordService(
    val stockTakeRepository: StockTakeRepository,
    val warehouseRepository: WarehouseRepository,
    val inventoryLotLineRepository: InventoryLotLineRepository,
    val stockTakeRecordRepository: StockTakeRecordRepository,
    val userRepository: UserRepository,
    val stockTakeLineRepository: StockTakeLineRepository,
    val inventoryLotLineService: InventoryLotLineService,
    val stockOutRepository: StockOutRepository,
    val stockOutLineRepository: StockOutLIneRepository,
    val stockInRepository: StockInRepository,
    val stockInLineRepository: StockInLineRepository,
    val inventoryLotRepository: InventoryLotRepository,
    val stockLedgerRepository: StockLedgerRepository,
    val inventoryRepository: InventoryRepository,
    val uomConversionRepository: UomConversionRepository,
    val itemsRepository: ItemsRepository

) {
    private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java)

    /** 建立盤點輪次時預建 stock_take_record 用的佔位盤點員（取任一有效 user id）。 */
    open fun resolvePlaceholderStockTakerId(): Long {
        val u = userRepository.findAll(PageRequest.of(0, 1)).firstOrNull()
            ?: throw IllegalStateException("No user in system; cannot create placeholder stock take records")
        return u.id ?: throw IllegalStateException("User id is null")
    }

    /**
     * 建立該 section 的 stock_take 後，依當下「可用」庫存行預先建立本輪全部盤點明細（pending），盤點員儲存時改為更新。
     */
    @Transactional
    open fun createPlaceholderStockTakeRecordsForStockTake(stockTake: StockTake, placeholderStockTakerId: Long) {
        val section = stockTake.stockTakeSection ?: return
        val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(section)
        val warehouseIds = warehouses.mapNotNull { it.id }
        if (warehouseIds.isEmpty()) return

        val roundId = stockTake.stockTakeRoundId ?: stockTake.id
            ?: throw IllegalStateException("stockTake id is null")

        val inventoryLotLines =
            inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalseAndStatusIsAvailable(warehouseIds)
        if (inventoryLotLines.isEmpty()) return

        val toSave = ArrayList<StockTakeRecord>(inventoryLotLines.size)
        val illPerRecord = ArrayList<InventoryLotLine>(inventoryLotLines.size)
        for (ill in inventoryLotLines) {
            val inventoryLot = ill.inventoryLot ?: continue
            val warehouse = ill.warehouse ?: continue
            val item = inventoryLot.item ?: continue
            val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                .subtract(ill.outQty ?: BigDecimal.ZERO)
                //.subtract(ill.holdQty ?: BigDecimal.ZERO)

            // 與舊版 getInventoryLotDetails 顯示規則一致：僅 availableQty > 0 才預建（排除 in−out=0 或全被 hold 扣光等）
            if (availableQty.compareTo(BigDecimal.ZERO) <= 0) {
                continue
            }

            toSave.add(
                StockTakeRecord().apply {
                    this.itemId = item.id
                    this.lotId = inventoryLot.id
                    this.warehouse = warehouse
                    this.stockTake = stockTake
                    this.stockTakeRoundId = roundId
                    this.stockTakeSection = section
                    this.inventoryLotId = inventoryLot.id
                    this.stockTakerId = placeholderStockTakerId
                    this.stockTakerName = null
                    this.bookQty = availableQty
                    this.uom = ill.stockUom?.uom?.udfudesc
                    this.date = LocalDate.now()
                    this.status = "pending"
                    this.itemCode = item.code
                    this.itemName = item.name
                    this.lotNo = inventoryLot.lotNo
                    this.expiredDate = inventoryLot.expiryDate
                }
            )
            illPerRecord.add(ill)
        }
        stockTakeRecordRepository.saveAll(toSave)

        // 開輪即建 stock_take_line，1 record : 1 line，綁定 stockTakeRecordId（核准差異為 0 時改為完成同一筆，不另建）
        if (toSave.isNotEmpty()) {
            val lines = toSave.zip(illPerRecord).map { (record, ill) ->
                StockTakeLine().apply {
                    this.stockTake = stockTake
                    this.inventoryLotLine = ill
                    this.initialQty = record.bookQty
                    this.finalQty = null
                    this.uom = ill.stockUom?.uom
                    this.status = StockTakeLineStatus.PENDING
                    this.stockTakeRecord = record
                }
            }
            stockTakeLineRepository.saveAll(lines)
        }
    }

    /**
     * 同一輪多 section 的 stock_take：優先用 [StockTake.stockTakeRoundId]；舊資料為 null 時退回以 planStart 相同辨識一輪。
     */
    private fun resolveRoundStockTakeIds(baseStockTake: StockTake): Set<Long> {
        val roundKey = baseStockTake.stockTakeRoundId
        if (roundKey != null) {
            return stockTakeRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundKey)
                .mapNotNull { it.id }
                .toSet()
        }
        val planStart = baseStockTake.planStart
        return if (planStart != null) {
            stockTakeRepository.findAll()
                .filter { !it.deleted && it.planStart == planStart }
                .mapNotNull { it.id }
                .toSet()
        } else {
            listOfNotNull(baseStockTake.id).toSet()
        }
    }

    /**
     * 逗號分隔多個關鍵字，任一包含於 [StockTakeRecord.itemCode] 即保留（不分大小寫）。
     */
    private fun filterStockTakeRecordsByItemKeyword(
        records: List<StockTakeRecord>,
        itemKeyword: String?
    ): List<StockTakeRecord> {
        if (itemKeyword.isNullOrBlank()) return records
        val parts = itemKeyword.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (parts.isEmpty()) return records
        return records.filter { rec ->
            val code = rec.itemCode ?: ""
            parts.any { part -> code.contains(part, ignoreCase = true) }
        }
    }
    private fun filterStockTakeRecordsByItemCode(
        records: List<StockTakeRecord>,
        itemCode: String?
    ): List<StockTakeRecord> {
        if (itemCode.isNullOrBlank()) return records
        val parts = itemCode.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (parts.isEmpty()) return records
        return records.filter { rec ->
            val code = rec.itemCode ?: ""
            parts.any { part -> code.contains(part, ignoreCase = true) }
        }
    }

    /** 無 [stockTakeId] 時對已組裝明細依貨品編號篩選（仍可能較耗資源；建議帶 stockTakeId）。 */
    private fun filterInventoryLotDetailsByItemCode(
        responses: List<InventoryLotDetailResponse>,
        itemCode: String?
    ): List<InventoryLotDetailResponse> {
        if (itemCode.isNullOrBlank()) return responses
        val parts = itemCode.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (parts.isEmpty()) return responses
        return responses.filter { r ->
            val code = r.itemCode ?: ""
            parts.any { part -> code.contains(part, ignoreCase = true) }
        }
    }

    private fun filterStockTakeRecordsBySectionDescription(
        records: List<StockTakeRecord>,
        sectionDescription: String?
    ): List<StockTakeRecord> {
        if (sectionDescription.isNullOrBlank() || sectionDescription == "All") return records
        return records.filter { rec ->
            rec.warehouse?.stockTakeSectionDescription?.equals(sectionDescription, ignoreCase = true) == true
        }
    }

    private fun filterStockTakeRecordsByStockTakeSections(
        records: List<StockTakeRecord>,
        stockTakeSections: String?
    ): List<StockTakeRecord> {
        if (stockTakeSections.isNullOrBlank()) return records
        val sections = stockTakeSections.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (sections.isEmpty()) return records
        return records.filter { rec ->
            val sec = rec.stockTakeSection ?: ""
            sections.any { part ->
                sec.equals(part, ignoreCase = true) || sec.contains(part, ignoreCase = true)
            }
        }
    }

    private fun filterStockTakeRecordsByItemName(
        records: List<StockTakeRecord>,
        itemName: String?
    ): List<StockTakeRecord> {
        if (itemName.isNullOrBlank()) return records
        val parts = itemName.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (parts.isEmpty()) return records
        return records.filter { rec ->
            val name = rec.itemName ?: ""
            parts.any { part -> name.contains(part, ignoreCase = true) }
        }
    }
    private fun filterStockTakeRecordsByWarehouseCode(
    records: List<StockTakeRecord>,
    warehouseCodeKeyword: String?
): List<StockTakeRecord> {
    if (warehouseCodeKeyword.isNullOrBlank()) return records
    val parts = warehouseCodeKeyword.split(",").map { it.trim() }.filter { it.isNotEmpty() }
    if (parts.isEmpty()) return records
    return records.filter { rec ->
        val code = rec.warehouse?.code ?: ""
        parts.any { part -> code.contains(part, ignoreCase = true) }
    }
}
    private fun filterInventoryLotDetailsBySectionDescription(
        responses: List<InventoryLotDetailResponse>,
        sectionDescription: String?
    ): List<InventoryLotDetailResponse> {
        if (sectionDescription.isNullOrBlank() || sectionDescription == "All") return responses
        return responses.filter { r ->
            r.stockTakeSectionDescription?.equals(sectionDescription, ignoreCase = true) == true
        }
    }
    private fun filterInventoryLotDetailsByItemKeyword(
        responses: List<InventoryLotDetailResponse>,
        itemKeyword: String?
    ): List<InventoryLotDetailResponse> {
        if (itemKeyword.isNullOrBlank()) return responses
        val parts = itemKeyword.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (parts.isEmpty()) return responses
    
        return responses.filter { r ->
            val code = r.itemCode ?: ""
            val name = r.itemName ?: ""
            parts.any { part ->
                code.contains(part, ignoreCase = true) || name.contains(part, ignoreCase = true)
            }
        }
    }
    private fun filterInventoryLotDetailsByWarehouseKeyword(
    responses: List<InventoryLotDetailResponse>,
    warehouseKeyword: String?
): List<InventoryLotDetailResponse> {
    if (warehouseKeyword.isNullOrBlank()) return responses
    val parts = warehouseKeyword.split(",").map { it.trim() }.filter { it.isNotEmpty() }
    if (parts.isEmpty()) return responses

    return responses.filter { r ->
        val code = r.warehouseCode ?: ""
        parts.any { part -> code.contains(part, ignoreCase = true) }
    }
}
    private fun filterInventoryLotDetailsByStockTakeSections(
        responses: List<InventoryLotDetailResponse>,
        stockTakeSections: String?
    ): List<InventoryLotDetailResponse> {
        if (stockTakeSections.isNullOrBlank()) return responses
        val sections = stockTakeSections.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (sections.isEmpty()) return responses
        return responses.filter { r ->
            val sec = r.stockTakeSection ?: ""
            sections.any { part ->
                sec.equals(part, ignoreCase = true) || sec.contains(part, ignoreCase = true)
            }
        }
    }

    /** 與 Approver 列表前端一致：優先第二次盤點數，否則第一次 */
    private fun resolvePickerQtyForVarianceFilter(response: InventoryLotDetailResponse): BigDecimal {
        val second = response.secondStockTakeQty
        val first = response.firstStockTakeQty
        return when {
            second != null && second >= BigDecimal.ZERO -> second
            first != null -> first
            else -> BigDecimal.ZERO
        }
    }

    private fun computeInventoryLotDetailVariancePercentage(response: InventoryLotDetailResponse): BigDecimal {
        val selectedQty = resolvePickerQtyForVarianceFilter(response)
        val bookQty = response.bookQty ?: response.availableQty ?: BigDecimal.ZERO
        val difference = selectedQty.subtract(bookQty)
        val raw = if (bookQty.compareTo(BigDecimal.ZERO) != 0) {
            difference
                .divide(bookQty, 6, RoundingMode.HALF_UP)
                .multiply(BigDecimal("100"))
        } else {
            when {
                difference.compareTo(BigDecimal.ZERO) == 0 -> BigDecimal.ZERO
                difference.compareTo(BigDecimal.ZERO) > 0 -> BigDecimal("100")
                else -> BigDecimal("-100")
            }
        }
        return roundDownVariancePercent(raw)
    }

    /** 與前端顯示一致：差異% 小數向零方向捨去（-48.43 → -48，48.9 → 48） */
    private fun roundDownVariancePercent(vp: BigDecimal): BigDecimal =
        vp.setScale(0, RoundingMode.DOWN)

    /**
     * @param varianceFilterInclusive false：只保留區間外（vp ≤ -t 或 vp ≥ t）；
     *                                true：只保留區間內（-t ≤ vp ≤ t）
     * @param varianceFilterStrict true：邊界用 &gt; &lt;；false：用 ≥ ≤
     */
    private fun filterInventoryLotDetailsByVariancePercent(
        responses: List<InventoryLotDetailResponse>,
        variancePercentTolerance: BigDecimal?,
        varianceFilterInclusive: Boolean?,
        varianceFilterStrict: Boolean?,
    ): List<InventoryLotDetailResponse> {
        if (variancePercentTolerance == null) {
            return responses
        }
        val tolerance = variancePercentTolerance.abs()
        if (tolerance.compareTo(BigDecimal.ZERO) <= 0) {
            return responses
        }
        val inclusive = varianceFilterInclusive == true
        val strict = varianceFilterStrict == true
        val negTolerance = tolerance.negate()
        return responses.filter { response ->
            val vp = computeInventoryLotDetailVariancePercentage(response)
            if (inclusive) {
                if (strict) {
                    vp > negTolerance && vp < tolerance
                } else {
                    vp >= negTolerance && vp <= tolerance
                }
            } else {
                if (strict) {
                    vp < negTolerance || vp > tolerance
                } else {
                    vp <= negTolerance || vp >= tolerance
                }
            }
        }
    }

    private fun filterInventoryLotDetailsByItemName(
        responses: List<InventoryLotDetailResponse>,
        itemName: String?
    ): List<InventoryLotDetailResponse> {
        if (itemName.isNullOrBlank()) return responses
        val parts = itemName.split(",").map { it.trim() }.filter { it.isNotEmpty() }
        if (parts.isEmpty()) return responses
        return responses.filter { r ->
            val name = r.itemName ?: ""
            parts.any { part -> name.contains(part, ignoreCase = true) }
        }
    }

    open fun AllPickedStockTakeList(): List<AllPickedStockTakeListReponse> {
        // 1. 获取所有不同的 stockTakeSection（从 warehouse 表）
        val allWarehouses = warehouseRepository.findAllByDeletedIsFalse()
        val distinctSections = allWarehouses
            .mapNotNull { it.stockTakeSection }
            .distinct()
            .filter { !it.isBlank() }

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


        val warehousesBySection = allWarehouses
            .filter { it.stockTakeSection != null && !it.stockTakeSection!!.isBlank() }
            .groupBy { it.stockTakeSection!! }

        val allStockTakes = stockTakeRepository.findAll()
            .filter { !it.deleted }
            .groupBy { it.stockTakeSection }


        val allStockTakeRecords = stockTakeRecordRepository.findAll()
            .filter { !it.deleted }


        val recordsByWarehouseId = allStockTakeRecords
            .filter { it.warehouse?.id != null }
            .groupBy { it.warehouse!!.id }

        val result = mutableListOf<AllPickedStockTakeListReponse>()
        var idCounter = 1L
        val globalLatestRoundKey = resolveGlobalLatestStockTakeRoundKey()

        // 3. 为每个 stockTakeSection 创建一个卡片
        distinctSections.forEach { stockTakeSection ->
            // 4. 获取该 section 下的所有 warehouse
            val warehouses = warehousesBySection[stockTakeSection] ?: emptyList()
            val warehouseIds = warehouses.mapNotNull { it.id }

            if (warehouseIds.isEmpty()) {
                return@forEach
            }

            // 5. 获取该 section 相关的所有 stock_take 记录
            val stockTakesForSection = allStockTakes[stockTakeSection] ?: emptyList()

            // 6. 获取 lastStockTakeDate：从 completed 状态的记录中，按 actualEnd 排序，取最新的
            val completedStockTakes = stockTakesForSection
                .filter { it.status == StockTakeStatus.COMPLETED && it.actualEnd != null }
                .sortedByDescending { it.actualEnd }

            val lastStockTakeDate = completedStockTakes.firstOrNull()?.actualEnd


            val latestStockTake = resolveLatestStockTakeForSection(
                stockTakesForSection,
                globalLatestRoundKey
            )

            val status = if (latestStockTake != null) {
                latestStockTake.status?.value ?: "pending"
            } else {
                "no_cycle"
            }
            val stockTakerName = if (latestStockTake != null) {
                // 从该 stock take 的 records 中获取最新的 stockTakerName
                val recordsForStockTake = allStockTakeRecords
                    .filter {
                        it.stockTake?.id == latestStockTake.id &&
                                it.stockTakeSection == stockTakeSection &&
                                it.stockTakerName != null
                    }
                    .sortedByDescending { it.stockTakeStartTime ?: LocalDateTime.MIN }

                recordsForStockTake.firstOrNull()?.stockTakerName
            } else {
                null
            }
            val approverName = if (latestStockTake != null) {
                // 从该 stock take 的 records 中获取最新的 approverName
                val recordsForStockTake = allStockTakeRecords
                    .filter {
                        it.stockTake?.id == latestStockTake.id &&
                                it.stockTakeSection == stockTakeSection &&
                                it.approverName != null
                    }
                    .sortedByDescending { it.stockTakeStartTime ?: LocalDateTime.MIN }

                recordsForStockTake.firstOrNull()?.approverName
            } else {
                null
            }
            val sectionDescription = warehouses
            .mapNotNull { it.stockTakeSectionDescription }  // 先去掉 null
            .distinct()                                     // 去重（防止误填多个不同值）
            .firstOrNull()
            val warehouseArea = warehouses
                .mapNotNull { it.area }
                .distinct()
                .firstOrNull()
            val storeId = warehouses
                .mapNotNull { it.store_id }
                .distinct()
                .firstOrNull()

            val roundIdForLatest = latestStockTake?.let { st -> st.stockTakeRoundId ?: st.id }
            val roundRecordsForSection = if (latestStockTake != null && roundIdForLatest != null) {
                allStockTakeRecords.filter {
                    it.stockTakeRoundId == roundIdForLatest &&
                        it.stockTakeSection == stockTakeSection &&
                        (it.warehouse?.id in warehouseIds)
                }
            } else {
                emptyList()
            }

            val totalItemNumber = if (roundRecordsForSection.isNotEmpty()) {
                roundRecordsForSection.mapNotNull { it.itemId }.distinct().count()
            } else {
                inventoryLotLineRepository.countDistinctItemsByWarehouseIds(warehouseIds).toInt()
            }
            val totalInventoryLotNumber = if (roundRecordsForSection.isNotEmpty()) {
                roundRecordsForSection.size
            } else {
                inventoryLotLineRepository.countAllByWarehouseIds(warehouseIds).toInt()
            }
            val currentStockTakeItemNumber = roundRecordsForSection.count { it.pickerFirstStockTakeQty != null }

            val reStockTakeTrueFalse = if (latestStockTake != null) {
                val hasNotMatch = if (roundIdForLatest != null) {
                    allStockTakeRecords.any {
                        !it.deleted &&
                            it.stockTakeRoundId == roundIdForLatest &&
                            it.stockTakeSection == stockTakeSection &&
                            it.status == "notMatch"
                    }
                } else {
                    allStockTakeRecords.any {
                        !it.deleted &&
                            it.stockTake?.id == latestStockTake.id &&
                            it.stockTakeSection == stockTakeSection &&
                            it.status == "notMatch"
                    }
                }
                hasNotMatch
            } else {
                false
            }
            result.add(
                AllPickedStockTakeListReponse(
                    id = idCounter++,
                    stockTakeSession = stockTakeSection,
                    lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(),
                    status = status ?: "",
                    currentStockTakeItemNumber = currentStockTakeItemNumber,
                    totalInventoryLotNumber = totalInventoryLotNumber,
                    stockTakeId = latestStockTake?.id ?: 0,
                    stockTakeRoundId = latestStockTake?.stockTakeRoundId ?: latestStockTake?.id,
                    stockTakerName = stockTakerName,
                    TotalItemNumber = totalItemNumber,
                    approverName = approverName,
                    startTime = latestStockTake?.actualStart,
                    endTime = latestStockTake?.actualEnd,
                    ReStockTakeTrueFalse = reStockTakeTrueFalse,
                    planStartDate = latestStockTake?.planStart?.toLocalDate(),
                    stockTakeSectionDescription = sectionDescription,
                    warehouseArea = warehouseArea,
                    storeId = storeId

                )
            )
        }

        return result.sortedBy { it.stockTakeSession }
    }

    /**
     * 區域內用於卡片展示的 stock_take：若該區域參與全系統最新輪次，優先取該輪次內最新一筆；
     * 否則退回區域內 planStart/actualStart 最新一筆（供未開 onlyLatestRound 時顯示舊輪）。
     */
    private fun resolveLatestStockTakeForSection(
        stockTakesForSection: List<StockTake>,
        globalLatestRoundKey: Long?
    ): StockTake? {
        if (stockTakesForSection.isEmpty()) return null
        val byActivity = compareBy<StockTake> {
            it.actualStart ?: it.planStart ?: LocalDateTime.MIN
        }.thenBy { it.id ?: 0L }

        return if (globalLatestRoundKey != null && globalLatestRoundKey > 0L) {
            val inGlobalRound = stockTakesForSection.filter { st ->
                val rk = st.stockTakeRoundId ?: st.id
                rk == globalLatestRoundKey
            }
            inGlobalRound.maxWithOrNull(byActivity)
                ?: stockTakesForSection.maxWithOrNull(byActivity)
        } else {
            stockTakesForSection.maxWithOrNull(byActivity)
        }
    }

    /**
     * 全系統最新盤點輪次 id（MAX(stockTakeRoundId)）；舊資料無 roundId 時退回 planStart 最新一筆。
     */
    open fun resolveGlobalLatestStockTakeRoundKey(): Long? {
        val maxRound = stockTakeRepository.findMaxStockTakeRoundId()
        if (maxRound > 0L) {
            return maxRound
        }
        val latest = stockTakeRepository.findAll()
            .filter { !it.deleted }
            .maxByOrNull { it.planStart ?: LocalDateTime.MIN }
        return latest?.let { st -> st.stockTakeRoundId ?: st.id }
    }

    open fun getGlobalLatestStockTakeRoundMeta(): LatestStockTakeRoundMetaResponse {
        val roundKey = resolveGlobalLatestStockTakeRoundKey()
        if (roundKey == null || roundKey <= 0L) {
            return LatestStockTakeRoundMetaResponse(stockTakeRoundId = null, planStartDate = null)
        }
        val byRound = stockTakeRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundKey)
        val planStart = if (byRound.isNotEmpty()) {
            byRound.mapNotNull { it.planStart }.maxOrNull()?.toLocalDate()
        } else {
            stockTakeRepository.findByIdAndDeletedIsFalse(roundKey)?.planStart?.toLocalDate()
        }
        return LatestStockTakeRoundMetaResponse(stockTakeRoundId = roundKey, planStartDate = planStart)
    }

    /**
     * 與盤點員列表一致：先取全系統 MAX(stockTakeRoundId)，再在該輪內選一筆代表 stock_take（供審核 API 解析整輪）。
     */
    private fun resolveLatestRoundRepresentativeStockTake(): StockTake? {
        val roundKey = resolveGlobalLatestStockTakeRoundKey() ?: return null
        val inRound = stockTakeRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundKey)
        if (inRound.isNotEmpty()) {
            return inRound.maxWithOrNull(
                compareBy<StockTake> { it.actualStart ?: it.planStart ?: LocalDateTime.MIN }
                    .thenBy { it.id ?: 0L }
            )
        }
        return stockTakeRepository.findByIdAndDeletedIsFalse(roundKey)
    }

    open fun getApproverInventoryLotDetailsAll(
        stockTakeId: Long? = null,
        pageNum: Int = 0,
        pageSize: Int = 100,
        approvalView: String? = null,
        itemKeyword: String? = null,
        sectionDescription: String? = null,
        stockTakeSections: String? = null,
        warehouseKeyword: String? = null,
        variancePercentTolerance: BigDecimal? = null,
        varianceFilterInclusive: Boolean? = null,
        varianceFilterStrict: Boolean? = null,
    ): RecordsRes<InventoryLotDetailResponse> {
    
        println("getApproverInventoryLotDetailsAll called with stockTakeId: $stockTakeId, pageNum: $pageNum, pageSize: $pageSize, itemKeyword: $itemKeyword, sectionDescription: $sectionDescription, stockTakeSections: $stockTakeSections, warehouseKeyword: $warehouseKeyword, variancePercentTolerance: $variancePercentTolerance, varianceFilterInclusive: $varianceFilterInclusive, varianceFilterStrict: $varianceFilterStrict")

        // 3. 如果传了 stockTakeId，就把「同一轮」的所有 stockTake 找出来（stockTakeRoundId，舊資料則 planStart）
        // stockTakeId != null 时，优先用 stocktakerecord.stockTakeRoundId 取整轮记录（更快）
        // 如果该轮因为旧数据尚未写入 roundId 导致取不到，再 fallback 到旧逻辑（根据 stock_take 的 planStart / stockTakeRoundId 求 stockTakeId 列表）
        val (roundStockTakeIds, roundStockTakeRecordsRaw) = if (stockTakeId != null) {
            val baseStockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId)
                ?: throw IllegalArgumentException("Stock take not found: $stockTakeId")

            val roundId = baseStockTake.stockTakeRoundId ?: baseStockTake.id
            val recordsByRound = stockTakeRecordRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId)

            if (recordsByRound.isNotEmpty()) {
                val ids = recordsByRound.mapNotNull { it.stockTake?.id }.toSet()
                ids to recordsByRound
            } else {
                val ids = resolveRoundStockTakeIds(baseStockTake)
                val records = if (ids.isNotEmpty()) stockTakeRecordRepository.findAllByStockTakeIdInAndDeletedIsFalse(ids) else emptyList()
                ids to records
            }
        } else {
            emptySet<Long>() to emptyList()
        }
        var roundStockTakeRecords = roundStockTakeRecordsRaw
        val parts = itemKeyword
        ?.split(",")
        ?.map { it.trim() }
        ?.filter { it.isNotEmpty() }
        .orEmpty()
        if (parts.isNotEmpty()) {
        val allowedItemIds = parts
            .flatMap { kw -> itemsRepository.findIdsByCodeOrNameContains(kw) }
            .distinct()
            .toSet()
        if (allowedItemIds.isEmpty()) {
            return RecordsRes(emptyList(), 0)
        }
        roundStockTakeRecords = roundStockTakeRecords.filter { rec ->
            val itemId = rec.itemId
            itemId != null && allowedItemIds.contains(itemId)
        }
        }
        roundStockTakeRecords = filterStockTakeRecordsBySectionDescription(roundStockTakeRecords, sectionDescription)
        roundStockTakeRecords = filterStockTakeRecordsByStockTakeSections(roundStockTakeRecords, stockTakeSections)
       // roundStockTakeRecords = filterStockTakeRecordsByItemCode(roundStockTakeRecords, itemCode)
        //roundStockTakeRecords = filterStockTakeRecordsByItemName(roundStockTakeRecords, itemName)
        //roundStockTakeRecords = filterStockTakeRecordsByItemKeyword(roundStockTakeRecords, itemKeyword)
        roundStockTakeRecords = filterStockTakeRecordsByWarehouseCode(roundStockTakeRecords, warehouseKeyword)
        if (stockTakeId != null && roundStockTakeRecords.isEmpty()) {
            return RecordsRes(emptyList(), 0)
        }
        val stockTakeRecordsMap = roundStockTakeRecords
            .groupBy { Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L) }
            .mapValues { (_, records) -> records.maxByOrNull { it.id ?: 0L }!! }

        // A：Approver 只处理“已有 picker record 的行”
        // stockTakeId != null 时，allowedPairs 里一定对应 stockTakeRecordId != null
        val allowedPairs: Set<Pair<Long, Long>> = if (stockTakeId != null) {
            stockTakeRecordsMap.keys
        } else {
            emptySet()
        }

        // 4.1 只抓本轮 record 涉及到的 lot/warehouse 对应库存行（避免扫描全仓库 inventory_lot_line）
        val inventoryLotLines = if (stockTakeId != null) {
            val lotIds = roundStockTakeRecords.mapNotNull { it.lotId }.toSet()
            val warehouseIds = roundStockTakeRecords.mapNotNull { it.warehouse?.id }.toSet()
            if (lotIds.isEmpty() || warehouseIds.isEmpty()) {
                emptyList()
            } else {
                inventoryLotLineRepository.findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse(
                    warehouseIds = warehouseIds,
                    inventoryLotIds = lotIds
                ).filter { ill ->
                    val lotId = ill.inventoryLot?.id
                    val warehouseId = ill.warehouse?.id
                    lotId != null && warehouseId != null && allowedPairs.contains(Pair(lotId, warehouseId))
                }
            }
        } else {
            val warehouses = warehouseRepository.findAllByDeletedIsFalse()
            if (warehouses.isEmpty()) {
                logger.warn("No warehouses found for approverInventoryLotDetailsAll")
                return RecordsRes(emptyList(), 0)
            }
            val warehouseIds = warehouses.mapNotNull { it.id }
            println("Found ${warehouses.size} warehouses for ALL sections")
            inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds)
        }

        // 4.2 预先拉取本轮 stockTakeLine，避免每行调用 repository 形成 N+1
        val stockTakeLineMap = if (stockTakeId != null && roundStockTakeRecords.isNotEmpty()) {
            val lineIds = inventoryLotLines.mapNotNull { it.id }.toSet()
            val roundIds = roundStockTakeIds
            stockTakeLineRepository.findAllByStockTakeIdInAndInventoryLotLineIdInAndDeletedIsFalse(roundIds, lineIds)
                .associateBy {
                    Pair(
                        it.inventoryLotLine?.id ?: 0L,
                        it.stockTake?.id ?: 0L
                    )
                }
        } else {
            emptyMap()
        }
    
        val targetInventoryLotLines = inventoryLotLines

        // 5. 组装 InventoryLotDetailResponse（基本复制 section 版里的 map 那段）
        val allResults = targetInventoryLotLines.map { ill ->
            val inventoryLot = ill.inventoryLot
            val item = inventoryLot?.item
            val warehouse = ill.warehouse
            val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                .subtract(ill.outQty ?: BigDecimal.ZERO)
                //.subtract(ill.holdQty ?: BigDecimal.ZERO)
    
            val stockTakeRecord = if (stockTakeId != null && inventoryLot?.id != null && warehouse?.id != null) {
                stockTakeRecordsMap[Pair(inventoryLot.id, warehouse.id)]
            } else {
                null
            }

            val inventoryLotLineId = ill.id
            val stockTakeLine = if (stockTakeRecord != null && stockTakeRecord.stockTake?.id != null && inventoryLotLineId != null) {
                stockTakeLineMap[Pair(inventoryLotLineId, stockTakeRecord.stockTake!!.id!!)]
            } else {
                null
            }
    
            InventoryLotDetailResponse(
                id = ill.id ?: 0L,
                inventoryLotId = inventoryLot?.id ?: 0L,
                itemId = item?.id ?: 0L,
                itemCode = item?.code,
                itemName = item?.name,
                lotNo = inventoryLot?.lotNo,
                expiryDate = inventoryLot?.expiryDate,
                productionDate = inventoryLot?.productionDate,
                stockInDate = inventoryLot?.stockInDate,
                inQty = ill.inQty,
                remarks = stockTakeRecord?.remarks,
                outQty = ill.outQty,
                holdQty = ill.holdQty,
                availableQty = availableQty,
                uom = ill.stockUom?.uom?.udfudesc,
                warehouseCode = warehouse?.code,
                warehouseName = warehouse?.name,
                status = ill.status?.name,
                warehouseSlot = warehouse?.slot,
                warehouseArea = warehouse?.area,
                warehouse = warehouse?.warehouse,
                storeId = warehouse?.store_id,
                varianceQty = stockTakeRecord?.varianceQty,
                stockTakeRecordId = stockTakeRecord?.id,
                stockTakeRecordStatus = stockTakeRecord?.status,
                firstStockTakeQty = stockTakeRecord?.pickerFirstStockTakeQty,
                secondStockTakeQty = stockTakeRecord?.pickerSecondStockTakeQty,
                firstBadQty = stockTakeRecord?.pickerFirstBadQty,
                secondBadQty = stockTakeRecord?.pickerSecondBadQty,
                approverQty = stockTakeRecord?.approverStockTakeQty,
                approverBadQty = stockTakeRecord?.approverBadQty,
                finalQty = stockTakeLine?.finalQty,
                bookQty = stockTakeRecord?.bookQty,
                stockTakeSection = stockTakeRecord?.stockTakeSection ?: warehouse?.stockTakeSection,
                stockTakeSectionDescription = warehouse?.stockTakeSectionDescription,
                stockTakerName = stockTakeRecord?.stockTakerName,
                stockTakeEndTime = stockTakeRecord?.stockTakeEndTime,
                approverTime = stockTakeRecord?.approverTime,
                lastSelect = stockTakeRecord?.lastSelect,
            )
        }
    
        // 6. 过滤结果：
        // stockTakeId != null 时已在 map 前过滤 allowedPairs => 只保留 stockTakeRecordId != null 的行
        val filteredResults = if (stockTakeId != null) {
            allResults
        } else {
            allResults.filter { response ->
                val av = response.availableQty ?: BigDecimal.ZERO
                av.compareTo(BigDecimal.ZERO) > 0 || response.stockTakeRecordId != null
            }
        }

        // Pending / Approved 只看「審核是否完成」，不要用 stockTakeRecord.status == "completed"：
        // picker 在 inQty==outQty 時也會寫 status=completed，會被誤判成已審核而只出現在 Approved 分頁。
        val approvalFilteredResults = when (approvalView?.lowercase()) {
            "approved" -> filteredResults.filter { response ->
                response.approverQty != null || response.finalQty != null
            }
            "pending" -> filteredResults.filter { response ->
                response.approverQty == null && response.finalQty == null
            }
            else -> filteredResults
        }

        val approvalFilteredFinal = if (stockTakeId == null) {
            var list = approvalFilteredResults
            list = filterInventoryLotDetailsBySectionDescription(list, sectionDescription)
            list = filterInventoryLotDetailsByStockTakeSections(list, stockTakeSections)
            //list = filterInventoryLotDetailsByItemCode(list, itemCode)
            //list = filterInventoryLotDetailsByItemName(list, itemName)
            list = filterInventoryLotDetailsByItemKeyword(list, itemKeyword)
            list = filterInventoryLotDetailsByWarehouseKeyword(list, warehouseKeyword)
            list
        } else {
            approvalFilteredResults
        }

        val varianceFilteredFinal = filterInventoryLotDetailsByVariancePercent(
            approvalFilteredFinal,
            variancePercentTolerance,
            varianceFilterInclusive,
            varianceFilterStrict,
        )
    
        // 7. 分页（和 section 版一模一样）
        val pageable = PageRequest.of(pageNum, pageSize)
        val startIndex = pageable.offset.toInt()
        val endIndex = minOf(startIndex + pageSize, varianceFilteredFinal.size)
    
        val paginatedResult = if (startIndex < varianceFilteredFinal.size) {
            varianceFilteredFinal.subList(startIndex, endIndex)
        } else {
            emptyList()
        }
    
        return RecordsRes(paginatedResult, varianceFilteredFinal.size)
    }
    open fun AllApproverStockTakeList(): List<AllPickedStockTakeListReponse> {
        // Overall 卡：只取“最新一轮”，并且总数口径与
        // `approverInventoryLotDetailsAll?stockTakeId=...` 保持一致：
        // - availableQty > 0
        // - 或该 (inventoryLotId, warehouseId) 在该轮次的 stockTakeRecord 已存在（即 stockTakeRecordId != null）

        val allWarehouses = warehouseRepository.findAllByDeletedIsFalse()
        val warehouseIds = allWarehouses.mapNotNull { it.id }
        if (warehouseIds.isEmpty()) return emptyList()

        val latestBaseStockTake = resolveLatestRoundRepresentativeStockTake()
            ?: return emptyList()

        // 优先用 stocktakerecord.stockTakeRoundId 直接取该轮次记录（避免再算 stockTakeId 列表 + in 查询）
        val roundId = latestBaseStockTake.stockTakeRoundId ?: latestBaseStockTake.id
        var roundStockTakeRecords = stockTakeRecordRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId)
            .filter { it.warehouse?.id in warehouseIds }

        // 兼容旧数据：如果该轮次 roundId 为空/未补写，则 fallback 到旧逻辑
        if (roundStockTakeRecords.isEmpty()) {
            val roundStockTakeIds = resolveRoundStockTakeIds(latestBaseStockTake)
            if (roundStockTakeIds.isEmpty()) return emptyList()
            roundStockTakeRecords = stockTakeRecordRepository.findAllByStockTakeIdInAndDeletedIsFalse(roundStockTakeIds)
                .filter { it.warehouse?.id in warehouseIds }
        }

        val recordKeySet = roundStockTakeRecords.mapNotNull { r ->
            val lotId = r.lotId
            val whId = r.warehouse?.id
            if (lotId != null && whId != null) Pair(lotId, whId) else null
        }.toSet()

        val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds)

        val relevantInventoryLotLines = inventoryLotLines.filter { ill ->
            val inventoryLot = ill.inventoryLot
            val warehouse = ill.warehouse
            val lotId = inventoryLot?.id
            val whId = warehouse?.id
            if (lotId == null || whId == null) return@filter false

            val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                .subtract(ill.outQty ?: BigDecimal.ZERO)
                //.subtract(ill.holdQty ?: BigDecimal.ZERO)

            availableQty.compareTo(BigDecimal.ZERO) > 0 || recordKeySet.contains(Pair(lotId, whId))
        }

        val totalInventoryLotNumber = relevantInventoryLotLines.size
        val totalItemNumber = relevantInventoryLotLines
            .mapNotNull { it.inventoryLot?.item?.id }
            .distinct()
            .count()

        val statusValue = latestBaseStockTake.status?.let { st ->
            if (st == StockTakeStatus.APPROVING || st == StockTakeStatus.COMPLETED) st.value else ""
        } ?: ""

        val anyNotMatch = roundStockTakeRecords.any { it.status == "notMatch" }

        return listOf(
            AllPickedStockTakeListReponse(
                id = 1L,
                stockTakeSession = "",
                lastStockTakeDate = latestBaseStockTake.actualStart?.toLocalDate(),
                status = statusValue,
                currentStockTakeItemNumber = 0,
                totalInventoryLotNumber = totalInventoryLotNumber,
                stockTakeId = latestBaseStockTake.id ?: 0,
                stockTakeRoundId = latestBaseStockTake.stockTakeRoundId ?: latestBaseStockTake.id,
                stockTakerName = null,
                approverName = null,
                TotalItemNumber = totalItemNumber,
                startTime = latestBaseStockTake.actualStart,
                endTime = latestBaseStockTake.actualEnd,
                ReStockTakeTrueFalse = anyNotMatch,
                planStartDate = latestBaseStockTake.planStart?.toLocalDate(),
                stockTakeSectionDescription = null,
                warehouseArea = null,
                storeId = null
            )
        )
    }

    /**
     * 轻量版：只返回 Approver 最新一轮的基本信息，不做 totalItem/totalLot 重计算。
     * 用于前端先拿 stockTakeId，再调用 pending/approved 明细接口。
     */
    open fun getLatestApproverStockTakeHeader(): AllPickedStockTakeListReponse? {
        val latestBaseStockTake = resolveLatestRoundRepresentativeStockTake()
            ?: return null

        val roundKey = latestBaseStockTake.stockTakeRoundId ?: latestBaseStockTake.id
        val roundStockTakes = if (roundKey != null) {
            stockTakeRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundKey)
        } else {
            emptyList()
        }
        val planStartDate = if (roundStockTakes.isNotEmpty()) {
            roundStockTakes.mapNotNull { it.planStart }.maxOrNull()?.toLocalDate()
        } else {
            latestBaseStockTake.planStart?.toLocalDate()
        }

        val statusValue = latestBaseStockTake.status?.let { st ->
            if (st == StockTakeStatus.APPROVING || st == StockTakeStatus.COMPLETED) st.value else ""
        } ?: ""

        return AllPickedStockTakeListReponse(
            id = 1L,
            stockTakeSession = "",
            lastStockTakeDate = latestBaseStockTake.actualStart?.toLocalDate(),
            status = statusValue,
            currentStockTakeItemNumber = 0,
            totalInventoryLotNumber = 0,
            stockTakeId = latestBaseStockTake.id ?: 0,
            stockTakeRoundId = roundKey,
            stockTakerName = null,
            approverName = null,
            TotalItemNumber = 0,
            startTime = latestBaseStockTake.actualStart,
            endTime = latestBaseStockTake.actualEnd,
            ReStockTakeTrueFalse = false,
            planStartDate = planStartDate,
            stockTakeSectionDescription = null,
            warehouseArea = null,
            storeId = null
        )
    }

    open fun getInventoryLotDetailsByWarehouseCode(warehouseCode: String): List<InventoryLotDetailResponse> {
        println("getInventoryLotDetailsByWarehouseCode called with code: $warehouseCode")

        // 1. 先查找 warehouse，可能有多个相同 code 的记录，选择 id 最小的（最早的）
        val warehouses = warehouseRepository.findAllByCodeAndDeletedIsFalse(warehouseCode)
        if (warehouses.isEmpty()) {
            logger.warn("Warehouse not found with code: $warehouseCode")
            return emptyList()
        }

        // 按 id 排序，取第一个（id 最小的，即最早的）
        val warehouse = warehouses.minByOrNull { it.id ?: Long.MAX_VALUE }
        if (warehouse == null) {
            logger.warn("Warehouse found but id is null for code: $warehouseCode")
            return emptyList()
        }
        println("Found warehouse: id=${warehouse.id}, code=${warehouse.code} (total ${warehouses.size} found, using earliest)")

        // 2. 根据 warehouse id 查找所有相关的 InventoryLotLine
        val inventoryLotLines =
            inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(listOf(warehouse.id!!))
        println("Found ${inventoryLotLines.size} inventory lot lines")

        // 3. 转换为 Response
        return inventoryLotLines.map { ill ->
            val inventoryLot = ill.inventoryLot
            val item = inventoryLot?.item
            val warehouse = ill.warehouse
            val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                .subtract(ill.outQty ?: BigDecimal.ZERO)
                //.subtract(ill.holdQty ?: BigDecimal.ZERO)

            InventoryLotDetailResponse(
                id = ill.id ?: 0L,
                inventoryLotId = inventoryLot?.id ?: 0L,
                itemId = item?.id ?: 0L,
                itemCode = item?.code,
                itemName = item?.name,
                lotNo = inventoryLot?.lotNo,
                expiryDate = inventoryLot?.expiryDate,
                productionDate = inventoryLot?.productionDate,
                stockInDate = inventoryLot?.stockInDate,
                inQty = ill.inQty,
                outQty = ill.outQty,
                holdQty = ill.holdQty,
                availableQty = availableQty,
                uom = ill.stockUom?.uom?.udfudesc,
                warehouseCode = warehouse?.code,
                warehouseName = warehouse?.name,
                status = ill.status?.name,
                storeId = warehouse?.store_id,
                stockTakeRecordStatus = null,
                stockTakeRecordId = null,
                firstStockTakeQty = null,
                secondStockTakeQty = null,
                approverQty = null,
                varianceQty = null,
                approverBadQty = null,
                finalQty = null,
                firstBadQty = null,
                secondBadQty = null,
                remarks = null,
                warehouseSlot = warehouse?.slot,
                warehouseArea = warehouse?.area,
                warehouse = warehouse?.warehouse,
                bookQty = null,

            )
        }
    }

    open fun getInventoryLotDetailsByStockTakeSection(
        stockTakeSection: String,
        stockTakeId: Long? = null,
        stockTakeRoundId: Long? = null,
        pageNum: Int = 0,
        pageSize: Int = 10
    ): RecordsRes<InventoryLotDetailResponse> {
        println(
            "getInventoryLotDetailsByStockTakeSection called with section: $stockTakeSection, stockTakeId: $stockTakeId, " +
                "stockTakeRoundId: $stockTakeRoundId, pageNum: $pageNum, pageSize: $pageSize"
        )

        val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection)
        if (warehouses.isEmpty()) {
            logger.warn("No warehouses found for stockTakeSection: $stockTakeSection")
            return RecordsRes(emptyList(), 0)
        }

        val warehouseIds = warehouses.mapNotNull { it.id }.toSet()
        println("Found ${warehouses.size} warehouses for section $stockTakeSection")

        if (stockTakeId != null) {
            val baseStockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId)
                ?: return RecordsRes(emptyList(), 0)
            val baseId = baseStockTake.id ?: return RecordsRes(emptyList(), 0)
            val roundId = stockTakeRoundId ?: baseStockTake.stockTakeRoundId ?: baseId
            val sectionRecords = stockTakeRecordRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId)
                .filter {
                    !it.deleted &&
                        it.stockTakeSection == stockTakeSection &&
                        (it.warehouse?.id in warehouseIds)
                }

            if (sectionRecords.isNotEmpty()) {
                val lotIds = sectionRecords.mapNotNull { it.lotId }.toSet()
                val whIds = sectionRecords.mapNotNull { it.warehouse?.id }.toSet()
                val illList =
                    if (lotIds.isNotEmpty() && whIds.isNotEmpty()) {
                        inventoryLotLineRepository.findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse(
                            whIds,
                            lotIds
                        )
                    } else {
                        emptyList()
                    }
                val illByPair = illList.associateBy {
                    Pair(it.inventoryLot?.id ?: 0L, it.warehouse?.id ?: 0L)
                }

                val effectiveStockTakeId = stockTakeId
                val allResults = sectionRecords.mapNotNull { rec ->
                    val ill = illByPair[Pair(rec.lotId ?: 0L, rec.warehouse?.id ?: 0L)]
                        ?: return@mapNotNull null
                    val inventoryLot = ill.inventoryLot
                    val item = inventoryLot?.item
                    val warehouse = ill.warehouse
                    val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                        .subtract(ill.outQty ?: BigDecimal.ZERO)
                       // .subtract(ill.holdQty ?: BigDecimal.ZERO)
                    val inventoryLotLineId = ill.id
                    val stockTakeLine =
                        if (effectiveStockTakeId != null && inventoryLotLineId != null) {
                            stockTakeLineRepository.findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse(
                                inventoryLotLineId,
                                effectiveStockTakeId
                            )
                        } else {
                            null
                        }
                    InventoryLotDetailResponse(
                        id = ill.id ?: 0L,
                        inventoryLotId = inventoryLot?.id ?: 0L,
                        itemId = item?.id ?: 0L,
                        itemCode = item?.code,
                        itemName = item?.name,
                        lotNo = inventoryLot?.lotNo,
                        expiryDate = inventoryLot?.expiryDate,
                        storeId = warehouse?.store_id,
                        productionDate = inventoryLot?.productionDate,
                        stockInDate = inventoryLot?.stockInDate,
                        inQty = ill.inQty,
                        remarks = rec.remarks,
                        outQty = ill.outQty,
                        holdQty = ill.holdQty,
                        availableQty = availableQty,
                        uom = ill.stockUom?.uom?.udfudesc,
                        warehouseCode = warehouse?.code,
                        warehouseArea = warehouse?.area,
                        warehouseName = warehouse?.name,
                        status = ill.status?.name,
                        warehouseSlot = warehouse?.slot,
                        warehouse = warehouse?.warehouse,
                        varianceQty = rec.varianceQty,
                        stockTakeRecordId = rec.id,
                        stockTakeRecordStatus = rec.status,
                        firstStockTakeQty = rec.pickerFirstStockTakeQty,
                        secondStockTakeQty = rec.pickerSecondStockTakeQty,
                        firstBadQty = rec.pickerFirstBadQty,
                        secondBadQty = rec.pickerSecondBadQty,
                        approverQty = rec.approverStockTakeQty,
                        approverBadQty = rec.approverBadQty,
                        finalQty = stockTakeLine?.finalQty,
                        bookQty = rec.bookQty,
                        stockTakeSection = rec.stockTakeSection ?: warehouse?.stockTakeSection,
                        stockTakeSectionDescription = warehouse?.stockTakeSectionDescription,
                        stockTakerName = rec.stockTakerName,
                        stockTakeEndTime = rec.stockTakeEndTime,
                        approverTime = rec.approverTime,
                    )
                }

                val pageable = PageRequest.of(pageNum, pageSize)
                val startIndex = pageable.offset.toInt()
                val filteredResults = allResults.sortedWith(compareBy { it.itemCode })
                val endIndex = minOf(startIndex + pageSize, filteredResults.size)
                val paginatedResult = if (startIndex < filteredResults.size) {
                    filteredResults.subList(startIndex, endIndex)
                } else {
                    emptyList()
                }
                return RecordsRes(paginatedResult, filteredResults.size)
            }
        }

        val inventoryLotLines =
            inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalseAndStatusIsAvailable(warehouseIds.toList())
        println("Found ${inventoryLotLines.size} inventory lot lines (legacy inventory-driven)")

        val stockTakeRecordsMap = if (stockTakeId != null) {
            val allStockTakeRecords = stockTakeRecordRepository.findAll()
                .filter {
                    !it.deleted &&
                        it.stockTake?.id == stockTakeId &&
                        it.warehouse?.id in warehouseIds
                }
            allStockTakeRecords.associateBy {
                Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L)
            }
        } else {
            emptyMap()
        }

        val allResults = inventoryLotLines.map { ill ->
            val inventoryLot = ill.inventoryLot
            val item = inventoryLot?.item
            val warehouse = ill.warehouse
            val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                .subtract(ill.outQty ?: BigDecimal.ZERO)
               // .subtract(ill.holdQty ?: BigDecimal.ZERO)

            val stockTakeRecord = if (stockTakeId != null && inventoryLot?.id != null && warehouse?.id != null) {
                stockTakeRecordsMap[Pair(inventoryLot.id, warehouse.id)]
            } else {
                null
            }
            val inventoryLotLineId = ill.id
            val stockTakeLine = if (stockTakeId != null && inventoryLotLineId != null) {
                stockTakeLineRepository.findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse(
                    inventoryLotLineId,
                    stockTakeId
                )
            } else {
                null
            }
            InventoryLotDetailResponse(
                id = ill.id ?: 0L,
                inventoryLotId = inventoryLot?.id ?: 0L,
                itemId = item?.id ?: 0L,
                itemCode = item?.code,
                itemName = item?.name,
                lotNo = inventoryLot?.lotNo,
                expiryDate = inventoryLot?.expiryDate,
                storeId = warehouse?.store_id,
                productionDate = inventoryLot?.productionDate,
                stockInDate = inventoryLot?.stockInDate,
                inQty = ill.inQty,
                remarks = stockTakeRecord?.remarks,
                outQty = ill.outQty,
                holdQty = ill.holdQty,
                availableQty = availableQty,
                uom = ill.stockUom?.uom?.udfudesc,
                warehouseCode = warehouse?.code,
                warehouseArea = warehouse?.area,
                warehouseName = warehouse?.name,
                status = ill.status?.name,
                warehouseSlot = warehouse?.slot,
                warehouse = warehouse?.warehouse,
                varianceQty = stockTakeRecord?.varianceQty,
                stockTakeRecordId = stockTakeRecord?.id,
                stockTakeRecordStatus = stockTakeRecord?.status,
                firstStockTakeQty = stockTakeRecord?.pickerFirstStockTakeQty,
                secondStockTakeQty = stockTakeRecord?.pickerSecondStockTakeQty,
                firstBadQty = stockTakeRecord?.pickerFirstBadQty,
                secondBadQty = stockTakeRecord?.pickerSecondBadQty,
                approverQty = stockTakeRecord?.approverStockTakeQty,
                approverBadQty = stockTakeRecord?.approverBadQty,
                finalQty = stockTakeLine?.finalQty,
                bookQty = stockTakeRecord?.bookQty,
                stockTakeSection = stockTakeRecord?.stockTakeSection ?: warehouse?.stockTakeSection,
                stockTakeSectionDescription = warehouse?.stockTakeSectionDescription,
                stockTakerName = stockTakeRecord?.stockTakerName,
                stockTakeEndTime = stockTakeRecord?.stockTakeEndTime,
                approverTime = stockTakeRecord?.approverTime,
            )
        }

        val pageable = PageRequest.of(pageNum, pageSize)
        val startIndex = pageable.offset.toInt()
        val filteredResults = allResults.filter { response ->
            val av = response.availableQty ?: BigDecimal.ZERO
            av.compareTo(BigDecimal.ZERO) > 0 || response.stockTakeRecordId != null
        }.sortedWith(
            compareBy { it.itemCode }
        )
        val endIndex = minOf(startIndex + pageSize, filteredResults.size)
        val paginatedResult = if (startIndex < filteredResults.size) {
            filteredResults.subList(startIndex, endIndex)
        } else {
            emptyList()
        }
        return RecordsRes(paginatedResult, filteredResults.size)
    }

    @Transactional
    open fun saveStockTakeRecord(
        request: SaveStockTakeRecordRequest,
        stockTakeId: Long,
        stockTakerId: Long
    ): SaveStockTakeRecordResponse {
        println("saveStockTakeRecord called with stockTakeRecordId: ${request.stockTakeRecordId}, inventoryLotLineId: ${request.inventoryLotLineId}")
        val user = userRepository.findById(stockTakerId).orElse(null)
        // 1. 获取 inventory lot line
        val inventoryLotLine = inventoryLotLineRepository.findByIdAndDeletedIsFalse(request.inventoryLotLineId)
            ?: throw IllegalArgumentException("Inventory lot line not found: ${request.inventoryLotLineId}")

        val inventoryLot = inventoryLotLine.inventoryLot
            ?: throw IllegalArgumentException("Inventory lot not found")
        val item = inventoryLot.item
            ?: throw IllegalArgumentException("Item not found")
        val warehouse = inventoryLotLine.warehouse
            ?: throw IllegalArgumentException("Warehouse not found")
        val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId)
            ?: throw IllegalArgumentException("Stock take not found: $stockTakeId")

        // 2. 计算 availableQty
        val availableQty = (inventoryLotLine.inQty ?: BigDecimal.ZERO)
            .subtract(inventoryLotLine.outQty ?: BigDecimal.ZERO)
            //.subtract(inventoryLotLine.holdQty ?: BigDecimal.ZERO)

        // 3. 新建、更新預建記錄之第一次盤點、或第二次盤點
        val stockTakeRecord: StockTakeRecord = if (request.stockTakeRecordId != null) {
            val existingRecord = stockTakeRecordRepository.findByIdAndDeletedIsFalse(request.stockTakeRecordId)
                ?: throw IllegalArgumentException("Stock take record not found: ${request.stockTakeRecordId}")

            when {
                existingRecord.pickerFirstStockTakeQty == null -> {
                    val isCompled = inventoryLotLine.inQty == inventoryLotLine.outQty
                    val varianceQty = availableQty - request.qty - request.badQty
                    existingRecord.apply {
                        this.stockTakeRoundId = this.stockTakeRoundId ?: (stockTake.stockTakeRoundId ?: stockTake.id)
                        this.pickerFirstStockTakeQty = request.qty
                        this.pickerFirstBadQty = request.badQty
                        this.bookQty = availableQty
                        this.varianceQty = varianceQty
                        this.status = if (isCompled) "completed" else "pass"
                        this.remarks = request.remark ?: this.remarks
                        this.stockTakerId = stockTakerId
                        this.stockTakerName = user?.name
                        if (this.stockTakeStartTime == null) {
                            this.stockTakeStartTime = java.time.LocalDateTime.now()
                        }
                    }
                    existingRecord
                }
                existingRecord.pickerSecondStockTakeQty == null -> {
                    val varianceQty = availableQty - request.qty - request.badQty
                    existingRecord.apply {
                        this.stockTakeRoundId = this.stockTakeRoundId ?: (stockTake.stockTakeRoundId ?: stockTake.id)
                        this.pickerSecondStockTakeQty = request.qty
                        this.pickerSecondBadQty = request.badQty
                        this.status = "pass"
                        this.remarks = request.remark ?: this.remarks
                        this.stockTakerName = user?.name
                        this.stockTakeEndTime = java.time.LocalDateTime.now()
                        this.varianceQty = varianceQty
                    }
                    existingRecord
                }
                else -> throw IllegalArgumentException("Stock take record already has first and second counts")
            }
        } else {
            val isCompled = inventoryLotLine.inQty == inventoryLotLine.outQty
            val varianceQty = availableQty - request.qty - request.badQty
            StockTakeRecord().apply {
                this.itemId = item.id
                this.lotId = inventoryLot.id
                this.warehouse = warehouse
                this.stockTake = stockTake
                this.stockTakeRoundId = stockTake.stockTakeRoundId ?: stockTake.id
                this.stockTakeSection = warehouse.stockTakeSection
                this.inventoryLotId = inventoryLot.id
                this.stockTakerId = stockTakerId
                this.stockTakerName = user?.name
                this.pickerFirstStockTakeQty = request.qty
                this.pickerFirstBadQty = request.badQty
                this.bookQty = availableQty
                this.varianceQty = varianceQty
                this.uom = inventoryLotLine.stockUom?.uom?.udfudesc
                this.date = java.time.LocalDate.now()
                this.status = if (isCompled) "completed" else "pass"
                this.remarks = request.remark
                this.itemCode = item.code
                this.itemName = item.name
                this.lotNo = inventoryLot.lotNo
                this.expiredDate = inventoryLot.expiryDate
                this.stockTakeStartTime = java.time.LocalDateTime.now()
            }
        }

        val savedRecord = stockTakeRecordRepository.save(stockTakeRecord)
        if (stockTake.actualStart == null) {
            val anyFirst = stockTakeRecordRepository.findAllByStockTakeIdAndDeletedIsFalse(stockTakeId)
                .any { it.pickerFirstStockTakeQty != null }
            if (anyFirst) {
                stockTake.actualStart = java.time.LocalDateTime.now()
                stockTake.status = StockTakeStatus.STOCKTAKING
                stockTakeRepository.save(stockTake)
                println("Stock take $stockTakeId actualStart updated - first picker count exists")
            }
        }

        val stockTakeSection = savedRecord.stockTakeSection
        if (stockTakeSection != null) {
            checkAndUpdateStockTakeStatus(stockTakeId, stockTakeSection)
        }

        return toSaveStockTakeRecordResponse(savedRecord)
    }

    /**
     * 拣货员一次提交多行已输入数量；每行复用 [saveStockTakeRecord]，与单行保存一致。
     */
    @Transactional
    open fun batchSavePickerStockTakeInputs(
        request: BatchSavePickerStockTakeInputRequest
    ): BatchSaveStockTakeRecordResponse {
        if (request.records.isEmpty()) {
            return BatchSaveStockTakeRecordResponse(
                0,
                0,
                listOf("No records provided")
            )
        }

        var successCount = 0
        var errorCount = 0
        val errors = mutableListOf<String>()

        request.records.forEach { item ->
            try {
                saveStockTakeRecord(item, request.stockTakeId, request.stockTakerId)
                successCount++
            } catch (e: Exception) {
                errorCount++
                val msg = "inventoryLotLineId ${item.inventoryLotLineId}: ${e.message ?: e.javaClass.simpleName}"
                errors.add(msg)
                logger.warn("batchSavePickerStockTakeInputs failed: $msg", e)
            }
        }

        if (successCount > 0 && request.stockTakeSection.isNotBlank()) {
            checkAndUpdateStockTakeStatus(request.stockTakeId, request.stockTakeSection)
        }

        return BatchSaveStockTakeRecordResponse(
            successCount = successCount,
            errorCount = errorCount,
            errors = errors
        )
    }

    private fun toSaveStockTakeRecordResponse(r: StockTakeRecord): SaveStockTakeRecordResponse {
        return SaveStockTakeRecordResponse(
            id = r.id,
            version = r.version,
            itemId = r.itemId,
            lotId = r.lotId,
            inventoryLotId = r.inventoryLotId,
            warehouseId = r.warehouse?.id,
            stockTakeId = r.stockTake?.id,
            stockTakeRoundId = r.stockTakeRoundId,
            status = r.status,
            bookQty = r.bookQty,
            varianceQty = r.varianceQty,
            pickerFirstStockTakeQty = r.pickerFirstStockTakeQty,
            pickerFirstBadQty = r.pickerFirstBadQty,
            pickerSecondStockTakeQty = r.pickerSecondStockTakeQty,
            pickerSecondBadQty = r.pickerSecondBadQty,
        )
    }


    open fun batchSaveStockTakeRecords(
        request: BatchSaveStockTakeRecordRequest
    ): BatchSaveStockTakeRecordResponse {

        println("batchSaveStockTakeRecords called for section: ${request.stockTakeSection}, stockTakeId: ${request.stockTakeId}")
        val user = userRepository.findById(request.stockTakerId).orElse(null)
        // 1. 获取 stock take
        val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId)
            ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}")

        // 2. 使用 stockTakeSection 获取所有 inventory lot lines（类似 getInventoryLotDetailsByStockTakeSection）
        val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(request.stockTakeSection)
        if (warehouses.isEmpty()) {
            logger.warn("No warehouses found for stockTakeSection: ${request.stockTakeSection}")
            return BatchSaveStockTakeRecordResponse(
                0,
                0,
                listOf("No warehouses found for section: ${request.stockTakeSection}")
            )
        }

        val warehouseIds = warehouses.mapNotNull { it.id }
        println("Found ${warehouses.size} warehouses for section ${request.stockTakeSection}")

        // 3. 批量获取所有相关的 InventoryLotLine
        val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds)
        println("Found ${inventoryLotLines.size} inventory lot lines")

        // 4. 使用 stockTakeId 获取已创建的记录，建立映射以排除它们
        val existingRecordsMap = stockTakeRecordRepository
            .findAllByStockTakeIdAndWarehouseIdInAndDeletedIsFalse(request.stockTakeId, warehouseIds)
            .associateBy {
                Pair(it.inventoryLotId ?: 0L, it.warehouse?.id ?: 0L)
            }

        println("Found ${existingRecordsMap.size} existing stock take records")

        // 5. 找出未创建的记录
        val uncreatedLines = inventoryLotLines.filter { ill ->
            val inventoryLot = ill.inventoryLot
            val warehouse = ill.warehouse
            if (inventoryLot?.id == null || warehouse?.id == null) {
                false
            } else {
                val key = Pair(inventoryLot.id, warehouse.id)
                !existingRecordsMap.containsKey(key)
            }
        }

        println("Found ${uncreatedLines.size} uncreated inventory lot lines to process")

        if (uncreatedLines.isEmpty()) {
            return BatchSaveStockTakeRecordResponse(0, 0, listOf("No uncreated records found"))
        }

        // 6. 批量创建记录
        var successCount = 0
        var errorCount = 0
        val errors = mutableListOf<String>()

        uncreatedLines.forEach { ill ->
            try {
                val inventoryLot = ill.inventoryLot
                    ?: throw IllegalArgumentException("Inventory lot not found")
                val itemEntity = inventoryLot.item
                    ?: throw IllegalArgumentException("Item not found")
                val warehouse = ill.warehouse
                    ?: throw IllegalArgumentException("Warehouse not found")

                // 计算 availableQty
                val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                    .subtract(ill.outQty ?: BigDecimal.ZERO)
                  //  .subtract(ill.holdQty ?: BigDecimal.ZERO)

                // 使用 availableQty 作为 qty，badQty 为 0
                val qty = availableQty
                val badQty = BigDecimal.ZERO
                val varianceQty = availableQty - qty - badQty
                // 拣货员第一次批量盘点：与单笔第一次保存一致，一律 pass；不在此写 stockTakeEndTime（仅第二次盘点结束）
                val stockTakeRecord = StockTakeRecord().apply {
                    this.itemId = itemEntity.id
                    this.lotId = inventoryLot.id
                    this.warehouse = warehouse
                    this.stockTake = stockTake
                    this.stockTakeRoundId = stockTake.stockTakeRoundId ?: stockTake.id
                    this.stockTakeSection = request.stockTakeSection
                    this.inventoryLotId = inventoryLot.id
                    this.stockTakerId = request.stockTakerId
                    this.stockTakerName = user?.name
                    this.pickerFirstStockTakeQty = qty
                    this.pickerFirstBadQty = badQty
                    this.bookQty = availableQty
                    this.varianceQty = varianceQty
                    this.uom = ill.stockUom?.uom?.udfudesc
                    this.date = java.time.LocalDate.now()
                    this.status = "pass"
                    this.remarks = null
                    this.itemCode = itemEntity.code
                    this.itemName = itemEntity.name
                    this.lotNo = inventoryLot.lotNo
                    this.expiredDate = inventoryLot.expiryDate
                    this.stockTakeStartTime = java.time.LocalDateTime.now()
                }

                stockTakeRecordRepository.save(stockTakeRecord)
                successCount++
            } catch (e: Exception) {
                errorCount++
                val errorMsg = "Error saving inventoryLotLineId ${ill.id}: ${e.message}"
                errors.add(errorMsg)
                logger.error(errorMsg, e)
            }
        }
        if (successCount > 0) {
            checkAndUpdateStockTakeStatus(request.stockTakeId, request.stockTakeSection)
        }
        println("batchSaveStockTakeRecords completed: success=$successCount, errors=$errorCount")
        return BatchSaveStockTakeRecordResponse(
            successCount = successCount,
            errorCount = errorCount,
            errors = errors
        )
    }

    open fun checkAndUpdateStockTakeStatus(stockTakeId: Long, stockTakeSection: String): Map<String, Any> {
        try {
            val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId)
                ?: return mapOf("success" to false, "message" to "Stock take not found")

            // 1. 获取该 section 下的所有 warehouse
            val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection)
            if (warehouses.isEmpty()) {
                return mapOf("success" to false, "message" to "No warehouses found for section")
            }

            val warehouseIds = warehouses.mapNotNull { it.id }.toSet()

            val roundId = stockTake.stockTakeRoundId ?: stockTake.id
                ?: return mapOf("success" to false, "message" to "Stock take id is null")

            var stockTakeRecords = stockTakeRecordRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId)
                .filter {
                    !it.deleted &&
                        it.stockTakeSection == stockTakeSection &&
                        (it.warehouse?.id in warehouseIds)
                }

            if (stockTakeRecords.isEmpty()) {
                stockTakeRecords = stockTakeRecordRepository.findAll()
                    .filter {
                        !it.deleted &&
                            it.stockTake?.id == stockTakeId &&
                            it.warehouse?.id in warehouseIds &&
                            it.stockTakeSection == stockTakeSection
                    }
            }

            // 輪次預建後：以本 section 本輪「全部記錄是否已完成第一次盤點」為準（不再與即時庫存行逐行對齊）
            val allLinesHaveRecords = stockTakeRecords.isNotEmpty() &&
                stockTakeRecords.all { it.pickerFirstStockTakeQty != null }

            val allRecordsPassed = stockTakeRecords.isNotEmpty() &&
                stockTakeRecords.all { it.status == "pass" || it.status == "completed" }
            val allRecordsCompleted = stockTakeRecords.isNotEmpty() &&
                stockTakeRecords.all { it.status == "completed" }
            // 6. 如果所有记录都已创建且都是 "pass"，更新 stock take 状态为 "approving"
            if (allLinesHaveRecords && allRecordsCompleted) {
                stockTake.status = StockTakeStatus.COMPLETED
                stockTake.planEnd = java.time.LocalDateTime.now()
                stockTakeRepository.save(stockTake)
                println("Stock take $stockTakeId status updated to COMPLETED - all records are completed")
                return mapOf(
                    "success" to true,
                    "message" to "Stock take status updated to COMPLETED",
                    "updated" to true
                )
            } else if (allLinesHaveRecords && allRecordsPassed) {
                // 如果所有记录都已创建且都是 "pass" 或 "completed"，更新 stock take 状态为 "approving"
                stockTake.status = StockTakeStatus.APPROVING
                stockTake.actualEnd = java.time.LocalDateTime.now()
                stockTakeRepository.save(stockTake)

                println("Stock take $stockTakeId status updated to APPROVING - all records are pass")
                return mapOf(
                    "success" to true,
                    "message" to "Stock take status updated to APPROVING",
                    "updated" to true
                )
            } else {
                return mapOf(
                    "success" to true,
                    "message" to "Conditions not met for status update",
                    "updated" to false,
                    "allLinesHaveRecords" to allLinesHaveRecords,
                    "allRecordsPassed" to allRecordsPassed,
                    "allRecordsCompleted" to allRecordsCompleted
                )
            }
        } catch (e: Exception) {
            logger.error("Error checking and updating stock take status for stockTakeId: $stockTakeId", e)
            return mapOf("success" to false, "message" to "Error: ${e.message}")
        }
    }

    private fun resolveApproverFinalQuantities(
        request: SaveApproverStockTakeRecordRequest
    ): Pair<BigDecimal, BigDecimal> {
        val finalQty = if (request.approverQty != null && request.approverBadQty != null) {
            request.approverQty!!
        } else {
            request.qty
        }
        val finalBadQty = if (request.approverQty != null && request.approverBadQty != null) {
            request.approverBadQty!!
        } else {
            request.badQty
        }
        return Pair(finalQty, finalBadQty)
    }

    private fun resolveApproverLastSelect(
        request: SaveApproverStockTakeRecordRequest,
        stockTakeRecord: StockTakeRecord
    ): Int {
        return request.lastSelect ?: run {
            if (request.approverQty != null && request.approverBadQty != null) {
                3
            } else if (
                stockTakeRecord.pickerSecondStockTakeQty != null &&
                stockTakeRecord.pickerSecondStockTakeQty!!.compareTo(request.qty) == 0
            ) {
                2
            } else {
                1
            }
        }
    }

    private fun applyApproverSaveToRecord(
        stockTakeRecord: StockTakeRecord,
        request: SaveApproverStockTakeRecordRequest,
        approverId: Long,
        approverName: String?
    ): Pair<BigDecimal, BigDecimal> {
        val (finalQty, finalBadQty) = resolveApproverFinalQuantities(request)
        val varianceQty = finalQty.subtract(stockTakeRecord.bookQty ?: BigDecimal.ZERO)
        stockTakeRecord.apply {
            this.approverId = approverId
            this.approverName = approverName
            this.approverStockTakeQty = finalQty
            this.approverBadQty = finalBadQty
            this.varianceQty = varianceQty
            this.status = "completed"
            this.approverTime = java.time.LocalDateTime.now()
            this.lastSelect = resolveApproverLastSelect(request, this)
            if (this.stockTakeEndTime == null) {
                this.stockTakeEndTime = java.time.LocalDateTime.now()
            }
        }
        return Pair(finalQty, varianceQty)
    }

    open fun saveApproverStockTakeRecord(
    request: SaveApproverStockTakeRecordRequest,
    stockTakeId: Long
): StockTakeRecord {
    println("saveApproverStockTakeRecord called with stockTakeRecordId: ${request.stockTakeRecordId}")
    val user = userRepository.findById(request.approverId ?: 0L).orElse(null)

    val stockTakeRecord = if (request.stockTakeRecordId != null) {
        stockTakeRecordRepository.findByIdAndDeletedIsFalse(request.stockTakeRecordId)
            ?: throw IllegalArgumentException("Stock take record not found: ${request.stockTakeRecordId}")
    } else {
        throw IllegalArgumentException("stockTakeRecordId is required for approver")
    }

    val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId)
        ?: throw IllegalArgumentException("Stock take not found: $stockTakeId")

    val (finalQty, varianceQty) = applyApproverSaveToRecord(
        stockTakeRecord,
        request,
        request.approverId ?: 0L,
        user?.name
    )

    println("finalQty: $finalQty")
    println("stockTakeRecord.bookQty: ${stockTakeRecord.bookQty}")
    println("varianceQty: $varianceQty")

    val savedRecord = stockTakeRecordRepository.save(stockTakeRecord)

    // variance != 0：過帳並更新／新建 StockTakeLine；= 0：僅將開輪預建 line 標記完成（不查庫存）
    if (varianceQty != BigDecimal.ZERO) {
        applyVarianceAdjustment(stockTake, stockTakeRecord, finalQty!!, varianceQty, request.approverId)
    } else {
        completeStockTakeLineForApproverNoVariance(stockTake, savedRecord, finalQty!!)
    }
    val stockTakeSection = savedRecord.stockTakeSection
    if (stockTakeSection != null) {
        checkAndUpdateStockTakeStatus(stockTakeId, stockTakeSection)
    }

    return savedRecord
}

open fun batchSaveApproverStockTakeRecords(
    request: BatchSaveApproverStockTakeRecordRequest
): BatchSaveApproverStockTakeRecordResponse {
    println("batchSaveApproverStockTakeRecords called for section: ${request.stockTakeSection}, stockTakeId: ${request.stockTakeId}")
    val user = userRepository.findById(request.approverId).orElse(null)
    
    // 1. Get stock take
    val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId)
        ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}")
    

    val stockTakeRecords = stockTakeRecordRepository
        .findPendingApproverRecordsByStockTakeAndSection(
            stockTakeId = request.stockTakeId,
            stockTakeSection = request.stockTakeSection
        )
    
    println("Found ${stockTakeRecords.size} records to process")
    
    if (stockTakeRecords.isEmpty()) {
        return BatchSaveApproverStockTakeRecordResponse(0, 0, listOf("No records found matching criteria"))
    }
    
    // 3. Process each record
    var successCount = 0
    var errorCount = 0
    var skippedCount = 0
    val errors = mutableListOf<String>()
    
    stockTakeRecords.forEach { record ->
        try {
            
            val qty: BigDecimal
            val badQty: BigDecimal
            
            if (record.pickerSecondStockTakeQty != null && record.pickerSecondStockTakeQty!! > BigDecimal.ZERO) {
                
                qty = record.pickerSecondStockTakeQty!!
                badQty = record.pickerSecondBadQty ?: BigDecimal.ZERO
            } else {
                
                qty = record.pickerFirstStockTakeQty ?: BigDecimal.ZERO
                badQty = record.pickerFirstBadQty ?: BigDecimal.ZERO
            }
            
            
            val bookQty = record.bookQty ?: BigDecimal.ZERO
            val varianceQty = qty.subtract(bookQty)

            val tolerancePercent = request.variancePercentTolerance ?: BigDecimal.ZERO
            val shouldSkip = if (tolerancePercent.compareTo(BigDecimal.ZERO) <= 0) {
                // 0 或负数表示不过滤差异：所有已盘点记录都参与批量保存
                false
            } else {
                if (bookQty.compareTo(BigDecimal.ZERO) == 0) {
                    varianceQty.compareTo(BigDecimal.ZERO) != 0
                } else {
                    val threshold = bookQty.abs()
                        .multiply(tolerancePercent)
                        .divide(BigDecimal("100"), 10, RoundingMode.HALF_UP)
                    varianceQty.abs().compareTo(threshold) > 0  // |variance| > threshold 则跳过
                }
            }
            if (shouldSkip) {
                skippedCount++
                println("Skipping record ${record.id}: |variance| > ${tolerancePercent}% of bookQty (variance=$varianceQty, bookQty=$bookQty)")
                return@forEach
            }
            
            // variance = 0，批量保存并设置为 completed
            record.apply {
                this.approverId = request.approverId
                this.approverName = user?.name
                this.approverStockTakeQty = qty
                this.approverBadQty = badQty
                this.varianceQty = varianceQty 
                this.lastSelect = if (
                    record.pickerSecondStockTakeQty != null &&
                        record.pickerSecondStockTakeQty!! > BigDecimal.ZERO
                ) 2 else 1
                this.status = "completed"
                this.approverTime = java.time.LocalDateTime.now()
                if (this.stockTakeEndTime == null) {
                    this.stockTakeEndTime = java.time.LocalDateTime.now()
                }
            }
            
            stockTakeRecordRepository.save(record)
            if (varianceQty != BigDecimal.ZERO) {
                try {
                    applyVarianceAdjustment(stockTake, record, qty, varianceQty, request.approverId)
                } catch (e: Exception) {
                    logger.error("Failed to apply variance adjustment for record ${record.id}", e)
                    errorCount++
                    errors.add("Record ${record.id}: ${e.message}")
                    return@forEach
                }
            } else {
                completeStockTakeLineForApproverNoVariance(stockTake, record, qty)
            }
            successCount++
        } catch (e: Exception) {
            errorCount++
            val errorMsg = "Error processing record ${record.id}: ${e.message}"
            errors.add(errorMsg)
            logger.error(errorMsg, e)
        }
    }
    
    // 只在最后调用一次 checkAndUpdateStockTakeStatus
    if (successCount > 0) {
        checkAndUpdateStockTakeStatus(request.stockTakeId, request.stockTakeSection)
    }
    
    println("batchSaveApproverStockTakeRecords completed: success=$successCount, skipped=$skippedCount, errors=$errorCount")
    return BatchSaveApproverStockTakeRecordResponse(
        successCount = successCount,
        errorCount = errorCount,
        errors = errors
    )
}

open fun batchSaveApproverStockTakeRecordsAll(
    request: BatchSaveApproverStockTakeAllRequest
): BatchSaveApproverStockTakeRecordResponse {
    println("batchSaveApproverStockTakeRecordsAll called for stockTakeId: ${request.stockTakeId}")
    val user = userRepository.findById(request.approverId).orElse(null)

    val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId)
        ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}")

    val roundStockTakeIds: Set<Long> = resolveRoundStockTakeIds(stockTake)

    var stockTakeRecords = stockTakeRecordRepository
        .findAllByStockTakeIdInAndDeletedIsFalse(roundStockTakeIds)
        .filter {
            // 只处理已经有 picker 盘点记录的行（第一或第二次盘点任意一个非 null 即视为已盘点）
            (it.pickerFirstStockTakeQty != null || it.pickerSecondStockTakeQty != null) &&
            it.approverStockTakeQty == null
        }
        val sectionParts = request.stockTakeSections
        ?.split(",")
        ?.map { it.trim() }
        ?.filter { it.isNotEmpty() }
        .orEmpty()
      
      if (sectionParts.isNotEmpty()) {
        stockTakeRecords = stockTakeRecords.filter { r ->
          val sec = r.stockTakeSection ?: ""
          sectionParts.any { part -> sec.equals(part, ignoreCase = true) || sec.contains(part, ignoreCase = true) }
        }
      }
      val sectionDesc = request.sectionDescription?.trim()
        if (!sectionDesc.isNullOrBlank() && sectionDesc != "All") {
        stockTakeRecords = stockTakeRecords.filter { r ->
            r.warehouse?.stockTakeSectionDescription?.equals(sectionDesc, ignoreCase = true) == true
        }
        }
        val whParts = request.warehouseKeyword
  ?.split(",")
  ?.map { it.trim() }
  ?.filter { it.isNotEmpty() }
  .orEmpty()
if (whParts.isNotEmpty()) {
  stockTakeRecords = stockTakeRecords.filter { r ->
    val code = r.warehouse?.code ?: ""
    whParts.any { part -> code.contains(part, ignoreCase = true) }
  }
}
val itemParts = request.itemKeyword
  ?.split(",")
  ?.map { it.trim() }
  ?.filter { it.isNotEmpty() }
  .orEmpty()

if (itemParts.isNotEmpty()) {
  val allowedItemIds = itemParts
    .flatMap { kw -> itemsRepository.findIdsByCodeOrNameContains(kw) }
    .distinct()
    .toSet()

  
    if (allowedItemIds.isEmpty()) {
        return BatchSaveApproverStockTakeRecordResponse(0, 0, listOf("No items found matching itemKeyword"))
      }
  stockTakeRecords = stockTakeRecords.filter { r ->
    val itemId = r.itemId
    itemId != null && allowedItemIds.contains(itemId)
  }

}
    println("Found ${stockTakeRecords.size} records to process for round (all sections)")

    if (stockTakeRecords.isEmpty()) {
        return BatchSaveApproverStockTakeRecordResponse(0, 0, listOf("No records found matching criteria"))
    }

    var successCount = 0
    var errorCount = 0
    var skippedCount = 0
    val errors = mutableListOf<String>()

    val processedStockTakes = mutableSetOf<Pair<Long, String>>()

    stockTakeRecords.forEach { record ->
        try {
            val qty: BigDecimal
            val badQty: BigDecimal

            if (record.pickerSecondStockTakeQty != null && record.pickerSecondStockTakeQty!! > BigDecimal.ZERO) {
                qty = record.pickerSecondStockTakeQty!!
                badQty = record.pickerSecondBadQty ?: BigDecimal.ZERO
            } else {
                qty = record.pickerFirstStockTakeQty ?: BigDecimal.ZERO
                badQty = record.pickerFirstBadQty ?: BigDecimal.ZERO
            }

            val bookQty = record.bookQty ?: BigDecimal.ZERO
            val varianceQty = qty.subtract(bookQty)

           
            record.apply {
                this.approverId = request.approverId
                this.approverName = user?.name
                this.approverStockTakeQty = qty
                this.approverBadQty = badQty
                this.varianceQty = varianceQty
                this.status = "completed"
                this.approverTime = java.time.LocalDateTime.now()
                if (this.stockTakeEndTime == null) {
                    this.stockTakeEndTime = java.time.LocalDateTime.now()
                }
            }

            stockTakeRecordRepository.save(record)
            if (varianceQty != BigDecimal.ZERO) {
                try {
                    applyVarianceAdjustment(record.stockTake ?: stockTake, record, qty, varianceQty, request.approverId)
                } catch (e: Exception) {
                    logger.error("Failed to apply variance adjustment for record ${record.id}", e)
                    errorCount++
                    errors.add("Record ${record.id}: ${e.message}")
                    return@forEach
                }
            } else {
                completeStockTakeLineForApproverNoVariance(record.stockTake ?: stockTake, record, qty)
            }
            val stId = record.stockTake?.id
            val section = record.stockTakeSection
            if (stId != null && section != null) {
                processedStockTakes.add(Pair(stId, section))
            }
            successCount++
        } catch (e: Exception) {
            errorCount++
            val errorMsg = "Error processing record ${record.id}: ${e.message}"
            errors.add(errorMsg)
            logger.error(errorMsg, e)
        }
    }

    if (successCount > 0) {
        processedStockTakes.forEach { (stId, section) ->
            checkAndUpdateStockTakeStatus(stId, section)
        }
    }

    println("batchSaveApproverStockTakeRecordsAll completed: success=$successCount, skipped=$skippedCount, errors=$errorCount")
    return BatchSaveApproverStockTakeRecordResponse(
        successCount = successCount,
        errorCount = errorCount,
        errors = errors
    )
}

open fun batchSaveApproverStockTakeRecordsByIds(
    request: BatchSaveApproverStockTakeByIdsRequest
): BatchSaveApproverStockTakeRecordResponse {
    val totalStartNs = System.nanoTime()
    fun elapsedMs(startNs: Long): Long = (System.nanoTime() - startNs) / 1_000_000

    val recordPayloadById = request.records
        .mapNotNull { r -> r.stockTakeRecordId?.let { id -> id to r } }
        .toMap()
    val useClientPayload = recordPayloadById.isNotEmpty()
    val idsToLoad = when {
        useClientPayload -> recordPayloadById.keys.toList()
        request.recordIds.isNotEmpty() -> request.recordIds
        else -> emptyList()
    }

    logger.info(
        "batchSaveApproverStockTakeRecordsByIds start: stockTakeId={}, ids={}, clientPayload={}",
        request.stockTakeId,
        idsToLoad.size,
        useClientPayload
    )
    if (idsToLoad.isEmpty()) {
        return BatchSaveApproverStockTakeRecordResponse(0, 0, listOf("No record IDs provided"))
    }

    val loadStartNs = System.nanoTime()
    val user = userRepository.findById(request.approverId).orElse(null)
    val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId)
        ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}")

    val idSet = idsToLoad.toSet()
    val stockTakeRecords = stockTakeRecordRepository.findAllById(idsToLoad)
        .filter {
            !it.deleted &&
                (it.id in idSet) &&
                (it.pickerFirstStockTakeQty != null || it.pickerSecondStockTakeQty != null) &&
                it.approverStockTakeQty == null
        }
    logger.info(
        "batchSaveApproverStockTakeRecordsByIds load completed: candidates={}, loadMs={}",
        stockTakeRecords.size,
        elapsedMs(loadStartNs)
    )
    if (stockTakeRecords.isEmpty()) {
        return BatchSaveApproverStockTakeRecordResponse(0, 0, listOf("No records found matching criteria"))
    }
    val cacheBuildStartNs = System.nanoTime()
    val adjustmentCache = buildBatchAdjustmentCache(stockTakeRecords)
    logger.info(
        "batchSaveApproverStockTakeRecordsByIds cache build completed: lotLinePairs={}, lots={}, inventories={}, stockTakeLines={}, cacheBuildMs={}",
        adjustmentCache.inventoryLotLineByWarehouseLot.size,
        adjustmentCache.inventoryLotById.size,
        adjustmentCache.inventoryByItemId.size,
        adjustmentCache.stockTakeLineByRecordId.size,
        elapsedMs(cacheBuildStartNs)
    )

    var successCount = 0
    var errorCount = 0
    val errors = mutableListOf<String>()
    val processedStockTakes = mutableSetOf<Pair<Long, String>>()
    val prepareStartNs = System.nanoTime()
    val recordsToPersist = mutableListOf<StockTakeRecord>()
    val postPersistActions = mutableListOf<Triple<StockTakeRecord, BigDecimal, BigDecimal>>()

    stockTakeRecords.forEach { record ->
        try {
            val payload = recordPayloadById[record.id]
            if (useClientPayload && payload == null) {
                errorCount++
                errors.add("No client payload for record ${record.id}")
                return@forEach
            }

            val (qty, varianceQty) = if (payload != null) {
                applyApproverSaveToRecord(
                    record,
                    payload.copy(approverId = request.approverId),
                    request.approverId,
                    user?.name
                )
            } else {
                val legacyQty: BigDecimal
                val legacyBadQty: BigDecimal
                if (record.pickerSecondStockTakeQty != null && record.pickerSecondStockTakeQty!! > BigDecimal.ZERO) {
                    legacyQty = record.pickerSecondStockTakeQty!!
                    legacyBadQty = record.pickerSecondBadQty ?: BigDecimal.ZERO
                } else {
                    legacyQty = record.pickerFirstStockTakeQty ?: BigDecimal.ZERO
                    legacyBadQty = record.pickerFirstBadQty ?: BigDecimal.ZERO
                }
                val legacyVariance = legacyQty.subtract(record.bookQty ?: BigDecimal.ZERO)
                record.apply {
                    this.approverId = request.approverId
                    this.approverName = user?.name
                    this.approverStockTakeQty = legacyQty
                    this.approverBadQty = legacyBadQty
                    this.varianceQty = legacyVariance
                    this.status = "completed"
                    this.approverTime = java.time.LocalDateTime.now()
                    this.lastSelect = if (
                        record.pickerSecondStockTakeQty != null &&
                        record.pickerSecondStockTakeQty!! > BigDecimal.ZERO
                    ) 2 else 1
                    if (this.stockTakeEndTime == null) {
                        this.stockTakeEndTime = java.time.LocalDateTime.now()
                    }
                }
                Pair(legacyQty, legacyVariance)
            }

            recordsToPersist.add(record)
            postPersistActions.add(Triple(record, qty, varianceQty))
        } catch (e: Exception) {
            errorCount++
            val errorMsg = "Error processing record ${record.id}: ${e.message}"
            errors.add(errorMsg)
            logger.error(errorMsg, e)
        }
    }
    logger.info(
        "batchSaveApproverStockTakeRecordsByIds prepare completed: readyToPersist={}, precheckErrors={}, prepareMs={}",
        recordsToPersist.size,
        errorCount,
        elapsedMs(prepareStartNs)
    )

    if (recordsToPersist.isNotEmpty()) {
        val persistStartNs = System.nanoTime()
        stockTakeRecordRepository.saveAll(recordsToPersist)
        logger.info(
            "batchSaveApproverStockTakeRecordsByIds persist completed: persisted={}, persistMs={}",
            recordsToPersist.size,
            elapsedMs(persistStartNs)
        )

        val adjustmentStartNs = System.nanoTime()
        val runtimeCache = BatchAdjustmentRuntimeCache(
            stockOutByStockTakeId = mutableMapOf(),
            stockInByStockTakeId = mutableMapOf(),
            runningLedgerBalanceByItemId = mutableMapOf()
        )
        val adjustmentContext = StockTakeAdjustmentBatchContext()
        var varianceCount = 0
        var noVarianceCount = 0
        var varianceMs = 0L
        var noVarianceMs = 0L
        postPersistActions.forEach { (record, qty, varianceQty) ->
            try {
                if (varianceQty != BigDecimal.ZERO) {
                    val varianceStartNs = System.nanoTime()
                    applyVarianceAdjustment(
                        record.stockTake ?: stockTake,
                        record,
                        qty,
                        varianceQty,
                        request.approverId,
                        adjustmentCache,
                        runtimeCache,
                        adjustmentContext
                    )
                    varianceMs += elapsedMs(varianceStartNs)
                    varianceCount++
                } else {
                    val noVarianceStartNs = System.nanoTime()
                    completeStockTakeLineForApproverNoVariance(
                        record.stockTake ?: stockTake,
                        record,
                        qty,
                        adjustmentCache,
                        adjustmentContext
                    )
                    noVarianceMs += elapsedMs(noVarianceStartNs)
                    noVarianceCount++
                }
                val stId = record.stockTake?.id
                val section = record.stockTakeSection
                if (stId != null && section != null) {
                    processedStockTakes.add(Pair(stId, section))
                }
                successCount++
            } catch (e: Exception) {
                logger.error("Failed to apply inventory/line update for record ${record.id}", e)
                errorCount++
                errors.add("Record ${record.id}: ${e.message}")
            }
        }
        val flushStartNs = System.nanoTime()
        flushStockTakeAdjustmentBatchContext(adjustmentContext)
        val flushMs = elapsedMs(flushStartNs)
        logger.info(
            "batchSaveApproverStockTakeRecordsByIds adjustment completed: successSoFar={}, errorsSoFar={}, adjustmentMs={}, varianceCount={}, varianceMs={}, noVarianceCount={}, noVarianceMs={}, flushMs={}",
            successCount,
            errorCount,
            elapsedMs(adjustmentStartNs),
            varianceCount,
            varianceMs,
            noVarianceCount,
            noVarianceMs,
            flushMs
        )
        logger.info(
            "batchSaveApproverStockTakeRecordsByIds runtime cache stats: stockOutHeads={}, stockInHeads={}, ledgerItems={}, batchedStockTakeLines={}, batchedOutLines={}, batchedInLines={}, batchedInventoryLotLines={}, batchedLedgers={}",
            runtimeCache.stockOutByStockTakeId.size,
            runtimeCache.stockInByStockTakeId.size,
            runtimeCache.runningLedgerBalanceByItemId.size,
            adjustmentContext.stockTakeLineByRecordId.size,
            adjustmentContext.stockOutLines.size,
            adjustmentContext.stockInLines.size,
            adjustmentContext.inventoryLotLineById.size,
            adjustmentContext.stockLedgers.size
        )
    }

    if (successCount > 0) {
        val statusStartNs = System.nanoTime()
        processedStockTakes.forEach { (stId, section) ->
            checkAndUpdateStockTakeStatus(stId, section)
        }
        logger.info(
            "batchSaveApproverStockTakeRecordsByIds status update completed: stockTakes={}, statusMs={}",
            processedStockTakes.size,
            elapsedMs(statusStartNs)
        )
    }

    logger.info(
        "batchSaveApproverStockTakeRecordsByIds completed: success={}, errors={}, totalMs={}",
        successCount,
        errorCount,
        elapsedMs(totalStartNs)
    )
    return BatchSaveApproverStockTakeRecordResponse(
        successCount = successCount,
        errorCount = errorCount,
        errors = errors
    )
}

/**
 * stockTakeRecord 上存的是 inventory_lot.id（批次），不是 inventory_lot_line.id；用倉庫 + 批次找唯一庫存行。
 */
private fun resolveInventoryLotLineForStockTakeRecord(record: StockTakeRecord): InventoryLotLine {
    return resolveInventoryLotLineForStockTakeRecord(record, null)
}

private fun resolveInventoryLotLineForStockTakeRecord(
    record: StockTakeRecord,
    cache: BatchAdjustmentCache?
): InventoryLotLine {
    val warehouseId = record.warehouse?.id
        ?: throw IllegalArgumentException("Warehouse not found on stock take record")
    val lotId = record.inventoryLotId ?: record.lotId
        ?: throw IllegalArgumentException("Inventory lot ID not found on stock take record")
    val cacheKey = Pair(warehouseId, lotId)
    cache?.inventoryLotLineByWarehouseLot?.get(cacheKey)?.let { return it }
    val lines = inventoryLotLineRepository.findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse(
        listOf(warehouseId),
        listOf(lotId)
    )
    if (lines.isEmpty()) {
        throw IllegalArgumentException(
            "Inventory lot line not found for warehouseId=$warehouseId inventoryLotId=$lotId"
        )
    }
    return lines.maxByOrNull { it.id ?: 0L }!!
}

/**
 * 核准後差異為 0：將開輪預建的 [StockTakeLine] 標記為完成（不過帳）。
 * 舊資料無預建 line 時不做事。
 */
private fun completeStockTakeLineForApproverNoVariance(
    stockTake: StockTake,
    stockTakeRecord: StockTakeRecord,
    finalQty: BigDecimal,
    cache: BatchAdjustmentCache? = null,
    context: StockTakeAdjustmentBatchContext? = null
) {
    val rid = stockTakeRecord.id ?: return
    val line = cache?.stockTakeLineByRecordId?.get(rid)
        ?: stockTakeLineRepository.findByStockTakeRecord_IdAndDeletedIsFalse(rid)
        ?: return
    line.apply {
        this.stockTake = stockTake
        this.initialQty = this.initialQty ?: stockTakeRecord.bookQty
        this.finalQty = finalQty
        this.status = StockTakeLineStatus.COMPLETED
        this.completeDate = LocalDateTime.now()
        this.stockTakeRecord = stockTakeRecord
    }
    if (context != null) {
        context.stockTakeLineByRecordId[rid] = line
    } else {
        stockTakeLineRepository.save(line)
    }
}

/**
 * 依盤點結果調整庫存並寫 Ledger。
 * - 盤虧：實際出庫量 = **核准當下**該庫存行剩餘（in−out−hold）與盤點目標 [finalQty] 之差，對齊「期中已有出庫」的情境（例如 book 快照 1000、期中出 250、盤 0 → 只再沖 750）。
 * - 盤盈：將盤盈量 **加在既有** [InventoryLotLine.inQty]（同一批／倉），不新建 [InventoryLot]；TKE 的 [StockInLine] 不綁 inventoryLotLine，避免與原入庫單行 OneToOne 衝突。
 * - [stockTakeRecord.varianceQty] 仍為 picker/approver 數量 − 預建 bookQty（報表用），與此處實際過帳量可不同。
 */
private fun applyVarianceAdjustment(
    stockTake: StockTake,
    stockTakeRecord: StockTakeRecord,
    finalQty: BigDecimal,
    varianceQty: BigDecimal,
    approverId: Long?,
    cache: BatchAdjustmentCache? = null,
    runtimeCache: BatchAdjustmentRuntimeCache? = null,
    context: StockTakeAdjustmentBatchContext? = null
) {
    if (varianceQty == BigDecimal.ZERO) return

    val inventoryLotLine = resolveInventoryLotLineForStockTakeRecord(stockTakeRecord, cache)

    val inventoryLotId = inventoryLotLine.inventoryLot?.id
        ?: throw IllegalArgumentException("Inventory lot ID not found")
    val inventoryLot = cache?.inventoryLotById?.get(inventoryLotId)
        ?: inventoryLotRepository.findByIdAndDeletedFalse(inventoryLotId)
        ?: throw IllegalArgumentException("Inventory lot not found")

    val itemId = inventoryLot.item?.id ?: throw IllegalArgumentException("Item ID not found")
    val inventory = cache?.inventoryByItemId?.get(itemId)
        ?: inventoryRepository.findByItemId(itemId).orElse(null)
        ?: throw IllegalArgumentException("Inventory not found for item")

    // 1. 更新開輪預建的 StockTakeLine，或舊資料無預建時新建
    val stockTakeLine = stockTakeRecord.id?.let { rid ->
        cache?.stockTakeLineByRecordId?.get(rid) ?: stockTakeLineRepository.findByStockTakeRecord_IdAndDeletedIsFalse(rid)
    }?.also { existing ->
        existing.apply {
            this.stockTake = stockTake
            this.inventoryLotLine = inventoryLotLine
            this.initialQty = this.initialQty ?: stockTakeRecord.bookQty
            this.finalQty = finalQty
            this.status = StockTakeLineStatus.COMPLETED
            this.completeDate = LocalDateTime.now()
            this.stockTakeRecord = stockTakeRecord
        }
    } ?: StockTakeLine().apply {
        this.stockTake = stockTake
        this.inventoryLotLine = inventoryLotLine
        this.initialQty = stockTakeRecord.bookQty
        this.finalQty = finalQty
        this.status = StockTakeLineStatus.COMPLETED
        this.completeDate = LocalDateTime.now()
        this.stockTakeRecord = stockTakeRecord
    }
    val stockTakeRecordId = stockTakeRecord.id
    if (context != null && stockTakeRecordId != null) {
        context.stockTakeLineByRecordId[stockTakeRecordId] = stockTakeLine
    } else {
        stockTakeLineRepository.save(stockTakeLine)
    }

    val zero = BigDecimal.ZERO

    // 2. variance < 0：出库 + StockLedger（實際沖銷量依「當下庫存行」計算）
    if (varianceQty < BigDecimal.ZERO) {
        val latestLine = inventoryLotLineRepository.findById(inventoryLotLine.id!!).orElseThrow()
        val currentRemaining = (latestLine.inQty ?: zero)
            .subtract(latestLine.outQty ?: zero)
            .subtract(latestLine.holdQty ?: zero)
        val qtyToRemove = currentRemaining.subtract(finalQty).coerceAtLeast(zero)
        if (qtyToRemove.compareTo(zero) == 0) {
            return
        }

        val stockTakeId = stockTake.id ?: throw IllegalArgumentException("Stock take ID not found")
        val stockOut = if (runtimeCache != null) {
            runtimeCache.stockOutByStockTakeId.getOrPut(stockTakeId) {
                stockOutRepository.findByStockTakeIdAndDeletedFalse(stockTakeId)
                    ?: StockOut().apply {
                        this.type = "stockTake"
                        this.status = "completed"
                        this.handler = approverId
                    }.also { stockOutRepository.save(it) }
            }
        } else {
            stockOutRepository.findByStockTakeIdAndDeletedFalse(stockTakeId)
                ?: StockOut().apply {
                    this.type = "stockTake"
                    this.status = "completed"
                    this.handler = approverId
                }.also { stockOutRepository.save(it) }
        }

        val stockOutLine = StockOutLine().apply {
            this.item = inventoryLot.item
            this.qty = qtyToRemove.toDouble()
            this.stockOut = stockOut
            this.inventoryLotLine = latestLine
            this.status = "completed"
            this.type = "TKE"
        }
        if (context != null) {
            context.stockOutLines.add(stockOutLine)
        } else {
            stockOutLineRepository.save(stockOutLine)
        }

        // 與 StockOutLineService.createStockLedgerForStockOut 一致：依上一筆 ledger balance 扣減，
        // 避免同一批多筆盤虧時每筆都用同一個 inventory.onHandQty 導致 balance 錯誤。
        val itemIdForLedger = inventoryLot.item?.id
            ?: throw IllegalArgumentException("Item ID not found for stock take ledger")
        val previousBalance = runtimeCache?.runningLedgerBalanceByItemId?.get(itemIdForLedger)
            ?: run {
                val latestLedger = stockLedgerRepository.findFirstByItemIdAndDeletedFalseOrderByDateDescIdDesc(itemIdForLedger)
                latestLedger?.balance ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
            }
        val newBalance = previousBalance - qtyToRemove.toDouble()

        val stockLedger = StockLedger().apply {
            this.inventory = inventory
            this.itemId = inventoryLot.item?.id
            this.itemCode = stockTakeRecord.itemCode
            this.inQty = null
            this.outQty = qtyToRemove.toDouble()
            this.stockOutLine = stockOutLine
            this.balance = newBalance
            this.type = "TKE"
            this.uomId = latestLine.stockUom?.uom?.id ?: inventory.uom?.id
            this.date = LocalDate.now()
        }

        if (context != null) {
            context.stockLedgers.add(stockLedger)
        } else {
            stockLedgerRepository.save(stockLedger)
        }
        runtimeCache?.runningLedgerBalanceByItemId?.put(itemIdForLedger, newBalance)

        val newOutQty = (latestLine.outQty ?: zero).add(qtyToRemove)
        latestLine.outQty = newOutQty
        latestLine.status = inventoryLotLineService.deriveInventoryLotLineStatus(
            latestLine.status,
            latestLine.inQty,
            latestLine.outQty,
            latestLine.holdQty
        )
        if (context != null && latestLine.id != null) {
            context.inventoryLotLineById[latestLine.id!!] = latestLine
        } else {
            val updateRequest = SaveInventoryLotLineRequest(
                id = latestLine.id,
                inventoryLotId = latestLine.inventoryLot?.id,
                warehouseId = latestLine.warehouse?.id,
                stockUomId = latestLine.stockUom?.id,
                inQty = latestLine.inQty,
                outQty = latestLine.outQty,
                holdQty = latestLine.holdQty,
                status = latestLine.status?.value,
                remarks = latestLine.remarks
            )
            inventoryLotLineService.saveInventoryLotLine(updateRequest)
        }
    }

    // 3. variance > 0：入庫加在既有庫存行 inQty + StockLedger
    if (varianceQty > BigDecimal.ZERO) {
        val plusQty = varianceQty
        val latestLine = inventoryLotLineRepository.findById(inventoryLotLine.id!!).orElseThrow()
        val newInQty = (latestLine.inQty ?: zero).add(plusQty)

        val stockTakeId = stockTake.id ?: throw IllegalArgumentException("Stock take ID not found")
        val stockIn = if (runtimeCache != null) {
            runtimeCache.stockInByStockTakeId.getOrPut(stockTakeId) {
                stockInRepository.findByStockTakeIdAndDeletedFalse(stockTakeId)
                    ?: StockIn().apply {
                        this.code = stockTake.code
                        this.status = "completed"
                        this.stockTake = stockTake
                    }.also { stockInRepository.save(it) }
            }
        } else {
            stockInRepository.findByStockTakeIdAndDeletedFalse(stockTakeId)
                ?: StockIn().apply {
                    this.code = stockTake.code
                    this.status = "completed"
                    this.stockTake = stockTake
                }.also { stockInRepository.save(it) }
        }

        val stockInLine = StockInLine().apply {
            this.stockTakeLine = stockTakeLine
            this.item = inventoryLot.item
            this.itemNo = stockTakeRecord.itemCode
            this.stockIn = stockIn
            this.demandQty = plusQty
            this.acceptedQty = plusQty
            this.expiryDate = inventoryLot.expiryDate
            this.lotNo = inventoryLot.lotNo
            this.status = "completed"
            this.type = "TKE"
            this.inventoryLot = inventoryLot
            // 原入庫已綁定之 inventory_lot_line 與 SIL 多為 OneToOne；盤盈改加同一行 inQty，此處不綁 line 以免衝突
            this.inventoryLotLine = null
        }
        if (context != null) {
            context.stockInLines.add(stockInLine)
        } else {
            stockInLineRepository.save(stockInLine)
        }

        latestLine.inQty = newInQty
        latestLine.status = inventoryLotLineService.deriveInventoryLotLineStatus(
            latestLine.status,
            latestLine.inQty,
            latestLine.outQty,
            latestLine.holdQty
        )
        if (context != null && latestLine.id != null) {
            context.inventoryLotLineById[latestLine.id!!] = latestLine
        } else {
            val updateRequest = SaveInventoryLotLineRequest(
                id = latestLine.id,
                inventoryLotId = latestLine.inventoryLot?.id,
                warehouseId = latestLine.warehouse?.id,
                stockUomId = latestLine.stockUom?.id,
                inQty = latestLine.inQty,
                outQty = latestLine.outQty,
                holdQty = latestLine.holdQty,
                status = latestLine.status?.value,
                remarks = latestLine.remarks
            )
            inventoryLotLineService.saveInventoryLotLine(updateRequest)
        }

        val itemIdForLedger = inventoryLot.item?.id
            ?: throw IllegalArgumentException("Item ID not found for stock take ledger (in)")
        val previousBalance = runtimeCache?.runningLedgerBalanceByItemId?.get(itemIdForLedger)
            ?: run {
                val latestLedger = stockLedgerRepository.findFirstByItemIdAndDeletedFalseOrderByDateDescIdDesc(itemIdForLedger)
                latestLedger?.balance ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
            }
        val newBalance = previousBalance + plusQty.toDouble()

        val stockLedger = StockLedger().apply {
            this.inventory = inventory
            this.itemId = inventoryLot.item?.id
            this.itemCode = inventoryLot.item?.code
            this.inQty = plusQty.toDouble()
            this.outQty = null
            this.stockInLine = stockInLine
            this.balance = newBalance
            this.type = "TKE"
            this.uomId = latestLine.stockUom?.uom?.id ?: inventory.uom?.id
            this.date = LocalDate.now()
        }

        if (context != null) {
            context.stockLedgers.add(stockLedger)
        } else {
            stockLedgerRepository.save(stockLedger)
        }
        runtimeCache?.runningLedgerBalanceByItemId?.put(itemIdForLedger, newBalance)
    }
}

private data class BatchAdjustmentCache(
    val inventoryLotLineByWarehouseLot: Map<Pair<Long, Long>, InventoryLotLine>,
    val inventoryLotById: Map<Long, InventoryLot>,
    val inventoryByItemId: Map<Long, com.ffii.fpsms.modules.stock.entity.Inventory>,
    val stockTakeLineByRecordId: Map<Long, StockTakeLine>
)

private data class BatchAdjustmentRuntimeCache(
    val stockOutByStockTakeId: MutableMap<Long, StockOut>,
    val stockInByStockTakeId: MutableMap<Long, StockIn>,
    val runningLedgerBalanceByItemId: MutableMap<Long, Double>
)

private data class StockTakeAdjustmentBatchContext(
    val stockTakeLineByRecordId: MutableMap<Long, StockTakeLine> = LinkedHashMap(),
    val stockOutLines: MutableList<StockOutLine> = mutableListOf(),
    val stockInLines: MutableList<StockInLine> = mutableListOf(),
    val inventoryLotLineById: MutableMap<Long, InventoryLotLine> = LinkedHashMap(),
    val stockLedgers: MutableList<StockLedger> = mutableListOf()
)

private fun flushStockTakeAdjustmentBatchContext(context: StockTakeAdjustmentBatchContext) {
    if (context.stockTakeLineByRecordId.isNotEmpty()) {
        stockTakeLineRepository.saveAll(context.stockTakeLineByRecordId.values.toList())
    }
    if (context.stockOutLines.isNotEmpty()) {
        stockOutLineRepository.saveAll(context.stockOutLines)
    }
    if (context.stockInLines.isNotEmpty()) {
        stockInLineRepository.saveAll(context.stockInLines)
    }
    if (context.inventoryLotLineById.isNotEmpty()) {
        inventoryLotLineRepository.saveAll(context.inventoryLotLineById.values.toList())
    }
    if (context.stockLedgers.isNotEmpty()) {
        stockLedgerRepository.saveAll(context.stockLedgers)
    }
}

private fun buildBatchAdjustmentCache(records: List<StockTakeRecord>): BatchAdjustmentCache {
    val pairs = records.mapNotNull { r ->
        val warehouseId = r.warehouse?.id
        val lotId = r.inventoryLotId ?: r.lotId
        if (warehouseId != null && lotId != null) Pair(warehouseId, lotId) else null
    }.toSet()
    val warehouseIds = pairs.map { it.first }.toSet()
    val lotIds = pairs.map { it.second }.toSet()
    val lotLines =
        if (warehouseIds.isNotEmpty() && lotIds.isNotEmpty()) {
            inventoryLotLineRepository.findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse(warehouseIds, lotIds)
        } else {
            emptyList()
        }
    val inventoryLotLineByWarehouseLot = lotLines
        .groupBy { Pair(it.warehouse?.id ?: 0L, it.inventoryLot?.id ?: 0L) }
        .mapValues { (_, lines) -> lines.maxByOrNull { it.id ?: 0L }!! }

    val inventoryLotIds = lotLines.mapNotNull { it.inventoryLot?.id }.distinct()
    val inventoryLotById =
        if (inventoryLotIds.isNotEmpty()) {
            inventoryLotRepository.findAllByIdIn(inventoryLotIds).associateByNotNull { it.id }
        } else {
            emptyMap()
        }
    val itemIds = inventoryLotById.values.mapNotNull { it.item?.id }.distinct()
    val inventoryByItemId =
        if (itemIds.isNotEmpty()) {
            inventoryRepository.findAllByItemIdInAndDeletedIsFalse(itemIds)
                .groupBy { it.item?.id ?: 0L }
                .mapValues { (_, list) -> list.minByOrNull { it.id ?: Long.MAX_VALUE }!! }
        } else {
            emptyMap()
        }
    val recordIds = records.mapNotNull { it.id }.distinct()
    val stockTakeLineByRecordId =
        if (recordIds.isNotEmpty()) {
            stockTakeLineRepository.findAllByStockTakeRecord_IdInAndDeletedIsFalse(recordIds)
                .associateByNotNull { it.stockTakeRecord?.id }
        } else {
            emptyMap()
        }

    return BatchAdjustmentCache(
        inventoryLotLineByWarehouseLot = inventoryLotLineByWarehouseLot,
        inventoryLotById = inventoryLotById,
        inventoryByItemId = inventoryByItemId,
        stockTakeLineByRecordId = stockTakeLineByRecordId
    )
}

private inline fun <T : Any, K : Any> Iterable<T>.associateByNotNull(keySelector: (T) -> K?): Map<K, T> {
    val destination = LinkedHashMap<K, T>()
    for (element in this) {
        val key = keySelector(element) ?: continue
        destination[key] = element
    }
    return destination
}
open fun updateStockTakeRecordStatusToNotMatch(stockTakeRecordId: Long): StockTakeRecord {
    println("updateStockTakeRecordStatusToNotMatch called with stockTakeRecordId: $stockTakeRecordId")
    
    val stockTakeRecord = stockTakeRecordRepository.findByIdAndDeletedIsFalse(stockTakeRecordId)
        ?: throw IllegalArgumentException("Stock take record not found: $stockTakeRecordId")
    
    stockTakeRecord.apply {
        this.status = "notMatch"
    }
    
    return stockTakeRecordRepository.save(stockTakeRecord)
}

open fun getInventoryLotDetailsByStockTakeSectionNotMatch(
    stockTakeSection: String,
    stockTakeId: Long? = null,
    stockTakeRoundId: Long? = null,
    pageNum: Int = 0,
    pageSize: Int = Int.MAX_VALUE
): RecordsRes<InventoryLotDetailResponse> {
    println(
        "getInventoryLotDetailsByStockTakeSectionNotMatch called with section: $stockTakeSection, stockTakeId: $stockTakeId, " +
            "stockTakeRoundId: $stockTakeRoundId, pageNum: $pageNum, pageSize: $pageSize"
    )

    val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection)
    if (warehouses.isEmpty()) {
        logger.warn("No warehouses found for stockTakeSection: $stockTakeSection")
        return RecordsRes(emptyList(), 0)
    }

    val warehouseIds = warehouses.mapNotNull { it.id }.toSet()
    println("Found ${warehouses.size} warehouses for section $stockTakeSection")

    if (stockTakeId != null) {
        val baseStockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId)
            ?: return RecordsRes(emptyList(), 0)
        val baseId = baseStockTake.id ?: return RecordsRes(emptyList(), 0)
        val roundId = stockTakeRoundId ?: baseStockTake.stockTakeRoundId ?: baseId

        val notMatchRecords = stockTakeRecordRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId)
            .filter {
                !it.deleted &&
                    it.stockTakeSection == stockTakeSection &&
                    (it.warehouse?.id in warehouseIds) &&
                    it.status == "notMatch"
            }

        if (notMatchRecords.isNotEmpty()) {
            val lotIds = notMatchRecords.mapNotNull { it.lotId }.toSet()
            val whIds = notMatchRecords.mapNotNull { it.warehouse?.id }.toSet()
            val illList =
                if (lotIds.isNotEmpty() && whIds.isNotEmpty()) {
                    inventoryLotLineRepository.findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse(whIds, lotIds)
                } else {
                    emptyList()
                }
            val illByPair = illList.associateBy {
                Pair(it.inventoryLot?.id ?: 0L, it.warehouse?.id ?: 0L)
            }

            val effectiveStockTakeId = stockTakeId
            val allResults = notMatchRecords.mapNotNull { rec ->
                val ill = illByPair[Pair(rec.lotId ?: 0L, rec.warehouse?.id ?: 0L)]
                    ?: return@mapNotNull null
                val inventoryLot = ill.inventoryLot
                val warehouse = ill.warehouse
                val availableQty = (ill.inQty ?: BigDecimal.ZERO)
                    .subtract(ill.outQty ?: BigDecimal.ZERO)
                   // .subtract(ill.holdQty ?: BigDecimal.ZERO)
                val inventoryLotLineId = ill.id
                val stockTakeLine =
                    if (effectiveStockTakeId != null && inventoryLotLineId != null) {
                        stockTakeLineRepository.findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse(
                            inventoryLotLineId,
                            effectiveStockTakeId
                        )
                    } else {
                        null
                    }
                InventoryLotDetailResponse(
                    id = ill.id ?: 0L,
                    inventoryLotId = inventoryLot?.id ?: 0L,
                    itemId = inventoryLot?.item?.id ?: 0L,
                    itemCode = inventoryLot?.item?.code,
                    itemName = inventoryLot?.item?.name,
                    lotNo = inventoryLot?.lotNo,
                    expiryDate = inventoryLot?.expiryDate,
                    productionDate = inventoryLot?.productionDate,
                    stockInDate = inventoryLot?.stockInDate,
                    inQty = ill.inQty,
                    remarks = rec.remarks,
                    outQty = ill.outQty,
                    holdQty = ill.holdQty,
                    availableQty = availableQty,
                    uom = ill.stockUom?.uom?.udfudesc,
                    warehouseCode = warehouse?.code,
                    warehouseName = warehouse?.name,
                    status = ill.status?.name,
                    warehouseSlot = warehouse?.slot,
                    warehouseArea = warehouse?.area,
                    warehouse = warehouse?.warehouse,
                    storeId = warehouse?.store_id,
                    varianceQty = rec.varianceQty,
                    stockTakeRecordId = rec.id,
                    stockTakeRecordStatus = rec.status,
                    firstStockTakeQty = rec.pickerFirstStockTakeQty,
                    secondStockTakeQty = rec.pickerSecondStockTakeQty,
                    firstBadQty = rec.pickerFirstBadQty,
                    secondBadQty = rec.pickerSecondBadQty,
                    approverQty = rec.approverStockTakeQty,
                    approverBadQty = rec.approverBadQty,
                    finalQty = stockTakeLine?.finalQty,
                    bookQty = rec.bookQty,
                    stockTakeSection = rec.stockTakeSection ?: warehouse?.stockTakeSection,
                    stockTakeSectionDescription = warehouse?.stockTakeSectionDescription,
                    stockTakerName = rec.stockTakerName,
                    stockTakeEndTime = rec.stockTakeEndTime,
                    approverTime = rec.approverTime,
                )
            }.sortedWith(compareBy { it.itemCode })

            val paginatedResult = if (pageSize >= allResults.size) {
                allResults
            } else {
                val pageable = PageRequest.of(pageNum, pageSize)
                val startIndex = pageable.offset.toInt()
                val endIndex = minOf(startIndex + pageSize, allResults.size)
                if (startIndex < allResults.size) {
                    allResults.subList(startIndex, endIndex)
                } else {
                    emptyList()
                }
            }
            return RecordsRes(paginatedResult, allResults.size)
        }
    }

    val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds.toList())
    println("Found ${inventoryLotLines.size} inventory lot lines (legacy)")

    val stockTakeRecordsMap = if (stockTakeId != null) {
        stockTakeRecordRepository.findAll()
            .filter {
                !it.deleted &&
                    it.stockTake?.id == stockTakeId &&
                    it.warehouse?.id in warehouseIds &&
                    it.status == "notMatch"
            }
            .associateBy {
                Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L)
            }
    } else {
        emptyMap()
    }

    val allResults = inventoryLotLines.mapNotNull { ill ->
        val inventoryLot = ill.inventoryLot
        val warehouse = ill.warehouse
        val availableQty = (ill.inQty ?: BigDecimal.ZERO)
            .subtract(ill.outQty ?: BigDecimal.ZERO)
            //.subtract(ill.holdQty ?: BigDecimal.ZERO)

        val stockTakeRecord = if (stockTakeId != null && inventoryLot?.id != null && warehouse?.id != null) {
            stockTakeRecordsMap[Pair(inventoryLot.id, warehouse.id)]
        } else {
            null
        }

        if (stockTakeRecord == null || stockTakeRecord.status != "notMatch") {
            return@mapNotNull null
        }

        val inventoryLotLineId = ill.id
        val stockTakeLine = if (stockTakeId != null && inventoryLotLineId != null) {
            stockTakeLineRepository.findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse(inventoryLotLineId, stockTakeId)
        } else {
            null
        }

        InventoryLotDetailResponse(
            id = ill.id ?: 0L,
            inventoryLotId = inventoryLot?.id ?: 0L,
            itemId = inventoryLot?.item?.id ?: 0L,
            itemCode = inventoryLot?.item?.code,
            itemName = inventoryLot?.item?.name,
            lotNo = inventoryLot?.lotNo,
            expiryDate = inventoryLot?.expiryDate,
            productionDate = inventoryLot?.productionDate,
            stockInDate = inventoryLot?.stockInDate,
            inQty = ill.inQty,
            remarks = stockTakeRecord.remarks,
            outQty = ill.outQty,
            holdQty = ill.holdQty,
            availableQty = availableQty,
            uom = ill.stockUom?.uom?.udfudesc,
            warehouseCode = warehouse?.code,
            warehouseName = warehouse?.name,
            status = ill.status?.name,
            warehouseSlot = warehouse?.slot,
            warehouseArea = warehouse?.area,
            warehouse = warehouse?.warehouse,
            storeId = warehouse?.store_id,
            varianceQty = stockTakeRecord.varianceQty,
            stockTakeRecordId = stockTakeRecord.id,
            stockTakeRecordStatus = stockTakeRecord.status,
            firstStockTakeQty = stockTakeRecord.pickerFirstStockTakeQty,
            secondStockTakeQty = stockTakeRecord.pickerSecondStockTakeQty,
            firstBadQty = stockTakeRecord.pickerFirstBadQty,
            secondBadQty = stockTakeRecord.pickerSecondBadQty,
            approverQty = stockTakeRecord.approverStockTakeQty,
            approverBadQty = stockTakeRecord.approverBadQty,
            finalQty = stockTakeLine?.finalQty,
            bookQty = stockTakeRecord.bookQty,
            stockTakeSection = stockTakeRecord.stockTakeSection ?: warehouse?.stockTakeSection,
            stockTakeSectionDescription = warehouse?.stockTakeSectionDescription,
            stockTakerName = stockTakeRecord.stockTakerName,
            stockTakeEndTime = stockTakeRecord.stockTakeEndTime,
            approverTime = stockTakeRecord.approverTime,
        )
    }

    val paginatedResult = if (pageSize >= allResults.size) {
        allResults
    } else {
        val pageable = PageRequest.of(pageNum, pageSize)
        val startIndex = pageable.offset.toInt()
        val endIndex = minOf(startIndex + pageSize, allResults.size)
        if (startIndex < allResults.size) {
            allResults.subList(startIndex, endIndex)
        } else {
            emptyList()
        }
    }

    return RecordsRes(paginatedResult, allResults.size)
}



open fun searchStockTransactions(request: SearchStockTransactionRequest): RecordsRes<StockTransactionResponse> {
    val startTime = System.currentTimeMillis()

    println(
        "Search request received: itemCode=${request.itemCode}, itemName=${request.itemName}, " +
            "type=${request.type}, startDate=${request.startDate}, endDate=${request.endDate}"
    )

    val itemCode = request.itemCode?.trim()?.takeIf { it.isNotEmpty() }
    val itemName = request.itemName?.trim()?.takeIf { it.isNotEmpty() }

    if (itemCode == null && itemName == null) {
        println("Search validation failed: both itemCode and itemName are null/empty")
        return RecordsRes(emptyList(), 0)
    }

    val startDateTime = request.startDate?.atStartOfDay()
    val endDateExclusive = request.endDate?.plusDays(1)?.atStartOfDay()

    println(
        "Processed params: itemCode=$itemCode, itemName=$itemName, " +
            "startDateTime=$startDateTime, endDateExclusive=$endDateExclusive"
    )

    val pageable = PageRequest.of(
        request.pageNum.coerceAtLeast(0),
        request.pageSize.coerceAtLeast(1),
        Sort.by(Sort.Order.asc("created"), Sort.Order.asc("itemId"))
    )

    val ledgerPage = stockLedgerRepository.findStockTransactions(
        itemCode = itemCode,
        itemName = itemName,
        type = request.type,
        startDateTime = startDateTime,
        endDateExclusive = endDateExclusive,
        pageable = pageable
    )

    println("Found ${ledgerPage.numberOfElements} ledgers in current page, total=${ledgerPage.totalElements}")

    val transactions = ledgerPage.content.map { ledger ->
        val stockInLine = ledger.stockInLine
        val stockOutLine = ledger.stockOutLine

        StockTransactionResponse(
            id = stockInLine?.id ?: stockOutLine?.id ?: 0L,
            transactionType = if (ledger.inQty != null && ledger.inQty!! > 0) "IN" else "OUT",
            itemId = ledger.itemId ?: 0L,
            itemCode = ledger.itemCode,
            itemName = ledger.inventory?.item?.name,
            uomId = ledger.uomId,
            uomDesc = ledger.uomId?.let { uomId ->
                uomConversionRepository.findByIdAndDeletedFalse(uomId)?.udfudesc
            },
            balanceQty = ledger.balance?.let { balance: Double -> BigDecimal(balance.toString()) },
            qty = if (ledger.inQty != null && ledger.inQty!! > 0) {
                BigDecimal(ledger.inQty.toString())
            } else {
                BigDecimal(ledger.outQty?.toString() ?: "0")
            },
            type = ledger.type,
            status = stockInLine?.status ?: stockOutLine?.status ?: "",
            transactionDate = ledger.created,
            date = ledger.date,
            lotNo = stockInLine?.lotNo ?: (stockOutLine?.inventoryLotLine?.inventoryLot?.lotNo),
            stockInId = stockInLine?.stockIn?.id,
            stockOutId = stockOutLine?.stockOut?.id,
            remarks = stockInLine?.remarks
        )
    }

    val totalTime = System.currentTimeMillis() - startTime
    println("Total time (Repository query): ${totalTime}ms, count: ${transactions.size}, total: ${ledgerPage.totalElements}")

    return RecordsRes(transactions, ledgerPage.totalElements.toInt())
}
}
