From 2a76180057ec09a4327a5abbc595995cd12eeaeb Mon Sep 17 00:00:00 2001 From: "Tommy\\2Fi-Staff" Date: Tue, 13 Jan 2026 11:09:02 +0800 Subject: [PATCH] Supporting Function FG Pick Status Dashboard --- .../service/DoPickOrderService.kt | 180 ++++++++++++++++++ .../models/TruckScheduleDashboardResponse.kt | 21 ++ 2 files changed, 201 insertions(+) create mode 100644 src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/TruckScheduleDashboardResponse.kt 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 f64deb8..be23065 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 @@ -48,9 +48,11 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord import org.springframework.context.annotation.Lazy import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository import com.ffii.fpsms.modules.deliveryOrder.web.models.TicketReleaseTableResponse +import com.ffii.fpsms.modules.deliveryOrder.web.models.TruckScheduleDashboardResponse import com.ffii.fpsms.modules.deliveryOrder.web.models.SearchDeliveryOrderInfoRequest import com.ffii.core.response.RecordsRes import org.springframework.data.domain.PageRequest +import java.time.temporal.ChronoUnit @Service open class DoPickOrderService( private val doPickOrderRepository: DoPickOrderRepository, @@ -748,5 +750,183 @@ open class DoPickOrderService( return allPickOrderLines.size } + /** + * Get truck schedule dashboard data aggregated by store, truck lane, and departure time. + * Groups DoPickOrder and DoPickOrderRecord data to provide summary statistics. + */ + open fun getTruckScheduleDashboard(): List { + val today = LocalDate.now() + + // Fetch all active DoPickOrders for today + val doPickOrders = doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( + "2/F", today, listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed) + ) + doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( + "4/F", today, listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed) + ) + + // Fetch all DoPickOrderRecords for today (completed records) + val doPickOrderRecords = doPickOrderRecordRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( + "2/F", today, listOf(DoPickOrderStatus.completed) + ) + doPickOrderRecordRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn( + "4/F", today, listOf(DoPickOrderStatus.completed) + ) + + // Combine both types into a unified data structure for aggregation + data class TicketData( + val storeId: String?, + val truckId: Long?, + val truckLanceCode: String?, + val truckDepartureTime: java.time.LocalTime?, + val shopId: Long?, + val shopCode: String?, + val ticketNo: String?, + val ticketReleaseTime: LocalDateTime?, + val ticketCompleteDateTime: LocalDateTime?, + val ticketStatus: DoPickOrderStatus?, + val doPickOrderId: Long?, + val isRecord: Boolean + ) + + val allTickets = mutableListOf() + + doPickOrders.forEach { dpo -> + allTickets.add(TicketData( + storeId = dpo.storeId, + truckId = dpo.truckId, + truckLanceCode = dpo.truckLanceCode, + truckDepartureTime = dpo.truckDepartureTime, + shopId = dpo.shopId, + shopCode = dpo.shopCode, + ticketNo = dpo.ticketNo, + ticketReleaseTime = dpo.ticketReleaseTime, + ticketCompleteDateTime = dpo.ticketCompleteDateTime, + ticketStatus = dpo.ticketStatus, + doPickOrderId = dpo.id, + isRecord = false + )) + } + + doPickOrderRecords.forEach { record -> + allTickets.add(TicketData( + storeId = record.storeId, + truckId = record.truckId, + truckLanceCode = record.truckLanceCode, + truckDepartureTime = record.truckDepartureTime, + shopId = record.shopId, + shopCode = record.shopCode, + ticketNo = record.ticketNo, + ticketReleaseTime = record.ticketReleaseTime, + ticketCompleteDateTime = record.ticketCompleteDateTime, + ticketStatus = record.ticketStatus, + doPickOrderId = record.recordId, + isRecord = true + )) + } + + // Group by storeId, truckLanceCode, truckDepartureTime + val grouped = allTickets.groupBy { + Triple(it.storeId, it.truckLanceCode, it.truckDepartureTime) + } + + return grouped.map { (key, tickets) -> + val (storeId, truckLanceCode, truckDepartureTime) = key + + // Count distinct shops + val distinctShops = tickets.mapNotNull { it.shopId ?: it.shopCode?.hashCode()?.toLong() }.distinct().size + + // Count distinct tickets + val distinctTickets = tickets.mapNotNull { it.ticketNo }.distinct().size + + // Calculate total items to pick + var totalItems = 0 + tickets.forEach { ticket -> + if (ticket.doPickOrderId != null) { + if (ticket.isRecord) { + totalItems += countFGItemsFromRecordById(ticket.doPickOrderId) + } else { + totalItems += countFGItemsById(ticket.doPickOrderId) + } + } + } + + // Count released tickets (ticketReleaseTime is not null) + val releasedTickets = tickets.count { it.ticketReleaseTime != null } + + // Find first ticket start time (earliest ticketReleaseTime) + val firstTicketStartTime = tickets + .mapNotNull { it.ticketReleaseTime } + .minOrNull() + + // Count completed tickets (ticketCompleteDateTime is not null) + val completedTickets = tickets.count { it.ticketCompleteDateTime != null } + + // Find last ticket end time (latest ticketCompleteDateTime) + val lastTicketEndTime = tickets + .mapNotNull { it.ticketCompleteDateTime } + .maxOrNull() + + // Calculate pick time taken in minutes + val pickTimeTakenMinutes = if (firstTicketStartTime != null && lastTicketEndTime != null) { + ChronoUnit.MINUTES.between(firstTicketStartTime, lastTicketEndTime) + } else { + null + } + + // Get truck ID (use first non-null) + val truckId = tickets.firstOrNull { it.truckId != null }?.truckId + + TruckScheduleDashboardResponse( + storeId = storeId, + truckId = truckId, + truckLanceCode = truckLanceCode, + truckDepartureTime = truckDepartureTime, + numberOfShopsToServe = distinctShops, + numberOfPickTickets = distinctTickets, + totalItemsToPick = totalItems, + numberOfTicketsReleased = releasedTickets, + firstTicketStartTime = firstTicketStartTime, + numberOfTicketsCompleted = completedTickets, + lastTicketEndTime = lastTicketEndTime, + pickTimeTakenMinutes = pickTimeTakenMinutes + ) + }.sortedWith(compareBy({ it.storeId }, { it.truckDepartureTime })) + } + + private fun countFGItemsById(doPickOrderId: Long): Int { + val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrderId) + val pickOrderIds = doPickOrderLines.mapNotNull { it.pickOrderId }.distinct() + + if (pickOrderIds.isEmpty()) { + val doPickOrder = doPickOrderRepository.findById(doPickOrderId).orElse(null) + val directPickOrderId = doPickOrder?.pickOrderId + if (directPickOrderId != null) { + val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(directPickOrderId) + return pickOrderLines.size + } + return 0 + } + val allPickOrderLines = pickOrderIds.flatMap { pickOrderId -> + pickOrderLineRepository.findAllByPickOrderId(pickOrderId) + } + return allPickOrderLines.size + } + + private fun countFGItemsFromRecordById(recordId: Long): Int { + val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderIdAndDeletedFalse(recordId) + if (doPickOrderLineRecords.isEmpty()) { + return 0 + } + + val pickOrderIds = doPickOrderLineRecords.mapNotNull { it.pickOrderId }.distinct() + if (pickOrderIds.isEmpty()) { + return 0 + } + + val allPickOrderLines = pickOrderIds.flatMap { pickOrderId -> + pickOrderLineRepository.findAllByPickOrderId(pickOrderId) + } + + return allPickOrderLines.size + } }// 类结束 \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/TruckScheduleDashboardResponse.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/TruckScheduleDashboardResponse.kt new file mode 100644 index 0000000..04918e2 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/TruckScheduleDashboardResponse.kt @@ -0,0 +1,21 @@ +package com.ffii.fpsms.modules.deliveryOrder.web.models + +import java.time.LocalDateTime +import java.time.LocalTime + +data class TruckScheduleDashboardResponse( + val storeId: String?, + val truckId: Long?, + val truckLanceCode: String?, + val truckDepartureTime: LocalTime?, + val numberOfShopsToServe: Int, + val numberOfPickTickets: Int, + val totalItemsToPick: Int, + val numberOfTicketsReleased: Int, + val firstTicketStartTime: LocalDateTime?, + val numberOfTicketsCompleted: Int, + val lastTicketEndTime: LocalDateTime?, + val pickTimeTakenMinutes: Long? +) + +