刘帅
2026-09-21 620a310bdcfb1d8f1b49bf163413afeb522e1b2f
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java
@@ -82,6 +82,101 @@
    }
    /**
     * 将门店统计目标限制在当前账号可见范围内。门店账号固定统计自身;运营商账号
     * 只能统计归属自己的门店,避免其它门店的数据混入其统计结果。
     */
    private Integer resolveStatisticsStoreId(Integer storeId) {
        Integer objectType = UserExt.getUser().getObjectType();
        if (Integer.valueOf(3).equals(objectType)) {
            return UserExt.getUser().getObjectId();
        }
        if (storeId == null) {
            return null;
        }
        TStore store = storeService.getById(storeId);
        if (store == null || Integer.valueOf(3).equals(store.getState())) {
            return null;
        }
        if (Integer.valueOf(2).equals(objectType)
                && !UserExt.getUser().getObjectId().equals(store.getOperatorId())) {
            return null;
        }
        return storeId;
    }
    /** 平台(0)或运营商统计均以门店的 operatorId 作为唯一归属依据。 */
    private List<TStore> listStatisticsStores(Integer operatorId, boolean enabledOnly) {
        LambdaQueryWrapper<TStore> wrapper = new LambdaQueryWrapper<>();
        if (enabledOnly) {
            wrapper.eq(TStore::getState, 1);
        } else {
            wrapper.ne(TStore::getState, 3);
        }
        if (operatorId == null) {
            // 平台统计:所有门店总和,不按门店所属运营商过滤。
        } else {
            wrapper.eq(TStore::getOperatorId, operatorId);
        }
        return storeService.list(wrapper);
    }
    /** 空门店范围必须显式传递无匹配值,不能让下游 SQL 因空集合而省略 IN 条件。 */
    private List<Integer> listStatisticsStoreIds(Integer operatorId, boolean enabledOnly) {
        List<Integer> storeIds = listStatisticsStores(operatorId, enabledOnly).stream()
                .map(TStore::getId).collect(Collectors.toList());
        return storeIds.isEmpty() ? Collections.singletonList(-1) : storeIds;
    }
    /** 获取统计范围门店发生课程、订场或赛事消费的用户。 */
    private List<Integer> listStatisticsAppUserIds(Integer operatorId) {
        List<Integer> storeIds = listStatisticsStores(operatorId, true).stream()
                .map(TStore::getId).collect(Collectors.toList());
        if (storeIds.isEmpty()) {
            return Collections.singletonList(-1);
        }
        List<Integer> userIds = new ArrayList<>();
        List<Integer> courseUserIds = coursePackagePaymentClient.queryPaymentCoursePackage(storeIds);
        if (courseUserIds != null) {
            userIds.addAll(courseUserIds);
        }
        userIds.addAll(siteBookingService.list(new QueryWrapper<SiteBooking>()
                        .in("storeId", storeIds).eq("state", 1))
                .stream().map(SiteBooking::getAppUserId).filter(Objects::nonNull)
                .collect(Collectors.toList()));
        List<Integer> competitionUserIds = competitionClient.queryAppUserId(storeIds);
        if (competitionUserIds != null) {
            userIds.addAll(competitionUserIds);
        }
        List<Integer> validUserIds = userIds.stream().filter(Objects::nonNull).distinct()
                .collect(Collectors.toList());
        return validUserIds.isEmpty() ? Collections.singletonList(-1) : validUserIds;
    }
    private List<Integer> listStatisticsAppUserIdsForStore(Integer storeId) {
        if (storeId == null) {
            return Collections.singletonList(-1);
        }
        List<Integer> userIds = new ArrayList<>();
        List<Integer> courseUserIds = coursePackagePaymentClient.queryPaymentCoursePackage(
                Collections.singletonList(storeId));
        if (courseUserIds != null) {
            userIds.addAll(courseUserIds);
        }
        userIds.addAll(siteBookingService.list(new QueryWrapper<SiteBooking>()
                        .eq("storeId", storeId).eq("state", 1))
                .stream().map(SiteBooking::getAppUserId).filter(Objects::nonNull)
                .collect(Collectors.toList()));
        List<Integer> competitionUserIds = competitionClient.queryAppUserId(
                Collections.singletonList(storeId));
        if (competitionUserIds != null) {
            userIds.addAll(competitionUserIds);
        }
        List<Integer> validUserIds = userIds.stream().filter(Objects::nonNull).distinct()
                .collect(Collectors.toList());
        return validUserIds.isEmpty() ? Collections.singletonList(-1) : validUserIds;
    }
    /**
     * 计算门店每日营业整小时数。相同的起止时间表示全天营业,结束时间早于
     * 开始时间时按跨午夜营业处理。
     */
@@ -159,8 +254,18 @@
     */
    @RequestMapping("/platform")
    public String index(Model model) {
        Object o = type1(null);
        model.addAttribute("data",o);
        // type1 保留课程/门票/订场扇形图所需的图表明细;金额卡片再以营收接口覆盖,
        // 避免图表数据丢失,也避免收入类型与净收入的统计口径不一致。
        Map<String, Object> typeData = (Map<String, Object>) type1(null);
        Map<String, Object> incomeData = (Map<String, Object>) getIncomeData(null, null, null);
        Map<Integer, BigDecimal> incomeTypeNet = (Map<Integer, BigDecimal>) incomeData.get("incomeTypeNet");
        typeData.put("fee1", incomeTypeNet.getOrDefault(1, BigDecimal.ZERO));
        typeData.put("fee2", incomeTypeNet.getOrDefault(2, BigDecimal.ZERO));
        typeData.put("fee3", incomeTypeNet.getOrDefault(3, BigDecimal.ZERO));
        typeData.put("fee4", incomeTypeNet.getOrDefault(4, BigDecimal.ZERO));
        typeData.put("fee5", incomeTypeNet.getOrDefault(5, BigDecimal.ZERO));
        typeData.put("fee7", incomeTypeNet.getOrDefault(6, BigDecimal.ZERO));
        model.addAttribute("data", typeData);
        // 教学统计
        HashMap<String, Object> map = appUserClient.queryUserAge();
        model.addAttribute("stuData",map);
@@ -173,19 +278,51 @@
     */
    @RequestMapping(value = "/getIncomeData")
    @ResponseBody
    public Object getIncomeData(Integer operatorId,Integer storeId) {
//        operatorId = resolveOperatorId(operatorId);
//        storeId = resolveStoreId(operatorId);
    public Object getIncomeData(Integer operatorId,Integer storeId, String time) {
        Integer roleType = UserExt.getUser().getObjectType();
        Integer objectId = UserExt.getUser().getObjectId();
        List<Integer> cityManagerId= new ArrayList<>();
        if(objectId>0){
            operatorId=objectId;
            if(storeId==null){
                cityManagerId= storeService.list(new QueryWrapper<TStore>().eq("cityManagerId", objectId)).stream().map(TStore::getId).collect(Collectors.toList());
            }else {
                cityManagerId.add(storeId);
            }
        // 同一接口由平台、运营商、门店三个统计页面复用:
        // 平台角色不应因自身 objectId 被错误收窄为某个运营商;运营商和门店只能查看各自范围。
        if (Integer.valueOf(3).equals(roleType)) {
            operatorId = null;
            storeId = objectId;
        } else if (Integer.valueOf(2).equals(roleType)) {
            operatorId = objectId;
            storeId = resolveStoreId(storeId);
        }
        // 下游多处查询会在门店集合为空时忽略门店过滤。运营商没有任何门店时,
        // 直接返回完整的零值结构,避免被误统计为全平台数据。
        if (operatorId != null && storeId == null
                && storeService.count(new LambdaQueryWrapper<TStore>()
                        .eq(TStore::getOperatorId, operatorId)
                        .ne(TStore::getState, 3)) == 0) {
            Map<String, Object> emptyIncomeData = buildIncomeStatistics(Collections.emptyList());
            emptyIncomeData.put("incomeTypeNet", buildIncomeTypeNet(Collections.emptyList(), null));
            Map<String, Object> emptyTicketIncome = new HashMap<>();
            emptyTicketIncome.put("day", BigDecimal.ZERO);
            emptyTicketIncome.put("month", BigDecimal.ZERO);
            emptyTicketIncome.put("quarter", BigDecimal.ZERO);
            emptyTicketIncome.put("year", BigDecimal.ZERO);
            emptyIncomeData.put("ticketIncome", emptyTicketIncome);
            return emptyIncomeData;
        }
        // 参数代表当前统计目标,而非当前登录人的角色;平台账号查看运营商/门店页时,
        // 会员、充值也必须跟随被查看的运营商/门店归属。
        Integer statisticsScopeType = Integer.valueOf(3).equals(roleType) || storeId != null ? 3
                : (Integer.valueOf(2).equals(roleType) || operatorId != null ? 2 : 1);
        Integer statisticsScopeObjectId = statisticsScopeType == 3 ? storeId
                : (statisticsScopeType == 2 ? operatorId : null);
        List<Integer> cityManagerId= new ArrayList<>();
        if (Integer.valueOf(2).equals(statisticsScopeType) && statisticsScopeObjectId != null) {
            cityManagerId = storeService.list(new QueryWrapper<TStore>()
                            .eq("cityManagerId", statisticsScopeObjectId).ne("state", 3))
                    .stream().map(TStore::getId).collect(Collectors.toList());
        } else if (Integer.valueOf(3).equals(statisticsScopeType) && statisticsScopeObjectId != null) {
            cityManagerId.add(statisticsScopeObjectId);
        }
        // 报名课程查询query
        RegisterOrderQuery query = new RegisterOrderQuery();
@@ -207,7 +344,7 @@
        // 根据运营商ID 查询运营商下的所有门店
        if (storeId==null){
            if (operatorId!=null){
                List<TStore> stores = storeService.list(new QueryWrapper<TStore>().eq("operatorId", operatorId));
                List<TStore> stores = listStatisticsStores(operatorId, false);
                // 拿到该运营商下的门店id集合
                storeIds = stores.stream().map(TStore::getId).collect(Collectors.toList());
                query.setStoresIds(storeIds);
@@ -225,14 +362,20 @@
            siteBookingQuery.setStoreIds(list);
            competitionQuery.setStoreId(storeId);
        }
        Integer roleType = UserExt.getUser().getObjectType();
        rechargeRecordsQuery.setInsertType(roleType);
                       query.setInsertType(roleType);
            competitionQuery.setInsertType(roleType);
                 incomeQuery.setInsertType(roleType);
        rechargeRecordsQuery.setObjectId(UserExt.getUser().getObjectId());
        // 会员、充值按当前统计目标的用户创建来源归属。平台全量统计仅按 insertType=1,
        // 不能叠加当前平台账号的 addUserId 条件。
        rechargeRecordsQuery.setInsertType(statisticsScopeType);
                       query.setInsertType(statisticsScopeType);
            competitionQuery.setInsertType(statisticsScopeType);
                 incomeQuery.setInsertType(statisticsScopeType);
        rechargeRecordsQuery.setTime(time);
        query.setRegisterTime(time);
        competitionQuery.setTime(time);
        incomeQuery.setTime(time);
        siteBookingQuery.setTime(time);
        if (!Integer.valueOf(1).equals(statisticsScopeType)) {
            rechargeRecordsQuery.setObjectId(statisticsScopeObjectId);
        }
        List<IncomeVO> incomeVOS = new ArrayList<>();
        Set<Long> usedCouponIds = new HashSet<>();
        // 会员支付记录
@@ -240,16 +383,19 @@
        for (VipPayment vipPayment : vipPayments) {
            if (vipPayment.getPayStatus() == 3 && vipPayment.getAmount() != null) {
                IncomeVO payment = new IncomeVO();
                payment.setIncomeType(1);
                payment.setInsertTime(vipPayment.getPayTime() == null ? vipPayment.getInsertTime() : vipPayment.getPayTime());
                payment.setAmount(BigDecimal.valueOf(vipPayment.getAmount()));
                incomeVOS.add(payment);
                IncomeVO refund = new IncomeVO();
                refund.setIncomeType(1);
                refund.setInsertTime(vipPayment.getRefundTime() == null ? vipPayment.getInsertTime() : vipPayment.getRefundTime());
                refund.setRefundAmount(BigDecimal.valueOf(vipPayment.getAmount()));
                incomeVOS.add(refund);
                continue;
            }
            IncomeVO temp = new IncomeVO();
            temp.setIncomeType(1);
            if (vipPayment.getState()!=3){
                temp.setInsertTime(vipPayment.getPayTime());
                incomeVOS.add(temp);
@@ -262,6 +408,7 @@
        List<RechargeRecordsVO> rechargeRecordsVOS = financeClient.rechargeList(rechargeRecordsQuery);
        for (RechargeRecordsVO rechargeRecordsVO : rechargeRecordsVOS) {
            IncomeVO temp = new IncomeVO();
            temp.setIncomeType(2);
            temp.setInsertTime(rechargeRecordsVO.getPayTime());
            if (rechargeRecordsVO.getAmount() != null) {
                temp.setAmount(rechargeRecordsVO.getAmount());
@@ -277,6 +424,7 @@
                continue;
            }
            IncomeVO temp = new IncomeVO();
            temp.setIncomeType(3);
            temp.setInsertTime(registerOrderVO.getInsertTime());
            if (registerOrderVO.getMoney() != null) {
                temp.setAmount(registerOrderVO.getMoney());
@@ -304,11 +452,13 @@
            }
            if (competition.getPayStatus() == 3 && competition.getAmount() != null) {
                IncomeVO payment = new IncomeVO();
                payment.setIncomeType(4);
                payment.setInsertTime(competition.getPayTime() == null
                        ? competition.getInsertTime() : competition.getPayTime());
                payment.setAmount(BigDecimal.valueOf(competition.getAmount()));
                incomeVOS.add(payment);
                IncomeVO refund = new IncomeVO();
                refund.setIncomeType(4);
                refund.setInsertTime(competition.getRefundTime() == null
                        ? competition.getInsertTime() : competition.getRefundTime());
                refund.setRefundAmount(BigDecimal.valueOf(competition.getAmount()));
@@ -319,6 +469,7 @@
                continue;
            }
            IncomeVO temp = new IncomeVO();
            temp.setIncomeType(4);
            temp.setInsertTime(competition.getPayTime());
            if (competition.getAmount() != null) {
                temp.setAmount(new BigDecimal(competition.getAmount().toString()));
@@ -332,14 +483,17 @@
            List<SiteBooking> siteBookings = siteClient.listAll(siteBookingQuery);
            for (SiteBooking siteBooking : siteBookings) {
                IncomeVO temp = new IncomeVO();
                temp.setIncomeType(5);
                if (siteBooking.getStatus() == 5) {
                    if (siteBooking.getPayMoney() != null) {
                        IncomeVO payment = new IncomeVO();
                        payment.setIncomeType(5);
                        payment.setInsertTime(siteBooking.getPayTime() == null
                                ? siteBooking.getInsertTime() : siteBooking.getPayTime());
                        payment.setAmount(BigDecimal.valueOf(siteBooking.getPayMoney()));
                        incomeVOS.add(payment);
                        IncomeVO refund = new IncomeVO();
                        refund.setIncomeType(5);
                        refund.setInsertTime(siteBooking.getCancelTime() == null
                                ? siteBooking.getInsertTime() : siteBooking.getCancelTime());
                        refund.setRefundAmount(BigDecimal.valueOf(siteBooking.getPayMoney()));
@@ -363,12 +517,9 @@
            }
        }
        Map<Long, BigDecimal> couponDeductionAmounts = getCouponDeductionAmounts(usedCouponIds);
        applyCouponDeductions(incomeVOS, couponDeductionAmounts);
        // 惠民卡和优惠券购买不归属到单一课程/场地门店,当前仅在平台或运营商汇总中计入。
        if (storeId == null) {
            appendHuiminIncome(incomeVOS, operatorId);
            appendHuiminIncome(incomeVOS, operatorId, usedCouponIds);
            if (operatorId == null) {
                appendCouponPurchaseIncome(incomeVOS);
            }
@@ -378,19 +529,27 @@
        List<PointMerchandiseIncomeVo> amount = pointMercharsClient.getAmount(cityManagerId);
        for (PointMerchandiseIncomeVo pointMerchandiseIncomeVo : amount) {
            IncomeVO temp = new IncomeVO();
            temp.setIncomeType(6);
            if (pointMerchandiseIncomeVo==null) continue;
            temp.setInsertTime(pointMerchandiseIncomeVo.getInsertTime());
            if (pointMerchandiseIncomeVo.getCash()!=null){
                temp.setAmount(pointMerchandiseIncomeVo.getCash());
                if (pointMerchandiseIncomeVo.getUserCouponId() != null) {
                    usedCouponIds.add(pointMerchandiseIncomeVo.getUserCouponId());
                    temp.setId(pointMerchandiseIncomeVo.getUserCouponId().toString());
                }
                incomeVOS.add(temp);
            }
        }
        Map<Long, BigDecimal> couponDeductionAmounts = getCouponDeductionAmounts(usedCouponIds);
        applyCouponDeductions(incomeVOS, couponDeductionAmounts);
        // 平台智慧球场
        if (storeId==null&&operatorId==null){
            gameDataQuery.setObjectId(0);
            List<TGameRecord> data = ballClient.getData(gameDataQuery);
            for (TGameRecord datum : data) {
                IncomeVO temp = new IncomeVO();
                temp.setIncomeType(5);
                temp.setAmount(datum.getMoney());
                temp.setInsertTime(datum.getTime());
                incomeVOS.add(temp);
@@ -402,6 +561,7 @@
            List<TGameRecord> data = ballClient.getData(gameDataQuery);
            for (TGameRecord datum : data) {
                IncomeVO temp = new IncomeVO();
                temp.setIncomeType(5);
                temp.setAmount(datum.getMoney());
                temp.setInsertTime(datum.getTime());
                incomeVOS.add(temp);
@@ -413,17 +573,48 @@
            List<TGameRecord> data = ballClient.getData(gameDataQuery);
            for (TGameRecord datum : data) {
                IncomeVO temp = new IncomeVO();
                temp.setIncomeType(5);
                temp.setAmount(datum.getMoney());
                temp.setInsertTime(datum.getTime());
                incomeVOS.add(temp);
            }
        }
        Map<String, Object> monthlyData = buildIncomeStatistics(incomeVOS);
        // 部分跨服务查询不支持日期参数,统一在收入明细层按支付/退款时间过滤,
        // 确保总营收、净收入和收入类型统计使用完全相同的数据集。
        List<IncomeVO> statisticsIncomeVOS = filterIncomeRecordsByTime(incomeVOS, time);
        Map<String, Object> monthlyData = buildIncomeStatistics(statisticsIncomeVOS);
        monthlyData.put("incomeTypeNet", buildIncomeTypeNet(statisticsIncomeVOS, null));
        // 门票饼图需要日/月/季/年卡拆分;此前该数据只在 type1 内部返回,
        // 现随统一营收接口返回,供平台页直接使用。
        monthlyData.put("ticketIncome", getTicketIncome(time, operatorId, storeId));
        // 平台收入类型饼图还需要课程、订场分类明细。此前页面只能读取 type1
        // 的模板数据,筛选后 getIncomeData 缺少该字段,导致课程图显示为空。
        if (operatorId == null && storeId == null) {
            Map<String, Object> platformTypeData = (Map<String, Object>) type1(time);
            monthlyData.put("courseData", platformTypeData.get("courseData"));
            monthlyData.put("siteData", platformTypeData.get("siteData"));
        }
        return monthlyData;
    }
    private Map<String, Object> getTicketIncome(String time, Integer operatorId, Integer storeId) {
        List<Integer> userIds;
        if (storeId != null) {
            userIds = listStatisticsAppUserIdsForStore(storeId);
        } else if (operatorId != null) {
            userIds = listStatisticsAppUserIds(operatorId);
        } else {
            userIds = listStatisticsAppUserIds(null);
        }
        if (userIds == null) {
            userIds = new ArrayList<>();
        }
        // 活动服务对空集合会补充 -1,传入可变副本以兼容该历史实现。
        return pointMercharsClient.queryFee(new QueryDataFee(time, new ArrayList<>(userIds)));
    }
    /** 惠民卡采用支付、退款两条事实流水,赠送卡不计营收。 */
    private void appendHuiminIncome(List<IncomeVO> incomeRecords, Integer operatorId) {
    private void appendHuiminIncome(List<IncomeVO> incomeRecords, Integer operatorId, Set<Long> usedCouponIds) {
        HuiminPayQuery query = new HuiminPayQuery();
        query.setGiftPaymentType(3);
        if (operatorId != null) {
@@ -438,13 +629,19 @@
                continue;
            }
            IncomeVO payment = new IncomeVO();
            payment.setIncomeType(6);
            payment.setInsertTime(record.getPaymentTime());
            payment.setAmount(record.getSalesMoney());
            if (record.getUserCouponId() != null) {
                usedCouponIds.add(record.getUserCouponId());
                payment.setId(record.getUserCouponId().toString());
            }
            incomeRecords.add(payment);
            if (Integer.valueOf(3).equals(record.getStatus())
                    || Integer.valueOf(2).equals(record.getRefundStatus())
                    || Integer.valueOf(3).equals(record.getRefundStatus())) {
                IncomeVO refund = new IncomeVO();
                refund.setIncomeType(6);
                refund.setInsertTime(record.getRefundTime() == null ? record.getPaymentTime() : record.getRefundTime());
                refund.setRefundAmount(record.getSalesMoney());
                incomeRecords.add(refund);
@@ -463,9 +660,63 @@
                continue;
            }
            IncomeVO payment = new IncomeVO();
            payment.setIncomeType(6);
            payment.setInsertTime(record.getPaymentTime());
            payment.setAmount(BigDecimal.valueOf(record.getCash()));
            incomeRecords.add(payment);
        }
    }
    /**
     * 收入类型统计与净收入共用同一份收入明细。
     * 类型 1-5 对应会员、充值、课程、赛事、订场;6 为商品及其他平台收入。
     */
    private Map<Integer, BigDecimal> buildIncomeTypeNet(List<IncomeVO> incomeRecords, String time) {
        Map<Integer, BigDecimal> result = new HashMap<>();
        for (int type = 1; type <= 6; type++) {
            result.put(type, BigDecimal.ZERO);
        }
        Date startTime = null;
        Date endTime = null;
        if (ToolUtil.isNotEmpty(time)) {
            try {
                startTime = DateUtil.parse(time.split(" - ")[0] + " 00:00:00");
                endTime = DateUtil.parse(time.split(" - ")[1] + " 23:59:59");
            } catch (Exception ignored) {
                // 页面日期格式异常时与原接口一致,不额外按日期过滤。
            }
        }
        for (IncomeVO record : incomeRecords) {
            if (record.getIncomeType() == null || record.getInsertTime() == null) {
                continue;
            }
            if (startTime != null && (record.getInsertTime().before(startTime)
                    || record.getInsertTime().after(endTime))) {
                continue;
            }
            BigDecimal netAmount = getZeroIfNull(record.getAmount())
                    .subtract(getZeroIfNull(record.getRefundAmount()));
            result.put(record.getIncomeType(), result.getOrDefault(record.getIncomeType(), BigDecimal.ZERO)
                    .add(netAmount));
        }
        return result;
    }
    private List<IncomeVO> filterIncomeRecordsByTime(List<IncomeVO> incomeRecords, String time) {
        if (ToolUtil.isEmpty(time)) {
            return incomeRecords;
        }
        try {
            Date startTime = DateUtil.parse(time.split(" - ")[0] + " 00:00:00");
            Date endTime = DateUtil.parse(time.split(" - ")[1] + " 23:59:59");
            return incomeRecords.stream()
                    .filter(record -> record.getInsertTime() != null
                            && !record.getInsertTime().before(startTime)
                            && !record.getInsertTime().after(endTime))
                    .collect(Collectors.toList());
        } catch (Exception ignored) {
            // 兼容旧页面的日期格式:解析失败时保留原有全量统计行为。
            return incomeRecords;
        }
    }
@@ -476,23 +727,28 @@
        int currentYear = today.getYear();
        List<IncomeStatisticsVO> yearly = new ArrayList<>();
        List<Object> years = new ArrayList<>();
        List<String> yearLabels = new ArrayList<>();
        for (int year = currentYear - 9; year <= currentYear; year++) {
            final int statisticYear = year;
            yearly.add(sumIncomeStatistics(incomeRecords, record -> getRecordDate(record)
                    .getYear() == statisticYear));
            years.add(year);
            yearLabels.add(String.valueOf(year));
        }
        List<IncomeStatisticsVO> monthly = new ArrayList<>();
        List<String> monthLabels = new ArrayList<>();
        for (int month = 1; month <= 12; month++) {
            final int statisticMonth = month;
            monthly.add(sumIncomeStatistics(incomeRecords, record -> {
                LocalDate date = getRecordDate(record);
                return date.getYear() == currentYear && date.getMonthValue() == statisticMonth;
            }));
            monthLabels.add(String.format("%d-%02d", currentYear, month));
        }
        List<IncomeStatisticsVO> weekly = new ArrayList<>();
        List<String> weekLabels = new ArrayList<>();
        for (int weekIndex = 3; weekIndex >= 0; weekIndex--) {
            LocalDate startDate = today.minusDays(weekIndex * 7L + 6L);
            LocalDate endDate = today.minusDays(weekIndex * 7L);
@@ -500,13 +756,17 @@
                LocalDate date = getRecordDate(record);
                return !date.isBefore(startDate) && !date.isAfter(endDate);
            }));
            weekLabels.add(startDate.format(DateTimeFormatter.ISO_LOCAL_DATE)
                    + " 至 " + endDate.format(DateTimeFormatter.ISO_LOCAL_DATE));
        }
        List<IncomeStatisticsVO> daily = new ArrayList<>();
        List<String> dayLabels = new ArrayList<>();
        for (int dayIndex = 6; dayIndex >= 0; dayIndex--) {
            LocalDate statisticDate = today.minusDays(dayIndex);
            daily.add(sumIncomeStatistics(incomeRecords, record -> getRecordDate(record)
                    .equals(statisticDate)));
            dayLabels.add(statisticDate.format(DateTimeFormatter.ISO_LOCAL_DATE));
        }
        IncomeStatisticsVO total = sumIncomeStatistics(incomeRecords, record -> true);
@@ -527,6 +787,10 @@
        result.put("weekIncomeStatistics", weekly);
        result.put("dayIncomeStatistics", daily);
        result.put("year", years);
        result.put("yearLabels", yearLabels);
        result.put("monthLabels", monthLabels);
        result.put("weekLabels", weekLabels);
        result.put("dayLabels", dayLabels);
        // 兼容既有图表消费字段,值调整为总营收金额。
        result.put("yearIncome", yearly.stream().map(IncomeStatisticsVO::getTotalIncomeAmount)
                .collect(Collectors.toList()));
@@ -711,10 +975,7 @@
    @ResponseBody
    public Object backPt(Integer type) {
        HashMap<String, Object> map = new HashMap<>();
        List<Integer> userPt = appUserClient.getUserPt(Arrays.asList(1));
        if(userPt.size()==0){
            userPt.add(-1);
        }
        List<Integer> userPt = listStatisticsAppUserIds(null);
        ArrayList<Object> integers = new ArrayList<>();
        int year = DateUtil.year(new Date());
        List<TBackRecord> thisYear = backRecordService.list(new LambdaQueryWrapper<TBackRecord>().like(TBackRecord::getTime, year).in(TBackRecord::getUserId,userPt));
@@ -817,10 +1078,7 @@
    public Object backYys(Integer id) {
        id = resolveOperatorId(id);
        HashMap<String, Object> map = new HashMap<>();
        List<Integer> userPt = appUserClient.getUserYYs(id);
        if(userPt.size()==0){
            userPt.add(-1);
        }
        List<Integer> userPt = listStatisticsAppUserIds(id);
        ArrayList<Object> integers = new ArrayList<>();
        int year = DateUtil.year(new Date());
        List<TBackRecord> thisYear = backRecordService.list(new LambdaQueryWrapper<TBackRecord>().like(TBackRecord::getTime, year).in(TBackRecord::getUserId,userPt));
@@ -920,7 +1178,8 @@
    @RequestMapping(value = "/backStore")
    @ResponseBody
    public Object backStore(Integer id) {
        List<Integer> collect1 = Arrays.asList(id);
        id = resolveStatisticsStoreId(id);
        List<Integer> collect1 = Collections.singletonList(id == null ? -1 : id);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -930,6 +1189,10 @@
        //赛事
        List<Integer> list2 = competitionClient.queryAppUserId(collect1);
        list.addAll(list2);
        // 没有消费用户时,避免 MyBatis-Plus 生成 userId IN () 的非法 SQL。
        if (list.isEmpty()) {
            list.add(-1);
        }
        //门票
        HashMap<String, Object> map = new HashMap<>();
@@ -1035,23 +1298,28 @@
    @RequestMapping(value = "/userAndVipPt")
    @ResponseBody
    public Object userAndVipPt(Integer type) {
        type=1;
        HashMap<String, Object> map = appUserClient.userAndVipPt(type);
        // 平台用户统计按用户归属全量统计,不能以“已有消费记录”筛选,
        // 否则会遗漏尚未在门店消费的新注册用户。
        HashMap<String, Object> map = appUserClient.userAndVipPt(1);
        return map;
    }
    @RequestMapping(value = "/stuPt")
    @ResponseBody
    public Object stuPt() {
        HashMap<String, Object> map = appUserClient.stuPt();
        HashMap<String, Object> map = appUserClient.stuYys(listStatisticsAppUserIds(null));
        return map;
    }
    @RequestMapping(value = "/stuYys")
    @ResponseBody
    public Object stuYys(Integer id) {
        id = resolveOperatorId(id);
        List<TStore> list4 = storeService.list(new QueryWrapper<TStore>().eq("operatorId", id).eq("state", 1));
        List<TStore> list4 = listStatisticsStores(id, true);
        List<Integer> collect1 = list4.stream().map(TStore::getId).collect(Collectors.toList());
        // 运营商没有门店时,后续查询不能生成 storeId IN ()。
        if (collect1.isEmpty()) {
            collect1.add(-1);
        }
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1061,6 +1329,10 @@
        //赛事
        List<Integer> list2 = competitionClient.queryAppUserId(collect1);
        list.addAll(list2);
        // 下游会员统计同样使用 IN 查询,空用户列表按无匹配记录处理。
        if (list.isEmpty()) {
            list.add(-1);
        }
        //门票
        HashMap<String, Object> map = appUserClient.stuYys(list);
@@ -1069,7 +1341,8 @@
    @RequestMapping(value = "/stuStores")
    @ResponseBody
    public Object stuStores(Integer id) {
        List<Integer> collect1 = Arrays.asList(id);
        id = resolveStatisticsStoreId(id);
        List<Integer> collect1 = Collections.singletonList(id == null ? -1 : id);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1089,7 +1362,7 @@
    @RequestMapping(value = "/coursePt")
    @ResponseBody
    public Object coursePt() {
        List<Integer> userPt = appUserClient.getUserPt(Arrays.asList(1));
        List<Integer> userPt = listStatisticsAppUserIds(null);
        HashMap<String, Object> map = coursePackagePaymentClient.coursePt(userPt);
        return map;
    }
@@ -1098,7 +1371,7 @@
    @ResponseBody
    public Object courseYys(Integer id) {
        id = resolveOperatorId(id);
        List<Integer> userPt = appUserClient.getUserYYs(id);
        List<Integer> userPt = listStatisticsAppUserIds(id);
        HashMap<String, Object> map = coursePackagePaymentClient.coursePt(userPt);
        return map;
    }
@@ -1106,7 +1379,8 @@
    @RequestMapping(value = "/courseStore")
    @ResponseBody
    public Object courseStore(Integer id) {
        List<Integer> userPt = appUserClient.getUserPt(Arrays.asList(id));
        id = resolveStatisticsStoreId(id);
        List<Integer> userPt = listStatisticsAppUserIdsForStore(id);
        HashMap<String, Object> map = coursePackagePaymentClient.coursePt(userPt);
        return map;
    }
@@ -1115,8 +1389,7 @@
    @ResponseBody
    public Object userAndVipYys(Integer id) {
        id = resolveOperatorId(id);
        List<TStore> list4 = storeService.list(new QueryWrapper<TStore>().eq("operatorId", id).eq("state", 1));
        List<Integer> collect1 = list4.stream().map(TStore::getId).collect(Collectors.toList());
        List<Integer> collect1 = listStatisticsStoreIds(id, true);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1134,7 +1407,8 @@
    @RequestMapping(value = "/userAndVipStore")
    @ResponseBody
    public Object userAndVipStore(Integer id) {
        List<Integer> collect1 = Arrays.asList(id);
        id = resolveStatisticsStoreId(id);
        List<Integer> collect1 = Collections.singletonList(id == null ? -1 : id);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1152,10 +1426,10 @@
    @RequestMapping(value = "/actPt")
    @ResponseBody
    public Object actPt(Integer type) {
        List<Integer> userPt = appUserClient.getUserPt(Arrays.asList(1));
        List<Integer> userPt = listStatisticsAppUserIds(null);
        HashMap<String, Object> map = competitionClient.actPt(userPt);
        List<TStore> list = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, 0));
        List<TStore> list = listStatisticsStores(null, false);
        long l1=0;
        for (TStore tStore : list) {
            l1 += getStoreOperatingHours(tStore, new Date());
@@ -1168,8 +1442,7 @@
    public Object actYys(Integer id) {
        id = resolveOperatorId(id);
        CompetionVO competionVO = new CompetionVO();
        List<TStore> list4 = storeService.list(new QueryWrapper<TStore>().eq("operatorId", id).eq("state", 1));
        List<Integer> collect1 = list4.stream().map(TStore::getId).collect(Collectors.toList());
        List<Integer> collect1 = listStatisticsStoreIds(id, true);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1187,7 +1460,7 @@
        competionVO.setStoreIds(collect1);
        HashMap<String, Object> map = competitionClient.actYys(competionVO);
        List<TStore> lists = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, id));
        List<TStore> lists = listStatisticsStores(id, true);
        long l1=0;
        for (TStore tStore : lists) {
            l1 += getStoreOperatingHours(tStore, new Date());
@@ -1198,26 +1471,22 @@
    @RequestMapping(value = "/actStore")
    @ResponseBody
    public Object actStore(Integer id) {
        List<Integer> collect1 = Arrays.asList(id);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
        List<SiteBooking> list1 = siteBookingService.list(new QueryWrapper<SiteBooking>().in("storeId", collect1).eq("state", 1));
        List<Integer> collect = list1.stream().map(SiteBooking::getAppUserId).collect(Collectors.toList());
        list.addAll(collect);
        //赛事
        List<Integer> list2 = competitionClient.queryAppUserId(collect1);
        list.addAll(list2);
        //门票
        HashMap<String, Object> map = competitionClient.actPt(list);
        List<TStore> lists = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, 0));
        long l1=0;
        for (TStore tStore : lists) {
            l1 += getStoreOperatingHours(tStore, new Date());
        if (Integer.valueOf(3).equals(UserExt.getUser().getObjectType())) {
            id = UserExt.getUser().getObjectId();
        } else {
            id = resolveStoreId(id);
        }
        map.put("allHour",l1);
        TStore store = id == null ? null : storeService.getById(id);
        CompetionVO competionVO = new CompetionVO();
        if (store == null || !Integer.valueOf(1).equals(store.getState())) {
            competionVO.setStoreIds(Collections.singletonList(-1));
        } else {
            List<Integer> storeIds = Collections.singletonList(store.getId());
            competionVO.setStoreIds(storeIds);
        }
        HashMap<String, Object> map = competitionClient.actStore(competionVO);
        map.put("allHour", store == null || !Integer.valueOf(1).equals(store.getState())
                ? 0 : getStoreOperatingHours(store, new Date()));
        return map;
    }
@@ -1252,8 +1521,7 @@
//        List<User> list = sysUserService.list(new LambdaQueryWrapper<User>().eq(User::getObjectType, 2));
//        List<Integer> ids = list.stream().map(User::getId).collect(Collectors.toList());
//        List<Integer> userIds = appUserClient.getUserYYs(id);
        List<TStore> list4 = storeService.list(new QueryWrapper<TStore>().eq("operatorId", id).eq("state", 1));
        List<Integer> collect1 = list4.stream().map(TStore::getId).collect(Collectors.toList());
        List<Integer> collect1 = listStatisticsStoreIds(id, true);
        QueryDataFee queryDataFee = new QueryDataFee(time, collect1);
        // 课程收入
        HashMap<String, Object> map3 = courseClient.queryFee(queryDataFee);
@@ -1273,7 +1541,7 @@
        }else{
            List<TSite> list = siteService.list(new QueryWrapper<TSite>().in("storeId", collect1).eq("state", 1));
            ids = list.stream().map(TSite::getId).collect(Collectors.toList());
            if(collect1.size()==0){
            if(ids.isEmpty()){
                ids.add(-1);
            }
        }
@@ -1298,7 +1566,7 @@
        if(collect.size()==0){
            collect.add(-1);
        }
        List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect));
        List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect).ne(TSite::getState, 3));
        for (SiteBooking siteBooking : list1) {
            for (TSite tSite : list3) {
                if(siteBooking.getSiteId().equals(tSite.getId())){
@@ -1308,7 +1576,7 @@
        }
        List<TSiteType> list2 = siteTypeService.list();
        List<TSiteType> list2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3));
        List<Map<String,Object>> mapList = new ArrayList<>();
@@ -1353,8 +1621,9 @@
    @ResponseBody
    @RequestMapping("/selectStore")
    public Object selectStore(Integer id,String time) {
        id = resolveStatisticsStoreId(id);
        HashMap<String, Object> map = new HashMap<>();
        List<Integer> collect1 = Arrays.asList(id);
        List<Integer> collect1 = Collections.singletonList(id == null ? -1 : id);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1364,6 +1633,10 @@
        //赛事
        List<Integer> list2 = competitionClient.queryAppUserId(collect1);
        list.addAll(list2);
        // 没有消费用户时,避免 MyBatis-Plus 生成 appUserId IN () 的非法 SQL。
        if (list.isEmpty()) {
            list.add(-1);
        }
        //门票
        QueryDataFee queryDataFee = new QueryDataFee(time, list);
@@ -1406,9 +1679,9 @@
        List<Integer> collects = list1.stream().map(SiteBooking::getSiteId).collect(Collectors.toList());
        if(collects.size()==0){
            collect.add(-1);
            collects.add(-1);
        }
        List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collects));
        List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collects).ne(TSite::getState, 3));
        for (SiteBooking siteBooking : list1) {
            for (TSite tSite : list3) {
                if(siteBooking.getSiteId().equals(tSite.getId())){
@@ -1418,7 +1691,7 @@
        }
        List<TSiteType> lists2 = siteTypeService.list();
        List<TSiteType> lists2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3));
        List<Map<String,Object>> mapList = new ArrayList<>();
@@ -1449,6 +1722,17 @@
        map.put("monthone",month);
        map.put("quarterone",quarter);
        map.put("yearone",year);
        // 数值卡片复用营收数据的同一批收入明细和退款口径。图表数据仍沿用原查询,
        // 但收入类型净额之和始终与“营收数据-净收入”一致。
        Map<String, Object> incomeData = (Map<String, Object>) getIncomeData(null, null, time);
        Map<Integer, BigDecimal> incomeTypeNet = (Map<Integer, BigDecimal>) incomeData.get("incomeTypeNet");
        map.put("fee1", incomeTypeNet.getOrDefault(1, BigDecimal.ZERO));
        map.put("fee2", incomeTypeNet.getOrDefault(2, BigDecimal.ZERO));
        map.put("fee3", incomeTypeNet.getOrDefault(3, BigDecimal.ZERO));
        map.put("fee4", incomeTypeNet.getOrDefault(4, BigDecimal.ZERO));
        map.put("fee5", incomeTypeNet.getOrDefault(5, BigDecimal.ZERO));
        map.put("fee7", incomeTypeNet.getOrDefault(6, BigDecimal.ZERO));
        return map;
    }
@@ -1488,11 +1772,14 @@
    @ResponseBody
    public Object type1(String time) {
        HashMap<String, Object> map = new HashMap<>();
        // 找出平台的用户
        List<User> list = sysUserService.list(new LambdaQueryWrapper<User>().eq(User::getObjectType, 1));
        List<Integer> ids = list.stream().map(User::getId).collect(Collectors.toList());
        List<Integer> userIds = appUserClient.getUserPt(ids);
        // 平台统计是所有门店总和,用户范围同样来自所有门店的消费用户。
        List<Integer> userIds = listStatisticsAppUserIds(null);
        QueryDataFee queryDataFee = new QueryDataFee(time, userIds);
        // 课程和赛事接口的 ids 参数表示门店 ID;不能把平台用户 ID 传入。
        List<Integer> platformStoreIds = storeService.list(new LambdaQueryWrapper<TStore>()
                        .eq(TStore::getState, 1))
                .stream().map(TStore::getId).collect(Collectors.toList());
        QueryDataFee storeFeeQuery = new QueryDataFee(time, platformStoreIds);
        // 会员费
        HashMap<String, Object> map1 = appUserClient.queryFee(queryDataFee);
        Object fee1 = map1.get("fee1");
@@ -1501,41 +1788,36 @@
        Object fee2 = map1.get("fee2");
        map.put("fee2",fee2);
        // 课程收入
        HashMap<String, Object> map3 = courseClient.queryFee(queryDataFee);
        HashMap<String, Object> map3 = courseClient.queryFee(storeFeeQuery);
        map.put("fee3",map3.get("fee"));
        Object data = map3.get("data");
        map.put("courseData",data);
        // 赛事收入
        Double aDouble1 = competitionClient.queryFee(queryDataFee);
        Double aDouble1 = competitionClient.queryFee(storeFeeQuery);
        map.put("fee4",aDouble1);
        // 订场
        if(ids.size()==0){
            ids.add(-1);
        }
        LambdaQueryWrapper<SiteBooking> vipPaymentLambdaQueryWrapper = new LambdaQueryWrapper<>();
        if(ToolUtil.isNotEmpty(time)){
            String stime = time.split(" - ")[0]+" 00:00:00";
            String etime = time.split(" - ")[1]+" 23:59:59";
            vipPaymentLambdaQueryWrapper.between(SiteBooking::getInsertTime,stime,etime);
            vipPaymentLambdaQueryWrapper.between(SiteBooking::getPayTime,stime,etime);
        }
        if(userIds.size()==0){
            userIds.add(-1);
        }
        vipPaymentLambdaQueryWrapper.eq(SiteBooking::getState, 1);
        vipPaymentLambdaQueryWrapper.ne(SiteBooking::getStatus,0);
        vipPaymentLambdaQueryWrapper.ne(SiteBooking::getStatus,5);
        ArrayList<Integer> objects = new ArrayList<>();
        objects.add(1);
        objects.add(2);
        vipPaymentLambdaQueryWrapper.in(SiteBooking::getPayType,objects);
        List<SiteBooking> list1 = siteBookingService.list(new QueryWrapper<SiteBooking>()
                .ne("status",0)
                .ne("status",5)
                .ne("payType",3)
                .ne("payType",4));
        List<SiteBooking> list1 = siteBookingService.list(vipPaymentLambdaQueryWrapper);
        double sum = list1.stream().mapToDouble(SiteBooking::getPayMoney).sum();
        // 智慧球场查询query
        GameDataQuery gameDataQuery = new GameDataQuery();
        gameDataQuery.setObjectId(0);
        if (ToolUtil.isNotEmpty(time)) {
            gameDataQuery.setStartTime(time.split(" - ")[0] + " 00:00:00");
            gameDataQuery.setEndTime(time.split(" - ")[1] + " 23:59:59");
        }
        // 平台智慧球场
        List<TGameRecord> data1 = ballClient.getData(gameDataQuery);
        double sum1 = data1.stream()
@@ -1549,7 +1831,7 @@
        if(collect.size()==0){
            collect.add(-1);
        }
        List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect));
        List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect).ne(TSite::getState, 3));
        for (SiteBooking siteBooking : list1) {
            for (TSite tSite : list3) {
                if(siteBooking.getSiteId().equals(tSite.getId())){
@@ -1559,7 +1841,7 @@
        }
        List<TSiteType> list2 = siteTypeService.list();
        List<TSiteType> list2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3));
        List<Map<String,Object>> mapList = new ArrayList<>();
@@ -1596,9 +1878,7 @@
    @RequestMapping("/stuCourseData")
    @ResponseBody
    public Object stuCourseData() {
        ArrayList<Integer> objects = new ArrayList<>();
        objects.add(1);
        List<Integer> userIds = appUserClient.getUserPt(objects);
        List<Integer> userIds = listStatisticsAppUserIds(null);
        List<Map<String,Object>> list = coursePackagePaymentClient.queryCourseData(userIds);
        ArrayList<Object> objects2 = new ArrayList<>();
        ArrayList<Object> objects3 = new ArrayList<>();
@@ -1616,9 +1896,7 @@
    @RequestMapping("/stuUserData")
    @ResponseBody
    public Object stuUserData() {
        ArrayList<Integer> objects = new ArrayList<>();
        objects.add(1);
        HashMap<String, Object> map = appUserClient.queryUserAge1();
        HashMap<String, Object> map = appUserClient.queryUserAgeYys(listStatisticsAppUserIds(null));
        return map;
    }
@@ -1626,8 +1904,7 @@
    @ResponseBody
    public Object stuUserDataYys(Integer id) {
        id = resolveOperatorId(id);
        List<TStore> list4 = storeService.list(new QueryWrapper<TStore>().eq("operatorId", id).eq("state", 1));
        List<Integer> collect1 = list4.stream().map(TStore::getId).collect(Collectors.toList());
        List<Integer> collect1 = listStatisticsStoreIds(id, true);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1644,8 +1921,8 @@
    @RequestMapping("/stuUserDataStore")
    @ResponseBody
    public Object stuUserDataStore(Integer id) {
        List<Integer> collect1 = Arrays.asList(id);
        id = resolveStatisticsStoreId(id);
        List<Integer> collect1 = Collections.singletonList(id == null ? -1 : id);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1665,8 +1942,7 @@
    @ResponseBody
    public Object selectYysJx(Integer id) {
        id = resolveOperatorId(id);
        List<TStore> list4 = storeService.list(new QueryWrapper<TStore>().eq("operatorId", id).eq("state", 1));
        List<Integer> collect1 = list4.stream().map(TStore::getId).collect(Collectors.toList());
        List<Integer> collect1 = listStatisticsStoreIds(id, true);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地
@@ -1698,7 +1974,8 @@
    @RequestMapping("/selectStoreJx")
    @ResponseBody
    public Object selectStoreJx(Integer id) {
        List<Integer> collect1 = Arrays.asList(id);
        id = resolveStatisticsStoreId(id);
        List<Integer> collect1 = Collections.singletonList(id == null ? -1 : id);
        //课程
        List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1);
        //场地