From 18a90db24c67ddaa53ac785f330435b3f345e60c Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 8 Dec 2025 15:53:48 +0800 Subject: [PATCH] updateSubmitLotConfirm --- .../pickOrder/service/PickOrderService.kt | 95 +++++------ .../models/LotSubstitutionConfirmRequest.kt | 2 +- .../service/ProductProcessService.kt | 3 +- .../entity/InventoryLotLineRepository.kt | 9 + .../stock/entity/InventoryLotRepository.kt | 7 + .../stock/service/StockOutLineService.kt | 159 +++++++++++++++++- .../stock/web/StockOutLineController.kt | 19 ++- .../stock/web/model/SaveStockOutRequest.kt | 15 +- 8 files changed, 238 insertions(+), 71 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index c4c49fa..989f86a 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -4493,75 +4493,68 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto @Transactional(rollbackFor = [java.lang.Exception::class]) open fun confirmLotSubstitution(req: LotSubstitutionConfirmRequest): MessageResponse { val zero = BigDecimal.ZERO - - // Validate entities + + // Validate pick order line val pol = req.pickOrderLineId.let { pickOrderLineRepository.findById(it).orElse(null) } ?: return MessageResponse( id = null, name = "Pick order line not found", code = "ERROR", type = "pickorder", message = "Pick order line ${req.pickOrderLineId} not found", errorPosition = null ) - - val newIll = req.newInventoryLotLineId.let { inventoryLotLineRepository.findById(it).orElse(null) } + + val polItemId = pol.item?.id + if (polItemId == null) { + return MessageResponse( + id = null, name = "Item not found", code = "ERROR", type = "pickorder", + message = "Pick order line item is null", errorPosition = null + ) + } + + // ✅ 根据 lotNo 和 itemId 查找新的 InventoryLotLine + val newIll = inventoryLotLineRepository.findByLotNoAndItemId(req.newInventoryLotNo, polItemId) ?: return MessageResponse( id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", - message = "Inventory lot line ${req.newInventoryLotLineId} not found", errorPosition = null + message = "Inventory lot line with lotNo '${req.newInventoryLotNo}' and itemId ${polItemId} not found", + errorPosition = null ) - - // Item consistency check - val polItemId = pol.item?.id + + // Item consistency check (应该已经通过上面的查询保证了,但再次确认) val newItemId = newIll.inventoryLot?.item?.id - if (polItemId == null || newItemId == null || polItemId != newItemId) { + if (newItemId == null || polItemId != newItemId) { return MessageResponse( id = null, name = "Item mismatch", code = "ERROR", type = "pickorder", message = "New lot line item does not match pick order line item", errorPosition = null ) } - + + val newIllId = newIll.id ?: return MessageResponse( + id = null, name = "Invalid lot line", code = "ERROR", type = "pickorder", + message = "New inventory lot line has no ID", errorPosition = null + ) + // 1) Update suggested pick lot (if provided): move holdQty from old ILL to new ILL and re-point the suggestion if (req.originalSuggestedPickLotId != null && req.originalSuggestedPickLotId > 0) { - // Get current suggested ILL id and qty - val row = jdbcDao.queryForMap( - """ - SELECT spl.suggestedLotLineId AS oldIllId, COALESCE(spl.qty,0) AS qty - FROM suggested_pick_lot spl - WHERE spl.id = :splId - """.trimIndent(), mapOf("splId" to req.originalSuggestedPickLotId) - ).orElse(null) - - if (row != null) { - val oldIllId = (row["oldIllId"] as Number?)?.toLong() - val qty = when (val qtyObj = row["qty"]) { - is BigDecimal -> qtyObj - is Number -> qtyObj.toDouble().toBigDecimal() - is String -> qtyObj.toBigDecimalOrNull() ?: zero - else -> zero - } - - if (oldIllId != null && oldIllId != req.newInventoryLotLineId) { + // ✅ 使用 repository 而不是 SQL + val originalSpl = suggestPickLotRepository.findById(req.originalSuggestedPickLotId).orElse(null) + + if (originalSpl != null) { + val oldIll = originalSpl.suggestedLotLine + val qty = originalSpl.qty ?: zero + + if (oldIll != null && oldIll.id != newIllId) { // Decrease hold on old, increase on new - val oldIll = inventoryLotLineRepository.findById(oldIllId).orElse(null) - if (oldIll != null) { - oldIll.holdQty = (oldIll.holdQty ?: zero).minus(qty).max(zero) - inventoryLotLineRepository.save(oldIll) - } - val newIllEntity = inventoryLotLineRepository.findById(req.newInventoryLotLineId).orElse(null) - if (newIllEntity != null) { - newIllEntity.holdQty = (newIllEntity.holdQty ?: zero).plus(qty) - inventoryLotLineRepository.save(newIllEntity) - } + oldIll.holdQty = (oldIll.holdQty ?: zero).minus(qty).max(zero) + inventoryLotLineRepository.save(oldIll) + + newIll.holdQty = (newIll.holdQty ?: zero).plus(qty) + inventoryLotLineRepository.save(newIll) } - - // Re-point suggestion to new ILL - jdbcDao.executeUpdate( - """ - UPDATE suggested_pick_lot - SET suggestedLotLineId = :newIllId - WHERE id = :splId - """.trimIndent(), mapOf("newIllId" to req.newInventoryLotLineId, "splId" to req.originalSuggestedPickLotId) - ) + + // ✅ 使用 repository 更新 suggestion + originalSpl.suggestedLotLine = newIll + suggestPickLotRepository.save(originalSpl) } } - + // 2) Update stock out line (if provided): re-point to new ILL; keep qty and status unchanged if (req.stockOutLineId != null && req.stockOutLineId > 0) { val sol = stockOutLIneRepository.findById(req.stockOutLineId).orElse(null) @@ -4571,13 +4564,13 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto stockOutLIneRepository.save(sol) } } - + return MessageResponse( id = null, name = "Lot substitution confirmed", code = "SUCCESS", type = "pickorder", - message = "Updated suggestion and stock out line to new lot line ${req.newInventoryLotLineId}", + message = "Updated suggestion and stock out line to new lot line with lotNo '${req.newInventoryLotNo}'", errorPosition = null ) } diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/LotSubstitutionConfirmRequest.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/LotSubstitutionConfirmRequest.kt index 9432cad..8ba04d7 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/LotSubstitutionConfirmRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/LotSubstitutionConfirmRequest.kt @@ -5,5 +5,5 @@ data class LotSubstitutionConfirmRequest( val pickOrderLineId: Long, val stockOutLineId: Long?, // optional val originalSuggestedPickLotId: Long?, // optional - val newInventoryLotLineId: Long + val newInventoryLotNo: String ) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt index 2f4a4e8..cb463b1 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt @@ -579,7 +579,8 @@ open class ProductProcessService( scrapRate = bom?.scrapRate?:-1, allergicSubstance = calculateAllergicSubstanceScore(bom?.allergicSubstances), outputQtyUom = bom?.outputQtyUom?:"", - outputQty = bom?.outputQty?.toInt()?:0, + //outputQty = bom?.outputQty?.toInt()?:0, + outputQty = jobOrder?.reqQty?.toInt()?:0, productProcessCode = process.productProcessCode?:"", status = process.status?:ProductProcessStatus.PENDING, startTime = process.startTime?:LocalDateTime.now(), diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt index 90fb34f..c26c454 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt @@ -43,4 +43,13 @@ interface InventoryLotLineRepository : AbstractRepository fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: InventoryLotLineStatus): List + @Query("SELECT ill FROM InventoryLotLine ill WHERE ill.inventoryLot.item.id IN :itemIds") + fun findAllByItemIdIn(@Param("itemIds") itemIds: List): List + @Query(""" + SELECT ill FROM InventoryLotLine ill + WHERE ill.inventoryLot.lotNo = :lotNo + AND ill.inventoryLot.item.id = :itemId + AND ill.deleted = false + """) + fun findByLotNoAndItemId(lotNo: String, itemId: Long): InventoryLotLine? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt index 12e5927..4dcfb55 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt @@ -16,4 +16,11 @@ interface InventoryLotRepository: AbstractRepository { """) fun findLatestLotNoByPrefix(prefix: String): String? fun findAllByIdIn(ids: List): List + @Query(""" + SELECT il FROM InventoryLot il + WHERE il.lotNo = :lotNo + AND il.item.id = :itemId + AND il.deleted = false + """) + fun findByLotNoAndItemId(lotNo: String, itemId: Long): InventoryLot? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt index 709d378..f0fc852 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt @@ -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.enums.DeliveryOrderStatus import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository +import com.ffii.fpsms.modules.stock.web.model.UpdateStockOutLineStatusByQRCodeAndLotNoRequest import com.ffii.fpsms.modules.common.CodeGenerator import org.springframework.context.annotation.Lazy @@ -53,6 +54,7 @@ private val doPickOrderRecordRepository: DoPickOrderRecordRepository, private val deliveryOrderRepository: DeliveryOrderRepository, private val doPickOrderLineRepository: DoPickOrderLineRepository, private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository, +private val inventoryLotLineService: InventoryLotLineService ): AbstractBaseEntityService(jdbcDao, stockOutLineRepository) { @Throws(IOException::class) @Transactional @@ -686,7 +688,7 @@ open fun batchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { val errors = mutableListOf() val processedIds = mutableListOf() - request.lines.forEach { line -> + request.lines.forEach { line:QrPickSubmitLineRequest -> val lineStartTime = System.currentTimeMillis() try { // 1) noLot 情况:等价于前端 handleSubmitAllScanned 里 noLot 分支 @@ -920,4 +922,157 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta ) } } -} \ No newline at end of file + +@Transactional(rollbackFor = [Exception::class]) +open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { + val startTime = System.currentTimeMillis() + println("=== BATCH SUBMIT START ===") + println("Start time: ${java.time.LocalDateTime.now()}") + println("Request lines count: ${request.lines.size}") + + 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() + val processedIds = mutableListOf() + + try { + // 1) Bulk load all lot lines and inventories + val lotLineIds = request.lines.mapNotNull { it.inventoryLotLineId } + println("Loading ${lotLineIds.size} lot lines...") + val lotLines = if (lotLineIds.isNotEmpty()) { + inventoryLotLineRepository.findAllById(lotLineIds).associateBy { it.id } + } else { + emptyMap() + } + + val itemIds = lotLines.values.mapNotNull { it.inventoryLot?.item?.id } + println("Loading ${itemIds.size} inventories...") + val inventories = itemIds.mapNotNull { itemId -> + inventoryRepository.findByItemId(itemId).orElse(null) + }.associateBy { it.item?.id } + + // 2) Bulk load all stock out lines to get current quantities + val stockOutLineIds = request.lines.map { it.stockOutLineId } + println("Loading ${stockOutLineIds.size} stock out lines...") + val stockOutLines = stockOutLineRepository.findAllById(stockOutLineIds).associateBy { it.id } + + // 3) Process each request line + request.lines.forEach { line: QrPickSubmitLineRequest -> + try { + println("Processing line: stockOutLineId=${line.stockOutLineId}, noLot=${line.noLot}") + + if (line.noLot) { + // noLot branch + updateStatus(UpdateStockOutLineStatusRequest( + id = line.stockOutLineId, + status = "completed", + qty = 0.0 + )) + processedIds += line.stockOutLineId + println(" ✓ noLot item processed") + return@forEach + } + + // 修复:从数据库获取当前实际数量 + val stockOutLine = stockOutLines[line.stockOutLineId] + ?: throw IllegalStateException("StockOutLine ${line.stockOutLineId} not found") + + // 修复:qty 是 Double?,需要转换为 BigDecimal + val currentActual = (stockOutLine.qty ?: 0.0).toBigDecimal() + val targetActual = line.actualPickQty ?: BigDecimal.ZERO + val required = line.requiredQty ?: BigDecimal.ZERO + + println(" Current qty: $currentActual, Target qty: $targetActual, Required: $required") + + // 修复:计算增量(前端发送的 actualPickQty 是目标累计值) + val submitQty = targetActual - currentActual + + println(" Submit qty (increment): $submitQty") + + // 修复:使用前端发送的状态,而不是重新计算 + val newStatus = line.stockOutLineStatus ?: if (targetActual >= required) "completed" else "partially_completed" + + // 修复:updateStatus 期望增量,所以传入 submitQty + updateStatus(UpdateStockOutLineStatusRequest( + id = line.stockOutLineId, + status = newStatus, + qty = submitQty.toDouble() + )) + + // Inventory updates - 修复:使用增量数量 + if (submitQty > BigDecimal.ZERO && line.inventoryLotLineId != null) { + println(" Updating inventory lot line ${line.inventoryLotLineId} with qty $submitQty") + inventoryLotLineService.updateInventoryLotLineQuantities( + UpdateInventoryLotLineQuantitiesRequest( + inventoryLotLineId = line.inventoryLotLineId, + qty = submitQty, + operation = "pick" + ) + ) + } + + processedIds += line.stockOutLineId + println(" ✓ Line processed successfully") + } catch (e: Exception) { + println(" ✗ Error processing line ${line.stockOutLineId}: ${e.message}") + e.printStackTrace() + errors += "stockOutLineId=${line.stockOutLineId}: ${e.message}" + } + } + + // 4) 移除:不需要保存 lotLines 和 inventories,因为它们没有被修改 + // inventoryLotLineRepository.saveAll(lotLines.values.toList()) + // inventoryRepository.saveAll(inventories.values.toList()) + + 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()}") + println("=== BATCH SUBMIT END ===") + + 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 + ) + ) + } catch (e: Exception) { + println("=== BATCH SUBMIT ERROR ===") + println("Error: ${e.message}") + e.printStackTrace() + return MessageResponse( + id = null, + name = "batch_submit", + code = "ERROR", + type = "batch_submit", + message = "Error: ${e.message}", + errorPosition = null, + entity = mapOf( + "processedIds" to processedIds, + "errors" to listOf(e.message ?: "Unknown error") + ) + ) + } +}} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/StockOutLineController.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/StockOutLineController.kt index 31654b4..3e93837 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/StockOutLineController.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/StockOutLineController.kt @@ -10,11 +10,12 @@ import com.ffii.fpsms.modules.stock.web.model.CreateStockOutLineRequest import com.ffii.fpsms.modules.stock.web.model.UpdateStockOutLineRequest import com.ffii.fpsms.modules.stock.web.model.UpdateStockOutLineStatusRequest import jakarta.validation.Valid +import com.ffii.fpsms.modules.stock.web.model.QrPickBatchSubmitRequest import org.springframework.web.bind.annotation.* import com.ffii.fpsms.modules.stock.web.model.CreateStockOutLineWithoutConsoRequest -import com.ffii.fpsms.modules.stock.web.model.QrPickBatchSubmitRequest import com.ffii.fpsms.modules.stock.web.model.UpdateStockOutLineStatusByQRCodeAndLotNoRequest + @RestController @RequestMapping("/stockOutLine") class StockOutLineController( @@ -45,16 +46,16 @@ class StockOutLineController( } - @PostMapping("/batchQrSubmit") + @PostMapping("/batchSubmitList") fun batchQrSubmit(@Valid @RequestBody request: QrPickBatchSubmitRequest): MessageResponse { - return stockOutLineService.batchSubmit(request) + return stockOutLineService.newBatchSubmit(request) } @PostMapping("/updateStatusByQRCodeAndLotNo") fun updateStatusByQRCodeAndLotNo(@Valid @RequestBody request: UpdateStockOutLineStatusByQRCodeAndLotNoRequest): MessageResponse { try { - println("=== 📥 CONTROLLER: updateStatusByQRCodeAndLotNo called ===") - println("📋 Request received:") - println(" - stockOutLineId: ${request.stockOutLineId}") + println("=== CONTROLLER: updateStatusByQRCodeAndLotNo called ===") + println(" Request received:") + println(" - stockOutLineId: ${request.stockOutLineId}") println(" - pickOrderLineId: ${request.pickOrderLineId}") println(" - inventoryLotNo: ${request.inventoryLotNo}") println(" - itemId: ${request.itemId}") @@ -62,11 +63,11 @@ class StockOutLineController( val result = stockOutLineService.updateStockOutLineStatusByQRCodeAndLotNo(request) - println("✅ CONTROLLER: Service call completed, returning result") + println(" CONTROLLER: Service call completed, returning result") return result } catch (e: Exception) { - println("❌ CONTROLLER ERROR: ${e.message}") - println("❌ Exception type: ${e.javaClass.simpleName}") + println(" CONTROLLER ERROR: ${e.message}") + println(" Exception type: ${e.javaClass.simpleName}") e.printStackTrace() // 返回错误响应而不是抛出异常 diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt index abee9c5..62013a4 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt @@ -61,6 +61,13 @@ data class UpdateStockOutLineStatusRequest( val qty: Double? = null, val remarks: String? = null ) +data class UpdateStockOutLineStatusByQRCodeAndLotNoRequest( + val pickOrderLineId: Long, + val inventoryLotNo: String, + val stockOutLineId: Long, + val itemId: Long, + val status: String +) data class QrPickSubmitLineRequest( val stockOutLineId: Long, val pickOrderLineId: Long, @@ -76,10 +83,4 @@ data class QrPickBatchSubmitRequest( val userId: Long, val lines: List ) -data class UpdateStockOutLineStatusByQRCodeAndLotNoRequest( - val pickOrderLineId: Long, - val inventoryLotNo: String, - val stockOutLineId: Long, - val itemId: Long, - val status: String -) \ No newline at end of file +