| @@ -3,12 +3,39 @@ package com.ffii.fpsms.modules.deliveryOrder.entity | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo | import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo | ||||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | 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 org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | import java.io.Serializable | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| @Repository | @Repository | ||||
| interface DeliveryOrderRepository : AbstractRepository<DeliveryOrder, Long> { | 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 findTopByM18DataLogIdAndDeletedIsFalseOrderByModifiedDesc(m18datalogId: Serializable): DeliveryOrder? | ||||
| fun findDeliveryOrderInfoByDeletedIsFalse(): List<DeliveryOrderInfo> | fun findDeliveryOrderInfoByDeletedIsFalse(): List<DeliveryOrderInfo> | ||||
| @@ -10,7 +10,7 @@ import org.springframework.data.jpa.repository.Query | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | import java.io.Serializable | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.LocalDate | |||||
| @Repository | @Repository | ||||
| interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> { | interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> { | ||||
| fun findByTicketNoStartingWith(prefix: String): List<DoPickOrder> | fun findByTicketNoStartingWith(prefix: String): List<DoPickOrder> | ||||
| @@ -24,4 +24,9 @@ interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> { | |||||
| // 在 DoPickOrderRepository 中添加这个方法 | // 在 DoPickOrderRepository 中添加这个方法 | ||||
| fun findByHandledByAndTicketStatusIn(handledBy: Long, status: List<DoPickOrderStatus>): List<DoPickOrder> | fun findByHandledByAndTicketStatusIn(handledBy: Long, status: List<DoPickOrderStatus>): List<DoPickOrder> | ||||
| fun findByStoreIdAndTicketStatusIn(storeId: String, 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.entity.DoPickOrderRecordRepository | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDNLabelsRequest | import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDNLabelsRequest | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDNLabelsRequest | 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.purchaseOrder.entity.PurchaseOrderRepository | ||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | ||||
| import com.ffii.fpsms.modules.stock.service.InventoryLotService | 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.JasperPrintManager | ||||
| import net.sf.jasperreports.engine.JRPrintPage | import net.sf.jasperreports.engine.JRPrintPage | ||||
| import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | ||||
| @@ -91,7 +90,6 @@ open class DeliveryOrderService( | |||||
| private val pickOrderLineRepository: PickOrderLineRepository, | private val pickOrderLineRepository: PickOrderLineRepository, | ||||
| private val printerService: PrinterService, | private val printerService: PrinterService, | ||||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | ||||
| private val routerRepository: RouterRepository, | |||||
| private val purchaseOrderRepository: PurchaseOrderRepository, | private val purchaseOrderRepository: PurchaseOrderRepository, | ||||
| private val inventoryLotService: InventoryLotService, | private val inventoryLotService: InventoryLotService, | ||||
| private val suggestedPickLotRepository: SuggestPickLotRepository, | private val suggestedPickLotRepository: SuggestPickLotRepository, | ||||
| @@ -276,7 +274,53 @@ open class DeliveryOrderService( | |||||
| open fun searchCodeAndShopName(code: String?, shopName: String?): List<DeliveryOrderInfo> { | open fun searchCodeAndShopName(code: String?, shopName: String?): List<DeliveryOrderInfo> { | ||||
| return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndDeletedIsFalse(code, shopName); | 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 { | open fun updateDeliveryOrderStatus(request: SaveDeliveryOrderStatusRequest): SaveDeliveryOrderResponse { | ||||
| val deliveryOrder = checkNotNull( | val deliveryOrder = checkNotNull( | ||||
| @@ -614,8 +658,8 @@ return MessageResponse( | |||||
| ) | ) | ||||
| // ... existing code ... | // ... existing code ... | ||||
| } | |||||
| } | |||||
| /* | |||||
| open fun getRouteAndIndexByInventoryLotId(inventoryLotId: Int): List<Router> { | open fun getRouteAndIndexByInventoryLotId(inventoryLotId: Int): List<Router> { | ||||
| return routerRepository.findByInventoryLotIdAndDeletedFalse(inventoryLotId) | return routerRepository.findByInventoryLotIdAndDeletedFalse(inventoryLotId) | ||||
| .sortedWith(compareBy( | .sortedWith(compareBy( | ||||
| @@ -636,6 +680,7 @@ return MessageResponse( | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| open fun getRouteByItemId(itemId: Long): String? { | open fun getRouteByItemId(itemId: Long): String? { | ||||
| val inventoryLots = inventoryLotService.findByItemId(itemId) | val inventoryLots = inventoryLotService.findByItemId(itemId) | ||||
| if (inventoryLots.isNotEmpty()){ | if (inventoryLots.isNotEmpty()){ | ||||
| @@ -658,7 +703,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? { | |||||
| } | } | ||||
| return null | return null | ||||
| } | } | ||||
| */ | |||||
| open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String { | open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String { | ||||
| try { | try { | ||||
| val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId) | val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId) | ||||
| @@ -706,7 +751,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? { | |||||
| for (info in deliveryNoteInfo) { | for (info in deliveryNoteInfo) { | ||||
| val sortedLines = info.deliveryOrderLines.sortedBy { line -> | val sortedLines = info.deliveryOrderLines.sortedBy { line -> | ||||
| line.itemId?.let { itemId -> | line.itemId?.let { itemId -> | ||||
| getRouterIndexByItemId(itemId) | |||||
| getWarehouseOrderByItemId(itemId) // ✅ 改用 warehouse order | |||||
| } ?: Int.MAX_VALUE | } ?: Int.MAX_VALUE | ||||
| } | } | ||||
| @@ -722,7 +767,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? { | |||||
| field["shortName"] = line.uomShortDesc ?:"" | field["shortName"] = line.uomShortDesc ?:"" | ||||
| val route = line.itemId?.let { itemId -> | val route = line.itemId?.let { itemId -> | ||||
| getRouteByItemId(itemId) | |||||
| getWarehouseCodeByItemId(itemId) // ✅ 使用新方法 | |||||
| } ?: "" | } ?: "" | ||||
| field["route"] = route | 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.deliveryOrder.web.models.* // ✅ 导入 | ||||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | ||||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | ||||
| @Service | @Service | ||||
| class DoPickOrderService( | class DoPickOrderService( | ||||
| private val doPickOrderRepository: DoPickOrderRepository, | private val doPickOrderRepository: DoPickOrderRepository, | ||||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | ||||
| private val userRepository: UserRepository, | private val userRepository: UserRepository, | ||||
| private val pickOrderRepository: PickOrderRepository | |||||
| private val pickOrderRepository: PickOrderRepository, | |||||
| ) { | ) { | ||||
| fun findReleasedDoPickOrders(): List<DoPickOrder> { | fun findReleasedDoPickOrders(): List<DoPickOrder> { | ||||
| return doPickOrderRepository.findByTicketStatusIn( | return doPickOrderRepository.findByTicketStatusIn( | ||||
| @@ -182,13 +184,21 @@ class DoPickOrderService( | |||||
| } | } | ||||
| return doPickOrderRepository.saveAll(doPickOrders) | 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, | storeId, | ||||
| targetDate, | |||||
| listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed) | 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 } | val grouped = allRecords.groupBy { it.truckDepartureTime to it.truckLanceCode } | ||||
| .mapValues { (_, list) -> | .mapValues { (_, list) -> | ||||
| LaneBtn( | LaneBtn( | ||||
| @@ -217,7 +227,6 @@ class DoPickOrderService( | |||||
| return StoreLaneSummary(storeId = storeId, rows = timeGroups) | return StoreLaneSummary(storeId = storeId, rows = timeGroups) | ||||
| } | } | ||||
| // ✅ 修复:把 assignByLane 移到类里面 | // ✅ 修复:把 assignByLane 移到类里面 | ||||
| fun assignByLane(request: AssignByLaneRequest): MessageResponse { | fun assignByLane(request: AssignByLaneRequest): MessageResponse { | ||||
| val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn( | val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn( | ||||
| @@ -232,6 +241,7 @@ class DoPickOrderService( | |||||
| errorPosition = null, entity = null | errorPosition = null, entity = null | ||||
| ) | ) | ||||
| } | } | ||||
| val candidates = doPickOrderRepository | val candidates = doPickOrderRepository | ||||
| .findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc( | .findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc( | ||||
| request.storeId, | request.storeId, | ||||
| @@ -256,19 +266,21 @@ class DoPickOrderService( | |||||
| val user = userRepository.findById(request.userId).orElse(null) | val user = userRepository.findById(request.userId).orElse(null) | ||||
| val handlerName = user?.name ?: "Unknown" | val handlerName = user?.name ?: "Unknown" | ||||
| // ✅ 更新 do_pick_order | |||||
| // ✅ 更新 do_pick_order - 保持原有的卡车信息 | |||||
| firstOrder.handledBy = request.userId | firstOrder.handledBy = request.userId | ||||
| firstOrder.handlerName = handlerName | firstOrder.handlerName = handlerName | ||||
| firstOrder.ticketStatus = DoPickOrderStatus.released | firstOrder.ticketStatus = DoPickOrderStatus.released | ||||
| firstOrder.ticketReleaseTime = LocalDateTime.now() | firstOrder.ticketReleaseTime = LocalDateTime.now() | ||||
| // ✅ 重要:不要修改 truckDepartureTime 和 truckLanceCode | |||||
| // 这些信息应该保持用户选择的值 | |||||
| doPickOrderRepository.save(firstOrder) | doPickOrderRepository.save(firstOrder) | ||||
| // ✅ 同步更新 pick_order 表 | // ✅ 同步更新 pick_order 表 | ||||
| if (firstOrder.pickOrderId != null) { | if (firstOrder.pickOrderId != null) { | ||||
| val pickOrder = pickOrderRepository.findById(firstOrder.pickOrderId!!).orElse(null) | val pickOrder = pickOrderRepository.findById(firstOrder.pickOrderId!!).orElse(null) | ||||
| if (pickOrder != null) { | if (pickOrder != null) { | ||||
| // ✅ 修复:assignTo 需要 User 对象,不是 Long | |||||
| val user = userRepository.findById(request.userId).orElse(null) | val user = userRepository.findById(request.userId).orElse(null) | ||||
| pickOrder.assignTo = user | pickOrder.assignTo = user | ||||
| pickOrder.status = PickOrderStatus.RELEASED | 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.entity.DoPickOrderRepository | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByStoreRequest | import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByStoreRequest | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.* | import com.ffii.fpsms.modules.deliveryOrder.web.models.* | ||||
| import org.springframework.format.annotation.DateTimeFormat | |||||
| import com.ffii.fpsms.modules.deliveryOrder.service.DoReleaseCoordinatorService | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/doPickOrder") | @RequestMapping("/doPickOrder") | ||||
| class DoPickOrderController( | class DoPickOrderController( | ||||
| private val doPickOrderService: DoPickOrderService, | private val doPickOrderService: DoPickOrderService, | ||||
| private val doPickOrderRepository: DoPickOrderRepository, | private val doPickOrderRepository: DoPickOrderRepository, | ||||
| private val doReleaseCoordinatorService: DoReleaseCoordinatorService | |||||
| ) { | ) { | ||||
| @PostMapping("/assign-by-store") | @PostMapping("/assign-by-store") | ||||
| fun assignPickOrderByStore(@RequestBody request: AssignByStoreRequest): MessageResponse { | fun assignPickOrderByStore(@RequestBody request: AssignByStoreRequest): MessageResponse { | ||||
| @@ -58,11 +61,26 @@ class DoPickOrderController( | |||||
| return doPickOrderService.findReleasedDoPickOrders() | return doPickOrderService.findReleasedDoPickOrders() | ||||
| } | } | ||||
| @GetMapping("/summary-by-store") | @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") | @PostMapping("/assign-by-lane") | ||||
| fun assignByLane(@RequestBody request: AssignByLaneRequest): MessageResponse { | fun assignByLane(@RequestBody request: AssignByLaneRequest): MessageResponse { | ||||
| return doPickOrderService.assignByLane(request) | 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.pickOrder.entity.projection.PickOrderGroupInfo | ||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | ||||
| import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository | 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.service.DoPickOrderService | ||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder | import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder | ||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord | import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord | ||||
| @@ -81,7 +80,6 @@ open class PickOrderService( | |||||
| private val deliveryOrderRepository: DeliveryOrderRepository, | private val deliveryOrderRepository: DeliveryOrderRepository, | ||||
| private val truckRepository: TruckRepository, | private val truckRepository: TruckRepository, | ||||
| private val doPickOrderService: DoPickOrderService, | private val doPickOrderService: DoPickOrderService, | ||||
| private val routerRepository: RouterRepository, | |||||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | ||||
| private val doPickOrderRepository: DoPickOrderRepository, | private val doPickOrderRepository: DoPickOrderRepository, | ||||
| private val userRepository: UserRepository, | private val userRepository: UserRepository, | ||||
| @@ -2700,7 +2698,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo | |||||
| } | } | ||||
| println("✅ Filtered result count: ${filteredResults.size}") | println("✅ Filtered result count: ${filteredResults.size}") | ||||
| /* | |||||
| // ✅ Add router information for each lot | // ✅ Add router information for each lot | ||||
| val enrichedResults = filteredResults.map { row -> | val enrichedResults = filteredResults.map { row -> | ||||
| val inventoryLotId = row["debugInventoryLotId"] as? Number | val inventoryLotId = row["debugInventoryLotId"] as? Number | ||||
| @@ -2759,7 +2757,8 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo | |||||
| putAll(routerInfo) | putAll(routerInfo) | ||||
| } | } | ||||
| } | } | ||||
| */ | |||||
| val enrichedResults = filteredResults | |||||
| return enrichedResults | return enrichedResults | ||||
| } | } | ||||
| @@ -3157,10 +3156,11 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map | |||||
| COALESCE(uc.udfudesc, 'N/A') as stockUnit, | COALESCE(uc.udfudesc, 'N/A') as stockUnit, | ||||
| -- Router Information | -- 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 | -- ✅ 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.uom_conversion uc ON uc.id = pol.uomId | ||||
| LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId | 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.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.inventory_lot il ON il.id = ill.inventoryLotId | ||||
| LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | 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 | 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) | AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) | ||||
| ORDER BY | ORDER BY | ||||
| CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, | 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, | po.code ASC, | ||||
| i.code ASC, | i.code ASC, | ||||
| il.expiryDate ASC, | il.expiryDate ASC, | ||||
| @@ -3274,7 +3271,7 @@ ORDER BY | |||||
| } | } | ||||
| println("✅ Filtered result count: ${filteredResults.size}") | println("✅ Filtered result count: ${filteredResults.size}") | ||||
| /* | |||||
| // ✅ Add router information for each lot | // ✅ Add router information for each lot | ||||
| val enrichedResults = filteredResults.map { row -> | val enrichedResults = filteredResults.map { row -> | ||||
| val inventoryLotId = row["debugInventoryLotId"] as? Number | val inventoryLotId = row["debugInventoryLotId"] as? Number | ||||
| @@ -3333,7 +3330,8 @@ ORDER BY | |||||
| putAll(routerInfo) | putAll(routerInfo) | ||||
| } | } | ||||
| } | } | ||||
| */ | |||||
| val enrichedResults = filteredResults | |||||
| return enrichedResults | return enrichedResults | ||||
| } | } | ||||
| // ... existing code ... | // ... existing code ... | ||||