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
13 changes: 12 additions & 1 deletion src/app/loans/loans-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ import { LoanBuyDownFeesDataResolver } from './common-resolvers/loan-buy-down-fe
import { LoanOriginatorsTabComponent } from './loans-view/loan-originators-tab/loan-originators-tab.component';
import { LoanOriginatorsResolver } from './common-resolvers/loan-originators.resolver';
import { LoanProductsResolver } from './common-resolvers/loan-products.resolver';
import { LoanPeriodPaymentRatesComponent } from './loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component';
import { LoanPeriodPaymentRatesResolver } from './loans-view/working-capital/common-resolvers/loan-period-payment-rates.resolver';

/** Loans Route. */
const routes: Routes = [
Expand Down Expand Up @@ -149,6 +151,14 @@ const routes: Routes = [
}
]
},
{
path: 'payment-rates',
component: LoanPeriodPaymentRatesComponent,
data: { title: 'Period Payment Rates', breadcrumb: 'Period Payment Rates', routeParamBreadcrumb: false },
resolve: {
loanPaymentRatesData: LoanPeriodPaymentRatesResolver
}
},
Comment on lines +154 to +161
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

Use translation keys instead of hardcoded route labels.

Line 157 introduces new user-facing strings (title/breadcrumb) as literals. Please switch to i18n keys and update extraction so the labels are translatable.

As per coding guidelines, "Use proper i18n variables from @ngx-translate/core for all user-facing strings instead of hardcoded text" and "Run npm run translations:extract to extract i18n variables whenever new strings are added to code."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/loans/loans-routing.module.ts` around lines 154 - 161, Replace the
hardcoded user-facing strings in the route definition for path 'payment-rates'
(data.title and data.breadcrumb) with i18n translation keys (e.g. use keys like
'loans.paymentRates.title' and 'loans.paymentRates.breadcrumb') so the route
config that references LoanPeriodPaymentRatesComponent contains only translation
keys; update any consumers that read route.data to pass keys into the translate
service or use TranslatePipe, and then run the extraction command (npm run
translations:extract) to add the new keys to the translation files.

{
path: 'deferred-income',
data: { title: 'Loans Deferred Income', breadcrumb: 'Deferred income', routeParamBreadcrumb: false },
Expand Down Expand Up @@ -447,7 +457,8 @@ const routes: Routes = [
LoanTermVariationsResolver,
LoanDeferredIncomeDataResolver,
LoanBuyDownFeesDataResolver,
LoanProductsResolver
LoanProductsResolver,
LoanPeriodPaymentRatesResolver
]
})
export class LoansRoutingModule {}
18 changes: 16 additions & 2 deletions src/app/loans/loans-view/loans-view.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,26 @@ <h3>{{ 'labels.heading.Account Overview' | translate }}</h3>
productType: loanProductService.productType.value
}"
routerLinkActive
#dashboard="routerLinkActive"
[active]="dashboard.isActive"
#transactions="routerLinkActive"
[active]="transactions.isActive"
>
{{ 'labels.inputs.Transactions' | translate }}
</a>
}
@if (loanProductService.isWorkingCapital && loanDetailsData.status.active) {
<a
mat-tab-link
[routerLink]="['./payment-rates']"
[queryParams]="{
productType: loanProductService.productType.value
}"
routerLinkActive
#paymentRates="routerLinkActive"
[active]="paymentRates.isActive"
>
{{ 'labels.inputs.Period Payment Rates' | translate }}
</a>
}
@if (loanProductService.isLoanProduct && loanDetailsData.enableIncomeCapitalization) {
<a
mat-tab-link
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* 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/.
*/

/** Angular Imports */
import { Injectable, inject } from '@angular/core';
import { ActivatedRouteSnapshot } from '@angular/router';
import { LoansService } from 'app/loans/loans.service';

/** rxjs Imports */
import { Observable } from 'rxjs';

/**
* Working Capital Period Payment Rates resolver.
*/
@Injectable()
export class LoanPeriodPaymentRatesResolver {
private loansService = inject(LoansService);

/**
* Returns the Loans data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const loanId = route.paramMap.get('loanId') || route.parent.paramMap.get('loanId');
return this.loansService.getWorkingCapitalPeriodPaymentRates(loanId);
Comment on lines +29 to +30
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify resolver route shape and service method signature.
rg -n --type ts -C3 "LoanPeriodPaymentRatesResolver|payment-rates|:loanId" src/app/loans/loans-routing.module.ts
rg -n --type ts -C3 "getWorkingCapitalPeriodPaymentRates\\s*\\(" src/app/loans/loans.service.ts

Repository: openMF/web-app

Length of output: 3052


🏁 Script executed:

# Read the resolver file to see the actual implementation
cat -n src/app/loans/loans-view/working-capital/common-resolvers/loan-period-payment-rates.resolver.ts

Repository: openMF/web-app

Length of output: 1250


🏁 Script executed:

# Check the parent route structure more carefully to understand the nesting
rg -n --type ts -B10 -A10 "path: 'payment-rates'" src/app/loans/loans-routing.module.ts | head -40

Repository: openMF/web-app

Length of output: 907


🏁 Script executed:

# Look at other resolvers in the same directory to see how they handle parent routes
find src/app/loans -name "*.resolver.ts" -type f -exec wc -l {} + | head -10

Repository: openMF/web-app

Length of output: 822


🏁 Script executed:

# Check if other resolvers use parent.paramMap pattern
rg -n "route\.parent" src/app/loans --type ts

Repository: openMF/web-app

Length of output: 8987


Guard route.parent with optional chaining and validate loanId before API call.

Line 29 accesses route.parent.paramMap without null checks, and line 30 calls the service with a potentially null loanId. While other resolvers in the codebase use the same pattern, loan-originators.resolver.ts demonstrates the safer approach with optional chaining. Add null validation to prevent runtime errors.

Recommended fix
   resolve(route: ActivatedRouteSnapshot): Observable<any> {
-    const loanId = route.paramMap.get('loanId') || route.parent.paramMap.get('loanId');
+    const loanId = route.paramMap.get('loanId') ?? route.parent?.paramMap.get('loanId');
+    if (!loanId) {
+      throw new Error('Missing route param: loanId');
+    }
     return this.loansService.getWorkingCapitalPeriodPaymentRates(loanId);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/loans/loans-view/working-capital/common-resolvers/loan-period-payment-rates.resolver.ts`
around lines 29 - 30, The resolver currently accesses route.parent.paramMap
without guarding and passes a possibly null loanId to
loansService.getWorkingCapitalPeriodPaymentRates; update the code in
loan-period-payment-rates.resolver (where loanId is retrieved) to use optional
chaining (route.parent?.paramMap.get('loanId')) and then validate that loanId is
non-null/undefined before calling
this.loansService.getWorkingCapitalPeriodPaymentRates(loanId) — if loanId is
missing, handle it deterministically (e.g., return EMPTY/throw an
Error/redirect) so the service is never called with a null id.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!--
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/.
-->

<div class="container">
<h3>{{ 'labels.inputs.Period Payment Rates' | translate }}</h3>

<div class="layout-row transaction-buttons align-end gap-20px">
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 | 🟡 Minor

Use an 8px-grid spacing token here.

gap-20px falls outside the project’s 8px spacing scale. Prefer an existing 16px/24px utility or move this spacing into SCSS using the project spacing variables.

As per coding guidelines, src/**/*.{scss,html} should “Stick to the 8px grid system for visual design and spacing” and “Leverage SCSS variables defined in src/main.scss and src/theme/mifosx-theme.scss rather than generating custom classes and explicit pixel values”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.html`
at line 12, The div using class "gap-20px" violates the 8px grid — replace that
utility with an existing 8px-grid token (e.g., use a 16px or 24px gap utility
such as "gap-16px" or "gap-24px") or remove the inline gap class and define the
spacing in the component SCSS by styling the .transaction-buttons selector using
the project's spacing variables (the theme/main spacing tokens) so the
layout-row .transaction-buttons uses an 8px-grid-compliant gap.

<button
mat-raised-button
color="primary"
(click)="addPaymentRate()"
*mifosxHasPermission="'UPDATERATE_WORKINGCAPITALLOAN'"
>
{{ 'labels.buttons.Add' | translate }} {{ 'labels.inputs.Period Payment Rate' | translate }}
</button>
</div>

<table mat-table [dataSource]="loanPaymentRatesData">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Id' | translate }}</th>
<td mat-cell *matCellDef="let item" [ngClass]="periodPaymentRateStyle(item.reversed)">
{{ item.id }}
</td>
</ng-container>
<ng-container matColumnDef="effectiveDate">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Effective Date' | translate }}</th>
<td mat-cell *matCellDef="let item" [ngClass]="periodPaymentRateStyle(item.reversed)">
{{ item.effectiveDate | dateFormat }}
</td>
</ng-container>
<ng-container matColumnDef="previousRate">
<th mat-header-cell *matHeaderCellDef>
{{ 'labels.buttons.Previous' | translate }} {{ 'labels.inputs.Rate' | translate }}
</th>
<td mat-cell *matCellDef="let item" [ngClass]="periodPaymentRateStyle(item.reversed)">
{{ item.previousRate | formatNumber }} %
</td>
</ng-container>
<ng-container matColumnDef="newRate">
<th mat-header-cell *matHeaderCellDef>
{{ 'labels.catalogs.New' | translate }} {{ 'labels.inputs.Rate' | translate }}
</th>
<td mat-cell *matCellDef="let item" [ngClass]="periodPaymentRateStyle(item.reversed)">
{{ item.newRate | formatNumber }} %
Comment on lines +41 to +49
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 | 🟡 Minor

Remove the hard-coded space before %.

The current template renders values like 1.00 %, while the PR screenshots show 1.00%. If locale-specific percent spacing is needed later, that should come from a formatter rather than a literal template space.

🧹 Suggested formatting fix
-        {{ item.previousRate | formatNumber }} %
+        {{ item.previousRate | formatNumber }}%
...
-        {{ item.newRate | formatNumber }} %
+        {{ item.newRate | formatNumber }}%
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{{ item.previousRate | formatNumber }} %
</td>
</ng-container>
<ng-container matColumnDef="newRate">
<th mat-header-cell *matHeaderCellDef>
{{ 'labels.catalogs.New' | translate }} {{ 'labels.inputs.Rate' | translate }}
</th>
<td mat-cell *matCellDef="let item" [ngClass]="periodPaymentRateStyle(item.reversed)">
{{ item.newRate | formatNumber }} %
{{ item.previousRate | formatNumber }}%
</td>
</ng-container>
<ng-container matColumnDef="newRate">
<th mat-header-cell *matHeaderCellDef>
{{ 'labels.catalogs.New' | translate }} {{ 'labels.inputs.Rate' | translate }}
</th>
<td mat-cell *matCellDef="let item" [ngClass]="periodPaymentRateStyle(item.reversed)">
{{ item.newRate | formatNumber }}%
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.html`
around lines 51 - 59, Remove the hard-coded space before the percent sign in the
template so rates render as "1.00%" not "1.00 %"; update the two bindings that
render percent values (the cell showing item.previousRate and the cell showing
item.newRate in the loan-period-payment-rates template) to place the "%"
immediately after the formatted value, and audit other occurrences in this
component/template (e.g., bindings using formatNumber and
periodPaymentRateStyle) to ensure no trailing space is present; if
locale-specific spacing is required later, implement it in the formatter
(formatNumber) rather than in the template.

</td>
</ng-container>
<ng-container matColumnDef="createdDate">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Created On' | translate }}</th>
<td mat-cell *matCellDef="let item" [ngClass]="periodPaymentRateStyle(item.reversed)">
{{ item.createdDate | dateFormat }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="loanPaymentRatesColumns"></tr>
<tr mat-row *matRowDef="let row; columns: loanPaymentRatesColumns"></tr>
</table>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* 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/.
*/
@use 'assets/styles/helper';
@use 'assets/styles/colours' as *;

.container {
padding-bottom: 2%;
max-width: 100%;

table {
width: 100%;

.reversed {
text-decoration: line-through;
color: $status-active-overdue;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* 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 { Component, inject, OnInit } from '@angular/core';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import {
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow
} from '@angular/material/table';
import { FormatNumberPipe } from '@pipes/format-number.pipe';
import { ActivatedRoute } from '@angular/router';
import { PeriodPaymentRateChange } from 'app/loans/models/working-capital-loan-account.model';
import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { LoansService } from 'app/loans/loans.service';
import { SettingsService } from 'app/settings/settings.service';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';

@Component({
selector: 'mifosx-loan-period-payment-rates',
templateUrl: './loan-period-payment-rates.component.html',
styleUrl: './loan-period-payment-rates.component.scss',
imports: [
...STANDALONE_SHARED_IMPORTS,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
FormatNumberPipe
]
})
export class LoanPeriodPaymentRatesComponent implements OnInit {
private route = inject(ActivatedRoute);
private dialog = inject(MatDialog);
private translateService = inject(TranslateService);
private loanService = inject(LoansService);
private settingsService = inject(SettingsService);

loanPaymentRatesData: PeriodPaymentRateChange[] = [];

loanId: number | null = null;

loanPaymentRatesColumns: string[] = [
'id',
'effectiveDate',
'previousRate',
'newRate',
'createdDate'
];

constructor() {}

ngOnInit(): void {
this.loanId = this.route.parent.snapshot.params['loanId'];
Comment on lines +61 to +74
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

loanId is assigned a string despite being typed number | null.

ActivatedRoute.snapshot.params['loanId'] returns a string, but the field is declared as number | null. The string is then forwarded to loanService.addWorkingCapitalPeriodPaymentRate(this.loanId, payload) and undoWorkingCapitalPeriodPaymentRate(this.loanId, payload) (lines 117, 140), as well as getWorkingCapitalPeriodPaymentRates (lines 118, 141). Depending on how those service methods serialize the URL/path, this can lead to incorrect typing at minimum and, if any downstream code does numeric comparisons or arithmetic on loanId, subtle bugs.

Also, this.route.parent can be null in principle — if this component is ever mounted as a top-level route the access will throw.

🛡️ Proposed fix
   ngOnInit(): void {
-    this.loanId = this.route.parent.snapshot.params['loanId'];
+    this.loanId = Number(this.route.parent?.snapshot.params['loanId']);
 
     this.route.data.subscribe((data: { loanPaymentRatesData: PeriodPaymentRateChange[] }) => {
       this.loanPaymentRatesData = data.loanPaymentRatesData;
     });
   }

As per coding guidelines, use TypeScript with strict typing conventions (src/app/**/*.ts).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
loanId: number | null = null;
loanPaymentRatesColumns: string[] = [
'id',
'effectiveDate',
'previousRate',
'newRate',
'createdDate'
];
constructor() {}
ngOnInit(): void {
this.loanId = this.route.parent.snapshot.params['loanId'];
loanId: number | null = null;
loanPaymentRatesColumns: string[] = [
'id',
'effectiveDate',
'previousRate',
'newRate',
'createdDate'
];
constructor() {}
ngOnInit(): void {
this.loanId = Number(this.route.parent?.snapshot.params['loanId']);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts`
around lines 62 - 75, The loanId field is declared as number | null but ngOnInit
assigns route.parent.snapshot.params['loanId'] (a string) and can throw if
parent is null; change ngOnInit to safely access the parent using optional
chaining (this.route.parent?.snapshot.params) and convert the param to a number
before assigning to loanId (e.g., parseInt/Number and handle NaN by setting
loanId = null or throwing a clear error), then ensure calls to
loanService.addWorkingCapitalPeriodPaymentRate,
undoWorkingCapitalPeriodPaymentRate, and getWorkingCapitalPeriodPaymentRates use
the numeric loanId or bail out early if loanId is null.


this.route.data.subscribe((data: { loanPaymentRatesData: PeriodPaymentRateChange[] }) => {
this.loanPaymentRatesData = data.loanPaymentRatesData;
});
}

addPaymentRate(): void {
const labelHeadingText: string = this.translateService.instant('labels.inputs.Period Payment Rate');
const formfields: FormfieldBase[] = [
new InputBase({
controlName: 'periodPaymentRate',
label: labelHeadingText,
value: '',
type: 'number',
required: true,
order: 1
}),
new InputBase({
controlName: 'note',
label: this.translateService.instant('labels.inputs.Note'),
value: '',
type: 'text',
required: false,
order: 2
})
];
const data = {
title: this.translateService.instant('labels.buttons.Add') + ' ' + labelHeadingText,
layout: { addButtonText: this.translateService.instant('labels.buttons.Add') },
Comment on lines +101 to +103
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 | 🟡 Minor

Use a full translation key for the dialog title too.

This has the same localization issue as the template buttons: translated fragments may not compose correctly across locales.

🌐 Suggested wording fix
-      title: this.translateService.instant('labels.buttons.Add') + ' ' + labelHeadingText,
+      title: this.translateService.instant('labels.buttons.Add Period Payment Rate'),

As per coding guidelines, use “proper i18n variables from @ngx-translate/core for all user-facing strings” and run npm run translations:extract whenever new strings are added.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts`
around lines 102 - 104, The dialog title currently composes two translated
fragments (translateService.instant('labels.buttons.Add') + ' ' +
labelHeadingText) which breaks localization; change to a single translation key
with interpolation (e.g., use translateService.instant('dialogs.addItem', {
item: labelHeadingText })) and update the layout.addButtonText similarly to use
a full key, referencing translateService.instant('dialogs.addButton') or a
parameterized key so translators see the whole phrase; ensure the translation
keys are added to i18n files and run npm run translations:extract.

formfields: formfields
};
const dialogRef = this.dialog.open(FormDialogComponent, { data });
dialogRef.afterClosed().subscribe((response: any) => {
if (response?.data) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const { periodPaymentRate, note } = response.data.value;
const payload = {
periodPaymentRate: periodPaymentRate,
note: note || '',
locale: this.settingsService.language.code,
dateFormat: this.settingsService.dateFormat
};
this.loanService.addWorkingCapitalPeriodPaymentRate(this.loanId, payload).subscribe((response: any) => {
this.loanService.getWorkingCapitalPeriodPaymentRates(this.loanId).subscribe((data: any) => {
this.loanPaymentRatesData = data;
});
});
}
});
}

periodPaymentRateStyle(reversed: boolean): string {
return reversed ? 'reversed' : '';
}
}
21 changes: 21 additions & 0 deletions src/app/loans/loans.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -843,4 +843,25 @@ export class LoansService {
const httpParams = new HttpParams().set('offset', offset.toString()).set('limit', limit.toString());
return this.http.get('/entityDatatableChecks', { params: httpParams });
}

/**
* Returns the Working Capital Loan Payment Rates data
*/
getWorkingCapitalPeriodPaymentRates(loanId: any) {
return this.http.get(`/working-capital-loans/${loanId}/rate-changes`);
}

/**
* Add a Working Capital Loan Payment Rates
*/
addWorkingCapitalPeriodPaymentRate(loanId: any, payload: any) {
return this.http.put(`/working-capital-loans/${loanId}/rate`, payload);
}

/**
* Undo a Working Capital Loan Payment Rates
*/
undoWorkingCapitalPeriodPaymentRate(loanId: any, payload: any) {
return this.http.post(`/working-capital-loans/${loanId}/rate/undo`, payload);
}
}
17 changes: 17 additions & 0 deletions src/app/loans/models/working-capital-loan-account.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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/.
*/

export interface PeriodPaymentRateChange {
id: number;
loanId: number;
effectiveDate: number[];
previousRate: number;
newRate: number;
reversed: boolean;
createdDate: number[];
}
24 changes: 24 additions & 0 deletions src/app/shared/undo-dialog/undo-dialog.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
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/.
-->

<h1 mat-dialog-title>{{ 'labels.heading.Undo' | translate }} {{ data.undoContext }}</h1>
<div>
<form [formGroup]="undoActionForm">
<mat-dialog-content>
<mat-form-field class="flex-100">
<textarea formControlName="note" matInput placeholder="{{ 'labels.inputs.Notes' | translate }}"></textarea>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
<button mat-raised-button color="primary" [mat-dialog-close]="{ confirm: true, data: undoActionForm.value }">
{{ 'labels.buttons.Undo' | translate }}
</button>
</mat-dialog-actions>
</form>
</div>
Comment on lines +10 to +24
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Expected after the fix: no matches.
rg -nP '<form(?=[^>]*\[formGroup\])(?=[^>]*#\w+="ngForm")' --type html

Repository: openMF/web-app

Length of output: 490


🏁 Script executed:

# Check if formRef is used in the component TypeScript files for all three instances
for file in \
  "src/app/shared/undo-dialog/undo-dialog.component.ts" \
  "src/app/organization/currencies/manage-currencies/manage-currencies.component.ts" \
  "src/app/clients/clients-view/custom-dialogs/edit-notes-dialog/edit-notes-dialog.component.ts"
do
  echo "=== Checking: $file ==="
  if [ -f "$file" ]; then
    rg -n "formRef" "$file" || echo "No matches for formRef"
  else
    echo "File not found"
  fi
  echo ""
done

Repository: openMF/web-app

Length of output: 523


🏁 Script executed:

# Also check the HTML templates more thoroughly to see if formRef is referenced anywhere
for file in \
  "src/app/shared/undo-dialog/undo-dialog.component.html" \
  "src/app/organization/currencies/manage-currencies/manage-currencies.component.html" \
  "src/app/clients/clients-view/custom-dialogs/edit-notes-dialog/edit-notes-dialog.component.html"
do
  echo "=== HTML file: $file ==="
  if [ -f "$file" ]; then
    rg -n "formRef" "$file" || echo "No references to formRef in template"
  else
    echo "File not found"
  fi
  echo ""
done

Repository: openMF/web-app

Length of output: 550


🏁 Script executed:

# Read the full HTML templates to see the complete structure and usage
echo "=== undo-dialog.component.html ===" && cat -n src/app/shared/undo-dialog/undo-dialog.component.html || echo "File not found"

Repository: openMF/web-app

Length of output: 1292


Remove the unused ngForm export and use mat-dialog-content for proper Material Dialog structure.

The #formRef="ngForm" export is unused in this component (not referenced in the .ts file) and unnecessary with reactive form setup. Replace the wrapper <div> with <mat-dialog-content> to follow Angular Material Dialog best practices.

Proposed fix
-<div>
-  <form `#formRef`="ngForm" [formGroup]="undoActionForm">
+<form [formGroup]="undoActionForm">
+  <mat-dialog-content>
     <mat-form-field class="flex-100">
       <textarea formControlName="note" matInput placeholder="{{ 'labels.inputs.Notes' | translate }}"></textarea>
     </mat-form-field>
+  </mat-dialog-content>
     <mat-dialog-actions align="end">
       <button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
       <button
         mat-raised-button
         color="primary"
         [disabled]="undoActionForm.pristine"
         [mat-dialog-close]="{ confirm: true, data: undoActionForm.value }"
       >
         {{ 'labels.buttons.Undo' | translate }}
       </button>
     </mat-dialog-actions>
-  </form>
-</div>
+</form>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/shared/undo-dialog/undo-dialog.component.html` around lines 10 - 27,
Remove the unused template export `#formRef`="ngForm" and replace the outer <div>
wrapper with a <mat-dialog-content> element to follow Angular Material dialog
structure; keep the reactive form binding [formGroup]="undoActionForm" and
existing controls (formControlName="note", mat-dialog-actions buttons) intact so
the component uses undoActionForm exclusively without the unused ngForm export.

7 changes: 7 additions & 0 deletions src/app/shared/undo-dialog/undo-dialog.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* 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/.
*/
Loading
Loading