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:
Dwindi Ramadhana
2026-06-17 20:40:00 +07:00
parent 35e93b826a
commit 6a6e74562c
401 changed files with 9517 additions and 397 deletions

View File

@@ -0,0 +1,105 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WalletBalanceService = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("../prisma/prisma.service");
const library_1 = require("@prisma/client/runtime/library");
let WalletBalanceService = class WalletBalanceService {
prisma;
constructor(prisma) {
this.prisma = prisma;
}
async calculateBalance(walletId) {
const wallet = await this.prisma.wallet.findUnique({
where: { id: walletId },
include: {
transactions: {
orderBy: { date: 'asc' },
},
},
});
if (!wallet) {
throw new Error('Wallet not found');
}
console.log(`[Balance] Calculating for wallet ${walletId}:`, {
name: wallet.name,
kind: wallet.kind,
initialAmount: wallet.initialAmount?.toString(),
transactionCount: wallet.transactions.length,
});
const reservedBalance = new library_1.Decimal(wallet.reservedBalance || 0);
if (wallet.kind === 'money') {
let totalBalance = wallet.initialAmount ? new library_1.Decimal(wallet.initialAmount) : new library_1.Decimal(0);
for (const tx of wallet.transactions) {
if (tx.direction === 'in') {
totalBalance = totalBalance.plus(tx.amount);
}
else {
totalBalance = totalBalance.minus(tx.amount);
}
}
const availableBalance = totalBalance.minus(reservedBalance);
return {
walletId: wallet.id,
kind: wallet.kind,
currency: wallet.currency || 'IDR',
totalBalance,
reservedBalance,
availableBalance: availableBalance.greaterThanOrEqualTo(0) ? availableBalance : new library_1.Decimal(0),
pricePerUnit: wallet.pricePerUnit || undefined,
};
}
else {
let totalUnits = wallet.initialAmount ? new library_1.Decimal(wallet.initialAmount) : new library_1.Decimal(0);
for (const tx of wallet.transactions) {
if (tx.direction === 'in') {
totalUnits = totalUnits.plus(tx.amount);
}
else {
totalUnits = totalUnits.minus(tx.amount);
}
}
const pricePerUnit = wallet.pricePerUnit ? new library_1.Decimal(wallet.pricePerUnit) : new library_1.Decimal(0);
const totalValue = totalUnits.times(pricePerUnit);
const reservedUnits = pricePerUnit.greaterThan(0) ? reservedBalance.dividedBy(pricePerUnit) : new library_1.Decimal(0);
const availableUnits = totalUnits.minus(reservedUnits);
const availableValue = totalValue.minus(reservedBalance);
return {
walletId: wallet.id,
kind: wallet.kind,
unit: wallet.unit || 'units',
totalUnits,
pricePerUnit,
totalValue,
totalBalance: totalUnits,
reservedBalance: reservedUnits,
availableBalance: availableUnits.greaterThanOrEqualTo(0) ? availableUnits : new library_1.Decimal(0),
};
}
}
async calculateMultipleBalances(walletIds) {
return Promise.all(walletIds.map(id => this.calculateBalance(id)));
}
async getAllUserWalletBalances(userId) {
const wallets = await this.prisma.wallet.findMany({
where: { userId, deletedAt: null },
select: { id: true },
});
return this.calculateMultipleBalances(wallets.map(w => w.id));
}
};
exports.WalletBalanceService = WalletBalanceService;
exports.WalletBalanceService = WalletBalanceService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], WalletBalanceService);
//# sourceMappingURL=wallet-balance.service.js.map