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 3562303..379f688 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 @@ -494,12 +494,12 @@ open class DeliveryOrderService( @Transactional(rollbackFor = [Exception::class]) open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse { - println("�� DEBUG: Starting releaseDeliveryOrder for DO ID: ${request.id}, User ID: ${request.userId}") + 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}") + println(" DEBUG: Found delivery order - ID: ${deliveryOrder.id}, Shop: ${deliveryOrder.shop?.code}, Status: ${deliveryOrder.status}") deliveryOrder.apply { status = DeliveryOrderStatus.PENDING @@ -530,10 +530,10 @@ open class DeliveryOrderService( 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: 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") + 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}") } @@ -605,125 +605,98 @@ open class DeliveryOrderService( } // 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")) println(" DEBUG: Target date: $targetDate, Date prefix: $datePrefix") + + // 新逻辑:根据 supplier code 决定楼层 + // 如果 supplier code 是 "P06B",使用 4F,否则使用 2F + val supplierCode = deliveryOrder.supplier?.code + val preferredFloor = if (supplierCode == "P06B") { + "4F" + } else { + "2F" + } + + println(" DEBUG: Supplier code: $supplierCode, Preferred floor: $preferredFloor") + + // 查找 truck 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") - // 移除提前返回,总是分析 items 分布 - // 分析 DO order lines 中的 items 分布 - // 分析 items 来确定 storeId - val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() - val inventoryQuery = """ - 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 floorItemCount = mutableMapOf() - inventoryResults.forEach { row -> - floorItemCount[row["floor"] as? String ?: "Other"] = (row["item_count"] as? Number)?.toInt() ?: 0 + val preferredStoreId = when (preferredFloor) { + "2F" -> "2F" + "4F" -> "4F" + "3F" -> "3F" + else -> "2F" } - - println(" DEBUG: Floor item count distribution: $floorItemCount") - println(" DEBUG: Total items: ${itemIds.size}, Items on 4F: ${floorItemCount["4F"] ?: 0}") - -// 新逻辑:只有所有 items 都在 4F,才算 4F,否则算 2F - val preferredFloor = if ((floorItemCount["4F"] ?: 0) == itemIds.size && (floorItemCount["2F"] ?: 0) == 0) { - "4F" // 所有 items 都在 4F + // 优先选择匹配的 truck + val selectedTruck = if (trucks.size > 1) { + trucks.find { it.storeId == preferredStoreId } + ?: trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } } else { - "2F" // 只要有任何 item 不在 4F,就算 2F + trucks.firstOrNull() } - println(" DEBUG: Preferred floor: $preferredFloor (All items on 4F: ${preferredFloor == "4F"})") + println(" DEBUG: Selected truck: ID=${selectedTruck?.id}, StoreId=${selectedTruck?.storeId}") + selectedTruck + } -// 查找 truck - 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") + // 检查 truck 和 preferredFloor 是否匹配 + val truckStoreId = truck?.storeId + val expectedStoreId = when (preferredFloor) { + "2F" -> "2F" + "4F" -> "4F" + "3F" -> "3F" + else -> "2F" + } + if (truck == null || truckStoreId != expectedStoreId) { + val errorMsg = + "Items preferredFloor ($preferredFloor) does not match available truck (Truck Store: $truckStoreId). Skipping DO ${deliveryOrder.id}." + println("⚠️ $errorMsg") + throw IllegalStateException(errorMsg) + } - val preferredStoreId = when (preferredFloor) { - "2F" -> "2F" - "4F" -> "4F" - "3F" -> "3F" - else -> "2F" - } - // 优先选择匹配的 truck - val selectedTruck = if (trucks.size > 1) { - trucks.find { it.storeId == preferredStoreId } - ?: trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } - } else { - trucks.firstOrNull() - } + println(" DEBUG: Truck matches preferred floor - Truck Store: $truckStoreId, Preferred: $preferredFloor") - println(" DEBUG: Selected truck: ID=${selectedTruck?.id}, StoreId=${selectedTruck?.storeId}") - selectedTruck - } + // storeId 基于 preferredFloor + val storeId = "$preferredFloor/F" + val loadingSequence = truck.loadingSequence ?: 999 -// 检查 truck 和 preferredFloor 是否匹配 - val truckStoreId = truck?.storeId - val expectedStoreId = when (preferredFloor) { - "2F" -> "2F" - "4F" -> "4F" - "3F" -> "3F" - else -> "2F" - } + println(" DEBUG: Creating DoPickOrder - Floor: $preferredFloor, Store: $storeId, Truck: ${truck.id}") - if (truck == null || truckStoreId != expectedStoreId) { - val errorMsg = - "Items preferredFloor ($preferredFloor) does not match available truck (Truck Store: $truckStoreId). Skipping DO ${deliveryOrder.id}." - println("⚠️ $errorMsg") - throw IllegalStateException(errorMsg) // 或返回错误响应 - } + val doPickOrder = DoPickOrder( + storeId = storeId, + ticketNo = "TEMP-${System.currentTimeMillis()}", + ticketStatus = DoPickOrderStatus.pending, + truckId = truck.id, + pickOrderId = createdPickOrder.id, + doOrderId = deliveryOrder.id, + ticketReleaseTime = null, + shopId = deliveryOrder.shop?.id, + handlerName = null, + handledBy = null, + ticketCompleteDateTime = null, + truckDepartureTime = truck.departureTime, + truckLanceCode = truck.truckLanceCode, + shopCode = deliveryOrder.shop?.code, + shopName = deliveryOrder.shop?.name, + requiredDeliveryDate = targetDate, + createdBy = null, + modifiedBy = null, + pickOrderCode = createdPickOrder.code, + deliveryOrderCode = deliveryOrder.code, + loadingSequence = loadingSequence, + releaseType = null + ) - println(" DEBUG: Truck matches preferred floor - Truck Store: $truckStoreId, Preferred: $preferredFloor") - - // storeId 基于 items 的 preferredFloor - val storeId = "$preferredFloor/F" - val loadingSequence = truck.loadingSequence ?: 999 - - println(" DEBUG: Creating DoPickOrder - Floor: $preferredFloor, Store: $storeId, Truck: ${truck.id}") - - 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 - ) + val savedDoPickOrder = doPickOrderService.save(doPickOrder) + println(" DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") - val savedDoPickOrder = doPickOrderService.save(doPickOrder) - println(" DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") - truck - } return MessageResponse( id = deliveryOrder.id, code = deliveryOrder.code, @@ -1273,63 +1246,20 @@ open class DeliveryOrderService( } } - // 分析楼层分布 + // 新逻辑:根据 supplier code 决定楼层 + // 如果 supplier code 是 "P06B",使用 4F,否则使用 2F val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() - val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() - - val itemsStoreIdQuery = """ - SELECT - i.store_id, - COUNT(DISTINCT i.id) as item_count - FROM items i - WHERE i.id IN (${itemIds.joinToString(",")}) - AND i.deleted = 0 - GROUP BY i.store_id -""".trimIndent() - - val itemsStoreIdResults = jdbcDao.queryForList(itemsStoreIdQuery) - val storeIdItemCount = mutableMapOf() - var totalItemsWithStoreId = 0 // 统计有 store_id 的商品总数 - - itemsStoreIdResults.forEach { row -> - val rawStoreId = row["store_id"] as? String - if (rawStoreId != null) { // 只统计非 NULL 的 store_id - val normalizedStoreId = when (rawStoreId) { - "3F" -> "4F" - else -> rawStoreId - } - val count = (row["item_count"] as? Number)?.toInt() ?: 0 - storeIdItemCount[normalizedStoreId] = - (storeIdItemCount[normalizedStoreId] ?: 0) + count - totalItemsWithStoreId += count - } - } - - val count2F = storeIdItemCount["2F"] ?: 0 - val count4F = storeIdItemCount["4F"] ?: 0 - - - val preferredFloor = when { - totalItemsWithStoreId == 0 -> { - println("⚠️ WARNING: All items have NULL store_id, defaulting to 2F") - "2F" - } - - count4F > count2F -> "4F" - count2F > count4F -> "2F" - count4F == totalItemsWithStoreId && count2F == 0 -> "4F" - else -> "2F" // 默认 2F + val supplierCode = deliveryOrder.supplier?.code + val preferredFloor = if (supplierCode == "P06B") { + "4F" + } else { + "2F" } println(" DEBUG: Floor calculation for DO ${deliveryOrder.id}") - println(" - Total items: ${itemIds.size}") - println(" - Items with store_id: $totalItemsWithStoreId") - println(" - Items without store_id: ${itemIds.size - totalItemsWithStoreId}") - println(" - 2F items: $count2F") - println(" - 4F items: $count4F") + println(" - Supplier code: $supplierCode") println(" - Preferred floor: $preferredFloor") - val truck = deliveryOrder.shop?.id?.let { shopId -> val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) val preferredStoreId = when (preferredFloor) { @@ -1339,19 +1269,16 @@ open class DeliveryOrderService( else -> "2F" } - val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } if (matchedTrucks.isEmpty()) { null } else { - if (preferredStoreId == "4F" && matchedTrucks.size > 1) { deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> val targetDate = estimatedArrivalDate.toLocalDate() val dayOfWeek = targetDate.dayOfWeek - val dayAbbr = when (dayOfWeek) { java.time.DayOfWeek.MONDAY -> "Mon" java.time.DayOfWeek.TUESDAY -> "Tue" @@ -1365,7 +1292,6 @@ open class DeliveryOrderService( println(" DEBUG: DO ${deliveryOrder.id} - Target date: $targetDate ($dayAbbr), Shop: $shopId") println(" DEBUG: Found ${matchedTrucks.size} matched 4F trucks") - val dayMatchedTrucks = matchedTrucks.filter { it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true } @@ -1376,21 +1302,17 @@ open class DeliveryOrderService( } if (dayMatchedTrucks.isNotEmpty()) { - val selected = dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } println("✅ DEBUG: Selected truck matching $dayAbbr - ID=${selected?.id}, Code=${selected?.truckLanceCode}") selected } else { - println("⚠️ WARNING: No truck matching $dayAbbr, using first available truck") matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } } } ?: run { - matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } } } else { - matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } } } @@ -1419,7 +1341,7 @@ open class DeliveryOrderService( truckId = truck.id, truckDepartureTime = truck.departureTime, truckLanceCode = truck.truckLanceCode, - loadingSequence = truck.loadingSequence // 直接使用 truck 的值 + loadingSequence = truck.loadingSequence ) } 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 4c6ce12..ff2acc4 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 @@ -77,48 +77,20 @@ class DoReleaseCoordinatorService( val updateSql = """ UPDATE fpsmsdb.do_pick_order dpo INNER JOIN ( - WITH DoStoreIdCounts AS ( - SELECT - dol.deliveryOrderId, - CASE - WHEN i.store_id = '3F' THEN '4F' - ELSE i.store_id - END AS store_id, - COUNT(DISTINCT dol.itemId) AS item_count - FROM fpsmsdb.delivery_order_line dol - INNER JOIN fpsmsdb.items i ON i.id = dol.itemId - AND i.deleted = 0 - WHERE dol.deleted = 0 - GROUP BY dol.deliveryOrderId, - CASE - WHEN i.store_id = '3F' THEN '4F' - ELSE i.store_id - END - ), - DoStoreIdSummary AS ( + WITH PreferredFloor AS ( SELECT do.id AS deliveryOrderId, - COALESCE(SUM(CASE WHEN dsc.store_id = '2F' THEN dsc.item_count ELSE 0 END), 0) AS count_2f, - COALESCE(SUM(CASE WHEN dsc.store_id = '4F' THEN dsc.item_count ELSE 0 END), 0) AS count_4f - FROM fpsmsdb.delivery_order do - LEFT JOIN DoStoreIdCounts dsc ON dsc.deliveryOrderId = do.id - WHERE do.deleted = 0 - GROUP BY do.id - ), - PreferredFloor AS ( - SELECT - deliveryOrderId, CASE - WHEN count_2f > count_4f THEN '2F' - WHEN count_4f > count_2f THEN '4F' + WHEN s.code = 'P06B' THEN '4F' ELSE '2F' END AS preferred_floor, CASE - WHEN count_2f > count_4f THEN 2 - WHEN count_4f > count_2f THEN 4 + WHEN s.code = 'P06B' THEN 4 ELSE 2 END AS preferred_store_id - FROM DoStoreIdSummary + FROM fpsmsdb.delivery_order do + LEFT JOIN fpsmsdb.shop s ON s.id = do.supplierId AND s.deleted = 0 + WHERE do.deleted = 0 ), TruckSelection AS ( SELECT @@ -261,68 +233,27 @@ class DoReleaseCoordinatorService( e.printStackTrace() } } - private fun getOrderedDeliveryOrderIds(ids: List): List { try { println(" DEBUG: Getting ordered IDs for ${ids.size} orders") println(" DEBUG: First 5 IDs: ${ids.take(5)}") val dayOfWeekSql = getDayOfWeekAbbrSql("do.estimatedArrivalDate") val sql = """ - WITH DoFloorCounts AS ( - SELECT - dol.deliveryOrderId, - w.store_id, - COUNT(DISTINCT dol.itemId) AS item_count - FROM fpsmsdb.delivery_order_line dol - INNER JOIN fpsmsdb.inventory i ON i.itemId = dol.itemId - AND i.deleted = 0 - AND i.onHandQty > 0 - INNER JOIN fpsmsdb.inventory_lot il ON il.itemId = i.itemId - AND il.deleted = 0 - INNER JOIN fpsmsdb.inventory_lot_line ill ON ill.inventoryLotId = il.id - AND ill.deleted = 0 - INNER JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId - AND w.deleted = 0 - AND w.store_id IN ('2F', '4F') - WHERE dol.deleted = 0 - AND dol.deliveryOrderId IN (${ids.joinToString(",")}) - AND dol.deliveryOrderId NOT IN ( - SELECT DISTINCT dpol.do_order_id - FROM fpsmsdb.do_pick_order_line dpol - INNER JOIN fpsmsdb.do_pick_order dpo ON dpo.id = dpol.do_pick_order_id - WHERE dpo.release_type = 'single' - AND dpo.deleted = 0 - AND dpol.deleted = 0 - ) - GROUP BY dol.deliveryOrderId, w.store_id - ), - DoFloorSummary AS ( + WITH PreferredFloor AS ( SELECT do.id AS deliveryOrderId, - COALESCE(SUM(CASE WHEN dfc.store_id = '2F' THEN dfc.item_count ELSE 0 END), 0) AS count_2f, - COALESCE(SUM(CASE WHEN dfc.store_id = '4F' THEN dfc.item_count ELSE 0 END), 0) AS count_4f - FROM fpsmsdb.delivery_order do - LEFT JOIN DoFloorCounts dfc ON dfc.deliveryOrderId = do.id - WHERE do.id IN (${ids.joinToString(",")}) - AND do.deleted = 0 - GROUP BY do.id - ), - PreferredFloor AS ( - SELECT - deliveryOrderId, - count_2f, - count_4f, CASE - WHEN count_2f > count_4f THEN '2F' - WHEN count_4f > count_2f THEN '4F' + WHEN s.code = 'P06B' THEN '4F' ELSE '2F' END AS preferred_floor, CASE - WHEN count_2f > count_4f THEN 2 - WHEN count_4f > count_2f THEN 4 + WHEN s.code = 'P06B' THEN 4 ELSE 2 END AS preferred_store_id - FROM DoFloorSummary + FROM fpsmsdb.delivery_order do + LEFT JOIN fpsmsdb.shop s ON s.id = do.supplierId AND s.deleted = 0 + WHERE do.id IN (${ids.joinToString(",")}) + AND do.deleted = 0 ), TruckSelection AS ( SELECT @@ -624,37 +555,45 @@ class DoReleaseCoordinatorService( ) } - private fun createMergedDoPickOrder(results: List) { - val first = results.first() - - val storeId = when (first.preferredFloor) { - "2F" -> "2/F" - "4F" -> "4/F" - else -> "2/F" - } - - val doPickOrder = DoPickOrder( - storeId = storeId, - ticketNo = "TEMP-${System.currentTimeMillis()}", - ticketStatus = DoPickOrderStatus.pending, - truckId = first.truckId, - truckDepartureTime = first.truckDepartureTime, - shopId = first.shopId, - handledBy = null, - loadingSequence = first.loadingSequence ?: 999, - ticketReleaseTime = null, - truckLanceCode = first.truckLanceCode, - shopCode = first.shopCode, - shopName = first.shopName, - requiredDeliveryDate = first.estimatedArrivalDate, - releaseType = "batch" + // 替换第 627-653 行 + private fun createMergedDoPickOrder(results: List) { + val first = results.first() + + val storeId = when (first.preferredFloor) { + "2F" -> "2/F" + "4F" -> "4/F" + else -> "2/F" + } - ) - - // 直接使用 doPickOrderRepository.save() 而不是 doPickOrderService.save() - val saved = doPickOrderRepository.save(doPickOrder) - println(" DEBUG: Saved DoPickOrder - ID: ${saved.id}, Ticket: ${saved.ticketNo}") - + val doPickOrder = DoPickOrder( + storeId = storeId, + ticketNo = "TEMP-${System.currentTimeMillis()}", + ticketStatus = DoPickOrderStatus.pending, + truckId = first.truckId, + pickOrderId = null, + doOrderId = null, + ticketReleaseTime = null, + shopId = first.shopId, + handlerName = null, + handledBy = null, + ticketCompleteDateTime = null, + truckDepartureTime = first.truckDepartureTime, + truckLanceCode = first.truckLanceCode, + shopCode = first.shopCode, + shopName = first.shopName, + requiredDeliveryDate = first.estimatedArrivalDate, + createdBy = null, + modifiedBy = null, + pickOrderCode = null, + deliveryOrderCode = null, + loadingSequence = first.loadingSequence, + releaseType = "batch" + ) + + // 直接使用 doPickOrderRepository.save() 而不是 doPickOrderService.save() + val saved = doPickOrderRepository.save(doPickOrder) + println(" DEBUG: Saved DoPickOrder - ID: ${saved.id}, Ticket: ${saved.ticketNo}") + // 创建多条 DoPickOrderLine(每个 DO 一条) results.forEach { result -> @@ -813,25 +752,34 @@ class DoReleaseCoordinatorService( doPickOrderLineRepository.save(line) println(" DEBUG: Created DoPickOrderLine for existing DoPickOrder ${existingDoPickOrder.id}") } - } else { - // 如果不存在,创建新的 do_pick_order - val doPickOrder = DoPickOrder( - storeId = storeId, - ticketNo = "TEMP-${System.currentTimeMillis()}", - ticketStatus = DoPickOrderStatus.pending, - truckId = result.truckId, - truckDepartureTime = result.truckDepartureTime, - shopId = result.shopId, - handledBy = null, - loadingSequence = result.loadingSequence ?: 999, - ticketReleaseTime = null, - truckLanceCode = result.truckLanceCode, - shopCode = result.shopCode, - shopName = result.shopName, - requiredDeliveryDate = result.estimatedArrivalDate, - releaseType = "single" // 设置为 single - ) - val saved = doPickOrderRepository.save(doPickOrder) + // 替换第 748-764 行 + } else { + // 如果不存在,创建新的 do_pick_order + val doPickOrder = DoPickOrder( + storeId = storeId, + ticketNo = "TEMP-${System.currentTimeMillis()}", + ticketStatus = DoPickOrderStatus.pending, + truckId = result.truckId, + pickOrderId = null, + doOrderId = null, + ticketReleaseTime = null, + shopId = result.shopId, + handlerName = null, + handledBy = null, + ticketCompleteDateTime = null, + truckDepartureTime = result.truckDepartureTime, + truckLanceCode = result.truckLanceCode, + shopCode = result.shopCode, + shopName = result.shopName, + requiredDeliveryDate = result.estimatedArrivalDate, + createdBy = null, + modifiedBy = null, + pickOrderCode = null, + deliveryOrderCode = null, + loadingSequence = result.loadingSequence, + releaseType = "single" + ) + val saved = doPickOrderRepository.save(doPickOrder) // 创建 do_pick_order_line val line = DoPickOrderLine().apply { diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt index e5f02de..f33649b 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt @@ -99,8 +99,8 @@ open class SuggestedPickLotService( val suggestedList: MutableList = mutableListOf() val holdQtyMap: MutableMap = request.holdQtyMap - - // get current inventory lot line qty & grouped by item id + val excludedWarehouseCodes = listOf("2F-W202-01-00") + val availableInventoryLotLines = inventoryLotLineService .allInventoryLotLinesByItemIdIn(itemIds) .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } @@ -111,6 +111,7 @@ open class SuggestedPickLotService( // loop for suggest pick lot line pols.forEach { line -> + val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() val ratio = one // (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) @@ -256,126 +257,97 @@ open class SuggestedPickLotService( // loop for suggest pick lot line pols.forEach { line -> - val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } + val ratio = one + + // === 新增:如果是 DO pick order,则按 DO 的 preferredFloor 过滤 lot === + // preferredFloor 规则:supplier.code == "P06B" -> 4F,否则 2F + val pickOrder = line.pickOrder + val isDoPickOrder = pickOrder?.type?.value == "do" || pickOrder?.type?.value == "delivery_order" + + val doPreferredFloor: String? = if (isDoPickOrder) { + val supplierCode = pickOrder?.deliveryOrder?.supplier?.code + if (supplierCode == "P06B") "4F" else "2F" + } else { + null + } + val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() - val ratio = one // (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) - - // FIX: Calculate remaining quantity needed (not the full required quantity) + + // 计算 remaining qty val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(line.id!!) val totalPickedQty = stockOutLines - .filter { - it.status == "completed" || - it.status == "partially_completed" || - (it.status == "rejected" && (it.qty ?: zero) > zero) // 包含已 picked 的 rejected - } - .sumOf { it.qty ?: zero } + .filter { + it.status == "completed" || + it.status == "partially_completed" || + (it.status == "rejected" && (it.qty ?: zero) > zero) + } + .sumOf { it.qty ?: zero } + val requiredQty = line.qty ?: zero val remainingQty = requiredQty.minus(totalPickedQty) - - println("=== SUGGESTION DEBUG for Pick Order Line ${line.id} ===") - println("Required qty: $requiredQty") - println("Total picked qty: $totalPickedQty") - println("Remaining qty needed: $remainingQty") - println("Stock out lines: ${stockOutLines.map { "${it.id}(status=${it.status}, qty=${it.qty})" }}") - - // FIX: Use remainingQty instead of line.qty + var remainingQtyToAllocate = remainingQty - println("remaining1 $remainingQtyToAllocate (sales units)") - val updatedLotLines = mutableListOf() - lotLines.forEachIndexed { index, lotLine -> + lotLines.forEachIndexed { _, lotLine -> if (remainingQtyToAllocate <= zero) return@forEachIndexed - println("calculateRemainingQtyForInfo(lotLine) ${calculateRemainingQtyForInfo(lotLine)}") + // 拿 entity 是因为 projection 的 warehouse 没有 store_id 字段 val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } - - // 修复:计算可用数量,转换为销售单位 + ?: return@forEachIndexed + + val warehouseStoreId = inventoryLotLine.warehouse?.store_id + val warehouseCode = inventoryLotLine.warehouse?.code + + // 规则 1:排除指定 warehouse code + if (warehouseCode != null && excludedWarehouseCodes.contains(warehouseCode)) { + return@forEachIndexed + } + + // 规则 2:原有逻辑:跳过 3F + if (warehouseStoreId == "3F") { + return@forEachIndexed + } + + // 规则 3:新增逻辑:DO 订单必须匹配楼层,否则不建议 + // 例:doPreferredFloor=2F,但 lot 在 4F => 跳过 + if (doPreferredFloor != null && warehouseStoreId != doPreferredFloor) { + return@forEachIndexed + } + val availableQtyInBaseUnits = calculateRemainingQtyForInfo(lotLine) val holdQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero val availableQtyInSalesUnits = availableQtyInBaseUnits .minus(holdQtyInBaseUnits) .divide(ratio, 2, RoundingMode.HALF_UP) - - println("holdQtyMap[lotLine.id] ?: zero ${holdQtyMap[lotLine.id] ?: zero}") - if (availableQtyInSalesUnits <= zero) { - updatedLotLines += lotLine - return@forEachIndexed - } - println("$index : ${lotLine.id}") - // val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } - val originalHoldQty = inventoryLotLine?.holdQty + if (availableQtyInSalesUnits <= zero) return@forEachIndexed - // 修复:在销售单位中计算分配数量 val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQtyToAllocate) remainingQtyToAllocate = remainingQtyToAllocate.minus(assignQtyInSalesUnits) - val newHoldQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero - // 修复:将销售单位转换为基础单位来更新 holdQty + val assignQtyInBaseUnits = assignQtyInSalesUnits.multiply(ratio) holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQtyInBaseUnits) - + suggestedList += SuggestedPickLot().apply { type = SuggestedPickLotType.PICK_ORDER suggestedLotLine = inventoryLotLine pickOrderLine = line - qty = assignQtyInSalesUnits // 保存销售单位 + qty = assignQtyInSalesUnits } } - // 修复:计算现有 suggestions 中 pending/checked 状态满足的数量 - var existingSatisfiedQty = BigDecimal.ZERO - - // 查询现有的 suggestions 用于这个 pick order line - val existingSuggestions = suggestedPickLotRepository.findAllByPickOrderLineId(line.id!!) - existingSuggestions.forEach { existingSugg -> - if (existingSugg.suggestedLotLine?.id != null) { - val stockOutLines = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( - line.id!!, existingSugg.suggestedLotLine?.id!! - ) - val canCountAsSatisfied = stockOutLines.isEmpty() || stockOutLines.any { - it.status == "pending" || it.status == "checked" || it.status == "partially_completed" - } - - if (canCountAsSatisfied) { - existingSatisfiedQty = existingSatisfiedQty.plus(existingSugg.qty ?: BigDecimal.ZERO) - } - } - } - - // 调整 remainingQtyToAllocate,减去已经通过现有 suggestions 满足的数量 - remainingQtyToAllocate = remainingQtyToAllocate.minus(existingSatisfiedQty) - println("Existing satisfied qty: $existingSatisfiedQty") - println("Adjusted remaining qty: $remainingQtyToAllocate") - - // if still have remainingQty - println("remaining2 $remainingQtyToAllocate (sales units)") - - // if still have remainingQty - println("remaining2 $remainingQtyToAllocate (sales units)") + + // 如果仍有剩余 -> 给 null lot(你举例的场景会走到这里) if (remainingQtyToAllocate > zero) { suggestedList += SuggestedPickLot().apply { type = SuggestedPickLotType.PICK_ORDER suggestedLotLine = null pickOrderLine = line - qty = remainingQtyToAllocate // 保存销售单位 - } - try { - /* - val pickOrder = line.pickOrder - if (pickOrder != null) { - createInsufficientStockIssue( - pickOrder = pickOrder, - pickOrderLine = line, - insufficientQty = remainingQtyToAllocate - ) - } - */ - } catch (e: Exception) { - println("❌ Error creating insufficient stock issue: ${e.message}") - e.printStackTrace() + qty = remainingQtyToAllocate } } } - return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) + + return SuggestedPickLotResponse(holdQtyMap = holdQtyMap, suggestedList = suggestedList) } // Convertion