From 8a85fc834d98b2c14c5b886a957ccf3197aad293 Mon Sep 17 00:00:00 2001
From: 刘帅 <rr.iii@qq.com>
Date: 星期日, 20 九月 2026 17:12:28 +0800
Subject: [PATCH] feat:优惠券使用入口:购买门票、预定场地、购买惠民卡

---
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java |  228 ++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 187 insertions(+), 41 deletions(-)

diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java
index ffa4b76..e744bdb 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java
@@ -202,8 +202,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);
@@ -216,19 +226,34 @@
      */
     @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).ne("state", 3)).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);
+        }
+
+        // 参数代表当前统计目标,而非当前登录人的角色;平台账号查看运营商/门店页时,
+        // 会员、充值也必须跟随被查看的运营商/门店归属。
+        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();
@@ -268,14 +293,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<>();
         // 会员支付记录
@@ -283,16 +314,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);
@@ -305,6 +339,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());
@@ -320,6 +355,7 @@
                 continue;
             }
             IncomeVO temp = new IncomeVO();
+            temp.setIncomeType(3);
             temp.setInsertTime(registerOrderVO.getInsertTime());
             if (registerOrderVO.getMoney() != null) {
                 temp.setAmount(registerOrderVO.getMoney());
@@ -347,11 +383,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()));
@@ -362,6 +400,7 @@
                 continue;
             }
             IncomeVO temp = new IncomeVO();
+            temp.setIncomeType(4);
             temp.setInsertTime(competition.getPayTime());
             if (competition.getAmount() != null) {
                 temp.setAmount(new BigDecimal(competition.getAmount().toString()));
@@ -375,14 +414,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()));
@@ -406,12 +448,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);
             }
@@ -421,19 +460,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);
@@ -445,6 +492,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);
@@ -456,17 +504,44 @@
             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));
         return monthlyData;
     }
 
+    private Map<String, Object> getTicketIncome(String time, Integer operatorId, Integer storeId) {
+        List<Integer> userIds;
+        if (storeId != null) {
+            userIds = appUserClient.getUserStore(storeId);
+        } else if (operatorId != null) {
+            userIds = appUserClient.getUserYYs(operatorId);
+        } else {
+            List<Integer> platformUserIds = sysUserService.list(new LambdaQueryWrapper<User>()
+                            .eq(User::getObjectType, 1))
+                    .stream().map(User::getId).collect(Collectors.toList());
+            userIds = appUserClient.getUserPt(platformUserIds);
+        }
+        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) {
@@ -481,13 +556,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);
@@ -506,9 +587,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;
         }
     }
 
@@ -1515,6 +1650,17 @@
         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;
     }
 
@@ -1558,6 +1704,11 @@
         List<Integer> ids = list.stream().map(User::getId).collect(Collectors.toList());
         List<Integer> userIds = appUserClient.getUserPt(ids);
         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");
@@ -1566,41 +1717,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()

--
Gitblit v1.7.1