# 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! ``` --- ## **💡 Recommended Solutions:** ### **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 --- ### **Option 2: Reserved Balance (Recommended)** **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:** ```typescript // 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:** ```prisma model Wallet { // ... existing fields reservedBalance Decimal @default(0) @db.Decimal(18, 2) } ``` 2. **Update Goals Service:** ```typescript // 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 } } }); ``` 3. **Update Transaction Validation:** ```typescript // Check available balance instead of total balance const availableBalance = wallet.totalBalance - wallet.reservedBalance; if (transactionAmount > availableBalance) { throw new Error('Insufficient available balance'); } ``` 4. **Update Frontend:** ```tsx // Show both balances
Total: {formatCurrency(wallet.totalBalance)}
Reserved: {formatCurrency(wallet.reservedBalance)}
Available: {formatCurrency(wallet.availableBalance)}