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
This commit is contained in:
343
GOALS_WALLET_BEHAVIOR.md
Executable file
343
GOALS_WALLET_BEHAVIOR.md
Executable file
@@ -0,0 +1,343 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user