From 77a9f19e6532fef315d22f52008ef9c73dc69bc8 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期二, 04 八月 2026 17:12:04 +0800
Subject: [PATCH] fix: 大屏充电用户数

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java |  117 ++++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 93 insertions(+), 24 deletions(-)

diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index 978b52a..4a5563a 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -6711,6 +6711,14 @@
             if (summary != null) {
                 result.putAll(summary);
             }
+            // 充电用户数精确去重(用户-站点对表, 跨站不重复); 对表未回填时回退到按站点求和
+            Long pairCount = this.baseMapper.userSiteTableCount();
+            if (pairCount != null && pairCount > 0) {
+                Long exactUserCount = this.baseMapper.userCountFromStat(siteIds);
+                if (exactUserCount != null) {
+                    result.put("chargingUserCount", exactUserCount);
+                }
+            }
             Long registered = this.baseMapper.registeredFromStat();
             result.put("registeredUserCount", registered == null ? 0L : registered);
         } catch (Exception e) {
@@ -6836,6 +6844,7 @@
         LocalDate currentMonth = LocalDate.now().withDayOfMonth(1);
         while (!month.isAfter(currentMonth)) {
             writeMonthSlice(month, month.plusMonths(1));
+            writeUserSitePairs(month, month.plusMonths(1));
             month = month.plusMonths(1);
         }
         // 2. 近90天日切片
@@ -6856,40 +6865,100 @@
         LocalDate currentMonth = LocalDate.now().withDayOfMonth(1);
         writeMonthSlice(currentMonth, currentMonth.plusMonths(1));
         writeDaySlice(LocalDate.now());
+        writeUserSitePairs(LocalDate.now(), LocalDate.now().plusDays(1));
         writeGlobal();
         log.info("大屏预聚合表刷新完成, 耗时 {}ms", System.currentTimeMillis() - start);
     }
 
     /**
-     * 写入一个月度切片聚合(M 行)
+     * 写入时间切片内的 (site_id, app_user_id) 对(充电用户精确去重用, INSERT IGNORE 幂等)
      */
-    private void writeMonthSlice(LocalDate monthStart, LocalDate monthEnd) {
-        List<Map<String, Object>> rows = this.baseMapper.monthSliceAgg(
-                monthStart.atStartOfDay().format(LARGE_SCREEN_SQL_FMT),
-                monthEnd.atStartOfDay().format(LARGE_SCREEN_SQL_FMT));
-        if (rows == null || rows.isEmpty()) {
+    private void writeUserSitePairs(LocalDate sliceStart, LocalDate sliceEnd) {
+        List<Map<String, Object>> pairs = this.baseMapper.userSitePairsAgg(
+                sliceStart.atStartOfDay().format(LARGE_SCREEN_SQL_FMT),
+                sliceEnd.atStartOfDay().format(LARGE_SCREEN_SQL_FMT));
+        if (pairs == null || pairs.isEmpty()) {
             return;
         }
-        List<Map<String, Object>> statRows = new ArrayList<>();
-        for (Map<String, Object> row : rows) {
-            if (row == null || row.get("site_id") == null) {
+        List<Map<String, Object>> rows = new ArrayList<>();
+        for (Map<String, Object> pair : pairs) {
+            if (pair == null || pair.get("site_id") == null || pair.get("app_user_id") == null) {
                 continue;
             }
-            BigDecimal electricFee = decimalValue(row, "electric_fee");
-            BigDecimal serviceFee = decimalValue(row, "service_fee");
-            Map<String, Object> stat = new HashMap<>();
-            stat.put("statType", "M");
-            stat.put("period", monthStart.format(LARGE_SCREEN_MONTH_FMT));
-            stat.put("siteId", row.get("site_id"));
-            stat.put("orderCount", decimalValue(row, "order_count"));
-            stat.put("userCount", decimalValue(row, "user_count"));
-            stat.put("capacity", decimalValue(row, "capacity"));
-            stat.put("electricFee", electricFee);
-            stat.put("serviceFee", serviceFee);
-            stat.put("income", electricFee.add(serviceFee));
-            stat.put("totalPower", BigDecimal.ZERO);
-            stat.put("registeredUserCount", 0L);
-            statRows.add(stat);
+            Map<String, Object> row = new HashMap<>();
+            row.put("siteId", pair.get("site_id"));
+            row.put("appUserId", pair.get("app_user_id"));
+            rows.add(row);
+        }
+        if (!rows.isEmpty()) {
+            this.baseMapper.insertUserSitePairs(rows);
+        }
+    }
+
+    /**
+     * 写入一个月度切片聚合(M 行)
+     * 金额/订单数取 monthSliceAgg(已结束+已支付口径);
+     * 充电用户数取 monthSliceUserAgg(工作台口径: 该站点所有订单的去重用户, 不区分状态/删除标记)
+     */
+    private void writeMonthSlice(LocalDate monthStart, LocalDate monthEnd) {
+        String startTime = monthStart.atStartOfDay().format(LARGE_SCREEN_SQL_FMT);
+        String endTime = monthEnd.atStartOfDay().format(LARGE_SCREEN_SQL_FMT);
+        List<Map<String, Object>> rows = this.baseMapper.monthSliceAgg(startTime, endTime);
+        List<Map<String, Object>> userRows = this.baseMapper.monthSliceUserAgg(startTime, endTime);
+        if ((rows == null || rows.isEmpty()) && (userRows == null || userRows.isEmpty())) {
+            return;
+        }
+        Map<Object, BigDecimal> userCountBySite = new HashMap<>();
+        if (userRows != null) {
+            for (Map<String, Object> userRow : userRows) {
+                if (userRow != null && userRow.get("site_id") != null) {
+                    userCountBySite.put(userRow.get("site_id"), decimalValue(userRow, "user_count"));
+                }
+            }
+        }
+        String period = monthStart.format(LARGE_SCREEN_MONTH_FMT);
+        List<Map<String, Object>> statRows = new ArrayList<>();
+        Map<Object, Boolean> written = new HashMap<>();
+        if (rows != null) {
+            for (Map<String, Object> row : rows) {
+                if (row == null || row.get("site_id") == null) {
+                    continue;
+                }
+                BigDecimal electricFee = decimalValue(row, "electric_fee");
+                BigDecimal serviceFee = decimalValue(row, "service_fee");
+                Map<String, Object> stat = new HashMap<>();
+                stat.put("statType", "M");
+                stat.put("period", period);
+                stat.put("siteId", row.get("site_id"));
+                stat.put("orderCount", decimalValue(row, "order_count"));
+                stat.put("userCount", userCountBySite.getOrDefault(row.get("site_id"), BigDecimal.ZERO));
+                stat.put("capacity", decimalValue(row, "capacity"));
+                stat.put("electricFee", electricFee);
+                stat.put("serviceFee", serviceFee);
+                stat.put("income", electricFee.add(serviceFee));
+                stat.put("totalPower", BigDecimal.ZERO);
+                stat.put("registeredUserCount", 0L);
+                statRows.add(stat);
+                written.put(row.get("site_id"), Boolean.TRUE);
+            }
+        }
+        // 当月仅有充电用户、暂无已结束订单的站点, 补一行保用户数(金额为0)
+        for (Map.Entry<Object, BigDecimal> entry : userCountBySite.entrySet()) {
+            if (!written.containsKey(entry.getKey())) {
+                Map<String, Object> stat = new HashMap<>();
+                stat.put("statType", "M");
+                stat.put("period", period);
+                stat.put("siteId", entry.getKey());
+                stat.put("orderCount", BigDecimal.ZERO);
+                stat.put("userCount", entry.getValue());
+                stat.put("capacity", BigDecimal.ZERO);
+                stat.put("electricFee", BigDecimal.ZERO);
+                stat.put("serviceFee", BigDecimal.ZERO);
+                stat.put("income", BigDecimal.ZERO);
+                stat.put("totalPower", BigDecimal.ZERO);
+                stat.put("registeredUserCount", 0L);
+                statRows.add(stat);
+            }
         }
         if (!statRows.isEmpty()) {
             this.baseMapper.replaceSiteStatBatch(statRows);

--
Gitblit v1.7.1