From 571edfe68e5d30c66256874ccc71502f5fa9f3bf Mon Sep 17 00:00:00 2001 From: "kelvin.yau" Date: Mon, 20 Oct 2025 17:44:47 +0800 Subject: [PATCH] update --- .../FinishedGoodSearch/FinishedGoodSearch.tsx | 3 + .../service/DeliveryOrderService.kt | 386 +++++++------ .../service/DoPickOrderService.kt | 174 +++--- .../web/models/ReleaseDoRequest.kt | 19 +- .../jobOrder/service/JoPickOrderService.kt | 2 +- .../pickOrder/service/PickOrderService.kt | 510 ++++++++---------- 6 files changed, 540 insertions(+), 554 deletions(-) create mode 100644 src/components/FinishedGoodSearch/FinishedGoodSearch.tsx diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx new file mode 100644 index 0000000..b28b04f --- /dev/null +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -0,0 +1,3 @@ + + + diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt index 37abfe1..9532214 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt @@ -399,242 +399,216 @@ open class DeliveryOrderService( return savedDeliveryOrder } - @Transactional(rollbackFor = [Exception::class]) - open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse { - println("�� DEBUG: Starting releaseDeliveryOrder for DO ID: ${request.id}, User ID: ${request.userId}") - - val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id) - ?: throw NoSuchElementException("Delivery Order not found") +@Transactional(rollbackFor = [Exception::class]) +open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse { + println("🔍 DEBUG: Starting releaseDeliveryOrder for DO ID: ${request.id}, User ID: ${request.userId}") + + val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id) + ?: throw NoSuchElementException("Delivery Order not found") + + println("🔍 DEBUG: Found delivery order - ID: ${deliveryOrder.id}, Shop: ${deliveryOrder.shop?.code}, Status: ${deliveryOrder.status}") + + deliveryOrder.apply { + status = DeliveryOrderStatus.PENDING + } + deliveryOrderRepository.save(deliveryOrder) + + val pols = deliveryOrder.deliveryOrderLines.map { + SavePickOrderLineRequest( + itemId = it.item?.id, + qty = it.qty ?: BigDecimal.ZERO, + uomId = it.uom?.id, + ) + } + val po = SavePickOrderRequest( + doId = deliveryOrder.id, + type = PickOrderType.DELIVERY_ORDER, + targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(), + pickOrderLine = pols + ) + + val createdPickOrder = pickOrderService.create(po) + println("🔍 DEBUG: Created pick order - ID: ${createdPickOrder.id}") + + val consoCode = pickOrderService.assignConsoCode() + val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null) + + if (pickOrderEntity != null) { + pickOrderEntity.consoCode = consoCode + pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED + pickOrderRepository.saveAndFlush(pickOrderEntity) + println("🔍 DEBUG: Assigned consoCode $consoCode to pick order ${createdPickOrder.id}") - println("�� DEBUG: Found delivery order - ID: ${deliveryOrder.id}, Shop: ${deliveryOrder.shop?.code}, Status: ${deliveryOrder.status}") + val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!) + println("🔍 DEBUG: Loaded ${lines.size} pick order lines from DB") - deliveryOrder.apply { - status = DeliveryOrderStatus.PENDING - } - deliveryOrderRepository.save(deliveryOrder) - - val pols = deliveryOrder.deliveryOrderLines.map { - SavePickOrderLineRequest( - itemId = it.item?.id, - qty = it.qty ?: BigDecimal.ZERO, - uomId = it.uom?.id, - ) - } - val po = SavePickOrderRequest( - doId = deliveryOrder.id, - type = PickOrderType.DELIVERY_ORDER, - targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(), - pickOrderLine = pols + val suggestions = suggestedPickLotService.suggestionForPickOrderLines( + SuggestedPickLotForPolRequest(pickOrderLines = lines) ) - - val createdPickOrder = pickOrderService.create(po) - println("🔍 DEBUG: Created pick order - ID: ${createdPickOrder.id}") + println("🔍 DEBUG: Got ${suggestions.suggestedList.size} suggested pick lots") - val consoCode = pickOrderService.assignConsoCode() - val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null) + val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) + println("🔍 DEBUG: Saved ${saveSuggestedPickLots.size} suggested pick lots") - if (pickOrderEntity != null) { - pickOrderEntity.consoCode = consoCode - pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED - pickOrderRepository.saveAndFlush(pickOrderEntity) - println("�� DEBUG: Assigned consoCode $consoCode to pick order ${createdPickOrder.id}") - - // ✅ Debug: Check pick order lines - println("�� DEBUG: Pick order has ${pickOrderEntity.pickOrderLines?.size ?: 0} pick order lines") - pickOrderEntity.pickOrderLines?.forEach { line -> - println("🔍 DEBUG: Pick order line - Item ID: ${line.item?.id}, Qty: ${line.qty}") - } - val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!) - println("🔍 DEBUG: Loaded ${lines.size} pick order lines from DB") - if (lines.isEmpty()) { - println("⚠️ No pick order lines found; suggestions will be empty") - } - // ✅ Create suggested pick lots and hold inventory (like normal release) - println("🔍 DEBUG: About to call suggestionForPickOrderLines for pick order ${pickOrderEntity.id}") - val suggestions = suggestedPickLotService.suggestionForPickOrderLines( - SuggestedPickLotForPolRequest(pickOrderLines = lines) - ) - println("🔍 DEBUG: Got ${suggestions.suggestedList.size} suggested pick lots") - - if (suggestions.suggestedList.isEmpty()) { - println("⚠️ WARNING: No suggested pick lots generated - this might be due to no inventory available") - } - - val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) - println("🔍 DEBUG: Saved ${saveSuggestedPickLots.size} suggested pick lots") - - - // ✅ Hold inventory quantities - val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( - saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } - ) - - saveSuggestedPickLots.forEach { lot -> - if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) { - val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine) - if (lineIndex >= 0) { - inventoryLotLines[lineIndex].holdQty = - (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO) - } + // ✅ Hold inventory quantities + val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( + saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } + ) + + saveSuggestedPickLots.forEach { lot -> + if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) { + val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine) + if (lineIndex >= 0) { + inventoryLotLines[lineIndex].holdQty = + (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO) } } - inventoryLotLineRepository.saveAll(inventoryLotLines) - - // ✅ Create stock out record and pre-create stock out lines - val stockOut = StockOut().apply { - this.type = "job" - this.consoPickOrderCode = consoCode - this.status = StockOutStatus.PENDING.status - this.handler = request.userId - } - val savedStockOut = stockOutRepository.saveAndFlush(stockOut) - - // ✅ Pre-create stock out lines for suggested lots - saveSuggestedPickLots.forEach { lot -> - val polId = lot.pickOrderLine?.id - val illId = lot.suggestedLotLine?.id - if (polId != null && illId != null) { - 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 = StockOutLine().apply { - this.stockOut = savedStockOut - this.pickOrderLine = pickOrderLine - this.inventoryLotLine = inventoryLotLine - this.item = pickOrderLine.item - this.status = StockOutLineStatus.PENDING.status - this.qty = 0.0 - } - stockOutLineRepository.save(line) + } + inventoryLotLineRepository.saveAll(inventoryLotLines) + + // ✅ Create stock out record and pre-create stock out lines + val stockOut = StockOut().apply { + this.type = "job" + this.consoPickOrderCode = consoCode + this.status = StockOutStatus.PENDING.status + this.handler = request.userId + } + val savedStockOut = stockOutRepository.saveAndFlush(stockOut) + + // ✅ Pre-create stock out lines for suggested lots + saveSuggestedPickLots.forEach { lot -> + val polId = lot.pickOrderLine?.id + val illId = lot.suggestedLotLine?.id + if (polId != null && illId != null) { + 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 = StockOutLine().apply { + this.stockOut = savedStockOut + this.pickOrderLine = pickOrderLine + this.inventoryLotLine = inventoryLotLine + this.item = pickOrderLine.item + this.status = StockOutLineStatus.PENDING.status + this.qty = 0.0 } + stockOutLineRepository.save(line) } } } } + } - // ✅ CREATE do_pick_order_record entries - // 第 471-555 行附近 - 修复创建逻辑 - -// ✅ CREATE do_pick_order_record entries -val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() -val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) + // ✅ CREATE do_pick_order entries + val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() + val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) -println("🔍 DEBUG: Target date: $targetDate, Date prefix: $datePrefix") -val truck = deliveryOrder.shop?.id?.let { shopId -> - println("🔍 DEBUG: Looking for truck with shop ID: $shopId") - val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) - println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId") - - if (trucks.size <= 1) { - // 如果只有一个或没有 truck,直接返回 - return@let trucks.firstOrNull() - } + println("🔍 DEBUG: Target date: $targetDate, Date prefix: $datePrefix") - // ✅ 分析 DO order lines 中的 items 分布 + // ✅ 分析 items 来确定 preferredFloor val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() - println("🔍 DEBUG: Analyzing ${itemIds.size} unique items in DO order lines") - - // 使用 SQL 查询统计每个楼层的库存数量 val inventoryQuery = """ - SELECT w.store_id as floor, COUNT(*) as inventory_count - FROM inventory_lot_line ill - JOIN inventory_lot il ON il.id = ill.inventoryLotId - JOIN warehouse w ON w.id = ill.warehouseId - WHERE il.itemId IN (${itemIds.joinToString(",")}) - AND ill.deleted = false - AND il.deleted = false - AND w.deleted = false - AND ill.inQty > ill.outQty + COALESCE(ill.holdQty, 0) + SELECT + w.store_id as floor, + COUNT(DISTINCT il.itemId) as item_count + FROM inventory_lot il + INNER JOIN inventory i ON i.itemId = il.itemId AND i.deleted = 0 AND i.onHandQty > 0 + INNER JOIN inventory_lot_line ill ON ill.inventoryLotId = il.id AND ill.deleted = 0 + INNER JOIN warehouse w ON w.id = ill.warehouseId AND w.deleted = 0 AND w.store_id IN ('2F', '4F') + WHERE il.itemId IN (${itemIds.joinToString(",")}) AND il.deleted = 0 GROUP BY w.store_id """.trimIndent() - + val inventoryResults = jdbcDao.queryForList(inventoryQuery) - val floorInventoryCount = mutableMapOf() - - inventoryResults.forEach { row: Map -> - val floor = row["floor"] as? String ?: "Other" - val count = (row["inventory_count"] as? Number)?.toInt() ?: 0 - floorInventoryCount[floor] = count + val floorItemCount = mutableMapOf() + inventoryResults.forEach { row -> + floorItemCount[row["floor"] as? String ?: "Other"] = (row["item_count"] as? Number)?.toInt() ?: 0 } - - println("🔍 DEBUG: Floor inventory distribution: $floorInventoryCount") - - // 决定使用哪个楼层 - val preferredFloor = when { - floorInventoryCount["2F"] ?: 0 > floorInventoryCount["4F"] ?: 0 -> "2F" - floorInventoryCount["4F"] ?: 0 > floorInventoryCount["2F"] ?: 0 -> "4F" - else -> "2F" // 默认使用 2F + println("🔍 DEBUG: Floor item count distribution: $floorItemCount") + + // ✅ 新逻辑:只有所有 items 都在 4F,才算 4F,否则算 2F + val preferredFloor = if ((floorItemCount["4F"] ?: 0) == itemIds.size && (floorItemCount["2F"] ?: 0) == 0) { + "4F" // 所有 items 都在 4F + } else { + "2F" // 只要有任何 item 不在 4F,就算 2F } - println("🔍 DEBUG: Preferred floor based on inventory: $preferredFloor") + println("🔍 DEBUG: Preferred floor: $preferredFloor") - val preferredStoreId = when (preferredFloor) { - "2F" -> 2 - "4F" -> 4 - else -> 2 + // ✅ 查找匹配 preferred floor 的 truck + val truck = deliveryOrder.shop?.id?.let { shopId -> + val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) + val preferredStoreId = when (preferredFloor) { + "2F" -> 2 + "4F" -> 4 + else -> 2 + } + + // 只选择 store_id 匹配的 truck + val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } + + if (matchedTrucks.isEmpty()) { + null // 没有匹配的 truck + } else { + matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } + } } - - val selectedTruck = if (trucks.size > 1) { - // Multiple trucks: prefer matching preferredStoreId, then earliest departure - trucks.find { it.storeId == preferredStoreId } - ?: trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } - } else { - trucks.firstOrNull() + + // ✅ 如果没有匹配的 truck,抛出异常跳过 + if (truck == null) { + val errorMsg = "No matching truck found for preferredFloor ($preferredFloor). Skipping DO ${deliveryOrder.id}." + println("⚠️ $errorMsg") + throw IllegalStateException(errorMsg) } - println("🔍 DEBUG: Selected truck: ID=${selectedTruck?.id}, StoreId=${selectedTruck?.storeId}, DepartureTime=${selectedTruck?.departureTime}") - selectedTruck -} + println("✅ DEBUG: Truck matches preferred floor - Truck Store: ${truck.storeId}, Preferred: $preferredFloor") -// ✅ 根据 truck 的 Store_id 字段确定 storeId -val storeId = when (truck?.storeId) { - 4 -> "4/F" - 2 -> "2/F" - else -> "2/F" // 默认值 + // ✅ 根据 truck 的 Store_id 字段确定 storeId + val storeId = when (truck.storeId) { + 4 -> "4/F" + 2 -> "2/F" + else -> "2/F" // 默认值 + } + val loadingSequence = truck.loadingSequence ?: 999 + + // ✅ 每个 pick order 只创建一条 DoPickOrder + val doPickOrder = DoPickOrder( + storeId = storeId, + ticketNo = "TEMP-${System.currentTimeMillis()}", + ticketStatus = DoPickOrderStatus.pending, + truckId = truck.id, + doOrderId = deliveryOrder.id, + pickOrderId = createdPickOrder.id, + truckDepartureTime = truck.departureTime, + shopId = deliveryOrder.shop?.id, + handledBy = null, + pickOrderCode = createdPickOrder.code, + deliveryOrderCode = deliveryOrder.code, + loadingSequence = loadingSequence, + ticketReleaseTime = null, + truckLanceCode = truck.truckLanceCode, + shopCode = deliveryOrder.shop?.code, + shopName = deliveryOrder.shop?.name, + requiredDeliveryDate = targetDate + ) + + println("🔍 DEBUG: Creating DoPickOrder - Store: $storeId, Truck: ${truck.id}") + val savedDoPickOrder = doPickOrderService.save(doPickOrder) + println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") + + return MessageResponse( + id = deliveryOrder.id, + code = deliveryOrder.code, + name = deliveryOrder.shop?.name, + type = null, + message = null, + errorPosition = null, + entity = mapOf("status" to deliveryOrder.status?.value) + ) } -val loadingSequence = truck?.loadingSequence ?: 999 -// ✅ 每个 pick order 只创建一条 DoPickOrder -val doPickOrder = DoPickOrder( - storeId = storeId, - ticketNo = "TEMP-${System.currentTimeMillis()}", - ticketStatus = DoPickOrderStatus.pending, - truckId = truck?.id, - doOrderId = deliveryOrder.id, - pickOrderId = createdPickOrder.id, - truckDepartureTime = truck?.departureTime, - shopId = deliveryOrder.shop?.id, - handledBy = null, - pickOrderCode = createdPickOrder.code, - deliveryOrderCode = deliveryOrder.code, - loadingSequence = loadingSequence, - ticketReleaseTime = null, - // ✅ 填充新增字段 - truckLanceCode = truck?.truckLanceCode, - shopCode = deliveryOrder.shop?.code, - shopName = deliveryOrder.shop?.name, - requiredDeliveryDate = targetDate -) - -println("🔍 DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: , Truck: ${truck?.id}") -val savedDoPickOrder = doPickOrderService.save(doPickOrder) -println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") - -return MessageResponse( - id = deliveryOrder.id, - code = deliveryOrder.code, - name = deliveryOrder.shop?.name, - type = null, - message = null, - errorPosition = null, - entity = mapOf("status" to deliveryOrder.status?.value) -) - -// ... existing code ... - } open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String { try { 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 0181121..4d8844c 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 @@ -214,13 +214,20 @@ class DoPickOrderService( println("🔍 DEBUG: Found ${allRecords.size} records for date $targetDate") val grouped = allRecords.groupBy { it.truckDepartureTime to it.truckLanceCode } - .mapValues { (_, list) -> - LaneBtn( - truckLanceCode = list.first().truckLanceCode ?: "", - unassigned = list.count { it.handledBy == null }, // 未分配的订单数 - total = list.size // 总订单数(包括已分配和未分配) - ) - } + .mapValues { (_, list) -> + // Group by shop_id within this lane + val shopGroups = list.groupBy { it.shopId } + + LaneBtn( + truckLanceCode = list.first().truckLanceCode ?: "", + // Count unique shops where ALL records are unassigned + unassigned = shopGroups.count { (_, shopRecords) -> + shopRecords.all { it.handledBy == null } + }, + // Count total unique shops in this lane + total = shopGroups.size + ) + } val timeGroups = grouped.entries .groupBy { it.key.first } @@ -243,68 +250,87 @@ class DoPickOrderService( } // ✅ 修复:把 assignByLane 移到类里面 - fun assignByLane(request: AssignByLaneRequest): MessageResponse { - val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn( - request.userId, - listOf(DoPickOrderStatus.released, DoPickOrderStatus.pending) + fun assignByLane(request: AssignByLaneRequest): MessageResponse { + // Check if user already has active orders + val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn( + request.userId, + listOf(DoPickOrderStatus.released, DoPickOrderStatus.pending) + ) + + if (existingOrders.isNotEmpty()) { + return MessageResponse( + id = null, code = "USER_BUSY", name = null, type = null, + message = "User already has an active pick order. Please complete it first.", + errorPosition = null, entity = null ) + } - if (existingOrders.isNotEmpty()) { - return MessageResponse( - id = null, code = "USER_BUSY", name = null, type = null, - message = "User already has an active pick order. Please complete it first.", - errorPosition = null, entity = null - ) + // Find all orders for this lane + val allLaneOrders = doPickOrderRepository + .findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc( + request.storeId, + DoPickOrderStatus.pending + ) + .filter { + it.handledBy == null && + it.truckLanceCode == request.truckLanceCode && + (request.truckDepartureTime == null || + it.truckDepartureTime?.toString() == request.truckDepartureTime) } - val candidates = doPickOrderRepository - .findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc( - request.storeId, - DoPickOrderStatus.pending - ) - .filter { - it.handledBy == null && - it.truckLanceCode == request.truckLanceCode && - (request.truckDepartureTime == null || - it.truckDepartureTime?.toString() == request.truckDepartureTime) - } + if (allLaneOrders.isEmpty()) { + return MessageResponse( + id = null, code = "NO_ORDERS", name = null, type = null, + message = "No available orders for lane ${request.truckLanceCode}", + errorPosition = null, entity = null + ) + } - if (candidates.isEmpty()) { - return MessageResponse( - id = null, code = "NO_ORDERS", name = null, type = null, - message = "No available orders for lane ${request.truckLanceCode}", - errorPosition = null, entity = null - ) - } + // ✅ Group by shop and pick the first shop (with earliest loading sequence) + val shopGroups = allLaneOrders.groupBy { it.shopId } + val firstShopOrders = shopGroups.values + .sortedBy { shopOrders -> shopOrders.minOf { it.loadingSequence ?: Int.MAX_VALUE } } + .firstOrNull() - val firstOrder = candidates.first() - val user = userRepository.findById(request.userId).orElse(null) - val handlerName = user?.name ?: "Unknown" + if (firstShopOrders == null || firstShopOrders.isEmpty()) { + return MessageResponse( + id = null, code = "NO_ORDERS", name = null, type = null, + message = "No available orders for lane ${request.truckLanceCode}", + errorPosition = null, entity = null + ) + } - // ✅ 更新 do_pick_order - 保持原有的卡车信息 - firstOrder.handledBy = request.userId - firstOrder.handlerName = handlerName - firstOrder.ticketStatus = DoPickOrderStatus.released - firstOrder.ticketReleaseTime = LocalDateTime.now() - - // ✅ 重要:不要修改 truckDepartureTime 和 truckLanceCode - // 这些信息应该保持用户选择的值 - - doPickOrderRepository.save(firstOrder) - - // ✅ 同步更新 pick_order 表 - if (firstOrder.pickOrderId != null) { - val pickOrder = pickOrderRepository.findById(firstOrder.pickOrderId!!).orElse(null) - if (pickOrder != null) { - val user = userRepository.findById(request.userId).orElse(null) - pickOrder.assignTo = user - pickOrder.status = PickOrderStatus.RELEASED - pickOrderRepository.save(pickOrder) - } + val user = userRepository.findById(request.userId).orElse(null) + val handlerName = user?.name ?: "Unknown" + + // ✅ Get shop info from first order + val firstOrder = firstShopOrders.first() + val shopName = firstOrder.shopName ?: "" + val shopCode = firstOrder.shopCode ?: "" + + // ✅ Assign ALL records for this shop to the user + firstShopOrders.forEach { order -> + order.handledBy = request.userId + order.handlerName = handlerName + order.ticketStatus = DoPickOrderStatus.released + order.ticketReleaseTime = LocalDateTime.now() + } + doPickOrderRepository.saveAll(firstShopOrders) + + // ✅ Update ALL pick_order tables for this shop + val pickOrderIds = firstShopOrders.mapNotNull { it.pickOrderId }.distinct() + pickOrderIds.forEach { pickOrderId -> + val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) + if (pickOrder != null) { + pickOrder.assignTo = user + pickOrder.status = PickOrderStatus.RELEASED + pickOrderRepository.save(pickOrder) } + } - // 同步更新 record - val records = doPickOrderRecordRepository.findByPickOrderId(firstOrder.pickOrderId!!) + // ✅ Update ALL do_pick_order_record entries for this shop + pickOrderIds.forEach { pickOrderId -> + val records = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) records.forEach { it.handledBy = request.userId it.handlerName = handlerName @@ -312,20 +338,24 @@ class DoPickOrderService( it.ticketReleaseTime = LocalDateTime.now() } doPickOrderRecordRepository.saveAll(records) + } - return MessageResponse( - id = firstOrder.pickOrderId, - code = "SUCCESS", - name = null, - type = null, - message = "Assigned pick order from lane ${request.truckLanceCode}", - errorPosition = null, - entity = mapOf( - "pickOrderId" to firstOrder.pickOrderId, - "ticketNo" to firstOrder.ticketNo - ) + return MessageResponse( + id = pickOrderIds.firstOrNull(), + code = "SUCCESS", + name = null, + type = null, + message = "Assigned ${firstShopOrders.size} order(s) for $shopName from lane ${request.truckLanceCode}", + errorPosition = null, + entity = mapOf( + "pickOrderIds" to pickOrderIds, + "ticketNos" to firstShopOrders.map { it.ticketNo }, + "shopCode" to shopCode, + "shopName" to shopName, + "orderCount" to firstShopOrders.size ) - } + ) +} // 在 DoPickOrderService 类中添加这个方法 open fun determineStoreId(doOrderId: Long): String { diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ReleaseDoRequest.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ReleaseDoRequest.kt index 31e498a..5795e7c 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ReleaseDoRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ReleaseDoRequest.kt @@ -1,6 +1,23 @@ package com.ffii.fpsms.modules.deliveryOrder.web.models - +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; data class ReleaseDoRequest( val id: Long, val userId: Long +) +data class ReleaseDoResult( + val deliveryOrderId: Long, + val deliveryOrderCode: String?, + val pickOrderId: Long, + val pickOrderCode: String?, + val shopId: Long?, + val shopCode: String?, + val shopName: String?, + val estimatedArrivalDate: LocalDate, + val preferredFloor: String, + val truckId: Long?, + val truckDepartureTime: LocalTime?, + val truckLanceCode: String?, + val loadingSequence: Int? ) \ 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 60f9d42..393b977 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 @@ -1325,7 +1325,7 @@ open fun getCompletedJobOrderPickOrderLotDetails(pickOrderId: Long): List { - println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical ===") - println("today: ${LocalDate.now()}") + println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (CURRENT STRUCTURE) ===") 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 emptyMap() } - val statusList = listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, - //PickOrderStatus.COMPLETED - ) - - // Get all pick orders assigned to user with PENDING or RELEASED status that have doId - val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( - user, - statusList - ).filter { it.deliveryOrder != null } // Only pick orders with doId - println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId") - - // ✅ 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 { - // ✅ 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() - } - - val pickOrderIds = filteredPickOrders.map { it.id!! } - println("🎯 Pick order IDs to fetch: $pickOrderIds") + // ✅ Step 1: 获取 do_pick_order 基本信息 (直接通过 pickOrderId 关联) + 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, + dpo.pick_order_id, + dpo.shop_id + FROM fpsmsdb.do_pick_order dpo + INNER JOIN fpsmsdb.pick_order po ON po.id = dpo.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() - if (pickOrderIds.isEmpty()) { + 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( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + "fgInfo" to null, + "pickOrders" to emptyList() ) } - // Use the same SQL query but transform the results into hierarchical structure - val pickOrderIdsStr = pickOrderIds.joinToString(",") + val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong() + val shopId = (doPickOrderInfo["shop_id"] as? Number)?.toLong() + println("🔍 Found do_pick_order ID: $doPickOrderId, shop_id: $shopId") - 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, - - -- ✅ 修改:直接使用 warehouse 作为路由信息 - w.`order` as routerIndex, - w.code as routerRoute, - w.code as routerArea, - - -- ✅ FIXED: 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, sol.inventoryLotLineId) as debugSuggestedLotLineId, - ill.inventoryLotId as debugInventoryLotId, + // ✅ Step 2: 获取该用户分配的所有 pick orders (通过 shop_id 关联) + val pickOrdersSql = """ + SELECT DISTINCT + dpo.pick_order_id, + po.code as pick_order_code, + dpo.do_order_id, + dpo.delivery_order_code, + po.consoCode, + po.status, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate, + dpo.ticket_no, + dpo.id as do_pick_order_id + FROM fpsmsdb.do_pick_order dpo + INNER JOIN fpsmsdb.pick_order po ON po.id = dpo.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 + AND dpo.shop_id = :shopId + ORDER BY dpo.pick_order_id + """.trimIndent() + + val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("userId" to userId, "shopId" to shopId)) + println("🔍 Found ${pickOrdersInfo.size} pick orders for user $userId in shop $shopId") + + // ✅ Step 3: 为每个 pick order 获取 lines 和 lots + val pickOrders = pickOrdersInfo.map { poInfo -> + val currentPickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() - -- 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, + // ✅ 查询该 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 + + -- ✅ 使用 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 + + 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() - -- Calculate remaining available quantity correctly - (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, + val linesResults = jdbcDao.queryForList(linesSql, mapOf("pickOrderId" to currentPickOrderId)) + println("🔍 Pick order $currentPickOrderId has ${linesResults.size} line-lot records") - -- Lot availability status - 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, + // ✅ 按 pickOrderLineId 分组 + val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() } - -- 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 + 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() - 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 - - -- Base lot links: all lot lines referenced by either suggestions or stock out lines for this POL - LEFT JOIN ( - SELECT spl.pickOrderLineId AS 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 - - -- Re-bind spl/sol strictly on the same lot line - 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 - -- ✅ 删除 router 相关的 JOIN - -- LEFT JOIN router r ON r.inventoryLotId = ill.inventoryLotId AND r.deleted = false - -- LEFT JOIN router_order ro ON r.router_id = ro.id - LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId - LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId -- ✅ 保留 warehouse JOIN - - WHERE po.deleted = false - AND po.id IN ($pickOrderIdsStr) - AND pol.deleted = false - AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED') - AND po.assignTo = :userId - AND ill.deleted = false - AND il.deleted = false - AND ll.lotLineId IS NOT NULL - AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) - ORDER BY - CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, - COALESCE(w.`order`, 999999) ASC, -- ✅ 使用 warehouse.order 排序 - po.code ASC, - i.code ASC, - il.expiryDate ASC, - il.lotNo ASC -""".trimIndent() - - println("�� Executing SQL for hierarchical structure: $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 - // } - val filteredResults = results - println("✅ Filtered result count: ${filteredResults.size}") - - // ✅ Transform flat results into hierarchical structure - if (filteredResults.isEmpty()) { - return mapOf( - "pickOrder" to null as Any?, - "pickOrderLines" to emptyList>() as Any? + mapOf( + "pickOrderId" to currentPickOrderId, + "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"], + "ticketNo" to poInfo["ticket_no"], + "doPickOrderId" to poInfo["do_pick_order_id"], + "pickOrderLines" to pickOrderLines ) } - // Get pick order info from first row (all rows have same pick order info) - val firstRow = filteredResults.first() - val pickOrderInfo = mapOf( - "id" to firstRow["pickOrderId"], - "code" to firstRow["pickOrderCode"], - "consoCode" to firstRow["pickOrderConsoCode"], - "targetDate" to firstRow["pickOrderTargetDate"], - "type" to firstRow["pickOrderType"], - "status" to firstRow["pickOrderStatus"], - "assignTo" to firstRow["pickOrderAssignTo"] + // ✅ 构建 FG 信息 + val fgInfo = mapOf( + "doPickOrderId" to doPickOrderId, + "ticketNo" to pickOrdersInfo.map { it["ticket_no"] }.joinToString(", "), + "storeId" to doPickOrderInfo["store_id"], + "shopCode" to doPickOrderInfo["ShopCode"], + "shopName" to doPickOrderInfo["ShopName"], + "truckLanceCode" to doPickOrderInfo["TruckLanceCode"], + "departureTime" to doPickOrderInfo["truck_departure_time"] ) - // Group by pick order line ID to create hierarchical structure - val pickOrderLinesMap = filteredResults - .groupBy { it["pickOrderLineId"] as Number } - .map { (pickOrderLineId, lots) -> - val firstLot = lots.first() - - // Item information (same for all lots of this line) - val itemInfo = mapOf( - "id" to firstLot["itemId"], - "code" to firstLot["itemCode"], - "name" to firstLot["itemName"], - "uomCode" to firstLot["uomCode"], - "uomDesc" to firstLot["uomDesc"] - ) - - // Transform lots for this pick order line - val lotsInfo = lots.map { lot -> - mapOf( - "id" to lot["lotId"], - "lotNo" to lot["lotNo"], - "expiryDate" to lot["expiryDate"], - "location" to lot["location"], - "stockUnit" to lot["stockUnit"], - "availableQty" to lot["availableQty"], - "requiredQty" to lot["requiredQty"], - "actualPickQty" to lot["actualPickQty"], - "inQty" to lot["inQty"], - "outQty" to lot["outQty"], - "holdQty" to lot["holdQty"], - "lotStatus" to lot["lotStatus"], - "lotAvailability" to lot["lotAvailability"], - "processingStatus" to lot["processingStatus"], - "suggestedPickLotId" to lot["suggestedPickLotId"], - "stockOutLineId" to lot["stockOutLineId"], - "stockOutLineStatus" to lot["stockOutLineStatus"], - "stockOutLineQty" to lot["stockOutLineQty"], - "router" to mapOf( - "id" to lot["routerId"], - "index" to lot["routerIndex"], - "route" to lot["routerRoute"], - "area" to lot["routerArea"], - "itemCode" to firstLot["itemId"], - "itemName" to firstLot["itemName"], - "uomId" to firstLot["uomShortDesc"], - "noofCarton" to lot["requiredQty"] // Use required qty as carton count - ) - ) - } - - // Pick order line with item and lots - mapOf( - "id" to pickOrderLineId, - "requiredQty" to firstLot["pickOrderLineRequiredQty"], - "status" to firstLot["pickOrderLineStatus"], - "item" to itemInfo, - "lots" to lotsInfo - ) - } - return mapOf( - "pickOrder" to pickOrderInfo as Any?, - "pickOrderLines" to pickOrderLinesMap as Any? + "fgInfo" to fgInfo, + "pickOrders" to pickOrders ) } - // Fix the type issues in the getPickOrdersByDateAndStore method open fun getPickOrdersByDateAndStore(storeId: String): Map { println("=== Debug: getPickOrdersByDateAndStore ===")