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

0% Statements 0/38
0% Branches 0/22
0% Functions 0/5
0% Lines 0/34

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 91                                                                                                                                                                                     
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { AllocationRule } from '../schemas/allocations.schema';
import { PostingEngineService } from './posting-engine.service';
import { JournalEntry, JournalLine } from '../schemas/journal-entry.schema';
 
@Injectable()
export class AllocationService {
  constructor(
    @InjectModel(AllocationRule.name)
    private readonly ruleModel: Model<AllocationRule>,
    @InjectModel(JournalEntry.name)
    private readonly journalModel: Model<JournalEntry>,
    @InjectModel(JournalLine.name)
    private readonly lineModel: Model<JournalLine>,
    private readonly postingEngine: PostingEngineService,
  ) {}
 
  async createAllocationRule(tenantId: string, data: any): Promise<AllocationRule> {
    const doc = new this.ruleModel({
      ...data,
      tenantId: new Types.ObjectId(tenantId),
      sourceAccountId: new Types.ObjectId(data.sourceAccountId),
      distributions: data.distributions.map((d: any) => ({
        targetAccountId: new Types.ObjectId(d.targetAccountId),
        ratioDecimal: d.ratioDecimal,
        dimensions: d.dimensions
      }))
    });
    return doc.save();
  }
 
  async runAllocationRule(tenantId: string, ruleId: string, totalAmountMinor: number, userId: string): Promise<JournalEntry> {
    const rule = await this.ruleModel.findOne({ _id: ruleId, tenantId }).exec();
    if (!rule) throw new NotFoundException('Allocation Rule not found');
 
    const jv = new this.journalModel({
      tenantId: new Types.ObjectId(tenantId),
      legalEntityId: new Types.ObjectId(),
      accountingBookId: new Types.ObjectId(),
      journalNumber: `AL-${Date.now()}`,
      postingDate: new Date(),
      status: 'draft',
      journalSource: 'Adjustment',
      currency: 'INR',
      exchangeRate: 1.0
    });
    await jv.save();
 
    // Source Account Credit (reducing cost)
    await this.lineModel.create({
      journalEntryId: jv._id,
      ledgerAccountId: rule.sourceAccountId,
      debitAmountMinor: 0,
      creditAmountMinor: totalAmountMinor,
      description: `Allocation distribution: source charge out`
    });
 
    // Distributions Debits
    for (const dist of rule.distributions) {
      const charge = Math.round(totalAmountMinor * dist.ratioDecimal);
      await this.lineModel.create({
        journalEntryId: jv._id,
        ledgerAccountId: dist.targetAccountId,
        debitAmountMinor: charge,
        creditAmountMinor: 0,
        description: `Allocation receipt: distributed overhead`,
        dimensions: dist.dimensions
      });
    }
 
    // Adjust rounding difference on the first target account if any
    const lines = await this.lineModel.find({ journalEntryId: jv._id }).exec();
    let totalDebit = 0;
    let targetLines = lines.filter(l => l.debitAmountMinor > 0);
    for (const l of targetLines) {
      totalDebit += l.debitAmountMinor;
    }
    const diff = totalAmountMinor - totalDebit;
    if (diff !== 0 && targetLines.length > 0) {
      const firstLine = targetLines[0];
      firstLine.debitAmountMinor += diff;
      await firstLine.save();
    }
 
    await this.postingEngine.postJournalEntry(tenantId, jv._id.toString(), userId);
    return jv;
  }
}