Parcourir la source

update

master
CANCERYS\kw093 il y a 10 heures
Parent
révision
8348d44b07
1 fichiers modifiés avec 60 ajouts et 164 suppressions
  1. +60
    -164
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt

+ 60
- 164
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt Voir le fichier

@@ -364,187 +364,83 @@ class StockTakeRecordService(
return RecordsRes(paginatedResult, filteredResults.size)
}
open fun AllApproverStockTakeList(): List<AllPickedStockTakeListReponse> {
// 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<AllPickedStockTakeListReponse>()
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<InventoryLotDetailResponse> {


Chargement…
Annuler
Enregistrer