package com.tourguide.modules.dashboard.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.tourguide.modules.dashboard.dto.DashboardOrderTypeRow; import com.tourguide.modules.dashboard.dto.DashboardResponse; import com.tourguide.modules.dashboard.dto.DashboardScenicRow; import com.tourguide.modules.dashboard.mapper.DashboardMapper; import com.tourguide.modules.order.entity.TourOrder; import com.tourguide.modules.order.mapper.TourOrderMapper; import com.tourguide.modules.review.entity.OrderReview; import com.tourguide.modules.review.mapper.OrderReviewMapper; import com.tourguide.modules.user.entity.MiniUser; import com.tourguide.modules.user.mapper.MiniUserMapper; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service public class DashboardService { private final MiniUserMapper miniUserMapper; private final TourOrderMapper orderMapper; private final OrderReviewMapper reviewMapper; private final DashboardMapper dashboardMapper; public DashboardService(MiniUserMapper miniUserMapper, TourOrderMapper orderMapper, OrderReviewMapper reviewMapper, DashboardMapper dashboardMapper) { this.miniUserMapper = miniUserMapper; this.orderMapper = orderMapper; this.reviewMapper = reviewMapper; this.dashboardMapper = dashboardMapper; } public DashboardResponse overview() { long userCount = miniUserMapper.selectCount(new LambdaQueryWrapper()); long orderCount = orderMapper.selectCount(new LambdaQueryWrapper()); long reviewCount = reviewMapper.selectCount(new LambdaQueryWrapper()); BigDecimal dailyGrowth = calculateDailyOrderGrowth(); List trend = dashboardMapper.selectRevenueTrend().stream() .map(row -> new DashboardResponse.TrendPoint(row.getDateLabel(), defaultDecimal(row.getRevenue()))) .collect(Collectors.toList()); List scenicRanking = buildScenicRanking(dashboardMapper.selectScenicRanking()); List typeStats = buildOrderTypeStats(dashboardMapper.selectOrderTypeStats()); return new DashboardResponse(userCount, orderCount, reviewCount, dailyGrowth, defaultDecimal(dashboardMapper.selectTotalRevenue()), trend, scenicRanking, typeStats); } private BigDecimal calculateDailyOrderGrowth() { LocalDate today = LocalDate.now(); long todayCount = countOrders(today.atStartOfDay(), today.plusDays(1).atStartOfDay()); long yesterdayCount = countOrders(today.minusDays(1).atStartOfDay(), today.atStartOfDay()); if (yesterdayCount == 0) return todayCount == 0 ? BigDecimal.ZERO : new BigDecimal("100.0"); return BigDecimal.valueOf(todayCount - yesterdayCount) .multiply(new BigDecimal("100")) .divide(BigDecimal.valueOf(yesterdayCount), 1, RoundingMode.HALF_UP); } private long countOrders(LocalDateTime start, LocalDateTime end) { return orderMapper.selectCount(new LambdaQueryWrapper() .ge(TourOrder::getCreatedAt, start) .lt(TourOrder::getCreatedAt, end)); } private List buildScenicRanking(List rows) { List result = new ArrayList(); for (int index = 0; index < rows.size(); index++) { DashboardScenicRow row = rows.get(index); long today = row.getTodayCount() == null ? 0 : row.getTodayCount(); long yesterday = row.getYesterdayCount() == null ? 0 : row.getYesterdayCount(); BigDecimal growth = yesterday == 0 ? (today == 0 ? BigDecimal.ZERO : new BigDecimal("100.0")) : BigDecimal.valueOf(today - yesterday).multiply(new BigDecimal("100")) .divide(BigDecimal.valueOf(yesterday), 1, RoundingMode.HALF_UP); result.add(new DashboardResponse.ScenicRank(index + 1, row.getScenicName(), row.getOrderCount(), growth)); } return result; } private List buildOrderTypeStats(List rows) { long total = rows.stream().mapToLong(row -> row.getOrderCount() == null ? 0 : row.getOrderCount()).sum(); return rows.stream().map(row -> { long count = row.getOrderCount() == null ? 0 : row.getOrderCount(); BigDecimal percentage = total == 0 ? BigDecimal.ZERO : BigDecimal.valueOf(count).multiply(new BigDecimal("100")) .divide(BigDecimal.valueOf(total), 1, RoundingMode.HALF_UP); return new DashboardResponse.OrderTypeStat(row.getOrderType(), count, percentage); }).collect(Collectors.toList()); } private BigDecimal defaultDecimal(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } }