|
|
|
@@ -18,6 +18,7 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository |
|
|
|
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository |
|
|
|
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus |
|
|
|
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository |
|
|
|
import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService |
|
|
|
data class BatchReleaseJobStatus( |
|
|
|
val jobId: String, |
|
|
|
val total: Int, |
|
|
|
@@ -34,7 +35,8 @@ class DoReleaseCoordinatorService( |
|
|
|
private val jdbcDao: JdbcDao, |
|
|
|
private val doPickOrderLineRepository: DoPickOrderLineRepository, |
|
|
|
private val deliveryOrderRepository: DeliveryOrderRepository, |
|
|
|
private val doPickOrderRepository: DoPickOrderRepository |
|
|
|
private val doPickOrderRepository: DoPickOrderRepository, |
|
|
|
private val pickExecutionIssueService: PickExecutionIssueService, |
|
|
|
) { |
|
|
|
private val poolSize = Runtime.getRuntime().availableProcessors() |
|
|
|
private val executor = Executors.newFixedThreadPool(min(poolSize, 4)) |
|
|
|
@@ -362,28 +364,32 @@ class DoReleaseCoordinatorService( |
|
|
|
} |
|
|
|
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) |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
executor.submit { |
|
|
|
try { |
|
|
|
println("📦 Starting batch release for ${ids.size} orders") |
|
|
|
val sortedIds = getOrderedDeliveryOrderIds(ids) |
|
|
|
println("🔍 DEBUG: Got ${sortedIds.size} sorted orders") |
|
|
|
|
|
|
|
|
|
|
|
val releaseResults = mutableListOf<ReleaseDoResult>() |
|
|
|
|
|
|
|
|
|
|
|
// 第一步:发布所有 DO(创建 pick orders,但不创建 DoPickOrder) |
|
|
|
// 第一步:发布所有 DO(创建 pick orders,但不创建 DoPickOrder) |
|
|
|
sortedIds.forEach { id -> |
|
|
|
try { |
|
|
|
val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(id) |
|
|
|
if (deliveryOrder?.status == DeliveryOrderStatus.COMPLETED || |
|
|
|
deliveryOrder?.status == DeliveryOrderStatus.RECEIVING) { |
|
|
|
if (deliveryOrder?.status == DeliveryOrderStatus.COMPLETED || |
|
|
|
deliveryOrder?.status == DeliveryOrderStatus.RECEIVING |
|
|
|
) { |
|
|
|
println("⏭️ DO $id is already ${deliveryOrder.status?.value}, skipping") |
|
|
|
return@forEach |
|
|
|
} |
|
|
|
@@ -398,27 +404,51 @@ class DoReleaseCoordinatorService( |
|
|
|
status.failed.add(id to (e.message ?: "Exception")) |
|
|
|
} |
|
|
|
println("❌ DO $id skipped: ${e.message}") |
|
|
|
|
|
|
|
// ✅ 调用 PickExecutionIssueService 创建 issue 记录 |
|
|
|
try { |
|
|
|
val issueCategory = when { |
|
|
|
e.message?.contains("Unable to find") == true && |
|
|
|
e.message?.contains("Warehouse") == true -> |
|
|
|
com.ffii.fpsms.modules.pickOrder.entity.IssueCategory.warehouse_issue |
|
|
|
|
|
|
|
e.message?.contains("No matching truck") == true -> |
|
|
|
com.ffii.fpsms.modules.pickOrder.entity.IssueCategory.truck_issue |
|
|
|
|
|
|
|
else -> |
|
|
|
com.ffii.fpsms.modules.pickOrder.entity.IssueCategory.match_issue |
|
|
|
} |
|
|
|
|
|
|
|
pickExecutionIssueService.createBatchReleaseIssue( |
|
|
|
deliveryOrderId = id, |
|
|
|
errorMessage = e.message ?: "Unknown error", |
|
|
|
issueCategory = issueCategory |
|
|
|
) |
|
|
|
} catch (issueException: Exception) { |
|
|
|
println("⚠️ Failed to create issue for DO $id: ${issueException.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 第二步:按日期、楼层、店铺分组(与 SQL 逻辑一致) |
|
|
|
val sortedResults = releaseResults.sortedWith(compareBy( |
|
|
|
{ it.estimatedArrivalDate }, |
|
|
|
{ it.preferredFloor }, |
|
|
|
{ it.truckDepartureTime }, |
|
|
|
{ it.truckLanceCode }, |
|
|
|
{ it.loadingSequence }, |
|
|
|
{ it.shopId } |
|
|
|
)) |
|
|
|
} // ✅ forEach 循环在这里结束 |
|
|
|
|
|
|
|
// ✅ 然后按正确的顺序分组(保持排序后的顺序) |
|
|
|
val grouped = sortedResults.groupBy { |
|
|
|
Triple(it.estimatedArrivalDate, it.preferredFloor, it.shopId) |
|
|
|
} |
|
|
|
// ✅ 第二步:按日期、楼层、店铺分组(在 forEach 之后) |
|
|
|
val sortedResults = releaseResults.sortedWith( |
|
|
|
compareBy( |
|
|
|
{ it.estimatedArrivalDate }, |
|
|
|
{ it.preferredFloor }, |
|
|
|
{ it.truckDepartureTime }, |
|
|
|
{ it.truckLanceCode }, |
|
|
|
{ it.loadingSequence }, |
|
|
|
{ it.shopId } |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
// ✅ 然后按正确的顺序分组(保持排序后的顺序) |
|
|
|
val grouped = sortedResults.groupBy { |
|
|
|
Triple(it.estimatedArrivalDate, it.preferredFloor, it.shopId) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println("🔍 DEBUG: Grouped into ${grouped.size} DoPickOrders") |
|
|
|
|
|
|
|
|
|
|
|
// 第三步:为每组创建一个 DoPickOrder 和多条 DoPickOrderLine |
|
|
|
grouped.forEach { (key, group) -> |
|
|
|
try { |
|
|
|
@@ -429,31 +459,31 @@ class DoReleaseCoordinatorService( |
|
|
|
e.printStackTrace() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 第四步:更新 ticket numbers |
|
|
|
if (status.success.get() > 0) { |
|
|
|
println("🎫 Updating ticket numbers...") |
|
|
|
updateBatchTicketNumbers() |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println("✅ Batch completed: ${status.success.get()} success, ${status.failed.size} failed") |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
println("❌ Batch release exception: ${e.javaClass.simpleName} - ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
} 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) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
println("❌ Batch release exception: ${e.javaClass.simpleName} - ${e.message}") |
|
|
|
e.printStackTrace() |
|
|
|
} 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) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
private fun createMergedDoPickOrder(results: List<ReleaseDoResult>) { |
|
|
|
val first = results.first() |
|
|
|
|
|
|
|
|