From 303ff9260a75b43a9c760f21ad80a91b0cee42ad Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Wed, 15 Oct 2025 21:24:31 +0800 Subject: [PATCH] update --- .../entity/DeliveryOrderRepository.kt | 27 ++++ .../entity/DoPickOrderRepository.kt | 7 +- .../service/DeliveryOrderService.kt | 63 +++++++-- .../service/DoPickOrderService.kt | 29 +++- .../service/DoReleaseCoordinatorService.kt | 124 ++++++++++++++++++ .../web/DoPickOrderController.kt | 22 +++- .../pickOrder/service/PickOrderService.kt | 26 ++-- 7 files changed, 265 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DeliveryOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DeliveryOrderRepository.kt index c533343..fc5c6d5 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DeliveryOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DeliveryOrderRepository.kt @@ -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 { + @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 + fun findTopByM18DataLogIdAndDeletedIsFalseOrderByModifiedDesc(m18datalogId: Serializable): DeliveryOrder? fun findDeliveryOrderInfoByDeletedIsFalse(): List diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt index 9dbafe0..ebc8eb9 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt @@ -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 { fun findByTicketNoStartingWith(prefix: String): List @@ -24,4 +24,9 @@ interface DoPickOrderRepository : JpaRepository { // 在 DoPickOrderRepository 中添加这个方法 fun findByHandledByAndTicketStatusIn(handledBy: Long, status: List): List fun findByStoreIdAndTicketStatusIn(storeId: String, status: List): List +fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( + storeId: String, + requiredDeliveryDate: LocalDate, + status: List +): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt index 7526d9b..1f0cd42 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt @@ -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 { 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 { 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 diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt index 0843e93..11f09dc 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt @@ -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 { 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 类中添加这个方法 + + } // ✅ 类结束 \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt new file mode 100644 index 0000000..e28bead --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt @@ -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> = 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() + + fun startBatchReleaseAsync(ids: List, 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 { + 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 + ) + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt index 5685eda..f92f6be 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt @@ -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, + @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) +} } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index 1e98404..d80be28 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -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 val inventoryLotId = row["debugInventoryLotId"] as? Number @@ -3333,7 +3330,8 @@ ORDER BY putAll(routerInfo) } } - + */ + val enrichedResults = filteredResults return enrichedResults } // ... existing code ...