"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.GoalsService = void 0; const common_1 = require("@nestjs/common"); const prisma_service_1 = require("../prisma/prisma.service"); const wallet_balance_service_1 = require("../wallets/wallet-balance.service"); const library_1 = require("@prisma/client/runtime/library"); let GoalsService = class GoalsService { prisma; walletBalanceService; constructor(prisma, walletBalanceService) { this.prisma = prisma; this.walletBalanceService = walletBalanceService; } async create(userId, createGoalDto) { const goal = await this.prisma.goal.create({ data: { userId, name: createGoalDto.name, description: createGoalDto.description, targetAmount: new library_1.Decimal(createGoalDto.targetAmount), currency: createGoalDto.currency, targetDate: createGoalDto.targetDate ? new Date(createGoalDto.targetDate) : null, imageUrl: createGoalDto.imageUrl, category: createGoalDto.category, }, include: { allocations: true, milestones: true, }, }); await this.createMilestones(goal.id, new library_1.Decimal(createGoalDto.targetAmount)); return this.findOne(userId, goal.id); } async findAll(userId, status) { const where = { userId }; if (status) { where.status = status; } return this.prisma.goal.findMany({ where, include: { allocations: { include: { wallet: true, }, }, milestones: { orderBy: { percentage: 'asc' }, }, }, orderBy: { createdAt: 'desc' }, }); } async findOne(userId, id) { const goal = await this.prisma.goal.findFirst({ where: { id, userId }, include: { allocations: { include: { wallet: true, }, orderBy: { createdAt: 'desc' }, }, milestones: { orderBy: { percentage: 'asc' }, }, }, }); if (!goal) { throw new common_1.NotFoundException('Goal not found'); } return goal; } async update(userId, id, updateGoalDto) { await this.findOne(userId, id); const updateData = {}; if (updateGoalDto.name !== undefined) updateData.name = updateGoalDto.name; if (updateGoalDto.description !== undefined) updateData.description = updateGoalDto.description; if (updateGoalDto.targetAmount !== undefined) { updateData.targetAmount = new library_1.Decimal(updateGoalDto.targetAmount); await this.prisma.goalMilestone.deleteMany({ where: { goalId: id } }); await this.createMilestones(id, new library_1.Decimal(updateGoalDto.targetAmount)); } if (updateGoalDto.currency !== undefined) updateData.currency = updateGoalDto.currency; if (updateGoalDto.targetDate !== undefined) { updateData.targetDate = updateGoalDto.targetDate ? new Date(updateGoalDto.targetDate) : null; } if (updateGoalDto.imageUrl !== undefined) updateData.imageUrl = updateGoalDto.imageUrl; if (updateGoalDto.category !== undefined) updateData.category = updateGoalDto.category; if (updateGoalDto.status !== undefined) { updateData.status = updateGoalDto.status; if (updateGoalDto.status === 'completed') { updateData.completedAt = new Date(); } } const goal = await this.prisma.goal.update({ where: { id }, data: updateData, include: { allocations: { include: { wallet: true, }, }, milestones: true, }, }); return goal; } async remove(userId, id) { await this.findOne(userId, id); await this.prisma.goal.delete({ where: { id }, }); return { message: 'Goal deleted successfully' }; } async addAllocation(userId, goalId, createAllocationDto) { const goal = await this.findOne(userId, goalId); const wallet = await this.prisma.wallet.findFirst({ where: { id: createAllocationDto.walletId, userId, deletedAt: null, }, }); if (!wallet) { throw new common_1.NotFoundException('Wallet not found'); } const walletBalance = await this.walletBalanceService.calculateBalance(wallet.id); const allocationAmountInIDR = new library_1.Decimal(createAllocationDto.amount); let amountInWalletCurrency = allocationAmountInIDR; let exchangeRate = new library_1.Decimal(1); if (wallet.currency && wallet.currency !== 'IDR') { exchangeRate = await this.getExchangeRate('IDR', wallet.currency); amountInWalletCurrency = allocationAmountInIDR.times(exchangeRate); } else if (wallet.kind === 'asset' && wallet.pricePerUnit) { amountInWalletCurrency = allocationAmountInIDR.dividedBy(wallet.pricePerUnit); } if (walletBalance.availableBalance.lessThan(amountInWalletCurrency)) { const currency = wallet.kind === 'money' ? wallet.currency : wallet.unit; throw new common_1.BadRequestException(`Insufficient available balance. Available: ${walletBalance.availableBalance.toString()} ${currency}, Reserved: ${walletBalance.reservedBalance.toString()} ${currency}`); } const allocation = await this.prisma.goalAllocation.create({ data: { goalId, walletId: wallet.id, amount: allocationAmountInIDR, currency: 'IDR', exchangeRate: wallet.currency !== 'IDR' ? exchangeRate : null, amountInGoalCurrency: allocationAmountInIDR, notes: createAllocationDto.notes, createdBy: userId, }, include: { wallet: true, }, }); const newCurrentAmount = new library_1.Decimal(goal.currentAmount).plus(allocationAmountInIDR); await this.prisma.goal.update({ where: { id: goalId }, data: { currentAmount: newCurrentAmount }, }); await this.prisma.wallet.update({ where: { id: wallet.id }, data: { reservedBalance: { increment: amountInWalletCurrency, }, }, }); await this.updateMilestones(goalId, newCurrentAmount); return allocation; } async removeAllocation(userId, goalId, allocationId) { const goal = await this.findOne(userId, goalId); const allocation = await this.prisma.goalAllocation.findFirst({ where: { id: allocationId, goalId, }, }); if (!allocation) { throw new common_1.NotFoundException('Allocation not found'); } const newCurrentAmount = new library_1.Decimal(goal.currentAmount).minus(allocation.amountInGoalCurrency); await this.prisma.goal.update({ where: { id: goalId }, data: { currentAmount: newCurrentAmount.greaterThanOrEqualTo(0) ? newCurrentAmount : new library_1.Decimal(0) }, }); await this.prisma.wallet.update({ where: { id: allocation.walletId }, data: { reservedBalance: { decrement: allocation.amount, }, }, }); await this.prisma.goalAllocation.delete({ where: { id: allocationId }, }); await this.updateMilestones(goalId, newCurrentAmount); return { message: 'Allocation removed successfully' }; } async getStats(userId) { const goals = await this.prisma.goal.findMany({ where: { userId }, include: { allocations: true, }, }); const totalGoals = goals.length; const activeGoals = goals.filter(g => g.status === 'active').length; const completedGoals = goals.filter(g => g.status === 'completed').length; let totalTargetAmount = new library_1.Decimal(0); let totalCurrentAmount = new library_1.Decimal(0); for (const goal of goals) { if (goal.status === 'active') { totalTargetAmount = totalTargetAmount.plus(goal.targetAmount); totalCurrentAmount = totalCurrentAmount.plus(goal.currentAmount); } } const overallProgress = totalTargetAmount.greaterThan(0) ? totalCurrentAmount.dividedBy(totalTargetAmount).times(100).toNumber() : 0; return { totalGoals, activeGoals, completedGoals, totalTargetAmount: totalTargetAmount.toNumber(), totalCurrentAmount: totalCurrentAmount.toNumber(), overallProgress: Math.round(overallProgress * 100) / 100, }; } async createMilestones(goalId, targetAmount) { const percentages = [25, 50, 75, 100]; for (const percentage of percentages) { await this.prisma.goalMilestone.create({ data: { goalId, percentage, targetAmount: targetAmount.times(percentage).dividedBy(100), }, }); } } async updateMilestones(goalId, currentAmount) { const milestones = await this.prisma.goalMilestone.findMany({ where: { goalId }, orderBy: { percentage: 'asc' }, }); for (const milestone of milestones) { if (currentAmount.greaterThanOrEqualTo(milestone.targetAmount) && !milestone.achievedAt) { await this.prisma.goalMilestone.update({ where: { id: milestone.id }, data: { achievedAt: new Date() }, }); } else if (currentAmount.lessThan(milestone.targetAmount) && milestone.achievedAt) { await this.prisma.goalMilestone.update({ where: { id: milestone.id }, data: { achievedAt: null }, }); } } } async getExchangeRate(fromCurrency, toCurrency) { const rates = { 'USD': 15000, 'EUR': 16500, 'GBP': 19000, 'JPY': 100, 'SGD': 11000, 'MYR': 3500, 'IDR': 1, }; const fromRate = rates[fromCurrency] || 1; const toRate = rates[toCurrency] || 1; return new library_1.Decimal(fromRate).dividedBy(toRate); } }; exports.GoalsService = GoalsService; exports.GoalsService = GoalsService = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [prisma_service_1.PrismaService, wallet_balance_service_1.WalletBalanceService]) ], GoalsService); //# sourceMappingURL=goals.service.js.map