|
|
@@ -235,4 +235,93 @@ public class RevenueDashboardService {
|
|
235
|
235
|
return totalPaid.multiply(BigDecimal.valueOf(100))
|
|
236
|
236
|
.divide(totalBilled, 2, RoundingMode.HALF_UP);
|
|
237
|
237
|
}
|
|
|
238
|
+
|
|
|
239
|
+ /**
|
|
|
240
|
+ * 获取实时营收数据(缓存优化)
|
|
|
241
|
+ */
|
|
|
242
|
+ public Map<String, Object> getRealtimeData() {
|
|
|
243
|
+ log.info("Getting realtime revenue data");
|
|
|
244
|
+
|
|
|
245
|
+ // 使用缓存表加速查询
|
|
|
246
|
+ Map<String, Object> realtime = jdbcTemplate.queryForMap(
|
|
|
247
|
+ "SELECT * FROM rev_revenue_daily WHERE stat_date = CURRENT_DATE LIMIT 1");
|
|
|
248
|
+
|
|
|
249
|
+ if (realtime.isEmpty()) {
|
|
|
250
|
+ // 缓存不存在,实时计算
|
|
|
251
|
+ realtime = getOverview();
|
|
|
252
|
+ realtime.put("cacheStatus", "calculated");
|
|
|
253
|
+ } else {
|
|
|
254
|
+ realtime.put("cacheStatus", "cached");
|
|
|
255
|
+ }
|
|
|
256
|
+
|
|
|
257
|
+ // 添加实时统计时间戳
|
|
|
258
|
+ realtime.put("lastUpdate", new java.util.Date());
|
|
|
259
|
+
|
|
|
260
|
+ log.info("Realtime data retrieved: cacheStatus={}", realtime.get("cacheStatus"));
|
|
|
261
|
+ return realtime;
|
|
|
262
|
+ }
|
|
|
263
|
+
|
|
|
264
|
+ /**
|
|
|
265
|
+ * 获取营收预测(基于历史趋势)
|
|
|
266
|
+ */
|
|
|
267
|
+ public Map<String, Object> getRevenueForecast() {
|
|
|
268
|
+ log.info("Getting revenue forecast");
|
|
|
269
|
+
|
|
|
270
|
+ Map<String, Object> forecast = new HashMap<>();
|
|
|
271
|
+
|
|
|
272
|
+ // 计算过去3个月平均日营收
|
|
|
273
|
+ BigDecimal avgDailyRevenue = jdbcTemplate.queryForObject(
|
|
|
274
|
+ "SELECT AVG(amount) FROM rev_payment WHERE paid_at >= CURRENT_DATE - INTERVAL '3 months'",
|
|
|
275
|
+ BigDecimal.class);
|
|
|
276
|
+
|
|
|
277
|
+ // 预测下月营收(基于过去3个月趋势)
|
|
|
278
|
+ BigDecimal nextMonthForecast = avgDailyRevenue != null ?
|
|
|
279
|
+ avgDailyRevenue.multiply(BigDecimal.valueOf(30)) : BigDecimal.ZERO;
|
|
|
280
|
+
|
|
|
281
|
+ // 计算过去3个月增长率
|
|
|
282
|
+ BigDecimal growthRate = calculateGrowthRate();
|
|
|
283
|
+
|
|
|
284
|
+ forecast.put("avgDailyRevenue", avgDailyRevenue);
|
|
|
285
|
+ forecast.put("nextMonthForecast", nextMonthForecast);
|
|
|
286
|
+ forecast.put("growthRate", growthRate);
|
|
|
287
|
+ forecast.put("forecastDate", java.time.LocalDate.now().plusMonths(1).toString());
|
|
|
288
|
+
|
|
|
289
|
+ log.info("Revenue forecast: nextMonthForecast={}, growthRate={}",
|
|
|
290
|
+ nextMonthForecast, growthRate);
|
|
|
291
|
+ return forecast;
|
|
|
292
|
+ }
|
|
|
293
|
+
|
|
|
294
|
+ /**
|
|
|
295
|
+ * 计算增长率
|
|
|
296
|
+ */
|
|
|
297
|
+ private BigDecimal calculateGrowthRate() {
|
|
|
298
|
+ try {
|
|
|
299
|
+ // 获取当前月和上个月的营收
|
|
|
300
|
+ String currentMonth = java.time.LocalDate.now().format(
|
|
|
301
|
+ java.time.format.DateTimeFormatter.ofPattern("yyyy-MM"));
|
|
|
302
|
+ String lastMonth = java.time.LocalDate.now().minusMonths(1).format(
|
|
|
303
|
+ java.time.format.DateTimeFormatter.ofPattern("yyyy-MM"));
|
|
|
304
|
+
|
|
|
305
|
+ BigDecimal currentRevenue = jdbcTemplate.queryForObject(
|
|
|
306
|
+ "SELECT COALESCE(SUM(amount), 0) FROM rev_payment WHERE DATE_TRUNC('month', paid_at) = ?::date",
|
|
|
307
|
+ BigDecimal.class, currentMonth);
|
|
|
308
|
+
|
|
|
309
|
+ BigDecimal lastRevenue = jdbcTemplate.queryForObject(
|
|
|
310
|
+ "SELECT COALESCE(SUM(amount), 0) FROM rev_payment WHERE DATE_TRUNC('month', paid_at) = ?::date",
|
|
|
311
|
+ BigDecimal.class, lastMonth);
|
|
|
312
|
+
|
|
|
313
|
+ if (lastRevenue.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
314
|
+ return BigDecimal.ZERO;
|
|
|
315
|
+ }
|
|
|
316
|
+
|
|
|
317
|
+ BigDecimal growth = currentRevenue.subtract(lastRevenue)
|
|
|
318
|
+ .divide(lastRevenue, 4, RoundingMode.HALF_UP)
|
|
|
319
|
+ .multiply(BigDecimal.valueOf(100));
|
|
|
320
|
+
|
|
|
321
|
+ return growth;
|
|
|
322
|
+ } catch (Exception e) {
|
|
|
323
|
+ log.warn("Error calculating growth rate: {}", e.getMessage());
|
|
|
324
|
+ return BigDecimal.ZERO;
|
|
|
325
|
+ }
|
|
|
326
|
+ }
|
|
238
|
327
|
}
|