@@ -15,6 +15,8 @@ import com.viewsh.module.ops.dal.dataobject.statistics.OpsTrafficStatisticsDO;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO ;
import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper ;
import com.viewsh.module.ops.dal.mysql.statistics.OpsTrafficStatisticsMapper ;
import com.viewsh.module.ops.dal.dataobject.queue.OpsOrderQueueDO ;
import com.viewsh.module.ops.dal.mysql.queue.OpsOrderQueueMapper ;
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper ;
import jakarta.annotation.Resource ;
import lombok.extern.slf4j.Slf4j ;
@@ -28,8 +30,10 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter ;
import java.util.ArrayList ;
import java.util.HashMap ;
import java.util.LinkedHashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.Set ;
import java.util.stream.Collectors ;
/**
@@ -70,6 +74,9 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
@Resource
private OpsTrafficStatisticsMapper trafficStatisticsMapper ;
@Resource
private OpsOrderQueueMapper opsOrderQueueMapper ;
/**
* 在线员工数提供者(由 ops-server 层注入,从 environment-biz 获取工牌状态)
* 返回 [onlineCount, totalCount]
@@ -118,18 +125,23 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
// 7. 时效趋势(单次 GROUP BY 查询)
TimeTrendData timeTrendData = buildTimeTrendData ( orderType , startDate , endDate ) ;
// 8. 漏斗数据(基于指定时间范围的工单状态推算 )
List < FunnelItem > funnelData = buildFunnelData ( orderType , startDateTime , endDateTime ) ;
// 9. 热力图( 近7天)
// 近7天时间范围( 多个图表复用 )
LocalDate sevenDaysAgo = LocalDate . now ( ) . minusDays ( 6 ) ; // 包含今天共7天
LocalDateTime sevenDaysAgoStart = sevenDaysAgo . atStartOfDay ( ) ;
LocalDateTime nowEnd = LocalDate . now ( ) . plusDays ( 1 ) . atStartOfDay ( ) ;
// 8. 工单状态分布( 近7天所有工单的状态占比)
List < StatusDistributionItem > statusDistribution = buildStatusDistribution ( orderType , sevenDaysAgoStart , nowEnd ) ;
// 9. 热力图( 近7天)
HeatmapData heatmapData = buildHeatmapData ( orderType , sevenDaysAgoStart , nowEnd ) ;
// 10. 功能类型排行(单次 GROUP BY 查询)
List < FunctionTypeRankingItem > functionTypeRanking = buildFunctionTypeRanking ( orderType ) ;
// 11. 工牌队列统计( 近7天每天排队的工单数 = QUEUED 状态)
BadgeQueueStats badgeQueueStats = buildBadgeQueueStats ( orderType , sevenDaysAgo , LocalDate . now ( ) ) ;
return DashboardStatsRespVO . builder ( )
. pendingCount ( pendingCount )
. inProgressCount ( inProgressCount )
@@ -138,38 +150,33 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
. trendData ( trendData )
. hourlyDistribution ( hourlyDistribution )
. timeTrendData ( timeTrendData )
. funnelData ( funnelData )
. statusDistribution ( statusDistribution )
. heatmapData ( heatmapData )
. functionTypeRanking ( functionTypeRanking )
. badgeQueueStats ( badgeQueueStats )
. build ( ) ;
}
@Override
@Cacheable ( value = " ops:statistics:traffic#5m " , unless = " #result == null " )
public TrafficRealtimeRespVO getTrafficRealtime ( ) {
LocalDate today = LocalDate . now ( ) ;
LocalDateTime todayStart = today . atStartOfDay ( ) ;
LocalDateTime todayEnd = todayStart . plusDays ( 1 ) ;
@Cacheable ( value = " ops:statistics:traffic#5m " , key = " #date != null ? #date.toString() : T(java.time.LocalDate).now().toString() " , unless = " #result == null " )
public TrafficRealtimeRespVO getTrafficRealtime ( LocalDate date ) {
if ( date = = null ) {
date = LocalDate . now ( ) ;
}
LocalDateTime dayStart = date . atStartOfDay ( ) ;
LocalDateTime dayEnd = dayStart . plusDays ( 1 ) ;
// 从 ops_traffic_statistics 表查询今日 汇总(使用 ge + lt 避免边界重叠)
List < OpsTrafficStatisticsDO > to dayStats = trafficStatisticsMapper . selectList (
// 从 ops_traffic_statistics 表查询指定日期 汇总(使用 ge + lt 避免边界重叠)
List < OpsTrafficStatisticsDO > dayStats = trafficStatisticsMapper . selectList (
new LambdaQueryWrapperX < OpsTrafficStatisticsDO > ( )
. ge ( OpsTrafficStatisticsDO : : getStatHour , to dayStart)
. lt ( OpsTrafficStatisticsDO : : getStatHour , to dayEnd) ) ;
// 查询昨日数据(用于昨日小时趋势对比)
LocalDate yesterday = today . minusDays ( 1 ) ;
LocalDateTime yesterdayStart = yesterday . atStartOfDay ( ) ;
List < OpsTrafficStatisticsDO > yesterdayStats = trafficStatisticsMapper . selectList (
new LambdaQueryWrapperX < OpsTrafficStatisticsDO > ( )
. ge ( OpsTrafficStatisticsDO : : getStatHour , yesterdayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , todayStart ) ) ;
. ge ( OpsTrafficStatisticsDO : : getStatHour , dayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , dayEnd ) ) ;
// 加载区域名称映射
Map < Long , String > areaNameMap = loadAreaNameMap ( ) ;
// 按区域汇总
Map < Long , List < OpsTrafficStatisticsDO > > byArea = to dayStats. stream ( )
Map < Long , List < OpsTrafficStatisticsDO > > byArea = dayStats . stream ( )
. filter ( s - > s . getAreaId ( ) ! = null )
. collect ( Collectors . groupingBy ( OpsTrafficStatisticsDO : : getAreaId ) ) ;
@@ -196,17 +203,16 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
. build ( ) ) ;
}
// 小时趋势(今日 + 昨日)
HourlyTrend hourlyTrend = buildTrafficHourlyTrend ( to dayStats) ;
HourlyTrend yesterdayHourlyTrend = buildTrafficHourlyTrend ( yesterdayStats ) ;
// 小时趋势
HourlyTrend hourlyTrend = buildTrafficHourlyTrend ( dayStats ) ;
return TrafficRealtimeRespVO . builder ( )
. totalIn ( totalIn )
. totalOut ( totalOut )
. currentOccupancy ( Math . max ( 0 , totalIn - totalOut ) )
. areas ( areas )
. statDate ( date . toString ( ) )
. hourlyTrend ( hourlyTrend )
. yesterdayHourlyTrend ( yesterdayHourlyTrend )
. build ( ) ;
}
@@ -231,8 +237,11 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
}
}
// 2. 待处理工单数
Integer pendingCount = countByStatusAndType ( " PENDING " , o rderTy pe) ;
// 2. 待处理工单数(所有未取消、未完成的工单)
Integer pendingCount = Math . toIntExact ( opsO rderMap per . selectCount (
new LambdaQueryWrapperX < OpsOrderDO > ( )
. eq ( OpsOrderDO : : getOrderType , orderType )
. notIn ( OpsOrderDO : : getStatus , " COMPLETED " , " CANCELLED " ) ) ) ;
// 3. 平均响应时长(今日已完成的工单,使用 ge + lt 避免边界重叠)
List < OpsOrderDO > completedToday = opsOrderMapper . selectList (
@@ -314,14 +323,24 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
}
@Override
@Cacheable ( value = " ops:statistics:traffic-trend#5m " , unless = " #result == null " )
public TrafficTrendRespVO getTrafficTrend ( ) {
@Cacheable ( value = " ops:statistics:traffic-trend#5m " ,
key = " (#startDate != null ? #startDate.toString() : 'default') + ':' + (#endDate != null ? #endDate.toString() : 'default') " , unless = " #result == null " )
public TrafficTrendRespVO getTrafficTrend ( LocalDate startDate , LocalDate endDate ) {
LocalDate today = LocalDate . now ( ) ;
LocalDate sevenDaysAgo = today . minusDays ( 6 ) ; // 包含今天共7天
if ( startDate = = null ) {
startDate = today . minusDays ( 6 ) ;
}
if ( endDate = = null ) {
endDate = today ;
}
// 限制最大范围为31天
if ( java . time . temporal . ChronoUnit . DAYS . between ( startDate , endDate ) > 30 ) {
startDate = endDate . minusDays ( 30 ) ;
}
// 从 ops_traffic_statistics 表查询近7天的 客流数据
LocalDateTime startDateTime = sevenDaysAgo . atStartOfDay ( ) ;
LocalDateTime endDateTime = today . plusDays ( 1 ) . atStartOfDay ( ) ;
// 从 ops_traffic_statistics 表查询客流数据
LocalDateTime startDateTime = startDate . atStartOfDay ( ) ;
LocalDateTime endDateTime = endDate . plusDays ( 1 ) . atStartOfDay ( ) ;
List < OpsTrafficStatisticsDO > stats = trafficStatisticsMapper . selectList (
new LambdaQueryWrapperX < OpsTrafficStatisticsDO > ( )
@@ -352,7 +371,7 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
long totalIn = 0 ;
long totalOut = 0 ;
for ( LocalDate d = sevenDaysAgo ; ! d . isAfter ( today ) ; d = d . plusDays ( 1 ) ) {
for ( LocalDate d = startDate ; ! d . isAfter ( endDate ) ; d = d . plusDays ( 1 ) ) {
dates . add ( d . format ( DATE_FORMATTER ) ) ;
long dayIn = dailyInMap . getOrDefault ( d , 0L ) ;
long dayOut = dailyOutMap . getOrDefault ( d , 0L ) ;
@@ -389,15 +408,6 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
. ge ( OpsTrafficStatisticsDO : : getStatHour , todayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , todayEnd ) ) ;
// 查询昨日数据
LocalDate yesterday = today . minusDays ( 1 ) ;
LocalDateTime yesterdayStart = yesterday . atStartOfDay ( ) ;
List < OpsTrafficStatisticsDO > yesterdayStats = trafficStatisticsMapper . selectList (
new LambdaQueryWrapperX < OpsTrafficStatisticsDO > ( )
. eq ( OpsTrafficStatisticsDO : : getDeviceId , deviceId )
. ge ( OpsTrafficStatisticsDO : : getStatHour , yesterdayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , todayStart ) ) ;
long totalIn = stats . stream ( ) . mapToLong ( s - > s . getPeopleIn ( ) ! = null ? s . getPeopleIn ( ) : 0 ) . sum ( ) ;
long totalOut = stats . stream ( ) . mapToLong ( s - > s . getPeopleOut ( ) ! = null ? s . getPeopleOut ( ) : 0 ) . sum ( ) ;
@@ -406,7 +416,6 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
Map < Long , String > areaNameMap = loadAreaNameMap ( ) ;
HourlyTrend hourlyTrend = buildTrafficHourlyTrend ( stats ) ;
HourlyTrend yesterdayHourlyTrend = buildTrafficHourlyTrend ( yesterdayStats ) ;
return DeviceTrafficRealtimeRespVO . builder ( )
. deviceId ( deviceId )
@@ -415,8 +424,8 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
. todayIn ( totalIn )
. todayOut ( totalOut )
. currentOccupancy ( Math . max ( 0 , totalIn - totalOut ) )
. statDate ( today . toString ( ) )
. hourlyTrend ( hourlyTrend )
. yesterdayHourlyTrend ( yesterdayHourlyTrend )
. build ( ) ;
}
@@ -433,28 +442,14 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
. ge ( OpsTrafficStatisticsDO : : getStatHour , todayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , todayEnd ) ) ;
// 查询昨日数据
LocalDate yesterday = today . minusDays ( 1 ) ;
LocalDateTime yesterdayStart = yesterday . atStartOfDay ( ) ;
List < OpsTrafficStatisticsDO > yesterdayStats = trafficStatisticsMapper . selectList (
new LambdaQueryWrapperX < OpsTrafficStatisticsDO > ( )
. eq ( OpsTrafficStatisticsDO : : getAreaId , areaId )
. ge ( OpsTrafficStatisticsDO : : getStatHour , yesterdayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , todayStart ) ) ;
Map < Long , String > areaNameMap = loadAreaNameMap ( ) ;
String areaName = areaNameMap . getOrDefault ( areaId , " 未知区域 " ) ;
// 按设备分组(今日)
// 按设备分组
Map < Long , List < OpsTrafficStatisticsDO > > byDevice = stats . stream ( )
. filter ( s - > s . getDeviceId ( ) ! = null )
. collect ( Collectors . groupingBy ( OpsTrafficStatisticsDO : : getDeviceId ) ) ;
// 按设备分组(昨日)
Map < Long , List < OpsTrafficStatisticsDO > > yesterdayByDevice = yesterdayStats . stream ( )
. filter ( s - > s . getDeviceId ( ) ! = null )
. collect ( Collectors . groupingBy ( OpsTrafficStatisticsDO : : getDeviceId ) ) ;
List < DeviceTrafficRealtimeRespVO > result = new ArrayList < > ( ) ;
for ( Map . Entry < Long , List < OpsTrafficStatisticsDO > > entry : byDevice . entrySet ( ) ) {
Long deviceId = entry . getKey ( ) ;
@@ -464,8 +459,6 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
long deviceOut = deviceStats . stream ( ) . mapToLong ( s - > s . getPeopleOut ( ) ! = null ? s . getPeopleOut ( ) : 0 ) . sum ( ) ;
HourlyTrend hourlyTrend = buildTrafficHourlyTrend ( deviceStats ) ;
HourlyTrend yesterdayHourlyTrend = buildTrafficHourlyTrend (
yesterdayByDevice . getOrDefault ( deviceId , List . of ( ) ) ) ;
result . add ( DeviceTrafficRealtimeRespVO . builder ( )
. deviceId ( deviceId )
@@ -474,8 +467,8 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
. todayIn ( deviceIn )
. todayOut ( deviceOut )
. currentOccupancy ( Math . max ( 0 , deviceIn - deviceOut ) )
. statDate ( today . toString ( ) )
. hourlyTrend ( hourlyTrend )
. yesterdayHourlyTrend ( yesterdayHourlyTrend )
. build ( ) ) ;
}
return result ;
@@ -509,8 +502,8 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
@Override
@Cacheable ( value = " ops:statistics:area-traffic-realtime#5m " ,
key = " T(String).join(',', #areaIds.![toString()]) " , unless = " #result == null " )
public TrafficRealtimeRespVO getAreaTrafficRealtime ( List < Long > areaIds ) {
key = " T(String).join(',', #areaIds.![toString()]) + ':' + (#date != null ? #date.toString() : T(java.time.LocalDate).now().toString()) " , unless = " #result == null " )
public TrafficRealtimeRespVO getAreaTrafficRealtime ( List < Long > areaIds , LocalDate date ) {
if ( areaIds = = null | | areaIds . isEmpty ( ) ) {
return TrafficRealtimeRespVO . builder ( )
. totalIn ( 0L ) . totalOut ( 0L ) . currentOccupancy ( 0L )
@@ -521,42 +514,36 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
if ( areaIds . size ( ) > 200 ) {
areaIds = areaIds . subList ( 0 , 200 ) ;
}
if ( date = = null ) {
date = LocalDate . now ( ) ;
}
LocalDate to day = LocalD ate. now ( ) ;
LocalDateTime to dayStart = to day. atStartOf Day( ) ;
LocalDateTime todayEnd = todayStart . plusDays ( 1 ) ;
LocalDateTime dayStart = d ate. atStartOfDay ( ) ;
LocalDateTime dayEnd = dayStart . plus Days ( 1 ) ;
// 查询今日 数据,条件 areaId IN (areaIds)
List < OpsTrafficStatisticsDO > to dayStats = trafficStatisticsMapper . selectList (
// 查询指定日期 数据,条件 areaId IN (areaIds)
List < OpsTrafficStatisticsDO > dayStats = trafficStatisticsMapper . selectList (
new LambdaQueryWrapperX < OpsTrafficStatisticsDO > ( )
. in ( OpsTrafficStatisticsDO : : getAreaId , areaIds )
. ge ( OpsTrafficStatisticsDO : : getStatHour , to dayStart)
. lt ( OpsTrafficStatisticsDO : : getStatHour , to dayEnd) ) ;
// 查询昨日数据
LocalDate yesterday = today . minusDays ( 1 ) ;
LocalDateTime yesterdayStart = yesterday . atStartOfDay ( ) ;
List < OpsTrafficStatisticsDO > yesterdayStats = trafficStatisticsMapper . selectList (
new LambdaQueryWrapperX < OpsTrafficStatisticsDO > ( )
. in ( OpsTrafficStatisticsDO : : getAreaId , areaIds )
. ge ( OpsTrafficStatisticsDO : : getStatHour , yesterdayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , todayStart ) ) ;
. ge ( OpsTrafficStatisticsDO : : getStatHour , dayStart )
. lt ( OpsTrafficStatisticsDO : : getStatHour , dayEnd ) ) ;
// 无数据时设置提示信息
if ( todayStats . isEmpty ( ) & & yester dayStats. isEmpty ( ) ) {
if ( dayStats . isEmpty ( ) ) {
return TrafficRealtimeRespVO . builder ( )
. totalIn ( 0L ) . totalOut ( 0L ) . currentOccupancy ( 0L )
. statDate ( date . toString ( ) )
. message ( " 该区域暂未配置客流设备 " )
. build ( ) ;
}
// 汇总 totalIn、totalOut
long totalIn = to dayStats. stream ( ) . mapToLong ( s - > s . getPeopleIn ( ) ! = null ? s . getPeopleIn ( ) : 0 ) . sum ( ) ;
long totalOut = to dayStats. stream ( ) . mapToLong ( s - > s . getPeopleOut ( ) ! = null ? s . getPeopleOut ( ) : 0 ) . sum ( ) ;
long totalIn = dayStats . stream ( ) . mapToLong ( s - > s . getPeopleIn ( ) ! = null ? s . getPeopleIn ( ) : 0 ) . sum ( ) ;
long totalOut = dayStats . stream ( ) . mapToLong ( s - > s . getPeopleOut ( ) ! = null ? s . getPeopleOut ( ) : 0 ) . sum ( ) ;
// 按区域汇总明细
Map < Long , String > areaNameMap = loadAreaNameMap ( ) ;
Map < Long , List < OpsTrafficStatisticsDO > > byArea = to dayStats. stream ( )
Map < Long , List < OpsTrafficStatisticsDO > > byArea = dayStats . stream ( )
. filter ( s - > s . getAreaId ( ) ! = null )
. collect ( Collectors . groupingBy ( OpsTrafficStatisticsDO : : getAreaId ) ) ;
@@ -579,16 +566,15 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
}
// 小时趋势
HourlyTrend hourlyTrend = buildTrafficHourlyTrend ( to dayStats) ;
HourlyTrend yesterdayHourlyTrend = buildTrafficHourlyTrend ( yesterdayStats ) ;
HourlyTrend hourlyTrend = buildTrafficHourlyTrend ( dayStats ) ;
return TrafficRealtimeRespVO . builder ( )
. totalIn ( totalIn )
. totalOut ( totalOut )
. currentOccupancy ( Math . max ( 0 , totalIn - totalOut ) )
. areas ( areas )
. statDate ( date . toString ( ) )
. hourlyTrend ( hourlyTrend )
. yesterdayHourlyTrend ( yesterdayHourlyTrend )
. build ( ) ;
}
@@ -743,48 +729,42 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
}
/**
* 构建漏斗数据(统计指定时间范围内的工单)
* 构建工单状态分布数据 -- SQL GROUP BY 聚合,避免大量数据加载到内存
*/
private List < FunnelItem > buildFunnelData ( String orderType , LocalDateTime startDateTime , LocalDateTime endDateTime ) {
// 创建工单 = 时间范围内创建的所有非取消工单
long totalCreated = opsOrderMapper . selectCount (
new LambdaQueryWrapperX < OpsOrderDO > ( )
. eq ( OpsOrderDO : : getOrderType , orderType )
. ne ( OpsOrderDO : : getStatus , " CANCELLED " )
. ge ( OpsOrderDO : : getCreateTime , startDateTime )
. l t( OpsOrderDO : : getCreateTime , endDateTime ) ) ;
private List < StatusDistributionItem > buildStatusDistribution ( String orderType ,
LocalDateTime startDateTime ,
LocalDateTime endDateTime ) {
Map < String , String > statusLabelMap = new LinkedHashMap < > ( ) ;
statusLabelMap . put ( " PENDING " , " 待处理 " ) ;
statusLabelMap . put ( " QUEUED " , " 排队中 " ) ;
statusLabelMap . put ( " DISPATCHED " , " 已派单 " ) ;
statusLabelMap . pu t( " ARRIVED " , " 已到岗 " ) ;
statusLabelMap . put ( " COMPLETED " , " 已完成 " ) ;
statusLabelMap . put ( " CANCELLED " , " 已取消 " ) ;
statusLabelMap . put ( " PAUSED " , " 已暂停 " ) ;
// 已分配 = 时间范围内创建且已进入工作流程的工单(排除 PENDING 和 CANCELLED )
long assigned = opsOrderMapper . selectCount (
new LambdaQueryWrapperX < OpsOrderDO > ( )
. eq ( OpsOrderDO : : get OrderTy pe , orderType )
. notIn ( OpsOrderDO : : getStatus , " PENDING " , " CANCELLED " )
. g e( OpsOrderDO : : getCreateTime , startDateTim e )
. lt ( OpsOrderDO : : getCreateTime , endDateTime ) ) ;
// 使用 selectCount 按状态逐个查询( 状态数量有限, 最多7次查询, 避免大量数据加载到内存 )
Map < String , Long > statusCountMap = new HashMap < > ( ) ;
for ( String status : statusLabelMap . keySet ( ) ) {
Long count = ops OrderMap per . selectCount (
new LambdaQueryWrapperX < OpsOrderDO > ( )
. eq ( OpsOrderDO : : getOrderType , orderTyp e )
. eq ( OpsOrderDO : : getStatus , status )
. ge ( OpsOrderDO : : getCreateTime , startDateTime )
. lt ( OpsOrderDO : : getCreateTime , endDateTime ) ) ;
if ( count > 0 ) {
statusCountMap . put ( status , count ) ;
}
}
// 已到岗 = 时间范围内创建且已到岗的工单( ARRIVED + COMPLETED + PAUSED)
long arrived = opsOrderMapper . selectCount (
new LambdaQueryWrapperX < OpsOrderDO > ( )
. eq ( OpsOrderDO : : getOrderType , orderType )
. in ( OpsOrderDO : : getStatus , " ARRIVED " , " COMPLETED " , " PAUSED " )
. ge ( OpsOrderDO : : getCreateTime , startDateTime )
. lt ( OpsOrderDO : : getCreateTime , endDateTime ) ) ;
// 已完成 = 时间范围内创建且已完成的工单
long completed = opsOrderMapper . selectCount (
new LambdaQueryWrapperX < OpsOrderDO > ( )
. eq ( OpsOrderDO : : getStatus , " COMPLETED " )
. eq ( OpsOrderDO : : getOrderType , orderType )
. ge ( OpsOrderDO : : getCreateTime , startDateTime )
. lt ( OpsOrderDO : : getCreateTime , endDateTime ) ) ;
List < FunnelItem > funnelData = new ArrayList < > ( ) ;
funnelData . add ( FunnelItem . builder ( ) . name ( " 创建工单 " ) . value ( ( int ) totalCreated ) . build ( ) ) ;
funnelData . add ( FunnelItem . builder ( ) . name ( " 已分配 " ) . value ( ( int ) assigned ) . build ( ) ) ;
funnelData . add ( FunnelItem . builder ( ) . name ( " 已到岗 " ) . value ( ( int ) arrived ) . build ( ) ) ;
funnelData . add ( FunnelItem . builder ( ) . name ( " 已完成 " ) . value ( ( int ) completed ) . build ( ) ) ;
return funnelData ;
List < StatusDistributionItem > result = new ArrayList < > ( ) ;
for ( Map . Entry < String , String > entry : statusLabelMap . entrySet ( ) ) {
Long count = statusCountMap . get ( entry . getKey ( ) ) ;
if ( count ! = null & & count > 0 ) {
result . add ( StatusDistributionItem . builder ( ) . name ( entry . getValue ( ) ) . value ( count . intValue ( ) ) . build ( ) ) ;
}
}
return result ;
}
/**
@@ -883,6 +863,61 @@ public class OpsStatisticsServiceImpl implements OpsStatisticsService {
. collect ( Collectors . toList ( ) ) ;
}
/**
* 构建工牌队列统计( 近7天每天入队的工单数, 查 ops_order_queue 表)
* 通过关联 ops_order 表按 orderType 过滤
*/
private BadgeQueueStats buildBadgeQueueStats ( String orderType , LocalDate startDate , LocalDate endDate ) {
LocalDateTime startDateTime = startDate . atStartOfDay ( ) ;
LocalDateTime endDateTime = endDate . plusDays ( 1 ) . atStartOfDay ( ) ;
// 先查询该类型在时间范围内的工单ID集合
List < OpsOrderDO > orders = opsOrderMapper . selectList (
new LambdaQueryWrapperX < OpsOrderDO > ( )
. select ( OpsOrderDO : : getId )
. eq ( OpsOrderDO : : getOrderType , orderType )
. ge ( OpsOrderDO : : getCreateTime , startDateTime )
. lt ( OpsOrderDO : : getCreateTime , endDateTime ) ) ;
Set < Long > orderIds = orders . stream ( )
. map ( OpsOrderDO : : getId )
. collect ( Collectors . toSet ( ) ) ;
if ( orderIds . isEmpty ( ) ) {
// 无工单,直接返回空数据
List < String > dates = new ArrayList < > ( ) ;
List < Integer > queueData = new ArrayList < > ( ) ;
for ( LocalDate d = startDate ; ! d . isAfter ( endDate ) ; d = d . plusDays ( 1 ) ) {
dates . add ( d . format ( DATE_FORMATTER ) ) ;
queueData . add ( 0 ) ;
}
return BadgeQueueStats . builder ( ) . dates ( dates ) . queueData ( queueData ) . build ( ) ;
}
// 查询队列记录, 按工单ID过滤
List < OpsOrderQueueDO > queueRecords =
opsOrderQueueMapper . selectList (
new LambdaQueryWrapperX < OpsOrderQueueDO > ( )
. in ( OpsOrderQueueDO : : getOpsOrderId , orderIds )
. ge ( OpsOrderQueueDO : : getEnqueueTime , startDateTime )
. lt ( OpsOrderQueueDO : : getEnqueueTime , endDateTime ) ) ;
Map < String , Integer > dailyQueueMap = queueRecords . stream ( )
. filter ( q - > q . getEnqueueTime ( ) ! = null )
. collect ( Collectors . groupingBy (
q - > q . getEnqueueTime ( ) . toLocalDate ( ) . toString ( ) ,
Collectors . summingInt ( q - > 1 )
) ) ;
List < String > dates = new ArrayList < > ( ) ;
List < Integer > queueData = new ArrayList < > ( ) ;
for ( LocalDate d = startDate ; ! d . isAfter ( endDate ) ; d = d . plusDays ( 1 ) ) {
dates . add ( d . format ( DATE_FORMATTER ) ) ;
queueData . add ( dailyQueueMap . getOrDefault ( d . toString ( ) , 0 ) ) ;
}
return BadgeQueueStats . builder ( ) . dates ( dates ) . queueData ( queueData ) . build ( ) ;
}
/**
* 构建客流小时趋势
*/