rk
2 天以前 4c8535b9f263a3b398832b7a588abbdd5ebe38f4
功能开发
已修改3个文件
223 ■■■■■ 文件已修改
server/services/src/main/java/com/doumee/service/business/ReportService.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/接口变更说明.txt 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/ReportService.java
@@ -3,11 +3,14 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
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.IncomeStatVO;
import com.doumee.dao.business.vo.OperationCenterVO;
import com.doumee.dao.business.vo.OperationOrderVO;
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;
@@ -68,4 +71,35 @@
     * @return éª‘行记录与轨迹详情(含车辆类型、是否有轨迹、骑行列表及每条下挂的轨迹点)
     */
    OrderRidesDetailVO orderRidesDetail(String orderId);
    /**
     * è¿‘30天收益统计(固定30天,无需入参):按日柱状图 + ç´¯è®¡ + çŽ¯æ¯”/同比。
     * <p>口径同 {@link #incomeStat},内部以 dateType=3(近30天)调用。
     *
     * @return è¿‘30天收益统计结果
     */
    IncomeStatVO incomeStat30();
    /**
     * è½¦è¾†ä½¿ç”¨æƒ…况:自行车/电动车各自的使用中(status=1)与空闲(status=0)数量。
     * <p>禁用车辆(status=3)不计入。
     *
     * @return è½¦è¾†ä½¿ç”¨æƒ…况
     */
    BikeUsageStatVO bikeUsageStat();
    /**
     * å¥—餐销售来源统计:本月/本年维度下,抖音兑换(payWay=2)与小程序购买(payWay=0)的套餐数。
     * <p>口径:goodsorder type=1 å¥—餐卡购买、payStatus=1 å·²æ”¯ä»˜ã€‚
     *
     * @return å¥—餐销售来源统计
     */
    PackageSourceStatVO packageSourceStat();
    /**
     * ç»¼åˆçœ‹æ¿:本月/昨日/今日收益与订单数、车辆总数/使用中/空闲,客户数相关暂搁置。
     *
     * @return ç»¼åˆçœ‹æ¿æ•°æ®
     */
    DashboardVO dashboard();
}
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();
    }
}
server/½Ó¿Ú±ä¸ü˵Ã÷.txt
@@ -29,6 +29,22 @@
  9. GET  /business/douyinProduct/{id}       æ ¹æ®ID查询(含SKU)
  10. POST /business/douyinProduct/bindDiscount  ç»‘定/解绑本地套餐
四、数据报表(新文件 ReportController,/business/report)
     é‰´æƒ:无 @RequiresPermissions,仅 Shiro ç™»å½•校验,登录后台即可访问。
     Service å¤ç”¨ services æ¨¡å— ReportService(与 web ç«¯ /web/report åŒæº)。
  11. GET /business/report/incomeStat30       è¿‘30天收益统计(按日柱状图+累计+环比/同比)
  12. GET /business/report/bikeUsageStat      è½¦è¾†ä½¿ç”¨æƒ…况(自行车/电动车 Ã— ä½¿ç”¨ä¸­/空闲;禁用车辆排除)
  13. GET /business/report/packageSourceStat  å¥—餐销售来源(本月/本年 Ã— æŠ–音兑换/小程序购买)
  14. GET /business/report/dashboard          ç»¼åˆçœ‹æ¿(本月/昨日/今日收益+订单数+车辆总数+客户数)
     å„接口出参字段:
     Â· incomeStat30       â†’ IncomeStatVO(dailyList/totalIncome/环比/同比),固定近30天,无入参
     Â· bikeUsageStat      â†’ BikeUsageStatVO:bikeIdle/bikeInUse/eleBikeIdle/eleBikeInUse
     Â· packageSourceStat  â†’ PackageSourceStatVO:month{douyinCount,miniCount} + year{douyinCount,miniCount}
     Â· dashboard          â†’ DashboardVO:monthIncome/yesterdayIncome/todayIncome(元)
                                   + monthOrderCount/yesterdayOrderCount/todayOrderCount
                                   + totalBikeCount + totalMemberCount/yesterdayNewMember/todayNewMember
============================================================