Files
tabungin/GOALS_WALLET_BEHAVIOR.md
Dwindi Ramadhana 6a6e74562c checkpoint: goals feature, wallet balance, and goals/wallet detail UI
- Add goals feature (models, migrations, API, web pages)
- Add reserved/centralized wallet balance service
- Add wallet detail page and overview components
- Add new UI components (progress, multi-select, FAB)
- Remove stray empty -H/-d files from working tree
2026-06-17 20:40:00 +07:00

11 KiB
Executable File

Goals & Wallet Behavior - How Money Works

📊 Current Implementation: Virtual Allocation (No Freeze)

Your Scenario:

Initial State:

Wallet A: Rp 5,000,000
Wallet B: Rp 2,000,000
Goal: MacbookPro M4 (Target: Rp 4,000,000)

After Adding Money to Goal:

Action 1: Add Rp 2,000,000 from Wallet A to Goal
Action 2: Add Rp 1,000,000 from Wallet B to Goal

What Happens:

Current Behavior (Virtual Allocation):

┌─────────────────────────────────────────────────────────────┐
│ Wallet A                                                    │
├─────────────────────────────────────────────────────────────┤
│ Balance: Rp 5,000,000                                       │
│ Status: STILL AVAILABLE                                     │
│                                                              │
│ ❌ Money is NOT frozen                                      │
│ ❌ Money is NOT deducted                                    │
│ ✅ You can still spend all Rp 5,000,000                    │
│                                                              │
│ Note: Goal allocation is just a "tracking record"          │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Wallet B                                                    │
├─────────────────────────────────────────────────────────────┤
│ Balance: Rp 2,000,000                                       │
│ Status: STILL AVAILABLE                                     │
│                                                              │
│ ❌ Money is NOT frozen                                      │
│ ❌ Money is NOT deducted                                    │
│ ✅ You can still spend all Rp 2,000,000                    │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Goal: MacbookPro M4                                         │
├─────────────────────────────────────────────────────────────┤
│ Target: Rp 4,000,000                                        │
│ Current: Rp 3,000,000 (75%)                                 │
│                                                              │
│ Allocations:                                                │
│ - From Wallet A: Rp 2,000,000                               │
│ - From Wallet B: Rp 1,000,000                               │
│                                                              │
│ ⚠️ This is just a TRACKING record                          │
│ ⚠️ Money still exists in wallets                           │
└─────────────────────────────────────────────────────────────┘

⚠️ Problem with Current Implementation:

Issue: Double Counting

You can spend the same money twice:

Scenario:
1. Add Rp 2,000,000 from Wallet A to Goal ✅
2. Goal shows Rp 2,000,000 saved ✅
3. Spend Rp 2,000,000 from Wallet A for something else ✅
4. Wallet A now has Rp 3,000,000
5. But Goal still shows Rp 2,000,000 saved! ❌

Result: Goal tracking becomes inaccurate!

Option 1: Virtual Allocation (Current - Simple but Inaccurate)

How it works:

  • Goal allocation is just a record
  • Money stays in wallet
  • No restrictions on spending

Pros:

  • Simple to implement (already done)
  • Flexible - can spend money anytime
  • No complex logic needed

Cons:

  • Can spend allocated money elsewhere
  • Goal progress becomes inaccurate
  • User might think they saved money but actually spent it

Best for:

  • Simple goal tracking
  • Users who just want to see progress
  • Not critical if goals are just aspirational

How it works:

  • When you allocate money to a goal, it's "reserved"
  • Wallet shows: Total Balance vs Available Balance
  • Reserved money can't be spent on transactions
  • Can only be used for the goal or released back

Example:

Wallet A:
├── Total Balance: Rp 5,000,000
├── Reserved for Goals: Rp 2,000,000
└── Available Balance: Rp 3,000,000 ← Only this can be spent

When creating transaction:
- System checks Available Balance (not Total Balance)
- If you try to spend Rp 4,000,000 → Error: Insufficient available balance

Implementation:

// Add to Wallet model
interface Wallet {
  totalBalance: number;
  reservedBalance: number;  // ← New field
  availableBalance: number; // = totalBalance - reservedBalance
}

// When adding money to goal
1. Check wallet.availableBalance >= amount
2. Create goal allocation
3. Update wallet.reservedBalance += amount
4. Update goal.currentAmount += amount

// When removing allocation
1. Delete allocation
2. Update wallet.reservedBalance -= amount
3. Update goal.currentAmount -= amount

// When creating transaction
1. Check wallet.availableBalance >= amount (not totalBalance)
2. If sufficient, allow transaction

Pros:

  • Accurate goal tracking
  • Prevents double-spending
  • Clear separation of allocated vs available money
  • User sees exactly how much they can spend

Cons:

  • ⚠️ More complex implementation
  • ⚠️ Need to update wallet model
  • ⚠️ Need to update transaction validation

Option 3: Actual Transfer (Most Strict)

How it works:

  • Create a special "Goal Wallet" for each goal
  • When allocating money, create a transaction that transfers money
  • Money is physically moved from Wallet A → Goal Wallet
  • Goal Wallet balance = Goal progress

Example:

Before:
Wallet A: Rp 5,000,000
Goal Wallet (MacbookPro): Rp 0

Add Rp 2,000,000 to goal:
1. Create transaction: Wallet A → Goal Wallet (Rp 2,000,000)
2. Wallet A: Rp 3,000,000
3. Goal Wallet: Rp 2,000,000

Result:
- Money is actually moved
- Wallet A balance decreases
- Goal Wallet balance increases

Pros:

  • Most accurate
  • Real money movement
  • Easy to understand
  • Can see goal money as a separate wallet

Cons:

  • ⚠️ Creates many wallets
  • ⚠️ More complex to manage
  • ⚠️ Harder to "un-allocate" money

📊 Comparison:

Feature Virtual (Current) Reserved Balance Actual Transfer
Accuracy Low High Very High
Complexity Simple ⚠️ Medium Complex
Flexibility High ⚠️ Medium Low
User Understanding ⚠️ Confusing Clear Very Clear
Double Spending Possible Prevented Prevented
Implementation Time Done ⚠️ 2-3 hours 4-6 hours

🎯 My Recommendation:

Use Option 2: Reserved Balance

Why:

  1. Accurate - Goals show real progress
  2. Prevents confusion - Users know what they can spend
  3. Not too complex - Can implement in 2-3 hours
  4. Flexible - Can still release money if needed

Implementation Steps:

  1. Update Prisma Schema:
model Wallet {
  // ... existing fields
  reservedBalance Decimal @default(0) @db.Decimal(18, 2)
}
  1. Update Goals Service:
// When adding allocation
const wallet = await prisma.wallet.findUnique({ where: { id: walletId } });
const availableBalance = wallet.totalBalance - wallet.reservedBalance;

if (amount > availableBalance) {
  throw new Error('Insufficient available balance');
}

// Update reserved balance
await prisma.wallet.update({
  where: { id: walletId },
  data: { reservedBalance: { increment: amount } }
});
  1. Update Transaction Validation:
// Check available balance instead of total balance
const availableBalance = wallet.totalBalance - wallet.reservedBalance;
if (transactionAmount > availableBalance) {
  throw new Error('Insufficient available balance');
}
  1. Update Frontend:
// Show both balances
<div>
  <p>Total: {formatCurrency(wallet.totalBalance)}</p>
  <p>Reserved: {formatCurrency(wallet.reservedBalance)}</p>
  <p>Available: {formatCurrency(wallet.availableBalance)}</p>
</div>

🤔 What Should We Do?

Questions for you:

  1. Do you want accurate goal tracking?

    • If YES → Implement Reserved Balance
    • If NO → Keep current (Virtual Allocation)
  2. Is it okay if users can spend allocated money elsewhere?

    • If NO → Implement Reserved Balance
    • If YES → Keep current
  3. Do you want to prevent double-spending?

    • If YES → Implement Reserved Balance
    • If NO → Keep current

My suggestion: Implement Reserved Balance because:

  • Goals become meaningful (not just aspirational)
  • Users won't be confused about their actual available money
  • Prevents the "I thought I saved money but I spent it" problem
  • Still flexible (can release reserved money if needed)

Current State Summary:

Right now with your example:

Wallet A: Rp 5,000,000 (can spend all of it)
Wallet B: Rp 2,000,000 (can spend all of it)
Goal: Shows Rp 3,000,000 saved

⚠️ But if you spend from wallets, goal progress doesn't decrease!
⚠️ Goal is just a "wish list" not actual savings

With Reserved Balance:

Wallet A: 
  Total: Rp 5,000,000
  Reserved: Rp 2,000,000
  Available: Rp 3,000,000 ← Can only spend this

Wallet B:
  Total: Rp 2,000,000
  Reserved: Rp 1,000,000
  Available: Rp 1,000,000 ← Can only spend this

Goal: Rp 3,000,000 saved (accurate!)

✅ Goal progress is protected
✅ Can't accidentally spend goal money

What would you like to do? 🤔

  1. Keep current implementation (simple but inaccurate)
  2. Implement Reserved Balance (recommended)
  3. Implement Actual Transfer (most accurate but complex)