All files / src/domains/finance/services budget.service.ts

0% Statements 0/30
0% Branches 0/18
0% Functions 0/5
0% Lines 0/27

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;
  }
}