Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export class LoanActionButtonResolver {
if (loanActionButton === 'Assign Loan Officer' || loanActionButton === 'Change Loan Officer') {
return this.loansService.getLoanTemplate(loanId);
} else if (loanActionButton === 'Make Repayment') {
return this.loansService.getLoanActionTemplate(loanId, 'repayment');
return this.loanProductService.isLoanProduct
? this.loansService.getLoanActionTemplate(loanId, 'repayment')
: this.loansService.getWorkingCapitalLoanActionTemplate(loanId, 'repayment');
} else if (loanActionButton === 'Goodwill Credit') {
return this.loansService.getLoanActionTemplate(loanId, 'goodwillCredit');
} else if (loanActionButton === 'Interest Payment Waiver') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
AfterViewInit,
ChangeDetectorRef,
Component,
DestroyRef,
QueryList,
ViewChild,
ViewChildren,
inject
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';

/** Custom Services */
Expand Down Expand Up @@ -62,6 +64,7 @@ import { Dates } from 'app/core/utils/dates';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CreateLoansAccountComponent extends LoanProductBaseComponent implements AfterViewInit {
private readonly destroyRef = inject(DestroyRef);
private route = inject(ActivatedRoute);
private loansService = inject(LoansService);
private settingsService = inject(SettingsService);
Expand Down Expand Up @@ -110,12 +113,12 @@ export class CreateLoansAccountComponent extends LoanProductBaseComponent implem
constructor() {
super();
this.loanProductsBasicDetails = [];
this.route.data.subscribe(
(data: { loansAccountTemplate: any; loanProductsBasicDetails: LoanProductBasicDetails[] }) => {
this.route.data
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data: { loansAccountTemplate: any; loanProductsBasicDetails: LoanProductBasicDetails[] }) => {
this.loanProductsBasicDetails = data.loanProductsBasicDetails;
this.loansAccountTemplate = data.loansAccountTemplate;
}
);
});
}

ngAfterViewInit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
MatDialogRef,
MAT_DIALOG_DATA,
Expand Down Expand Up @@ -34,6 +35,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoansAccountAddCollateralDialogComponent implements OnInit {
private readonly destroyRef = inject(DestroyRef);
dialogRef = inject<MatDialogRef<LoansAccountAddCollateralDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
private formBuilder = inject(UntypedFormBuilder);
Expand Down Expand Up @@ -81,16 +83,20 @@ export class LoansAccountAddCollateralDialogComponent implements OnInit {
* Subscribe to Form controls value changes
*/
buildDependencies() {
this.addCollateralForm.controls.collateral.valueChanges.subscribe((collateral: any) => {
this.collateralData = collateral;
this.maxQuantity = collateral.quantity;
});
this.addCollateralForm.controls.collateral.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((collateral: any) => {
this.collateralData = collateral;
this.maxQuantity = collateral.quantity;
});

this.addCollateralForm.controls.quantity.valueChanges.subscribe((quantity: any) => {
this.addCollateralForm.patchValue({
totalValue: this.collateralData.basePrice * quantity,
totalCollateralValue: (this.collateralData.basePrice * this.collateralData.pctToBase * quantity) / 100
this.addCollateralForm.controls.quantity.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((quantity: any) => {
this.addCollateralForm.patchValue({
totalValue: this.collateralData.basePrice * quantity,
totalCollateralValue: (this.collateralData.basePrice * this.collateralData.pctToBase * quantity) / 100
});
Comment on lines +93 to +99
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard quantity calculations when no collateral is selected.

At Line 97 and Line 98, this.collateralData is dereferenced without a guard. Entering quantity first can throw at runtime.

Proposed fix
     this.addCollateralForm.controls.quantity.valueChanges
       .pipe(takeUntilDestroyed(this.destroyRef))
       .subscribe((quantity: any) => {
+        if (!this.collateralData || quantity === null || quantity === '') {
+          this.addCollateralForm.patchValue(
+            { totalValue: '', totalCollateralValue: '' },
+            { emitEvent: false }
+          );
+          return;
+        }
+
+        const basePrice = Number(this.collateralData.basePrice) || 0;
+        const pctToBase = Number(this.collateralData.pctToBase) || 0;
+        const qty = Number(quantity) || 0;
         this.addCollateralForm.patchValue({
-          totalValue: this.collateralData.basePrice * quantity,
-          totalCollateralValue: (this.collateralData.basePrice * this.collateralData.pctToBase * quantity) / 100
+          totalValue: basePrice * qty,
+          totalCollateralValue: (basePrice * pctToBase * qty) / 100
         });
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component.ts`
around lines 93 - 99, The subscription in
addCollateralForm.controls.quantity.valueChanges is dereferencing
this.collateralData (used to compute totalValue and totalCollateralValue)
without guarding for null/undefined; update the callback in
loans-account-add-collateral-dialog.component.ts to first check that
this.collateralData exists (or provide safe defaults) before using its
properties — e.g., use a truthy check or optional chaining on
this.collateralData and fallback 0 for basePrice and pctToBase so patchValue
only writes computed numbers and never throws when quantity is entered before a
collateral is selected.

});
});
}
}
12 changes: 7 additions & 5 deletions src/app/loans/edit-loans-account/edit-loans-account.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { ChangeDetectionStrategy, Component, ViewChild, inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, DestroyRef, ViewChild, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { LoansService } from '../loans.service';
import { LoansAccountDetailsStepComponent } from '../loans-account-stepper/loans-account-details-step/loans-account-details-step.component';
Expand Down Expand Up @@ -47,6 +48,7 @@ import { LoanProductBaseComponent } from 'app/products/loan-products/common/loan
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditLoansAccountComponent extends LoanProductBaseComponent {
private readonly destroyRef = inject(DestroyRef);
private route = inject(ActivatedRoute);
private dateUtils = inject(Dates);
private loansService = inject(LoansService);
Expand Down Expand Up @@ -79,8 +81,9 @@ export class EditLoansAccountComponent extends LoanProductBaseComponent {
this.loanProductService.initialize(LoanProductBaseComponent.resolveProductTypeDefault(this.route, 'loan'));

this.loanId = this.route.snapshot.params['loanId'];
this.route.data.subscribe(
(data: { loansAccountAndTemplate: any; loanProductsBasicDetails: LoanProductBasicDetails[] }) => {
this.route.data
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data: { loansAccountAndTemplate: any; loanProductsBasicDetails: LoanProductBasicDetails[] }) => {
this.loansAccountAndTemplate = data.loansAccountAndTemplate;
if (this.loanProductService.isLoanProduct) {
this.loansAccountProductTemplate = data.loansAccountAndTemplate;
Expand All @@ -92,8 +95,7 @@ export class EditLoansAccountComponent extends LoanProductBaseComponent {
);
}
this.loanProductsBasicDetails = data.loanProductsBasicDetails;
}
);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
*/

/** Angular Imports */
import { ChangeDetectionStrategy, Component, QueryList, ViewChild, ViewChildren, inject } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
QueryList,
ViewChild,
ViewChildren,
inject
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { I18nService } from 'app/core/i18n/i18n.service';
import { ActivatedRoute, Router } from '@angular/router';

Expand Down Expand Up @@ -51,6 +60,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CreateGlimAccountComponent {
private readonly destroyRef = inject(DestroyRef);
private route = inject(ActivatedRoute);
private router = inject(Router);
private loansService = inject(LoansService);
Expand Down Expand Up @@ -97,10 +107,12 @@ export class CreateGlimAccountComponent {
* @param {ClientsService} clientService Client Service
*/
constructor() {
this.route.data.subscribe((data: { loansAccountTemplate: any; groupsData: any }) => {
this.loansAccountTemplate = data.loansAccountTemplate;
this.dataSource = data.groupsData.activeClientMembers;
});
this.route.data
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data: { loansAccountTemplate: any; groupsData: any }) => {
this.loansAccountTemplate = data.loansAccountTemplate;
this.dataSource = data.groupsData.activeClientMembers;
});
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/app/loans/glim-account/glim-account.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { ChangeDetectionStrategy, Component, OnInit, ViewChild, inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, ViewChild, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import {
Expand Down Expand Up @@ -53,6 +54,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class GlimAccountComponent implements OnInit {
private readonly destroyRef = inject(DestroyRef);
private route = inject(ActivatedRoute);
dialog = inject(MatDialog);

Expand Down Expand Up @@ -81,7 +83,7 @@ export class GlimAccountComponent implements OnInit {
* @param {MatDialog} dialog Dialog reference.
*/
constructor() {
this.route.data.subscribe((data: { glimData: any }) => {
this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { glimData: any }) => {
this.glimOverviewData = data.glimData;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
OnInit,
Input,
Output,
EventEmitter,
OnDestroy,
inject
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { SettingsService } from 'app/settings/settings.service';
Expand All @@ -26,8 +27,7 @@ import { TranslateService } from '@ngx-translate/core';
/** Custom Services */
import { LoansService } from '../../loans.service';
import { Commons } from 'app/core/utils/commons';
import { takeUntil } from 'rxjs/operators';
import { ReplaySubject, Subject } from 'rxjs';
import { ReplaySubject } from 'rxjs';
import { MatTooltip } from '@angular/material/tooltip';
import { NgxMatSelectSearchModule } from 'ngx-mat-select-search';
import { AsyncPipe } from '@angular/common';
Expand Down Expand Up @@ -62,7 +62,8 @@ import { LoanProductBaseComponent } from 'app/products/loan-products/common/loan
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoansAccountDetailsStepComponent extends LoanProductBaseComponent implements OnInit, OnDestroy {
export class LoansAccountDetailsStepComponent extends LoanProductBaseComponent implements OnInit {
private readonly destroyRef = inject(DestroyRef);
private formBuilder = inject(UntypedFormBuilder);
private loansService = inject(LoansService);
private route = inject(ActivatedRoute);
Expand Down Expand Up @@ -106,8 +107,6 @@ export class LoansAccountDetailsStepComponent extends LoanProductBaseComponent i
protected productData: ReplaySubject<string[]> = new ReplaySubject<string[]>(1);
/** control for the filter select */
protected filterFormCtrl: UntypedFormControl = new UntypedFormControl('');
/** Subject that emits when the component has been destroyed. */
protected _onDestroy = new Subject<void>();

productSelected: LoanProductBasicDetails | null = null;

Expand Down Expand Up @@ -167,17 +166,12 @@ export class LoansAccountDetailsStepComponent extends LoanProductBaseComponent i
this.getProductTemplate(false);
}
}
this.filterFormCtrl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => {
this.filterFormCtrl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.searchItem();
});
this.productData.next(this.productList.slice());
}

ngOnDestroy(): void {
this._onDestroy.next();
this._onDestroy.complete();
}

searchItem(): void {
if (this.productList) {
const search: string = this.filterFormCtrl.value.toLowerCase();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
OnInit,
Input,
OnChanges,
SimpleChanges,
inject
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute } from '@angular/router';
Expand Down Expand Up @@ -99,6 +101,7 @@ interface DisbursementData {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoansAccountTermsStepComponent extends LoanProductBaseComponent implements OnInit, OnChanges {
private readonly destroyRef = inject(DestroyRef);
private formBuilder = inject(UntypedFormBuilder);
private settingsService = inject(SettingsService);
private route = inject(ActivatedRoute);
Expand Down Expand Up @@ -515,42 +518,57 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
const repaymentFrequencyNthDayType = this.loansAccountTermsForm.get('repaymentFrequencyNthDayType');
const repaymentFrequencyDayOfWeekType = this.loansAccountTermsForm.get('repaymentFrequencyDayOfWeekType');

this.loansAccountTermsForm.get('repaymentFrequencyType')?.valueChanges.subscribe((repaymentFrequencyType) => {
repaymentFrequencyNthDayType?.setValidators(null);
repaymentFrequencyDayOfWeekType?.setValidators(null);
this.loansAccountTermsForm
.get('repaymentFrequencyType')
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((repaymentFrequencyType) => {
repaymentFrequencyNthDayType?.setValidators(null);
repaymentFrequencyDayOfWeekType?.setValidators(null);

setTimeout(() => {
repaymentFrequencyNthDayType?.updateValueAndValidity();
repaymentFrequencyDayOfWeekType?.updateValueAndValidity();
setTimeout(() => {
repaymentFrequencyNthDayType?.updateValueAndValidity();
repaymentFrequencyDayOfWeekType?.updateValueAndValidity();
});
});
});
}

/** Custom Listeners for the form to calculate Loan Term */
setLoanTermListener() {
this.loansAccountTermsForm.get('numberOfRepayments')?.valueChanges.subscribe((numberOfRepayments) => {
const repaymentEvery: number = this.loansAccountTermsForm.value.repaymentEvery;
this.calculateLoanTerm(numberOfRepayments, repaymentEvery);
});
this.loansAccountTermsForm
.get('numberOfRepayments')
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((numberOfRepayments) => {
const repaymentEvery: number = this.loansAccountTermsForm.value.repaymentEvery;
this.calculateLoanTerm(numberOfRepayments, repaymentEvery);
});

this.loansAccountTermsForm.get('repaymentEvery')?.valueChanges.subscribe((repaymentEvery) => {
const numberOfRepayments: number = this.loansAccountTermsForm.value.numberOfRepayments;
this.calculateLoanTerm(numberOfRepayments, repaymentEvery);
});
this.loansAccountTermsForm
.get('repaymentEvery')
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((repaymentEvery) => {
const numberOfRepayments: number = this.loansAccountTermsForm.value.numberOfRepayments;
this.calculateLoanTerm(numberOfRepayments, repaymentEvery);
});

this.loansAccountTermsForm.get('loanTermFrequencyType')?.valueChanges.subscribe((loanTermFrequencyType) => {
this.loansAccountTermsForm.patchValue({ repaymentFrequencyType: loanTermFrequencyType });
});
this.loansAccountTermsForm
.get('loanTermFrequencyType')
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((loanTermFrequencyType) => {
this.loansAccountTermsForm.patchValue({ repaymentFrequencyType: loanTermFrequencyType });
});

this.loansAccountTermsForm.get('amortizationType')?.valueChanges.subscribe((amortizationType) => {
if (amortizationType === 0) {
// Equal Principal Payments
this.loansAccountTermsForm.addControl('fixedPrincipalPercentagePerInstallment', new UntypedFormControl(''));
} else {
// Equal Installments
this.loansAccountTermsForm.removeControl('fixedPrincipalPercentagePerInstallment');
}
});
this.loansAccountTermsForm
.get('amortizationType')
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((amortizationType) => {
if (amortizationType === 0) {
// Equal Principal Payments
this.loansAccountTermsForm.addControl('fixedPrincipalPercentagePerInstallment', new UntypedFormControl(''));
} else {
// Equal Installments
this.loansAccountTermsForm.removeControl('fixedPrincipalPercentagePerInstallment');
}
});
}

/** Prevent negative values in numeric fields */
Expand All @@ -568,7 +586,7 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
numericFieldsWithMinZero.forEach((fieldName) => {
const control = this.loansAccountTermsForm.get(fieldName);
if (control) {
control.valueChanges.subscribe((value) => {
control.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
if (typeof value === 'number' && value < 0) {
control.setValue(0, { emitEvent: false });
}
Expand All @@ -577,7 +595,7 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
});
const interestRateControl = this.loansAccountTermsForm.get('interestRatePerPeriod');
if (interestRateControl) {
interestRateControl.valueChanges.subscribe((value) => {
interestRateControl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
if (typeof value === 'number' && value < 0.01) {
interestRateControl.setValue(0.01, { emitEvent: false });
}
Expand Down
Loading
Loading