|
|
|
@@ -204,6 +204,7 @@ class StockTakeRecordService( |
|
|
|
currentStockTakeItemNumber = 0, |
|
|
|
totalInventoryLotNumber = totalInventoryLotNumber, |
|
|
|
stockTakeId = latestStockTake?.id ?: 0, |
|
|
|
stockTakeRoundId = latestStockTake?.stockTakeRoundId ?: latestStockTake?.id, |
|
|
|
stockTakerName = stockTakerName, |
|
|
|
TotalItemNumber = totalItemNumber, |
|
|
|
approverName = approverName, |
|
|
|
@@ -250,19 +251,28 @@ class StockTakeRecordService( |
|
|
|
emptySet() |
|
|
|
} |
|
|
|
|
|
|
|
// 4. 如果有 stockTakeId,则预先把这一轮相关的 stockTakeRecord 查出来建 map(跟 section 版类似) |
|
|
|
val stockTakeRecordsMap = if (stockTakeId != null) { |
|
|
|
val allStockTakeRecords = stockTakeRecordRepository.findAll() |
|
|
|
.filter { |
|
|
|
!it.deleted && |
|
|
|
it.stockTake?.id != null && |
|
|
|
it.stockTake!!.id!! in roundStockTakeIds && |
|
|
|
it.warehouse?.id in warehouseIds |
|
|
|
// 4. 如果有 stockTakeId,则预先把这一轮相关的 stockTakeRecord 查出来建 map(避免全表扫描 + N^2) |
|
|
|
val roundStockTakeRecords = if (stockTakeId != null && roundStockTakeIds.isNotEmpty()) { |
|
|
|
stockTakeRecordRepository.findAllByStockTakeIdInAndDeletedIsFalse(roundStockTakeIds) |
|
|
|
.filter { it.warehouse?.id in warehouseIds } |
|
|
|
} else { |
|
|
|
emptyList() |
|
|
|
} |
|
|
|
val stockTakeRecordsMap = roundStockTakeRecords |
|
|
|
.groupBy { Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L) } |
|
|
|
.mapValues { (_, records) -> records.maxByOrNull { it.id ?: 0L }!! } |
|
|
|
|
|
|
|
// 4.1 预先拉取本轮 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 |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
allStockTakeRecords.associateBy { |
|
|
|
Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L) |
|
|
|
} |
|
|
|
} else { |
|
|
|
emptyMap() |
|
|
|
} |
|
|
|
@@ -284,10 +294,7 @@ class StockTakeRecordService( |
|
|
|
|
|
|
|
val inventoryLotLineId = ill.id |
|
|
|
val stockTakeLine = if (stockTakeRecord != null && stockTakeRecord.stockTake?.id != null && inventoryLotLineId != null) { |
|
|
|
stockTakeLineRepository.findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse( |
|
|
|
inventoryLotLineId, |
|
|
|
stockTakeRecord.stockTake!!.id!! |
|
|
|
) |
|
|
|
stockTakeLineMap[Pair(inventoryLotLineId, stockTakeRecord.stockTake!!.id!!)] |
|
|
|
} else { |
|
|
|
null |
|
|
|
} |
|
|
|
@@ -357,36 +364,70 @@ class StockTakeRecordService( |
|
|
|
return RecordsRes(paginatedResult, filteredResults.size) |
|
|
|
} |
|
|
|
open fun AllApproverStockTakeList(): List<AllPickedStockTakeListReponse> { |
|
|
|
// 1. 获取所有不同的 stockTakeSection(从 warehouse 表) |
|
|
|
// 1. 获取 section 与仓库基础数据 |
|
|
|
val allWarehouses = warehouseRepository.findAllByDeletedIsFalse() |
|
|
|
val distinctSections = allWarehouses |
|
|
|
.mapNotNull { it.stockTakeSection } |
|
|
|
.distinct() |
|
|
|
.filter { !it.isBlank() } |
|
|
|
.filter { it.isNotBlank() } |
|
|
|
if (distinctSections.isEmpty()) return emptyList() |
|
|
|
|
|
|
|
if (distinctSections.isEmpty()) { |
|
|
|
return emptyList() |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 批量获取所有相关的数据(优化性能 - 只查询一次) |
|
|
|
// 2.1 按 section 分组 warehouse |
|
|
|
val warehousesBySection = allWarehouses |
|
|
|
.filter { it.stockTakeSection != null && !it.stockTakeSection!!.isBlank() } |
|
|
|
.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 } |
|
|
|
|
|
|
|
// 2.2 批量获取所有 stock_take 记录(只获取一次),按 stockTakeSection 分组 |
|
|
|
// 2. 批量加载核心数据(一次拿齐,避免循环中 repository 查询) |
|
|
|
val allStockTakes = stockTakeRepository.findAll() |
|
|
|
.filter { !it.deleted } |
|
|
|
.groupBy { it.stockTakeSection } |
|
|
|
|
|
|
|
// 2.3 批量获取所有 stocktakeRecord(只获取一次) |
|
|
|
val allStockTakeRecords = stockTakeRecordRepository.findAll() |
|
|
|
.filter { !it.deleted } |
|
|
|
.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] ?: "" |
|
|
|
} |
|
|
|
|
|
|
|
// 2.4 按 warehouseId 分组 stocktakeRecord,便于快速查找 |
|
|
|
val recordsByWarehouseId = allStockTakeRecords |
|
|
|
.filter { it.warehouse?.id != null } |
|
|
|
.groupBy { it.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<AllPickedStockTakeListReponse>() |
|
|
|
var idCounter = 1L |
|
|
|
@@ -427,52 +468,15 @@ class StockTakeRecordService( |
|
|
|
} else { |
|
|
|
null |
|
|
|
} |
|
|
|
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 reStockTakeTrueFalse = if (latestStockTake != null) { |
|
|
|
// 检查该 stock take 下该 section 的记录中是否有 notMatch 状态 |
|
|
|
val hasNotMatch = allStockTakeRecords.any { |
|
|
|
!it.deleted && |
|
|
|
it.stockTake?.id == latestStockTake.id && |
|
|
|
it.stockTakeSection == stockTakeSection && |
|
|
|
it.status == "notMatch" |
|
|
|
} |
|
|
|
hasNotMatch |
|
|
|
} else { |
|
|
|
false |
|
|
|
} |
|
|
|
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 = |
|
|
|
inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) |
|
|
|
val inventoryLotLinesForSection = inventoryLotLinesBySection[stockTakeSection] ?: emptyList() |
|
|
|
val recordedKeysForLatest = latestStockTake?.id?.let { recordKeyByStockTakeId[it] } ?: emptySet() |
|
|
|
|
|
|
|
val relevantInventoryLotLines = inventoryLotLinesForSection.filter { ill -> |
|
|
|
val inventoryLot = ill.inventoryLot |
|
|
|
@@ -485,12 +489,7 @@ class StockTakeRecordService( |
|
|
|
val holdQty = ill.holdQty ?: BigDecimal.ZERO |
|
|
|
val availableQty = inQty.subtract(outQty).subtract(holdQty) |
|
|
|
|
|
|
|
val hasRecordForLatest = allStockTakeRecords.any { record -> |
|
|
|
!record.deleted && |
|
|
|
record.stockTake?.id == latestStockTake.id && |
|
|
|
record.inventoryLotId == inventoryLot.id && |
|
|
|
record.warehouse?.id == warehouse.id |
|
|
|
} |
|
|
|
val hasRecordForLatest = recordedKeysForLatest.contains(Pair(inventoryLot.id, warehouse.id)) |
|
|
|
availableQty.compareTo(BigDecimal.ZERO) > 0 || hasRecordForLatest |
|
|
|
} |
|
|
|
} |
|
|
|
@@ -515,6 +514,7 @@ class StockTakeRecordService( |
|
|
|
currentStockTakeItemNumber = 0, // 临时设为 0,测试性能 |
|
|
|
totalInventoryLotNumber = totalInventoryLotNumber, // 临时设为 0,测试性能 |
|
|
|
stockTakeId = latestStockTake?.id ?: 0, |
|
|
|
stockTakeRoundId = latestStockTake?.stockTakeRoundId ?: latestStockTake?.id, |
|
|
|
stockTakerName = stockTakerName, |
|
|
|
approverName = approverName, |
|
|
|
TotalItemNumber = totalItemNumber, |
|
|
|
@@ -528,7 +528,23 @@ class StockTakeRecordService( |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
return result.sortedBy { it.stockTakeSession } |
|
|
|
// 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 } |
|
|
|
|
|
|
|
representative.copy( |
|
|
|
id = (idx + 1).toLong(), |
|
|
|
stockTakeSession = "", |
|
|
|
TotalItemNumber = totalItems, |
|
|
|
totalInventoryLotNumber = totalLots, |
|
|
|
stockTakeSectionDescription = null |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
return roundCards.sortedByDescending { it.planStartDate ?: LocalDate.MIN } |
|
|
|
} |
|
|
|
|
|
|
|
open fun getInventoryLotDetailsByWarehouseCode(warehouseCode: String): List<InventoryLotDetailResponse> { |
|
|
|
@@ -1429,6 +1445,7 @@ private fun applyVarianceAdjustment( |
|
|
|
this.stockOutLine = stockOutLine |
|
|
|
this.balance = newBalance |
|
|
|
this.type = "TKE" |
|
|
|
this.uomId = inventoryLotLine.stockUom?.uom?.id ?: inventory.uom?.id |
|
|
|
this.date = LocalDate.now() |
|
|
|
} |
|
|
|
stockLedgerRepository.save(stockLedger) |
|
|
|
@@ -1509,6 +1526,7 @@ private fun applyVarianceAdjustment( |
|
|
|
this.stockInLine = stockInLine |
|
|
|
this.balance = newBalance |
|
|
|
this.type = "TKE" |
|
|
|
this.uomId = inventoryLotLine.stockUom?.uom?.id ?: inventory.uom?.id |
|
|
|
this.date = LocalDate.now() |
|
|
|
} |
|
|
|
stockLedgerRepository.save(stockLedger) |
|
|
|
|