diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt index 8bac6ad..fb42cbd 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt @@ -18,6 +18,7 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository +import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService data class BatchReleaseJobStatus( val jobId: String, val total: Int, @@ -34,7 +35,8 @@ class DoReleaseCoordinatorService( private val jdbcDao: JdbcDao, private val doPickOrderLineRepository: DoPickOrderLineRepository, private val deliveryOrderRepository: DeliveryOrderRepository, - private val doPickOrderRepository: DoPickOrderRepository + private val doPickOrderRepository: DoPickOrderRepository, + private val pickExecutionIssueService: PickExecutionIssueService, ) { private val poolSize = Runtime.getRuntime().availableProcessors() private val executor = Executors.newFixedThreadPool(min(poolSize, 4)) @@ -362,28 +364,32 @@ class DoReleaseCoordinatorService( } fun startBatchReleaseAsync(ids: List, userId: Long): MessageResponse { if (ids.isEmpty()) { - return MessageResponse(id = null, code = "NO_IDS", name = null, type = null, - message = "No IDs provided", errorPosition = null, entity = null) + return MessageResponse( + id = null, code = "NO_IDS", name = null, type = null, + message = "No IDs provided", errorPosition = null, entity = null + ) } - + val jobId = UUID.randomUUID().toString() val status = BatchReleaseJobStatus(jobId = jobId, total = ids.size) jobs[jobId] = status - + executor.submit { try { println("📦 Starting batch release for ${ids.size} orders") val sortedIds = getOrderedDeliveryOrderIds(ids) println("🔍 DEBUG: Got ${sortedIds.size} sorted orders") - + val releaseResults = mutableListOf() - + // 第一步:发布所有 DO(创建 pick orders,但不创建 DoPickOrder) + // 第一步:发布所有 DO(创建 pick orders,但不创建 DoPickOrder) sortedIds.forEach { id -> try { val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(id) - if (deliveryOrder?.status == DeliveryOrderStatus.COMPLETED || - deliveryOrder?.status == DeliveryOrderStatus.RECEIVING) { + if (deliveryOrder?.status == DeliveryOrderStatus.COMPLETED || + deliveryOrder?.status == DeliveryOrderStatus.RECEIVING + ) { println("⏭️ DO $id is already ${deliveryOrder.status?.value}, skipping") return@forEach } @@ -398,27 +404,51 @@ class DoReleaseCoordinatorService( status.failed.add(id to (e.message ?: "Exception")) } println("❌ DO $id skipped: ${e.message}") + + // ✅ 调用 PickExecutionIssueService 创建 issue 记录 + try { + val issueCategory = when { + e.message?.contains("Unable to find") == true && + e.message?.contains("Warehouse") == true -> + com.ffii.fpsms.modules.pickOrder.entity.IssueCategory.warehouse_issue + + e.message?.contains("No matching truck") == true -> + com.ffii.fpsms.modules.pickOrder.entity.IssueCategory.truck_issue + + else -> + com.ffii.fpsms.modules.pickOrder.entity.IssueCategory.match_issue + } + + pickExecutionIssueService.createBatchReleaseIssue( + deliveryOrderId = id, + errorMessage = e.message ?: "Unknown error", + issueCategory = issueCategory + ) + } catch (issueException: Exception) { + println("⚠️ Failed to create issue for DO $id: ${issueException.message}") + } } - } - - // 第二步:按日期、楼层、店铺分组(与 SQL 逻辑一致) - val sortedResults = releaseResults.sortedWith(compareBy( - { it.estimatedArrivalDate }, - { it.preferredFloor }, - { it.truckDepartureTime }, - { it.truckLanceCode }, - { it.loadingSequence }, - { it.shopId } - )) + } // ✅ forEach 循环在这里结束 - // ✅ 然后按正确的顺序分组(保持排序后的顺序) - val grouped = sortedResults.groupBy { - Triple(it.estimatedArrivalDate, it.preferredFloor, it.shopId) - } + // ✅ 第二步:按日期、楼层、店铺分组(在 forEach 之后) + val sortedResults = releaseResults.sortedWith( + compareBy( + { it.estimatedArrivalDate }, + { it.preferredFloor }, + { it.truckDepartureTime }, + { it.truckLanceCode }, + { it.loadingSequence }, + { it.shopId } + ) + ) + + // ✅ 然后按正确的顺序分组(保持排序后的顺序) + val grouped = sortedResults.groupBy { + Triple(it.estimatedArrivalDate, it.preferredFloor, it.shopId) + } - println("🔍 DEBUG: Grouped into ${grouped.size} DoPickOrders") - + // 第三步:为每组创建一个 DoPickOrder 和多条 DoPickOrderLine grouped.forEach { (key, group) -> try { @@ -429,31 +459,31 @@ class DoReleaseCoordinatorService( e.printStackTrace() } } - + // 第四步:更新 ticket numbers if (status.success.get() > 0) { println("🎫 Updating ticket numbers...") updateBatchTicketNumbers() } - + println("✅ Batch completed: ${status.success.get()} success, ${status.failed.size} failed") - - } catch (e: Exception) { - println("❌ Batch release exception: ${e.javaClass.simpleName} - ${e.message}") - e.printStackTrace() - } finally { - status.running = false - status.finishedAt = Instant.now().toEpochMilli() - } - } - - return MessageResponse( - id = null, code = "STARTED", name = null, type = null, - message = "Batch release started", errorPosition = null, - entity = mapOf("jobId" to jobId, "total" to ids.size) - ) - } - + + } catch (e: Exception) { + println("❌ Batch release exception: ${e.javaClass.simpleName} - ${e.message}") + e.printStackTrace() + } finally { + status.running = false + status.finishedAt = Instant.now().toEpochMilli() + } + } + + return MessageResponse( + id = null, code = "STARTED", name = null, type = null, + message = "Batch release started", errorPosition = null, + entity = mapOf("jobId" to jobId, "total" to ids.size) + ) + } + private fun createMergedDoPickOrder(results: List) { val first = results.first() diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt index b0de9bf..6c034fc 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt @@ -144,5 +144,7 @@ enum class HandleStatus { enum class IssueCategory { lot_issue, // 正常 Issue Button 提交 resuggest_issue, // Resuggest 失败 - match_issue // Matching 问题 + match_issue , + warehouse_issue, + truck_issue // Matching 问题 } \ No newline at end of file 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 10ad4c7..b8d5c44 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 @@ -674,4 +674,111 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty println("=== End Debug ===") return result } + /** + * 为 batch release 失败的 delivery order 创建 issue 记录 + */ +@Transactional(rollbackFor = [Exception::class]) +open fun createBatchReleaseIssue( + deliveryOrderId: Long, + errorMessage: String, + issueCategory: IssueCategory = IssueCategory.match_issue +): Int { + try { + println("=== Creating batch release issue ===") + println("Delivery Order ID: $deliveryOrderId") + println("Error Message: $errorMessage") + println("Issue Category: $issueCategory") + + // 查询 delivery order + val deliveryOrderSql = """ + SELECT do.id, do.code, do.estimatedArrivalDate + FROM fpsmsdb.delivery_order do + WHERE do.id = :deliveryOrderId + AND do.deleted = 0 + """.trimIndent() + + val deliveryOrder = jdbcDao.queryForMap( + deliveryOrderSql, + mapOf("deliveryOrderId" to deliveryOrderId) + ).orElse(null) + + if (deliveryOrder == null) { + println("❌ Delivery Order not found: $deliveryOrderId") + return 0 + } + + // 查询 delivery order lines + val linesSql = """ + SELECT dol.id, dol.itemId, i.code as itemCode, i.name as itemName, dol.qty + FROM fpsmsdb.delivery_order_line dol + INNER JOIN fpsmsdb.item i ON i.id = dol.itemId + WHERE dol.doId = :deliveryOrderId + AND dol.deleted = 0 + """.trimIndent() + + val lines = jdbcDao.queryForList(linesSql, mapOf("deliveryOrderId" to deliveryOrderId)) + + if (lines.isEmpty()) { + println("⚠️ No delivery order lines found for DO: $deliveryOrderId") + return 0 + } + + // 生成 issue number + val issueNo = generateIssueNo() + var createdCount = 0 + + // 为每个 line 创建 issue 记录 + lines.forEach { line -> + val itemId = (line["itemId"] as? Number)?.toLong() ?: 0L + val itemCode = line["itemCode"] as? String + val itemName = line["itemName"] as? String + val qty = (line["qty"] as? Number)?.let { BigDecimal(it.toString()) } ?: BigDecimal.ZERO + + val issue = PickExecutionIssue( + id = null, + pickOrderId = 0L, // batch release 失败时可能还没有 pick order + pickOrderCode = deliveryOrder["code"] as? String ?: "", + pickOrderCreateDate = LocalDate.now(), + pickExecutionDate = LocalDate.now(), + pickOrderLineId = 0L, // batch release 失败时可能还没有 pick order line + issueNo = issueNo, + joPickOrderId = null, + doPickOrderId = null, + issueCategory = issueCategory, + itemId = itemId, + itemCode = itemCode, + itemDescription = itemName, + lotId = null, + lotNo = null, + storeLocation = null, + requiredQty = qty, + actualPickQty = BigDecimal.ZERO, + missQty = qty, + badItemQty = BigDecimal.ZERO, + issueRemark = "Batch release failed: $errorMessage", + pickerName = null, + handleStatus = HandleStatus.pending, + handleDate = null, + handledBy = null, + created = LocalDateTime.now(), + createdBy = "SYSTEM", + version = 0, + modified = LocalDateTime.now(), + modifiedBy = "SYSTEM", + deleted = false + ) + + pickExecutionIssueRepository.save(issue) + createdCount++ + } + + println("✅ Created $createdCount ${issueCategory.name} issues for DO $deliveryOrderId") + return createdCount + + } catch (e: Exception) { + println("❌ Failed to create batch release issue: ${e.message}") + e.printStackTrace() + return 0 + } +} } \ No newline at end of file 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 1ecab46..ff0f297 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 @@ -188,7 +188,7 @@ open class PickOrderService( // Get full pick orders with relationships val fullPickOrders = pickOrderRepository.findAllByIdIn(pickOrderIds) val groupsByPickOrderId = pickOrderGroupRepository.findByPickOrderIdInAndDeletedIsFalse(pickOrderIds) - .groupBy { it.pickOrderId } + .groupBy { it.pickOrderId } // Get all item IDs val itemIds = fullPickOrders .flatMap { it.pickOrderLines } @@ -674,8 +674,8 @@ open class PickOrderService( val pos = pickOrderRepository.findAllByIdIn(ids) println(pos) val groupsByPickOrderId = pickOrderGroupRepository.findByPickOrderIdInAndDeletedIsFalse(ids) - .groupBy { it.pickOrderId } - + .groupBy { it.pickOrderId } + // Get Inventory Data val requiredItems = pos .flatMap { it.pickOrderLines } @@ -690,7 +690,7 @@ open class PickOrderService( override val requiredQty: BigDecimal = value.sumOf { it.qty ?: zero } } } // itemId - requiredQty - + val itemIds = requiredItems.mapNotNull { it.first } val inventories = inventoryLotLineService .allInventoryLotLinesByItemIdIn(itemIds) @@ -699,10 +699,10 @@ open class PickOrderService( .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } .sortedBy { it.expiryDate } .groupBy { it.item?.id } - + val suggestions = suggestedPickLotService.suggestionForPickOrders(SuggestedPickLotForPoRequest(pickOrders = pos)) - + // ✅ Get stock out line data for each pick order line val pickOrderLineIds = pos.flatMap { it.pickOrderLines }.mapNotNull { it.id } val stockOutLinesByPickOrderLineId = if (pickOrderLineIds.isNotEmpty()) { @@ -712,12 +712,12 @@ open class PickOrderService( } else { emptyMap() } - + // ✅ Pre-calculate available quantities for each item ONCE val itemAvailableQtyMap = mutableMapOf() itemIds.forEach { itemId -> val inventory = inventories[itemId] - val totalAvailableQty = inventory?.sumOf { i -> + val totalAvailableQty = inventory?.sumOf { i -> val inQty = i.inQty ?: zero val outQty = i.outQty ?: zero val holdQty = i.holdQty ?: zero @@ -725,27 +725,27 @@ open class PickOrderService( } ?: zero itemAvailableQtyMap[itemId] = totalAvailableQty } - + // Pick Orders val releasePickOrderLineInfos = pos .map { po -> val releasePickOrderLineInfos = po.pickOrderLines.map { pol -> val itemId = pol.item?.id val availableQty = itemId?.let { itemAvailableQtyMap[it] } ?: zero - + // ✅ Move stockOutLines declaration inside the pol loop val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList() - + // ✅ Calculate total picked quantity from stock out lines println("=== PICKED QTY DEBUG: Line ${pol.id} ===") println("Stock Out Lines: ${stockOutLines.map { "${it.id}(status=${it.status}, qty=${it.qty})" }}") - + val totalPickedQty = stockOutLines - .sumOf { it.qty ?: zero } - + .sumOf { it.qty ?: zero } + println("Total Picked Qty: $totalPickedQty") println("=== END DEBUG ===") - + // Return GetPickOrderLineInfo( id = pol.id, @@ -766,7 +766,7 @@ open class PickOrderService( // Return GetPickOrderInfo( id = po.id, - code = po.code, + code = po.code, consoCode = po.consoCode, targetDate = po.targetDate, type = po.type?.value, @@ -776,18 +776,18 @@ open class PickOrderService( pickOrderLines = releasePickOrderLineInfos ) } - + // Items val currentInventoryInfos = requiredItems.map { item -> val inventory = item.first?.let { inventories[it] } val itemUom = item.first?.let { itemUomService.findSalesUnitByItemId(it) } - + item.second.let { - val convertedAvailableQty = inventory?.sumOf { i -> + val convertedAvailableQty = inventory?.sumOf { i -> val baseQty = (i.availableQty ?: zero) baseQty } - + it.availableQty = convertedAvailableQty it } @@ -807,9 +807,9 @@ open class PickOrderService( .filter { it.status == PickOrderStatus.RELEASED } .filter { userId == null || it.assignTo?.id == userId } // ✅ Fixed: now userId is a parameter .map { it.id!! } - + println("All released pick order IDs for user $userId: $releasedPickOrderIds") - + // 如果没有任何已发布的 Pick Orders,返回空结果 if (releasedPickOrderIds.isEmpty()) { return GetPickOrderInfoResponse( @@ -818,7 +818,7 @@ open class PickOrderService( items = emptyList() ) } - + // 重用现有的 getPickOrdersInfo 方法 return getPickOrdersInfo(releasedPickOrderIds) } @@ -911,13 +911,13 @@ open class PickOrderService( val isExpired = row["debug_is_expired"] val solStatus = row["debug_sol_status"] val lotAvailability = row["lotAvailability"] - + println("--- Lot: $lotNo (ID: $lotId) ---") println(" ill.status: $illStatus") println(" is_expired: $isExpired") println(" sol.status: $solStatus") println(" lotAvailability: $lotAvailability") - + // ✅ Check each condition step by step if (isExpired == true) { println(" ❌ FAILED: lot is expired") @@ -935,13 +935,13 @@ open class PickOrderService( val stockOutLineStatus = row["stockOutLineStatus"] as String? val stockOutLineQty = row["stockOutLineQty"] as Number? val requiredQty = row["requiredQty"] as Number? - + // Show lot if: // 1. No stock out line exists, OR // 2. Stock out line is not completed, OR // 3. Stock out line qty doesn't equal required qty - stockOutLineStatus != "completed" || - stockOutLineQty?.toDouble() != requiredQty?.toDouble() + stockOutLineStatus != "completed" || + stockOutLineQty?.toDouble() != requiredQty?.toDouble() } println("Final result count: ${filteredResult.size}") filteredResult.forEach { row -> @@ -1011,9 +1011,9 @@ open class PickOrderService( val zero = BigDecimal.ZERO val releasedBy = SecurityUtils.getUser().getOrNull() val user = userService.find(assignTo).orElse(null) - + val pickOrders = pickOrderRepository.findAllByIdInAndStatus(pickOrderIds, PickOrderStatus.ASSIGNED) - + if (pickOrders.isEmpty()) { return MessageResponse( id = null, @@ -1024,11 +1024,11 @@ open class PickOrderService( errorPosition = null ) } - + // ← GENERATE CONSOCODE EARLY AND SAVE IMMEDIATELY val newConsoCode = assignConsoCode() val currUser = SecurityUtils.getUser().orElseThrow() - + // Create and save StockOut immediately to prevent duplicate consoCodes val stockOut = StockOut().apply { this.type = "job" @@ -1037,7 +1037,7 @@ open class PickOrderService( this.handler = currUser.id } val savedStockOut = stockOutRepository.saveAndFlush(stockOut) // ← FLUSH to commit immediately - + // Update pick orders pickOrders.forEach { pickOrder -> pickOrder.apply { @@ -1047,15 +1047,15 @@ open class PickOrderService( this.consoCode = newConsoCode // ← Also assign consoCode to pick orders } } - + val suggestions = suggestedPickLotService.suggestionForPickOrders( SuggestedPickLotForPoRequest(pickOrders = pickOrders) ) - + val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) pickOrderRepository.saveAll(pickOrders) - + val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } ) @@ -1073,37 +1073,42 @@ open class PickOrderService( logger.info("saveSuggestedPickLots: $saveSuggestedPickLots") inventoryLotLineRepository.saveAll(inventoryLotLines) var precreated = 0 -saveSuggestedPickLots.forEach { lot -> - val polId = lot.pickOrderLine?.id - val illId = lot.suggestedLotLine?.id - if (polId != null && illId != null) { - try { - val existingLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) - if (existingLines.isEmpty()) { - val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null) - val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null) - - if (pickOrderLine != null && inventoryLotLine != null) { - val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply { - this.stockOut = savedStockOut - this.pickOrderLine = pickOrderLine - this.inventoryLotLine = inventoryLotLine - this.item = pickOrderLine.item // ✅ Add the required item field - this.status = com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status - this.qty = 0.0 + saveSuggestedPickLots.forEach { lot -> + val polId = lot.pickOrderLine?.id + val illId = lot.suggestedLotLine?.id + if (polId != null && illId != null) { + try { + val existingLines = + stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( + polId, + illId + ) + if (existingLines.isEmpty()) { + val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null) + val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null) + + if (pickOrderLine != null && inventoryLotLine != null) { + val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply { + this.stockOut = savedStockOut + this.pickOrderLine = pickOrderLine + this.inventoryLotLine = inventoryLotLine + this.item = pickOrderLine.item // ✅ Add the required item field + this.status = + com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status + this.qty = 0.0 + } + stockOutLIneRepository.save(line) + precreated++ + } else { + logger.warn("Could not find pickOrderLine (id=$polId) or inventoryLotLine (id=$illId)") + } + } + } catch (e: Exception) { + logger.warn("Failed to create stock out line for pickOrderLineId=$polId, inventoryLotLineId=$illId: ${e.message}") } - stockOutLIneRepository.save(line) - precreated++ - } else { - logger.warn("Could not find pickOrderLine (id=$polId) or inventoryLotLine (id=$illId)") } } - } catch (e: Exception) { - logger.warn("Failed to create stock out line for pickOrderLineId=$polId, inventoryLotLineId=$illId: ${e.message}") - } - } -} -logger.info("Precreated $precreated stock out lines for suggested lots on release.") + logger.info("Precreated $precreated stock out lines for suggested lots on release.") return MessageResponse( id = null, name = "Pick orders released successfully with inventory management", @@ -1113,7 +1118,7 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas errorPosition = null, entity = mapOf("consoCode" to newConsoCode) // ← Return the generated consoCode ) - + } catch (e: Exception) { return MessageResponse( id = null, @@ -1125,16 +1130,16 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas ) } } - + @Throws(IOException::class) @Transactional open fun completeStockOut(consoCode: String): MessageResponse { println("=== DEBUG: completeStockOut ===") println("consoCode: $consoCode") - + val stockOut = stockOutRepository.findByConsoPickOrderCode(consoCode).orElseThrow() println("StockOut ID: ${stockOut.id}") - + // ✅ FIXED: 直接通过 consoCode 查找相关的 stock out lines,而不依赖 stockOutId 关联 val stockOutLinesSql = """ SELECT sol.* @@ -1144,13 +1149,13 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas WHERE po.consoCode = :consoCode AND sol.deleted = false """.trimIndent() - + val stockOutLinesResult = jdbcDao.queryForList(stockOutLinesSql, mapOf("consoCode" to consoCode)) println("=== DEBUG: Stock Out Lines for consoCode $consoCode ===") stockOutLinesResult.forEach { row -> println("StockOutLine ID: ${row["id"]}, stockOutId: ${row["stockOutId"]}, status: ${row["status"]}, qty: ${row["qty"]}, pickOrderLineId: ${row["pickOrderLineId"]}") } - + // ✅ 将结果转换为 StockOutLine 对象 val stockOutLineIds = stockOutLinesResult.mapNotNull { row -> val id = row["id"] @@ -1163,37 +1168,37 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas println("Converted ID: $convertedId") convertedId } - + val stockOutLines = if (stockOutLineIds.isNotEmpty()) { stockOutLIneRepository.findAllById(stockOutLineIds) } else { emptyList() } - + println("Total stock out lines for consoCode $consoCode: ${stockOutLines.size}") - + stockOutLines.forEach { line -> println("Stock out line ${line.id}: status=${line.status}, qty=${line.qty}") } - + val unfinishedLines = stockOutLines.filter { it.status != StockOutLineStatus.COMPLETE.status && it.status != StockOutLineStatus.REJECTED.status } - + println("Unfinished lines: ${unfinishedLines.size}") - + if (unfinishedLines.isEmpty()) { stockOut.apply { this.status = StockOutStatus.COMPLETE.status } val savedStockOut = stockOutRepository.saveAndFlush(stockOut) - + // ✅ NEW APPROACH: Update through relationship chain // 1. Get all pick order lines from completed stock out lines val completedPickOrderLineIds = stockOutLines.mapNotNull { it.pickOrderLine?.id } println("Completed pick order line IDs: $completedPickOrderLineIds") - + // 2. Get all pick order lines and update their status if (completedPickOrderLineIds.isNotEmpty()) { val pickOrderLines = pickOrderLineRepository.findAllById(completedPickOrderLineIds) @@ -1203,11 +1208,11 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas } pickOrderLineRepository.saveAll(pickOrderLines) println("✅ Updated ${pickOrderLines.size} pick order lines to COMPLETED status") - + // 3. Get all unique pick order IDs from the lines val pickOrderIds = pickOrderLines.mapNotNull { it.pickOrder?.id }.distinct() println("Affected pick order IDs: $pickOrderIds") - + // 4. Check if each pick order is fully completed pickOrderIds.forEach { pickOrderId -> val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) @@ -1215,113 +1220,114 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas // Check if all lines of this pick order are completed val allLines = pickOrder.pickOrderLines val completedLines = allLines.filter { it.status == PickOrderLineStatus.COMPLETED } - + println("Pick order ${pickOrder.code}: ${completedLines.size}/${allLines.size} lines completed") - + if (completedLines.size == allLines.size && allLines.isNotEmpty()) { // All lines are completed, update pick order status pickOrder.status = PickOrderStatus.COMPLETED pickOrder.completeDate = LocalDateTime.now() pickOrderRepository.save(pickOrder) println("✅ Updated pick order ${pickOrder.code} to COMPLETED status") - - // ✅ 修改:通过 do_pick_order_line 查询(因为 do_pick_order.pick_order_id 可能为 null) -val doPickOrderLines = doPickOrderLineRepository.findByPickOrderIdAndDeletedFalse(pickOrderId) -val doPickOrderIds = doPickOrderLines.mapNotNull { it.doPickOrderId }.distinct() - -println("🔍 DEBUG: Found ${doPickOrderLines.size} do_pick_order_line records for pick order $pickOrderId") -println("🔍 DEBUG: Unique do_pick_order IDs: $doPickOrderIds") - -if (doPickOrderIds.isEmpty()) { - println("ℹ️ INFO: No do_pick_order records found - skipping record copying") -} else { - var copied = 0 - var deleted = 0 - - doPickOrderIds.forEach { doPickOrderId -> - val dpo = doPickOrderRepository.findById(doPickOrderId).orElse(null) - if (dpo == null) { - println("⚠️ WARNING: do_pick_order $doPickOrderId not found, skipping") - return@forEach - } - - println("🔍 Processing do_pick_order ID: ${dpo.id}, ticket: ${dpo.ticketNo}") - - // 2) 先复制 do_pick_order -> do_pick_order_record - val dpoRecord = DoPickOrderRecord( - storeId = dpo.storeId ?: "", - ticketNo = dpo.ticketNo ?: "", - ticketStatus = DoPickOrderStatus.completed, - truckId = dpo.truckId, - - truckDepartureTime = dpo.truckDepartureTime, - shopId = dpo.shopId, - handledBy = dpo.handledBy, - handlerName = dpo.handlerName, - doOrderId = dpo.doOrderId, - pickOrderCode = dpo.pickOrderCode, - deliveryOrderCode = dpo.deliveryOrderCode, - loadingSequence = dpo.loadingSequence, - ticketReleaseTime = dpo.ticketReleaseTime, - ticketCompleteDateTime = java.time.LocalDateTime.now(), - truckLanceCode = dpo.truckLanceCode, - shopCode = dpo.shopCode, - shopName = dpo.shopName, - requiredDeliveryDate = dpo.requiredDeliveryDate - ) - val savedHeader = doPickOrderRecordRepository.save(dpoRecord) - - // 3) 复制行 do_pick_order_line -> do_pick_order_line_record - val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!) - val lineRecords = lines.map { l: DoPickOrderLine -> - DoPickOrderLineRecord().apply { - this.doPickOrderId = savedHeader.id - this.pickOrderId = l.pickOrderId - this.doOrderId = l.doOrderId - this.pickOrderCode = l.pickOrderCode - this.deliveryOrderCode = l.deliveryOrderCode - this.status = l.status - } - } - if (lineRecords.isNotEmpty()) { - doPickOrderLineRecordRepository.saveAll(lineRecords) - } - copied++ - - // 4) 删除原行、原表 - if (lines.isNotEmpty()) doPickOrderLineRepository.deleteAll(lines) - doPickOrderRepository.delete(dpo) - deleted++ - - // 5) 同步更新 delivery_order 状态为 completed - dpo.doOrderId?.let { doId -> - val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId) - if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) { - deliveryOrder.status = DeliveryOrderStatus.COMPLETED - deliveryOrderRepository.save(deliveryOrder) - println("✅ Updated delivery order $doId to COMPLETED") - } - } - } - println("✅ Copied $copied do_pick_order to record and deleted $deleted original(s)") -} + // ✅ 修改:通过 do_pick_order_line 查询(因为 do_pick_order.pick_order_id 可能为 null) + val doPickOrderLines = + doPickOrderLineRepository.findByPickOrderIdAndDeletedFalse(pickOrderId) + val doPickOrderIds = doPickOrderLines.mapNotNull { it.doPickOrderId }.distinct() + + println("🔍 DEBUG: Found ${doPickOrderLines.size} do_pick_order_line records for pick order $pickOrderId") + println("🔍 DEBUG: Unique do_pick_order IDs: $doPickOrderIds") + + if (doPickOrderIds.isEmpty()) { + println("ℹ️ INFO: No do_pick_order records found - skipping record copying") + } else { + var copied = 0 + var deleted = 0 + + doPickOrderIds.forEach { doPickOrderId -> + val dpo = doPickOrderRepository.findById(doPickOrderId).orElse(null) + if (dpo == null) { + println("⚠️ WARNING: do_pick_order $doPickOrderId not found, skipping") + return@forEach + } + + println("🔍 Processing do_pick_order ID: ${dpo.id}, ticket: ${dpo.ticketNo}") + + // 2) 先复制 do_pick_order -> do_pick_order_record + val dpoRecord = DoPickOrderRecord( + storeId = dpo.storeId ?: "", + ticketNo = dpo.ticketNo ?: "", + ticketStatus = DoPickOrderStatus.completed, + truckId = dpo.truckId, + + truckDepartureTime = dpo.truckDepartureTime, + shopId = dpo.shopId, + handledBy = dpo.handledBy, + handlerName = dpo.handlerName, + doOrderId = dpo.doOrderId, + pickOrderCode = dpo.pickOrderCode, + deliveryOrderCode = dpo.deliveryOrderCode, + loadingSequence = dpo.loadingSequence, + ticketReleaseTime = dpo.ticketReleaseTime, + ticketCompleteDateTime = java.time.LocalDateTime.now(), + truckLanceCode = dpo.truckLanceCode, + shopCode = dpo.shopCode, + shopName = dpo.shopName, + requiredDeliveryDate = dpo.requiredDeliveryDate + ) + val savedHeader = doPickOrderRecordRepository.save(dpoRecord) + + // 3) 复制行 do_pick_order_line -> do_pick_order_line_record + val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!) + val lineRecords = lines.map { l: DoPickOrderLine -> + DoPickOrderLineRecord().apply { + this.doPickOrderId = savedHeader.id + this.pickOrderId = l.pickOrderId + this.doOrderId = l.doOrderId + this.pickOrderCode = l.pickOrderCode + this.deliveryOrderCode = l.deliveryOrderCode + this.status = l.status + } + } + if (lineRecords.isNotEmpty()) { + doPickOrderLineRecordRepository.saveAll(lineRecords) + } + copied++ + + // 4) 删除原行、原表 + if (lines.isNotEmpty()) doPickOrderLineRepository.deleteAll(lines) + doPickOrderRepository.delete(dpo) + deleted++ + + // 5) 同步更新 delivery_order 状态为 completed + dpo.doOrderId?.let { doId -> + val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId) + if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) { + deliveryOrder.status = DeliveryOrderStatus.COMPLETED + deliveryOrderRepository.save(deliveryOrder) + println("✅ Updated delivery order $doId to COMPLETED") + } + } + } + + println("✅ Copied $copied do_pick_order to record and deleted $deleted original(s)") + } // ✅ 添加:直接使用 Repository 处理 JO pick order(避免循环依赖) if (pickOrder.jobOrder != null) { val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId) - joPickOrders.forEach { + joPickOrders.forEach { it.ticketCompleteTime = LocalDateTime.now() // 记录拣货完成时间 // match_status 保持为 'pending',等待二次扫描 } joPickOrderRepository.saveAll(joPickOrders) - + val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId) - joPickOrderRecords.forEach { + joPickOrderRecords.forEach { it.ticketCompleteTime = LocalDateTime.now() // 记录拣货完成时间 // match_status 保持为 'pending',等待二次扫描 } joPickOrderRecordRepository.saveAll(joPickOrderRecords) - + println("✅ Set jo_pick_order ticketCompleteTime for pick order ${pickOrderId}, waiting for second scan") } } @@ -1330,7 +1336,7 @@ if (doPickOrderIds.isEmpty()) { // After updating pick order to COMPLETED status, add: } - + return MessageResponse( id = savedStockOut.id, name = savedStockOut.consoPickOrderCode ?: savedStockOut.deliveryOrderCode, @@ -1379,20 +1385,20 @@ if (doPickOrderIds.isEmpty()) { WHERE po.consoCode = :consoCode AND sol.deleted = false """.trimIndent() - - val stockOutLinesResult = jdbcDao.queryForList(stockOutLinesSql, mapOf("consoCode" to consoCode)) - val stockOutLineIds = stockOutLinesResult.mapNotNull { row -> - when (val id = row["id"]) { - is Number -> id.toLong() - is String -> id.toLongOrNull() - else -> null + + val stockOutLinesResult = jdbcDao.queryForList(stockOutLinesSql, mapOf("consoCode" to consoCode)) + val stockOutLineIds = stockOutLinesResult.mapNotNull { row -> + when (val id = row["id"]) { + is Number -> id.toLong() + is String -> id.toLongOrNull() + else -> null + } + } + val stockOutLines = if (stockOutLineIds.isNotEmpty()) { + stockOutLIneRepository.findAllById(stockOutLineIds) + } else { + emptyList() } - } - val stockOutLines = if (stockOutLineIds.isNotEmpty()) { - stockOutLIneRepository.findAllById(stockOutLineIds) - } else { - emptyList() - } val unfinishedLines = stockOutLines.filter { it.status != StockOutLineStatus.COMPLETE.status && it.status != StockOutLineStatus.REJECTED.status @@ -1429,7 +1435,7 @@ if (doPickOrderIds.isEmpty()) { } } - + open fun createGroup(name: String, targetDate: LocalDate, pickOrderId: Long?): PickOrderGroup { val group = PickOrderGroup().apply { this.name = name @@ -1438,7 +1444,7 @@ if (doPickOrderIds.isEmpty()) { } return pickOrderGroupRepository.save(group) } - + fun getGroupsByPickOrderId(pickOrderId: Long): List { return pickOrderGroupRepository.findByPickOrderIdAndDeletedIsFalse(pickOrderId) } @@ -1450,7 +1456,7 @@ if (doPickOrderIds.isEmpty()) { WHERE deleted = false ORDER BY name ASC """.trimIndent() - + return jdbcDao.queryForList(sql, emptyMap()) } @@ -1465,10 +1471,10 @@ if (doPickOrderIds.isEmpty()) { // Sort by numeric part of the name (A001 -> 1, A006 -> 6) val latestName = allGroups .filter { it.name?.startsWith("A") == true } - .maxByOrNull { group -> - group.name?.substring(1)?.toIntOrNull() ?: 0 + .maxByOrNull { group -> + group.name?.substring(1)?.toIntOrNull() ?: 0 }?.name ?: "A000" - + // Generate next group name val currentNumber = latestName.substring(1).toIntOrNull() ?: 0 val nextNumber = currentNumber + 1 @@ -1476,17 +1482,17 @@ if (doPickOrderIds.isEmpty()) { } else { "A001" // If no groups exist, return A001 } - + // Use the new flexible createNewGroups method val request = SavePickOrderGroupRequest( names = listOf(nextGroupName), // Create new group with the generated name targetDate = null, pickOrderId = null ) - + return createNewGroups(request) } - + fun getLatestGroupName(): String { // Use allPickOrdersGroup() instead of the failing repository method val allGroups = allPickOrdersGroup() @@ -1494,10 +1500,10 @@ if (doPickOrderIds.isEmpty()) { // Sort by numeric part of the name (A001 -> 1, A006 -> 6) val latestName = allGroups .filter { it.name?.startsWith("A") == true } - .maxByOrNull { group -> - group.name?.substring(1)?.toIntOrNull() ?: 0 + .maxByOrNull { group -> + group.name?.substring(1)?.toIntOrNull() ?: 0 }?.name ?: "A000" - + // Generate next group name val currentNumber = latestName.substring(1).toIntOrNull() ?: 0 val nextNumber = currentNumber + 1 @@ -1510,7 +1516,7 @@ if (doPickOrderIds.isEmpty()) { open fun createNewGroups(request: SavePickOrderGroupRequest): MessageResponse { val updatedGroups = mutableListOf() val createdGroups = mutableListOf() - + // Case 1: Update existing groups by IDs request.groupIds?.forEach { groupId -> val group = pickOrderGroupRepository.findById(groupId).orElse(null) @@ -1521,7 +1527,7 @@ if (doPickOrderIds.isEmpty()) { updatedGroups.add(savedGroup) } } - + // Case 2: Create new groups by names request.names?.forEach { name -> val group = PickOrderGroup().apply { @@ -1532,10 +1538,10 @@ if (doPickOrderIds.isEmpty()) { val savedGroup = pickOrderGroupRepository.save(group) createdGroups.add(savedGroup) } - + val totalGroups = updatedGroups.size + createdGroups.size val allGroups = updatedGroups + createdGroups - + return MessageResponse( id = allGroups.firstOrNull()?.id, name = allGroups.map { it.name ?: "" }.joinToString(", "), @@ -1567,7 +1573,7 @@ if (doPickOrderIds.isEmpty()) { // ✅ 添加检查是否需要 resuggest 的方法 private fun checkIfNeedsResuggest(pickOrderLineId: Long): Boolean { println("🔍 checkIfNeedsResuggest called with pickOrderLineId: $pickOrderLineId") - + // ✅ 首先执行一个调试查询来查看实际的数值 val debugSql = """ SELECT @@ -1591,7 +1597,7 @@ if (doPickOrderIds.isEmpty()) { LEFT JOIN fpsmsdb.item_uom sales_iu ON sales_iu.itemId = il.itemId AND sales_iu.salesUnit = true AND sales_iu.deleted = false WHERE spl.pickOrderLineId = :pickOrderLineId """.trimIndent() - + println("🔍 Debug SQL: $debugSql") val debugResult = jdbcDao.queryForList(debugSql, mapOf("pickOrderLineId" to pickOrderLineId)) println("🔍 Debug result:") @@ -1611,7 +1617,7 @@ if (doPickOrderIds.isEmpty()) { println(" Is Insufficient: ${convertedAvailableQty < suggestedQty}") println(" ---") } - + val checkSql = """ SELECT COUNT(*) as totalSuggestions, @@ -1646,7 +1652,7 @@ if (doPickOrderIds.isEmpty()) { println("🔍 With parameters: pickOrderLineId = $pickOrderLineId") val result = jdbcDao.queryForList(checkSql, mapOf("pickOrderLineId" to pickOrderLineId)) - + println("🔍 SQL result size: ${result.size}") if (result.isNotEmpty()) { @@ -1668,13 +1674,13 @@ if (doPickOrderIds.isEmpty()) { // 检查是否需要 resuggest 的条件 val needsResuggest = ( - unavailableLots > 0 || - expiredLots > 0 || - insufficientStockLots > 0 || - problematicStockOutLines > 0 || - unitMismatchLots > 0 || // ✅ 单位不一致检查 - totalSuggestions == 0 // 没有建议也需要 resuggest - ) + unavailableLots > 0 || + expiredLots > 0 || + insufficientStockLots > 0 || + problematicStockOutLines > 0 || + unitMismatchLots > 0 || // ✅ 单位不一致检查 + totalSuggestions == 0 // 没有建议也需要 resuggest + ) println("Needs resuggest: $needsResuggest") return needsResuggest @@ -1687,29 +1693,33 @@ if (doPickOrderIds.isEmpty()) { // Add this new method to PickOrderService.kt -@Transactional(rollbackFor = [java.lang.Exception::class]) -open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo: String, userId: Long): MessageResponse { - try { - println("=== DEBUG: autoAssignAndReleasePickOrderByStoreAndTicket ===") - println("storeId: $storeId, ticketNo: $ticketNo, userId: $userId") + @Transactional(rollbackFor = [java.lang.Exception::class]) + open fun autoAssignAndReleasePickOrderByStoreAndTicket( + storeId: String, + ticketNo: String, + userId: Long + ): MessageResponse { + try { + println("=== DEBUG: autoAssignAndReleasePickOrderByStoreAndTicket ===") + println("storeId: $storeId, ticketNo: $ticketNo, userId: $userId") - val zero = BigDecimal.ZERO - val releasedBy = SecurityUtils.getUser().getOrNull() - val user = userService.find(userId).orElse(null) - - if (user == null) { - return MessageResponse( - id = null, - name = "User not found", - code = "ERROR", - type = "pickorder", - message = "User with ID $userId not found", - errorPosition = null - ) - } + val zero = BigDecimal.ZERO + val releasedBy = SecurityUtils.getUser().getOrNull() + val user = userService.find(userId).orElse(null) - // Find the do_pick_order by store_id and ticket_no - val sql = """ + if (user == null) { + return MessageResponse( + id = null, + name = "User not found", + code = "ERROR", + type = "pickorder", + message = "User with ID $userId not found", + errorPosition = null + ) + } + + // Find the do_pick_order by store_id and ticket_no + val sql = """ SELECT DISTINCT dpo.pick_order_id AS pickOrderId FROM do_pick_order dpo WHERE dpo.deleted = false @@ -1717,200 +1727,206 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo AND dpo.ticket_no = :ticketNo AND dpo.pick_order_id IS NOT NULL """.trimIndent() - - val idRows = jdbcDao.queryForList(sql, mapOf("storeId" to storeId, "ticketNo" to ticketNo)) - val pickOrderIdsByTicket = idRows.mapNotNull { row -> - when (val id = row["pickOrderId"]) { - is Number -> id.toLong() - is String -> id.toLongOrNull() - else -> null - } - }.toSet() - - println("Candidate pickOrderIds by ticket $ticketNo: $pickOrderIdsByTicket") - if (pickOrderIdsByTicket.isEmpty()) { - return MessageResponse( - id = null, - name = "No pick orders found", - code = "NO_ORDERS", - type = "pickorder", - message = "No pick orders found for store $storeId with ticket $ticketNo", - errorPosition = null - ) - } - - // Check if user already has pick orders in progress - val userExistingOrders = pickOrderRepository.findAllByAssignToAndStatusIn( - user, - listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) - ).filter { it.deliveryOrder != null } + val idRows = jdbcDao.queryForList(sql, mapOf("storeId" to storeId, "ticketNo" to ticketNo)) + val pickOrderIdsByTicket = idRows.mapNotNull { row -> + when (val id = row["pickOrderId"]) { + is Number -> id.toLong() + is String -> id.toLongOrNull() + else -> null + } + }.toSet() - if (userExistingOrders.isNotEmpty()) { - println("🔍 DEBUG: User $userId already has ${userExistingOrders.size} pick orders in progress") - return MessageResponse( - id = null, - name = "User already has pick orders", - code = "USER_BUSY", - type = "pickorder", - message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.", - errorPosition = null, - entity = mapOf( - "existingOrders" to userExistingOrders.map { mapOf( - "id" to it.id, - "code" to it.code, - "status" to it.status?.value - )} - ) - ) - } + println("Candidate pickOrderIds by ticket $ticketNo: $pickOrderIdsByTicket") - // Find available pick orders for the specific ticket - val availablePickOrders = pickOrderRepository - .findAll() - .filter { it.id != null && pickOrderIdsByTicket.contains(it.id!!) } - .filter { it.deliveryOrder != null } - .filter { it.assignTo == null } - .filter { it.status == PickOrderStatus.PENDING || it.status == PickOrderStatus.RELEASED } - .sortedBy { it.targetDate } - .take(1) // Take only one pick order - - if (availablePickOrders.isEmpty()) { - return MessageResponse( - id = null, - name = "No available pick orders", - code = "NO_ORDERS", - type = "pickorder", - message = "No unassigned pick orders available for store $storeId with ticket $ticketNo", - errorPosition = null - ) - } + if (pickOrderIdsByTicket.isEmpty()) { + return MessageResponse( + id = null, + name = "No pick orders found", + code = "NO_ORDERS", + type = "pickorder", + message = "No pick orders found for store $storeId with ticket $ticketNo", + errorPosition = null + ) + } - val selected = availablePickOrders.first() - val currUser = SecurityUtils.getUser().orElseThrow() - - // If still PENDING, perform full release flow - if (selected.status == PickOrderStatus.PENDING) { - val newConsoCode = assignConsoCode() + // Check if user already has pick orders in progress + val userExistingOrders = pickOrderRepository.findAllByAssignToAndStatusIn( + user, + listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) + ).filter { it.deliveryOrder != null } - val stockOut = StockOut().apply { - this.type = "job" - this.consoPickOrderCode = newConsoCode - this.status = StockOutStatus.PENDING.status - this.handler = currUser.id + if (userExistingOrders.isNotEmpty()) { + println("🔍 DEBUG: User $userId already has ${userExistingOrders.size} pick orders in progress") + return MessageResponse( + id = null, + name = "User already has pick orders", + code = "USER_BUSY", + type = "pickorder", + message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.", + errorPosition = null, + entity = mapOf( + "existingOrders" to userExistingOrders.map { + mapOf( + "id" to it.id, + "code" to it.code, + "status" to it.status?.value + ) + } + ) + ) } - val savedStockOut = stockOutRepository.saveAndFlush(stockOut) - selected.apply { - this.releasedBy = releasedBy - status = PickOrderStatus.RELEASED - this.consoCode = newConsoCode + // Find available pick orders for the specific ticket + val availablePickOrders = pickOrderRepository + .findAll() + .filter { it.id != null && pickOrderIdsByTicket.contains(it.id!!) } + .filter { it.deliveryOrder != null } + .filter { it.assignTo == null } + .filter { it.status == PickOrderStatus.PENDING || it.status == PickOrderStatus.RELEASED } + .sortedBy { it.targetDate } + .take(1) // Take only one pick order + + if (availablePickOrders.isEmpty()) { + return MessageResponse( + id = null, + name = "No available pick orders", + code = "NO_ORDERS", + type = "pickorder", + message = "No unassigned pick orders available for store $storeId with ticket $ticketNo", + errorPosition = null + ) } - val suggestions = suggestedPickLotService.suggestionForPickOrders( - SuggestedPickLotForPoRequest(pickOrders = listOf(selected)) - ) - val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) - pickOrderRepository.saveAndFlush(selected) + val selected = availablePickOrders.first() + val currUser = SecurityUtils.getUser().orElseThrow() - val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( - saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } - ) - saveSuggestedPickLots.forEach { lot -> - val lotLineId = lot.suggestedLotLine?.id - if (lotLineId != null) { - val idx = inventoryLotLines.indexOf(lot.suggestedLotLine) - if (idx >= 0) { - val currentHold = inventoryLotLines[idx].holdQty ?: zero - val addHold = lot.qty ?: zero - inventoryLotLines[idx].holdQty = currentHold.plus(addHold) + // If still PENDING, perform full release flow + if (selected.status == PickOrderStatus.PENDING) { + val newConsoCode = assignConsoCode() + + val stockOut = StockOut().apply { + this.type = "job" + this.consoPickOrderCode = newConsoCode + this.status = StockOutStatus.PENDING.status + this.handler = currUser.id + } + val savedStockOut = stockOutRepository.saveAndFlush(stockOut) + + selected.apply { + this.releasedBy = releasedBy + status = PickOrderStatus.RELEASED + this.consoCode = newConsoCode + } + + val suggestions = suggestedPickLotService.suggestionForPickOrders( + SuggestedPickLotForPoRequest(pickOrders = listOf(selected)) + ) + val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) + pickOrderRepository.saveAndFlush(selected) + + val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( + saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } + ) + saveSuggestedPickLots.forEach { lot -> + val lotLineId = lot.suggestedLotLine?.id + if (lotLineId != null) { + val idx = inventoryLotLines.indexOf(lot.suggestedLotLine) + if (idx >= 0) { + val currentHold = inventoryLotLines[idx].holdQty ?: zero + val addHold = lot.qty ?: zero + inventoryLotLines[idx].holdQty = currentHold.plus(addHold) + } } } - } - inventoryLotLineRepository.saveAll(inventoryLotLines) + inventoryLotLineRepository.saveAll(inventoryLotLines) - // Pre-create stock out lines - var precreated = 0 - saveSuggestedPickLots.forEach { lot -> - val polId = lot.pickOrderLine?.id - val illId = lot.suggestedLotLine?.id - if (polId != null && illId != null) { - val existing = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) - if (existing.isEmpty()) { - val pol = pickOrderLineRepository.findById(polId).orElse(null) - val ill = inventoryLotLineRepository.findById(illId).orElse(null) - if (pol != null && ill != null) { - val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply { - this.stockOut = savedStockOut - this.pickOrderLine = pol - this.inventoryLotLine = ill - this.item = pol.item - this.status = com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status - this.qty = 0.0 + // Pre-create stock out lines + var precreated = 0 + saveSuggestedPickLots.forEach { lot -> + val polId = lot.pickOrderLine?.id + val illId = lot.suggestedLotLine?.id + if (polId != null && illId != null) { + val existing = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( + polId, + illId + ) + if (existing.isEmpty()) { + val pol = pickOrderLineRepository.findById(polId).orElse(null) + val ill = inventoryLotLineRepository.findById(illId).orElse(null) + if (pol != null && ill != null) { + val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply { + this.stockOut = savedStockOut + this.pickOrderLine = pol + this.inventoryLotLine = ill + this.item = pol.item + this.status = + com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status + this.qty = 0.0 + } + stockOutLIneRepository.save(line) + precreated++ } - stockOutLIneRepository.save(line) - precreated++ } } } + println("Precreated $precreated stock out lines for ticket $ticketNo") } - println("Precreated $precreated stock out lines for ticket $ticketNo") - } - // Assign user (both pending->released and already released) - selected.assignTo = user - pickOrderRepository.saveAndFlush(selected) - - // Update do_pick_order_record entries - val existingRecords = doPickOrderRecordRepository.findByPickOrderId(selected.id!!) - println("🔍 DEBUG: Found ${existingRecords.size} existing DoPickOrderRecord entries for pick order ${selected.id}") - - if (existingRecords.isNotEmpty()) { - existingRecords.forEach { record -> - record.handledBy = user.id - record.ticketStatus = DoPickOrderStatus.released - record.ticketReleaseTime = LocalDateTime.now() // ✅ 添加这行 - println("🔍 DEBUG: Updating existing DoPickOrderRecord ID: ${record.id} - handledBy: ${user.id}, status: released") + // Assign user (both pending->released and already released) + selected.assignTo = user + pickOrderRepository.saveAndFlush(selected) + + // Update do_pick_order_record entries + val existingRecords = doPickOrderRecordRepository.findByPickOrderId(selected.id!!) + println("🔍 DEBUG: Found ${existingRecords.size} existing DoPickOrderRecord entries for pick order ${selected.id}") + + if (existingRecords.isNotEmpty()) { + existingRecords.forEach { record -> + record.handledBy = user.id + record.ticketStatus = DoPickOrderStatus.released + record.ticketReleaseTime = LocalDateTime.now() // ✅ 添加这行 + println("🔍 DEBUG: Updating existing DoPickOrderRecord ID: ${record.id} - handledBy: ${user.id}, status: released") + } + doPickOrderRecordRepository.saveAll(existingRecords) + println("✅ Updated ${existingRecords.size} existing DoPickOrderRecord entries") } - doPickOrderRecordRepository.saveAll(existingRecords) - println("✅ Updated ${existingRecords.size} existing DoPickOrderRecord entries") - } - - doPickOrderService.updateHandledByForPickOrder(selected.id!!, user.id!!) - println("✅ Updated DoPickOrder handledBy to user $userId for pick order ${selected.id}") - - return MessageResponse( - id = null, - name = "Pick order assigned by ticket", - code = "SUCCESS", - type = "pickorder", - message = "Assigned to user $userId for store $storeId with ticket $ticketNo", - errorPosition = null, - entity = mapOf( - "pickOrderIds" to listOf(selected.id!!), - "storeId" to storeId, - "ticketNo" to ticketNo, - "status" to selected.status?.value + + doPickOrderService.updateHandledByForPickOrder(selected.id!!, user.id!!) + println("✅ Updated DoPickOrder handledBy to user $userId for pick order ${selected.id}") + + return MessageResponse( + id = null, + name = "Pick order assigned by ticket", + code = "SUCCESS", + type = "pickorder", + message = "Assigned to user $userId for store $storeId with ticket $ticketNo", + errorPosition = null, + entity = mapOf( + "pickOrderIds" to listOf(selected.id!!), + "storeId" to storeId, + "ticketNo" to ticketNo, + "status" to selected.status?.value + ) ) - ) - - } catch (e: Exception) { - e.printStackTrace() - return MessageResponse( - id = null, - name = "Failed to auto-assign by ticket", - code = "ERROR", - type = "pickorder", - message = "Failed to auto-assign by ticket: ${e.message}", - errorPosition = null - ) - } + + } catch (e: Exception) { + e.printStackTrace() + return MessageResponse( + id = null, + name = "Failed to auto-assign by ticket", + code = "ERROR", + type = "pickorder", + message = "Failed to auto-assign by ticket: ${e.message}", + errorPosition = null + ) + } } open fun getPickOrderDetailsOptimized(pickOrderIds: List): GetPickOrderInfoResponse { val today = LocalDate.now() val zero = BigDecimal.ZERO - + if (pickOrderIds.isEmpty()) { return GetPickOrderInfoResponse( consoCode = null, @@ -1918,9 +1934,9 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo items = emptyList() ) } - + val pickOrderIdsStr = pickOrderIds.joinToString(",") - + val sql = """ SELECT -- Pick Order Information @@ -2009,26 +2025,26 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo po.code ASC, i.code ASC """.trimIndent() - + println("🔍 Executing optimized SQL: $sql") - + val result = jdbcDao.queryForList(sql, emptyMap()) - + println("✅ Optimized result count: ${result.size}") - + // ✅ Transform the flat data into the expected nested structure val pickOrdersMap = mutableMapOf() var consoCode: String? = null // ✅ 用于存储 consoCode - + result.forEach { row -> val pickOrderId = (row["pickOrderId"] as Number).toLong() val pickOrderLineId = (row["pickOrderLineId"] as Number).toLong() - + // ✅ 获取 consoCode(所有行应该有相同的 consoCode) if (consoCode == null) { consoCode = row["pickOrderConsoCode"] as String? } - + // Create or get pick order if (!pickOrdersMap.containsKey(pickOrderId)) { pickOrdersMap[pickOrderId] = GetPickOrderInfo( @@ -2043,9 +2059,9 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo pickOrderLines = mutableListOf() ) } - + val pickOrder = pickOrdersMap[pickOrderId]!! - + // Create pick order line val pickOrderLine = GetPickOrderLineInfo( id = pickOrderLineId, @@ -2060,11 +2076,11 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo suggestedList = emptyList(), pickedQty = (row["totalPickedQty"] as Number).toDouble().toBigDecimal() // ✅ Convert to BigDecimal ) - + // ✅ Fix the add method call (pickOrder.pickOrderLines as MutableList).add(pickOrderLine) } - + return GetPickOrderInfoResponse( consoCode = consoCode, // ✅ 使用获取到的 consoCode pickOrders = pickOrdersMap.values.toList(), @@ -2075,7 +2091,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo open fun getPickOrderDetailsOptimizedByUser(userId: Long): GetPickOrderInfoResponse { val today = LocalDate.now() val zero = BigDecimal.ZERO - + // Get all released pick order IDs for the user val allPickOrders = pickOrderRepository.findAll() val releasedPickOrderIds = allPickOrders @@ -2083,7 +2099,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo .filter { it.assignTo?.id == userId } .filter { it.type?.value != "do" && it.type?.value != "jo" } // ✅ 排除 do 和 job 类型 .map { it.id!! } - + if (releasedPickOrderIds.isEmpty()) { return GetPickOrderInfoResponse( consoCode = null, @@ -2091,7 +2107,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo items = emptyList() ) } - + // Use the existing optimized method return getPickOrderDetailsOptimized(releasedPickOrderIds) } @@ -2354,20 +2370,23 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo ) } } + @Transactional(rollbackFor = [java.lang.Exception::class]) open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): MessageResponse { try { println("=== DEBUG: autoAssignAndReleasePickOrderByStore ===") println("userId: $userId, storeId: $storeId") - + val zero = BigDecimal.ZERO val releasedBy = SecurityUtils.getUser().getOrNull() val user = userService.find(userId).orElse(null) if (user == null) { - return MessageResponse(id = null, name = "User not found", code = "ERROR", type = "pickorder", - message = "User with ID $userId not found", errorPosition = null) + return MessageResponse( + id = null, name = "User not found", code = "ERROR", type = "pickorder", + message = "User with ID $userId not found", errorPosition = null + ) } - + val sql = """ SELECT DISTINCT dpo.pick_order_id AS pickOrderId FROM do_pick_order dpo @@ -2385,40 +2404,44 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo } }.toSet() println("Candidate pickOrderIds by store $storeId: $pickOrderIdsByStore") - + if (pickOrderIdsByStore.isEmpty()) { - return MessageResponse(id = null, name = "No pick orders", code = "NO_ORDERS", type = "pickorder", - message = "No pending pick orders found for store $storeId", errorPosition = null) + return MessageResponse( + id = null, name = "No pick orders", code = "NO_ORDERS", type = "pickorder", + message = "No pending pick orders found for store $storeId", errorPosition = null + ) } - + // ✅ Check if user already has pick orders in progress val userExistingOrders = pickOrderRepository.findAllByAssignToAndStatusIn( user, listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED) ).filter { it.deliveryOrder != null } - + if (userExistingOrders.isNotEmpty()) { println("🔍 DEBUG: User $userId already has ${userExistingOrders.size} pick orders in progress:") userExistingOrders.forEach { po -> println("🔍 DEBUG: Existing order ${po.id}: code=${po.code}, status=${po.status}") } return MessageResponse( - id = null, - name = "User already has pick orders", - code = "USER_BUSY", + id = null, + name = "User already has pick orders", + code = "USER_BUSY", type = "pickorder", - message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.", + message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.", errorPosition = null, entity = mapOf( - "existingOrders" to userExistingOrders.map { mapOf( - "id" to it.id, - "code" to it.code, - "status" to it.status?.value - )} + "existingOrders" to userExistingOrders.map { + mapOf( + "id" to it.id, + "code" to it.code, + "status" to it.status?.value + ) + } ) ) } - + val availablePickOrders = pickOrderRepository .findAll() .filter { it.id != null && pickOrderIdsByStore.contains(it.id!!) } @@ -2427,19 +2450,21 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo .filter { it.status == PickOrderStatus.PENDING || it.status == PickOrderStatus.RELEASED } .sortedBy { it.targetDate } .take(1) - + if (availablePickOrders.isEmpty()) { - return MessageResponse(id = null, name = "No available pick orders", code = "NO_ORDERS", type = "pickorder", - message = "No unassigned pick orders available for store $storeId", errorPosition = null) + return MessageResponse( + id = null, name = "No available pick orders", code = "NO_ORDERS", type = "pickorder", + message = "No unassigned pick orders available for store $storeId", errorPosition = null + ) } - + val selected = availablePickOrders.first() val currUser = SecurityUtils.getUser().orElseThrow() - + // If still PENDING, perform full release flow if (selected.status == PickOrderStatus.PENDING) { val newConsoCode = assignConsoCode() - + val stockOut = StockOut().apply { this.type = "job" this.consoPickOrderCode = newConsoCode @@ -2447,19 +2472,19 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo this.handler = currUser.id } val savedStockOut = stockOutRepository.saveAndFlush(stockOut) - + selected.apply { this.releasedBy = releasedBy status = PickOrderStatus.RELEASED this.consoCode = newConsoCode } - + val suggestions = suggestedPickLotService.suggestionForPickOrders( SuggestedPickLotForPoRequest(pickOrders = listOf(selected)) ) val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) pickOrderRepository.saveAndFlush(selected) - + val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } ) @@ -2475,13 +2500,16 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo } } inventoryLotLineRepository.saveAll(inventoryLotLines) - + var precreated = 0 saveSuggestedPickLots.forEach { lot -> val polId = lot.pickOrderLine?.id val illId = lot.suggestedLotLine?.id if (polId != null && illId != null) { - val existing = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) + val existing = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( + polId, + illId + ) if (existing.isEmpty()) { val pol = pickOrderLineRepository.findById(polId).orElse(null) val ill = inventoryLotLineRepository.findById(illId).orElse(null) @@ -2491,7 +2519,8 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo this.pickOrderLine = pol this.inventoryLotLine = ill this.item = pol.item - this.status = com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status + this.status = + com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status this.qty = 0.0 } stockOutLIneRepository.save(line) @@ -2502,19 +2531,19 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo } println("Precreated $precreated stock out lines for store $storeId") } - + // Assign user (both pending->released and already released) selected.assignTo = user pickOrderRepository.saveAndFlush(selected) - + // ✅ 更新 DoPickOrderRecord,填充新字段 val deliveryOrder = selected.deliveryOrder if (deliveryOrder != null) { val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) - + println("🔍 DEBUG: Target date: $targetDate, Date prefix: $datePrefix") - + // ✅ Find truck by shop ID with earliest departure time val truck = deliveryOrder.shop?.id?.let { shopId -> println("🔍 DEBUG: Looking for truck with shop ID: $shopId") @@ -2524,24 +2553,24 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo println("🔍 DEBUG: Selected truck: ID=${selectedTruck?.id}, DepartureTime=${selectedTruck?.departureTime}") selectedTruck } - + // ✅ 根据 truck 的 Store_id 字段确定 storeId val determinedStoreId = when (truck?.storeId) { 4 -> "4/F" 2 -> "2/F" else -> "2/F" } - + // Get next ticket number for this date and store val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix, determinedStoreId) println("🔍 DEBUG: Next ticket number: $nextTicketNumber") - + println("🔍 DEBUG: Processing ${deliveryOrder.deliveryOrderLines.size} delivery order lines") - + // ✅ UPDATE existing do_pick_order_record entries instead of creating new ones val existingRecords = doPickOrderRecordRepository.findByPickOrderId(selected.id!!) println("🔍 DEBUG: Found ${existingRecords.size} existing DoPickOrderRecord entries for pick order ${selected.id}") - + if (existingRecords.isNotEmpty()) { // ✅ Update existing records existingRecords.forEach { record -> @@ -2562,7 +2591,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo println("⚠️ No existing DoPickOrderRecord entries found, creating new ones") deliveryOrder.deliveryOrderLines.forEach { line -> println("🔍 DEBUG: Processing line - Store ID: $determinedStoreId") - + val doPickOrderRecord = DoPickOrderRecord( storeId = determinedStoreId, ticketNo = nextTicketNumber, @@ -2578,15 +2607,15 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo shopName = deliveryOrder.shop?.name, requiredDeliveryDate = targetDate ) - + println("🔍 DEBUG: Creating new DoPickOrderRecord - Store: $determinedStoreId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") - + val savedDoPickOrderRecord = doPickOrderRecordRepository.save(doPickOrderRecord) println("🔍 DEBUG: Saved new DoPickOrderRecord - ID: ${savedDoPickOrderRecord.id}") } } } - + doPickOrderService.updateHandledByForPickOrder(selected.id!!, user.id!!) doPickOrderService.updateRecordHandledByForPickOrder(selected.id!!, user.id!!) // ✅ 添加这行 println("✅ Updated DoPickOrder and DoPickOrderRecord handledBy to user $userId for pick order ${selected.id}") @@ -2598,8 +2627,12 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo type = "pickorder", message = "Assigned to user $userId for store $storeId", errorPosition = null, - entity = mapOf("pickOrderIds" to listOf(selected.id!!), "storeId" to storeId, "status" to selected.status?.value) - ) + entity = mapOf( + "pickOrderIds" to listOf(selected.id!!), + "storeId" to storeId, + "status" to selected.status?.value + ) + ) } catch (e: Exception) { e.printStackTrace() return MessageResponse( @@ -2608,40 +2641,41 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo ) } } + open fun getAllPickOrderLotsWithDetailsWithAutoAssign(userId: Long): List> { println("=== Debug: getAllPickOrderLotsWithDetailsWithAutoAssign ===") println("today: ${LocalDate.now()}") println("userId filter: $userId") - + // ✅ First attempt auto-assignment for the user println("🎯 Attempting auto-assignment for user $userId") val assignedPickOrderResponse = autoAssignAndReleasePickOrder(userId) - + // Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId) val user = userService.find(userId).orElse(null) if (user == null) { println("❌ User not found: $userId") return emptyList() } - + // Get all pick orders assigned to user with PENDING or RELEASED status that have doId val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( user, - listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED,PickOrderStatus.COMPLETED) - + listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) + ).filter { it.deliveryOrder != null } // Only pick orders with doId println("🔍 DEBUG: All assigned pick orders: ${allAssignedPickOrders.size}") val pickOrderIds = allAssignedPickOrders.map { it.id!! } println("🔍 DEBUG: All assigned pick orders: ${pickOrderIds.size}") println(" Pick order IDs to fetch: $pickOrderIds") - + if (pickOrderIds.isEmpty()) { return emptyList() } - + // ✅ Use SQL query approach similar to getPickOrderLineLotDetails for detailed lot information val pickOrderIdsStr = pickOrderIds.joinToString(",") - + val sql = """ SELECT -- Pick Order Information @@ -2765,21 +2799,21 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo il.expiryDate ASC, il.lotNo ASC """.trimIndent() - + println("🔍 Executing SQL for all pick order lots with details: $sql") println(" With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") - + val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) println("✅ Total result count: ${results.size}") - + // Filter out lots with null availableQty (rejected lots) val filteredResults = results.filter { row -> val availableQty = row["availableQty"] availableQty != null } - + println("✅ Filtered result count: ${filteredResults.size}") - + val enrichedResults = filteredResults return enrichedResults } @@ -2787,35 +2821,37 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo open fun getFgPickOrders(): List> { try { println("🔍 Starting getFgPickOrders method - using repository") - + // Use repository to find pick orders with type 'do' - val pickOrders = pickOrderRepository.findAll().filter { - it.type?.value == "do" && - it.status?.value in listOf("assigned", "released", "picking") + val pickOrders = pickOrderRepository.findAll().filter { + it.type?.value == "do" && + it.status?.value in listOf("assigned", "released", "picking") } - + println("🔍 Found ${pickOrders.size} FG pick orders using repository") - + val results = pickOrders.map { po -> val deliveryOrder = po.deliveryOrder val shop = deliveryOrder?.shop val supplier = deliveryOrder?.supplier - + // ✅ Get truck information for this shop - handle Optional properly val truckInfo = if (shop?.id != null) { try { - jdbcDao.queryForMap(""" + jdbcDao.queryForMap( + """ SELECT TruckLanceCode, DepartureTime FROM truck WHERE shopId = :shopId AND deleted = false LIMIT 1 - """, mapOf("shopId" to shop.id)).orElse(null) + """, mapOf("shopId" to shop.id) + ).orElse(null) } catch (e: Exception) { println("⚠️ No truck found for shop ${shop.id}: ${e.message}") null } } else null - + mapOf( "pickOrderId" to (po.id ?: 0L), "pickOrderCode" to (po.code ?: ""), @@ -2836,20 +2872,21 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo "qrCodeData" to (po.id ?: 0L) ) } - + println("✅ FG Pick Orders result count: ${results.size}") return results - + } catch (e: Exception) { println("❌ Error in getFgPickOrders: ${e.message}") e.printStackTrace() return emptyList() } } + open fun getFgPickOrdersByUserId(userId: Long): List> { try { println("🔍 Starting getFgPickOrdersByUserId with userId: $userId") - + // ✅ 修复:从 do_pick_order_line 获取 pick order 信息 val sql = """ SELECT @@ -2897,19 +2934,19 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo dpo.ShopCode, dpo.ShopName, s.id, s.name, s.addr1, s.addr2, s.addr3, s.addr4, s.district ORDER BY MIN(po.targetDate) DESC, MIN(po.code) ASC """.trimIndent() - + println("🔍 Executing SQL for FG pick orders by userId: $sql") println("🔍 With parameters: userId = $userId") - + val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) - + if (results.isEmpty()) { println("❌ No active FG pick orders found for user: $userId") return emptyList() } - + println("🔍 Found ${results.size} active FG pick orders for user: $userId") - + // ✅ 添加调试信息 results.forEachIndexed { index, row -> println("🔍 DEBUG: Result $index:") @@ -2918,7 +2955,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo println(" - numberOfPickOrders: ${row["numberOfPickOrders"]}") println(" - ticketNo: ${row["ticketNo"]}") } - + val formattedResults = results.map { row -> // ✅ 解析 pick order IDs 列表 val pickOrderIdsStr = row["pickOrderIds"] as? String ?: "" @@ -2927,14 +2964,15 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo } else { emptyList() } - + mapOf( "doPickOrderId" to (row["doPickOrderId"] ?: 0L), "pickOrderIds" to pickOrderIds, // ✅ 返回所有 pick order IDs "pickOrderId" to (pickOrderIds.firstOrNull() ?: 0L), // ✅ 兼容:返回第一个 "pickOrderCodes" to (row["pickOrderCodes"] ?: ""), "pickOrderCode" to ((row["pickOrderCodes"] as? String)?.split(", ")?.firstOrNull() ?: ""), // ✅ 兼容 - "deliveryOrderIds" to (row["deliveryOrderIds"] as? String ?: "").split(",").mapNotNull { it.toLongOrNull() }, + "deliveryOrderIds" to (row["deliveryOrderIds"] as? String ?: "").split(",") + .mapNotNull { it.toLongOrNull() }, "deliveryNos" to (row["deliveryNos"] ?: ""), "pickOrderConsoCode" to (row["pickOrderConsoCode"] ?: ""), "pickOrderTargetDate" to (row["pickOrderTargetDate"]?.toString() ?: ""), @@ -2954,35 +2992,37 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo "qrCodeData" to (row["doPickOrderId"] ?: 0L) // ✅ 改为 doPickOrderId ) } - + println("✅ Built ${formattedResults.size} FG pick orders for user: $userId") return formattedResults - + } catch (e: Exception) { println("❌ Error in getFgPickOrdersByUserId: ${e.message}") e.printStackTrace() return emptyList() } } + open fun getFgPickOrdersByPickOrderId(pickOrderId: Long): List> { try { println("🔍 Starting getFgPickOrdersByPickOrderId method with pickOrderId: $pickOrderId") - + val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) - + if (pickOrder == null) { println("❌ Pick order not found with ID: $pickOrderId") return emptyList() } - + println("🔍 Found pick order: ${pickOrder.code}, type: ${pickOrder.type?.value}, status: ${pickOrder.status?.value}") - + if (pickOrder.type?.value != "do") { println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") return emptyList() } - val allowedstatuses= listOf("assigned", "released", "picking" - //, "completed" + val allowedstatuses = listOf( + "assigned", "released", "picking" + //, "completed" ) if (pickOrder.status?.value !in allowedstatuses) { println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") @@ -2992,31 +3032,31 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo val deliveryOrder = pickOrder.deliveryOrder val shop = deliveryOrder?.shop val supplier = deliveryOrder?.supplier - + println(" Delivery order: ${deliveryOrder?.code}, Shop: ${shop?.name}, Supplier: ${supplier?.code}") println("🔍 Shop ID: ${shop?.id}") - + // ✅ Get truck information using repository with detailed debugging val truck = if (shop?.id != null) { try { println("🔍 Querying truck repository for shopId: ${shop.id}") - + // Get all trucks for this shop val trucksForShop = truckRepository.findByShopIdAndDeletedFalse(shop.id) println("🔍 Trucks for shop ${shop.id}: ${trucksForShop.size}") trucksForShop.forEach { t -> println(" - Truck ID: ${t.id}, truckLanceCode: ${t.truckLanceCode}, ShopId: ${t.shop?.id}, Deleted: ${t.deleted}") } - + // Take the first truck (or null if none found) val selectedTruck = trucksForShop.firstOrNull() - + if (selectedTruck != null) { println("✅ Selected truck: ID=${selectedTruck.id}, truckLanceCode=${selectedTruck.truckLanceCode}, DepartureTime=${selectedTruck.departureTime}") } else { println("❌ No truck found for shopId ${shop.id}") } - + selectedTruck } catch (e: Exception) { println("⚠️ Error querying truck repository for shop ${shop.id}: ${e.message}") @@ -3059,40 +3099,42 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo "shopPoNo" to (supplier?.code ?: ""), "numberOfCartons" to (pickOrder.pickOrderLines.size), "truckLanceCode" to (truck?.truckLanceCode ?: ""), // ✅ Use entity property - "DepartureTime" to (truck?.departureTime?.toString() ?: ""), + "DepartureTime" to (truck?.departureTime?.toString() ?: ""), "ticketNo" to ticketNo, "storeId" to dpoStoreId, "qrCodeData" to (pickOrder.id ?: 0L) ) - + println("✅ FG Pick Orders by ID result count: 1") return listOf(result) - + } catch (e: Exception) { println("❌ Error in getFgPickOrdersByPickOrderId: ${e.message}") e.printStackTrace() return emptyList() } } + open fun getnewFgPickOrdersByPickOrderId(pickOrderId: Long): List> { try { println("🔍 Starting getnewFgPickOrdersByPickOrderId method with pickOrderId: $pickOrderId") - + val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) - + if (pickOrder == null) { println("❌ Pick order not found with ID: $pickOrderId") return emptyList() } - + println("🔍 Found pick order: ${pickOrder.code}, type: ${pickOrder.type?.value}, status: ${pickOrder.status?.value}") - + if (pickOrder.type?.value != "do") { println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") return emptyList() } - val allowedstatuses= listOf("assigned", "released", "picking","completed" - //, "completed" + val allowedstatuses = listOf( + "assigned", "released", "picking", "completed" + //, "completed" ) if (pickOrder.status?.value !in allowedstatuses) { println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") @@ -3102,31 +3144,31 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo val deliveryOrder = pickOrder.deliveryOrder val shop = deliveryOrder?.shop val supplier = deliveryOrder?.supplier - + println(" Delivery order: ${deliveryOrder?.code}, Shop: ${shop?.name}, Supplier: ${supplier?.code}") println("🔍 Shop ID: ${shop?.id}") - + // ✅ Get truck information using repository with detailed debugging val truck = if (shop?.id != null) { try { println("🔍 Querying truck repository for shopId: ${shop.id}") - + // Get all trucks for this shop val trucksForShop = truckRepository.findByShopIdAndDeletedFalse(shop.id) println("🔍 Trucks for shop ${shop.id}: ${trucksForShop.size}") trucksForShop.forEach { t -> println(" - Truck ID: ${t.id}, TruckLanceCode: ${t.truckLanceCode}, ShopId: ${t.shop?.id}, Deleted: ${t.deleted}") } - + // Take the first truck (or null if none found) val selectedTruck = trucksForShop.firstOrNull() - + if (selectedTruck != null) { println("✅ Selected truck: ID=${selectedTruck.id}, TruckLanceCode=${selectedTruck.truckLanceCode}, DepartureTime=${selectedTruck.departureTime}") } else { println("❌ No truck found for shopId ${shop.id}") } - + selectedTruck } catch (e: Exception) { println("⚠️ Error querying truck repository for shop ${shop.id}: ${e.message}") @@ -3169,101 +3211,102 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo "shopPoNo" to (supplier?.code ?: ""), "numberOfCartons" to (pickOrder.pickOrderLines.size), "TruckLanceCode" to (truck?.truckLanceCode ?: ""), // ✅ Use entity property - "DepartureTime" to (truck?.departureTime?.toString() ?: ""), + "DepartureTime" to (truck?.departureTime?.toString() ?: ""), "ticketNo" to ticketNo, "storeId" to dpoStoreId, "qrCodeData" to (pickOrder.id ?: 0L) ) - + println("✅ FG Pick Orders by ID result count: 1") return listOf(result) - + } catch (e: Exception) { println("❌ Error in getFgPickOrdersByPickOrderId: ${e.message}") e.printStackTrace() return emptyList() } } - + private fun buildShopAddress(shop: com.ffii.fpsms.modules.master.entity.Shop?): String { if (shop == null) return "" - + val addressParts = mutableListOf() - + shop.addr1?.let { if (it.isNotBlank()) addressParts.add(it) } shop.addr2?.let { if (it.isNotBlank()) addressParts.add(it) } shop.addr3?.let { if (it.isNotBlank()) addressParts.add(it) } shop.addr4?.let { if (it.isNotBlank()) addressParts.add(it) } shop.district?.let { if (it.isNotBlank()) addressParts.add(it) } - + return addressParts.joinToString(", ") } // Add this new method that doesn't auto-assign -open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List> { - println("=== Debug: getAllPickOrderLotsWithDetailsWithoutAutoAssign ===") - println("today: ${LocalDate.now()}") - println("userId filter: $userId") - - // Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId) - val user = userService.find(userId).orElse(null) - if (user == null) { - println("❌ User not found: $userId") - return emptyList() - } - - // Get all pick orders assigned to user with PENDING or RELEASED status that have doId - val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( - user, - listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) - ).filter { it.deliveryOrder != null } // Only pick orders with doId - println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId") - allAssignedPickOrders.forEach { po -> - println("🔍 DEBUG: Pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}") - } - // ✅ NEW LOGIC: Filter based on assignment and status - val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) { - // Check if there are any RELEASED orders assigned to this user (active work) - val assignedReleasedOrders = allAssignedPickOrders.filter { - it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId + open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List> { + println("=== Debug: getAllPickOrderLotsWithDetailsWithoutAutoAssign ===") + println("today: ${LocalDate.now()}") + println("userId filter: $userId") + + // Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId) + val user = userService.find(userId).orElse(null) + if (user == null) { + println("❌ User not found: $userId") + return emptyList() } - - if (assignedReleasedOrders.isNotEmpty()) { - // ✅ If there are assigned RELEASED orders, show only those - println("🔍 DEBUG: Found ${assignedReleasedOrders.size} assigned RELEASED orders, showing only those") - assignedReleasedOrders - } else { - // ✅ If no assigned RELEASED orders, show only the latest COMPLETED order - val completedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.COMPLETED } - if (completedOrders.isNotEmpty()) { - val latestCompleted = completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN } - println("�� DEBUG: No assigned RELEASED orders, showing latest completed order: ${latestCompleted?.code}") - listOfNotNull(latestCompleted) + + // Get all pick orders assigned to user with PENDING or RELEASED status that have doId + val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( + user, + listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) + ).filter { it.deliveryOrder != null } // Only pick orders with doId + println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId") + allAssignedPickOrders.forEach { po -> + println("🔍 DEBUG: Pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}") + } + // ✅ NEW LOGIC: Filter based on assignment and status + val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) { + // Check if there are any RELEASED orders assigned to this user (active work) + val assignedReleasedOrders = allAssignedPickOrders.filter { + it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId + } + + if (assignedReleasedOrders.isNotEmpty()) { + // ✅ If there are assigned RELEASED orders, show only those + println("🔍 DEBUG: Found ${assignedReleasedOrders.size} assigned RELEASED orders, showing only those") + assignedReleasedOrders } else { - println("🔍 DEBUG: No orders found") - emptyList() + // ✅ If no assigned RELEASED orders, show only the latest COMPLETED order + val completedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.COMPLETED } + if (completedOrders.isNotEmpty()) { + val latestCompleted = + completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN } + println("�� DEBUG: No assigned RELEASED orders, showing latest completed order: ${latestCompleted?.code}") + listOfNotNull(latestCompleted) + } else { + println("🔍 DEBUG: No orders found") + emptyList() + } } + } else { + emptyList() } - } else { - emptyList() - } - - println("�� DEBUG: After assignment filtering, ${filteredPickOrders.size} pick orders remain") - filteredPickOrders.forEach { po -> - println("�� DEBUG: Final pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}") - } - - val pickOrderIds = filteredPickOrders.map { it.id!! } - println(" Pick order IDs to fetch: $pickOrderIds") - - if (pickOrderIds.isEmpty()) { - return emptyList() - } - - // Use the same SQL query as the auto-assign version but without the auto-assignment - val pickOrderIdsStr = pickOrderIds.joinToString(",") - - val sql = """ + + println("�� DEBUG: After assignment filtering, ${filteredPickOrders.size} pick orders remain") + filteredPickOrders.forEach { po -> + println("�� DEBUG: Final pick order ${po.id}: code=${po.code}, status=${po.status}, assignTo=${po.assignTo?.id}, doId=${po.deliveryOrder?.id}") + } + + val pickOrderIds = filteredPickOrders.map { it.id!! } + println(" Pick order IDs to fetch: $pickOrderIds") + + if (pickOrderIds.isEmpty()) { + return emptyList() + } + + // Use the same SQL query as the auto-assign version but without the auto-assignment + val pickOrderIdsStr = pickOrderIds.joinToString(",") + + val sql = """ SELECT -- Pick Order Information po.id as pickOrderId, @@ -3396,21 +3439,21 @@ ORDER BY il.expiryDate ASC, il.lotNo ASC """.trimIndent() - - println("🔍 Executing SQL for all pick order lots with details (no auto-assign): $sql") - println(" With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") - - val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) - println("✅ Total result count: ${results.size}") - - // Filter out lots with null availableQty (rejected lots) - val filteredResults = results.filter { row -> - val availableQty = row["availableQty"] - availableQty != null - } - - println("✅ Filtered result count: ${filteredResults.size}") - /* + + println("🔍 Executing SQL for all pick order lots with details (no auto-assign): $sql") + println(" With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr") + + val results = jdbcDao.queryForList(sql, mapOf("userId" to userId)) + println("✅ Total result count: ${results.size}") + + // Filter out lots with null availableQty (rejected lots) + val filteredResults = results.filter { row -> + val availableQty = row["availableQty"] + availableQty != null + } + + println("✅ Filtered result count: ${filteredResults.size}") + /* // ✅ Add router information for each lot val enrichedResults = filteredResults.map { row -> val inventoryLotId = row["debugInventoryLotId"] as? Number @@ -3470,95 +3513,740 @@ ORDER BY } } */ - val enrichedResults = filteredResults - return enrichedResults -} -// ... existing code ... - -open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { - println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===") - println("userId filter: $userId") - - val user = userService.find(userId).orElse(null) - if (user == null) { - println("❌ User not found: $userId") - return emptyMap() - } - - // ✅ Step 1: 获取 do_pick_order 基本信息 - val doPickOrderSql = """ - SELECT DISTINCT - dpo.id as do_pick_order_id, - dpo.ticket_no, - dpo.store_id, - dpo.TruckLanceCode, - dpo.truck_departure_time, - dpo.ShopCode, - dpo.ShopName - FROM fpsmsdb.do_pick_order dpo - INNER JOIN fpsmsdb.do_pick_order_line dpol ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0 - INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id - WHERE po.assignTo = :userId - AND po.type = 'do' - AND po.status IN ('assigned', 'released', 'picking') - AND po.deleted = false - AND dpo.deleted = false - LIMIT 1 - """.trimIndent() - - val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null) - if (doPickOrderInfo == null) { - println("❌ No do_pick_order found for user $userId") - return mapOf( - "fgInfo" to null, - "pickOrders" to emptyList() - ) + val enrichedResults = filteredResults + return enrichedResults } - - val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong() - println("🔍 Found do_pick_order ID: $doPickOrderId") - - // ✅ Step 2: 获取该 do_pick_order 下的所有 pick orders - val pickOrdersSql = """ - SELECT DISTINCT - dpol.pick_order_id, - dpol.pick_order_code, - dpol.do_order_id, - dpol.delivery_order_code, - po.consoCode, - po.status, - DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate - FROM fpsmsdb.do_pick_order_line dpol - INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id - WHERE dpol.do_pick_order_id = :doPickOrderId - AND dpol.deleted = 0 - AND po.deleted = false - ORDER BY dpol.pick_order_id - """.trimIndent() - - val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderId" to doPickOrderId)) - println("🔍 Found ${pickOrdersInfo.size} pick orders") - - // ✅ Step 3: 为每个 pick order 获取 lines 和 lots(包括 null stock 的) - val pickOrders = pickOrdersInfo.map { poInfo -> - val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() + open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { + println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===") + println("userId filter: $userId") - // ✅ 查询该 pick order 的所有 lines 和 lots - val linesSql = """ - SELECT - po.id as pickOrderId, - po.code as pickOrderCode, - po.consoCode as pickOrderConsoCode, - DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, - po.type as pickOrderType, - po.status as pickOrderStatus, - po.assignTo as pickOrderAssignTo, + val user = userService.find(userId).orElse(null) + if (user == null) { + println("❌ User not found: $userId") + return emptyMap() + } + + // ✅ Step 1: 获取 do_pick_order 基本信息 + val doPickOrderSql = """ + SELECT DISTINCT + dpo.id as do_pick_order_id, + dpo.ticket_no, + dpo.store_id, + dpo.TruckLanceCode, + dpo.truck_departure_time, + dpo.ShopCode, + dpo.ShopName + FROM fpsmsdb.do_pick_order dpo + INNER JOIN fpsmsdb.do_pick_order_line dpol ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0 + INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id + WHERE po.assignTo = :userId + AND po.type = 'do' + AND po.status IN ('assigned', 'released', 'picking') + AND po.deleted = false + AND dpo.deleted = false + LIMIT 1 + """.trimIndent() + + val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null) + if (doPickOrderInfo == null) { + println("❌ No do_pick_order found for user $userId") + return mapOf( + "fgInfo" to null, + "pickOrders" to emptyList() + ) + } + + val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong() + println("🔍 Found do_pick_order ID: $doPickOrderId") + + // ✅ Step 2: 获取该 do_pick_order 下的所有 pick orders + val pickOrdersSql = """ + SELECT DISTINCT + dpol.pick_order_id, + dpol.pick_order_code, + dpol.do_order_id, + dpol.delivery_order_code, + po.consoCode, + po.status, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate + FROM fpsmsdb.do_pick_order_line dpol + INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id + WHERE dpol.do_pick_order_id = :doPickOrderId + AND dpol.deleted = 0 + AND po.deleted = false + ORDER BY dpol.pick_order_id + """.trimIndent() + + val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderId" to doPickOrderId)) + println("🔍 Found ${pickOrdersInfo.size} pick orders") + + // ✅ Step 3: 为每个 pick order 获取 lines 和 lots(包括 null stock 的) + val pickOrders = pickOrdersInfo.map { poInfo -> + val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() + + // ✅ 查询该 pick order 的所有 lines 和 lots + val linesSql = """ + SELECT + po.id as pickOrderId, + po.code as pickOrderCode, + po.consoCode as pickOrderConsoCode, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, + po.type as pickOrderType, + po.status as pickOrderStatus, + po.assignTo as pickOrderAssignTo, + + pol.id as pickOrderLineId, + pol.qty as pickOrderLineRequiredQty, + pol.status as pickOrderLineStatus, + + i.id as itemId, + i.code as itemCode, + i.name as itemName, + uc.code as uomCode, + uc.udfudesc as uomDesc, + uc.udfShortDesc as uomShortDesc, + + ill.id as lotId, + il.lotNo, + DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, + w.name as location, + COALESCE(uc.udfudesc, 'N/A') as stockUnit, + w.`order` as routerIndex, + w.code as routerRoute, + + CASE + WHEN sol.status = 'rejected' THEN NULL + ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) + END as availableQty, + + COALESCE(spl.qty, 0) as requiredQty, + COALESCE(sol.qty, 0) as actualPickQty, + spl.id as suggestedPickLotId, + ill.status as lotStatus, + sol.id as stockOutLineId, + sol.status as stockOutLineStatus, + COALESCE(sol.qty, 0) as stockOutLineQty, + COALESCE(ill.inQty, 0) as inQty, + COALESCE(ill.outQty, 0) as outQty, + COALESCE(ill.holdQty, 0) as holdQty, + + CASE + WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' + WHEN ill.status = 'unavailable' THEN 'status_unavailable' + ELSE 'available' + END as lotAvailability, + + CASE + WHEN sol.status = 'completed' THEN 'completed' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN sol.status = 'created' THEN 'pending' + ELSE 'pending' + END as processingStatus + + FROM fpsmsdb.pick_order po + JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false + JOIN fpsmsdb.items i ON i.id = pol.itemId + LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId - pol.id as pickOrderLineId, - pol.qty as pickOrderLineRequiredQty, - pol.status as pickOrderLineStatus, + -- ✅ 关键修改:使用 LEFT JOIN 来包含没有 lot 的 pick order lines + LEFT JOIN ( + SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId + FROM fpsmsdb.suggested_pick_lot spl + UNION + SELECT sol.pickOrderLineId, sol.inventoryLotLineId + FROM fpsmsdb.stock_out_line sol + WHERE sol.deleted = false + ) ll ON ll.pickOrderLineId = pol.id - i.id as itemId, + LEFT JOIN fpsmsdb.suggested_pick_lot spl + ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId + LEFT JOIN fpsmsdb.stock_out_line sol + ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ll.lotLineId AND sol.deleted = false + LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false + LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false + LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId + + WHERE po.id = :pickOrderId + AND po.deleted = false + ORDER BY + COALESCE(w.`order`, 999999) ASC, + pol.id ASC, + il.lotNo ASC + """.trimIndent() + + val linesResults = jdbcDao.queryForList(linesSql, mapOf("pickOrderId" to pickOrderId)) + println("🔍 Pick order $pickOrderId has ${linesResults.size} line-lot records") + + // ✅ 按 pickOrderLineId 分组 + val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() } + + val pickOrderLines = lineGroups.map { (lineId, lineRows) -> + val firstLineRow = lineRows.firstOrNull() + + if (firstLineRow == null) { + null + } else { + // ✅ 构建 lots 列表(如果没有 lot,返回空数组) + val lots = if (lineRows.any { it["lotId"] != null }) { + lineRows.filter { it["lotId"] != null }.map { lotRow -> + mapOf( + "id" to lotRow["lotId"], + "lotNo" to lotRow["lotNo"], + "expiryDate" to lotRow["expiryDate"], + "location" to lotRow["location"], + "stockUnit" to lotRow["stockUnit"], + "availableQty" to lotRow["availableQty"], + "requiredQty" to lotRow["requiredQty"], + "actualPickQty" to lotRow["actualPickQty"], + "inQty" to lotRow["inQty"], + "outQty" to lotRow["outQty"], + "holdQty" to lotRow["holdQty"], + "lotStatus" to lotRow["lotStatus"], + "lotAvailability" to lotRow["lotAvailability"], + "processingStatus" to lotRow["processingStatus"], + "suggestedPickLotId" to lotRow["suggestedPickLotId"], + "stockOutLineId" to lotRow["stockOutLineId"], + "stockOutLineStatus" to lotRow["stockOutLineStatus"], + "stockOutLineQty" to lotRow["stockOutLineQty"], + "router" to mapOf( + "id" to null, + "index" to lotRow["routerIndex"], + "route" to lotRow["routerRoute"], + "area" to lotRow["routerRoute"], + "itemCode" to lotRow["itemId"], + "itemName" to lotRow["itemName"], + "uomId" to lotRow["uomCode"], + "noofCarton" to lotRow["requiredQty"] + ) + ) + } + } else { + emptyList() // ✅ 返回空数组而不是 null + } + + mapOf( + "id" to lineId, + "requiredQty" to firstLineRow["pickOrderLineRequiredQty"], + "status" to firstLineRow["pickOrderLineStatus"], + "item" to mapOf( + "id" to firstLineRow["itemId"], + "code" to firstLineRow["itemCode"], + "name" to firstLineRow["itemName"], + "uomCode" to firstLineRow["uomCode"], + "uomDesc" to firstLineRow["uomDesc"], + "uomShortDesc" to firstLineRow["uomShortDesc"], + ), + "lots" to lots // ✅ 即使是空数组也返回 + ) + } + }.filterNotNull() + + mapOf( + "pickOrderId" to pickOrderId, + "pickOrderCode" to poInfo["pick_order_code"], + "doOrderId" to poInfo["do_order_id"], + "deliveryOrderCode" to poInfo["delivery_order_code"], + "consoCode" to poInfo["consoCode"], + "status" to poInfo["status"], + "targetDate" to poInfo["targetDate"], + "pickOrderLines" to pickOrderLines + ) + } + + // ✅ 构建 FG 信息 + val fgInfo = mapOf( + "doPickOrderId" to doPickOrderId, + "ticketNo" to doPickOrderInfo["ticket_no"], + "storeId" to doPickOrderInfo["store_id"], + "shopCode" to doPickOrderInfo["ShopCode"], + "shopName" to doPickOrderInfo["ShopName"], + "truckLanceCode" to doPickOrderInfo["TruckLanceCode"], + "departureTime" to doPickOrderInfo["truck_departure_time"] + ) + + return mapOf( + "fgInfo" to fgInfo, + "pickOrders" to pickOrders + ) + } + // Fix the type issues in the getPickOrdersByDateAndStore method + open fun getPickOrdersByDateAndStore(storeId: String): Map { + println("=== Debug: getPickOrdersByDateAndStore ===") + println("storeId: $storeId") + + try { + val today = LocalDate.now() + println("Today's date: $today") + + // SQL query to get pick orders grouped by date and store + // Get all available orders for this store + val sql = """ + SELECT + dpo.store_id as storeId, + DATE_FORMAT(dpo.created, '%Y-%m-%d') as orderDate, + COUNT(DISTINCT po.id) as orderTotal, + COUNT(DISTINCT CASE WHEN po.status = 'COMPLETED' THEN po.id END) as orderCompleted, + GROUP_CONCAT(DISTINCT po.id ORDER BY po.id) as pickOrderIds, + GROUP_CONCAT(DISTINCT po.code ORDER BY po.id SEPARATOR ',') as pickOrderCodes, + -- Get do_pick_order details + GROUP_CONCAT( + CONCAT( + dpo.id, ':', + dpo.ticket_no, ':', + dpo.pick_order_id + ) + ORDER BY dpo.id SEPARATOR '|' + ) as doPickOrderDetails + FROM do_pick_order dpo + JOIN pick_order po ON po.id = dpo.pick_order_id + WHERE dpo.deleted = false + AND po.deleted = false + AND dpo.store_id = :storeId + GROUP BY dpo.store_id, DATE_FORMAT(dpo.created, '%Y-%m-%d') + ORDER BY orderDate ASC + """.trimIndent() + + println("🔍 Executing SQL: $sql") + println("🔍 With parameters: storeId = $storeId") + + val results = jdbcDao.queryForList(sql, mapOf("storeId" to storeId)) + println("✅ Found ${results.size} records") + + if (results.isEmpty()) { + return mapOf( + "storeId" to storeId, + "orders" to emptyList>() + ) + } + + // Analyze each date's completion status + val ordersByDate = results.map { row -> + val orderDate = LocalDate.parse(row["orderDate"] as String) + val orderTotal = (row["orderTotal"] as Number).toInt() + val orderCompleted = (row["orderCompleted"] as Number).toInt() + val isAllCompleted = orderTotal == orderCompleted && orderTotal > 0 + + mapOf( + "orderDate" to orderDate, + "orderTotal" to orderTotal, + "orderCompleted" to orderCompleted, + "isAllCompleted" to isAllCompleted, + "rawData" to row + ) + } + + println("📊 Orders by date analysis:") + ordersByDate.forEach { order -> + println(" Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}, AllCompleted: ${order["isAllCompleted"]}") + } + + // Simple logic: Find the first incomplete date and return it + next 2 days + val ordersToReturn = mutableListOf>() + + // Find the first incomplete order date + val firstIncompleteDate = ordersByDate + .firstOrNull { !(it["isAllCompleted"] as Boolean) } + + if (firstIncompleteDate != null) { + val startDate = firstIncompleteDate["orderDate"] as LocalDate + println("�� First incomplete date: $startDate") + + // Get the incomplete date + next 2 days (total 3 days) + val targetDates = (0..2).map { days -> startDate.plusDays(days.toLong()) } + println("🎯 Target dates to return: $targetDates") + + // Only return dates that actually exist in the database + ordersToReturn.addAll( + ordersByDate.filter { order -> + val orderDate = order["orderDate"] as LocalDate + targetDates.contains(orderDate) + }.map { it["rawData"] as Map } + ) + } else { + println("🎯 All orders are completed, returning empty list") + } + + // Transform results into the required structure + val finalOrders = ordersToReturn.map { row -> + val doPickOrderDetails = (row["doPickOrderDetails"] as String?)?.split("|") ?: emptyList() + + val doPickOrderList = doPickOrderDetails.map { detail -> + val parts = detail.split(":") + if (parts.size >= 3) { + mapOf( + "doPickOrderId" to parts[0].toLongOrNull(), + "ticketNo" to parts[1], + "pickOrderId" to parts[2].toLongOrNull() + ) + } else { + mapOf( + "doPickOrderId" to null, + "ticketNo" to "", + "pickOrderId" to null + ) + } + } + + mapOf( + "orderTotal" to row["orderTotal"], + "orderCompleted" to row["orderCompleted"], + "orderDate" to row["orderDate"], + "pickOrderIds" to ((row["pickOrderIds"] as String?)?.split(",") + ?.mapNotNull { it.toLongOrNull() } ?: emptyList()), + "pickOrderCodes" to ((row["pickOrderCodes"] as String?)?.split(",") ?: emptyList()), + "doPickOrderDetails" to doPickOrderList + ) + } + + println("✅ Final orders to return: ${finalOrders.size}") + finalOrders.forEach { order -> + println(" 📅 Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}") + } + + return mapOf( + "storeId" to storeId, + "orders" to finalOrders + ) + + } catch (e: Exception) { + println("❌ Error in getPickOrdersByDateAndStore: ${e.message}") + e.printStackTrace() + return mapOf( + "storeId" to storeId, + "orders" to emptyList>(), + "error" to e.message + ) + } + } + + @Transactional(rollbackFor = [java.lang.Exception::class]) + open fun confirmLotSubstitution(req: LotSubstitutionConfirmRequest): MessageResponse { + val zero = BigDecimal.ZERO + + // Validate entities + 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) } + ?: return MessageResponse( + id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", + message = "Inventory lot line ${req.newInventoryLotLineId} not found", errorPosition = null + ) + + // Item consistency check + val polItemId = pol.item?.id + val newItemId = newIll.inventoryLot?.item?.id + if (polItemId == null || 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 + ) + } + + // 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) { + // 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) + } + } + + // 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) + ) + } + } + + // 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) + if (sol != null) { + sol.inventoryLotLine = newIll + sol.item = pol.item + 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}", + errorPosition = null + ) + } + + open fun getCompletedDoPickOrders( + userId: Long, + pickOrderCode: String?, + shopName: String?, + deliveryNo: String?, + ticketNo: String? + ): List> { + return try { + println("=== getCompletedDoPickOrders (from record table) ===") + println("userId: $userId") + + // ✅ 从 do_pick_order_record 表查询已完成的订单 + val sql = """ + SELECT DISTINCT + dpor.id as doPickOrderRecordId, + dpor.ticket_no as ticketNo, + dpor.store_id as storeId, + dpor.ticketCompleteDateTime as completedDate, + dpor.ShopCode as shopCode, + dpor.ShopName as shopName, + dpor.TruckLanceCode as truckLanceCode, + dpor.truck_departure_time as departureTime, + + -- ✅ 聚合该 do_pick_order_record 下的所有 pick orders + GROUP_CONCAT(DISTINCT dpolr.pick_order_id ORDER BY dpolr.pick_order_id) as pickOrderIds, + GROUP_CONCAT(DISTINCT dpolr.pick_order_code ORDER BY dpolr.pick_order_id SEPARATOR ', ') as pickOrderCodes, + GROUP_CONCAT(DISTINCT dpolr.do_order_id ORDER BY dpolr.do_order_id) as deliveryOrderIds, + GROUP_CONCAT(DISTINCT dpolr.delivery_order_code ORDER BY dpolr.do_order_id SEPARATOR ', ') as deliveryNos, + + -- ✅ 获取第一个 pick order 的详细信息(用于显示) + (SELECT po.consoCode FROM pick_order po WHERE po.id = MIN(dpolr.pick_order_id) LIMIT 1) as pickOrderConsoCode, + (SELECT po.status FROM pick_order po WHERE po.id = MIN(dpolr.pick_order_id) LIMIT 1) as pickOrderStatus, + (SELECT do.orderDate FROM delivery_order do WHERE do.id = MIN(dpolr.do_order_id) LIMIT 1) as deliveryDate, + + -- ✅ 计算总箱数 + (SELECT SUM(pol_count.line_count) + FROM ( + SELECT COUNT(*) as line_count + FROM pick_order po3 + JOIN pick_order_line pol3 ON pol3.poId = po3.id AND pol3.deleted = false + WHERE po3.id IN (SELECT dpolr2.pick_order_id FROM do_pick_order_line_record dpolr2 WHERE dpolr2.do_pick_order_id = dpor.id AND dpolr2.deleted = 0) + GROUP BY po3.id + ) pol_count + ) as numberOfCartons, + + s.id as shopId, + CONCAT_WS(', ', s.addr1, s.addr2, s.addr3, s.addr4, s.district) as shopAddress + + FROM fpsmsdb.do_pick_order_record dpor + INNER JOIN fpsmsdb.do_pick_order_line_record dpolr ON dpolr.do_pick_order_id = dpor.id AND dpolr.deleted = 0 + INNER JOIN fpsmsdb.pick_order po ON po.id = dpolr.pick_order_id + LEFT JOIN fpsmsdb.shop s ON s.id = dpor.shop_id + WHERE dpor.deleted = false + AND dpor.handled_by = :userId + AND dpor.ticket_status = 'completed' + """.trimIndent() + + // ✅ 添加搜索过滤条件 + val conditions = mutableListOf() + val params = mutableMapOf("userId" to userId) + + if (pickOrderCode != null) { + conditions.add("dpolr.pick_order_code LIKE :pickOrderCode") + params["pickOrderCode"] = "%$pickOrderCode%" + } + if (shopName != null) { + conditions.add("dpor.ShopName LIKE :shopName") + params["shopName"] = "%$shopName%" + } + if (deliveryNo != null) { + conditions.add("dpolr.delivery_order_code LIKE :deliveryNo") + params["deliveryNo"] = "%$deliveryNo%" + } + if (ticketNo != null) { + conditions.add("dpor.ticket_no LIKE :ticketNo") + params["ticketNo"] = "%$ticketNo%" + } + + val finalSql = if (conditions.isNotEmpty()) { + sql + " AND " + conditions.joinToString(" AND ") + + " GROUP BY dpor.id, dpor.ticket_no, dpor.store_id, dpor.ticketCompleteDateTime, " + + "dpor.ShopCode, dpor.ShopName, dpor.TruckLanceCode, dpor.truck_departure_time, " + + "s.id, s.addr1, s.addr2, s.addr3, s.addr4, s.district " + + " ORDER BY dpor.ticketCompleteDateTime DESC" + } else { + sql + + " GROUP BY dpor.id, dpor.ticket_no, dpor.store_id, dpor.ticketCompleteDateTime, " + + "dpor.ShopCode, dpor.ShopName, dpor.TruckLanceCode, dpor.truck_departure_time, " + + "s.id, s.addr1, s.addr2, s.addr3, s.addr4, s.district " + + " ORDER BY dpor.ticketCompleteDateTime DESC" + } + + println("🔍 Executing SQL: $finalSql") + val results = jdbcDao.queryForList(finalSql, params) + println("✅ Found ${results.size} completed do_pick_order_record entries") + + // ✅ 格式化返回数据 + val formattedResults = results.map { row -> + // ✅ 解析 pick order IDs 列表 + val pickOrderIdsStr = row["pickOrderIds"] as? String ?: "" + val pickOrderIds = if (pickOrderIdsStr.isNotEmpty()) { + pickOrderIdsStr.split(",").mapNotNull { it.trim().toLongOrNull() } + } else { + emptyList() + } + + // ✅ 解析 delivery order IDs 列表 + val deliveryOrderIdsStr = row["deliveryOrderIds"] as? String ?: "" + val deliveryOrderIds = if (deliveryOrderIdsStr.isNotEmpty()) { + deliveryOrderIdsStr.split(",").mapNotNull { it.trim().toLongOrNull() } + } else { + emptyList() + } + + mapOf( + "id" to (row["doPickOrderRecordId"] ?: 0L), + "doPickOrderRecordId" to (row["doPickOrderRecordId"] ?: 0L), + "ticketNo" to (row["ticketNo"] ?: ""), + "storeId" to (row["storeId"] ?: ""), + "completedDate" to (row["completedDate"]?.toString() ?: ""), + "shopCode" to (row["shopCode"] ?: ""), + "shopName" to (row["shopName"] ?: ""), + "truckLanceCode" to (row["truckLanceCode"] ?: ""), + "departureTime" to (row["departureTime"]?.toString() ?: ""), + "pickOrderIds" to pickOrderIds, + "pickOrderCodes" to (row["pickOrderCodes"] ?: ""), + "pickOrderCode" to ((row["pickOrderCodes"] as? String)?.split(", ")?.firstOrNull() ?: ""), + "deliveryOrderIds" to deliveryOrderIds, + "deliveryNos" to (row["deliveryNos"] ?: ""), + "deliveryNo" to ((row["deliveryNos"] as? String)?.split(", ")?.firstOrNull() ?: ""), + "pickOrderConsoCode" to (row["pickOrderConsoCode"] ?: ""), + "pickOrderStatus" to (row["pickOrderStatus"] ?: ""), + "deliveryDate" to (row["deliveryDate"]?.toString() ?: ""), + "numberOfCartons" to (row["numberOfCartons"] ?: 0), + "shopId" to (row["shopId"] ?: 0L), + "shopAddress" to (row["shopAddress"] ?: ""), + "fgPickOrders" to pickOrderIds.map { pickOrderId -> + mapOf( + "pickOrderId" to pickOrderId, + "deliveryOrderId" to deliveryOrderIds.firstOrNull() + ) + } + ) + } + + println("✅ Formatted ${formattedResults.size} results") + formattedResults // ✅ 返回格式化后的结果 + } catch (e: Exception) { + println("❌ Error in getCompletedDoPickOrders: ${e.message}") + e.printStackTrace() + emptyList() + } + } + +open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map { + println("=== Debug: getLotDetailsByDoPickOrderRecordId (Hierarchical Format) ===") + println("doPickOrderRecordId: $doPickOrderRecordId") + + // ✅ 获取 FG 信息(从 record 表) + val doPickOrderRecordSql = """ + SELECT + dpor.id as do_pick_order_record_id, + dpor.ticket_no, + dpor.store_id, + dpor.TruckLanceCode, + dpor.truck_departure_time, + dpor.ShopCode, + dpor.ShopName + FROM fpsmsdb.do_pick_order_record dpor + WHERE dpor.id = :doPickOrderRecordId + AND dpor.deleted = false + """.trimIndent() + + val doPickOrderRecordInfo = jdbcDao.queryForMap(doPickOrderRecordSql, mapOf("doPickOrderRecordId" to doPickOrderRecordId)).orElse(null) + + if (doPickOrderRecordInfo == null) { + println("❌ No do_pick_order_record found with ID: $doPickOrderRecordId") + return mapOf( + "fgInfo" to null, + "pickOrders" to emptyList() + ) + } + + // ✅ 构建 FG 信息 + val fgInfo = mapOf( + "doPickOrderId" to doPickOrderRecordId, + "ticketNo" to doPickOrderRecordInfo["ticket_no"], + "storeId" to doPickOrderRecordInfo["store_id"], + "shopCode" to doPickOrderRecordInfo["ShopCode"], + "shopName" to doPickOrderRecordInfo["ShopName"], + "truckLanceCode" to doPickOrderRecordInfo["TruckLanceCode"], + "departureTime" to doPickOrderRecordInfo["truck_departure_time"] + ) + + // ✅ Step 2: 获取该 do_pick_order_record 下的所有 pick orders + val pickOrdersSql = """ + SELECT DISTINCT + dpolr.pick_order_id, + dpolr.pick_order_code, + dpolr.do_order_id, + dpolr.delivery_order_code, + po.consoCode, + po.status, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate + FROM fpsmsdb.do_pick_order_line_record dpolr + INNER JOIN fpsmsdb.pick_order po ON po.id = dpolr.pick_order_id + WHERE dpolr.do_pick_order_id = :doPickOrderRecordId + AND dpolr.deleted = 0 + AND po.deleted = false + ORDER BY dpolr.pick_order_id + """.trimIndent() + + val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderRecordId" to doPickOrderRecordId)) + println("🔍 Found ${pickOrdersInfo.size} pick orders in do_pick_order_record") + + // ✅ Step 3: 为每个 pick order 获取 lines 和 lots + val pickOrders = pickOrdersInfo.map { poInfo -> + val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() + + // ✅ 查询该 pick order 的所有 lines 和 lots(复用之前的 SQL) + val linesSql = """ + SELECT + po.id as pickOrderId, + po.code as pickOrderCode, + po.consoCode as pickOrderConsoCode, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, + po.type as pickOrderType, + po.status as pickOrderStatus, + + pol.id as pickOrderLineId, + pol.qty as pickOrderLineRequiredQty, + pol.status as pickOrderLineStatus, + + i.id as itemId, i.code as itemCode, i.name as itemName, uc.code as uomCode, @@ -3600,7 +4288,6 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map @@ -3647,7 +4321,6 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map mapOf( @@ -3673,16 +4346,12 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { - println("=== Debug: getPickOrdersByDateAndStore ===") - println("storeId: $storeId") - +// ✅ 新增:从 do_pick_order_record 表查询(用于已完成的 pick orders) +open fun getFgPickOrdersFromRecordByPickOrderId(pickOrderId: Long): List> { try { - val today = LocalDate.now() - println("Today's date: $today") - - // SQL query to get pick orders grouped by date and store - // Get all available orders for this store - val sql = """ - SELECT - dpo.store_id as storeId, - DATE_FORMAT(dpo.created, '%Y-%m-%d') as orderDate, - COUNT(DISTINCT po.id) as orderTotal, - COUNT(DISTINCT CASE WHEN po.status = 'COMPLETED' THEN po.id END) as orderCompleted, - GROUP_CONCAT(DISTINCT po.id ORDER BY po.id) as pickOrderIds, - GROUP_CONCAT(DISTINCT po.code ORDER BY po.id SEPARATOR ',') as pickOrderCodes, - -- Get do_pick_order details - GROUP_CONCAT( - CONCAT( - dpo.id, ':', - dpo.ticket_no, ':', - dpo.pick_order_id - ) - ORDER BY dpo.id SEPARATOR '|' - ) as doPickOrderDetails - FROM do_pick_order dpo - JOIN pick_order po ON po.id = dpo.pick_order_id - WHERE dpo.deleted = false - AND po.deleted = false - AND dpo.store_id = :storeId - GROUP BY dpo.store_id, DATE_FORMAT(dpo.created, '%Y-%m-%d') - ORDER BY orderDate ASC - """.trimIndent() - - println("🔍 Executing SQL: $sql") - println("🔍 With parameters: storeId = $storeId") - - val results = jdbcDao.queryForList(sql, mapOf("storeId" to storeId)) - println("✅ Found ${results.size} records") - - if (results.isEmpty()) { - return mapOf( - "storeId" to storeId, - "orders" to emptyList>() - ) - } - - // Analyze each date's completion status - val ordersByDate = results.map { row -> - val orderDate = LocalDate.parse(row["orderDate"] as String) - val orderTotal = (row["orderTotal"] as Number).toInt() - val orderCompleted = (row["orderCompleted"] as Number).toInt() - val isAllCompleted = orderTotal == orderCompleted && orderTotal > 0 - - mapOf( - "orderDate" to orderDate, - "orderTotal" to orderTotal, - "orderCompleted" to orderCompleted, - "isAllCompleted" to isAllCompleted, - "rawData" to row - ) - } - - println("📊 Orders by date analysis:") - ordersByDate.forEach { order -> - println(" Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}, AllCompleted: ${order["isAllCompleted"]}") - } - - // Simple logic: Find the first incomplete date and return it + next 2 days - val ordersToReturn = mutableListOf>() - - // Find the first incomplete order date - val firstIncompleteDate = ordersByDate - .firstOrNull { !(it["isAllCompleted"] as Boolean) } - - if (firstIncompleteDate != null) { - val startDate = firstIncompleteDate["orderDate"] as LocalDate - println("�� First incomplete date: $startDate") - - // Get the incomplete date + next 2 days (total 3 days) - val targetDates = (0..2).map { days -> startDate.plusDays(days.toLong()) } - println("🎯 Target dates to return: $targetDates") - - // Only return dates that actually exist in the database - ordersToReturn.addAll( - ordersByDate.filter { order -> - val orderDate = order["orderDate"] as LocalDate - targetDates.contains(orderDate) - }.map { it["rawData"] as Map } - ) - } else { - println("🎯 All orders are completed, returning empty list") - } - - // Transform results into the required structure - val finalOrders = ordersToReturn.map { row -> - val doPickOrderDetails = (row["doPickOrderDetails"] as String?)?.split("|") ?: emptyList() - - val doPickOrderList = doPickOrderDetails.map { detail -> - val parts = detail.split(":") - if (parts.size >= 3) { - mapOf( - "doPickOrderId" to parts[0].toLongOrNull(), - "ticketNo" to parts[1], - "pickOrderId" to parts[2].toLongOrNull() - ) - } else { - mapOf( - "doPickOrderId" to null, - "ticketNo" to "", - "pickOrderId" to null - ) - } - } - - mapOf( - "orderTotal" to row["orderTotal"], - "orderCompleted" to row["orderCompleted"], - "orderDate" to row["orderDate"], - "pickOrderIds" to ((row["pickOrderIds"] as String?)?.split(",")?.mapNotNull { it.toLongOrNull() } ?: emptyList()), - "pickOrderCodes" to ((row["pickOrderCodes"] as String?)?.split(",") ?: emptyList()), - "doPickOrderDetails" to doPickOrderList - ) + println("🔍 Starting getFgPickOrdersFromRecordByPickOrderId with pickOrderId: $pickOrderId") + + val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) + if (pickOrder == null) { + println("❌ Pick order not found with ID: $pickOrderId") + return emptyList() } - - println("✅ Final orders to return: ${finalOrders.size}") - finalOrders.forEach { order -> - println(" 📅 Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}") + + val deliveryOrder = pickOrder.deliveryOrder + val shop = deliveryOrder?.shop + val supplier = deliveryOrder?.supplier + + // ✅ 从 do_pick_order_record 和 do_pick_order_line_record 表查询 + val ticketNo = try { + val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) + doPickOrderRecords.firstOrNull()?.ticketNo ?: "" + } catch (e: Exception) { + println("⚠️ Error getting ticket number from record: ${e.message}") + "" } - return mapOf( - "storeId" to storeId, - "orders" to finalOrders - ) - + val dpoStoreId = try { + val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) + doPickOrderRecords.firstOrNull()?.storeId ?: "" } catch (e: Exception) { - println("❌ Error in getPickOrdersByDateAndStore: ${e.message}") - e.printStackTrace() - return mapOf( - "storeId" to storeId, - "orders" to emptyList>(), - "error" to e.message - ) - } -} -@Transactional(rollbackFor = [java.lang.Exception::class]) -open fun confirmLotSubstitution(req: LotSubstitutionConfirmRequest): MessageResponse { - val zero = BigDecimal.ZERO - - // Validate entities - 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) } - ?: return MessageResponse(id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", - message = "Inventory lot line ${req.newInventoryLotLineId} not found", errorPosition = null) - - // Item consistency check - val polItemId = pol.item?.id - val newItemId = newIll.inventoryLot?.item?.id - if (polItemId == null || 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 - ) - } - - // 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) { - // 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) - } - } - - // 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)) + "" } - } - // 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) - if (sol != null) { - sol.inventoryLotLine = newIll - sol.item = pol.item - stockOutLIneRepository.save(sol) + val truckLanceCode = try { + val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) + doPickOrderRecords.firstOrNull()?.truckLanceCode ?: "" + } catch (e: Exception) { + "" } - } - - 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}", - errorPosition = null - ) -} -open fun getCompletedDoPickOrders( - userId: Long, - pickOrderCode: String?, - shopName: String?, - deliveryNo: String?, - ticketNo: String? -): List> { - return try { - println("=== getCompletedDoPickOrders ===") - println("userId: $userId") - println("pickOrderCode: $pickOrderCode") - println("shopName: $shopName") - println("deliveryNo: $deliveryNo") - println("ticketNo: $ticketNo") - - // ✅ 修复:使用正确的方法获取已完成的 pick orders - val user = userRepository.findById(userId).orElse(null) - if (user == null) { - println("User not found: $userId") - return emptyList() + val departureTime = try { + val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) + doPickOrderRecords.firstOrNull()?.truckDepartureTime?.toString() ?: "" + } catch (e: Exception) { + "" } - - // ✅ 修复:使用正确的方法获取已完成的 pick orders - val completedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn( - userId, - listOf(PickOrderStatus.COMPLETED) + + val result = mapOf( + "pickOrderId" to (pickOrder.id ?: 0L), + "pickOrderCode" to (pickOrder.code ?: ""), + "pickOrderConsoCode" to (pickOrder.consoCode ?: ""), + "pickOrderTargetDate" to (pickOrder.targetDate?.toString() ?: ""), + "pickOrderStatus" to (pickOrder.status?.value ?: ""), + "deliveryOrderId" to (deliveryOrder?.id ?: 0L), + "deliveryNo" to (deliveryOrder?.code ?: ""), + "deliveryDate" to (deliveryOrder?.orderDate?.toString() ?: ""), + "shopId" to (shop?.id ?: 0L), + "shopCode" to (shop?.code ?: ""), + "shopName" to (shop?.name ?: ""), + "shopAddress" to buildShopAddress(shop), + "shopPoNo" to (supplier?.code ?: ""), + "numberOfCartons" to (pickOrder.pickOrderLines.size), + "truckLanceCode" to truckLanceCode, // ✅ 从 record 表 + "DepartureTime" to departureTime, // ✅ 从 record 表 + "ticketNo" to ticketNo, // ✅ 从 record 表 + "storeId" to dpoStoreId, // ✅ 从 record 表 + "qrCodeData" to (pickOrder.id ?: 0L) ) - - println("Found ${completedPickOrders.size} completed pick orders for user $userId") - - val result = mutableListOf>() - - for (pickOrder in completedPickOrders) { - // 获取该 pick order 的 FG pick orders - val fgPickOrders = getnewFgPickOrdersByPickOrderId(pickOrder.id!!) - - if (fgPickOrders.isNotEmpty()) { - val firstFgOrder = fgPickOrders[0] as Map // ✅ 修复:转换为 Map - - // ✅ 修复:使用正确的属性名进行搜索过滤,并处理空值 - val matchesSearch = when { - pickOrderCode != null && !(pickOrder.code?.contains(pickOrderCode, ignoreCase = true) ?: false) -> false - shopName != null && !((firstFgOrder["shopName"] as? String)?.contains(shopName, ignoreCase = true) ?: false) -> false - deliveryNo != null && !((firstFgOrder["deliveryNo"] as? String)?.contains(deliveryNo, ignoreCase = true) ?: false) -> false - ticketNo != null && !((firstFgOrder["ticketNo"] as? String)?.contains(ticketNo, ignoreCase = true) ?: false) -> false - else -> true - } - - if (matchesSearch) { - result.add(mapOf( - "id" to pickOrder.id, - "pickOrderId" to pickOrder.id, - "pickOrderCode" to pickOrder.code, - "pickOrderConsoCode" to pickOrder.consoCode, - "pickOrderStatus" to pickOrder.status.toString(), - "deliveryOrderId" to firstFgOrder["deliveryOrderId"], - "deliveryNo" to firstFgOrder["deliveryNo"], - "deliveryDate" to firstFgOrder["deliveryDate"], - "shopId" to firstFgOrder["shopId"], - "shopCode" to firstFgOrder["shopCode"], - "shopName" to firstFgOrder["shopName"], - "shopAddress" to firstFgOrder["shopAddress"], - "ticketNo" to firstFgOrder["ticketNo"], - "shopPoNo" to firstFgOrder["shopPoNo"], - "numberOfCartons" to firstFgOrder["numberOfCartons"], - "TruckLanceCode" to firstFgOrder["TruckLanceCode"], - "storeId" to firstFgOrder["storeId"], - "completedDate" to (pickOrder.completeDate?.toString() ?: pickOrder.modified?.toString()), - "fgPickOrders" to fgPickOrders - )) - } - } - } - - // 按完成时间倒序排列 - result.sortByDescending { it["completedDate"] as String? } - - println("Returning ${result.size} completed DO pick orders") - result - + + println("✅ FG Pick Orders from record: 1") + return listOf(result) + } catch (e: Exception) { - println("❌ Error in getCompletedDoPickOrders: ${e.message}") + println("❌ Error in getFgPickOrdersFromRecordByPickOrderId: ${e.message}") e.printStackTrace() - emptyList() - } -} -// ... existing code ... - -open fun getLotDetailsByPickOrderId(pickOrderId: Long): List> { - println("=== Debug: getLotDetailsByPickOrderId ===") - println("pickOrderId: $pickOrderId") - - // Get the pick order - val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) - if (pickOrder == null) { - println("❌ Pick order not found: $pickOrderId") return emptyList() } - - // ✅ 修复:使用正确的方法名 - val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId) - if (pickOrderLines.isEmpty()) { - println("❌ No pick order lines found for pick order: $pickOrderId") - return emptyList() - } - println("✅ Found ${pickOrderLines.size} pick order lines") - - - - // Build the SQL query similar to getAllPickOrderLotsWithDetailsWithoutAutoAssign - // but filtered by specific pickOrderId - val sql = """ - SELECT - -- Pick Order Information - po.id as pickOrderId, - po.code as pickOrderCode, - po.consoCode as pickOrderConsoCode, - DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, - po.type as pickOrderType, - po.status as pickOrderStatus, - po.assignTo as pickOrderAssignTo, - - -- Pick Order Line Information - pol.id as pickOrderLineId, - pol.qty as pickOrderLineRequiredQty, - pol.status as pickOrderLineStatus, - - -- Item Information - i.id as itemId, - i.code as itemCode, - i.name as itemName, - uc.code as uomCode, - uc.udfudesc as uomDesc, - uc.udfShortDesc as uomShortDesc, - - -- Lot Information - ill.id as lotId, - il.lotNo, - DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, - w.name as location, - COALESCE(uc.udfudesc, 'N/A') as stockUnit, - - -- Router Information - w.id as routerId, - w.`order` as routerIndex, - - - - -- Set quantities to NULL for rejected lots - CASE - WHEN sol.status = 'rejected' THEN NULL - ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) - END as availableQty, - - -- Required quantity for this lot - COALESCE(spl.qty, 0) as requiredQty, - - -- Actual picked quantity - COALESCE(sol.qty, 0) as actualPickQty, - - -- Suggested pick lot information - spl.id as suggestedPickLotId, - ill.status as lotStatus, - - -- Stock out line information - sol.id as stockOutLineId, - sol.status as stockOutLineStatus, - COALESCE(sol.qty, 0) as stockOutLineQty, - - -- Additional detailed fields - COALESCE(ill.inQty, 0) as inQty, - COALESCE(ill.outQty, 0) as outQty, - COALESCE(ill.holdQty, 0) as holdQty, - COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId, - ill.inventoryLotId as debugInventoryLotId, - - -- Calculate total picked quantity by ALL pick orders for this lot - COALESCE(( - SELECT SUM(sol_all.qty) - FROM fpsmsdb.stock_out_line sol_all - WHERE sol_all.inventoryLotLineId = ill.id - AND sol_all.deleted = false - AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed') - ), 0) as totalPickedByAllPickOrders, - - -- Calculate remaining available quantity correctly - (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, - - -- Add detailed debug fields for lotAvailability calculation - ill.status as debug_ill_status, - (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) as debug_is_expired, - sol.status as debug_sol_status, - (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as debug_remaining_stock, - (COALESCE(spl.qty, 0) - COALESCE(sol.qty, 0)) as debug_required_after_picked, - - -- Lot availability status - CASE - -- Check if lot is expired - WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' - -- Check if lot has rejected stock out line for this pick order - WHEN sol.status = 'rejected' THEN 'rejected' - -- Check if lot is unavailable due to insufficient stock - WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' - -- Check if lot status is unavailable - WHEN ill.status = 'unavailable' THEN 'status_unavailable' - -- Default to available - ELSE 'available' - END as lotAvailability, - - -- Processing status - CASE - WHEN sol.status = 'completed' THEN 'completed' - WHEN sol.status = 'rejected' THEN 'rejected' - WHEN sol.status = 'created' THEN 'pending' - ELSE 'pending' - END as processingStatus - - FROM fpsmsdb.pick_order po - JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id - JOIN fpsmsdb.items i ON i.id = pol.itemId - LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId - LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId - LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id - LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId - LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId - LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false - WHERE po.deleted = false - AND po.id = :pickOrderId - AND pol.deleted = false - AND ill.deleted = false - AND il.deleted = false - AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) - ORDER BY - CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, -- Show rejected lots first - COALESCE(w.`order`, 999999) ASC, - po.code ASC, - i.code ASC, - il.expiryDate ASC, - il.lotNo ASC - """.trimIndent() - - // ✅ 使用 jdbcDao 而不是 entityManager - println("🔍 Executing SQL for lot details by pick order: $sql") - println("🔍 With parameters: pickOrderId = $pickOrderId") - - val results = jdbcDao.queryForList(sql, mapOf("pickOrderId" to pickOrderId)) - println("✅ Total result count: ${results.size}") - - return results } + } - - -} diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt index 6334370..0fbb619 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt @@ -161,7 +161,10 @@ class PickOrderController( fun getPickOrderLineLotDetails(@PathVariable pickOrderLineId: Long): List> { return pickOrderService.getPickOrderLineLotDetails(pickOrderLineId); } - + @GetMapping("/lot-details-by-do-pick-order-record/{doPickOrderRecordId}") + fun getLotDetailsByDoPickOrderRecordId(@PathVariable doPickOrderRecordId: Long): Map { + return pickOrderService.getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId) + } @PostMapping("/groups") fun createGroup(@RequestBody request: Map): ResponseEntity> { @@ -297,9 +300,6 @@ fun getCompletedDoPickOrders( ): List> { return pickOrderService.getCompletedDoPickOrders(userId, pickOrderCode, shopName, deliveryNo, ticketNo) } -@GetMapping("/lot-details-by-pick-order/{pickOrderId}") -fun getLotDetailsByPickOrderId(@PathVariable pickOrderId: Long): List> { - return pickOrderService.getLotDetailsByPickOrderId(pickOrderId); -} + } \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20251023_01_enson/01_alter_table.sql b/src/main/resources/db/changelog/changes/20251023_01_enson/01_alter_table.sql new file mode 100644 index 0000000..4bbdee0 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251023_01_enson/01_alter_table.sql @@ -0,0 +1,12 @@ +-- liquibase formatted sql +-- changeset enson:alter_pick_execution_issue_category_20251023 + +ALTER TABLE pick_execution_issue +MODIFY COLUMN issue_category ENUM( + 'lot_issue', + 'resuggest_issue', + 'match_issue', + 'warehouse_issue', + 'truck_issue' +) NOT NULL; +