|
|
@@ -31,6 +31,7 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord |
|
|
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord |
|
|
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord |
|
|
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus |
|
|
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus |
|
|
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository |
|
|
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository |
|
|
|
|
|
|
|
|
import com.ffii.fpsms.modules.common.CodeGenerator |
|
|
import com.ffii.fpsms.modules.common.CodeGenerator |
|
|
import org.springframework.context.annotation.Lazy |
|
|
import org.springframework.context.annotation.Lazy |
|
|
|
|
|
|
|
|
@@ -46,6 +47,7 @@ open class StockOutLineService( |
|
|
private val pickOrderRepository: PickOrderRepository, |
|
|
private val pickOrderRepository: PickOrderRepository, |
|
|
private val inventoryLotLineRepository: InventoryLotLineRepository, |
|
|
private val inventoryLotLineRepository: InventoryLotLineRepository, |
|
|
@Lazy private val suggestedPickLotService: SuggestedPickLotService, |
|
|
@Lazy private val suggestedPickLotService: SuggestedPickLotService, |
|
|
|
|
|
private val inventoryLotRepository: InventoryLotRepository, |
|
|
private val doPickOrderRepository: DoPickOrderRepository, |
|
|
private val doPickOrderRepository: DoPickOrderRepository, |
|
|
private val doPickOrderRecordRepository: DoPickOrderRecordRepository, |
|
|
private val doPickOrderRecordRepository: DoPickOrderRecordRepository, |
|
|
private val deliveryOrderRepository: DeliveryOrderRepository, |
|
|
private val deliveryOrderRepository: DeliveryOrderRepository, |
|
|
@@ -665,4 +667,257 @@ private fun updateInventoryTableAfterLotRejection(inventoryLotLine: InventoryLot |
|
|
e.printStackTrace() |
|
|
e.printStackTrace() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional(rollbackFor = [Exception::class]) |
|
|
|
|
|
open fun batchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { |
|
|
|
|
|
val startTime = System.currentTimeMillis() |
|
|
|
|
|
println(" Start time: ${java.time.LocalDateTime.now()}") |
|
|
|
|
|
if (request.lines.isEmpty()) { |
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = null, |
|
|
|
|
|
name = "No lines", |
|
|
|
|
|
code = "EMPTY", |
|
|
|
|
|
type = "batch_submit", |
|
|
|
|
|
message = "No scanned lines", |
|
|
|
|
|
errorPosition = null |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val errors = mutableListOf<String>() |
|
|
|
|
|
val processedIds = mutableListOf<Long>() |
|
|
|
|
|
|
|
|
|
|
|
request.lines.forEach { line -> |
|
|
|
|
|
val lineStartTime = System.currentTimeMillis() |
|
|
|
|
|
try { |
|
|
|
|
|
// 1) noLot 情况:等价于前端 handleSubmitAllScanned 里 noLot 分支 |
|
|
|
|
|
if (line.noLot) { |
|
|
|
|
|
val req = UpdateStockOutLineStatusRequest( |
|
|
|
|
|
id = line.stockOutLineId, |
|
|
|
|
|
status = "completed", |
|
|
|
|
|
qty = 0.0 |
|
|
|
|
|
) |
|
|
|
|
|
updateStatus(req) // 直接复用已有逻辑(会自动更新行/订单状态) |
|
|
|
|
|
processedIds += line.stockOutLineId |
|
|
|
|
|
// 如果需要,这里可以再调记录 issue 的 service,而不是让前端调 |
|
|
|
|
|
return@forEach |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2) 正常有 lot:照抄你前端 handleSubmitAllScanned 的计算逻辑 |
|
|
|
|
|
val required = line.requiredQty ?: BigDecimal.ZERO |
|
|
|
|
|
val currentActual = line.actualPickQty ?: BigDecimal.ZERO |
|
|
|
|
|
val submitQty = if (line.requiredQty != null) line.requiredQty else line.requiredQty |
|
|
|
|
|
val submitQtyNotNull = submitQty ?: BigDecimal.ZERO |
|
|
|
|
|
val cumulative = currentActual + submitQtyNotNull |
|
|
|
|
|
|
|
|
|
|
|
var newStatus = "partially_completed" |
|
|
|
|
|
if (cumulative >= required) { |
|
|
|
|
|
newStatus = "completed" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 注意:updateStatus 是「在原 qty 上 + qty」,所以这里传的是「本次增量」而不是 cumulative |
|
|
|
|
|
val updateReq = UpdateStockOutLineStatusRequest( |
|
|
|
|
|
id = line.stockOutLineId, |
|
|
|
|
|
status = newStatus, |
|
|
|
|
|
qty = submitQtyNotNull.toDouble() |
|
|
|
|
|
) |
|
|
|
|
|
updateStatus(updateReq) // 内部已经会做 pickOrderLine / pickOrder / DO 完成检查 |
|
|
|
|
|
|
|
|
|
|
|
// 3) 扣库存:等价于前端 updateInventoryLotLineQuantities |
|
|
|
|
|
if (submitQtyNotNull > BigDecimal.ZERO && line.inventoryLotLineId != null) { |
|
|
|
|
|
val lotLine = inventoryLotLineRepository.findById(line.inventoryLotLineId).orElseThrow() |
|
|
|
|
|
val zero = BigDecimal.ZERO |
|
|
|
|
|
val one = BigDecimal.ONE |
|
|
|
|
|
val salesUnit = lotLine.inventoryLot?.item?.id?.let { itemId -> |
|
|
|
|
|
itemUomRespository.findByItemIdAndSalesUnitIsTrueAndDeletedIsFalse(itemId) |
|
|
|
|
|
} |
|
|
|
|
|
val ratio = 1.0 //(salesUnit?.ratioN ?: zero).divide(salesUnit?.ratioD ?: one).toDouble() |
|
|
|
|
|
val baseQty = (submitQtyNotNull.toDouble() / ratio).toBigDecimal() |
|
|
|
|
|
|
|
|
|
|
|
// 更新 lot line |
|
|
|
|
|
lotLine.apply { |
|
|
|
|
|
this.outQty = (this.outQty ?: zero) + baseQty |
|
|
|
|
|
this.holdQty = (this.holdQty ?: zero) - baseQty |
|
|
|
|
|
} |
|
|
|
|
|
inventoryLotLineRepository.save(lotLine) |
|
|
|
|
|
|
|
|
|
|
|
// 更新 inventory |
|
|
|
|
|
val inv = inventoryRepository.findByItemId(lotLine.inventoryLot?.item?.id!!).orElseThrow() |
|
|
|
|
|
inv.apply { |
|
|
|
|
|
this.onHandQty = (this.onHandQty ?: zero) - baseQty |
|
|
|
|
|
this.onHoldQty = (this.onHoldQty ?: zero) - baseQty |
|
|
|
|
|
} |
|
|
|
|
|
inventoryRepository.save(inv) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
processedIds += line.stockOutLineId |
|
|
|
|
|
val lineTime = System.currentTimeMillis() - lineStartTime |
|
|
|
|
|
// 4) 如果你还想保留「按 consoCode 再单独 check-complete」的接口,也可以在这里再调一次: |
|
|
|
|
|
// if (newStatus == "completed" && line.pickOrderConsoCode != null) { |
|
|
|
|
|
// pickOrderService.checkAndCompletePickOrderByConsoCode(line.pickOrderConsoCode) |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
errors += "stockOutLineId=${line.stockOutLineId}: ${e.message}" |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val msg = if (errors.isEmpty()) { |
|
|
|
|
|
"Batch submit success (${processedIds.size} lines)." |
|
|
|
|
|
} else { |
|
|
|
|
|
"Batch submit partial success (${processedIds.size} lines), errors: ${errors.joinToString("; ")}" |
|
|
|
|
|
} |
|
|
|
|
|
val totalTime = System.currentTimeMillis() - startTime |
|
|
|
|
|
println(" Processed: ${processedIds.size}/${request.lines.size} items") |
|
|
|
|
|
println(" Total time: ${totalTime}ms (${totalTime / 1000.0}s)") |
|
|
|
|
|
println(" Average time per item: ${if (processedIds.isNotEmpty()) totalTime / processedIds.size else 0}ms") |
|
|
|
|
|
println(" End time: ${java.time.LocalDateTime.now()}") |
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = null, |
|
|
|
|
|
name = "batch_submit", |
|
|
|
|
|
code = if (errors.isEmpty()) "SUCCESS" else "PARTIAL_SUCCESS", |
|
|
|
|
|
type = "batch_submit", |
|
|
|
|
|
message = msg, |
|
|
|
|
|
errorPosition = null, |
|
|
|
|
|
entity = mapOf( |
|
|
|
|
|
"processedIds" to processedIds, |
|
|
|
|
|
"errors" to errors |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Transactional |
|
|
|
|
|
open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineStatusByQRCodeAndLotNoRequest): MessageResponse { |
|
|
|
|
|
val startTime = System.currentTimeMillis() |
|
|
|
|
|
try { |
|
|
|
|
|
println("=== QR SCAN REQUEST RECEIVED ===") |
|
|
|
|
|
println(" Request details:") |
|
|
|
|
|
println(" - stockOutLineId: ${request.stockOutLineId}") |
|
|
|
|
|
println(" - pickOrderLineId: ${request.pickOrderLineId}") |
|
|
|
|
|
println(" - inventoryLotNo: ${request.inventoryLotNo}") |
|
|
|
|
|
println(" - itemId: ${request.itemId}") |
|
|
|
|
|
println(" - status: ${request.status}") |
|
|
|
|
|
|
|
|
|
|
|
val stockOutLine = stockOutLineRepository.findById(request.stockOutLineId).orElseThrow { |
|
|
|
|
|
println(" StockOutLine not found with ID: ${request.stockOutLineId}") |
|
|
|
|
|
IllegalArgumentException("StockOutLine not found with ID: ${request.stockOutLineId}") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println(" Found StockOutLine:") |
|
|
|
|
|
println(" - ID: ${stockOutLine.id}") |
|
|
|
|
|
println(" - Current status: ${stockOutLine.status}") |
|
|
|
|
|
println(" - Qty: ${stockOutLine.qty}") |
|
|
|
|
|
println(" - PickOrderLine ID: ${stockOutLine.pickOrderLine?.id}") |
|
|
|
|
|
println(" - Item ID: ${stockOutLine.item?.id}") |
|
|
|
|
|
println(" - InventoryLotLine ID: ${stockOutLine.inventoryLotLine?.id}") |
|
|
|
|
|
|
|
|
|
|
|
// 修复:从 stockOutLine.inventoryLotLine 获取 inventoryLot,而不是使用错误的参数 |
|
|
|
|
|
val inventoryLotLine = stockOutLine.inventoryLotLine |
|
|
|
|
|
if (inventoryLotLine == null) { |
|
|
|
|
|
println(" StockOutLine has no associated InventoryLotLine") |
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = null, |
|
|
|
|
|
name = "No inventory lot line", |
|
|
|
|
|
code = "NO_INVENTORY_LOT_LINE", |
|
|
|
|
|
type = "error", |
|
|
|
|
|
message = "StockOutLine ${request.stockOutLineId} has no associated InventoryLotLine", |
|
|
|
|
|
errorPosition = null |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val inventoryLot = inventoryLotLine.inventoryLot |
|
|
|
|
|
if (inventoryLot == null) { |
|
|
|
|
|
println(" InventoryLotLine has no associated InventoryLot") |
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = null, |
|
|
|
|
|
name = "No inventory lot", |
|
|
|
|
|
code = "NO_INVENTORY_LOT", |
|
|
|
|
|
type = "error", |
|
|
|
|
|
message = "InventoryLotLine ${inventoryLotLine.id} has no associated InventoryLot", |
|
|
|
|
|
errorPosition = null |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println("🔍 Checking inventory lot:") |
|
|
|
|
|
println(" - Found inventory lot:") |
|
|
|
|
|
println(" - Lot No: ${inventoryLot.lotNo}") |
|
|
|
|
|
println(" - Item ID: ${inventoryLot.item?.id}") |
|
|
|
|
|
println(" - InventoryLot ID: ${inventoryLot.id}") |
|
|
|
|
|
|
|
|
|
|
|
// 修复:比较逻辑 |
|
|
|
|
|
val lotNoMatch = inventoryLot.lotNo == request.inventoryLotNo |
|
|
|
|
|
val itemIdMatch = inventoryLot.item?.id == request.itemId |
|
|
|
|
|
|
|
|
|
|
|
println(" Matching results:") |
|
|
|
|
|
println(" - Lot No match: $lotNoMatch (Expected: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo})") |
|
|
|
|
|
println(" - Item ID match: $itemIdMatch (Expected: ${inventoryLot.item?.id}, Got: ${request.itemId})") |
|
|
|
|
|
|
|
|
|
|
|
if (lotNoMatch && itemIdMatch) { |
|
|
|
|
|
// 匹配成功,更新状态 |
|
|
|
|
|
println(" MATCH SUCCESS: Lot and Item both match!") |
|
|
|
|
|
|
|
|
|
|
|
stockOutLine.status = request.status |
|
|
|
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
|
|
|
|
println(" Status updated successfully:") |
|
|
|
|
|
println(" - New status: ${savedStockOutLine.status}") |
|
|
|
|
|
println(" - StockOutLine ID: ${savedStockOutLine.id}") |
|
|
|
|
|
|
|
|
|
|
|
val mappedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) |
|
|
|
|
|
|
|
|
|
|
|
println("=== QR SCAN COMPLETED SUCCESSFULLY ===") |
|
|
|
|
|
|
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = savedStockOutLine.id, |
|
|
|
|
|
name = inventoryLot.lotNo, |
|
|
|
|
|
code = "checked", // 修复:返回操作结果,而不是 consoPickOrderCode |
|
|
|
|
|
type = request.status, |
|
|
|
|
|
message = "Stock out line status updated successfully", |
|
|
|
|
|
errorPosition = null, |
|
|
|
|
|
entity = mappedStockOutLine |
|
|
|
|
|
) |
|
|
|
|
|
} else if (!lotNoMatch && itemIdMatch) { |
|
|
|
|
|
// Item 匹配但 lotNo 不匹配 |
|
|
|
|
|
println(" LOT NUMBER MISMATCH:") |
|
|
|
|
|
println(" - Expected lotNo: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}") |
|
|
|
|
|
println(" - Item ID matches: ${inventoryLot.item?.id} == ${request.itemId}") |
|
|
|
|
|
|
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = null, |
|
|
|
|
|
name = "Item match, lot number mismatch", |
|
|
|
|
|
code = "LOT_NUMBER_MISMATCH", |
|
|
|
|
|
type = "lot_not_found", |
|
|
|
|
|
message = "Item match, lot number mismatch. Expected: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}", |
|
|
|
|
|
errorPosition = null |
|
|
|
|
|
) |
|
|
|
|
|
} else { |
|
|
|
|
|
// Item 不匹配 |
|
|
|
|
|
println(" ITEM MISMATCH:") |
|
|
|
|
|
println(" - Expected itemId: ${inventoryLot.item?.id}, Got: ${request.itemId}") |
|
|
|
|
|
println(" - Expected lotNo: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}") |
|
|
|
|
|
|
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = null, |
|
|
|
|
|
name = "Item mismatch", |
|
|
|
|
|
code = "ITEM_MISMATCH", |
|
|
|
|
|
type = "item_not_found", |
|
|
|
|
|
message = "Item mismatch. Expected: ${inventoryLot.item?.id}, Got: ${request.itemId}", |
|
|
|
|
|
errorPosition = null |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
println(" ERROR updating stock out line status by QR: ${e.message}") |
|
|
|
|
|
println(" Exception type: ${e.javaClass.simpleName}") |
|
|
|
|
|
println(" Stack trace:") |
|
|
|
|
|
e.printStackTrace() |
|
|
|
|
|
return MessageResponse( |
|
|
|
|
|
id = null, |
|
|
|
|
|
name = "Error", |
|
|
|
|
|
code = "ERROR", |
|
|
|
|
|
type = "error", |
|
|
|
|
|
message = "Error updating stock out line: ${e.message}", |
|
|
|
|
|
errorPosition = null |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |