Răsfoiți Sursa

feat: 完整记账功能 — 收支管理+分类统计+月度概览

E2E Test Bot 1 lună în urmă
părinte
comite
6b05c87173

+ 8 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -7095,5 +7095,13 @@ private void runMigration100() {
 		} catch (Exception e) {
 			log.warn("创建 energy_behavior_config 表失败: {}", e.getMessage());
 		}
+
+		// 迁移109: finance_checkins表添加type列(记账类型: income/expense)
+		try {
+			ensureColumn("finance_checkins", "type", "VARCHAR(10) DEFAULT 'expense' COMMENT '类型: income(收入)/expense(支出)'");
+			log.info("已完成finance_checkins.type字段添加");
+		} catch (Exception e) {
+			log.warn("添加finance_checkins.type字段失败: {}", e.getMessage());
+		}
 	}
 }

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FinanceCheckin.java

@@ -19,10 +19,14 @@ public class FinanceCheckin implements Serializable {
 
     private Long childId;
 
+    /** 类型: income(收入) / expense(支出) */
+    private String type;
+
     private String checkinType;
 
     private Date checkinDate;
 
+    /** 金额(分) */
     private Integer amount;
 
     private String category;

+ 33 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/FinanceCheckinService.java

@@ -175,12 +175,44 @@ public class FinanceCheckinService {
                 .mapToLong(FinanceCheckin::getEarnedEnergy)
                 .sum();
 
+        // 收支统计
+        long totalIncome = all.stream()
+                .filter(c -> "income".equals(c.getType()) && c.getAmount() != null)
+                .mapToLong(FinanceCheckin::getAmount)
+                .sum();
+        long totalExpense = all.stream()
+                .filter(c -> "expense".equals(c.getType()) && c.getAmount() != null)
+                .mapToLong(FinanceCheckin::getAmount)
+                .sum();
+
+        // 本月统计
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        int year = cal.get(java.util.Calendar.YEAR);
+        int month = cal.get(java.util.Calendar.MONTH) + 1;
+        String monthPrefix = year + "-" + (month < 10 ? "0" : "") + month;
+        long monthIncome = all.stream()
+                .filter(c -> "income".equals(c.getType()) && c.getAmount() != null
+                        && c.getCheckinDate() != null && c.getCheckinDate().toString().startsWith(monthPrefix))
+                .mapToLong(FinanceCheckin::getAmount)
+                .sum();
+        long monthExpense = all.stream()
+                .filter(c -> "expense".equals(c.getType()) && c.getAmount() != null
+                        && c.getCheckinDate() != null && c.getCheckinDate().toString().startsWith(monthPrefix))
+                .mapToLong(FinanceCheckin::getAmount)
+                .sum();
+
         Map<String, Object> stats = new HashMap<>();
         stats.put("totalCheckins", totalCheckins);
         stats.put("totalEnergy", totalEnergy);
+        stats.put("totalIncome", totalIncome);
+        stats.put("totalExpense", totalExpense);
+        stats.put("balance", totalIncome - totalExpense);
+        stats.put("monthIncome", monthIncome);
+        stats.put("monthExpense", monthExpense);
+        stats.put("monthBalance", monthIncome - monthExpense);
         stats.put("dailyPlanCount", all.stream().filter(c -> "daily_plan".equals(c.getCheckinType())).count());
         stats.put("savingCount", all.stream().filter(c -> "saving".equals(c.getCheckinType())).count());
-        stats.put("expenseCount", all.stream().filter(c -> "expense".equals(c.getCheckinType())).count());
+        stats.put("expenseCount", all.stream().filter(c -> c.getCheckinType() != null && c.getCheckinType().contains("expense")).count());
         stats.put("reviewCount", all.stream().filter(c -> "review".equals(c.getCheckinType())).count());
         return stats;
     }

+ 4 - 3
cfc-backend/src/main/resources/schema.sql

@@ -1482,9 +1482,10 @@ CREATE TABLE IF NOT EXISTS finance_checkins (
     id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
     user_id BIGINT NOT NULL COMMENT '用户ID',
     child_id BIGINT NOT NULL COMMENT '孩子ID',
-    checkin_type VARCHAR(20) NOT NULL COMMENT '打卡类型: daily_plan/saving/expense/review',
-    checkin_date DATE NOT NULL COMMENT '打卡日期',
-    amount INT COMMENT '金额(分,收支记录)',
+checkin_type VARCHAR(20) NOT NULL COMMENT '打卡类型: daily_plan/saving/expense/review',
+    checkin_date DATE NOT NULL COMMENT '记账日期',
+    type VARCHAR(10) DEFAULT 'expense' COMMENT '类型: income(收入)/expense(支出)',
+    amount INT COMMENT '金额(分,正数)',
     category VARCHAR(50) COMMENT '收支分类',
     note TEXT COMMENT '备注',
     mood VARCHAR(20) COMMENT '心情',