A:
感謝你的說明,現在可以回到「程式夥伴」的角色了,可以進行第二十二步的階段 A 的第一步:擴充數據層 (DAO),謝謝
Q:
好的,角色切換完畢!讓我們以「程式夥伴」的身份,開始為「錢流」App 的最終章——「年度預算規劃」——打下堅實的數據基礎。
我們的第一步,是為我們的 DAO (Data Access Object) 增加幾個強大的新查詢。這些查詢就像是精密的數據提煉工具,能從我們龐大的交易和快照紀錄中,萃取出進行預算規劃所需要的兩個最關鍵的數據:
使用者最新的總淨值 (Net Worth)。
特定期間內的總支出 (Total Expenses)。
第廿二步,階段 A-1:擴充數據層 (DAO)
1. 查詢最新淨值
這個查詢需要從 account_snapshots 資料表中,計算出所有資產型帳戶的最新總額,以及所有負債型帳戶的最新總額。這個邏輯放在 SnapshotDao 最為合適。
A. 建立新的資料模型 NetWorth.kt
我們先建立一個用來承載查詢結果的資料類別。
路徑: app/src/main/java/com/yourcompany/cashflow/data/model/FinancialSummary.kt (加入這個 class)
// ... (檔案頂部已有 MonthlyTotal, AssetAllocation 等)
// 用於承載最新淨值的查詢結果
data class NetWorth(
val totalAssets: Double,
val totalLiabilities: Double
) {
// 提供一個計算屬性,方便直接取得淨值
val value: Double
get() = totalAssets - totalLiabilities
}
B. 在 SnapshotDao.kt 中加入新查詢
這是本次最核心的 SQL 查詢,它會一次性計算出總資產和總負債。
路徑: app/src/main/java/com/yourcompany/cashflow/data/local/dao/SnapshotDao.kt
// ...
import com.yourcompany.cashflow.data.model.NetWorth // 匯入
@Dao
interface SnapshotDao {
// ... (現有方法)
// vvv-- 加入這個新方法 --vvv
/**
* 計算所有帳戶最新快照的總資產與總負債。
* 1. 找出每個帳戶(accountId)最新的一筆快照(MAX(snapshotDate))。
* 2. 根據上述結果,篩選出所有這些最新的快照紀錄。
* 3. JOIN accounts 表來取得帳戶類型 (type)。
* 4. 使用 CASE WHEN 條件式加總,將 BANK 和 INVESTMENT 類型的值加到 totalAssets,
* 將 LIABILITY 類型的值加到 totalLiabilities。
*/
@Query("""
SELECT
SUM(CASE WHEN a.type IN ('BANK', 'INVESTMENT') THEN s.value ELSE 0 END) as totalAssets,
SUM(CASE WHEN a.type = 'LIABILITY' THEN s.value ELSE 0 END) as totalLiabilities
FROM account_snapshots s
INNER JOIN accounts a ON s.accountId = a.id
WHERE s.snapshotDate IN (SELECT MAX(snapshotDate) FROM account_snapshots GROUP BY accountId)
""")
fun getLatestNetWorth(): Flow<NetWorth?> // 結果可能為 null (如果沒有任何快照)
}
2. 查詢期間總支出
這個查詢相對簡單,我們需要它來幫助我們在主畫面上追蹤預算使用狀況。它屬於 TransactionDao 的職責。
A. 在 TransactionDao.kt 中加入新查詢
路徑: app/src/main/java/com/yourcompany/cashflow/data/local/dao/TransactionDao.kt
// ...
@Dao
interface TransactionDao {
// ... (現有方法)
// vvv-- 加入這個新方法 --vvv
/**
* 查詢在指定時間範圍內的總支出金額
*/
@Query("""
SELECT SUM(amount) FROM transactions
WHERE type = 'EXPENSE' AND transactionDate BETWEEN :startDate AND :endDate
""")
fun getExpensesBetween(startDate: Long, endDate: Long): Flow<Double?> // 結果可能為 null (如果沒有支出)
}
3. 更新 Repository 層
最後,我們在對應的 Repository 中公開這些新的 DAO 方法,以保持我們架構的清晰與一致。
A. 更新 SnapshotRepository.kt
// /data/repository/SnapshotRepository.kt
interface SnapshotRepository {
// ... (現有方法)
fun getLatestNetWorth(): Flow<NetWorth?>
}
class SnapshotRepositoryImpl @Inject constructor(...) : SnapshotRepository {
// ... (現有實作)
override fun getLatestNetWorth(): Flow<NetWorth?> = snapshotDao.getLatestNetWorth()
}
B. 更新 TransactionRepository.kt
// /data/repository/TransactionRepository.kt
interface TransactionRepository {
// ... (現有方法)
fun getExpensesBetween(startDate: Long, endDate: Long): Flow<Double?>
}
class TransactionRepositoryImpl @Inject constructor(...) : TransactionRepository {
// ... (現有實作)
override fun getExpensesBetween(startDate: Long, endDate: Long): Flow<Double?> = transactionDao.getExpensesBetween(startDate, endDate)
}
數據層擴充完成!
我們現在的 DAO 已經具備了提供「財務現況快照(最新淨值)」與「期間內支出總和」這兩個核心數據的能力。這就像是我們已經準備好了所有最高品質的食材。
下一步,我們將建立 BudgetViewModel,它會擔任「大廚」的角色,使用這些新的數據(食材),並結合我們之前建立的 PMT 公式(食譜),來烹調出最終的「年度可支配預算」這道大餐。
準備好開始撰寫 ViewModel 的邏輯了嗎?