From 75e1a25a35047f051b189cad30afe9b92c41b3ee Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 13 Mar 2026 12:56:39 +0800 Subject: [PATCH] update stock in line lotNo and joborder show lotNo --- .../common/internalSetup/SetupController.kt | 67 ++++++++++ .../common/internalSetup/inventorySetup.kt | 114 +++++++++++++++++- .../entity/projections/JobOrderInfo.kt | 6 +- .../jobOrder/service/JoPickOrderService.kt | 8 +- .../jobOrder/service/JobOrderService.kt | 3 +- .../web/model/CreateJobOrderRequest.kt | 1 + .../service/ProductProcessService.kt | 1 + .../web/model/SaveProductProcessRequest.kt | 1 + .../stock/entity/StockInLineRepository.kt | 8 ++ .../stock/service/StockInLineService.kt | 29 ++++- 10 files changed, 230 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/common/internalSetup/SetupController.kt b/src/main/java/com/ffii/fpsms/modules/common/internalSetup/SetupController.kt index c3c3ef8..f21a9b4 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/internalSetup/SetupController.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/internalSetup/SetupController.kt @@ -207,4 +207,71 @@ class SetupController( ) return ResponseEntity.ok(mapOf("success" to true, "message" to "Lot stock-in labels printed successfully", "printedCount" to printedCount)) } + data class PrintProgressResult( + val success: Boolean, + val lastIndex: Int, + val totalLots: Int, + val errorMessage: String? = null + ) + @PostMapping("/inventory/print-lot-stockin-labels-by-item-ids-v2") + fun printLotStockInLabelsByItemIdsV2(@RequestBody request: Map): ResponseEntity> { + val printerId = (request["printerId"] as? Number)?.toLong() + val printQty = (request["printQty"] as? Number)?.toInt() ?: 1 + val fromIndex = (request["fromIndex"] as? Number)?.toInt() + val toIndex = (request["toIndex"] as? Number)?.toInt() + val itemIds = (request["itemIds"] as? List<*>)?.mapNotNull { (it as? Number)?.toLong() } ?: emptyList() + + if (printerId == null) { + return ResponseEntity.badRequest().body( + mapOf( + "success" to false, + "message" to "printerId is required" + ) + ) + } + if (itemIds.isEmpty()) { + return ResponseEntity.badRequest().body( + mapOf( + "success" to false, + "message" to "itemIds is required" + ) + ) + } + + return try { + val result = inventorySetup.printLotStockInLabelsByItemIdsV2( + printerId = printerId, + itemIds = itemIds, + printQty = printQty, + fromIndex = fromIndex, + toIndex = toIndex + ) + + val body = if (result.success) { + mapOf( + "success" to true, + "message" to "Lot stock-in labels printed successfully", + "lastIndex" to result.lastIndex, + "totalLots" to result.totalLots + ) + } else { + mapOf( + "success" to false, + "message" to (result.errorMessage ?: "Printer error"), + "lastIndex" to result.lastIndex, + "totalLots" to result.totalLots + ) + } + + ResponseEntity.ok(body) + } catch (e: Exception) { + e.printStackTrace() + ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body( + mapOf( + "success" to false, + "message" to (e.message ?: "Unknown error occurred while printing lot stock-in labels") + ) + ) + } + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt b/src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt index 3c11007..be0a2e5 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt @@ -353,5 +353,117 @@ open class InventorySetup { } return printedCount } - + data class PrintProgressResult( + val success: Boolean, + val lastIndex: Int, + val totalLots: Int, + val errorMessage: String? = null +) + fun printLotStockInLabelsByItemIdsV2( + printerId: Long, + itemIds: List, + printQty: Int = 1, + fromIndex: Int? = null, + toIndex: Int? = null + ): PrintProgressResult { + if (itemIds.isEmpty()) { + println("No itemIds provided, nothing to print") + return PrintProgressResult( + success = false, + lastIndex = -1, + totalLots = 0, + errorMessage = "itemIds is empty" + ) + } + + // 1. 查出并过滤 lot(可以按你 v2 的逻辑过滤 inQty-outQty=0) + val allInventoryLotLines = inventoryLotLineRepository + .findAllByItemIdIn(itemIds) + .filter { it.deleted == false && it.inventoryLot?.stockInLine != null } + + if (allInventoryLotLines.isEmpty()) { + println("No inventory lot lines found for itemIds=$itemIds") + return PrintProgressResult( + success = false, + lastIndex = -1, + totalLots = 0, + errorMessage = "no lots found for given itemIds" + ) + } + + val totalLots = allInventoryLotLines.size + + // 如果要过滤 inQty-outQty = 0,就在这里再 filter 一次(你已经写过类似逻辑了) + val lotsToUse = allInventoryLotLines // 或者 nonZeroLots + val effectiveTotal = lotsToUse.size // 这就是你的 totalLots(按你的业务决定) + + // 2. 计算本次要打的范围 + val startIndex = (fromIndex ?: 0).coerceAtLeast(0) + val endIndex = (toIndex ?: (effectiveTotal - 1)).coerceAtMost(effectiveTotal - 1) + + if (startIndex > endIndex || startIndex >= effectiveTotal) { + println("Invalid range: fromIndex=$fromIndex, toIndex=$toIndex, totalLots=$effectiveTotal") + return PrintProgressResult( + success = false, + lastIndex = startIndex - 1, + totalLots = effectiveTotal, + errorMessage = "invalid index range" + ) + } + + println("Printing range: $startIndex to $endIndex (out of $effectiveTotal)") + + // 3. 循环打印,实时更新 lastIndex,出现打印错误时立即返回 + var lastIndex = startIndex - 1 // 还没成功打印任何一张时是 startIndex-1 + + for (globalIndex in startIndex..endIndex) { + val inventoryLotLine = lotsToUse[globalIndex] + val stockInLineId = inventoryLotLine.inventoryLot?.stockInLine?.id + ?: return PrintProgressResult( + success = false, + lastIndex = lastIndex, + totalLots = effectiveTotal, + errorMessage = "Stock in line missing for inventoryLotLineId=${inventoryLotLine.id}" + ) + + try { + println("Processing lot ${globalIndex + 1}/$effectiveTotal: lotNo=${inventoryLotLine.inventoryLot?.lotNo}, stockInLineId=$stockInLineId") + stockInLineService.printQrCode( + PrintQrCodeForSilRequest( + stockInLineId = stockInLineId, + printerId = printerId, + printQty = printQty + ) + ) + lastIndex = globalIndex + println("✓ Printed label for lotNo=${inventoryLotLine.inventoryLot?.lotNo}") + } catch (e: Exception) { + // 打印异常(最常见:打印机没纸 / 打印机错误) + println("✗ Printer error at index=$globalIndex, lotLineId=${inventoryLotLine.id}: ${e.message}") + e.printStackTrace() + + val msg = when { + e.message?.contains("paper", ignoreCase = true) == true -> + "Printer error (maybe out of paper): ${e.message}" + else -> + "Printer error: ${e.message}" + } + + return PrintProgressResult( + success = false, + lastIndex = lastIndex, // 最后成功打印到哪一张 + totalLots = effectiveTotal, + errorMessage = msg + ) + } + } + + // 能循环走完,说明这段全部打完 + return PrintProgressResult( + success = true, + lastIndex = lastIndex, // 成功场景下,这里会等于 endIndex + totalLots = effectiveTotal, + errorMessage = null + ) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt index 784bfd5..5f67a5f 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt @@ -30,7 +30,8 @@ interface JobOrderInfo { @get:Value("#{target.stockInLines?.size() > 0 ? target.stockInLines[0].escalationLog.^[status.value == 'pending']?.handler?.id : null}") val silHandlerId: Long?; - + @get:Value("#{target.stockInLines?.size() > 0 ? target.stockInLines[0].lotNo : null}") + val lotNo: String? val planStart: LocalDateTime?; // @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom}") //// @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") @@ -66,7 +67,8 @@ data class JobOrderInfoWithTypeName( val productionPriority: Int?, val status: String, val jobTypeId: Long?, - val jobTypeName: String? + val jobTypeName: String?, + val lotNo: String? ) // Job Order interface JobOrderDetailWithJsonString { diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt index 601ce5e..27f971b 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -1809,7 +1809,7 @@ open fun getAllJoPickOrders(isDrink: Boolean?): List { val bom = jobOrder.bom // 按 isDrink 过滤:null 表示不过滤(全部) -if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null + if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null println("BOM found: ${bom?.id}") val item = bom?.item @@ -1831,7 +1831,10 @@ if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id ?: return@mapNotNull null) val finishedLines = pickOrderLines.count { it.status == PickOrderLineStatus.COMPLETED } val jobOrderType = jobOrder.jobTypeId?.let { jobTypeRepository.findById(it).orElse(null) } - + val stockInLine = stockInLineRepository.findFirstByJobOrder_IdAndDeletedFalse( + jobOrder.id ?: return@mapNotNull null + ) + val lotNo = stockInLine?.lotNo println("✅ Building response for pick order ${pickOrder.id}") val floorPickCounts = floorCountsByPickOrderId[pickOrder.id]?.map { row -> com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto( @@ -1854,6 +1857,7 @@ if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null //uomId = bom.outputQtyUom?.id : 0L, uomId = 0, uomName = bom?.outputQtyUom?: "", + lotNo = lotNo, jobOrderStatus = jobOrder.status?.value ?: "", finishedPickOLineCount = finishedLines, floorPickCounts = floorPickCounts diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index 577dca7..20b3ce6 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -192,7 +192,8 @@ open class JobOrderService( productionPriority = info.productionPriority, status = info.status, jobTypeId = info.jobTypeId, - jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name } + jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name }, + lotNo = info.lotNo ) } .filter { info -> diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt index 180db91..d2f4a42 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt @@ -52,6 +52,7 @@ data class AllJoPickOrderResponse( val reqQty: BigDecimal, val uomId: Long, val uomName: String, + val lotNo: String?, val jobOrderStatus: String, val finishedPickOLineCount: Int, val floorPickCounts: List = emptyList() 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 362cd05..a763685 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 @@ -1414,6 +1414,7 @@ open class ProductProcessService( }, RequiredQty = jobOrder?.reqQty?.toInt() ?: 0, Uom = bomUom?.udfudesc, + lotNo = stockInLine?.lotNo, productionPriority = productProcesses.productionPriority, date = productProcesses.date, bomId = productProcesses.bom?.id, diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt index cd1494f..283af9a 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt @@ -169,6 +169,7 @@ data class AllJoborderProductProcessInfoResponse( val matchStatus: String?, val RequiredQty: Int?, val Uom: String?, + val lotNo: String?, val jobOrderId: Long?, val productionPriority: Int?, val jobOrderCode: String?, diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt index f595a2c..9bb5931 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt @@ -78,4 +78,12 @@ fun findFirstByJobOrder_IdAndDeletedFalse(jobOrderId: Long): StockInLine? ORDER BY sil.purchaseOrder.id """) fun findCompletedDnByReceiptDate(@Param("receiptDate") receiptDate: LocalDate): List + + @Query(""" + select max(sil.lotNo) + from StockInLine sil + where sil.deleted = false + and sil.lotNo like concat(:prefix, '%') +""") +fun findLatestLotNoByPrefix(@Param("prefix") prefix: String): String? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index 7fa49d1..d4c1201 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -110,7 +110,7 @@ open class StockInLineService( open fun getReceivedStockInLineInfo(stockInLineId: Long): StockInLineInfo { return stockInLineRepository.findStockInLineInfoByIdAndStatusAndDeletedFalse(id = stockInLineId, status = StockInLineStatus.RECEIVED.status).orElseThrow() } - +/* open fun assignLotNo(): String { val prefix = "LT" // ✅ 每次调用都取今天的日期段 @@ -126,7 +126,32 @@ open class StockInLineService( latestCode = latestCode ) } - + */ + fun assignLotNo(): String { + val prefix = "LT" + // 建議統一用同一種日期格式,現在 StockInLineService 是 DEFAULT_FORMATTER (LocalDate.today) + val midfix = LocalDate.now().format(CodeGenerator.DEFAULT_FORMATTER) + val fullPrefix = "$prefix-$midfix" + + // 1) 今天在 inventory_lot 裡的最大 lotNo + val latestFromInventory = + inventoryLotRepository.findLatestLotNoByPrefix(fullPrefix) + + // 2) 今天在 stock_in_line 裡的最大 lotNo(不分來源:JO、PO、盤點...) + val latestFromStockInLine = + stockInLineRepository.findLatestLotNoByPrefix(fullPrefix) + + // 3) 兩邊取最大的一個當成 latestCode + val latestCode = listOfNotNull(latestFromInventory, latestFromStockInLine) + .maxOrNull() + + // 4) 丟給你現有的 CodeGenerator 產下一號 + return CodeGenerator.generateNo( + prefix = prefix, + midfix = midfix, + latestCode = latestCode, + ) + } @Throws(IOException::class) @Transactional open fun create(request: SaveStockInLineRequest): MessageResponse {