Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; import { FinanceBudget, BudgetLine } from '../schemas/budgeting.schema'; @Injectable() export class BudgetService { constructor( @InjectModel(FinanceBudget.name) private readonly budgetModel: Model<FinanceBudget>, @InjectModel(BudgetLine.name) private readonly lineModel: Model<BudgetLine>, ) {} async createBudget(tenantId: string, data: any): Promise<FinanceBudget> { const doc = new this.budgetModel({ ...data, tenantId: new Types.ObjectId(tenantId), legalEntityId: new Types.ObjectId(data.legalEntityId), fiscalYearId: new Types.ObjectId(data.fiscalYearId) }); return doc.save(); } async addBudgetLine(data: any): Promise<BudgetLine> { const line = new this.lineModel({ ...data, budgetId: new Types.ObjectId(data.budgetId), ledgerAccountId: new Types.ObjectId(data.ledgerAccountId) }); return line.save(); } async checkBudgetLimit(tenantId: string, params: { legalEntityId: string; ledgerAccountId: string; amountMinor: number; dimensions: Record<string, string>; mode: 'warning' | 'soft_block' | 'hard_block'; }): Promise<{ isAllowed: boolean; message: string }> { // Locate active budget matching dimensions const budget = await this.budgetModel.findOne({ tenantId, legalEntityId: params.legalEntityId, status: 'approved' }).exec(); if (!budget) { return { isAllowed: true, message: 'No active approved budget found' }; } const line = await this.lineModel.findOne({ budgetId: budget._id, ledgerAccountId: params.ledgerAccountId }).exec(); if (!line) { return { isAllowed: true, message: 'No budget line registered for this account' }; } const totalUsage = line.actualAmountMinor + line.committedAmountMinor + params.amountMinor; if (totalUsage > line.budgetAmountMinor) { const overage = totalUsage - line.budgetAmountMinor; if (params.mode === 'hard_block') { return { isAllowed: false, message: `Budget exceeded for account by ${overage} minor units (Hard Block limit)` }; } else { return { isAllowed: true, message: `Budget warning: Exceeded limit for account by ${overage} minor units` }; } } return { isAllowed: true, message: 'Within budget limit' }; } async commitFunds(budgetId: string, ledgerAccountId: string, amountMinor: number): Promise<BudgetLine> { const line = await this.lineModel.findOneAndUpdate( { budgetId, ledgerAccountId }, { $inc: { committedAmountMinor: amountMinor } }, { new: true } ).exec(); if (!line) throw new NotFoundException('Budget line not found'); return line; } } |