From 98a2fa939cf07dc5f65880a9c3f9fde483f9d074 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 3 Mar 2026 14:28:17 +0800 Subject: [PATCH] improve do pick order and show not yet finish do pick order and jo floor count --- .../entity/DoPickOrderRepository.kt | 5 + .../service/DoPickOrderService.kt | 48 ++ .../service/DoReleaseCoordinatorService.kt | 97 +++- .../web/DoPickOrderController.kt | 17 +- .../web/models/DoDetailResponse.kt | 19 +- .../jobOrder/service/JoPickOrderService.kt | 47 +- .../web/model/CreateJobOrderRequest.kt | 8 +- .../pickOrder/service/PickOrderService.kt | 548 +++++++----------- .../stock/service/StockTakeRecordService.kt | 7 +- .../stock/web/model/StockTakeRecordReponse.kt | 3 +- 10 files changed, 436 insertions(+), 363 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt index cff3b77..c547873 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt @@ -45,4 +45,9 @@ fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( releaseType: String, ticketStatus: DoPickOrderStatus ): List + + fun findFirstByHandledByAndDeletedFalseAndTicketStatusIn( + handledBy: Long, + ticketStatus: List + ): DoPickOrder? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt index fed9d14..c77b676 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt @@ -926,5 +926,53 @@ open class DoPickOrderService( return allPickOrderLines.size } + open fun findReleasedDoPickOrdersForSelection( + shopName: String?, + storeId: String?, + truck: String? +): List { + val doPickOrders = doPickOrderRepository.findByTicketStatusIn( + listOf(DoPickOrderStatus.released, DoPickOrderStatus.pending) + ) + + var filtered = doPickOrders + + if (!storeId.isNullOrBlank()) { + filtered = filtered.filter { it.storeId == storeId } + } + if (!shopName.isNullOrBlank()) { + filtered = filtered.filter { + it.shopName?.contains(shopName, ignoreCase = true) == true || + it.shopCode?.contains(shopName, ignoreCase = true) == true + } + } + if (!truck.isNullOrBlank()) { + filtered = filtered.filter { it.truckLanceCode == truck } + } + + return filtered.map { dpo -> + val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!) + val deliveryOrderCodes = lines + .mapNotNull { it.deliveryOrderCode } + .distinct() + + ReleasedDoPickOrderListItem( + id = dpo.id!!, + requiredDeliveryDate = dpo.requiredDeliveryDate, + shopCode = dpo.shopCode, + shopName = dpo.shopName, + storeId = dpo.storeId, + truckLanceCode = dpo.truckLanceCode, + truckDepartureTime = dpo.truckDepartureTime, + deliveryOrderCodes = deliveryOrderCodes.ifEmpty { + listOfNotNull(dpo.deliveryOrderCode) + } + ) + }.sortedWith( + compareBy { it.requiredDeliveryDate } + .thenBy { it.truckDepartureTime } + .thenBy { it.truckLanceCode } + ) + } }// 类结束 \ No newline at end of file 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 99e654f..b0d2a4a 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 @@ -20,7 +20,13 @@ import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService import java.time.LocalDate +import java.time.LocalDateTime import java.time.DayOfWeek +import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByDoPickOrderIdRequest +import com.ffii.fpsms.modules.user.entity.UserRepository +import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository +import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository +import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus data class BatchReleaseJobStatus( val jobId: String, val total: Int, @@ -39,6 +45,9 @@ class DoReleaseCoordinatorService( private val deliveryOrderRepository: DeliveryOrderRepository, private val doPickOrderRepository: DoPickOrderRepository, private val pickExecutionIssueService: PickExecutionIssueService, + private val userRepository: UserRepository, + private val pickOrderRepository: PickOrderRepository, + private val doPickOrderRecordRepository: DoPickOrderRecordRepository, ) { private val poolSize = Runtime.getRuntime().availableProcessors() private val executor = Executors.newFixedThreadPool(min(poolSize, 4)) @@ -911,4 +920,90 @@ class DoReleaseCoordinatorService( e.printStackTrace() } } -} \ No newline at end of file + + fun assignByDoPickOrderId(request: AssignByDoPickOrderIdRequest): MessageResponse { + val user = userRepository.findById(request.userId).orElse(null) + ?: return MessageResponse(id = null, code = "USER_NOT_FOUND", name = null, type = null, message = null, errorPosition = null, entity = null) + + val doPickOrder = doPickOrderRepository.findById(request.doPickOrderId).orElse(null) + ?: return MessageResponse(id = null, code = "NOT_FOUND", name = null, type = null, message = null, errorPosition = null, entity = null) + + // 檢查狀態是否可 assign + if (doPickOrder.ticketStatus !in listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released)) { + return MessageResponse(id = null, code = "INVALID_STATUS", name = null, type = null, message = null, errorPosition = null, entity = null) + } + + // 檢查是否有 non-issue lines(可重用 checkDoPickOrderHasNonIssueLines) + if (!checkDoPickOrderHasNonIssueLines(doPickOrder.id!!)) { + return MessageResponse(id = null, code = "NO_ORDERS", name = null, type = null, message = "All lines are issues", errorPosition = null, entity = null) + } + + // 更新 do_pick_order + doPickOrder.handledBy = request.userId + doPickOrder.handlerName = user.name + doPickOrder.ticketStatus = DoPickOrderStatus.released + doPickOrder.ticketReleaseTime = LocalDateTime.now() + doPickOrderRepository.save(doPickOrder) + + // 更新關聯的 pick orders + val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!) + lines.forEach { line -> + line.pickOrderId?.let { pickOrderId -> + pickOrderRepository.findById(pickOrderId).ifPresent { po -> + po.assignTo = user + po.status = PickOrderStatus.RELEASED + pickOrderRepository.save(po) + } + } + } + + // 更新 do_pick_order_record(若有) + lines.forEach { line -> + line.pickOrderId?.let { pickOrderId -> + val records = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) + records.forEach { record -> + record.handledBy = request.userId + record.handlerName = user.name + record.ticketStatus = DoPickOrderStatus.released + record.ticketReleaseTime = LocalDateTime.now() + } + if (records.isNotEmpty()) doPickOrderRecordRepository.saveAll(records) + } + } + + return MessageResponse( + id = doPickOrder.id, + code = "SUCCESS", + name = null, + type = null, + message = null, + errorPosition = null, + entity = null + ) + } + private fun checkDoPickOrderHasNonIssueLines(doPickOrderId: Long): Boolean { + return try { + val totalLinesSql = """ + SELECT COUNT(*) as total_lines + FROM fpsmsdb.do_pick_order_line dpol + WHERE dpol.do_pick_order_id = :doPickOrderId + AND dpol.deleted = 0 + """.trimIndent() + val totalLinesResult = jdbcDao.queryForList(totalLinesSql, mapOf("doPickOrderId" to doPickOrderId)) + val totalLines = (totalLinesResult.firstOrNull()?.get("total_lines") as? Number)?.toInt() ?: 0 + if (totalLines == 0) return true + val nonIssueLinesSql = """ + SELECT COUNT(*) as non_issue_lines + FROM fpsmsdb.do_pick_order_line dpol + WHERE dpol.do_pick_order_id = :doPickOrderId + AND dpol.deleted = 0 + AND (dpol.status IS NULL OR dpol.status != 'issue') + """.trimIndent() + val nonIssueLinesResult = jdbcDao.queryForList(nonIssueLinesSql, mapOf("doPickOrderId" to doPickOrderId)) + val nonIssueLines = (nonIssueLinesResult.firstOrNull()?.get("non_issue_lines") as? Number)?.toInt() ?: 0 + nonIssueLines > 0 + } catch (e: Exception) { + println("❌ Error checking non-issue lines for do_pick_order $doPickOrderId: ${e.message}") + true + } + }} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt index 9d58b48..45bbee6 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt @@ -61,11 +61,26 @@ class DoPickOrderController( fun releaseAssignedPickOrderByStore(@RequestBody request: AssignByStoreRequest): MessageResponse { return doPickOrderService.releaseAssignedByStore(request) } - +/* @GetMapping("/released") fun getReleasedDoPickOrders(): List { return doPickOrderService.findReleasedDoPickOrders() } + */ + + @GetMapping("/released") + fun getReleasedDoPickOrders( + @RequestParam(required = false) shopName: String?, + @RequestParam(required = false) storeId: String?, + @RequestParam(required = false) truck: String? + ): List { + return doPickOrderService.findReleasedDoPickOrdersForSelection(shopName, storeId, truck) + } + + @PostMapping("/assign-by-id") + fun assignByDoPickOrderId(@RequestBody request: AssignByDoPickOrderIdRequest): MessageResponse { + return doReleaseCoordinatorService.assignByDoPickOrderId(request) // 改用 doReleaseCoordinatorService + } @GetMapping("/summary-by-store") fun getSummaryByStore( @RequestParam storeId: String, diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt index aa31693..77dc880 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt @@ -3,6 +3,7 @@ package com.ffii.fpsms.modules.deliveryOrder.web.models import java.time.LocalDateTime import java.time.LocalDate import com.fasterxml.jackson.annotation.JsonFormat +import java.time.LocalTime data class DoDetailResponse( val id: Long, val code: String, @@ -89,4 +90,20 @@ interface DoSearchRowProjection { val estimatedArrivalDate: LocalDateTime? val status: String? val deliveryOrderLineCount: Long -} \ No newline at end of file +} +data class ReleasedDoPickOrderListItem( + val id: Long, // doPickOrderId,用於 assign + val requiredDeliveryDate: LocalDate?, // Date 欄 + val shopCode: String?, // Shop + val shopName: String?, // Shop + val storeId: String?, // 2/F or 4/F + val truckLanceCode: String?, // Truck (Lane) + val truckDepartureTime: LocalTime?, // Truck 時間 + val deliveryOrderCodes: List // 多個 DO code,前端換行顯示 +) + +// Assign 用的 request +data class AssignByDoPickOrderIdRequest( + val userId: Long, + val doPickOrderId: Long +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt index f2fb2b0..dd4dc1e 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -12,7 +12,7 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional - +import com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus import java.time.LocalDateTime @@ -1751,7 +1751,12 @@ open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Lo } } - +private fun normalizeFloor(raw: String): String { + if (raw.isBlank()) return raw + val cleaned = raw.trim().uppercase() + val num = cleaned.replace(Regex("[^0-9]"), "") + return if (num.isNotEmpty()) "${num}F" else cleaned +} open fun getAllJoPickOrders(): List { println("=== getAllJoPickOrders ===") @@ -1763,6 +1768,33 @@ open fun getAllJoPickOrders(): List { } println("Found ${releasedPickOrders.size} released job order pick orders") + val pickOrderIds = releasedPickOrders.mapNotNull { it.id } + + // 2. 批量查询每个 pick order 的按楼层统计(若没有则跳过) + val floorCountsByPickOrderId: Map>> = if (pickOrderIds.isNotEmpty()) { + val sql = """ + SELECT + po.id AS pickOrderId, + COALESCE(NULLIF(TRIM(w.store_id), ''), SUBSTRING_INDEX(w.code, '-', 1)) AS floorKey, + COUNT(DISTINCT pol.id) AS totalCount, + COUNT(DISTINCT CASE WHEN pol.status = 'COMPLETED' THEN pol.id END) AS finishedCount + FROM fpsmsdb.pick_order po + JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false + LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId AND spl.deleted = false + LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id AND ill.deleted = false + LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId + WHERE po.deleted = false + AND po.status = 'RELEASED' + AND po.joId IS NOT NULL + AND po.id IN (${pickOrderIds.joinToString(",")}) + AND w.id IS NOT NULL + GROUP BY po.id, floorKey + """.trimIndent() + jdbcDao.queryForList(sql, emptyMap()).groupBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L } + } else { + emptyMap() + } + val jobOrderPickOrders = releasedPickOrders.mapNotNull { pickOrder -> println("Processing pick order: ${pickOrder.id}, code: ${pickOrder.code}") @@ -1800,7 +1832,13 @@ open fun getAllJoPickOrders(): List { val jobOrderType = jobOrder.jobTypeId?.let { jobTypeRepository.findById(it).orElse(null) } println("✅ Building response for pick order ${pickOrder.id}") - + val floorPickCounts = floorCountsByPickOrderId[pickOrder.id]?.map { row -> + com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto( + floor = normalizeFloor((row["floorKey"] as? String).orEmpty()), + finishedCount = (row["finishedCount"] as? Number)?.toInt() ?: 0, + totalCount = (row["totalCount"] as? Number)?.toInt() ?: 0 + ) + }.orEmpty() AllJoPickOrderResponse( id = pickOrder.id ?: 0L, pickOrderId = pickOrder.id, @@ -1816,7 +1854,8 @@ open fun getAllJoPickOrders(): List { uomId = 0, uomName = bom?.outputQtyUom?: "", jobOrderStatus = jobOrder.status?.value ?: "", - finishedPickOLineCount = finishedLines + finishedPickOLineCount = finishedLines, + floorPickCounts = floorPickCounts ) } diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt index f746605..180db91 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt @@ -34,7 +34,11 @@ data class CreateJobOrderProcessRequest ( val status: String = "pending", ) - +data class FloorPickCountDto( + val floor: String, // e.g. "2F", "4F" + val finishedCount: Int, + val totalCount: Int +) data class AllJoPickOrderResponse( val id: Long, val pickOrderId: Long?, @@ -50,7 +54,7 @@ data class AllJoPickOrderResponse( val uomName: String, val jobOrderStatus: String, val finishedPickOLineCount: Int, - + val floorPickCounts: List = emptyList() ) 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 1d999d9..770f052 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 @@ -3328,406 +3328,252 @@ ORDER BY return enrichedResults } // 修改后的逻辑 -open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { + /* + open fun getAllPickOrderLotsWithDetailsHierarchicalold(userId: Long): Map { println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (Repository-based) ===") 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(通过 do_pick_order_line) - // 这样可以确保只获取同一个 ticket 的 pick orders - val userPickOrdersForDo = pickOrderRepository.findAll() - .filter { - it.deleted == false && - it.assignTo?.id == userId && - it.type?.value == "do" && - (it.status == PickOrderStatus.RELEASED || - it.status == PickOrderStatus.PENDING || - it.status == PickOrderStatus.PICKING || - it.status == PickOrderStatus.ASSIGNED) - } - - if (userPickOrdersForDo.isEmpty()) { - println("❌ No active pick orders found for user $userId") - return mapOf( - "fgInfo" to null, - "pickOrders" to emptyList() - ) - } - - val activePickOrderIds = userPickOrdersForDo.mapNotNull { it.id } - println(" Found ${activePickOrderIds.size} active pick orders assigned to user $userId") - - // Step 2: 通过 do_pick_order_line 找到相关的 do_pick_order - val doPickOrderLineRecords = activePickOrderIds.flatMap { poId -> - doPickOrderLineRepository.findByPickOrderIdAndDeletedFalse(poId) - } - - val doPickOrderIds = doPickOrderLineRecords.mapNotNull { it.doPickOrderId }.distinct() - - if (doPickOrderIds.isEmpty()) { - println("❌ No do_pick_order found for pick orders: $activePickOrderIds") - return mapOf( - "fgInfo" to null, - "pickOrders" to emptyList() - ) - } - - println(" Found ${doPickOrderIds.size} do_pick_order records") - - // Step 3: 获取第一个 do_pick_order 的详细信息(用于构建 fgInfo) - val doPickOrder = doPickOrderRepository.findById(doPickOrderIds.first()).orElse(null) + + // Step 1:直接按 handledBy 查当前用户的活动 do_pick_order(一个 ticket) + val activeTicketStatuses = listOf("released", "picking") // 如果你用的是 DoPickOrderStatus 枚举,也可以改成 List + val doPickOrder = doPickOrderRepository + .findFirstByHandledByAndDeletedFalseAndTicketStatusIn(user.id!!, activeTicketStatuses) + if (doPickOrder == null) { - println("❌ do_pick_order not found: ${doPickOrderIds.first()}") + println("❌ No active do_pick_order found for handledBy user $userId") return mapOf( "fgInfo" to null, "pickOrders" to emptyList() ) } - + val doPickOrderId = doPickOrder.id!! - println(" Using do_pick_order ID: $doPickOrderId") - - // Step 4: 关键修改 - 只获取这个 do_pick_order 下的所有 pick orders(无论状态) + println(" Using do_pick_order ID (by handledBy): $doPickOrderId") + + // Step 2:用这个 do_pick_orderId 查对应的 do_pick_order_line / pick_order val allDoPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrderId) val allPickOrderIdsForThisTicket = allDoPickOrderLines.mapNotNull { it.pickOrderId }.distinct() - + println(" Found ${allPickOrderIdsForThisTicket.size} pick orders in this do_pick_order (including completed)") - - // Step 5: 加载这些 pick orders(包括 COMPLETED 状态) + + // Step 3:加载这些 pick orders(包括 COMPLETED) val pickOrders = pickOrderRepository.findAllById(allPickOrderIdsForThisTicket) - .filter { - it.deleted == false && - it.assignTo?.id == userId && // 确保是分配给该用户的 + .filter { + it.deleted == false && + it.assignTo?.id == userId && it.type?.value == "do" - // 不限制状态,包括 COMPLETED } - + println(" Loaded ${pickOrders.size} pick orders (including completed)") - - // 收集所有需要的数据 - // 收集所有需要的数据 - val allPickOrderLineIds = pickOrders.flatMap { it.pickOrderLines }.mapNotNull { it.id } - - // 使用 Repository 批量加载所有 suggestions 和 stock out lines - // 使用 Repository 批量加载所有 suggestions 和 stock out lines - val allSuggestions = suggestPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds) // 修复:使用 suggestPickLotRepository + + // Step 4:原来你从 3413 行开始的收集所有 line / lots 的逻辑,全部保留 + val allPickOrderLineIds = pickOrders.flatMap { it.pickOrderLines }.mapNotNull { it.id } + + val allSuggestions = suggestPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds) val allStockOutLines = allPickOrderLineIds.flatMap { lineId -> stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(lineId) } - - // 按 pickOrderLineId 分组 - val suggestionsByLineId = allSuggestions.groupBy { spl: SuggestedPickLot -> // 修复:明确类型 + + val suggestionsByLineId = allSuggestions.groupBy { spl: SuggestedPickLot -> spl.pickOrderLine?.id } - val stockOutLinesByLineId = allStockOutLines.groupBy { sol: StockOutLineInfo -> // 修复:明确类型 + val stockOutLinesByLineId = allStockOutLines.groupBy { sol: StockOutLineInfo -> sol.pickOrderLineId } - - // 构建层级数据结构 + val allPickOrderLines = mutableListOf>() val lineCountsPerPickOrder = mutableListOf() val pickOrderIdsList = mutableListOf() val pickOrderCodesList = mutableListOf() val doOrderIdsList = mutableListOf() val deliveryOrderCodesList = mutableListOf() - + pickOrders.forEach { po -> pickOrderIdsList.add(po.id!!) pickOrderCodesList.add(po.code ?: "") - + val doOrderId = po.deliveryOrder?.id if (doOrderId != null) doOrderIdsList.add(doOrderId) deliveryOrderCodesList.add(po.deliveryOrder?.code ?: "") - + val lines = po.pickOrderLines.filter { !it.deleted } - - val lineDtos = lines.map { pol -> - val lineId = pol.id!! + + val lineDtos = po.pickOrderLines + .filter { !it.deleted } + .map { pol -> + val lineId = pol.id val item = pol.item val uom = pol.uom - val zero = BigDecimal.ZERO - val today = LocalDate.now() - // 使用 Repository 获取该 line 的 suggestions 和 stock out lines - val suggestions = suggestionsByLineId[lineId] ?: emptyList() // 修复:明确类型 - val stockOutLines = stockOutLinesByLineId[lineId] ?: emptyList() // 修复:明确类型 - // 合并 suggestions 和 stock out lines,按 (lotId, stockOutLineId) 去重 - // 合并 suggestions 和 stock out lines,按 lotId 去重(合并相同 lot 的多个条目) - val lotMap = mutableMapOf>() - - // 第一步:处理 suggestions,构建基础数据 - suggestions.forEach { spl: SuggestedPickLot -> - val ill = spl.suggestedLotLine - if (ill != null) { - val lotId = ill.id - val il = ill.inventoryLot - val w = ill.warehouse - val isExpired = il?.expiryDate?.let { exp: LocalDate -> exp.isBefore(today) } == true - val availableQty = (ill.inQty ?: zero) - .minus(ill.outQty ?: zero) - .minus(ill.holdQty ?: zero) - - // 查找对应的 stock out line(通过 lotId 查找,而不是通过 suggestion.stockOutLine) - val stockOutLine = stockOutLines.find { sol: StockOutLineInfo -> - sol.inventoryLotLineId == lotId - } - - if (lotMap.containsKey(lotId)) { - // 如果已存在,合并 requiredQty(累加) - val existing = lotMap[lotId]!! - val existingRequiredQty = (existing["requiredQty"] as? BigDecimal) ?: zero - val newRequiredQty = spl.qty ?: zero - existing["requiredQty"] = existingRequiredQty.plus(newRequiredQty) - - // 如果当前有 stockOutLine 但 existing 没有,更新 stockOutLine 信息 - if (stockOutLine != null && existing["stockOutLineId"] == null) { - existing["stockOutLineId"] = stockOutLine.id - existing["stockOutLineStatus"] = stockOutLine.status - existing["stockOutLineQty"] = numToBigDecimal(stockOutLine.qty as? Number) - existing["actualPickQty"] = numToBigDecimal(stockOutLine.qty as? Number) - existing["processingStatus"] = when { - stockOutLine.status == "completed" -> "completed" - stockOutLine.status == "rejected" -> "rejected" - else -> "pending" - } - existing["lotAvailability"] = when { - isExpired -> "expired" - stockOutLine.status == "rejected" -> "rejected" - availableQty <= zero -> "insufficient_stock" - ill.status?.value == "unavailable" -> "status_unavailable" - else -> "available" - } - } - } else { - // 首次遇到,创建新条目 - lotMap[lotId] = mutableMapOf( - "id" to lotId, - "lotNo" to il?.lotNo, - "expiryDate" to il?.expiryDate?.toString(), - "location" to w?.code, - "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), - "availableQty" to availableQty, - "requiredQty" to (spl.qty ?: zero), - "actualPickQty" to numToBigDecimal(stockOutLine?.qty as? Number), - "inQty" to ill.inQty, - "outQty" to ill.outQty, - "holdQty" to ill.holdQty, - "lotStatus" to ill.status?.value, - "stockInLineId" to il?.stockInLine?.id, - "lotAvailability" to when { - isExpired -> "expired" - stockOutLine?.status == "rejected" -> "rejected" - availableQty <= zero -> "insufficient_stock" - ill.status?.value == "unavailable" -> "status_unavailable" - else -> "available" - }, - "processingStatus" to when { - stockOutLine?.status == "completed" -> "completed" - stockOutLine?.status == "rejected" -> "rejected" - else -> "pending" - }, - "suggestedPickLotId" to spl.id, - "stockOutLineId" to stockOutLine?.id, - "stockOutLineStatus" to stockOutLine?.status, - "stockOutLineQty" to numToBigDecimal(stockOutLine?.qty as? Number), - "router" to mapOf( - "id" to null, - "index" to w?.order, - "route" to w?.code, - "area" to w?.code, - "itemCode" to item?.id, - "itemName" to item?.name, - "uomId" to uom?.code, - "noofCarton" to (spl.qty ?: zero) - ) - ) - } - } - } - - // 第二步:处理 stock out lines(包括没有 suggestion 的,或更新已存在的) - stockOutLines.forEach { sol: StockOutLineInfo -> - val inventoryLotLineId = sol.inventoryLotLineId - val stockOutLineId = sol.id - - if (inventoryLotLineId != null) { - // 有 lot 的 stock out line - if (lotMap.containsKey(inventoryLotLineId)) { - // 如果 lot 已存在,更新 stockOutLine 信息(如果还没有) - val existing = lotMap[inventoryLotLineId]!! - if (existing["stockOutLineId"] == null) { - existing["stockOutLineId"] = stockOutLineId - existing["stockOutLineStatus"] = sol.status - existing["stockOutLineQty"] = numToBigDecimal(sol.qty as? Number) - existing["actualPickQty"] = numToBigDecimal(sol.qty as? Number) - existing["processingStatus"] = when { - sol.status == "completed" -> "completed" - sol.status == "rejected" -> "rejected" - else -> "pending" - } - - // 更新 lotAvailability(如果 stockOutLine 是 rejected) - if (sol.status == "rejected") { - existing["lotAvailability"] = "rejected" - } - } - } else { - // lot 不存在,创建新条目(没有 suggestion 的情况) - val ill = inventoryLotLineRepository.findById(inventoryLotLineId).orElse(null) - if (ill != null) { - val il = ill.inventoryLot - val w = ill.warehouse - val isExpired = il?.expiryDate?.let { exp: LocalDate -> exp.isBefore(today) } == true - val availableQty = (ill.inQty ?: zero) - .minus(ill.outQty ?: zero) - .minus(ill.holdQty ?: zero) - - // 查找对应的 suggestion(如果有) - val suggestion = suggestions.find { spl: SuggestedPickLot -> - spl.suggestedLotLine?.id == inventoryLotLineId - } - - lotMap[inventoryLotLineId] = mutableMapOf( - "id" to inventoryLotLineId, - "lotNo" to il?.lotNo, - "expiryDate" to il?.expiryDate?.toString(), - "location" to w?.code, - "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), - "availableQty" to availableQty, - "requiredQty" to (suggestion?.qty ?: zero), - "actualPickQty" to numToBigDecimal(sol.qty as? Number), - "inQty" to ill.inQty, - "outQty" to ill.outQty, - "holdQty" to ill.holdQty, - "lotStatus" to ill.status?.value, - "stockInLineId" to il?.stockInLine?.id, - "lotAvailability" to when { - isExpired -> "expired" - sol.status == "rejected" -> "rejected" - availableQty <= zero -> "insufficient_stock" - ill.status?.value == "unavailable" -> "status_unavailable" - else -> "available" - }, - "processingStatus" to when { - sol.status == "completed" -> "completed" - sol.status == "rejected" -> "rejected" - else -> "pending" - }, - "suggestedPickLotId" to suggestion?.id, - "stockOutLineId" to stockOutLineId, - "stockOutLineStatus" to sol.status, - "stockOutLineQty" to numToBigDecimal(sol.qty as? Number), - "router" to mapOf( - "id" to null, - "index" to w?.order, - "route" to w?.code, - "area" to w?.code, - "itemCode" to item?.id, - "itemName" to item?.name, - "uomId" to uom?.code, - "noofCarton" to (suggestion?.qty ?: zero) - ) - ) - } - } - } - } - - val lots = lotMap.values.toList() - - // 处理没有 lot 的 stock out lines(noLot = true) - val stockouts = stockOutLines - .filter { sol: StockOutLineInfo -> sol.inventoryLotLineId == null } - .map { sol: StockOutLineInfo -> - mapOf( - "id" to sol.id, - "status" to sol.status, - "qty" to numToBigDecimal(sol.qty as? Number), - "lotId" to null, - "lotNo" to "", - "location" to "", - "availableQty" to null, - "noLot" to true - ) - } + // 获取该 line 的 suggestions 和 stock out lines + val suggestions = lineId?.let { suggestionsByLineId[it] } ?: emptyList() + val stockOutLines = lineId?.let { stockOutLinesByLineId[it] } ?: emptyList() + + // 构建 lots(合并相同 lot 的多个 suggestions) + val lotMap = mutableMapOf() + + suggestions.forEach { spl -> + val ill = spl.suggestedLotLine + if (ill != null && ill.id != null) { + val illId = ill.id!! + val illEntity = inventoryLotLinesMap[illId] ?: ill + val il = illEntity.inventoryLot + val w = illEntity.warehouse + val isExpired = il?.expiryDate?.let { exp -> exp.isBefore(today) } == true + + val availableQty = (illEntity.inQty ?: zero) + .minus(illEntity.outQty ?: zero) + .minus(illEntity.holdQty ?: zero) + + // 查找对应的 stock out line + val stockOutLine = stockOutLines.find { sol -> + sol.inventoryLotLineId == illId + } + + // 计算 actualPickQty + val actualPickQty = stockOutLine?.qty?.let { numToBigDecimal(it as? Number) } + + if (lotMap.containsKey(illId)) { + // 合并 requiredQty + val existing = lotMap[illId]!! + val newRequiredQty = (existing.requiredQty ?: zero).plus(spl.qty ?: zero) + lotMap[illId] = existing.copy(requiredQty = newRequiredQty) + } else { + lotMap[illId] = LotDetailResponse( + id = illId, + lotNo = il?.lotNo, + expiryDate = il?.expiryDate, + location = w?.code, + stockUnit = illEntity.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A", + availableQty = availableQty, + requiredQty = spl.qty, + actualPickQty = actualPickQty, + inQty = illEntity.inQty, + outQty = illEntity.outQty, + holdQty = illEntity.holdQty, + lotStatus = illEntity.status?.value, + lotAvailability = when { + isExpired -> "expired" + stockOutLine?.status == "rejected" -> "rejected" + availableQty <= zero -> "insufficient_stock" + illEntity.status?.value == "unavailable" -> "status_unavailable" + else -> "available" + }, + processingStatus = when { + stockOutLine?.status == "completed" -> "completed" + stockOutLine?.status == "rejected" -> "rejected" + else -> "pending" + }, + suggestedPickLotId = spl.id, + stockOutLineId = stockOutLine?.id, + stockOutLineStatus = stockOutLine?.status, + stockOutLineQty = stockOutLine?.qty?.let { numToBigDecimal(it as? Number) }, + router = RouterInfoResponse( + id = null, + index = w?.order.toString(), + route = w?.code, + area = w?.code + ) + ) + } + } + } + + val lots = lotMap.values.toList() + + // 构建 stockouts(包括没有 lot 的) + val stockouts = stockOutLines.map { sol -> + val illId = sol.inventoryLotLineId + val ill = illId?.let { inventoryLotLinesMap[it] } + val il = ill?.inventoryLot + val w = ill?.warehouse + val available = if (ill == null) null else + (ill.inQty ?: zero) + .minus(ill.outQty ?: zero) + .minus(ill.holdQty ?: zero) - mapOf( - "id" to lineId, - "pickOrderLineId" to lineId, - "pickOrderId" to po.id, - "requiredQty" to pol.qty, - "status" to pol.status?.value, - "item" to mapOf( - "id" to item?.id, - "code" to item?.code, - "name" to item?.name, - "uomCode" to uom?.code, - "uomDesc" to uom?.udfudesc, - "uomShortDesc" to uom?.udfShortDesc - ), - "lots" to lots, - "stockouts" to stockouts + StockOutDetailResponse( + id = sol.id, + status = sol.status, + qty = sol.qty?.let { numToBigDecimal(it as? Number) }, + lotId = ill?.id, + lotNo = il?.lotNo ?: "", + location = w?.code ?: "", + availableQty = available, + noLot = (ill == null) ) } - lineCountsPerPickOrder.add(lineDtos.size) - allPickOrderLines.addAll(lineDtos) + PickOrderLineDetailResponse( + id = lineId, + requiredQty = pol.qty, + status = pol.status?.value, + item = ItemInfoResponse( + id = item?.id, + code = item?.code, + name = item?.name, + uomCode = uom?.code, + uomDesc = uom?.udfudesc, + uomShortDesc = uom?.udfShortDesc + ), + lots = lots, + stockouts = stockouts + ) } - - // 按 router index 排序 + + + lineCountsPerPickOrder.add(lineDtos.size) + allPickOrderLines.addAll(lineDtos) + } + + // 排序、fgInfo、mergedPickOrder 这些也全部沿用你当前代码,只要用上面定义好的 doPickOrder/doPickOrderId 即可: allPickOrderLines.sortWith(compareBy( - { line -> - val lots = line["lots"] as? List> - val firstLot = lots?.firstOrNull() - val router = firstLot?.get("router") as? Map - val indexValue = router?.get("index") - - // 提取楼层排序值:1F = 1, 2F/4F = 2, 其他 = 3 - val floorSortValue = when (indexValue) { - is String -> { - val parts = indexValue.split("-") - if (parts.isNotEmpty()) { - val floorPart = parts[0].uppercase() // "1F", "2F", "4F" - when (floorPart) { - "1F" -> 1 - "2F", "4F" -> 2 - else -> 3 - } - } else { - 3 + { line -> + val lots = line["lots"] as? List> + val firstLot = lots?.firstOrNull() + val router = firstLot?.get("router") as? Map + val indexValue = router?.get("index") + val floorSortValue = when (indexValue) { + is String -> { + val parts = indexValue.split("-") + if (parts.isNotEmpty()) { + val floorPart = parts[0].uppercase() + when (floorPart) { + "1F" -> 1 + "2F", "4F" -> 2 + else -> 3 + } + } else 3 } + else -> 3 } - else -> 3 - } - floorSortValue - }, - { line -> - val lots = line["lots"] as? List> - val firstLot = lots?.firstOrNull() - val router = firstLot?.get("router") as? Map - val indexValue = router?.get("index") - // 提取数字部分:格式为 "store_id-number" (如 "2F-004") - when (indexValue) { - is Number -> indexValue.toInt() - is String -> { - val parts = indexValue.split("-") - if (parts.size > 1) { - parts.last().toIntOrNull() ?: 999999 - } else { - indexValue.toIntOrNull() ?: 999999 + floorSortValue + }, + { line -> + val lots = line["lots"] as? List> + val firstLot = lots?.firstOrNull() + val router = firstLot?.get("router") as? Map + val indexValue = router?.get("index") + when (indexValue) { + is Number -> indexValue.toInt() + is String -> { + val parts = indexValue.split("-") + if (parts.size > 1) { + parts.last().toIntOrNull() ?: 999999 + } else { + indexValue.toIntOrNull() ?: 999999 + } } + else -> 999999 } - else -> 999999 } - } -)) - - // 构建 FG 信息 + )) + val fgInfo = mapOf( "doPickOrderId" to doPickOrderId, "ticketNo" to doPickOrder.ticketNo, @@ -3737,8 +3583,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { +*/ +open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===") println("userId filter: $userId") @@ -3796,6 +3642,8 @@ open fun getAllPickOrderLotsWithDetailsHierarchicalold(userId: Long): Map