diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt index e3ba26d..10ad4c7 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt @@ -21,6 +21,16 @@ import java.math.BigDecimal import java.time.LocalDate import java.time.LocalDateTime import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository +import com.ffii.core.support.JdbcDao +import com.ffii.fpsms.modules.stock.entity.StockOutRepository +import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository +import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService +import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository +import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecordRepository +import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus +import com.ffii.fpsms.modules.stock.web.model.StockOutStatus +import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus +import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus @Service open class PickExecutionIssueService( private val pickExecutionIssueRepository: PickExecutionIssueRepository, @@ -28,7 +38,14 @@ open class PickExecutionIssueService( private val inventoryLotLineRepository: InventoryLotLineRepository, private val inventoryRepository: InventoryRepository, private val suggestedPickLotService: SuggestedPickLotService, - private val pickOrderRepository: PickOrderRepository + private val pickOrderRepository: PickOrderRepository, + private val jdbcDao: JdbcDao, + private val stockOutRepository: StockOutRepository, + private val pickOrderLineRepository: PickOrderLineRepository, + private val doPickOrderService: DoPickOrderService, + private val joPickOrderRepository: JoPickOrderRepository, + private val joPickOrderRecordRepository: JoPickOrderRecordRepository + ) { @Transactional(rollbackFor = [Exception::class]) @@ -137,7 +154,19 @@ open class PickExecutionIssueService( } } - + val pickOrderForCompletion = pickOrderRepository.findById(request.pickOrderId).orElse(null) + val consoCode = pickOrderForCompletion?.consoCode + + if (consoCode != null) { + println("🔍 Checking if pick order $consoCode should be completed after lot rejection...") + try { + checkAndCompletePickOrder(consoCode) + } catch (e: Exception) { + println("⚠️ Error checking pick order completion: ${e.message}") + } + } + + return MessageResponse( id = savedIssue.id, name = "Pick execution issue recorded successfully", @@ -185,6 +214,137 @@ open class PickExecutionIssueService( // 格式化为 SKO-YYMM-001 return "SKO-${yearMonth}-${nextSequence.toString().padStart(3, '0')}" } + + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) +private fun checkAndCompletePickOrder(consoCode: String) { + println("=== DEBUG: checkAndCompletePickOrder ===") + println("consoCode: $consoCode") + + // 1. 查找 StockOut + val stockOut = stockOutRepository.findByConsoPickOrderCode(consoCode).orElse(null) + if (stockOut == null) { + println("❌ No stock_out found for consoCode: $consoCode") + return + } + + // 2. 查找所有相关的 stock out lines + val stockOutLinesSql = """ + SELECT sol.* + FROM stock_out_line sol + JOIN pick_order_line pol ON pol.id = sol.pickOrderLineId + JOIN pick_order po ON po.id = pol.poId + WHERE po.consoCode = :consoCode + AND sol.deleted = false + """.trimIndent() + + val stockOutLinesResult = jdbcDao.queryForList(stockOutLinesSql, mapOf("consoCode" to consoCode)) + val stockOutLineIds = stockOutLinesResult.mapNotNull { row -> + when (val id = row["id"]) { + is Number -> id.toLong() + is String -> id.toLongOrNull() + else -> null + } + } + + val stockOutLines = if (stockOutLineIds.isNotEmpty()) { + stockOutLineRepository.findAllById(stockOutLineIds) + } else { + emptyList() + } + + println("Total stock out lines for consoCode $consoCode: ${stockOutLines.size}") + + // 3. 检查是否有未完成的行 + val unfinishedLines = stockOutLines.filter { + it.status != StockOutLineStatus.COMPLETE.status + && it.status != StockOutLineStatus.REJECTED.status + } + + println("📊 Stock out lines: ${stockOutLines.size}, Unfinished: ${unfinishedLines.size}") + + // 4. 如果所有行都完成或被拒绝,则完成 pick order + if (unfinishedLines.isEmpty()) { + println("✅ All stock out lines completed or rejected, completing pick order...") + + // 4.1 更新 StockOut 状态 + stockOut.status = StockOutStatus.COMPLETE.status + stockOutRepository.saveAndFlush(stockOut) + + // 4.2 更新所有 pick order lines 状态 + val completedPickOrderLineIds = stockOutLines.mapNotNull { it.pickOrderLine?.id } + println("Completed pick order line IDs: $completedPickOrderLineIds") + + if (completedPickOrderLineIds.isNotEmpty()) { + val pickOrderLines = pickOrderLineRepository.findAllById(completedPickOrderLineIds) + pickOrderLines.forEach { line -> + line.status = PickOrderLineStatus.COMPLETED + println("Updated pick order line ${line.id} to COMPLETED") + } + pickOrderLineRepository.saveAll(pickOrderLines) + println("✅ Updated ${pickOrderLines.size} pick order lines to COMPLETED status") + + // 4.3 获取所有受影响的 pick orders + val pickOrderIds = pickOrderLines.mapNotNull { it.pickOrder?.id }.distinct() + println("Affected pick order IDs: $pickOrderIds") + + // 4.4 检查每个 pick order 是否完全完成 + pickOrderIds.forEach { pickOrderId -> + val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) + if (pickOrder != null) { + // 检查这个 pick order 的所有行是否都完成了 + val allLines = pickOrder.pickOrderLines + val completedLines = allLines.filter { it.status == PickOrderLineStatus.COMPLETED } + + println("Pick order ${pickOrder.code}: ${completedLines.size}/${allLines.size} lines completed") + + if (completedLines.size == allLines.size && allLines.isNotEmpty()) { + // 所有行都完成了,更新 pick order 状态 + pickOrder.status = PickOrderStatus.COMPLETED + pickOrder.completeDate = LocalDateTime.now() + pickOrderRepository.save(pickOrder) + println("✅ Updated pick order ${pickOrder.code} to COMPLETED status") + + // 4.5 处理 DO pick order 相关记录 + try { + val removedCount = doPickOrderService.removeDoPickOrdersForPickOrder(pickOrderId) + println("✅ Removed $removedCount do_pick_order records for completed pick order ${pickOrderId}") + + doPickOrderService.completeDoPickOrderRecordsForPickOrder(pickOrderId) + println("✅ Updated do_pick_order_record status to COMPLETED for pick order ${pickOrderId}") + } catch (e: Exception) { + println("⚠️ Error updating DO pick order records: ${e.message}") + } + + // 4.6 处理 JO pick order 相关记录 + if (pickOrder.jobOrder != null) { + try { + val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId) + joPickOrders.forEach { + it.ticketCompleteTime = LocalDateTime.now() + } + joPickOrderRepository.saveAll(joPickOrders) + + val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId) + joPickOrderRecords.forEach { + it.ticketCompleteTime = LocalDateTime.now() + } + joPickOrderRecordRepository.saveAll(joPickOrderRecords) + + println("✅ Set jo_pick_order ticketCompleteTime for pick order ${pickOrderId}") + } catch (e: Exception) { + println("⚠️ Error updating JO pick order records: ${e.message}") + } + } + } + } + } + } + + println("🎉 Pick order completed successfully!") + } else { + println("⏳ Still have ${unfinishedLines.size} unfinished lines, pick order not completed yet") + } +} // FPSMS-backend/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt // ✅ 修复:处理有部分拣货但有 miss item 的情况 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt index 16ba5d3..1c5bc45 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt @@ -172,7 +172,34 @@ open class SuggestedPickLotService( qty = assignQtyInSalesUnits // ✅ 保存销售单位 } } - + // ✅ 修复:计算现有 suggestions 中 pending/checked 状态满足的数量 + var existingSatisfiedQty = BigDecimal.ZERO + + // 查询现有的 suggestions 用于这个 pick order line + val existingSuggestions = suggestedPickLotRepository.findAllByPickOrderLineId(line.id!!) +existingSuggestions.forEach { existingSugg -> + if (existingSugg.suggestedLotLine?.id != null) { + val stockOutLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( + line.id!!, existingSugg.suggestedLotLine?.id!! + ) + val canCountAsSatisfied = stockOutLines.isEmpty() || stockOutLines.any { + it.status == "pending" || it.status == "checked" || it.status == "partially_completed" + } + + if (canCountAsSatisfied) { + existingSatisfiedQty = existingSatisfiedQty.plus(existingSugg.qty ?: BigDecimal.ZERO) + } + } + } + + // ✅ 调整 remainingQtyToAllocate,减去已经通过现有 suggestions 满足的数量 + remainingQtyToAllocate = remainingQtyToAllocate.minus(existingSatisfiedQty) + println("Existing satisfied qty: $existingSatisfiedQty") + println("Adjusted remaining qty: $remainingQtyToAllocate") + + // if still have remainingQty + println("remaining2 $remainingQtyToAllocate (sales units)") + // if still have remainingQty println("remaining2 $remainingQtyToAllocate (sales units)") if (remainingQtyToAllocate > zero) { @@ -409,7 +436,7 @@ val suggestionsToDelete = allSuggestions.filter { suggestion -> ) stockOutLines.any { it.status == "rejected" } // ✅ 只删除 rejected 的 } else { - false + suggestedLotLineId == null } } @@ -430,14 +457,7 @@ val suggestionsToDelete = allSuggestions.filter { suggestion -> println("Cleared holdQty for rejected lot ${lot.id}: $originalHoldQty -> 0") } } - - // ✅ NEW: Reduce holdQty for lots that are no longer suggested - - // ✅ 不删除任何 suggestions - 保留 rejected suggestions 用于显示 -// if (suggestionsToDelete.isNotEmpty()) { -// suggestedPickLotRepository.deleteAll(suggestionsToDelete) -// println("Deleted ${suggestionsToDelete.size} suggestions") -// } + println("Keeping all suggestions (including rejected ones for display)") // ✅ NEW: Build holdQtyMap with existing holdQty from other pick orders @@ -496,13 +516,95 @@ println("Keeping all suggestions (including rejected ones for display)") if (response.suggestedList.isNotEmpty()) { println("Saving ${response.suggestedList.size} new suggestions") - // ✅ 保存所有新生成的 suggestions - val savedSuggestions = suggestedPickLotRepository.saveAllAndFlush(response.suggestedList) - println("Saved ${savedSuggestions.size} new suggestions") + // ✅ 获取现有的 pending/checked 状态的 suggestions(可以更新的) + val existingUpdatableSuggestions = suggestionsToKeep + .filter { it.suggestedLotLine?.id != null } + .groupBy { it.pickOrderLine?.id to it.suggestedLotLine?.id } + .mapValues { it.value.first() } // 每个 (lineId, lotId) 只取第一个 + + // ✅ 处理新的 suggestions:更新现有的或创建新的 + val suggestionsToSave = response.suggestedList.mapNotNull { newSugg -> + val key = newSugg.pickOrderLine?.id to newSugg.suggestedLotLine?.id + val lineId = newSugg.pickOrderLine?.id + val lotId = newSugg.suggestedLotLine?.id + + if (lineId != null && lotId != null) { + // ✅ 检查这个 lot 是否已有 suggestion + val existingSugg = existingUpdatableSuggestions[key] + + if (existingSugg != null) { + // ✅ 检查现有 suggestion 的 stock_out_line 状态 + val stockOutLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( + lineId, lotId + ) + + val canUpdate = stockOutLines.isEmpty() || stockOutLines.all { + it.status == "pending" || it.status == "checked" || it.status == "partially_completed" + } + + if (canUpdate) { + // ✅ Case 1: 更新现有的 suggestion + existingSugg.qty = newSugg.qty + existingSugg.modified = LocalDateTime.now() + existingSugg.modifiedBy = "system" + println("⚠️ Updated existing suggestion ${existingSugg.id} for lot $lotId: new qty=${newSugg.qty}") + existingSugg + } else { + // ✅ Case 2: 已完成/拒绝,跳过(不更新,也不创建新的) + println("⏭️ Skipping lot $lotId - already ${stockOutLines.first().status}") + null + } + } else { + // ✅ 没有现有的 suggestion,创建新的 + newSugg + } + } else if (lotId == null) { + // ✅ lotId=null:检查是否已有 resuggest_issue + val existingResuggestIssues = pickExecutionIssueRepository + .findByPickOrderLineIdAndDeletedFalse(lineId ?: 0L) + .filter { it.issueCategory.name == "resuggest_issue" } + if (existingResuggestIssues.isEmpty()) { + newSugg // 创建新的 null suggestion(后续会创建 issue) + } else { + println("⏭️ Resuggest issue already exists for line $lineId, skipping null suggestion") + null // 跳过,避免创建重复的 resuggest_issue + } + } else { + newSugg + } + }.filterNotNull() + + val updatedSuggestions = suggestionsToSave.filter { it.id != null } // 有 id 的是更新的 + val newSuggestions = suggestionsToSave.filter { it.id == null } // 没有 id 的是新创建的 + + val allSavedSuggestions = mutableListOf() + + // 保存更新的 suggestions + if (updatedSuggestions.isNotEmpty()) { + val savedUpdated = suggestedPickLotRepository.saveAllAndFlush(updatedSuggestions) + allSavedSuggestions.addAll(savedUpdated) + println("✅ Updated ${savedUpdated.size} existing suggestions") + } + + // 保存新的 suggestions + if (newSuggestions.isNotEmpty()) { + val savedNew = suggestedPickLotRepository.saveAllAndFlush(newSuggestions) + allSavedSuggestions.addAll(savedNew) + println("✅ Created ${savedNew.size} new suggestions") + } + + val savedSuggestions = allSavedSuggestions + println("Saved/Updated ${savedSuggestions.size} suggestions") + // ✅ 为每个新 suggestion 创建 stock out line 或 issue savedSuggestions.forEach { suggestion -> if (suggestion.suggestedLotLine != null) { + val isNewSuggestion = response.suggestedList.any { + it.pickOrderLine?.id == suggestion.pickOrderLine?.id && + it.suggestedLotLine?.id == suggestion.suggestedLotLine?.id && + it.id == null // 新的 suggestion 还没有 id + } val stockOutLine = createStockOutLineForSuggestion(suggestion, pickOrderToResuggest) if (stockOutLine != null) { println("✅ Created stock out line ${stockOutLine.id} for suggestion ${suggestion.id}") @@ -518,11 +620,12 @@ println("Keeping all suggestions (including rejected ones for display)") .findAllByPickOrderLineIdAndDeletedFalse(pickOrderLine.id!!) .filter { it.status == "rejected" } - rejectedStockOutLines.forEach { rejectedLine -> + // ✅ 修复:只创建一个 resuggest_issue(如果有 rejected lines) + if (rejectedStockOutLines.isNotEmpty()) { createResuggestFailureIssue( pickOrder = pickOrderToResuggest, pickOrderLine = pickOrderLine, - rejectedStockOutLine = rejectedLine + rejectedStockOutLine = rejectedStockOutLines.first() ) } }