瀏覽代碼

update new reject lot logic

master
CANCERYS\kw093 4 天之前
父節點
當前提交
38560d02ee
共有 8 個檔案被更改,包括 1397 行新增242 行删除
  1. +1082
    -169
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
  2. +13
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickExecutionIssueController.kt
  3. +36
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt
  4. +1
    -0
      src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt
  5. +2
    -1
      src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt
  6. +11
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt
  7. +76
    -14
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt
  8. +176
    -58
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt

+ 1082
- 169
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
文件差異過大導致無法顯示
查看文件


+ 13
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickExecutionIssueController.kt 查看文件

@@ -92,5 +92,18 @@ fun submitExpiryItem(@RequestBody request: SubmitExpiryRequest): MessageResponse
fun batchSubmitExpiryItem(@RequestBody request: BatchSubmitExpiryRequest): MessageResponse {
return pickExecutionIssueService.batchSubmitExpiryItem(request)
}
@GetMapping("/lotIssueDetails")
fun getLotIssueDetails(
@RequestParam lotId: Long,
@RequestParam itemId: Long,
@RequestParam issueType: String
): LotIssueDetailResponse {
return pickExecutionIssueService.getLotIssueDetails(lotId, itemId, issueType)
}

@PostMapping("/submitIssueWithQty")
fun submitIssueWithQty(@RequestBody request: SubmitIssueWithQtyRequest): MessageResponse {
return pickExecutionIssueService.submitIssueWithQty(request)
}
}


+ 36
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt 查看文件

@@ -31,4 +31,40 @@ data class ExpiryItemResponse(
val storeLocation: String?,
val expiryDate: LocalDate?,
val remainingQty: BigDecimal,
)
data class LotIssueDetailRequest(
val lotId: Long,
val itemId: Long,
val issueType: String // "miss" or "bad"
)

// New: Response for lot issue details
data class LotIssueDetailResponse(
val lotId: Long?,
val lotNo: String?,
val itemId: Long,
val itemCode: String?,
val itemDescription: String?,
val storeLocation: String?,
val issues: List<IssueDetailItem>
)

data class IssueDetailItem(
val issueId: Long,
val pickerName: String?,
val missQty: BigDecimal?,
val issueQty: BigDecimal?,
val pickOrderCode: String,
val doOrderCode: String?,
val joOrderCode: String?,
val issueRemark: String?
)

// New: Request to submit with custom quantity
data class SubmitIssueWithQtyRequest(
val lotId: Long,
val itemId: Long,
val issueType: String, // "miss" or "bad"
val submitQty: BigDecimal, // User input quantity
val handler: Long? = null
)

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt 查看文件

@@ -49,6 +49,7 @@ data class ProductProcessInfo(

data class ProductProcessLineInfo(
val id:Long?,
val processId: Long?,
val bomprocessId: Long?,
val operatorId: Long?,
val operatorName: String?,


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt 查看文件

@@ -499,7 +499,7 @@ open class ProductProcessService(
// get bom materials
val bomMaterials = jobOrderBomMaterialRepository.findAllByJobOrderId(jobOrder?.id?:0)
val bomProcessIds = bomProcess.mapNotNull { it.id }
val itemIds = bomMaterials.mapNotNull { it.item?.id }
// calculate each item's available stock
@@ -637,6 +637,7 @@ val sufficientStockQty = bomMaterials
println("line id${line.id}")
ProductProcessLineInfo(
id = line.id?:0,
processId = process.id?:0,
bomprocessId = line.bomProcess?.id?:0,
operatorId = line.operator?.id?:0,
operatorName = line.operator?.name?:"",


+ 11
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt 查看文件

@@ -69,4 +69,15 @@ fun findAllByPickOrderLineIdInAndInventoryLotLineIdInAndDeletedFalse(
@Param("pickOrderLineIds") pickOrderLineIds: List<Long>,
@Param("inventoryLotLineIds") inventoryLotLineIds: List<Long>
): List<StockOutLine>

// 查找所有使用指定 lot 的 stockOutLines(排除已完成和已拒绝的)
@Query("""
SELECT sol FROM StockOutLine sol
WHERE sol.inventoryLotLine.id IN :inventoryLotLineIds
AND sol.deleted = false
AND sol.status NOT IN ('completed', 'rejected', 'COMPLETE', 'REJECTED')
""")
fun findAllByInventoryLotLineIdInAndNotCompletedOrRejected(
@Param("inventoryLotLineIds") inventoryLotLineIds: List<Long>
): List<StockOutLine>
}

+ 76
- 14
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt 查看文件

@@ -339,28 +339,66 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long {
// }
@Transactional
fun checkIsStockOutLineCompleted(pickOrderLineId: Long) {
val unfinishedLine = stockOutLineRepository
val allStockOutLines = stockOutLineRepository
.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
.filter {
it.status != StockOutLineStatus.COMPLETE.status
&& it.status != StockOutLineStatus.REJECTED.status
println("=== checkIsStockOutLineCompleted for pickOrderLineId: $pickOrderLineId ===")
println("Total stock out lines: ${allStockOutLines.size}")
allStockOutLines.forEach { sol ->
println(" StockOutLine ${sol.id}: status=${sol.status}, qty=${sol.qty}")
}
val unfinishedLine = allStockOutLines.filter {
val status = it.status?.trim()?.lowercase()
val isComplete = status == StockOutLineStatus.COMPLETE.status.lowercase()
val isRejected = status == StockOutLineStatus.REJECTED.status.lowercase()
val isPartiallyComplete = status == StockOutLineStatus.PARTIALLY_COMPLETE.status.lowercase()
// Check if partially_completed should be considered as finished
// (if total picked qty meets required qty)
val shouldConsiderPartiallyCompleteAsFinished = if (isPartiallyComplete) {
// ✅ 修复:从 repository 获取 pickOrderLine,避免懒加载问题
val pickOrderLine = pickOrderLineRepository.findById(pickOrderLineId).orElse(null)
if (pickOrderLine != null && pickOrderLine.qty != null) {
val totalPickedQty = allStockOutLines
.filter { sol ->
val solStatus = sol.status?.trim()?.lowercase()
solStatus == "completed" || solStatus == "partially_completed"
}
.fold(BigDecimal.ZERO) { acc, sol ->
val qtyValue = sol.qty ?: 0.0
acc + BigDecimal(qtyValue.toString())
}
totalPickedQty >= pickOrderLine.qty
} else {
false
}
} else {
false
}
!isComplete && !isRejected && !shouldConsiderPartiallyCompleteAsFinished
}
println("Unfinished lines: ${unfinishedLine.size}")
if (unfinishedLine.isNotEmpty()) {
unfinishedLine.forEach { sol ->
println(" - StockOutLine ${sol.id}: status=${sol.status}, qty=${sol.qty}")
}
}
if (unfinishedLine.isEmpty()) {
// set pick order line status to complete
val pol = pickOrderLineRepository.findById(pickOrderLineId).orElseThrow()
val previousStatus = pol.status
pickOrderLineRepository.save(
pol.apply {
this.status = PickOrderLineStatus.COMPLETED
}
)
println("✅ Updated pick order line $pickOrderLineId from $previousStatus to COMPLETED")
} else {
// return unfinished ids to frontend
println(unfinishedLine.map {
Pair(
it.id,
it.status
)
})
println("⏳ Pick order line $pickOrderLineId not completed yet - has ${unfinishedLine.size} unfinished stock out lines")
}
}
private fun completeDoIfAllPickOrdersCompleted(pickOrderId: Long) {
@@ -1124,11 +1162,15 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
))

// Inventory updates - 修复:使用增量数量
if (submitQty > BigDecimal.ZERO && line.inventoryLotLineId != null) {
println(" Updating inventory lot line ${line.inventoryLotLineId} with qty $submitQty")
// ✅ 修复:如果 inventoryLotLineId 为 null,从 stock_out_line 中获取
val actualInventoryLotLineId = line.inventoryLotLineId
?: stockOutLine.inventoryLotLine?.id
if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) {
println(" Updating inventory lot line ${actualInventoryLotLineId} with qty $submitQty")
inventoryLotLineService.updateInventoryLotLineQuantities(
UpdateInventoryLotLineQuantitiesRequest(
inventoryLotLineId = line.inventoryLotLineId,
inventoryLotLineId = actualInventoryLotLineId,
qty = submitQty,
operation = "pick"
)
@@ -1136,6 +1178,10 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
if (submitQty > BigDecimal.ZERO) {
createStockLedgerForPickDelta(line.stockOutLineId, submitQty)
}
} else if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId == null) {
// ✅ 修复:即使没有 inventoryLotLineId,也应该创建 stock_ledger(用于无批次物品或批次切换后的情况)
println(" Warning: No inventoryLotLineId found, but creating stock ledger anyway for stockOutLineId ${line.stockOutLineId}")
createStockLedgerForPickDelta(line.stockOutLineId, submitQty)
}
try {
val stockOutLine = stockOutLines[line.stockOutLineId]
@@ -1189,6 +1235,22 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
// inventoryLotLineRepository.saveAll(lotLines.values.toList())
// inventoryRepository.saveAll(inventories.values.toList())

// ✅ 修复:批处理完成后,检查所有受影响的 pick order lines 是否应该标记为完成
val affectedPickOrderLineIds = request.lines
.mapNotNull { line ->
stockOutLines[line.stockOutLineId]?.pickOrderLine?.id
}
.distinct()
println("=== Checking ${affectedPickOrderLineIds.size} affected pick order lines after batch submit ===")
affectedPickOrderLineIds.forEach { pickOrderLineId ->
try {
checkIsStockOutLineCompleted(pickOrderLineId)
} catch (e: Exception) {
println(" ✗ Error checking pick order line $pickOrderLineId: ${e.message}")
}
}

val msg = if (errors.isEmpty()) {
"Batch submit success (${processedIds.size} lines)."
} else {


+ 176
- 58
src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt 查看文件

@@ -36,6 +36,7 @@ import com.ffii.fpsms.modules.stock.entity.FailInventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus
import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.web.model.StockOutStatus
@@ -533,24 +534,28 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
println("Pick Order Code: ${pickOrder.code}")
println("Pick Order Status: ${pickOrder.status}")
// NEW: Get ALL pick orders for the same items
val itemIds = pickOrder.pickOrderLines.mapNotNull { it.item?.id }.distinct()
println("Item IDs in current pick order: $itemIds")
val allCompetingPickOrders = mutableListOf<PickOrder>()
itemIds.forEach { itemId ->
val competingOrders = pickOrderLineRepository.findAllPickOrdersByItemId(itemId)
.filter { it.id != pickOrderId } // Exclude current pick order
println("Found ${competingOrders.size} competing pick orders for item $itemId")
competingOrders.forEach { order ->
println(" - Competing Order: ${order.code} (ID: ${order.id}, Status: ${order.status})")
}
allCompetingPickOrders.addAll(competingOrders)
// NEW: Get ALL pick orders for the same items
val itemIds = pickOrder.pickOrderLines.mapNotNull { it.item?.id }.distinct()
println("Item IDs in current pick order: $itemIds")

// 收集所有競爭 pick order,並按 id 去重
// ✅ 优化:只考虑活跃的 pick orders(排除已完成和已取消的)
val allCompetingPickOrders = itemIds.flatMap { itemId ->
val competingOrders = pickOrderLineRepository.findAllPickOrdersByItemId(itemId)
.filter {
it.id != pickOrderId && // Exclude current pick order
it.status != PickOrderStatus.COMPLETED
}
// OPTIMIZATION 3: 批量收集所有需要检查的 pick order line IDs
val allPickOrderLineIdsToCheck = (listOf(pickOrder) + allCompetingPickOrders)
if (competingOrders.isNotEmpty()) {
println("Found ${competingOrders.size} active competing pick orders for item $itemId")
}
competingOrders
}.distinctBy { it.id }

// basePickOrders 也做一次去重,避免後面 filter 產生大量重複記錄
val basePickOrders = (listOf(pickOrder) + allCompetingPickOrders).distinctBy { it.id }

val allPickOrderLineIdsToCheck = basePickOrders
.flatMap { it.pickOrderLines }
.mapNotNull { it.id }
.distinct()
@@ -558,7 +563,6 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
println("Checking ${allPickOrderLineIdsToCheck.size} pick order lines for rejected stock out lines")
// OPTIMIZATION 3: 批量查询所有 stock out lines(一次查询代替 N*M 次查询)
// 注意:findAllByPickOrderLineIdInAndDeletedFalse 返回 List<StockOutLine>(实体类)
val allStockOutLines = if (allPickOrderLineIdsToCheck.isNotEmpty()) {
stockOutLIneRepository.findAllByPickOrderLineIdInAndDeletedFalse(allPickOrderLineIdsToCheck)
} else {
@@ -568,32 +572,52 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
println("Found ${allStockOutLines.size} stock out lines in batch query (1 query instead of ${allPickOrderLineIdsToCheck.size} queries)")
// OPTIMIZATION 3: 按 pickOrderLineId 分组,便于后续查找
// 注意:StockOutLine 实体使用 pickOrderLine?.id,不是 pickOrderLineId
val stockOutLinesByPickOrderLineId = allStockOutLines
.groupBy { it.pickOrderLine?.id }
// FIX: Only resuggest pick orders that have rejected stock out lines
val allPickOrdersToResuggest = (listOf(pickOrder) + allCompetingPickOrders)
.filter { pickOrderToCheck ->
// Only resuggest if the pick order has rejected stock out lines
pickOrderToCheck.pickOrderLines.any { pol ->
// OPTIMIZATION 3: 从预加载的 Map 中获取,而不是查询数据库
val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList()
val hasRejectedStockOutLine = stockOutLines.any {
it.status?.equals("rejected", ignoreCase = true) == true
}
if (hasRejectedStockOutLine) {
println("Pick Order ${pickOrderToCheck.code} has rejected stock out lines - will resuggest")
stockOutLines.filter { it.status?.equals("rejected", ignoreCase = true) == true }.forEach { sol ->
// NEW: 預先查出 basePickOrders 所有行對應的 suggestions,用來判斷是否用了 UNAVAILABLE lot
val allSuggestionsForBaseOrders = if (allPickOrderLineIdsToCheck.isNotEmpty()) {
suggestedPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIdsToCheck)
} else {
emptyList()
}
val suggestionsByPickOrderLineId = allSuggestionsForBaseOrders.groupBy { it.pickOrderLine?.id }
val pickOrderIdsWithChanges = mutableSetOf<Long>()
// FIX: 重算條件 = 有 rejected 行,或者 有使用 UNAVAILABLE lot 的 suggestion
val allPickOrdersToResuggest = basePickOrders.filter { po ->
val hasRejected = po.pickOrderLines.any { pol ->
val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList()
val hasRejectedStockOutLine = stockOutLines.any {
it.status?.equals("rejected", ignoreCase = true) == true
}
if (hasRejectedStockOutLine) {
println("Pick Order ${po.code} has rejected stock out lines - will resuggest")
stockOutLines
.filter { it.status?.equals("rejected", ignoreCase = true) == true }
.forEach { sol ->
println(" - Rejected stock out line: ${sol.id} (lot: ${sol.inventoryLotLine?.id}, qty: ${sol.qty})")
}
}
hasRejectedStockOutLine
}
hasRejectedStockOutLine
}
val hasUnavailableLot = po.pickOrderLines.any { pol ->
val suggestions = suggestionsByPickOrderLineId[pol.id] ?: emptyList()
val usedUnavailable = suggestions.any {
it.suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE
}
if (usedUnavailable) {
println("Pick Order ${po.code} has suggestions using UNAVAILABLE lots - will resuggest")
}
usedUnavailable
}
hasRejected || hasUnavailableLot
}.distinctBy { it.id }
println("=== RESUGGEST DEBUG ===")
println("Original pick orders: ${(listOf(pickOrder) + allCompetingPickOrders).size}")
println("Filtered pick orders to resuggest: ${allPickOrdersToResuggest.size}")
@@ -656,35 +680,88 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
// FIX: Separate suggestions to keep (those WITHOUT rejected stock out lines) and delete
val suggestionsToKeep = allSuggestions.filter { suggestion ->
val pickOrderLineId = suggestion.pickOrderLine?.id
val suggestedLotLineId = suggestion.suggestedLotLine?.id
val suggestedLotLine = suggestion.suggestedLotLine
val suggestedLotLineId = suggestedLotLine?.id
val isLotUnavailable = suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE
if (isLotUnavailable) {
println("⏭️ Suggestion ${suggestion.id} uses UNAVAILABLE lot ${suggestedLotLine?.id}, will be deleted")
return@filter false
}
if (pickOrderLineId != null && suggestedLotLineId != null) {
// OPTIMIZATION 3: 从预加载的 Map 中获取
val stockOutLines = stockOutLinesByLineAndLot[pickOrderLineId to suggestedLotLineId] ?: emptyList()
// 保留没有 rejected stock out lines 的 suggestions
!stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true }
} else {
true // 保留有问题的 suggestions 用于调试
true
}
}

// 只删除有 rejected stock out lines 的 suggestions
val suggestionsToDelete = allSuggestions.filter { suggestion ->
val pickOrderLineId = suggestion.pickOrderLine?.id
val suggestedLotLineId = suggestion.suggestedLotLine?.id
val suggestedLotLine = suggestion.suggestedLotLine
val suggestedLotLineId = suggestedLotLine?.id
val isLotUnavailable = suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE
if (pickOrderLineId != null && suggestedLotLineId != null) {
// OPTIMIZATION 3: 从预加载的 Map 中获取
val stockOutLines = stockOutLinesByLineAndLot[pickOrderLineId to suggestedLotLineId] ?: emptyList()
stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true } // 只删除 rejected 的
val hasRejected = stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true }
// ✅ 逻辑:只要 (lot UNAVAILABLE) 或 (有 rejected 的 stock_out_line),就删掉这条 suggestion
if (isLotUnavailable || hasRejected) {
println("🗑 Deleting suggestion ${suggestion.id} for lot $suggestedLotLineId, " +
"unavailable=$isLotUnavailable, hasRejected=$hasRejected")
true
} else {
false
}
} else {
suggestedLotLineId == null
// lotId == null 的老逻辑保留
suggestedLotLineId == null
}
}
println("Suggestions to keep: ${suggestionsToKeep.size}")
println("Suggestions to delete: ${suggestionsToDelete.size}")
// ✅ FIX: 更新使用 UNAVAILABLE lot 的 stockOutLine 状态为 rejected
val unavailableLotIds = suggestionsToDelete
.filter { it.suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE }
.mapNotNull { it.suggestedLotLine?.id }
.distinct()
if (unavailableLotIds.isNotEmpty()) {
println("=== Updating stock out lines for UNAVAILABLE lots ===")
println("UNAVAILABLE lot IDs: $unavailableLotIds")
// ✅ FIX: Find ALL stockOutLines using unavailable lots (not just those with suggestions)
val stockOutLinesToReject = stockOutLIneRepository
.findAllByInventoryLotLineIdInAndNotCompletedOrRejected(unavailableLotIds)
.filter { sol ->
sol.status != StockOutLineStatus.REJECTED.status &&
sol.status != StockOutLineStatus.COMPLETE.status
}
println("Found ${stockOutLinesToReject.size} stock out lines to reject for UNAVAILABLE lots (across all pick orders)")
stockOutLinesToReject.forEach { sol ->
val oldStatus = sol.status
sol.status = StockOutLineStatus.REJECTED.status
sol.modified = LocalDateTime.now()
sol.modifiedBy = "system"
println(" - Updated stock out line ${sol.id} (lot: ${sol.inventoryLotLine?.id}, pickOrderLine: ${sol.pickOrderLine?.id}) status: $oldStatus -> rejected")
}
if (stockOutLinesToReject.isNotEmpty()) {
stockOutLIneRepository.saveAll(stockOutLinesToReject)
stockOutLIneRepository.flush()
println("✅ Updated ${stockOutLinesToReject.size} stock out lines to REJECTED status")
}
}
// FIX: Clear holdQty ONLY for lots that have rejected stock out lines
val rejectedLotIds = suggestionsToDelete.mapNotNull { it.suggestedLotLine?.id }.distinct()
println("Rejected lot IDs (clearing holdQty only): $rejectedLotIds")
@@ -737,17 +814,27 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
println("Final existing holdQtyMap: $existingHoldQtyMap")
// FIX: Create new suggestions for all pick orders to resuggest
allPickOrdersToResuggest.forEach { pickOrderToResuggest ->
// 只获取有 rejected stock out lines 的 pick order lines
// OPTIMIZATION 3: 使用预加载的 Map
// 获取需要重新建议的 pick order lines:
// 1. 有 rejected stock out lines 的行
// 2. 有使用 UNAVAILABLE lot 的 suggestion 的行
val problematicPickOrderLines = pickOrderToResuggest.pickOrderLines.filter { pol ->
// 情况1: 有 rejected stock out line
val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList()
stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true }
val hasRejected = stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true }
// 情况2: 有使用 UNAVAILABLE lot 的 suggestion
val suggestions = suggestionsByPickOrderLineId[pol.id] ?: emptyList()
val hasUnavailableLot = suggestions.any {
it.suggestedLotLine?.status == InventoryLotLineStatus.UNAVAILABLE
}
hasRejected || hasUnavailableLot
}
if (problematicPickOrderLines.isNotEmpty()) {
println("=== Creating new suggestions for pick order: ${pickOrderToResuggest.code} ===")
println(" - Problematic lines: ${problematicPickOrderLines.map { it.id }}")
// 调用 suggestionForPickOrderLines 生成新的 suggestions
val request = SuggestedPickLotForPolRequest(
@@ -760,7 +847,7 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
if (response.suggestedList.isNotEmpty()) {
println("Saving ${response.suggestedList.size} new suggestions")
pickOrderToResuggest.id?.let { pickOrderIdsWithChanges.add(it) }
// 获取现有的 pending/checked 状态的 suggestions(可以更新的)
val existingUpdatableSuggestions = suggestionsToKeep
.filter { it.suggestedLotLine?.id != null }
@@ -879,7 +966,8 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
}
}
// 更新 holdQty
// ✅ 优化:批量更新 holdQty(避免逐个保存)
val lotsToUpdate = mutableListOf<InventoryLotLine>()
response.holdQtyMap.forEach { (lotId, newHoldQty) ->
if (lotId != null && newHoldQty != null && newHoldQty > BigDecimal.ZERO) {
val lot = inventoryLotLineRepository.findById(lotId).orElse(null)
@@ -889,12 +977,16 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
val additionalHoldQty = newHoldQty.minus(existingHoldQty)
val finalHoldQty = currentHoldQty.plus(additionalHoldQty)
it.holdQty = finalHoldQty
inventoryLotLineRepository.save(it)
lotsToUpdate.add(it)
existingHoldQtyMap[lotId] = newHoldQty
println("Updated holdQty for lot $lotId: $currentHoldQty + $additionalHoldQty = $finalHoldQty")
}
}
}
// 批量保存所有更新的 lots
if (lotsToUpdate.isNotEmpty()) {
inventoryLotLineRepository.saveAll(lotsToUpdate)
println("Batch updated holdQty for ${lotsToUpdate.size} lots")
}
} else {
// 如果完全没有生成任何 suggestions
println("No suggestions generated at all for pick order: ${pickOrderToResuggest.code}")
@@ -918,12 +1010,38 @@ open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
}
}
// FIX: Update inventory table for each pick order
allPickOrdersToResuggest.forEach { pickOrderToUpdate ->
println("=== Updating inventory table for pick order: ${pickOrderToUpdate.code} ===")
updateInventoryTableAfterResuggest(pickOrderToUpdate)
}
// ✅ 优化:批量更新 inventory table(收集所有 item IDs,只更新一次)
val allItemIdsToUpdate = allPickOrdersToResuggest
.flatMap { it.pickOrderLines }
.mapNotNull { it.item?.id }
.distinct()
if (allItemIdsToUpdate.isNotEmpty()) {
println("=== Batch updating inventory table for ${allItemIdsToUpdate.size} items ===")
allItemIdsToUpdate.forEach { itemId ->
// Calculate onHoldQty for ALL pick orders that use this item
// ✅ FIX: Use enum directly, not .value
val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE)
.sumOf { it.holdQty ?: BigDecimal.ZERO }
// ✅ FIX: Use enum directly, not .value
val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE)
.sumOf {
val inQty = it.inQty ?: BigDecimal.ZERO
val outQty = it.outQty ?: BigDecimal.ZERO
val remainingQty = inQty.minus(outQty)
remainingQty
}
val inventory = inventoryRepository.findByItemId(itemId).orElse(null)
if (inventory != null) {
inventory.onHoldQty = onHoldQty
inventory.unavailableQty = unavailableQty
inventoryRepository.save(inventory)
}
}
println("✅ Batch updated inventory table for ${allItemIdsToUpdate.size} items")
}
println("=== RESUGGEST DEBUG END ===")
return MessageResponse(


Loading…
取消
儲存