/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { firstValueFrom } from 'rxjs'; import { Dates } from '../core/utils/dates'; import { LoansService } from '../loans/loans.service'; import { ProductsService } from '../products/products.service'; import { SettingsService } from '../settings/settings.service'; import { LoanApplication, ORIGINATION_PRODUCTS } from './loan-origination-data'; export interface RealLoanCreationResult { loanId: number; loanAccountNo: string; } const cleanName = (name: string): string => name.trim().toLowerCase().replace(/\s+/g, ' '); /** Creates, approves and disburses a real Fineract loan account for an Existing-Customer * application at the moment an officer disburses it -- see loan-fineract-bridge plan for why this * only fires at final disbursement rather than at wizard-submit or Committee sanction. Every * mandatory Fineract field this mock pipeline never collected (loan purpose, interest type, * amortization type, interest calculation period, repayment strategy) is pulled from the real * loan product's own template rather than guessed, so it's always valid for whatever that * Fineract instance actually has configured. */ export async function createRealLoanAccount( app: LoanApplication, loansService: LoansService, productsService: ProductsService, dateUtils: Dates, settings: SettingsService ): Promise { const mockProduct = ORIGINATION_PRODUCTS.find((p) => p.code === app.productCode); const wantedName = cleanName(mockProduct?.name || app.productName); const products: any[] = (await firstValueFrom(productsService.getLoanProducts())) || []; const matchedProduct = products.find((p) => cleanName(p.name) === wantedName); if (!matchedProduct) { throw new Error( `No Fineract loan product named "${mockProduct?.name || app.productName}" was found -- ask an admin to configure one before disbursing.` ); } const template: any = await firstValueFrom(productsService.getLoanProduct(String(matchedProduct.id), true)); const repaymentEvery: number = template.repaymentEvery || 1; const locale = settings.language.code; const dateFormat = settings.dateFormat; const today = dateUtils.formatDate(new Date(), dateFormat); const payload = { clientId: app.clientId, productId: matchedProduct.id, loanType: 'individual', principal: app.requestedAmount, loanTermFrequency: app.tenureMonths, loanTermFrequencyType: 2, // Months numberOfRepayments: Math.max(1, Math.round(app.tenureMonths / repaymentEvery)), repaymentEvery, repaymentFrequencyType: template.repaymentFrequencyType?.id, interestRatePerPeriod: app.interestRate, interestType: template.interestType?.id, interestCalculationPeriodType: template.interestCalculationPeriodType?.id, amortizationType: template.amortizationType?.id, transactionProcessingStrategyCode: template.transactionProcessingStrategyCode, submittedOnDate: today, expectedDisbursementDate: today, dateFormat, locale }; const created: any = await firstValueFrom(loansService.createLoansAccount(payload)); const loanId: number = created.loanId ?? created.resourceId; await firstValueFrom( loansService.loanActionButtons(loanId, 'approve', { approvedOnDate: today, dateFormat, locale }) ); await firstValueFrom( loansService.loanActionButtons(loanId, 'disburse', { actualDisbursementDate: today, transactionAmount: app.requestedAmount, dateFormat, locale }) ); const account: any = await firstValueFrom(loansService.getLoanAccountResource(String(loanId), '')); return { loanId, loanAccountNo: account?.accountNo || String(loanId) }; }