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 d691498..4aea5fe 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 @@ -494,7 +494,8 @@ open class JoPickOrderService( uomShortDesc = uom?.udfShortDesc, matchStatus = jpo?.matchStatus?.value, matchBy = jpo?.matchBy, - matchQty = jpo?.matchQty?.toDouble() + matchQty = jpo?.matchQty?.toDouble(), + stockInLineId = null, ) } @@ -1914,6 +1915,15 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId) } + // 获取 stock in lines 通过 inventoryLotLineId(用于填充 stockInLineId) + val stockInLinesByInventoryLotLineId = if (inventoryLotLineIds.isNotEmpty()) { + inventoryLotLineIds.associateWith { illId -> + stockInLineRepository.findStockInLineInfoByInventoryLotLineId(illId).orElse(null)?.id + }.filterValues { it != null } + } else { + emptyMap() + } + // 获取 jo_pick_order 记录 val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!) @@ -1983,6 +1993,11 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo else -> "pending" } + // 获取 stockInLineId + val stockInLineId = ill.id?.let { illId -> + stockInLinesByInventoryLotLineId[illId] + } + LotDetailResponse( lotId = ill.id, lotNo = il.lotNo, @@ -2000,6 +2015,7 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo pickOrderConsoCode = pickOrder.consoCode, pickOrderLineId = pol.id, stockOutLineId = sol?.id, + stockInLineId = stockInLineId, suggestedPickLotId = spl.id, stockOutLineQty = sol?.qty ?: 0.0, stockOutLineStatus = sol?.status, 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 893055e..f746605 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 @@ -109,6 +109,7 @@ data class LotDetailResponse( val pickOrderConsoCode: String?, val pickOrderLineId: Long?, val stockOutLineId: Long?, + val stockInLineId: Long?, val suggestedPickLotId: Long?, val stockOutLineQty: Double?, val stockOutLineStatus: String?, diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt index c7d07e2..2ca6359 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt @@ -590,7 +590,7 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD val newStatus = if (actualPickQtyDouble >= requiredQty) { "completed" } else { - "partially_completed" + "completed" } stockOutLine.status = newStatus @@ -606,6 +606,42 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD createStockLedgerForStockOut(savedStockOutLine, "Nor") } } + + // ✅ NEW: If all stock_out_line under this pick order line are finished (completed/rejected), + // mark pick_order_line as COMPLETED. This is required for "miss all required qty" flows + // where qty can be 0 but picking decision is finalized via issue form. + try { + stockOutLineRepository.flush() + + val allStockOutLines = stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(request.pickOrderLineId) + val unfinishedLines = allStockOutLines.filter { + it.status != StockOutLineStatus.COMPLETE.status && + it.status != StockOutLineStatus.REJECTED.status + } + + println("Miss item: 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("✅ Miss item: Updated pick order line ${request.pickOrderLineId} status to COMPLETED") + } else { + println("⚠️ Miss item: Pick order line ${request.pickOrderLineId} not found") + } + } else { + println("⚠️ Miss item: Pick order line ${request.pickOrderLineId} still has ${unfinishedLines.size} unfinished lines") + } + } catch (e: Exception) { + println("⚠️ Miss item: Error checking pick order line completion: ${e.message}") + e.printStackTrace() + } // ✅ 修改:不重新建议拣货批次(因为 lot 仍然可用) // resuggestPickOrder(request.pickOrderId) // 删除这行 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 4c38d41..cf0e86a 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 @@ -51,6 +51,8 @@ import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintQrCodeForDoRequest import com.ffii.fpsms.modules.stock.service.InventoryLotLineService import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository import com.ffii.fpsms.modules.stock.entity.InventoryRepository +import org.springframework.http.HttpStatus +import org.springframework.web.server.ResponseStatusException @Serializable data class QrContent(val itemId: Long, val stockInLineId: Long) @@ -81,7 +83,10 @@ open class StockInLineService( ): AbstractBaseEntityService(jdbcDao, stockInLineRepository) { open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { - return stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(stockInLineId) + // Use Optional-returning repository method to avoid EmptyResultDataAccessException + return stockInLineRepository.findStockInLineInfoById(stockInLineId).orElseThrow { + ResponseStatusException(HttpStatus.NOT_FOUND, "stockInLineId $stockInLineId not found") + } } open fun getReceivedStockInLineInfo(stockInLineId: Long): StockInLineInfo { return stockInLineRepository.findStockInLineInfoByIdAndStatusAndDeletedFalse(id = stockInLineId, status = StockInLineStatus.RECEIVED.status).orElseThrow() 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 6d7bdb6..69ad4fa 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 @@ -855,9 +855,11 @@ open fun batchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { @Transactional open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineStatusByQRCodeAndLotNoRequest): MessageResponse { + val totalStartTime = System.currentTimeMillis() val startTime = System.currentTimeMillis() try { println("=== QR SCAN REQUEST RECEIVED ===") + println("⏰ Request received at: ${java.time.LocalDateTime.now()}") println(" Request details:") println(" - stockOutLineId: ${request.stockOutLineId}") println(" - pickOrderLineId: ${request.pickOrderLineId}") @@ -865,10 +867,14 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta println(" - itemId: ${request.itemId}") println(" - status: ${request.status}") + // Step 1: Find StockOutLine + val findStockOutLineStart = System.currentTimeMillis() val stockOutLine = stockOutLineRepository.findById(request.stockOutLineId).orElseThrow { println(" StockOutLine not found with ID: ${request.stockOutLineId}") IllegalArgumentException("StockOutLine not found with ID: ${request.stockOutLineId}") } + val findStockOutLineTime = System.currentTimeMillis() - findStockOutLineStart + println("⏱️ [STEP 1] Find StockOutLine: ${findStockOutLineTime}ms") println(" Found StockOutLine:") println(" - ID: ${stockOutLine.id}") @@ -878,8 +884,13 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta println(" - Item ID: ${stockOutLine.item?.id}") println(" - InventoryLotLine ID: ${stockOutLine.inventoryLotLine?.id}") + // Step 2: Get InventoryLotLine + val getInventoryLotLineStart = System.currentTimeMillis() // 修复:从 stockOutLine.inventoryLotLine 获取 inventoryLot,而不是使用错误的参数 val inventoryLotLine = stockOutLine.inventoryLotLine + val getInventoryLotLineTime = System.currentTimeMillis() - getInventoryLotLineStart + println("⏱️ [STEP 2] Get InventoryLotLine: ${getInventoryLotLineTime}ms") + if (inventoryLotLine == null) { println(" StockOutLine has no associated InventoryLotLine") return MessageResponse( @@ -892,7 +903,12 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta ) } + // Step 3: Get InventoryLot + val getInventoryLotStart = System.currentTimeMillis() val inventoryLot = inventoryLotLine.inventoryLot + val getInventoryLotTime = System.currentTimeMillis() - getInventoryLotStart + println("⏱️ [STEP 3] Get InventoryLot: ${getInventoryLotTime}ms") + if (inventoryLot == null) { println(" InventoryLotLine has no associated InventoryLot") return MessageResponse( @@ -911,9 +927,13 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta println(" - Item ID: ${inventoryLot.item?.id}") println(" - InventoryLot ID: ${inventoryLot.id}") + // Step 4: Validation + val validationStart = System.currentTimeMillis() // 修复:比较逻辑 val lotNoMatch = inventoryLot.lotNo == request.inventoryLotNo val itemIdMatch = inventoryLot.item?.id == request.itemId + val validationTime = System.currentTimeMillis() - validationStart + println("⏱️ [STEP 4] Validation: ${validationTime}ms") println(" Matching results:") println(" - Lot No match: $lotNoMatch (Expected: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo})") @@ -923,6 +943,8 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta println(" MATCH SUCCESS: Lot and Item both match!") + // Step 5: Update StockOutLine + val updateStart = System.currentTimeMillis() stockOutLine.status = request.status if (request.status == "checked") { stockOutLine.startTime = LocalDateTime.now() @@ -930,15 +952,30 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta if (request.status == "completed") { stockOutLine.endTime = LocalDateTime.now() } + val updateTime = System.currentTimeMillis() - updateStart + println("⏱️ [STEP 5] Update entity: ${updateTime}ms") + + // Step 6: Save to database + val saveStart = System.currentTimeMillis() val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) + val saveTime = System.currentTimeMillis() - saveStart + println("⏱️ [STEP 6] Save to DB: ${saveTime}ms") println(" Status updated successfully:") println(" - New status: ${savedStockOutLine.status}") println(" - StockOutLine ID: ${savedStockOutLine.id}") + // Step 7: Fetch mapped info + val fetchMappedStart = System.currentTimeMillis() val mappedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) + val fetchMappedTime = System.currentTimeMillis() - fetchMappedStart + println("⏱️ [STEP 7] Fetch mapped info: ${fetchMappedTime}ms") + val totalTime = System.currentTimeMillis() - totalStartTime println("=== QR SCAN COMPLETED SUCCESSFULLY ===") + println("⏱️ [TOTAL BACKEND TIME] ${totalTime}ms (${totalTime / 1000.0}s)") + println("📊 Breakdown: findStockOutLine=${findStockOutLineTime}ms, getInventoryLotLine=${getInventoryLotLineTime}ms, getInventoryLot=${getInventoryLotTime}ms, validation=${validationTime}ms, update=${updateTime}ms, save=${saveTime}ms, fetchMapped=${fetchMappedTime}ms") + println("⏰ Completed at: ${java.time.LocalDateTime.now()}") return MessageResponse( id = savedStockOutLine.id, @@ -951,6 +988,8 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta ) } else if (!lotNoMatch && itemIdMatch) { // Item 匹配但 lotNo 不匹配 + val totalTime = System.currentTimeMillis() - totalStartTime + println("⏱️ [TOTAL BACKEND TIME] ${totalTime}ms (LOT MISMATCH)") println(" LOT NUMBER MISMATCH:") println(" - Expected lotNo: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}") println(" - Item ID matches: ${inventoryLot.item?.id} == ${request.itemId}") @@ -965,6 +1004,8 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta ) } else { // Item 不匹配 + val totalTime = System.currentTimeMillis() - totalStartTime + println("⏱️ [TOTAL BACKEND TIME] ${totalTime}ms (ITEM MISMATCH)") println(" ITEM MISMATCH:") println(" - Expected itemId: ${inventoryLot.item?.id}, Got: ${request.itemId}") println(" - Expected lotNo: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}") @@ -980,6 +1021,8 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta } } catch (e: Exception) { + val totalTime = System.currentTimeMillis() - totalStartTime + println("⏱️ [TOTAL BACKEND TIME] ${totalTime}ms (ERROR)") println(" ERROR updating stock out line status by QR: ${e.message}") println(" Exception type: ${e.javaClass.simpleName}") println(" Stack trace:") diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt index 6558153..d7580a1 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/InventoryLotLineController.kt @@ -26,6 +26,8 @@ import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineStatusRequest import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisRequest import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisResponse +import org.springframework.http.HttpStatus +import org.springframework.web.server.ResponseStatusException @RequestMapping("/inventoryLotLine") @RestController @@ -53,7 +55,9 @@ class InventoryLotLineController ( @GetMapping("/lot-detail/{stockInLineId}") fun getLotDetail(@Valid @PathVariable stockInLineId: Long): LotLineInfo { - val stockInLine = stockInLineRepository.findById(stockInLineId).orElseThrow() + val stockInLine = stockInLineRepository.findById(stockInLineId).orElseThrow { + ResponseStatusException(HttpStatus.NOT_FOUND, "stockInLineId $stockInLineId not found") + } val inventoryLotLine = stockInLine.inventoryLotLine!! val zero = BigDecimal.ZERO return LotLineInfo( 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 5f1b4f0..daa2e37 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 @@ -52,8 +52,10 @@ class StockOutLineController( } @PostMapping("/updateStatusByQRCodeAndLotNo") fun updateStatusByQRCodeAndLotNo(@Valid @RequestBody request: UpdateStockOutLineStatusByQRCodeAndLotNoRequest): MessageResponse { + val controllerStartTime = System.currentTimeMillis() try { println("=== CONTROLLER: updateStatusByQRCodeAndLotNo called ===") + println("⏰ Controller start time: ${java.time.LocalDateTime.now()}") println(" Request received:") println(" - stockOutLineId: ${request.stockOutLineId}") println(" - pickOrderLineId: ${request.pickOrderLineId}") @@ -61,11 +63,21 @@ class StockOutLineController( println(" - itemId: ${request.itemId}") println(" - status: ${request.status}") + // Measure service call time + val serviceCallStart = System.currentTimeMillis() val result = stockOutLineService.updateStockOutLineStatusByQRCodeAndLotNo(request) + val serviceCallTime = System.currentTimeMillis() - serviceCallStart + println("⏱️ [CONTROLLER] Service call time: ${serviceCallTime}ms (${serviceCallTime / 1000.0}s)") + val controllerTotalTime = System.currentTimeMillis() - controllerStartTime + println("⏱️ [CONTROLLER] Total controller time: ${controllerTotalTime}ms (${controllerTotalTime / 1000.0}s)") + println("📊 Controller breakdown: serviceCall=${serviceCallTime}ms, overhead=${controllerTotalTime - serviceCallTime}ms") println(" CONTROLLER: Service call completed, returning result") + println("⏰ Controller end time: ${java.time.LocalDateTime.now()}") return result } catch (e: Exception) { + val controllerTotalTime = System.currentTimeMillis() - controllerStartTime + println("⏱️ [CONTROLLER] Total time before error: ${controllerTotalTime}ms") println(" CONTROLLER ERROR: ${e.message}") println(" Exception type: ${e.javaClass.simpleName}") e.printStackTrace()