Vibe coding全紀錄-42

A:

 感謝你的說明,現在可以回到「程式夥伴」的角色了,可以進行第二十二步的階段 A 的第一步:擴充數據層 (DAO),謝謝 

Q:

好的,角色切換完畢!讓我們以「程式夥伴」的身份,開始為「錢流」App 的最終章——「年度預算規劃」——打下堅實的數據基礎。

我們的第一步,是為我們的 DAO (Data Access Object) 增加幾個強大的新查詢。這些查詢就像是精密的數據提煉工具,能從我們龐大的交易和快照紀錄中,萃取出進行預算規劃所需要的兩個最關鍵的數據:

  1. 使用者最新的總淨值 (Net Worth)。

  2. 特定期間內的總支出 (Total Expenses)。


第廿二步,階段 A-1:擴充數據層 (DAO)

1. 查詢最新淨值

這個查詢需要從 account_snapshots 資料表中,計算出所有資產型帳戶的最新總額,以及所有負債型帳戶的最新總額。這個邏輯放在 SnapshotDao 最為合適。

A. 建立新的資料模型 NetWorth.kt

我們先建立一個用來承載查詢結果的資料類別。

路徑: app/src/main/java/com/yourcompany/cashflow/data/model/FinancialSummary.kt (加入這個 class)

Kotlin
// ... (檔案頂部已有 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

Kotlin
// ...
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

Kotlin
// ...
@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

Kotlin
// /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

Kotlin
// /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 的邏輯了嗎?