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

fix scan lot and scan not match lt and new issue handle

master
CANCERYS\kw093 пре 1 недеља
родитељ
комит
dac9b90e74
7 измењених фајлова са 865 додато и 575 уклоњено
  1. +396
    -197
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
  2. +28
    -14
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  3. +2
    -1
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt
  4. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLine.kt
  5. +22
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt
  6. +409
    -363
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt
  7. +5
    -0
      src/main/resources/db/changelog/changes/20260122_01_Enson/01_alter_table.sql

+ 396
- 197
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt Прегледај датотеку

@@ -11,7 +11,9 @@ import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryRepository import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.service.StockOutLineService


import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@@ -30,7 +32,7 @@ import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRecordRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum
import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus
import com.ffii.fpsms.modules.stock.web.model.StockOutStatus 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 import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus
import com.ffii.fpsms.modules.pickOrder.web.models.* import com.ffii.fpsms.modules.pickOrder.web.models.*
import com.ffii.fpsms.modules.common.SecurityUtils import com.ffii.fpsms.modules.common.SecurityUtils
@@ -52,6 +54,7 @@ open class PickExecutionIssueService(
private val pickOrderRepository: PickOrderRepository, private val pickOrderRepository: PickOrderRepository,
private val jdbcDao: JdbcDao, private val jdbcDao: JdbcDao,
private val stockOutRepository: StockOutRepository, private val stockOutRepository: StockOutRepository,
private val stockOutLineService: StockOutLineService,
private val pickOrderLineRepository: PickOrderLineRepository, private val pickOrderLineRepository: PickOrderLineRepository,
private val doPickOrderService: DoPickOrderService, private val doPickOrderService: DoPickOrderService,
private val joPickOrderRepository: JoPickOrderRepository, private val joPickOrderRepository: JoPickOrderRepository,
@@ -62,217 +65,227 @@ open class PickExecutionIssueService(
) { ) {


@Transactional(rollbackFor = [Exception::class]) @Transactional(rollbackFor = [Exception::class])
open fun recordPickExecutionIssue(request: PickExecutionIssueRequest): MessageResponse {
try {
// 1. 检查是否已经存在相同的 pick execution issue 记录
val existingIssues = pickExecutionIssueRepository.findByPickOrderLineIdAndLotIdAndDeletedFalse(
request.pickOrderLineId,
request.lotId ?: 0L
)
if (existingIssues.isNotEmpty()) {
return MessageResponse(
id = null,
name = "Pick execution issue already exists",
code = "DUPLICATE",
type = "pick_execution_issue",
message = "A pick execution issue for this lot has already been recorded",
errorPosition = null
)
}
val pickOrder = pickOrderRepository.findById(request.pickOrderId).orElse(null)
// 2. 获取 inventory_lot_line 并计算账面数量 (bookQty)
val inventoryLotLine = request.lotId?.let {
inventoryLotLineRepository.findById(it).orElse(null)
}
// 计算账面数量(创建 issue 时的快照)
val bookQty = if (inventoryLotLine != null) {
val inQty = inventoryLotLine.inQty ?: BigDecimal.ZERO
val outQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
inQty.subtract(outQty) // bookQty = inQty - outQty
} else {
BigDecimal.ZERO
}
// 3. 获取数量值
val requiredQty = request.requiredQty ?: BigDecimal.ZERO
val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO
val missQty = request.missQty ?: BigDecimal.ZERO
val badItemQty = request.badItemQty ?: BigDecimal.ZERO
// 4. 验证逻辑:如果 actualPickQty == requiredQty,missQty 必须为 0
if (actualPickQty == requiredQty && missQty > BigDecimal.ZERO) {
return MessageResponse(
id = null,
name = "Invalid issue",
code = "INVALID",
type = "pick_execution_issue",
message = "If actual pick qty equals required qty, miss qty must be 0",
errorPosition = null
open fun recordPickExecutionIssue(request: PickExecutionIssueRequest): MessageResponse {
try {
// 1. 检查是否已经存在相同的 pick execution issue 记录
val existingIssues = pickExecutionIssueRepository.findByPickOrderLineIdAndLotIdAndDeletedFalse(
request.pickOrderLineId,
request.lotId ?: 0L
) )
}
// 5. 计算 issueQty(实际的问题数量)
val issueQty = when {
// 情况1: 已拣完但有坏品
actualPickQty == requiredQty && badItemQty > BigDecimal.ZERO -> {
badItemQty // issueQty = badItemQty
}
// 情况2: 未拣完(有缺失或坏品)
actualPickQty < requiredQty -> {
// issueQty = bookQty - actualPickQty
// 这是实际缺失的数量(账面应该有的数量 - 实际拣到的数量)
val calculatedIssueQty = bookQty.subtract(actualPickQty)
// 验证:如果用户报告了 missQty,它应该 <= issueQty(但允许用户报告的值)
if (missQty > BigDecimal.ZERO && missQty > calculatedIssueQty) {
println("⚠️ Warning: User reported missQty (${missQty}) exceeds calculated issueQty (${calculatedIssueQty})")
println(" BookQty: ${bookQty}, ActualPickQty: ${actualPickQty}")
}
calculatedIssueQty
if (existingIssues.isNotEmpty()) {
return MessageResponse(
id = null,
name = "Pick execution issue already exists",
code = "DUPLICATE",
type = "pick_execution_issue",
message = "A pick execution issue for this lot has already been recorded",
errorPosition = null
)
} }
else -> BigDecimal.ZERO
}
println("=== PICK EXECUTION ISSUE PROCESSING ===")
println("Required Qty: ${requiredQty}")
println("Actual Pick Qty: ${actualPickQty}")
println("Miss Qty (Reported): ${missQty}")
println("Bad Item Qty: ${badItemQty}")
println("Book Qty (inQty - outQty): ${bookQty}")
println("Issue Qty (Calculated): ${issueQty}")
println("Lot ID: ${request.lotId}")
println("Item ID: ${request.itemId}")
println("================================================")
// 6. 创建 pick execution issue 记录
val pickExecutionIssue = PickExecutionIssue(
id = null,
pickOrderId = request.pickOrderId,
pickOrderCode = request.pickOrderCode,
pickOrderCreateDate = request.pickOrderCreateDate,
pickExecutionDate = request.pickExecutionDate ?: LocalDate.now(),
pickOrderLineId = request.pickOrderLineId,
issueNo = generateIssueNo(),
joPickOrderId = pickOrder?.jobOrder?.id,
doPickOrderId = if (pickOrder?.type?.value == "do") pickOrder.id else null,
issueCategory = IssueCategory.valueOf(
request.issueCategory ?: "lot_issue"
),
itemId = request.itemId,
itemCode = request.itemCode,
itemDescription = request.itemDescription,
lotId = request.lotId,
lotNo = request.lotNo,
storeLocation = request.storeLocation,
requiredQty = request.requiredQty,
actualPickQty = request.actualPickQty,
missQty = request.missQty,
badItemQty = request.badItemQty,
bookQty = bookQty, // 添加账面数量
issueQty = issueQty, // 添加计算的问题数量
issueRemark = request.issueRemark,
pickerName = request.pickerName,
handleStatus = HandleStatus.pending,
handleDate = null,
handledBy = request.handledBy,
created = LocalDateTime.now(),
createdBy = "system",
version = 0,
modified = LocalDateTime.now(),
modifiedBy = "system",
deleted = false
)

val savedIssue = pickExecutionIssueRepository.save(pickExecutionIssue)

// 7. 获取相关数据用于后续处理
val actualPickQtyForProcessing = request.actualPickQty ?: BigDecimal.ZERO
val missQtyForProcessing = request.missQty ?: BigDecimal.ZERO
val badItemQtyForProcessing = request.badItemQty ?: BigDecimal.ZERO
val lotId = request.lotId
val itemId = request.itemId

println("=== PICK EXECUTION ISSUE PROCESSING (NEW LOGIC) ===")
println("Actual Pick Qty: ${actualPickQtyForProcessing}")
println("Miss Qty: ${missQtyForProcessing}")
println("Bad Item Qty: ${badItemQtyForProcessing}")
println("Lot ID: ${lotId}")
println("Item ID: ${itemId}")
println("================================================")

// 8. 新的统一处理逻辑
when {
// 情况1: 只有 miss item (actualPickQty = 0, missQty > 0, badItemQty = 0)
actualPickQtyForProcessing == BigDecimal.ZERO && missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing == BigDecimal.ZERO -> {
handleMissItemOnly(request, missQtyForProcessing)
}
val pickOrder = pickOrderRepository.findById(request.pickOrderId).orElse(null)
// 情况2: 只有 bad item (badItemQty > 0, missQty = 0)
badItemQtyForProcessing > BigDecimal.ZERO && missQtyForProcessing == BigDecimal.ZERO -> {
handleBadItemOnly(request, badItemQtyForProcessing)
// 2. 获取 inventory_lot_line 并计算账面数量 (bookQty)
val inventoryLotLine = request.lotId?.let {
inventoryLotLineRepository.findById(it).orElse(null)
} }
// 情况3: 既有 miss item 又有 bad item
missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing > BigDecimal.ZERO -> {
handleBothMissAndBadItem(request, missQtyForProcessing, badItemQtyForProcessing)
// 计算账面数量(创建 issue 时的快照)
val bookQty = if (inventoryLotLine != null) {
val inQty = inventoryLotLine.inQty ?: BigDecimal.ZERO
val outQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
inQty.subtract(outQty) // bookQty = inQty - outQty
} else {
BigDecimal.ZERO
} }
// 修复:情况4: 有 miss item 的情况(无论 actualPickQty 是多少)
missQtyForProcessing > BigDecimal.ZERO -> {
handleMissItemWithPartialPick(request, actualPickQtyForProcessing, missQtyForProcessing)
}
// 3. 获取数量值
val requiredQty = request.requiredQty ?: BigDecimal.ZERO
val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO
val missQty = request.missQty ?: BigDecimal.ZERO
val badItemQty = request.badItemQty ?: BigDecimal.ZERO
// 情况5: 正常拣货 (actualPickQty > 0, 没有 miss 或 bad item)
actualPickQtyForProcessing > BigDecimal.ZERO -> {
handleNormalPick(request, actualPickQtyForProcessing)
// 4. 计算 issueQty(实际的问题数量)
val issueQty = when {
// 情况1: 已拣完但有坏品
actualPickQty == requiredQty && badItemQty > BigDecimal.ZERO -> {
badItemQty // issueQty = badItemQty
}
// 情况2: 未拣完(有缺失或坏品)
actualPickQty < requiredQty -> {
// issueQty = bookQty - actualPickQty
val calculatedIssueQty = bookQty.subtract(actualPickQty)
if (missQty > BigDecimal.ZERO && missQty > calculatedIssueQty) {
println("⚠️ Warning: User reported missQty (${missQty}) exceeds calculated issueQty (${calculatedIssueQty})")
println(" BookQty: ${bookQty}, ActualPickQty: ${actualPickQty}")
}
calculatedIssueQty
}
else -> BigDecimal.ZERO
} }
else -> {
println("Unknown case: actualPickQty=${actualPickQtyForProcessing}, missQty=${missQtyForProcessing}, badItemQty=${badItemQtyForProcessing}")
println("=== PICK EXECUTION ISSUE PROCESSING ===")
println("Required Qty: ${requiredQty}")
println("Actual Pick Qty: ${actualPickQty}")
println("Miss Qty (Reported): ${missQty}")
println("Bad Item Qty: ${badItemQty}")
println("Book Qty (inQty - outQty): ${bookQty}")
println("Issue Qty (Calculated): ${issueQty}")
println("Bad Reason: ${request.badReason}")
println("Lot ID: ${request.lotId}")
println("Item ID: ${request.itemId}")
println("================================================")
// 5. 创建 pick execution issue 记录
val pickExecutionIssue = PickExecutionIssue(
id = null,
pickOrderId = request.pickOrderId,
pickOrderCode = request.pickOrderCode,
pickOrderCreateDate = request.pickOrderCreateDate,
pickExecutionDate = request.pickExecutionDate ?: LocalDate.now(),
pickOrderLineId = request.pickOrderLineId,
issueNo = generateIssueNo(),
joPickOrderId = pickOrder?.jobOrder?.id,
doPickOrderId = if (pickOrder?.type?.value == "do") pickOrder.id else null,
issueCategory = IssueCategory.valueOf(
request.issueCategory ?: "lot_issue"
),
itemId = request.itemId,
itemCode = request.itemCode,
itemDescription = request.itemDescription,
lotId = request.lotId,
lotNo = request.lotNo,
storeLocation = request.storeLocation,
requiredQty = request.requiredQty,
actualPickQty = request.actualPickQty,
missQty = request.missQty,
badItemQty = request.badItemQty,
bookQty = bookQty,
issueQty = issueQty,
issueRemark = request.issueRemark,
pickerName = request.pickerName,
handleStatus = HandleStatus.pending,
handleDate = null,
handledBy = request.handledBy,
created = LocalDateTime.now(),
createdBy = "system",
version = 0,
modified = LocalDateTime.now(),
modifiedBy = "system",
deleted = false
)
val savedIssue = pickExecutionIssueRepository.save(pickExecutionIssue)
// 6. NEW: Update inventory_lot_line.issueQty
if (request.lotId != null && inventoryLotLine != null) {
val currentIssueQty = inventoryLotLine.issueQty ?: BigDecimal.ZERO
val newIssueQty = currentIssueQty.add(issueQty)
inventoryLotLine.issueQty = newIssueQty
inventoryLotLine.modified = LocalDateTime.now()
inventoryLotLine.modifiedBy = "system"
inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
println("Updated inventory_lot_line ${request.lotId} issueQty: ${currentIssueQty} -> ${newIssueQty}")
} }
}

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}")
// 7. 获取相关数据用于后续处理
val actualPickQtyForProcessing = request.actualPickQty ?: BigDecimal.ZERO
val missQtyForProcessing = request.missQty ?: BigDecimal.ZERO
val badItemQtyForProcessing = request.badItemQty ?: BigDecimal.ZERO
val lotId = request.lotId
val itemId = request.itemId
println("=== PICK EXECUTION ISSUE PROCESSING (NEW LOGIC) ===")
println("Actual Pick Qty: ${actualPickQtyForProcessing}")
println("Miss Qty: ${missQtyForProcessing}")
println("Bad Item Qty: ${badItemQtyForProcessing}")
println("Bad Reason: ${request.badReason}")
println("Lot ID: ${lotId}")
println("Item ID: ${itemId}")
println("================================================")
// 8. 新的统一处理逻辑(根据 badReason 决定处理方式)
when {
// 情况1: 只有 miss item (actualPickQty = 0, missQty > 0, badItemQty = 0)
actualPickQtyForProcessing == BigDecimal.ZERO && missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing == BigDecimal.ZERO -> {
handleMissItemOnly(request, missQtyForProcessing)
}
// 情况2: 只有 bad item (badItemQty > 0, missQty = 0)
badItemQtyForProcessing > BigDecimal.ZERO && missQtyForProcessing == BigDecimal.ZERO -> {
// NEW: Check bad reason
if (request.badReason == "package_problem") {
handleBadItemPackageProblem(request, badItemQtyForProcessing)
} else {
// quantity_problem or default: handle as normal bad item
handleBadItemOnly(request, badItemQtyForProcessing)
}
}
// 情况3: 既有 miss item 又有 bad item
missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing > BigDecimal.ZERO -> {
// NEW: Check bad reason
if (request.badReason == "package_problem") {
handleBothMissAndBadItemPackageProblem(request, missQtyForProcessing, badItemQtyForProcessing)
} else {
handleBothMissAndBadItem(request, missQtyForProcessing, badItemQtyForProcessing)
}
}
// 情况4: 有 miss item 的情况(无论 actualPickQty 是多少)
missQtyForProcessing > BigDecimal.ZERO -> {
handleMissItemWithPartialPick(request, actualPickQtyForProcessing, missQtyForProcessing)
}
// 情况5: 正常拣货 (actualPickQty > 0, 没有 miss 或 bad item)
actualPickQtyForProcessing > BigDecimal.ZERO -> {
handleNormalPick(request, actualPickQtyForProcessing)
}
else -> {
println("Unknown case: actualPickQty=${actualPickQtyForProcessing}, missQty=${missQtyForProcessing}, badItemQty=${badItemQtyForProcessing}")
}
} }
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",
code = "SUCCESS",
type = "pick_execution_issue",
message = "Pick execution issue recorded successfully",
errorPosition = null
)
} catch (e: Exception) {
println("=== ERROR IN recordPickExecutionIssue ===")
e.printStackTrace()
return MessageResponse(
id = null,
name = "Failed to record pick execution issue",
code = "ERROR",
type = "pick_execution_issue",
message = "Error: ${e.message}",
errorPosition = null
)
} }

return MessageResponse(
id = savedIssue.id,
name = "Pick execution issue recorded successfully",
code = "SUCCESS",
type = "pick_execution_issue",
message = "Pick execution issue recorded successfully",
errorPosition = null
)

} catch (e: Exception) {
println("=== ERROR IN recordPickExecutionIssue ===")
e.printStackTrace()
return MessageResponse(
id = null,
name = "Failed to record pick execution issue",
code = "ERROR",
type = "pick_execution_issue",
message = "Error: ${e.message}",
errorPosition = null
)
} }
}
private fun generateIssueNo(): String { private fun generateIssueNo(): String {
val now = LocalDateTime.now() val now = LocalDateTime.now()
val yearMonth = now.format(java.time.format.DateTimeFormatter.ofPattern("yyMM")) val yearMonth = now.format(java.time.format.DateTimeFormatter.ofPattern("yyMM"))
@@ -1511,7 +1524,8 @@ private fun updateLotLineAfterIssue(lotLineId: Long, qty: BigDecimal) {
val currentOutQty = lotLine.outQty ?: BigDecimal.ZERO val currentOutQty = lotLine.outQty ?: BigDecimal.ZERO
val newOutQty = currentOutQty.add(qty) val newOutQty = currentOutQty.add(qty)
lotLine.outQty = newOutQty lotLine.outQty = newOutQty
val currentIssueQty = lotLine.issueQty ?: BigDecimal.ZERO
lotLine.issueQty = currentIssueQty.subtract(qty)
// If outQty != inQty, set status to AVAILABLE // If outQty != inQty, set status to AVAILABLE
val inQty = lotLine.inQty ?: BigDecimal.ZERO val inQty = lotLine.inQty ?: BigDecimal.ZERO
if (newOutQty != inQty) { if (newOutQty != inQty) {
@@ -1627,4 +1641,189 @@ private fun createStockLedgerForStockOut(stockOutLine: StockOutLine, ledgerType:
println("Balance: ${finalBalance}") println("Balance: ${finalBalance}")
println("===========================") println("===========================")
} }



@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleBadItemPackageProblem(request: PickExecutionIssueRequest, badItemQty: BigDecimal) {
println("=== HANDLING BAD ITEM PACKAGE PROBLEM (DON'T REJECT LOT) ===")
println("Bad Item Qty: ${badItemQty}")
val lotId = request.lotId ?: return
val itemId = request.itemId ?: return
val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
if (inventoryLotLine != null) {
// Package problem: Don't mark as unavailable, just record the issue
// Update outQty for actual picked items if any
if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
val newOutQty = currentOutQty.add(request.actualPickQty)
inventoryLotLine.outQty = newOutQty
println("Package problem: Updated outQty: ${currentOutQty} -> ${newOutQty}")
}
// IssueQty is already updated in recordPickExecutionIssue
inventoryLotLine.modified = LocalDateTime.now()
inventoryLotLine.modifiedBy = "system"
inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
println("Package problem: Updated lot ${lotId}, but did NOT mark as unavailable")
}
// Update inventory unavailableQty (still need to track unavailable quantity)
updateInventoryUnavailableQty(itemId, badItemQty)
// DON'T reject stock_out_line for package problem
val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
request.pickOrderLineId,
request.lotId ?: 0L
)
stockOutLines.forEach { stockOutLine ->
if (request.actualPickQty != null) {
stockOutLine.qty = request.actualPickQty.toDouble()
}
// Determine status based on actual pick qty vs required qty
val requiredQty = request.requiredQty?.toDouble() ?: 0.0
val actualPickQtyDouble = request.actualPickQty?.toDouble() ?: 0.0
val newStatus = if (actualPickQtyDouble >= requiredQty) {
"completed"
} else {
"partially_completed"
}
stockOutLine.status = newStatus
stockOutLine.modified = LocalDateTime.now()
stockOutLine.modifiedBy = "system"
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine)
// Create stock ledger for actual picked items
if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
createStockLedgerForStockOut(savedStockOutLine, "Nor")
}
println("Package problem: Updated stock out line ${stockOutLine.id} status to: ${newStatus} (NOT rejected)")
}
try {
// 确保所有更改都已刷新到数据库
stockOutLineRepository.flush()
// 直接查询并检查该 pick order line 下的所有 stock out lines
val allStockOutLines = stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(request.pickOrderLineId)
val unfinishedLines = allStockOutLines.filter {
it.status != StockOutLineStatus.COMPLETE.status
&& it.status != StockOutLineStatus.REJECTED.status
}
println("Package problem: Checking pick order line ${request.pickOrderLineId}")
println(" Total stock out lines: ${allStockOutLines.size}")
println(" Unfinished lines: ${unfinishedLines.size}")
unfinishedLines.forEach { line ->
println(" - Line ${line.id}: status=${line.status}")
}
if (unfinishedLines.isEmpty() && allStockOutLines.isNotEmpty()) {
val pickOrderLine = pickOrderLineRepository.findById(request.pickOrderLineId).orElse(null)
if (pickOrderLine != null) {
pickOrderLine.status = PickOrderLineStatus.COMPLETED
pickOrderLineRepository.saveAndFlush(pickOrderLine)
println("✅ Package problem: Updated pick order line ${request.pickOrderLineId} status to COMPLETED")
} else {
println("⚠️ Package problem: Pick order line ${request.pickOrderLineId} not found")
}
} else {
println("⚠️ Package problem: Pick order line ${request.pickOrderLineId} still has ${unfinishedLines.size} unfinished lines")
}
} catch (e: Exception) {
println("⚠️ Error checking pick order line completion: ${e.message}")
e.printStackTrace()
}
// Don't resuggest for package problem (lot is still available)
println("Package problem: Did NOT resuggest pick order (lot remains available)")
}

// NEW: Handle both miss and bad item with package problem
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class])
private fun handleBothMissAndBadItemPackageProblem(request: PickExecutionIssueRequest, missQty: BigDecimal, badItemQty: BigDecimal) {
println("=== HANDLING BOTH MISS AND BAD ITEM PACKAGE PROBLEM ===")
println("Miss Qty: ${missQty}, Bad Item Qty: ${badItemQty}")
val lotId = request.lotId ?: return
val itemId = request.itemId ?: return
val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null)
if (inventoryLotLine != null) {
// Update outQty for actual picked items
if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO
val newOutQty = currentOutQty.add(request.actualPickQty)
inventoryLotLine.outQty = newOutQty
println("Both miss and bad (package): Updated outQty: ${currentOutQty} -> ${newOutQty}")
}
// Miss item: mark as unavailable
if (missQty > BigDecimal.ZERO) {
inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE
println("Both miss and bad (package): Set status to UNAVAILABLE due to missQty: ${missQty}")
}
// IssueQty is already updated in recordPickExecutionIssue
inventoryLotLine.modified = LocalDateTime.now()
inventoryLotLine.modifiedBy = "system"
inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
}
// Update inventory unavailableQty (miss qty + bad qty)
val totalUnavailableQty = missQty.add(badItemQty)
updateInventoryUnavailableQty(itemId, totalUnavailableQty)
// Update stock_out_line: reject only if missQty > 0, otherwise partial/completed
val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
request.pickOrderLineId,
request.lotId ?: 0L
)
stockOutLines.forEach { stockOutLine ->
if (request.actualPickQty != null) {
stockOutLine.qty = request.actualPickQty.toDouble()
}
val requiredQty = request.requiredQty?.toDouble() ?: 0.0
val actualPickQtyDouble = request.actualPickQty?.toDouble() ?: 0.0
// If missQty > 0, reject; otherwise check completion status
val newStatus = if (missQty > BigDecimal.ZERO) {
"rejected"
} else if (actualPickQtyDouble >= requiredQty) {
"completed"
} else {
"partially_completed"
}
stockOutLine.status = newStatus
stockOutLine.modified = LocalDateTime.now()
stockOutLine.modifiedBy = "system"
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine)
if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) {
createStockLedgerForStockOut(savedStockOutLine, "Nor")
}
println("Both miss and bad (package): Updated stock out line ${stockOutLine.id} status to: ${newStatus}")
}
// Resuggest only for miss qty
if (missQty > BigDecimal.ZERO) {
try {
resuggestPickOrder(request.pickOrderId)
println("Resuggested pick order for miss qty: ${missQty}")
} catch (e: Exception) {
println("Error during resuggest: ${e.message}")
}
} else {
println("Both miss and bad (package): Did NOT resuggest (only bad item with package problem)")
}
}
} }

+ 28
- 14
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt Прегледај датотеку

@@ -3601,8 +3601,8 @@ ORDER BY
val enrichedResults = filteredResults val enrichedResults = filteredResults
return enrichedResults return enrichedResults
} }
// ... existing code ...
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
// 修改后的逻辑
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (Repository-based) ===") println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (Repository-based) ===")
println("userId filter: $userId") println("userId filter: $userId")
@@ -3612,8 +3612,9 @@ ORDER BY
return emptyMap() return emptyMap()
} }
// Step 1: 先找到分配给该用户的所有 pick orders
val userPickOrders = pickOrderRepository.findAll()
// Step 1: 先找到用户有活跃任务的 do_pick_order(通过 do_pick_order_line)
// 这样可以确保只获取同一个 ticket 的 pick orders
val userPickOrdersForDo = pickOrderRepository.findAll()
.filter { .filter {
it.deleted == false && it.deleted == false &&
it.assignTo?.id == userId && it.assignTo?.id == userId &&
@@ -3624,26 +3625,26 @@ ORDER BY
it.status == PickOrderStatus.ASSIGNED) it.status == PickOrderStatus.ASSIGNED)
} }
if (userPickOrders.isEmpty()) {
println("❌ No pick orders found for user $userId")
if (userPickOrdersForDo.isEmpty()) {
println("❌ No active pick orders found for user $userId")
return mapOf( return mapOf(
"fgInfo" to null, "fgInfo" to null,
"pickOrders" to emptyList<Any>() "pickOrders" to emptyList<Any>()
) )
} }
println(" Found ${userPickOrders.size} pick orders assigned to user $userId")
val pickOrderIds = userPickOrders.mapNotNull { it.id }
val activePickOrderIds = userPickOrdersForDo.mapNotNull { it.id }
println(" Found ${activePickOrderIds.size} active pick orders assigned to user $userId")
// Step 2: 通过 do_pick_order_line 找到相关的 do_pick_order // Step 2: 通过 do_pick_order_line 找到相关的 do_pick_order
val doPickOrderLineRecords = pickOrderIds.flatMap { poId ->
val doPickOrderLineRecords = activePickOrderIds.flatMap { poId ->
doPickOrderLineRepository.findByPickOrderIdAndDeletedFalse(poId) doPickOrderLineRepository.findByPickOrderIdAndDeletedFalse(poId)
} }
val doPickOrderIds = doPickOrderLineRecords.mapNotNull { it.doPickOrderId }.distinct() val doPickOrderIds = doPickOrderLineRecords.mapNotNull { it.doPickOrderId }.distinct()
if (doPickOrderIds.isEmpty()) { if (doPickOrderIds.isEmpty()) {
println("❌ No do_pick_order found for pick orders: $pickOrderIds")
println("❌ No do_pick_order found for pick orders: $activePickOrderIds")
return mapOf( return mapOf(
"fgInfo" to null, "fgInfo" to null,
"pickOrders" to emptyList<Any>() "pickOrders" to emptyList<Any>()
@@ -3665,11 +3666,22 @@ ORDER BY
val doPickOrderId = doPickOrder.id!! val doPickOrderId = doPickOrder.id!!
println(" Using do_pick_order ID: $doPickOrderId") println(" Using do_pick_order ID: $doPickOrderId")
// Step 4: 使用 Repository 加载 pick orders 及其关联数据
val pickOrders = pickOrderRepository.findAllById(pickOrderIds)
.filter { it.deleted == false }
// Step 4: 关键修改 - 只获取这个 do_pick_order 下的所有 pick orders(无论状态)
val allDoPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrderId)
val allPickOrderIdsForThisTicket = allDoPickOrderLines.mapNotNull { it.pickOrderId }.distinct()
println(" Loaded ${pickOrders.size} pick orders")
println(" Found ${allPickOrderIdsForThisTicket.size} pick orders in this do_pick_order (including completed)")
// Step 5: 加载这些 pick orders(包括 COMPLETED 状态)
val pickOrders = pickOrderRepository.findAllById(allPickOrderIdsForThisTicket)
.filter {
it.deleted == false &&
it.assignTo?.id == userId && // 确保是分配给该用户的
it.type?.value == "do"
// 不限制状态,包括 COMPLETED
}
println(" Loaded ${pickOrders.size} pick orders (including completed)")
// 收集所有需要的数据 // 收集所有需要的数据
// 收集所有需要的数据 // 收集所有需要的数据
@@ -3780,6 +3792,7 @@ ORDER BY
"outQty" to ill.outQty, "outQty" to ill.outQty,
"holdQty" to ill.holdQty, "holdQty" to ill.holdQty,
"lotStatus" to ill.status?.value, "lotStatus" to ill.status?.value,
"stockInLineId" to il?.stockInLine?.id,
"lotAvailability" to when { "lotAvailability" to when {
isExpired -> "expired" isExpired -> "expired"
stockOutLine?.status == "rejected" -> "rejected" stockOutLine?.status == "rejected" -> "rejected"
@@ -3866,6 +3879,7 @@ ORDER BY
"outQty" to ill.outQty, "outQty" to ill.outQty,
"holdQty" to ill.holdQty, "holdQty" to ill.holdQty,
"lotStatus" to ill.status?.value, "lotStatus" to ill.status?.value,
"stockInLineId" to il?.stockInLine?.id,
"lotAvailability" to when { "lotAvailability" to when {
isExpired -> "expired" isExpired -> "expired"
sol.status == "rejected" -> "rejected" sol.status == "rejected" -> "rejected"


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt Прегледај датотеку

@@ -27,7 +27,8 @@ data class PickExecutionIssueRequest(
val badItemQty: BigDecimal = BigDecimal.ZERO, val badItemQty: BigDecimal = BigDecimal.ZERO,
val issueRemark: String? = null, val issueRemark: String? = null,
val pickerName: String? = null, val pickerName: String? = null,
val handledBy: Long? = null
val handledBy: Long? = null,
val badReason: String? = null,
) )






+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLine.kt Прегледај датотеку

@@ -45,6 +45,9 @@ open class InventoryLotLine : BaseEntity<Long>() {
@Column(name = "holdQty") @Column(name = "holdQty")
open var holdQty: BigDecimal? = null open var holdQty: BigDecimal? = null


@Column(name = "issueQty")
open var issueQty: BigDecimal? = null

@Convert(converter = InventoryLotLineStringConverter::class) @Convert(converter = InventoryLotLineStringConverter::class)
@Column(name = "status") @Column(name = "status")
open var status: InventoryLotLineStatus? = null open var status: InventoryLotLineStatus? = null


+ 22
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt Прегледај датотеку

@@ -47,4 +47,26 @@ fun searchStockOutLines(


@Query("SELECT sol FROM StockOutLine sol WHERE sol.item.id IN :itemIds AND sol.deleted = false") @Query("SELECT sol FROM StockOutLine sol WHERE sol.item.id IN :itemIds AND sol.deleted = false")
fun findAllByItemIdInAndDeletedFalse(itemIds: List<Long>): List<StockOutLine> fun findAllByItemIdInAndDeletedFalse(itemIds: List<Long>): List<StockOutLine>


@Query("""
SELECT sol FROM StockOutLine sol
WHERE sol.pickOrderLine.id IN :pickOrderLineIds
AND sol.deleted = false
""")
fun findAllByPickOrderLineIdInAndDeletedFalse(
@Param("pickOrderLineIds") pickOrderLineIds: List<Long>
): List<StockOutLine>

// 添加批量查询方法:按 (pickOrderLineId, inventoryLotLineId) 组合查询
@Query("""
SELECT sol FROM StockOutLine sol
WHERE sol.pickOrderLine.id IN :pickOrderLineIds
AND sol.inventoryLotLine.id IN :inventoryLotLineIds
AND sol.deleted = false
""")
fun findAllByPickOrderLineIdInAndInventoryLotLineIdInAndDeletedFalse(
@Param("pickOrderLineIds") pickOrderLineIds: List<Long>,
@Param("inventoryLotLineIds") inventoryLotLineIds: List<Long>
): List<StockOutLine>
} }

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

@@ -523,249 +523,301 @@ open class SuggestedPickLotService(
return null return null
} }
} }
@Transactional(rollbackFor = [Exception::class])
open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
try {
val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow()
println("=== RESUGGEST DEBUG START ===")
println("Pick Order ID: $pickOrderId")
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)
@Transactional(rollbackFor = [Exception::class])
open fun resuggestPickOrder(pickOrderId: Long): MessageResponse {
try {
val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow()
println("=== RESUGGEST DEBUG START ===")
println("Pick Order ID: $pickOrderId")
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})")
} }
// 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 ->
val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!)
val hasRejectedStockOutLine = stockOutLines.any { it.status.equals("rejected", ignoreCase = true)}
if (hasRejectedStockOutLine) {
println("Pick Order ${pickOrderToCheck.code} has rejected stock out lines - will resuggest")
stockOutLines.filter { it.status == "rejected" }.forEach { sol ->
println(" - Rejected stock out line: ${sol.id} (lot: ${sol.inventoryLotLineId}, qty: ${sol.qty})")
}
allCompetingPickOrders.addAll(competingOrders)
}
// OPTIMIZATION 3: 批量收集所有需要检查的 pick order line IDs
val allPickOrderLineIdsToCheck = (listOf(pickOrder) + allCompetingPickOrders)
.flatMap { it.pickOrderLines }
.mapNotNull { it.id }
.distinct()
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 {
emptyList()
}
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 ->
println(" - Rejected stock out line: ${sol.id} (lot: ${sol.inventoryLotLine?.id}, qty: ${sol.qty})")
} }
hasRejectedStockOutLine
}
hasRejectedStockOutLine
}
} }
println("=== RESUGGEST DEBUG ===")
println("Original pick orders: ${(listOf(pickOrder) + allCompetingPickOrders).size}")
println("Filtered pick orders to resuggest: ${allPickOrdersToResuggest.size}")
println("Pick orders being resuggested: ${allPickOrdersToResuggest.map { "${it.code}(${it.status})" }}")
// FIX: Only resuggest if there are actually pick orders with rejected lots
if (allPickOrdersToResuggest.isEmpty()) {
println("No pick orders need resuggesting - no rejected lots found")
return MessageResponse(
id = pickOrderId,
name = "No resuggest needed",
code = "SUCCESS",
type = "resuggest",
message = "No pick orders have rejected lots, no resuggest needed",
errorPosition = null
)
} }
// FIX: Get all pick order line IDs for the orders to resuggest
val allPickOrderLineIds = allPickOrdersToResuggest
.flatMap { it.pickOrderLines }
.mapNotNull { it.id }
println("All pick order line IDs to resuggest: $allPickOrderLineIds")
// FIX: Get all existing suggestions for these pick order lines
val allSuggestions = suggestedPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds)
println("Found ${allSuggestions.size} existing suggestions")
// OPTIMIZATION 3: 批量查询所有相关的 stock out lines(用于检查 rejected 状态)
val allSuggestionStockOutLines = if (allSuggestions.isNotEmpty()) {
val suggestionLineIds = allSuggestions.mapNotNull { it.pickOrderLine?.id }.distinct()
val suggestionLotIds = allSuggestions.mapNotNull { it.suggestedLotLine?.id }.distinct()
println("=== RESUGGEST DEBUG ===")
println("Original pick orders: ${(listOf(pickOrder) + allCompetingPickOrders).size}")
println("Filtered pick orders to resuggest: ${allPickOrdersToResuggest.size}")
println("Pick orders being resuggested: ${allPickOrdersToResuggest.map { "${it.code}(${it.status})" }}")
// FIX: Only resuggest if there are actually pick orders with rejected lots
if (allPickOrdersToResuggest.isEmpty()) {
println("No pick orders need resuggesting - no rejected lots found")
return MessageResponse(
id = pickOrderId,
name = "No resuggest needed",
code = "SUCCESS",
type = "resuggest",
message = "No pick orders have rejected lots, no resuggest needed",
errorPosition = null
// OPTIMIZATION 3: 使用批量查询方法(一次查询代替 N*M 次查询)
// 注意:返回 List<StockOutLine>(实体类)
if (suggestionLineIds.isNotEmpty() && suggestionLotIds.isNotEmpty()) {
stockOutLIneRepository.findAllByPickOrderLineIdInAndInventoryLotLineIdInAndDeletedFalse(
suggestionLineIds,
suggestionLotIds
) )
} else {
emptyList()
} }
// FIX: Get all pick order line IDs for the orders to resuggest
val allPickOrderLineIds = allPickOrdersToResuggest
.flatMap { it.pickOrderLines }
.mapNotNull { it.id }
println("All pick order line IDs to resuggest: $allPickOrderLineIds")
// FIX: Get all existing suggestions for these pick order lines
val allSuggestions = suggestedPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds)
println("Found ${allSuggestions.size} existing suggestions")
// 删除第 376-395 行的旧代码,替换为:
// 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
if (pickOrderLineId != null && suggestedLotLineId != null) {
val stockOutLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
pickOrderLineId,
suggestedLotLineId
)
// 保留没有 rejected stock out lines 的 suggestions
!stockOutLines.any { it.status == "rejected" }
} else {
true // 保留有问题的 suggestions 用于调试
}
}

// 只删除有 rejected stock out lines 的 suggestions
val suggestionsToDelete = allSuggestions.filter { suggestion ->
val pickOrderLineId = suggestion.pickOrderLine?.id
val suggestedLotLineId = suggestion.suggestedLotLine?.id
if (pickOrderLineId != null && suggestedLotLineId != null) {
val stockOutLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
pickOrderLineId,
suggestedLotLineId
)
stockOutLines.any { it.status == "rejected" } // 只删除 rejected 的
} else {
suggestedLotLineId == null
}
}
} else {
emptyList()
}
println("Found ${allSuggestionStockOutLines.size} stock out lines for suggestions in batch query (1 query instead of ${allSuggestions.size} queries)")
// OPTIMIZATION 3: 按 (pickOrderLineId, inventoryLotLineId) 分组
// 注意:StockOutLine 实体使用 pickOrderLine?.id 和 inventoryLotLine?.id
val stockOutLinesByLineAndLot = allSuggestionStockOutLines
.groupBy {
val lineId = it.pickOrderLine?.id
val lotId = it.inventoryLotLine?.id
lineId to lotId
}
// 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
println("Suggestions to keep (with rejected stock out lines): ${suggestionsToKeep.size}")
println("Suggestions to delete: ${suggestionsToDelete.size}")
// 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")
rejectedLotIds.forEach { lotId ->
val lot = inventoryLotLineRepository.findById(lotId).orElse(null)
lot?.let {
val originalHoldQty = it.holdQty ?: BigDecimal.ZERO
it.holdQty = BigDecimal.ZERO
inventoryLotLineRepository.save(it)
println("Cleared holdQty for rejected lot ${lot.id}: $originalHoldQty -> 0")
}
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 用于调试
} }
}


println("Keeping all suggestions (including rejected ones for display)")
// NEW: Build holdQtyMap with existing holdQty from other pick orders
val existingHoldQtyMap = mutableMapOf<Long?, BigDecimal?>()
// 只删除有 rejected stock out lines 的 suggestions
val suggestionsToDelete = allSuggestions.filter { suggestion ->
val pickOrderLineId = suggestion.pickOrderLine?.id
val suggestedLotLineId = suggestion.suggestedLotLine?.id
// Get all lots that are being used by other pick orders (including those NOT being resuggested)
val allOtherPickOrderLineIds = allCompetingPickOrders
.flatMap { it.pickOrderLines }
.mapNotNull { it.id }
println("Other pick order line IDs: $allOtherPickOrderLineIds")
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 的
} else {
suggestedLotLineId == null
}
}
println("Suggestions to keep: ${suggestionsToKeep.size}")
println("Suggestions to delete: ${suggestionsToDelete.size}")
// 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")
// OPTIMIZATION 3: 批量加载和更新 lots
val rejectedLots = if (rejectedLotIds.isNotEmpty()) {
inventoryLotLineRepository.findAllById(rejectedLotIds)
} else {
emptyList()
}
rejectedLots.forEach { lot ->
val originalHoldQty = lot.holdQty ?: BigDecimal.ZERO
lot.holdQty = BigDecimal.ZERO
println("Cleared holdQty for rejected lot ${lot.id}: $originalHoldQty -> 0")
}
if (rejectedLots.isNotEmpty()) {
inventoryLotLineRepository.saveAll(rejectedLots)
println("Batch cleared holdQty for ${rejectedLots.size} rejected lots")
}

println("Keeping all suggestions (including rejected ones for display)")
// NEW: Build holdQtyMap with existing holdQty from other pick orders
val existingHoldQtyMap = mutableMapOf<Long?, BigDecimal?>()
// Get all lots that are being used by other pick orders (including those NOT being resuggested)
val allOtherPickOrderLineIds = allCompetingPickOrders
.flatMap { it.pickOrderLines }
.mapNotNull { it.id }
println("Other pick order line IDs: $allOtherPickOrderLineIds")
if (allOtherPickOrderLineIds.isNotEmpty()) {
val otherSuggestions = suggestedPickLotRepository.findAllByPickOrderLineIdIn(allOtherPickOrderLineIds)
println("Found ${otherSuggestions.size} other suggestions")
if (allOtherPickOrderLineIds.isNotEmpty()) {
val otherSuggestions = suggestedPickLotRepository.findAllByPickOrderLineIdIn(allOtherPickOrderLineIds)
println("Found ${otherSuggestions.size} other suggestions")
otherSuggestions.forEach { suggestion ->
val lotId = suggestion.suggestedLotLine?.id
val qty = suggestion.qty
otherSuggestions.forEach { suggestion ->
val lotId = suggestion.suggestedLotLine?.id
val qty = suggestion.qty
println("Processing other suggestion ${suggestion.id}: lotId=$lotId, qty=$qty")
if (lotId != null && qty != null) {
val currentHoldQty = existingHoldQtyMap[lotId] ?: BigDecimal.ZERO
val newHoldQty = currentHoldQty.plus(qty)
existingHoldQtyMap[lotId] = newHoldQty
println(" Updated holdQty for lot $lotId: $currentHoldQty + $qty = $newHoldQty")
}
if (lotId != null && qty != null) {
val currentHoldQty = existingHoldQtyMap[lotId] ?: BigDecimal.ZERO
val newHoldQty = currentHoldQty.plus(qty)
existingHoldQtyMap[lotId] = newHoldQty
} }
} }
}
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
val problematicPickOrderLines = pickOrderToResuggest.pickOrderLines.filter { pol ->
val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList()
stockOutLines.any { it.status?.equals("rejected", ignoreCase = true) == true }
}
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
val problematicPickOrderLines = pickOrderToResuggest.pickOrderLines.filter { pol ->
val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!)
stockOutLines.any { it.status == "rejected" }
}
if (problematicPickOrderLines.isNotEmpty()) {
println("=== Creating new suggestions for pick order: ${pickOrderToResuggest.code} ===")
if (problematicPickOrderLines.isNotEmpty()) {
println("=== Creating new suggestions for pick order: ${pickOrderToResuggest.code} ===")
// 调用 suggestionForPickOrderLines 生成新的 suggestions
val request = SuggestedPickLotForPolRequest(
pickOrderLines = problematicPickOrderLines,
holdQtyMap = existingHoldQtyMap.toMutableMap()
)
val response = suggestionForPickOrderLines(request)
println("Generated ${response.suggestedList.size} new suggestions")
response.suggestedList.forEach { suggestion ->
println(" - Suggestion: lotId=${suggestion.suggestedLotLine?.id}, qty=${suggestion.qty}")
}
// 调用 suggestionForPickOrderLines 生成新的 suggestions
val request = SuggestedPickLotForPolRequest(
pickOrderLines = problematicPickOrderLines,
holdQtyMap = existingHoldQtyMap.toMutableMap()
)
val response = suggestionForPickOrderLines(request)
println("Generated ${response.suggestedList.size} new suggestions")
if (response.suggestedList.isNotEmpty()) {
println("Saving ${response.suggestedList.size} new suggestions")
if (response.suggestedList.isNotEmpty()) {
println("Saving ${response.suggestedList.size} new suggestions")
// 获取现有的 pending/checked 状态的 suggestions(可以更新的)
// 获取现有的 pending/checked 状态的 suggestions(可以更新的)
val existingUpdatableSuggestions = suggestionsToKeep val existingUpdatableSuggestions = suggestionsToKeep
.filter { it.suggestedLotLine?.id != null }
.groupBy { it.pickOrderLine?.id to it.suggestedLotLine?.id }
.mapValues { it.value.first() } // 每个 (lineId, lotId) 只取第一个
.filter { it.suggestedLotLine?.id != null }
.groupBy { it.pickOrderLine?.id to it.suggestedLotLine?.id }
.mapValues { it.value.first() } // 每个 (lineId, lotId) 只取第一个


// 处理新的 suggestions:更新现有的或创建新的 // 处理新的 suggestions:更新现有的或创建新的
val suggestionsToSave = response.suggestedList.mapNotNull { newSugg -> 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
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
)
if (lineId != null && lotId != null) {
// 检查这个 lot 是否已有 suggestion
val existingSugg = existingUpdatableSuggestions[key]
val canUpdate = stockOutLines.isEmpty() || stockOutLines.all {
it.status == "pending" || it.status == "checked" || it.status == "partially_completed"
if (existingSugg != null) {
// OPTIMIZATION 3: 从预加载的 Map 中获取
val stockOutLines = stockOutLinesByLineAndLot[lineId to lotId] ?: emptyList()
val canUpdate = stockOutLines.isEmpty() || stockOutLines.all {
val status = it.status?.lowercase()
status == "pending" || status == "checked" || 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: 已完成/拒绝,跳过(不更新,也不创建新的)
val firstStatus = stockOutLines.firstOrNull()?.status ?: "unknown"
println("⏭️ Skipping lot $lotId - already $firstStatus")
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 (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
if (existingResuggestIssues.isEmpty()) {
newSugg // 创建新的 null suggestion(后续会创建 issue)
} else { } else {
// Case 2: 已完成/拒绝,跳过(不更新,也不创建新的)
println("⏭️ Skipping lot $lotId - already ${stockOutLines.first().status}")
null
println("⏭️ Resuggest issue already exists for line $lineId, skipping null suggestion")
null // 跳过,避免创建重复的 resuggest_issue
} }
} else { } else {
// 没有现有的 suggestion,创建新的
newSugg 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() }.filterNotNull()


val updatedSuggestions = suggestionsToSave.filter { it.id != null } // 有 id 的是更新的 val updatedSuggestions = suggestionsToSave.filter { it.id != null } // 有 id 的是更新的
@@ -789,175 +841,169 @@ println("Keeping all suggestions (including rejected ones for display)")


val savedSuggestions = allSavedSuggestions val savedSuggestions = allSavedSuggestions
println("Saved/Updated ${savedSuggestions.size} suggestions") 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}")
}
} else {
// 如果 lot 是 null,表示没有可用的 lot,创建 resuggest_issue
println("❌ No available lot for pick order line ${suggestion.pickOrderLine?.id}, creating resuggest_issue")
val pickOrderLine = suggestion.pickOrderLine
if (pickOrderLine != null) {
// 获取这个 line 的 rejected stock out lines
val rejectedStockOutLines = stockOutLIneRepository
.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLine.id!!)
.filter { it.status == "rejected" }
println("Rejected stock out lines: ${rejectedStockOutLines.size}")
// 修复:只创建一个 resuggest_issue(如果有 rejected lines)
if (rejectedStockOutLines.isNotEmpty()) {
println("Creating resuggest failure issue")
createResuggestFailureIssue(
pickOrder = pickOrderToResuggest,
pickOrderLine = pickOrderLine,
rejectedStockOutLine = rejectedStockOutLines.first()
)
}
println("Creating stock out line for suggestion")
//createStockOutLineForSuggestion(suggestion, pickOrderToResuggest)
println("Stock out line created")
}
// 为每个新 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
} }
}
// 更新 holdQty
response.holdQtyMap.forEach { (lotId, newHoldQty) ->
if (lotId != null && newHoldQty != null && newHoldQty > BigDecimal.ZERO) {
val lot = inventoryLotLineRepository.findById(lotId).orElse(null)
lot?.let {
val currentHoldQty = it.holdQty ?: BigDecimal.ZERO
val existingHoldQty = existingHoldQtyMap[lotId] ?: BigDecimal.ZERO
val additionalHoldQty = newHoldQty.minus(existingHoldQty)
val finalHoldQty = currentHoldQty.plus(additionalHoldQty)
it.holdQty = finalHoldQty
inventoryLotLineRepository.save(it)
existingHoldQtyMap[lotId] = newHoldQty
println("Updated holdQty for lot $lotId: $currentHoldQty + $additionalHoldQty = $finalHoldQty")
}
val stockOutLine = createStockOutLineForSuggestion(suggestion, pickOrderToResuggest)
if (stockOutLine != null) {
println(" Created stock out line ${stockOutLine.id} for suggestion ${suggestion.id}")
} }
}
} else {
// 如果完全没有生成任何 suggestions
println("No suggestions generated at all for pick order: ${pickOrderToResuggest.code}")
problematicPickOrderLines.forEach { pickOrderLine ->
val rejectedStockOutLines = stockOutLIneRepository
.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLine.id!!)
.filter { it.status == "rejected" }
} else {
// 如果 lot 是 null,表示没有可用的 lot,创建 resuggest_issue
println("❌ No available lot for pick order line ${suggestion.pickOrderLine?.id}, creating resuggest_issue")
if (rejectedStockOutLines.isNotEmpty()) {
rejectedStockOutLines.forEach { rejectedLine ->
val pickOrderLine = suggestion.pickOrderLine
if (pickOrderLine != null) {
// OPTIMIZATION 3: 从预加载的 Map 中获取
val rejectedStockOutLines = stockOutLinesByPickOrderLineId[pickOrderLine.id]
?.filter { it.status?.equals("rejected", ignoreCase = true) == true } ?: emptyList()
println("Rejected stock out lines: ${rejectedStockOutLines.size}")
// 修复:只创建一个 resuggest_issue(如果有 rejected lines)
if (rejectedStockOutLines.isNotEmpty()) {
println("Creating resuggest failure issue")
// 注意:rejectedStockOutLines 是 List<StockOutLine>,需要转换为合适的类型
val firstRejectedLine = rejectedStockOutLines.first()
createResuggestFailureIssue( createResuggestFailureIssue(
pickOrder = pickOrderToResuggest, pickOrder = pickOrderToResuggest,
pickOrderLine = pickOrderLine, pickOrderLine = pickOrderLine,
rejectedStockOutLine = rejectedLine
rejectedStockOutLine = firstRejectedLine
) )
} }
} }


}
}
// 更新 holdQty
response.holdQtyMap.forEach { (lotId, newHoldQty) ->
if (lotId != null && newHoldQty != null && newHoldQty > BigDecimal.ZERO) {
val lot = inventoryLotLineRepository.findById(lotId).orElse(null)
lot?.let {
val currentHoldQty = it.holdQty ?: BigDecimal.ZERO
val existingHoldQty = existingHoldQtyMap[lotId] ?: BigDecimal.ZERO
val additionalHoldQty = newHoldQty.minus(existingHoldQty)
val finalHoldQty = currentHoldQty.plus(additionalHoldQty)
it.holdQty = finalHoldQty
inventoryLotLineRepository.save(it)
existingHoldQtyMap[lotId] = newHoldQty
println("Updated holdQty for lot $lotId: $currentHoldQty + $additionalHoldQty = $finalHoldQty")
}
}
}
} else {
// 如果完全没有生成任何 suggestions
println("No suggestions generated at all for pick order: ${pickOrderToResuggest.code}")
problematicPickOrderLines.forEach { pickOrderLine ->
// OPTIMIZATION 3: 从预加载的 Map 中获取
val rejectedStockOutLines = stockOutLinesByPickOrderLineId[pickOrderLine.id]
?.filter { it.status?.equals("rejected", ignoreCase = true) == true } ?: emptyList()
if (rejectedStockOutLines.isNotEmpty()) {
rejectedStockOutLines.forEach { rejectedLine ->
createResuggestFailureIssue(
pickOrder = pickOrderToResuggest,
pickOrderLine = pickOrderLine,
rejectedStockOutLine = rejectedLine
)
}
} }
} }
} }
} }
// FIX: Update inventory table for each pick order
allPickOrdersToResuggest.forEach { pickOrderToUpdate ->
println("=== Updating inventory table for pick order: ${pickOrderToUpdate.code} ===")
updateInventoryTableAfterResuggest(pickOrderToUpdate)
}
println("=== RESUGGEST DEBUG END ===")
return MessageResponse(
id = pickOrderId,
name = "Pick order resuggested successfully",
code = "SUCCESS",
type = "resuggest",
message = "Resuggested ${allPickOrdersToResuggest.size} pick orders",
errorPosition = null
)
} catch (e: Exception) {
println("Error in resuggestPickOrder: ${e.message}")
e.printStackTrace()
return MessageResponse(
id = null,
name = "Failed to resuggest pick order",
code = "ERROR",
type = "resuggest",
message = "Error: ${e.message}",
errorPosition = null
)
} }
// FIX: Update inventory table for each pick order
allPickOrdersToResuggest.forEach { pickOrderToUpdate ->
println("=== Updating inventory table for pick order: ${pickOrderToUpdate.code} ===")
updateInventoryTableAfterResuggest(pickOrderToUpdate)
}
println("=== RESUGGEST DEBUG END ===")
return MessageResponse(
id = pickOrderId,
name = "Pick order resuggested successfully",
code = "SUCCESS",
type = "resuggest",
message = "Resuggested ${allPickOrdersToResuggest.size} pick orders",
errorPosition = null
)
} catch (e: Exception) {
println("Error in resuggestPickOrder: ${e.message}")
e.printStackTrace()
return MessageResponse(
id = null,
name = "Failed to resuggest pick order",
code = "ERROR",
type = "resuggest",
message = "Error: ${e.message}",
errorPosition = null
)
} }
}



private fun createResuggestFailureIssue(
pickOrder: PickOrder,
pickOrderLine: PickOrderLine,
rejectedStockOutLine: StockOutLineInfo // 使用 StockOutLineInfo
) {
try {
val item = pickOrderLine.item
// 从 StockOutLineInfo 获取 inventoryLotLineId
val inventoryLotLineId = rejectedStockOutLine.inventoryLotLineId
val inventoryLotLine = if (inventoryLotLineId != null) {
inventoryLotLineRepository.findById(inventoryLotLineId).orElse(null)
} else {
null
}
val issue = PickExecutionIssue(
id = null,
pickOrderId = pickOrder.id!!,
pickOrderCode = pickOrder.code!!,
pickOrderCreateDate = pickOrder.created?.toLocalDate(),
pickExecutionDate = LocalDate.now(),
pickOrderLineId = pickOrderLine.id!!,
issueNo = generateIssueNo(),
joPickOrderId=pickOrder.jobOrder?.id,
doPickOrderId=pickOrder.deliveryOrder?.id,
issueCategory = IssueCategory.resuggest_issue,
itemId = item?.id!!,
itemCode = item.code,
itemDescription = item.name,
lotId = inventoryLotLine?.id,
lotNo = inventoryLotLine?.inventoryLot?.lotNo,
storeLocation = inventoryLotLine?.warehouse?.code,
requiredQty = pickOrderLine.qty,
actualPickQty = rejectedStockOutLine.qty ?: BigDecimal.ZERO, // 直接使用,不需要 toBigDecimal()
missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(rejectedStockOutLine.qty ?: BigDecimal.ZERO), // 直接使用
badItemQty = BigDecimal.ZERO,
issueRemark = "Resuggest failed: No alternative lots available for rejected lot ${inventoryLotLine?.inventoryLot?.lotNo}",
pickerName = null,
handleStatus = HandleStatus.pending,
handleDate = null,
handledBy = null,
created = LocalDateTime.now(),
createdBy = "system",
version = 0,
modified = LocalDateTime.now(),
modifiedBy = "system",
deleted = false
)
pickExecutionIssueRepository.save(issue)
println(" Created resuggest_issue: ${issue.issueNo} for pick order ${pickOrder.code}")
} catch (e: Exception) {
println("❌ Error creating resuggest_issue: ${e.message}")
e.printStackTrace()
}
private fun createResuggestFailureIssue(
pickOrder: PickOrder,
pickOrderLine: PickOrderLine,
rejectedStockOutLine: StockOutLine // 改为 StockOutLine
) {
try {
val item = pickOrderLine.item
// 从 StockOutLine 获取 inventoryLotLineId
val inventoryLotLine = rejectedStockOutLine.inventoryLotLine
val inventoryLotLineId = inventoryLotLine?.id
val issue = PickExecutionIssue(
id = null,
pickOrderId = pickOrder.id!!,
pickOrderCode = pickOrder.code!!,
pickOrderCreateDate = pickOrder.created?.toLocalDate(),
pickExecutionDate = LocalDate.now(),
pickOrderLineId = pickOrderLine.id!!,
issueNo = generateIssueNo(),
joPickOrderId = pickOrder.jobOrder?.id,
doPickOrderId = pickOrder.deliveryOrder?.id,
issueCategory = IssueCategory.resuggest_issue,
itemId = item?.id!!,
itemCode = item.code,
itemDescription = item.name,
lotId = inventoryLotLineId,
lotNo = inventoryLotLine?.inventoryLot?.lotNo,
storeLocation = inventoryLotLine?.warehouse?.code,
requiredQty = pickOrderLine.qty,
actualPickQty = rejectedStockOutLine.qty?.let { BigDecimal.valueOf(it) } ?: BigDecimal.ZERO,
missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(
rejectedStockOutLine.qty?.let { BigDecimal.valueOf(it) } ?: BigDecimal.ZERO
),
badItemQty = BigDecimal.ZERO,
issueRemark = "Resuggest failed: No alternative lots available for rejected lot ${inventoryLotLine?.inventoryLot?.lotNo}",
pickerName = null,
handleStatus = HandleStatus.pending,
handleDate = null,
handledBy = null,
created = LocalDateTime.now(),
createdBy = "system",
version = 0,
modified = LocalDateTime.now(),
modifiedBy = "system",
deleted = false
)
val savedIssue = pickExecutionIssueRepository.save(issue)
println(" Created resuggest_issue: ${savedIssue.issueNo} for pick order ${pickOrder.code}")
} catch (e: Exception) {
println("❌ Error creating resuggest_issue: ${e.message}")
e.printStackTrace()
} }
}
private fun generateIssueNo(): String { private fun generateIssueNo(): String {
val now = LocalDateTime.now() val now = LocalDateTime.now()
val yearMonth = now.format(DateTimeFormatter.ofPattern("yyMM")) val yearMonth = now.format(DateTimeFormatter.ofPattern("yyMM"))


+ 5
- 0
src/main/resources/db/changelog/changes/20260122_01_Enson/01_alter_table.sql Прегледај датотеку

@@ -0,0 +1,5 @@
--liquibase formatted sql
--changeset author:add_time_fields_to_productprocessline

ALTER TABLE `inventory_lot_line`
ADD COLUMN `issueQty` DECIMAL(18,2) NULL AFTER `holdQty`;

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