| @@ -3,12 +3,39 @@ package com.ffii.fpsms.modules.deliveryOrder.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | |||
| import org.springframework.data.jpa.repository.JpaRepository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.data.repository.query.Param | |||
| import org.springframework.data.domain.Page | |||
| import org.springframework.data.domain.Pageable | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| import java.time.LocalDateTime | |||
| @Repository | |||
| interface DeliveryOrderRepository : AbstractRepository<DeliveryOrder, Long> { | |||
| @Query(""" | |||
| select d from DeliveryOrder d | |||
| where d.deleted = false | |||
| and (:code is null or d.code like concat('%', :code, '%')) | |||
| and (:shopName is null or d.shop.name like concat('%', :shopName, '%')) | |||
| and (:status is null or d.status = :status) | |||
| and (:orderFrom is null or d.orderDate >= :orderFrom) | |||
| and (:orderTo is null or d.orderDate < :orderTo) | |||
| and (:etaFrom is null or d.estimatedArrivalDate >= :etaFrom) | |||
| and (:etaTo is null or d.estimatedArrivalDate < :etaTo) | |||
| """) | |||
| fun search( | |||
| @Param("code") code: String?, | |||
| @Param("shopName") shopName: String?, | |||
| @Param("status") status: String?, | |||
| @Param("orderFrom") orderFrom: LocalDateTime?, | |||
| @Param("orderTo") orderTo: LocalDateTime?, | |||
| @Param("etaFrom") etaFrom: LocalDateTime?, | |||
| @Param("etaTo") etaTo: LocalDateTime?, | |||
| pageable: Pageable | |||
| ): Page<DeliveryOrder> | |||
| fun findTopByM18DataLogIdAndDeletedIsFalseOrderByModifiedDesc(m18datalogId: Serializable): DeliveryOrder? | |||
| fun findDeliveryOrderInfoByDeletedIsFalse(): List<DeliveryOrderInfo> | |||
| @@ -10,7 +10,7 @@ import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| import java.time.LocalDateTime | |||
| import java.time.LocalDate | |||
| @Repository | |||
| interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> { | |||
| fun findByTicketNoStartingWith(prefix: String): List<DoPickOrder> | |||
| @@ -24,4 +24,9 @@ interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> { | |||
| // 在 DoPickOrderRepository 中添加这个方法 | |||
| fun findByHandledByAndTicketStatusIn(handledBy: Long, status: List<DoPickOrderStatus>): List<DoPickOrder> | |||
| fun findByStoreIdAndTicketStatusIn(storeId: String, status: List<DoPickOrderStatus>): List<DoPickOrder> | |||
| fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( | |||
| storeId: String, | |||
| requiredDeliveryDate: LocalDate, | |||
| status: List<DoPickOrderStatus> | |||
| ): List<DoPickOrder> | |||
| } | |||
| @@ -61,11 +61,10 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDNLabelsRequest | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDNLabelsRequest | |||
| import com.ffii.fpsms.modules.pickOrder.entity.RouterRepository | |||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | |||
| import com.ffii.fpsms.modules.stock.service.InventoryLotService | |||
| import com.ffii.fpsms.modules.pickOrder.entity.Router | |||
| import net.sf.jasperreports.engine.JasperPrintManager | |||
| import net.sf.jasperreports.engine.JRPrintPage | |||
| import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | |||
| @@ -91,7 +90,6 @@ open class DeliveryOrderService( | |||
| private val pickOrderLineRepository: PickOrderLineRepository, | |||
| private val printerService: PrinterService, | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | |||
| private val routerRepository: RouterRepository, | |||
| private val purchaseOrderRepository: PurchaseOrderRepository, | |||
| private val inventoryLotService: InventoryLotService, | |||
| private val suggestedPickLotRepository: SuggestPickLotRepository, | |||
| @@ -276,7 +274,53 @@ open class DeliveryOrderService( | |||
| open fun searchCodeAndShopName(code: String?, shopName: String?): List<DeliveryOrderInfo> { | |||
| return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndDeletedIsFalse(code, shopName); | |||
| } | |||
| open fun getWarehouseOrderByItemId(itemId: Long): Int? { | |||
| val inventoryLots = inventoryLotService.findByItemId(itemId) | |||
| if (inventoryLots.isNotEmpty()) { | |||
| val inventoryLotId = inventoryLots.first().id?.toInt() | |||
| return inventoryLotId?.let { lotId -> | |||
| // 查询 warehouse 的 order 字段 | |||
| val sql = """ | |||
| SELECT w.`order` as warehouseOrder | |||
| FROM fpsmsdb.inventory_lot_line ill | |||
| JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| WHERE ill.inventoryLotId = :lotId | |||
| AND ill.deleted = false | |||
| ORDER BY w.`order` ASC | |||
| LIMIT 1 | |||
| """.trimIndent() | |||
| val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId)) | |||
| if (result.isNotEmpty()) { | |||
| (result.first()["warehouseOrder"] as Number?)?.toInt() | |||
| } else null | |||
| } | |||
| } | |||
| return null | |||
| } | |||
| // ✅ 新增方法2:获取 warehouse 的 code 字段(用于显示路由) | |||
| open fun getWarehouseCodeByItemId(itemId: Long): String? { | |||
| val inventoryLots = inventoryLotService.findByItemId(itemId) | |||
| if (inventoryLots.isNotEmpty()) { | |||
| val inventoryLotId = inventoryLots.first().id?.toInt() | |||
| return inventoryLotId?.let { lotId -> | |||
| val sql = """ | |||
| SELECT w.code as warehouseCode | |||
| FROM fpsmsdb.inventory_lot_line ill | |||
| JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| WHERE ill.inventoryLotId = :lotId | |||
| AND ill.deleted = false | |||
| ORDER BY w.`order` ASC | |||
| LIMIT 1 | |||
| """.trimIndent() | |||
| val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId)) | |||
| if (result.isNotEmpty()) { | |||
| result.first()["warehouseCode"] as String? | |||
| } else null | |||
| } | |||
| } | |||
| return null | |||
| } | |||
| open fun updateDeliveryOrderStatus(request: SaveDeliveryOrderStatusRequest): SaveDeliveryOrderResponse { | |||
| val deliveryOrder = checkNotNull( | |||
| @@ -614,8 +658,8 @@ return MessageResponse( | |||
| ) | |||
| // ... existing code ... | |||
| } | |||
| } | |||
| /* | |||
| open fun getRouteAndIndexByInventoryLotId(inventoryLotId: Int): List<Router> { | |||
| return routerRepository.findByInventoryLotIdAndDeletedFalse(inventoryLotId) | |||
| .sortedWith(compareBy( | |||
| @@ -636,6 +680,7 @@ return MessageResponse( | |||
| } | |||
| } | |||
| } | |||
| open fun getRouteByItemId(itemId: Long): String? { | |||
| val inventoryLots = inventoryLotService.findByItemId(itemId) | |||
| if (inventoryLots.isNotEmpty()){ | |||
| @@ -658,7 +703,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? { | |||
| } | |||
| return null | |||
| } | |||
| */ | |||
| open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String { | |||
| try { | |||
| val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId) | |||
| @@ -706,7 +751,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? { | |||
| for (info in deliveryNoteInfo) { | |||
| val sortedLines = info.deliveryOrderLines.sortedBy { line -> | |||
| line.itemId?.let { itemId -> | |||
| getRouterIndexByItemId(itemId) | |||
| getWarehouseOrderByItemId(itemId) // ✅ 改用 warehouse order | |||
| } ?: Int.MAX_VALUE | |||
| } | |||
| @@ -722,7 +767,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? { | |||
| field["shortName"] = line.uomShortDesc ?:"" | |||
| val route = line.itemId?.let { itemId -> | |||
| getRouteByItemId(itemId) | |||
| getWarehouseCodeByItemId(itemId) // ✅ 使用新方法 | |||
| } ?: "" | |||
| field["route"] = route | |||
| @@ -38,12 +38,14 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.* // ✅ 导入 | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | |||
| @Service | |||
| class DoPickOrderService( | |||
| private val doPickOrderRepository: DoPickOrderRepository, | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | |||
| private val userRepository: UserRepository, | |||
| private val pickOrderRepository: PickOrderRepository | |||
| private val pickOrderRepository: PickOrderRepository, | |||
| ) { | |||
| fun findReleasedDoPickOrders(): List<DoPickOrder> { | |||
| return doPickOrderRepository.findByTicketStatusIn( | |||
| @@ -182,13 +184,21 @@ class DoPickOrderService( | |||
| } | |||
| return doPickOrderRepository.saveAll(doPickOrders) | |||
| } | |||
| fun getSummaryByStore(storeId: String): StoreLaneSummary { | |||
| // ✅ 修改:查询所有状态的订单,不只是 pending | |||
| val allRecords = doPickOrderRepository.findByStoreIdAndTicketStatusIn( | |||
| fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary { | |||
| // ✅ 使用传入的日期,如果没有传入则使用今天 | |||
| val targetDate = requiredDate ?: LocalDate.now() | |||
| println("🔍 DEBUG: Getting summary for store=$storeId, date=$targetDate") | |||
| // ✅ 修改:按日期查询订单 | |||
| val allRecords = doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( | |||
| storeId, | |||
| targetDate, | |||
| listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed) | |||
| ) | |||
| println("🔍 DEBUG: Found ${allRecords.size} records for date $targetDate") | |||
| val grouped = allRecords.groupBy { it.truckDepartureTime to it.truckLanceCode } | |||
| .mapValues { (_, list) -> | |||
| LaneBtn( | |||
| @@ -217,7 +227,6 @@ class DoPickOrderService( | |||
| return StoreLaneSummary(storeId = storeId, rows = timeGroups) | |||
| } | |||
| // ✅ 修复:把 assignByLane 移到类里面 | |||
| fun assignByLane(request: AssignByLaneRequest): MessageResponse { | |||
| val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn( | |||
| @@ -232,6 +241,7 @@ class DoPickOrderService( | |||
| errorPosition = null, entity = null | |||
| ) | |||
| } | |||
| val candidates = doPickOrderRepository | |||
| .findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc( | |||
| request.storeId, | |||
| @@ -256,19 +266,21 @@ class DoPickOrderService( | |||
| val user = userRepository.findById(request.userId).orElse(null) | |||
| val handlerName = user?.name ?: "Unknown" | |||
| // ✅ 更新 do_pick_order | |||
| // ✅ 更新 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) { | |||
| // ✅ 修复:assignTo 需要 User 对象,不是 Long | |||
| val user = userRepository.findById(request.userId).orElse(null) | |||
| pickOrder.assignTo = user | |||
| pickOrder.status = PickOrderStatus.RELEASED | |||
| @@ -299,4 +311,7 @@ class DoPickOrderService( | |||
| ) | |||
| ) | |||
| } | |||
| // 在 DoPickOrderService 类中添加这个方法 | |||
| } // ✅ 类结束 | |||
| @@ -0,0 +1,124 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleaseDoRequest | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import org.springframework.stereotype.Service | |||
| import java.time.Instant | |||
| import java.util.UUID | |||
| import java.util.concurrent.ConcurrentHashMap | |||
| import java.util.concurrent.Executors | |||
| import java.util.concurrent.atomic.AtomicInteger | |||
| import kotlin.math.min | |||
| data class BatchReleaseJobStatus( | |||
| val jobId: String, | |||
| val total: Int, | |||
| val success: AtomicInteger = AtomicInteger(0), | |||
| val failed: MutableList<Pair<Long, String>> = mutableListOf(), | |||
| @Volatile var running: Boolean = true, | |||
| val startedAt: Long = Instant.now().toEpochMilli(), | |||
| @Volatile var finishedAt: Long? = null | |||
| ) | |||
| @Service | |||
| class DoReleaseCoordinatorService( | |||
| private val deliveryOrderService: DeliveryOrderService | |||
| ) { | |||
| // 可按机器/DB调优:CPU核数 * 2 或固定 8~12 | |||
| private val poolSize = Runtime.getRuntime().availableProcessors().coerceAtLeast(4) * 2 | |||
| private val executor = Executors.newFixedThreadPool(min(poolSize, 12)) | |||
| private val jobs = ConcurrentHashMap<String, BatchReleaseJobStatus>() | |||
| fun startBatchReleaseAsync(ids: List<Long>, userId: Long): MessageResponse { | |||
| if (ids.isEmpty()) { | |||
| return MessageResponse(id = null, code = "NO_IDS", name = null, type = null, | |||
| message = "No IDs provided", errorPosition = null, entity = null) | |||
| } | |||
| val jobId = UUID.randomUUID().toString() | |||
| val status = BatchReleaseJobStatus(jobId = jobId, total = ids.size) | |||
| jobs[jobId] = status | |||
| // 可按压测调优 | |||
| val chunkSize = 20 | |||
| val chunks = ids.chunked(chunkSize) | |||
| executor.submit { | |||
| try { | |||
| chunks.forEach { chunk -> | |||
| val futures = chunk.map { id -> | |||
| executor.submit<Unit> { | |||
| var attempts = 0 | |||
| while (attempts < 2) { | |||
| try { | |||
| val res = deliveryOrderService.releaseDeliveryOrder( | |||
| ReleaseDoRequest(id = id, userId = userId) | |||
| ) | |||
| val code = res.code ?: "OK" | |||
| // 成功码放宽(关键修复) | |||
| if (code in setOf("SUCCESS", "OK", "PARTIAL_SUCCESS")) { | |||
| status.success.incrementAndGet() | |||
| } else { | |||
| synchronized(status.failed) { | |||
| status.failed.add(id to (res.message ?: "Release failed: $code")) | |||
| } | |||
| } | |||
| // 打印以便核对统计 | |||
| println("DEBUG release DO id=$id -> code=${res.code}, msg=${res.message}") | |||
| break | |||
| } catch (e: Exception) { | |||
| attempts++ | |||
| val msg = e.message ?: "" | |||
| // 死锁重试一次(1213) | |||
| if (attempts < 2 && msg.contains("Deadlock", ignoreCase = true)) { | |||
| Thread.sleep(150) | |||
| continue | |||
| } | |||
| synchronized(status.failed) { | |||
| status.failed.add(id to (e.message ?: "Unknown error")) | |||
| } | |||
| break | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // 等待当前块全部完成,避免任务堆积 | |||
| futures.forEach { f -> try { f.get() } catch (_: Exception) {} } | |||
| } | |||
| } finally { | |||
| status.running = false | |||
| status.finishedAt = Instant.now().toEpochMilli() | |||
| } | |||
| } | |||
| return MessageResponse( | |||
| id = null, code = "STARTED", name = null, type = null, | |||
| message = "Batch release started", errorPosition = null, | |||
| entity = mapOf("jobId" to jobId, "total" to ids.size) | |||
| ) | |||
| } | |||
| fun getBatchReleaseProgress(jobId: String): MessageResponse { | |||
| val s = jobs[jobId] ?: return MessageResponse( | |||
| id = null, code = "NOT_FOUND", name = null, type = null, | |||
| message = "Job not found", errorPosition = null, entity = null | |||
| ) | |||
| val finished = s.success.get() + s.failed.size | |||
| val progress = (finished.toDouble() / s.total).coerceIn(0.0, 1.0) | |||
| return MessageResponse( | |||
| id = null, code = if (s.running) "RUNNING" else "FINISHED", name = null, type = null, | |||
| message = null, errorPosition = null, entity = mapOf( | |||
| "jobId" to s.jobId, | |||
| "total" to s.total, | |||
| "finished" to finished, | |||
| "success" to s.success.get(), | |||
| "failedCount" to s.failed.size, | |||
| "failed" to s.failed.take(50), // 避免超大返回,可分页 | |||
| "running" to s.running, | |||
| "progress" to progress, | |||
| "startedAt" to s.startedAt, | |||
| "finishedAt" to s.finishedAt | |||
| ) | |||
| ) | |||
| } | |||
| } | |||
| @@ -37,11 +37,14 @@ import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByStoreRequest | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.* | |||
| import org.springframework.format.annotation.DateTimeFormat | |||
| import com.ffii.fpsms.modules.deliveryOrder.service.DoReleaseCoordinatorService | |||
| @RestController | |||
| @RequestMapping("/doPickOrder") | |||
| class DoPickOrderController( | |||
| private val doPickOrderService: DoPickOrderService, | |||
| private val doPickOrderRepository: DoPickOrderRepository, | |||
| private val doReleaseCoordinatorService: DoReleaseCoordinatorService | |||
| ) { | |||
| @PostMapping("/assign-by-store") | |||
| fun assignPickOrderByStore(@RequestBody request: AssignByStoreRequest): MessageResponse { | |||
| @@ -58,11 +61,26 @@ class DoPickOrderController( | |||
| return doPickOrderService.findReleasedDoPickOrders() | |||
| } | |||
| @GetMapping("/summary-by-store") | |||
| fun getSummaryByStore(@RequestParam storeId: String): StoreLaneSummary { | |||
| return doPickOrderService.getSummaryByStore(storeId) | |||
| fun getSummaryByStore( | |||
| @RequestParam storeId: String, | |||
| @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) requiredDate: LocalDate? | |||
| ): StoreLaneSummary { | |||
| return doPickOrderService.getSummaryByStore(storeId, requiredDate) | |||
| } | |||
| @PostMapping("/assign-by-lane") | |||
| fun assignByLane(@RequestBody request: AssignByLaneRequest): MessageResponse { | |||
| return doPickOrderService.assignByLane(request) | |||
| } | |||
| @PostMapping("/batch-release/async") | |||
| fun startBatchReleaseAsync( | |||
| @RequestBody ids: List<Long>, | |||
| @RequestParam(defaultValue = "1") userId: Long | |||
| ): MessageResponse { | |||
| return doReleaseCoordinatorService.startBatchReleaseAsync(ids, userId) | |||
| } | |||
| @GetMapping("/batch-release/progress/{jobId}") | |||
| fun getBatchReleaseProgress(@PathVariable jobId: String): MessageResponse { | |||
| return doReleaseCoordinatorService.getBatchReleaseProgress(jobId) | |||
| } | |||
| } | |||
| @@ -50,7 +50,6 @@ import kotlin.jvm.optionals.getOrNull | |||
| import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderGroupInfo | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | |||
| import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository | |||
| import com.ffii.fpsms.modules.pickOrder.entity.RouterRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord | |||
| @@ -81,7 +80,6 @@ open class PickOrderService( | |||
| private val deliveryOrderRepository: DeliveryOrderRepository, | |||
| private val truckRepository: TruckRepository, | |||
| private val doPickOrderService: DoPickOrderService, | |||
| private val routerRepository: RouterRepository, | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | |||
| private val doPickOrderRepository: DoPickOrderRepository, | |||
| private val userRepository: UserRepository, | |||
| @@ -2700,7 +2698,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo | |||
| } | |||
| println("✅ Filtered result count: ${filteredResults.size}") | |||
| /* | |||
| // ✅ Add router information for each lot | |||
| val enrichedResults = filteredResults.map { row -> | |||
| val inventoryLotId = row["debugInventoryLotId"] as? Number | |||
| @@ -2759,7 +2757,8 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo | |||
| putAll(routerInfo) | |||
| } | |||
| } | |||
| */ | |||
| val enrichedResults = filteredResults | |||
| return enrichedResults | |||
| } | |||
| @@ -3157,10 +3156,11 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map | |||
| COALESCE(uc.udfudesc, 'N/A') as stockUnit, | |||
| -- Router Information | |||
| r.id as routerId, | |||
| ro.`order` as routerIndex, | |||
| CONCAT(COALESCE(ro.route_area, ''), COALESCE(r.route, '')) as routerRoute, | |||
| ro.route_area as routerArea, | |||
| w.id as routerId, | |||
| w.`order` as routerIndex, | |||
| w.code as routerRoute, | |||
| w.code as routerArea, | |||
| -- ✅ FIXED: Set quantities to NULL for rejected lots | |||
| @@ -3238,8 +3238,6 @@ JOIN fpsmsdb.items i ON i.id = pol.itemId | |||
| LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId | |||
| LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId | |||
| LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id | |||
| LEFT JOIN fpsmsdb.router r ON r.inventoryLotId = ill.id AND r.deleted = false | |||
| LEFT JOIN fpsmsdb.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 | |||
| LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false | |||
| @@ -3253,8 +3251,7 @@ AND il.deleted = false | |||
| AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) | |||
| ORDER BY | |||
| CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, | |||
| COALESCE(ro.`order`, 999999) ASC, | |||
| COALESCE(r.route, 999999) ASC, | |||
| COALESCE(w.`order`, 999999) ASC, -- ✅ 改用 warehouse.order | |||
| po.code ASC, | |||
| i.code ASC, | |||
| il.expiryDate ASC, | |||
| @@ -3274,7 +3271,7 @@ ORDER BY | |||
| } | |||
| println("✅ Filtered result count: ${filteredResults.size}") | |||
| /* | |||
| // ✅ Add router information for each lot | |||
| val enrichedResults = filteredResults.map { row -> | |||
| val inventoryLotId = row["debugInventoryLotId"] as? Number | |||
| @@ -3333,7 +3330,8 @@ ORDER BY | |||
| putAll(routerInfo) | |||
| } | |||
| } | |||
| */ | |||
| val enrichedResults = filteredResults | |||
| return enrichedResults | |||
| } | |||
| // ... existing code ... | |||