Преглед изворни кода

stock take batch save fix

production
CANCERYS\kw093 пре 3 недеља
родитељ
комит
3b7199cd90
4 измењених фајлова са 159 додато и 85 уклоњено
  1. +1
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  2. +1
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/TruckLaneSearchSpec.kt
  3. +154
    -82
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt
  4. +3
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt Прегледај датотеку

@@ -315,7 +315,7 @@ open class DeliveryOrderService(
}

/**
* DO 輕量搜 v2:車線關鍵字正規化(`車線-X`/`x`/`車線-` 前綴含未指派)、
* DO 輕量搜 v2:車線關鍵字正規化(`車線-X`/`x`/`車線-` 前綴含未指派)、
* 以允許供應商 + 分批掃描取代單次 100000 筆載入;無車線條件時等同 [searchDoLiteByPage] 無車線分支。
*/
open fun searchDoLiteByPageV2(


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/TruckLaneSearchSpec.kt Прегледај датотеку

@@ -3,7 +3,7 @@ package com.ffii.fpsms.modules.deliveryOrder.service
import java.util.Locale

/**
* 車線搜正規化(search-do-lite-v2)。
* 車線搜正規化(search-do-lite-v2)。
* 實際 [com.ffii.fpsms.modules.pickOrder.entity.Truck] 編碼主要為 `車線-…` 或 `P06B_…`;未指派預設列為 `車線-X`(shopId 可為 null)。
*/
object TruckLaneSearchSpec {


+ 154
- 82
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt Прегледај датотеку

@@ -631,6 +631,21 @@ open class StockTakeRecordService(
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,
@@ -883,11 +898,7 @@ open class StockTakeRecordService(
val warehouseIds = allWarehouses.mapNotNull { it.id }
if (warehouseIds.isEmpty()) return emptyList()

val allStockTakes = stockTakeRepository.findAll().filter { !it.deleted }
if (allStockTakes.isEmpty()) return emptyList()

val latestBaseStockTake = allStockTakes
.maxByOrNull { it.actualStart ?: it.planStart ?: LocalDateTime.MIN }
val latestBaseStockTake = resolveLatestRoundRepresentativeStockTake()
?: return emptyList()

// 优先用 stocktakerecord.stockTakeRoundId 直接取该轮次记录(避免再算 stockTakeId 列表 + in 查询)
@@ -966,11 +977,21 @@ open class StockTakeRecordService(
* 用于前端先拿 stockTakeId,再调用 pending/approved 明细接口。
*/
open fun getLatestApproverStockTakeHeader(): AllPickedStockTakeListReponse? {
val latestBaseStockTake = stockTakeRepository.findAll()
.filter { !it.deleted }
.maxByOrNull { it.actualStart ?: it.planStart ?: LocalDateTime.MIN }
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 ""
} ?: ""
@@ -983,14 +1004,14 @@ open class StockTakeRecordService(
currentStockTakeItemNumber = 0,
totalInventoryLotNumber = 0,
stockTakeId = latestBaseStockTake.id ?: 0,
stockTakeRoundId = latestBaseStockTake.stockTakeRoundId ?: latestBaseStockTake.id,
stockTakeRoundId = roundKey,
stockTakerName = null,
approverName = null,
TotalItemNumber = 0,
startTime = latestBaseStockTake.actualStart,
endTime = latestBaseStockTake.actualEnd,
ReStockTakeTrueFalse = false,
planStartDate = latestBaseStockTake.planStart?.toLocalDate(),
planStartDate = planStartDate,
stockTakeSectionDescription = null,
warehouseArea = null,
storeId = null
@@ -1674,6 +1695,64 @@ open class StockTakeRecordService(
}
}

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
@@ -1691,53 +1770,17 @@ open class StockTakeRecordService(
val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId)
?: throw IllegalArgumentException("Stock take not found: $stockTakeId")

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
}

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

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

stockTakeRecord.apply {
this.approverId = request.approverId
this.approverName = user?.name
this.approverStockTakeQty = finalQty
this.approverBadQty = finalBadQty
this.varianceQty = varianceQty
this.status = "completed"
this.approverTime = java.time.LocalDateTime.now()
this.lastSelect = request.lastSelect ?: run {
// 兼容:旧客户端未传 lastSelect 时,尝试根据请求类型推断
if (request.approverQty != null && request.approverBadQty != null) {
3
} else if (
this.pickerSecondStockTakeQty != null &&
this.pickerSecondStockTakeQty!!.compareTo(request.qty) == 0
) {
2
} else {
1
}
}
// stockTakeEndTime 目前只在 saveStockTakeRecord「第二次盤點」時寫入;只做第一次盤點時會一直是 null。
// 審核通過時若仍為空,補上時間,讓列表「審核/完成時間」有值(不覆寫已有第二次盤點結束時間)。
if (this.stockTakeEndTime == null) {
this.stockTakeEndTime = java.time.LocalDateTime.now()
}
}

val savedRecord = stockTakeRecordRepository.save(stockTakeRecord)

// variance != 0:過帳並更新/新建 StockTakeLine;= 0:僅將開輪預建 line 標記完成(不查庫存)
@@ -2034,8 +2077,24 @@ open fun batchSaveApproverStockTakeRecordsByIds(
): BatchSaveApproverStockTakeRecordResponse {
val totalStartNs = System.nanoTime()
fun elapsedMs(startNs: Long): Long = (System.nanoTime() - startNs) / 1_000_000
logger.info("batchSaveApproverStockTakeRecordsByIds start: stockTakeId={}, ids={}", request.stockTakeId, request.recordIds.size)
if (request.recordIds.isEmpty()) {

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"))
}

@@ -2044,8 +2103,8 @@ open fun batchSaveApproverStockTakeRecordsByIds(
val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId)
?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}")

val idSet = request.recordIds.toSet()
val stockTakeRecords = stockTakeRecordRepository.findAllById(request.recordIds)
val idSet = idsToLoad.toSet()
val stockTakeRecords = stockTakeRecordRepository.findAllById(idsToLoad)
.filter {
!it.deleted &&
(it.id in idSet) &&
@@ -2081,35 +2140,48 @@ open fun batchSaveApproverStockTakeRecordsByIds(

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 payload = recordPayloadById[record.id]
if (useClientPayload && payload == null) {
errorCount++
errors.add("No client payload for record ${record.id}")
return@forEach
}

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()
this.lastSelect = if (
record.pickerSecondStockTakeQty != null &&
record.pickerSecondStockTakeQty!! > BigDecimal.ZERO
) 2 else 1
if (this.stockTakeEndTime == null) {
this.stockTakeEndTime = java.time.LocalDateTime.now()
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)


+ 3
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt Прегледај датотеку

@@ -162,7 +162,9 @@ data class BatchSaveApproverStockTakeAllRequest(
data class BatchSaveApproverStockTakeByIdsRequest(
val stockTakeId: Long,
val approverId: Long,
val recordIds: List<Long>,
val recordIds: List<Long> = emptyList(),
/** 與單筆 saveApproverStockTakeRecord 相同;有值時依每筆 qty / lastSelect 保存。 */
val records: List<SaveApproverStockTakeRecordRequest> = emptyList(),
)

data class BatchSaveApproverStockTakeRecordResponse(


Loading…
Откажи
Сачувај