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

344 lines
11 KiB
Markdown
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!
```
---
## **💡 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
<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)