|
|
|
@@ -11,7 +11,7 @@ import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository |
|
|
|
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine |
|
|
|
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository |
|
|
|
import com.ffii.fpsms.modules.stock.entity.InventoryRepository |
|
|
|
import com.ffii.fpsms.modules.stock.web.model.PickExecutionIssueRequest |
|
|
|
|
|
|
|
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService |
|
|
|
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus |
|
|
|
import org.springframework.stereotype.Service |
|
|
|
@@ -38,7 +38,10 @@ import com.ffii.fpsms.modules.stock.entity.StockOut |
|
|
|
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine |
|
|
|
import com.ffii.fpsms.modules.master.entity.ItemsRepository |
|
|
|
import com.ffii.fpsms.modules.common.CodeGenerator |
|
|
|
import com.ffii.fpsms.modules.pickOrder.web.models.SubmitIssueRequest |
|
|
|
|
|
|
|
import com.ffii.fpsms.modules.stock.entity.StockLedger |
|
|
|
import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository |
|
|
|
|
|
|
|
@Service |
|
|
|
open class PickExecutionIssueService( |
|
|
|
private val pickExecutionIssueRepository: PickExecutionIssueRepository, |
|
|
|
@@ -53,151 +56,223 @@ open class PickExecutionIssueService( |
|
|
|
private val doPickOrderService: DoPickOrderService, |
|
|
|
private val joPickOrderRepository: JoPickOrderRepository, |
|
|
|
private val joPickOrderRecordRepository: JoPickOrderRecordRepository, |
|
|
|
private val itemsRepository: ItemsRepository |
|
|
|
private val itemsRepository: ItemsRepository, |
|
|
|
private val stockLedgerRepository: StockLedgerRepository |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
@Transactional(rollbackFor = [Exception::class]) |
|
|
|
open fun recordPickExecutionIssue(request: PickExecutionIssueRequest): MessageResponse { |
|
|
|
try { |
|
|
|
// 1. 检查是否已经存在相同的 pick execution issue 记录 |
|
|
|
val existingIssues = pickExecutionIssueRepository.findByPickOrderLineIdAndLotIdAndDeletedFalse( |
|
|
|
request.pickOrderLineId, |
|
|
|
request.lotId ?: 0L |
|
|
|
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 |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// 5. 计算 issueQty(实际的问题数量) |
|
|
|
val issueQty = when { |
|
|
|
// 情况1: 已拣完但有坏品 |
|
|
|
actualPickQty == requiredQty && badItemQty > BigDecimal.ZERO -> { |
|
|
|
badItemQty // issueQty = badItemQty |
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
) |
|
|
|
// 情况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 |
|
|
|
} |
|
|
|
val pickOrder = pickOrderRepository.findById(request.pickOrderId).orElse(null) |
|
|
|
// 2. 创建 pick execution issue 记录 |
|
|
|
val pickExecutionIssue = PickExecutionIssue( |
|
|
|
id = null, // 添加 id |
|
|
|
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, |
|
|
|
issueRemark = request.issueRemark, |
|
|
|
pickerName = request.pickerName, // 确保从 request 中获取 |
|
|
|
handleStatus = HandleStatus.pending, // 添加 |
|
|
|
handleDate = null, // 添加 |
|
|
|
handledBy = request.handledBy, |
|
|
|
created = LocalDateTime.now(), |
|
|
|
createdBy = "system", |
|
|
|
version = 0, // 添加 |
|
|
|
modified = LocalDateTime.now(), |
|
|
|
modifiedBy = "system", |
|
|
|
deleted = false // 添加 |
|
|
|
) |
|
|
|
|
|
|
|
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) |
|
|
|
val savedIssue = pickExecutionIssueRepository.save(pickExecutionIssue) |
|
|
|
|
|
|
|
// 3. 获取相关数据 |
|
|
|
val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO |
|
|
|
val missQty = request.missQty ?: BigDecimal.ZERO |
|
|
|
val badItemQty = request.badItemQty ?: BigDecimal.ZERO |
|
|
|
val lotId = request.lotId |
|
|
|
val itemId = request.itemId |
|
|
|
// 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: ${actualPickQty}") |
|
|
|
println("Miss Qty: ${missQty}") |
|
|
|
println("Bad Item Qty: ${badItemQty}") |
|
|
|
println("Lot ID: ${lotId}") |
|
|
|
println("Item ID: ${itemId}") |
|
|
|
println("================================================") |
|
|
|
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("================================================") |
|
|
|
|
|
|
|
// 4. 新的统一处理逻辑 |
|
|
|
when { |
|
|
|
// 情况1: 只有 miss item (actualPickQty = 0, missQty > 0, badItemQty = 0) |
|
|
|
actualPickQty == BigDecimal.ZERO && missQty > BigDecimal.ZERO && badItemQty == BigDecimal.ZERO -> { |
|
|
|
handleMissItemOnly(request, missQty) |
|
|
|
} |
|
|
|
|
|
|
|
// 情况2: 只有 bad item (badItemQty > 0, missQty = 0) |
|
|
|
badItemQty > BigDecimal.ZERO && missQty == BigDecimal.ZERO -> { |
|
|
|
handleBadItemOnly(request, badItemQty) |
|
|
|
} |
|
|
|
|
|
|
|
// 情况3: 既有 miss item 又有 bad item |
|
|
|
missQty > BigDecimal.ZERO && badItemQty > BigDecimal.ZERO -> { |
|
|
|
handleBothMissAndBadItem(request, missQty, badItemQty) |
|
|
|
} |
|
|
|
|
|
|
|
// 修复:情况4: 有 miss item 的情况(无论 actualPickQty 是多少) |
|
|
|
missQty > BigDecimal.ZERO -> { |
|
|
|
handleMissItemWithPartialPick(request, actualPickQty, missQty) |
|
|
|
} |
|
|
|
|
|
|
|
// 情况5: 正常拣货 (actualPickQty > 0, 没有 miss 或 bad item) |
|
|
|
actualPickQty > BigDecimal.ZERO -> { |
|
|
|
handleNormalPick(request, actualPickQty) |
|
|
|
} |
|
|
|
|
|
|
|
else -> { |
|
|
|
println("Unknown case: actualPickQty=${actualPickQty}, missQty=${missQty}, badItemQty=${badItemQty}") |
|
|
|
} |
|
|
|
// 8. 新的统一处理逻辑 |
|
|
|
when { |
|
|
|
// 情况1: 只有 miss item (actualPickQty = 0, missQty > 0, badItemQty = 0) |
|
|
|
actualPickQtyForProcessing == BigDecimal.ZERO && missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing == BigDecimal.ZERO -> { |
|
|
|
handleMissItemOnly(request, missQtyForProcessing) |
|
|
|
} |
|
|
|
|
|
|
|
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}") |
|
|
|
} |
|
|
|
// 情况2: 只有 bad item (badItemQty > 0, missQty = 0) |
|
|
|
badItemQtyForProcessing > BigDecimal.ZERO && missQtyForProcessing == BigDecimal.ZERO -> { |
|
|
|
handleBadItemOnly(request, badItemQtyForProcessing) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
) |
|
|
|
|
|
|
|
// 情况3: 既有 miss item 又有 bad item |
|
|
|
missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing > BigDecimal.ZERO -> { |
|
|
|
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}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} 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 |
|
|
|
) |
|
|
|
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 |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
private fun generateIssueNo(): String { |
|
|
|
val now = LocalDateTime.now() |
|
|
|
val yearMonth = now.format(java.time.format.DateTimeFormatter.ofPattern("yyMM")) |
|
|
|
@@ -383,7 +458,7 @@ private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, ac |
|
|
|
|
|
|
|
inventoryLotLine.modified = LocalDateTime.now() |
|
|
|
inventoryLotLine.modifiedBy = "system" |
|
|
|
inventoryLotLineRepository.save(inventoryLotLine) |
|
|
|
inventoryLotLineRepository.saveAndFlush(inventoryLotLine) // 使用 saveAndFlush |
|
|
|
|
|
|
|
println("Miss item with partial pick: Updated lot ${lotId}") |
|
|
|
println(" - Added to outQty: ${actualPickQty} (${currentOutQty} -> ${newOutQty})") |
|
|
|
@@ -395,7 +470,33 @@ private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, ac |
|
|
|
updateInventoryUnavailableQty(itemId, missQty) |
|
|
|
|
|
|
|
// 修复5:更新 stock_out_line 状态为 rejected(因为还有 miss item) |
|
|
|
updateStockOutLineStatus(request, "rejected") |
|
|
|
// 修复:同时创建 stock_ledger 记录 |
|
|
|
val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( |
|
|
|
request.pickOrderLineId, |
|
|
|
request.lotId ?: 0L |
|
|
|
) |
|
|
|
|
|
|
|
stockOutLines.forEach { stockOutLine -> |
|
|
|
stockOutLine.status = "rejected" |
|
|
|
|
|
|
|
// 更新 qty 为 actualPickQty |
|
|
|
if (request.actualPickQty != null) { |
|
|
|
stockOutLine.qty = request.actualPickQty.toDouble() |
|
|
|
println("Updated stock out line ${stockOutLine.id} qty to: ${request.actualPickQty}") |
|
|
|
} |
|
|
|
|
|
|
|
stockOutLine.modified = LocalDateTime.now() |
|
|
|
stockOutLine.modifiedBy = "system" |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
println("Updated stock out line ${stockOutLine.id} status to: rejected") |
|
|
|
|
|
|
|
// 修复:为实际拣货的部分创建 stock_ledger 记录(即使状态是 rejected) |
|
|
|
// 因为这部分确实从库存中出库了 |
|
|
|
if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) { |
|
|
|
createStockLedgerForStockOut(savedStockOutLine, "Nor") // 实际拣货的部分 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 重新建议拣货批次(针对 miss 的数量) |
|
|
|
try { |
|
|
|
@@ -417,12 +518,12 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD |
|
|
|
val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) |
|
|
|
|
|
|
|
if (inventoryLotLine != null) { |
|
|
|
// 修复:Miss item 意味着剩余的所有物品都找不到 |
|
|
|
|
|
|
|
val currentInQty = inventoryLotLine.inQty ?: BigDecimal.ZERO |
|
|
|
val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO |
|
|
|
val remainingQty = currentInQty.minus(currentOutQty) |
|
|
|
|
|
|
|
// 标记批次为 unavailable |
|
|
|
|
|
|
|
inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE |
|
|
|
inventoryLotLine.modified = LocalDateTime.now() |
|
|
|
inventoryLotLine.modifiedBy = "system" |
|
|
|
@@ -434,16 +535,15 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD |
|
|
|
println(" - Unavailable qty (should be remaining qty): ${remainingQty}") |
|
|
|
} |
|
|
|
|
|
|
|
// 修复:更新 inventory 表的 unavailableQty |
|
|
|
// 应该是剩余的数量,而不是 missQty |
|
|
|
|
|
|
|
val currentInQty = inventoryLotLine?.inQty ?: BigDecimal.ZERO |
|
|
|
val currentOutQty = inventoryLotLine?.outQty ?: BigDecimal.ZERO |
|
|
|
val remainingQty = currentInQty.minus(currentOutQty) |
|
|
|
|
|
|
|
// 修复:只增加剩余数量,不要重复计算 missQty |
|
|
|
|
|
|
|
updateInventoryUnavailableQty(itemId, remainingQty) |
|
|
|
|
|
|
|
// 修复:更新 stock_out_line 状态为 rejected |
|
|
|
|
|
|
|
updateStockOutLineStatus(request, "rejected") |
|
|
|
|
|
|
|
// 重新建议拣货批次 |
|
|
|
@@ -555,11 +655,14 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty |
|
|
|
stockOutLine.qty = actualPickQtyDouble // 直接设置,不累积 |
|
|
|
stockOutLine.modified = LocalDateTime.now() |
|
|
|
stockOutLine.modifiedBy = "system" |
|
|
|
stockOutLineRepository.save(stockOutLine) |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) // 使用 saveAndFlush |
|
|
|
|
|
|
|
println("Updated stock out line ${stockOutLine.id}: status=${newStatus}, qty=${actualPickQtyDouble}") |
|
|
|
|
|
|
|
// 修复:为正常拣货创建 stock_ledger 记录 |
|
|
|
createStockLedgerForStockOut(savedStockOutLine, "Nor") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 修复:更新 inventory_lot_line 的 outQty |
|
|
|
val lotId = request.lotId ?: return |
|
|
|
val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) |
|
|
|
@@ -572,7 +675,7 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty |
|
|
|
inventoryLotLine.outQty = newOutQty |
|
|
|
inventoryLotLine.modified = LocalDateTime.now() |
|
|
|
inventoryLotLine.modifiedBy = "system" |
|
|
|
inventoryLotLineRepository.save(inventoryLotLine) |
|
|
|
inventoryLotLineRepository.saveAndFlush(inventoryLotLine) // 使用 saveAndFlush 确保触发器执行 |
|
|
|
|
|
|
|
println("Updated inventory lot line ${lotId} outQty: ${currentOutQty} -> ${newOutQty}") |
|
|
|
} |
|
|
|
@@ -801,22 +904,68 @@ open fun createBatchReleaseIssue( |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
open fun getMissItemList(issueCategory: String = "lot_issue"): List<PickExecutionIssue> { |
|
|
|
open fun getMissItemList(issueCategory: String = "lot_issue"): List<StockIssueResponse> { |
|
|
|
val category = try { |
|
|
|
IssueCategory.valueOf(issueCategory) |
|
|
|
} catch (e: Exception) { |
|
|
|
IssueCategory.lot_issue |
|
|
|
} |
|
|
|
return pickExecutionIssueRepository.findMissItemList(category).filter { it.handleStatus != HandleStatus.completed } |
|
|
|
return pickExecutionIssueRepository.findMissItemList(category) |
|
|
|
.filter { it.handleStatus != HandleStatus.completed } |
|
|
|
.map { issue -> |
|
|
|
StockIssueResponse( |
|
|
|
id = issue.id ?: 0L, |
|
|
|
itemId = issue.itemId, |
|
|
|
itemCode = issue.itemCode, |
|
|
|
itemDescription = issue.itemDescription, |
|
|
|
lotId = issue.lotId, |
|
|
|
lotNo = issue.lotNo, |
|
|
|
storeLocation = issue.storeLocation, |
|
|
|
requiredQty = issue.requiredQty, |
|
|
|
actualPickQty = issue.actualPickQty, |
|
|
|
missQty = issue.missQty, |
|
|
|
badItemQty = issue.badItemQty, |
|
|
|
bookQty = issue.bookQty ?: BigDecimal.ZERO, |
|
|
|
issueQty = issue.issueQty ?: BigDecimal.ZERO, // 返回计算的问题数量 |
|
|
|
issueRemark = issue.issueRemark, |
|
|
|
pickerName = issue.pickerName, |
|
|
|
handleStatus = issue.handleStatus.name, |
|
|
|
handleDate = issue.handleDate, |
|
|
|
handledBy = issue.handledBy |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
open fun getBadItemList(issueCategory: String = "lot_issue"): List<PickExecutionIssue> { |
|
|
|
open fun getBadItemList(issueCategory: String = "lot_issue"): List<StockIssueResponse> { |
|
|
|
val category = try { |
|
|
|
IssueCategory.valueOf(issueCategory) |
|
|
|
} catch (e: Exception) { |
|
|
|
IssueCategory.lot_issue |
|
|
|
} |
|
|
|
return pickExecutionIssueRepository.findBadItemListByCategory(category).filter { it.handleStatus != HandleStatus.completed } |
|
|
|
return pickExecutionIssueRepository.findBadItemListByCategory(category) |
|
|
|
.filter { it.handleStatus != HandleStatus.completed } |
|
|
|
.map { issue -> |
|
|
|
StockIssueResponse( |
|
|
|
id = issue.id ?: 0L, |
|
|
|
itemId = issue.itemId, |
|
|
|
itemCode = issue.itemCode, |
|
|
|
itemDescription = issue.itemDescription, |
|
|
|
lotId = issue.lotId, |
|
|
|
lotNo = issue.lotNo, |
|
|
|
storeLocation = issue.storeLocation, |
|
|
|
requiredQty = issue.requiredQty, |
|
|
|
actualPickQty = issue.actualPickQty, |
|
|
|
missQty = issue.missQty, |
|
|
|
badItemQty = issue.badItemQty, |
|
|
|
bookQty = issue.bookQty ?: BigDecimal.ZERO, |
|
|
|
issueQty = issue.issueQty ?: BigDecimal.ZERO, // 返回计算的问题数量 |
|
|
|
issueRemark = issue.issueRemark, |
|
|
|
pickerName = issue.pickerName, |
|
|
|
handleStatus = issue.handleStatus.name, |
|
|
|
handleDate = issue.handleDate, |
|
|
|
handledBy = issue.handledBy |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
open fun getBadItemOnlyList(): List<PickExecutionIssue> { |
|
|
|
return pickExecutionIssueRepository.findBadItemOnlyList(IssueCategory.lot_issue) |
|
|
|
@@ -856,13 +1005,15 @@ open fun submitMissItem(request: SubmitIssueRequest): MessageResponse { |
|
|
|
errorPosition = null |
|
|
|
) |
|
|
|
|
|
|
|
if (issue.missQty <= BigDecimal.ZERO || issue.deleted) { |
|
|
|
// 修改:使用 issueQty 而不是 missQty |
|
|
|
val issueQty = issue.issueQty ?: BigDecimal.ZERO |
|
|
|
if (issueQty <= BigDecimal.ZERO || issue.deleted) { |
|
|
|
return MessageResponse( |
|
|
|
id = null, |
|
|
|
name = "Error", |
|
|
|
code = "INVALID", |
|
|
|
type = "stock_issue", |
|
|
|
message = "Invalid issue or no miss quantity", |
|
|
|
message = "Invalid issue or no issue quantity", |
|
|
|
errorPosition = null |
|
|
|
) |
|
|
|
} |
|
|
|
@@ -888,22 +1039,26 @@ open fun submitMissItem(request: SubmitIssueRequest): MessageResponse { |
|
|
|
errorPosition = null |
|
|
|
) |
|
|
|
|
|
|
|
// 修改:使用 issueQty 创建 stock_out_line |
|
|
|
val stockOutLine = StockOutLine().apply { |
|
|
|
this.stockOut = stockOut |
|
|
|
this.inventoryLotLine = lotLine |
|
|
|
this.item = item |
|
|
|
this.qty = issue.missQty.toDouble() |
|
|
|
this.status = StockOutLineStatus.PENDING.status |
|
|
|
this.qty = issueQty.toDouble() // 使用 issueQty 而不是 missQty |
|
|
|
this.status = StockOutLineStatus.COMPLETE.status |
|
|
|
this.pickOrderLine = pickOrderLine |
|
|
|
this.type = "Miss" |
|
|
|
} |
|
|
|
stockOutLineRepository.save(stockOutLine) |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
// 修改:使用 issueQty 更新 inventory_lot_line |
|
|
|
if (issue.lotId != null) { |
|
|
|
updateLotLineAfterIssue(issue.lotId, issue.missQty) |
|
|
|
updateLotLineAfterIssue(issue.lotId, issueQty) // 使用 issueQty |
|
|
|
} |
|
|
|
|
|
|
|
markIssueHandled(issue, handler) |
|
|
|
|
|
|
|
// Create stock ledger entry |
|
|
|
createStockLedgerForStockOut(savedStockOutLine) |
|
|
|
return MessageResponse( |
|
|
|
id = stockOut.id, |
|
|
|
name = "Success", |
|
|
|
@@ -923,7 +1078,6 @@ open fun submitMissItem(request: SubmitIssueRequest): MessageResponse { |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Transactional(rollbackFor = [Exception::class]) |
|
|
|
open fun submitBadItem(request: SubmitIssueRequest): MessageResponse { |
|
|
|
try { |
|
|
|
@@ -937,13 +1091,15 @@ open fun submitBadItem(request: SubmitIssueRequest): MessageResponse { |
|
|
|
errorPosition = null |
|
|
|
) |
|
|
|
|
|
|
|
if (issue.badItemQty <= BigDecimal.ZERO || issue.deleted) { |
|
|
|
// 修改:使用 issueQty 而不是 badItemQty |
|
|
|
val issueQty = issue.issueQty ?: BigDecimal.ZERO |
|
|
|
if (issueQty <= BigDecimal.ZERO || issue.deleted) { |
|
|
|
return MessageResponse( |
|
|
|
id = null, |
|
|
|
name = "Error", |
|
|
|
code = "INVALID", |
|
|
|
type = "stock_issue", |
|
|
|
message = "Invalid issue or no bad item quantity", |
|
|
|
message = "Invalid issue or no issue quantity", |
|
|
|
errorPosition = null |
|
|
|
) |
|
|
|
} |
|
|
|
@@ -969,22 +1125,26 @@ open fun submitBadItem(request: SubmitIssueRequest): MessageResponse { |
|
|
|
errorPosition = null |
|
|
|
) |
|
|
|
|
|
|
|
// 修改:使用 issueQty 创建 stock_out_line |
|
|
|
val stockOutLine = StockOutLine().apply { |
|
|
|
this.stockOut = stockOut |
|
|
|
this.inventoryLotLine = lotLine |
|
|
|
this.item = item |
|
|
|
this.qty = issue.badItemQty.toDouble() |
|
|
|
this.status = StockOutLineStatus.PENDING.status |
|
|
|
this.qty = issueQty.toDouble() // 使用 issueQty 而不是 badItemQty |
|
|
|
this.status = StockOutLineStatus.COMPLETE.status |
|
|
|
this.pickOrderLine = pickOrderLine |
|
|
|
this.type = "Bad" |
|
|
|
} |
|
|
|
stockOutLineRepository.save(stockOutLine) |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
// 修改:使用 issueQty 更新 inventory_lot_line |
|
|
|
if (issue.lotId != null) { |
|
|
|
updateLotLineAfterIssue(issue.lotId, issue.badItemQty) |
|
|
|
updateLotLineAfterIssue(issue.lotId, issueQty) // 使用 issueQty |
|
|
|
} |
|
|
|
|
|
|
|
markIssueHandled(issue, handler) |
|
|
|
|
|
|
|
// Create stock ledger entry |
|
|
|
createStockLedgerForStockOut(savedStockOutLine) |
|
|
|
return MessageResponse( |
|
|
|
id = stockOut.id, |
|
|
|
name = "Success", |
|
|
|
@@ -1065,12 +1225,16 @@ open fun submitExpiryItem(request: SubmitExpiryRequest): MessageResponse { |
|
|
|
this.inventoryLotLine = lotLine |
|
|
|
this.item = item |
|
|
|
this.qty = remainingQty.toDouble() |
|
|
|
this.status = StockOutLineStatus.PENDING.status |
|
|
|
this.status = StockOutLineStatus.COMPLETE.status |
|
|
|
this.type = "Expiry" |
|
|
|
} |
|
|
|
stockOutLineRepository.save(stockOutLine) |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
updateLotLineAfterIssue(lotLine.id ?: 0L, remainingQty) |
|
|
|
|
|
|
|
|
|
|
|
updateLotLineAfterIssue(lotLine.id ?: 0L, remainingQty) |
|
|
|
// Create stock ledger entry |
|
|
|
createStockLedgerForStockOut(savedStockOutLine) |
|
|
|
return MessageResponse( |
|
|
|
id = stockOut.id, |
|
|
|
name = "Success", |
|
|
|
@@ -1091,12 +1255,12 @@ open fun submitExpiryItem(request: SubmitExpiryRequest): MessageResponse { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Fix batchSubmitMissItem method (around line 835): |
|
|
|
@Transactional(rollbackFor = [Exception::class]) |
|
|
|
open fun batchSubmitMissItem(request: BatchSubmitIssueRequest): MessageResponse { |
|
|
|
try { |
|
|
|
// 修改:检查 issueQty 而不是 missQty |
|
|
|
val issues = pickExecutionIssueRepository.findAllById(request.issueIds) |
|
|
|
.filter { it.missQty > BigDecimal.ZERO && !it.deleted } |
|
|
|
.filter { (it.issueQty ?: BigDecimal.ZERO) > BigDecimal.ZERO && !it.deleted } |
|
|
|
|
|
|
|
if (issues.isEmpty()) { |
|
|
|
return MessageResponse( |
|
|
|
@@ -1110,44 +1274,40 @@ open fun batchSubmitMissItem(request: BatchSubmitIssueRequest): MessageResponse |
|
|
|
} |
|
|
|
|
|
|
|
val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L |
|
|
|
|
|
|
|
// Create single StockOut header for all submissions |
|
|
|
val stockOut = createIssueStockOutHeader("MISS_ITEM", "Batch miss item submission", handler) |
|
|
|
|
|
|
|
issues.forEach { issue -> |
|
|
|
val issueQty = issue.issueQty ?: BigDecimal.ZERO |
|
|
|
|
|
|
|
// Get pickOrderLine if available |
|
|
|
val pickOrderLine = issue.pickOrderLineId?.let { |
|
|
|
pickOrderLineRepository.findById(it).orElse(null) |
|
|
|
} |
|
|
|
|
|
|
|
// Get inventoryLotLine |
|
|
|
val lotLine = issue.lotId?.let { |
|
|
|
inventoryLotLineRepository.findById(it).orElse(null) |
|
|
|
} |
|
|
|
|
|
|
|
// Get item from issue (it has itemId) |
|
|
|
val item = itemsRepository.findById(issue.itemId).orElse(null) |
|
|
|
|
|
|
|
|
|
|
|
// Create StockOutLine |
|
|
|
// 修改:使用 issueQty |
|
|
|
val stockOutLine = StockOutLine().apply { |
|
|
|
this.stockOut = stockOut |
|
|
|
this.inventoryLotLine = lotLine |
|
|
|
this.item = item |
|
|
|
this.qty = issue.missQty.toDouble() |
|
|
|
this.status = StockOutLineStatus.PENDING.status |
|
|
|
this.qty = issueQty.toDouble() // 使用 issueQty |
|
|
|
this.status = StockOutLineStatus.COMPLETE.status |
|
|
|
this.pickOrderLine = pickOrderLine |
|
|
|
this.type = "Miss" |
|
|
|
} |
|
|
|
stockOutLineRepository.save(stockOutLine) |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
// Update InventoryLotLine |
|
|
|
// 修改:使用 issueQty |
|
|
|
if (issue.lotId != null) { |
|
|
|
updateLotLineAfterIssue(issue.lotId, issue.missQty) |
|
|
|
updateLotLineAfterIssue(issue.lotId, issueQty) // 使用 issueQty |
|
|
|
} |
|
|
|
|
|
|
|
// Mark issue as handled |
|
|
|
markIssueHandled(issue, handler) |
|
|
|
createStockLedgerForStockOut(savedStockOutLine) |
|
|
|
} |
|
|
|
|
|
|
|
return MessageResponse( |
|
|
|
@@ -1170,12 +1330,12 @@ open fun batchSubmitMissItem(request: BatchSubmitIssueRequest): MessageResponse |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Fix batchSubmitBadItem method (around line 890) - same pattern: |
|
|
|
@Transactional(rollbackFor = [Exception::class]) |
|
|
|
open fun batchSubmitBadItem(request: BatchSubmitIssueRequest): MessageResponse { |
|
|
|
try { |
|
|
|
// 修改:检查 issueQty 而不是 badItemQty |
|
|
|
val issues = pickExecutionIssueRepository.findAllById(request.issueIds) |
|
|
|
.filter { it.badItemQty > BigDecimal.ZERO && !it.deleted } |
|
|
|
.filter { (it.issueQty ?: BigDecimal.ZERO) > BigDecimal.ZERO && !it.deleted } |
|
|
|
|
|
|
|
if (issues.isEmpty()) { |
|
|
|
return MessageResponse( |
|
|
|
@@ -1189,44 +1349,40 @@ open fun batchSubmitBadItem(request: BatchSubmitIssueRequest): MessageResponse { |
|
|
|
} |
|
|
|
|
|
|
|
val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L |
|
|
|
|
|
|
|
// Create single StockOut header for all submissions |
|
|
|
val stockOut = createIssueStockOutHeader("BAD_ITEM", "Batch bad item submission", handler) |
|
|
|
|
|
|
|
issues.forEach { issue -> |
|
|
|
val issueQty = issue.issueQty ?: BigDecimal.ZERO |
|
|
|
|
|
|
|
// Get pickOrderLine if available |
|
|
|
val pickOrderLine = issue.pickOrderLineId?.let { |
|
|
|
pickOrderLineRepository.findById(it).orElse(null) |
|
|
|
} |
|
|
|
|
|
|
|
// Get inventoryLotLine |
|
|
|
val lotLine = issue.lotId?.let { |
|
|
|
inventoryLotLineRepository.findById(it).orElse(null) |
|
|
|
} |
|
|
|
|
|
|
|
// Get item from issue |
|
|
|
val item = itemsRepository.findById(issue.itemId).orElse(null) |
|
|
|
|
|
|
|
|
|
|
|
// Create StockOutLine |
|
|
|
// 修改:使用 issueQty |
|
|
|
val stockOutLine = StockOutLine().apply { |
|
|
|
this.stockOut = stockOut |
|
|
|
this.inventoryLotLine = lotLine |
|
|
|
this.item = item |
|
|
|
this.qty = issue.badItemQty.toDouble() |
|
|
|
this.status = StockOutLineStatus.PENDING.status |
|
|
|
this.qty = issueQty.toDouble() // 使用 issueQty |
|
|
|
this.status = StockOutLineStatus.COMPLETE.status |
|
|
|
this.pickOrderLine = pickOrderLine |
|
|
|
this.type = "Bad" |
|
|
|
} |
|
|
|
stockOutLineRepository.save(stockOutLine) |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
// Update InventoryLotLine |
|
|
|
// 修改:使用 issueQty |
|
|
|
if (issue.lotId != null) { |
|
|
|
updateLotLineAfterIssue(issue.lotId, issue.badItemQty) |
|
|
|
updateLotLineAfterIssue(issue.lotId, issueQty) // 使用 issueQty |
|
|
|
} |
|
|
|
|
|
|
|
// Mark issue as handled |
|
|
|
markIssueHandled(issue, handler) |
|
|
|
createStockLedgerForStockOut(savedStockOutLine) |
|
|
|
} |
|
|
|
|
|
|
|
return MessageResponse( |
|
|
|
@@ -1248,7 +1404,6 @@ open fun batchSubmitBadItem(request: BatchSubmitIssueRequest): MessageResponse { |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Fix batchSubmitExpiryItem method (around line 945): |
|
|
|
@Transactional(rollbackFor = [Exception::class]) |
|
|
|
open fun batchSubmitExpiryItem(request: BatchSubmitExpiryRequest): MessageResponse { |
|
|
|
@@ -1289,12 +1444,17 @@ open fun batchSubmitExpiryItem(request: BatchSubmitExpiryRequest): MessageRespon |
|
|
|
this.inventoryLotLine = lotLine |
|
|
|
this.item = item |
|
|
|
this.qty = remainingQty.toDouble() |
|
|
|
this.status = StockOutLineStatus.PENDING.status |
|
|
|
this.status = StockOutLineStatus.COMPLETE.status |
|
|
|
this.type = "Expiry" |
|
|
|
} |
|
|
|
stockOutLineRepository.save(stockOutLine) |
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
// Create stock ledger entry |
|
|
|
|
|
|
|
|
|
|
|
// Update InventoryLotLine |
|
|
|
updateLotLineAfterIssue(lotLine.id ?: 0L, remainingQty) |
|
|
|
createStockLedgerForStockOut(savedStockOutLine) |
|
|
|
} |
|
|
|
|
|
|
|
return MessageResponse( |
|
|
|
@@ -1360,7 +1520,111 @@ private fun updateLotLineAfterIssue(lotLineId: Long, qty: BigDecimal) { |
|
|
|
|
|
|
|
lotLine.modified = LocalDateTime.now() |
|
|
|
lotLine.modifiedBy = "system" |
|
|
|
inventoryLotLineRepository.save(lotLine) |
|
|
|
// 修复:使用 saveAndFlush 确保立即提交到数据库,触发触发器 |
|
|
|
inventoryLotLineRepository.saveAndFlush(lotLine) |
|
|
|
updateInventoryAfterLotLineChange(lotLine) |
|
|
|
} |
|
|
|
} |
|
|
|
private fun updateInventoryAfterLotLineChange(lotLine: InventoryLotLine) { |
|
|
|
try { |
|
|
|
val item = lotLine.inventoryLot?.item ?: return |
|
|
|
val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return |
|
|
|
|
|
|
|
// 使用 SQL 查询计算所有相关 lot lines 的总和 |
|
|
|
val sql = """ |
|
|
|
SELECT |
|
|
|
SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0)) as totalOnHandQty, |
|
|
|
SUM(COALESCE(ill.holdQty, 0)) as totalOnHoldQty, |
|
|
|
SUM(CASE |
|
|
|
WHEN ill.status = 'unavailable' |
|
|
|
THEN COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0) |
|
|
|
ELSE 0 |
|
|
|
END) as totalUnavailableQty |
|
|
|
FROM inventory_lot_line ill |
|
|
|
INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId |
|
|
|
WHERE il.itemId = :itemId |
|
|
|
AND ill.deleted = 0 |
|
|
|
""".trimIndent() |
|
|
|
|
|
|
|
val result = jdbcDao.queryForMap(sql, mapOf("itemId" to item.id!!)).orElse(null) |
|
|
|
|
|
|
|
if (result != null) { |
|
|
|
val totalOnHandQty = (result["totalOnHandQty"] as? Number)?.let { |
|
|
|
BigDecimal(it.toString()) |
|
|
|
} ?: BigDecimal.ZERO |
|
|
|
|
|
|
|
val totalOnHoldQty = (result["totalOnHoldQty"] as? Number)?.let { |
|
|
|
BigDecimal(it.toString()) |
|
|
|
} ?: BigDecimal.ZERO |
|
|
|
|
|
|
|
val totalUnavailableQty = (result["totalUnavailableQty"] as? Number)?.let { |
|
|
|
BigDecimal(it.toString()) |
|
|
|
} ?: BigDecimal.ZERO |
|
|
|
|
|
|
|
// 更新 inventory |
|
|
|
inventory.onHandQty = totalOnHandQty |
|
|
|
inventory.onHoldQty = totalOnHoldQty |
|
|
|
inventory.unavailableQty = totalUnavailableQty |
|
|
|
inventory.status = if (totalOnHandQty.subtract(totalOnHoldQty).subtract(totalUnavailableQty) > BigDecimal.ZERO) { |
|
|
|
"available" |
|
|
|
} else { |
|
|
|
"unavailable" |
|
|
|
} |
|
|
|
inventory.modified = LocalDateTime.now() |
|
|
|
inventory.modifiedBy = "system" |
|
|
|
|
|
|
|
inventoryRepository.saveAndFlush(inventory) |
|
|
|
|
|
|
|
println("=== MANUALLY UPDATED INVENTORY ===") |
|
|
|
println("Item ID: ${item.id}") |
|
|
|
println("Total OnHandQty: ${totalOnHandQty}") |
|
|
|
println("Total OnHoldQty: ${totalOnHoldQty}") |
|
|
|
println("Total UnavailableQty: ${totalUnavailableQty}") |
|
|
|
println("Status: ${inventory.status}") |
|
|
|
println("==================================") |
|
|
|
} |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
println("Error updating inventory manually: ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
} |
|
|
|
} |
|
|
|
private fun createStockLedgerForStockOut(stockOutLine: StockOutLine, ledgerType: String? = null) { |
|
|
|
val item = stockOutLine.item ?: return |
|
|
|
|
|
|
|
val outQty = stockOutLine.qty?.toDouble() ?: 0.0 |
|
|
|
|
|
|
|
// 修复:重新查询 inventory 以获取触发器更新后的最新 onHandQty |
|
|
|
val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return |
|
|
|
|
|
|
|
// 计算新的 balance = 当前 onHandQty - 本次出库数量 |
|
|
|
val currentOnHandQty = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble() |
|
|
|
val newBalance = currentOnHandQty - outQty |
|
|
|
|
|
|
|
// 确保 balance 不为负数 |
|
|
|
val finalBalance = if (newBalance < 0) 0.0 else newBalance |
|
|
|
|
|
|
|
// 使用传入的 ledgerType,如果没有则使用 stockOutLine.type |
|
|
|
val ledgerTypeToUse = ledgerType ?: stockOutLine.type ?: "Nor" |
|
|
|
|
|
|
|
val stockLedger = StockLedger().apply { |
|
|
|
this.stockOutLine = stockOutLine |
|
|
|
this.inventory = inventory |
|
|
|
this.inQty = null |
|
|
|
this.outQty = outQty |
|
|
|
this.balance = finalBalance |
|
|
|
this.type = ledgerTypeToUse // 使用指定的 type |
|
|
|
this.itemId = item.id |
|
|
|
this.itemCode = item.code |
|
|
|
this.date = LocalDate.now() |
|
|
|
} |
|
|
|
|
|
|
|
stockLedgerRepository.saveAndFlush(stockLedger) |
|
|
|
|
|
|
|
println("=== CREATED STOCK LEDGER ===") |
|
|
|
println("Type: ${ledgerTypeToUse}") |
|
|
|
println("OutQty: ${outQty}") |
|
|
|
println("Balance: ${finalBalance}") |
|
|
|
println("===========================") |
|
|
|
} |
|
|
|
} |