diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt index 2d93d08..5e45ca8 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt @@ -364,187 +364,83 @@ class StockTakeRecordService( return RecordsRes(paginatedResult, filteredResults.size) } open fun AllApproverStockTakeList(): List { - // 1. 获取 section 与仓库基础数据 - val allWarehouses = warehouseRepository.findAllByDeletedIsFalse() - val distinctSections = allWarehouses - .mapNotNull { it.stockTakeSection } - .distinct() - .filter { it.isNotBlank() } - if (distinctSections.isEmpty()) return emptyList() - - val warehousesBySection = allWarehouses - .filter { !it.stockTakeSection.isNullOrBlank() } - .groupBy { it.stockTakeSection!! } - val sectionByWarehouseId = allWarehouses - .mapNotNull { w -> w.id?.let { id -> id to (w.stockTakeSection ?: "") } } - .toMap() - val allWarehouseIds = allWarehouses.mapNotNull { it.id } + // Overall 卡:只取“最新一轮”,并且总数口径与 + // `approverInventoryLotDetailsAll?stockTakeId=...` 保持一致: + // - availableQty > 0 + // - 或该 (inventoryLotId, warehouseId) 在该轮次的 stockTakeRecord 已存在(即 stockTakeRecordId != null) - // 2. 批量加载核心数据(一次拿齐,避免循环中 repository 查询) - val allStockTakes = stockTakeRepository.findAll() - .filter { !it.deleted && !it.stockTakeSection.isNullOrBlank() } - .groupBy { it.stockTakeSection!! } - val latestStockTakeBySection = allStockTakes.mapValues { (_, takes) -> - takes.maxByOrNull { it.actualStart ?: it.planStart ?: LocalDateTime.MIN } - } - val latestStockTakeIds = latestStockTakeBySection.values.mapNotNull { it?.id }.toSet() - val allStockTakeRecords = if (latestStockTakeIds.isEmpty()) { - emptyList() - } else { - stockTakeRecordRepository.findAllByStockTakeIdInAndDeletedIsFalse(latestStockTakeIds) - } - val allInventoryLotLines = if (allWarehouseIds.isEmpty()) { - emptyList() - } else { - inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(allWarehouseIds) - } - val inventoryLotLinesBySection = allInventoryLotLines.groupBy { ill -> - sectionByWarehouseId[ill.warehouse?.id] ?: "" - } - - // 3. 预计算:同一 stockTake 下已盘点 key、人员名称、notMatch - val recordKeyByStockTakeId = allStockTakeRecords - .groupBy { it.stockTake?.id ?: -1L } - .mapValues { (_, records) -> - records.mapNotNull { r -> - val lotId = r.inventoryLotId - val whId = r.warehouse?.id - if (lotId != null && whId != null) Pair(lotId, whId) else null - }.toSet() - } - val latestStockTakerNameByStockTakeAndSection = allStockTakeRecords - .filter { !it.stockTakeSection.isNullOrBlank() && it.stockTake?.id != null && it.stockTakerName != null } - .groupBy { Pair(it.stockTake!!.id!!, it.stockTakeSection!!) } - .mapValues { (_, records) -> - records.maxByOrNull { it.stockTakeStartTime ?: LocalDateTime.MIN }?.stockTakerName - } - val latestApproverNameByStockTakeAndSection = allStockTakeRecords - .filter { !it.stockTakeSection.isNullOrBlank() && it.stockTake?.id != null && it.approverName != null } - .groupBy { Pair(it.stockTake!!.id!!, it.stockTakeSection!!) } - .mapValues { (_, records) -> - records.maxByOrNull { it.stockTakeStartTime ?: LocalDateTime.MIN }?.approverName - } - val hasNotMatchByStockTakeAndSection = allStockTakeRecords - .filter { it.stockTake?.id != null && !it.stockTakeSection.isNullOrBlank() && it.status == "notMatch" } - .map { Pair(it.stockTake!!.id!!, it.stockTakeSection!!) } - .toSet() - - val result = mutableListOf() - var idCounter = 1L + val allWarehouses = warehouseRepository.findAllByDeletedIsFalse() + val warehouseIds = allWarehouses.mapNotNull { it.id } + if (warehouseIds.isEmpty()) return emptyList() - // 3. 为每个 stockTakeSection 创建一个卡片 - distinctSections.forEach { stockTakeSection -> - // 4. 获取该 section 下的所有 warehouse - val warehouses = warehousesBySection[stockTakeSection] ?: emptyList() - val warehouseIds = warehouses.mapNotNull { it.id } + val allStockTakes = stockTakeRepository.findAll().filter { !it.deleted } + if (allStockTakes.isEmpty()) return emptyList() - if (warehouseIds.isEmpty()) { - return@forEach - } + val latestBaseStockTake = allStockTakes + .maxByOrNull { it.actualStart ?: it.planStart ?: LocalDateTime.MIN } + ?: return emptyList() - // 5. 获取该 section 相关的所有 stock_take 记录 - val stockTakesForSection = allStockTakes[stockTakeSection] ?: emptyList() + val roundStockTakeIds = resolveRoundStockTakeIds(latestBaseStockTake) + if (roundStockTakeIds.isEmpty()) return emptyList() - // 6. 获取 lastStockTakeDate:从 completed 状态的记录中,按 actualEnd 排序,取最新的 - val completedStockTakes = stockTakesForSection - .filter { it.status == StockTakeStatus.COMPLETED && it.actualEnd != null } - .sortedByDescending { it.actualEnd } + // stockTakeRecord 存在性:用来对齐 details 接口的 `stockTakeRecordId != null` + val roundStockTakeRecords = stockTakeRecordRepository.findAllByStockTakeIdInAndDeletedIsFalse(roundStockTakeIds) + .filter { it.warehouse?.id in warehouseIds } - val lastStockTakeDate = completedStockTakes.firstOrNull()?.actualEnd?.toLocalDate() + 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() - // 7. 获取 status:获取最新的 stock_take 记录(按 actualStart 或 planStart 排序) - val latestStockTake = stockTakesForSection - .maxByOrNull { it.actualStart ?: it.planStart ?: LocalDateTime.MIN } + val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) - // 8. 确定 status:只有 APPROVING 或 COMPLETED 状态才输出,其他为 null - val status = if (latestStockTake != null) { - val stockTakeStatus = latestStockTake.status - // 只有 APPROVING 或 COMPLETED 状态才输出值,其他返回 null - if (stockTakeStatus == StockTakeStatus.APPROVING || stockTakeStatus == StockTakeStatus.COMPLETED) { - stockTakeStatus.value - } else { - null - } - } else { - null - } - val stockTakerName = latestStockTake?.id?.let { latestStockTakerNameByStockTakeAndSection[Pair(it, stockTakeSection)] } - val approverName = latestStockTake?.id?.let { latestApproverNameByStockTakeAndSection[Pair(it, stockTakeSection)] } - val reStockTakeTrueFalse = latestStockTake?.id?.let { hasNotMatchByStockTakeAndSection.contains(Pair(it, stockTakeSection)) } ?: false - // 9. 计算 TotalItemNumber / TotalInventoryLotNumber(只统计“需要盘点的行”): - // 规则与前端 Picker / Approver 明细一致: - // - availableQty > 0,或 - // - 在 latestStockTake 下已经有 stockTakeRecord(即本轮参与盘点的 lot) - val inventoryLotLinesForSection = inventoryLotLinesBySection[stockTakeSection] ?: emptyList() - val recordedKeysForLatest = latestStockTake?.id?.let { recordKeyByStockTakeId[it] } ?: emptySet() - - val relevantInventoryLotLines = inventoryLotLinesForSection.filter { ill -> - val inventoryLot = ill.inventoryLot - val warehouse = ill.warehouse - if (inventoryLot?.id == null || warehouse?.id == null || latestStockTake?.id == null) { - false - } else { - val inQty = ill.inQty ?: BigDecimal.ZERO - val outQty = ill.outQty ?: BigDecimal.ZERO - val holdQty = ill.holdQty ?: BigDecimal.ZERO - val availableQty = inQty.subtract(outQty).subtract(holdQty) + 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 hasRecordForLatest = recordedKeysForLatest.contains(Pair(inventoryLot.id, warehouse.id)) - availableQty.compareTo(BigDecimal.ZERO) > 0 || hasRecordForLatest - } - } + val availableQty = (ill.inQty ?: BigDecimal.ZERO) + .subtract(ill.outQty ?: BigDecimal.ZERO) + .subtract(ill.holdQty ?: BigDecimal.ZERO) - val totalItemNumber = relevantInventoryLotLines - .mapNotNull { it.inventoryLot?.item?.id } - .distinct() - .count() + availableQty.compareTo(BigDecimal.ZERO) > 0 || recordKeySet.contains(Pair(lotId, whId)) + } - val totalInventoryLotNumber = relevantInventoryLotLines.size - val sectionDescription = warehouses - .mapNotNull { it.stockTakeSectionDescription } - .distinct() - .firstOrNull() - // 9. 使用 stockTakeSection 作为 stockTakeSession - result.add( - AllPickedStockTakeListReponse( - id = idCounter++, - stockTakeSession = stockTakeSection, - lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(), - status = status ?: "", - currentStockTakeItemNumber = 0, // 临时设为 0,测试性能 - totalInventoryLotNumber = totalInventoryLotNumber, // 临时设为 0,测试性能 - stockTakeId = latestStockTake?.id ?: 0, - stockTakeRoundId = latestStockTake?.stockTakeRoundId ?: latestStockTake?.id, - stockTakerName = stockTakerName, - approverName = approverName, - TotalItemNumber = totalItemNumber, - startTime = latestStockTake?.actualStart, - endTime = latestStockTake?.actualEnd, - ReStockTakeTrueFalse = reStockTakeTrueFalse, - planStartDate = latestStockTake?.planStart?.toLocalDate(), - stockTakeSectionDescription = sectionDescription + 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 "" + } ?: "" - // 10. 以 stockTakeRoundId 聚合为“每轮一张卡”;旧资料 roundId 为空时退回 stockTakeId - val groupedByRound = result.groupBy { it.stockTakeRoundId ?: it.stockTakeId } - val roundCards = groupedByRound.values.mapIndexed { idx, cardsInRound -> - val representative = cardsInRound.maxByOrNull { it.startTime ?: LocalDateTime.MIN } ?: cardsInRound.first() - val totalItems = cardsInRound.sumOf { it.TotalItemNumber } - val totalLots = cardsInRound.sumOf { it.totalInventoryLotNumber } + val anyNotMatch = roundStockTakeRecords.any { it.status == "notMatch" } - representative.copy( - id = (idx + 1).toLong(), + return listOf( + AllPickedStockTakeListReponse( + id = 1L, stockTakeSession = "", - TotalItemNumber = totalItems, - totalInventoryLotNumber = totalLots, + 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 ) - } - - return roundCards.sortedByDescending { it.planStartDate ?: LocalDate.MIN } + ) } open fun getInventoryLotDetailsByWarehouseCode(warehouseCode: String): List {