rk
2 天以前 4c8535b9f263a3b398832b7a588abbdd5ebe38f4
server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
@@ -22,6 +22,8 @@
import com.doumee.dao.business.model.MemberRides;
import com.doumee.dao.business.model.MemberRidesTrack;
import com.doumee.dao.business.vo.BikeIncomeStatVO;
import com.doumee.dao.business.vo.BikeUsageStatVO;
import com.doumee.dao.business.vo.DashboardVO;
import com.doumee.dao.business.vo.IncomeDailyVO;
import com.doumee.dao.business.vo.IncomeStatVO;
import com.doumee.dao.business.vo.OperationCenterVO;
@@ -30,6 +32,7 @@
import com.doumee.dao.business.vo.OrderRideTrackVO;
import com.doumee.dao.business.vo.OrderRidesDetailVO;
import com.doumee.dao.business.vo.OverviewStatVO;
import com.doumee.dao.business.vo.PackageSourceStatVO;
import com.doumee.dao.business.web.request.BikeIncomeQueryDTO;
import com.doumee.dao.business.web.request.OperationOrderQueryDTO;
import com.doumee.service.business.ReportService;
@@ -41,6 +44,7 @@
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@@ -89,6 +93,9 @@
    /** 百分比基数(增长率 = (本期 - 对比期) / 对比期 × 100) */
    private static final BigDecimal PERCENT_BASE = new BigDecimal("100");
    /** 支付方式:抖音券核销(套餐销售来源识别用) */
    private static final int PAY_WAY_DOUYIN = 2;
    @Override
    public OverviewStatVO overview() {
@@ -617,4 +624,170 @@
        }
        return "未知";
    }
    @Override
    public IncomeStatVO incomeStat30() {
        // 近30天(含今天共30天),复用 incomeStat 既有实现与口径
        BikeIncomeQueryDTO query = new BikeIncomeQueryDTO();
        query.setDateType(3);
        return incomeStat(query);
    }
    @Override
    public BikeUsageStatVO bikeUsageStat() {
        // status:0 空闲 / 1 使用中;禁用(3)不计入;type:0 自行车 / 1 电动车
        BikeUsageStatVO vo = new BikeUsageStatVO();
        vo.setBikeIdle(countBikeByStatus(Constants.ZERO, Constants.ZERO));
        vo.setBikeInUse(countBikeByStatus(Constants.ZERO, Constants.ONE));
        vo.setEleBikeIdle(countBikeByStatus(Constants.ONE, Constants.ZERO));
        vo.setEleBikeInUse(countBikeByStatus(Constants.ONE, Constants.ONE));
        return vo;
    }
    /**
     * 按车辆类型 + 状态统计未删除车辆数量。
     *
     * @param type   车辆类型 0 自行车 / 1 电动车
     * @param status 车辆状态 0 空闲 / 1 使用中
     * @return 满足条件的车辆数
     */
    private long countBikeByStatus(Integer type, Integer status) {
        return bikesMapper.selectCount(
                new QueryWrapper<Bikes>().lambda()
                        .eq(Bikes::getType, type)
                        .eq(Bikes::getStatus, status)
                        .eq(Bikes::getIsdeleted, Constants.ZERO));
    }
    @Override
    public PackageSourceStatVO packageSourceStat() {
        // 本月/本年起止(pay_date 落在区间内);goodsorder type=1 套餐卡购买 + payStatus=1 已支付
        Calendar cal = Calendar.getInstance();
        Date monthStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.MONTH));
        Date monthEnd = DateUtil.getEndOfDay(new Date());
        Date yearStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.YEAR));
        Date yearEnd = DateUtil.getEndOfDay(new Date());
        PackageSourceStatVO vo = new PackageSourceStatVO();
        vo.setMonth(countPackageSource(monthStart, monthEnd));
        vo.setYear(countPackageSource(yearStart, yearEnd));
        return vo;
    }
    /**
     * 统计指定时段内套餐销售来源数量。
     * <p>抖音兑换 = payWay 2,小程序购买 = payWay 0(微信)。
     *
     * @param start 起始时间(含)
     * @param end   结束时间(含)
     * @return 抖音/小程序套餐数量
     */
    private PackageSourceStatVO.PeriodCount countPackageSource(Date start, Date end) {
        List<Goodsorder> orders = goodsorderMapper.selectList(
                new QueryWrapper<Goodsorder>().lambda()
                        .select(Goodsorder::getPayWay)
                        .eq(Goodsorder::getType, Constants.ONE)
                        .eq(Goodsorder::getPayStatus, Constants.ONE)
                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
                        .ge(Goodsorder::getPayDate, start)
                        .le(Goodsorder::getPayDate, end));
        long douyin = 0L;
        long mini = 0L;
        for (Goodsorder o : orders) {
            if (o.getPayWay() == null) {
                continue;
            }
            if (o.getPayWay() == PAY_WAY_DOUYIN) {
                douyin++;
            } else if (o.getPayWay() == Constants.ZERO) {
                mini++;
            }
        }
        PackageSourceStatVO.PeriodCount pc = new PackageSourceStatVO.PeriodCount();
        pc.setDouyinCount(douyin);
        pc.setMiniCount(mini);
        return pc;
    }
    @Override
    public DashboardVO dashboard() {
        DashboardVO vo = new DashboardVO();
        // 各时段起止:本月/昨日/今日
        Date now = new Date();
        Calendar cal = Calendar.getInstance();
        Date monthStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.MONTH));
        Date monthEnd = DateUtil.getEndOfDay(now);
        Date yesterdayStart = DateUtil.getStartOfDay(DateUtil.increaseDay(now, -1));
        Date yesterdayEnd = DateUtil.getEndOfDay(DateUtil.increaseDay(now, -1));
        Date todayStart = DateUtil.getStartOfDay(now);
        Date todayEnd = DateUtil.getEndOfDay(now);
        // 收益(口径同 incomeStat:type=0 押金 + status=4 已结算 的 closeMoney),复用 sumClosedMoney
        vo.setMonthIncome(sumClosedMoney(monthStart, monthEnd));
        vo.setYesterdayIncome(sumClosedMoney(yesterdayStart, yesterdayEnd));
        vo.setTodayIncome(sumClosedMoney(todayStart, todayEnd));
        // 订单数:已支付(payStatus=1)订单计数
        vo.setMonthOrderCount(countPaidOrders(monthStart, monthEnd));
        vo.setYesterdayOrderCount(countPaidOrders(yesterdayStart, yesterdayEnd));
        vo.setTodayOrderCount(countPaidOrders(todayStart, todayEnd));
        // 车辆:仅返回未删除总数
        vo.setTotalBikeCount((long) bikesMapper.selectCount(
                new QueryWrapper<Bikes>().lambda().eq(Bikes::getIsdeleted, Constants.ZERO)));
        // 客户数:总会员=未删除全部;今日/昨日新增按 create_date 落在对应区间(与 overview 口径一致)
        vo.setTotalMemberCount((long) memberMapper.selectCount(
                new QueryWrapper<Member>().lambda().eq(Member::getIsdeleted, Constants.ZERO)));
        vo.setYesterdayNewMember((long) memberMapper.selectCount(
                new QueryWrapper<Member>().lambda()
                        .eq(Member::getIsdeleted, Constants.ZERO)
                        .ge(Member::getCreateDate, yesterdayStart)
                        .le(Member::getCreateDate, yesterdayEnd)));
        vo.setTodayNewMember((long) memberMapper.selectCount(
                new QueryWrapper<Member>().lambda()
                        .eq(Member::getIsdeleted, Constants.ZERO)
                        .ge(Member::getCreateDate, todayStart)));
        return vo;
    }
    /**
     * 统计指定时段内已支付订单数量(payStatus=1)。
     *
     * @param start 起始时间(含)
     * @param end   结束时间(含)
     * @return 已支付订单数
     */
    private long countPaidOrders(Date start, Date end) {
        return goodsorderMapper.selectCount(
                new QueryWrapper<Goodsorder>().lambda()
                        .eq(Goodsorder::getPayStatus, Constants.ONE)
                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
                        .ge(Goodsorder::getPayDate, start)
                        .le(Goodsorder::getPayDate, end));
    }
    /**
     * 取本月/本年第一天的时间原值(时分秒归零前的毫秒),用于构造区间起始。
     * <p>用 Calendar 把「当月1日 00:00:00.000」或「当年1月1日 00:00:00.000」取出。
     *
     * @param cal   日历(会被修改)
     * @param field Calendar.MONTH 本月第一天 / Calendar.YEAR 本年第一天
     * @return 区间起始时间
     */
    private Date getFirstMs(Calendar cal, int field) {
        cal.setTime(new Date());
        if (field == Calendar.MONTH) {
            cal.set(Calendar.DAY_OF_MONTH, 1);
        } else {
            cal.set(Calendar.MONTH, Calendar.JANUARY);
            cal.set(Calendar.DAY_OF_MONTH, 1);
        }
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }
}