| @@ -494,12 +494,12 @@ open class DeliveryOrderService( | |||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse { | 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) | val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id) | ||||
| ?: throw NoSuchElementException("Delivery Order not found") | ?: 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 { | deliveryOrder.apply { | ||||
| status = DeliveryOrderStatus.PENDING | status = DeliveryOrderStatus.PENDING | ||||
| @@ -530,10 +530,10 @@ open class DeliveryOrderService( | |||||
| pickOrderEntity.consoCode = consoCode | pickOrderEntity.consoCode = consoCode | ||||
| pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED | pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED | ||||
| pickOrderRepository.saveAndFlush(pickOrderEntity) | 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 | // 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 -> | pickOrderEntity.pickOrderLines?.forEach { line -> | ||||
| println(" DEBUG: Pick order line - Item ID: ${line.item?.id}, Qty: ${line.qty}") | 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 | // CREATE do_pick_order_record entries | ||||
| // 第 471-555 行附近 - 修复创建逻辑 | |||||
| // CREATE do_pick_order_record entries | |||||
| val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() | val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() | ||||
| val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) | val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) | ||||
| println(" DEBUG: Target date: $targetDate, Date prefix: $datePrefix") | 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 -> | val truck = deliveryOrder.shop?.id?.let { shopId -> | ||||
| println(" DEBUG: Looking for truck with shop ID: $shopId") | println(" DEBUG: Looking for truck with shop ID: $shopId") | ||||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | ||||
| println(" DEBUG: Found ${trucks.size} trucks for shop $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<String, Int>() | |||||
| 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 { | } 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( | return MessageResponse( | ||||
| id = deliveryOrder.id, | id = deliveryOrder.id, | ||||
| code = deliveryOrder.code, | 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 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<String, Int>() | |||||
| 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(" 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") | println(" - Preferred floor: $preferredFloor") | ||||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | val truck = deliveryOrder.shop?.id?.let { shopId -> | ||||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | ||||
| val preferredStoreId = when (preferredFloor) { | val preferredStoreId = when (preferredFloor) { | ||||
| @@ -1339,19 +1269,16 @@ open class DeliveryOrderService( | |||||
| else -> "2F" | else -> "2F" | ||||
| } | } | ||||
| val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } | val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } | ||||
| if (matchedTrucks.isEmpty()) { | if (matchedTrucks.isEmpty()) { | ||||
| null | null | ||||
| } else { | } else { | ||||
| if (preferredStoreId == "4F" && matchedTrucks.size > 1) { | if (preferredStoreId == "4F" && matchedTrucks.size > 1) { | ||||
| deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> | deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> | ||||
| val targetDate = estimatedArrivalDate.toLocalDate() | val targetDate = estimatedArrivalDate.toLocalDate() | ||||
| val dayOfWeek = targetDate.dayOfWeek | val dayOfWeek = targetDate.dayOfWeek | ||||
| val dayAbbr = when (dayOfWeek) { | val dayAbbr = when (dayOfWeek) { | ||||
| java.time.DayOfWeek.MONDAY -> "Mon" | java.time.DayOfWeek.MONDAY -> "Mon" | ||||
| java.time.DayOfWeek.TUESDAY -> "Tue" | 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: DO ${deliveryOrder.id} - Target date: $targetDate ($dayAbbr), Shop: $shopId") | ||||
| println(" DEBUG: Found ${matchedTrucks.size} matched 4F trucks") | println(" DEBUG: Found ${matchedTrucks.size} matched 4F trucks") | ||||
| val dayMatchedTrucks = matchedTrucks.filter { | val dayMatchedTrucks = matchedTrucks.filter { | ||||
| it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true | it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true | ||||
| } | } | ||||
| @@ -1376,21 +1302,17 @@ open class DeliveryOrderService( | |||||
| } | } | ||||
| if (dayMatchedTrucks.isNotEmpty()) { | if (dayMatchedTrucks.isNotEmpty()) { | ||||
| val selected = dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | val selected = dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | ||||
| println("✅ DEBUG: Selected truck matching $dayAbbr - ID=${selected?.id}, Code=${selected?.truckLanceCode}") | println("✅ DEBUG: Selected truck matching $dayAbbr - ID=${selected?.id}, Code=${selected?.truckLanceCode}") | ||||
| selected | selected | ||||
| } else { | } else { | ||||
| println("⚠️ WARNING: No truck matching $dayAbbr, using first available truck") | println("⚠️ WARNING: No truck matching $dayAbbr, using first available truck") | ||||
| matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | ||||
| } | } | ||||
| } ?: run { | } ?: run { | ||||
| matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | ||||
| } | } | ||||
| } else { | } else { | ||||
| matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1419,7 +1341,7 @@ open class DeliveryOrderService( | |||||
| truckId = truck.id, | truckId = truck.id, | ||||
| truckDepartureTime = truck.departureTime, | truckDepartureTime = truck.departureTime, | ||||
| truckLanceCode = truck.truckLanceCode, | truckLanceCode = truck.truckLanceCode, | ||||
| loadingSequence = truck.loadingSequence // 直接使用 truck 的值 | |||||
| loadingSequence = truck.loadingSequence | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -77,48 +77,20 @@ class DoReleaseCoordinatorService( | |||||
| val updateSql = """ | val updateSql = """ | ||||
| UPDATE fpsmsdb.do_pick_order dpo | UPDATE fpsmsdb.do_pick_order dpo | ||||
| INNER JOIN ( | 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 | SELECT | ||||
| do.id AS deliveryOrderId, | 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 | CASE | ||||
| WHEN count_2f > count_4f THEN '2F' | |||||
| WHEN count_4f > count_2f THEN '4F' | |||||
| WHEN s.code = 'P06B' THEN '4F' | |||||
| ELSE '2F' | ELSE '2F' | ||||
| END AS preferred_floor, | END AS preferred_floor, | ||||
| CASE | CASE | ||||
| WHEN count_2f > count_4f THEN 2 | |||||
| WHEN count_4f > count_2f THEN 4 | |||||
| WHEN s.code = 'P06B' THEN 4 | |||||
| ELSE 2 | ELSE 2 | ||||
| END AS preferred_store_id | 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 ( | TruckSelection AS ( | ||||
| SELECT | SELECT | ||||
| @@ -261,68 +233,27 @@ class DoReleaseCoordinatorService( | |||||
| e.printStackTrace() | e.printStackTrace() | ||||
| } | } | ||||
| } | } | ||||
| private fun getOrderedDeliveryOrderIds(ids: List<Long>): List<Long> { | private fun getOrderedDeliveryOrderIds(ids: List<Long>): List<Long> { | ||||
| try { | try { | ||||
| println(" DEBUG: Getting ordered IDs for ${ids.size} orders") | println(" DEBUG: Getting ordered IDs for ${ids.size} orders") | ||||
| println(" DEBUG: First 5 IDs: ${ids.take(5)}") | println(" DEBUG: First 5 IDs: ${ids.take(5)}") | ||||
| val dayOfWeekSql = getDayOfWeekAbbrSql("do.estimatedArrivalDate") | val dayOfWeekSql = getDayOfWeekAbbrSql("do.estimatedArrivalDate") | ||||
| val sql = """ | 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 | SELECT | ||||
| do.id AS deliveryOrderId, | 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 | CASE | ||||
| WHEN count_2f > count_4f THEN '2F' | |||||
| WHEN count_4f > count_2f THEN '4F' | |||||
| WHEN s.code = 'P06B' THEN '4F' | |||||
| ELSE '2F' | ELSE '2F' | ||||
| END AS preferred_floor, | END AS preferred_floor, | ||||
| CASE | CASE | ||||
| WHEN count_2f > count_4f THEN 2 | |||||
| WHEN count_4f > count_2f THEN 4 | |||||
| WHEN s.code = 'P06B' THEN 4 | |||||
| ELSE 2 | ELSE 2 | ||||
| END AS preferred_store_id | 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 ( | TruckSelection AS ( | ||||
| SELECT | SELECT | ||||
| @@ -624,37 +555,45 @@ class DoReleaseCoordinatorService( | |||||
| ) | ) | ||||
| } | } | ||||
| private fun createMergedDoPickOrder(results: List<ReleaseDoResult>) { | |||||
| 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<ReleaseDoResult>) { | |||||
| 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 一条) | // 创建多条 DoPickOrderLine(每个 DO 一条) | ||||
| results.forEach { result -> | results.forEach { result -> | ||||
| @@ -813,25 +752,34 @@ class DoReleaseCoordinatorService( | |||||
| doPickOrderLineRepository.save(line) | doPickOrderLineRepository.save(line) | ||||
| println(" DEBUG: Created DoPickOrderLine for existing DoPickOrder ${existingDoPickOrder.id}") | 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 | // 创建 do_pick_order_line | ||||
| val line = DoPickOrderLine().apply { | val line = DoPickOrderLine().apply { | ||||
| @@ -99,8 +99,8 @@ open class SuggestedPickLotService( | |||||
| val suggestedList: MutableList<SuggestedPickLot> = mutableListOf() | val suggestedList: MutableList<SuggestedPickLot> = mutableListOf() | ||||
| val holdQtyMap: MutableMap<Long?, BigDecimal?> = request.holdQtyMap | val holdQtyMap: MutableMap<Long?, BigDecimal?> = request.holdQtyMap | ||||
| // get current inventory lot line qty & grouped by item id | |||||
| val excludedWarehouseCodes = listOf("2F-W202-01-00") | |||||
| val availableInventoryLotLines = inventoryLotLineService | val availableInventoryLotLines = inventoryLotLineService | ||||
| .allInventoryLotLinesByItemIdIn(itemIds) | .allInventoryLotLinesByItemIdIn(itemIds) | ||||
| .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } | .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } | ||||
| @@ -111,6 +111,7 @@ open class SuggestedPickLotService( | |||||
| // loop for suggest pick lot line | // loop for suggest pick lot line | ||||
| pols.forEach { line -> | pols.forEach { line -> | ||||
| val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | ||||
| val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() | val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() | ||||
| val ratio = one // (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) | 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 | // loop for suggest pick lot line | ||||
| pols.forEach { 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 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 stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(line.id!!) | ||||
| val totalPickedQty = stockOutLines | 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 requiredQty = line.qty ?: zero | ||||
| val remainingQty = requiredQty.minus(totalPickedQty) | 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 | var remainingQtyToAllocate = remainingQty | ||||
| println("remaining1 $remainingQtyToAllocate (sales units)") | |||||
| val updatedLotLines = mutableListOf<InventoryLotLineInfo>() | |||||
| lotLines.forEachIndexed { index, lotLine -> | |||||
| lotLines.forEachIndexed { _, lotLine -> | |||||
| if (remainingQtyToAllocate <= zero) return@forEachIndexed | if (remainingQtyToAllocate <= zero) return@forEachIndexed | ||||
| println("calculateRemainingQtyForInfo(lotLine) ${calculateRemainingQtyForInfo(lotLine)}") | |||||
| // 拿 entity 是因为 projection 的 warehouse 没有 store_id 字段 | |||||
| val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } | 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 availableQtyInBaseUnits = calculateRemainingQtyForInfo(lotLine) | ||||
| val holdQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero | val holdQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero | ||||
| val availableQtyInSalesUnits = availableQtyInBaseUnits | val availableQtyInSalesUnits = availableQtyInBaseUnits | ||||
| .minus(holdQtyInBaseUnits) | .minus(holdQtyInBaseUnits) | ||||
| .divide(ratio, 2, RoundingMode.HALF_UP) | .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) | val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQtyToAllocate) | ||||
| remainingQtyToAllocate = remainingQtyToAllocate.minus(assignQtyInSalesUnits) | remainingQtyToAllocate = remainingQtyToAllocate.minus(assignQtyInSalesUnits) | ||||
| val newHoldQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero | |||||
| // 修复:将销售单位转换为基础单位来更新 holdQty | |||||
| val assignQtyInBaseUnits = assignQtyInSalesUnits.multiply(ratio) | val assignQtyInBaseUnits = assignQtyInSalesUnits.multiply(ratio) | ||||
| holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQtyInBaseUnits) | holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQtyInBaseUnits) | ||||
| suggestedList += SuggestedPickLot().apply { | suggestedList += SuggestedPickLot().apply { | ||||
| type = SuggestedPickLotType.PICK_ORDER | type = SuggestedPickLotType.PICK_ORDER | ||||
| suggestedLotLine = inventoryLotLine | suggestedLotLine = inventoryLotLine | ||||
| pickOrderLine = line | 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) { | if (remainingQtyToAllocate > zero) { | ||||
| suggestedList += SuggestedPickLot().apply { | suggestedList += SuggestedPickLot().apply { | ||||
| type = SuggestedPickLotType.PICK_ORDER | type = SuggestedPickLotType.PICK_ORDER | ||||
| suggestedLotLine = null | suggestedLotLine = null | ||||
| pickOrderLine = line | 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 | // Convertion | ||||