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
@@ -0,0 +1,39 @@
/**
* 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 { Injectable, inject } from '@angular/core';
import { ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { LoanBaseResolver } from '../loan-base.resolver';
import { LoansService } from 'app/loans/loans.service';

@Injectable({
providedIn: 'root'
})
export class LoanAmortizationScheduleResolver extends LoanBaseResolver {
private loansService = inject(LoansService);

constructor() {
super();
}

/**
* Returns the Loans with Association data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> | null {
this.initialize(route);
const loanId = route.paramMap.get('loanId') || route.parent?.paramMap.get('loanId');
if (!isNaN(+loanId)) {
if (this.isWorkingCapital) {
return this.loansService.getWorkingCapitalLoanAmortizationSchedule(loanId);
}
}
return null;
Comment on lines +29 to +37
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 | ⚡ Quick win

loanId can be null but still passes the !isNaN(+loanId) guard.

When neither route.paramMap nor route.parent?.paramMap contains loanId, the expression evaluates to null. Since +null === 0 and !isNaN(0) is true, the guard is entered and getWorkingCapitalLoanAmortizationSchedule(null) would be called.

Replace the numeric coercion check with an explicit string guard:

🐛 Proposed fix
-    const loanId = route.paramMap.get('loanId') || route.parent?.paramMap.get('loanId');
-    if (!isNaN(+loanId)) {
+    const loanId = route.paramMap.get('loanId') ?? route.parent?.paramMap.get('loanId');
+    if (loanId != null && !isNaN(+loanId)) {

Also, per project conventions, prefer Observable<ProjectedAmortizationSchedule> over Observable<any> for the return type.

Based on learnings: "avoid using Observable<any> as a project-wide pattern for API responses…introduce specific interfaces/types for the response shapes."

🤖 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/common-resolvers/working-capital/loan-amortization-schedule.resolver.ts`
around lines 29 - 37, The resolve method currently allows loanId === null to
pass the numeric coercion guard; change the guard to explicitly check that
loanId is a non-empty string (e.g. loanId !== null && loanId.trim() !== '')
before calling
this.loansService.getWorkingCapitalLoanAmortizationSchedule(loanId), keeping the
existing initialize(route) and isWorkingCapital check; also tighten the resolver
return type from Observable<any> | null to
Observable<ProjectedAmortizationSchedule> | null and update imports/types to use
the ProjectedAmortizationSchedule interface.

}
}
19 changes: 18 additions & 1 deletion src/app/loans/loans-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ import { LoanOriginatorsTabComponent } from './loans-view/loan-originators-tab/l
import { LoanOriginatorsResolver } from './common-resolvers/loan-originators.resolver';
import { LoanProductsResolver } from './common-resolvers/loan-products.resolver';
import { LoanDelinquencyRangeScheduleResolver } from './common-resolvers/working-capital/loan-delinquency-actions.resolver';
import { LoanAmortizationScheduleTabComponent } from './loans-view/working-capital/loan-amortization-schedule-tab/loan-amortization-schedule-tab.component';
import { LoanAmortizationScheduleResolver } from './common-resolvers/working-capital/loan-amortization-schedule.resolver';
import { LoanBalancesTabComponent } from './loans-view/working-capital/loan-balances-tab/loan-balances-tab.component';

/** Loans Route. */
const routes: Routes = [
Expand Down Expand Up @@ -136,6 +139,19 @@ const routes: Routes = [
component: RepaymentScheduleTabComponent,
data: { title: 'Repayment Schedule', breadcrumb: 'Repayment Schedule', routeParamBreadcrumb: false }
},
{
path: 'balances',
component: LoanBalancesTabComponent,
data: { title: 'Balances', breadcrumb: 'Balances', routeParamBreadcrumb: false }
},
{
path: 'amortization-schedule',
component: LoanAmortizationScheduleTabComponent,
data: { title: 'Amortization Schedule', breadcrumb: 'Amortization Schedule', routeParamBreadcrumb: false },
resolve: {
amortizationSchedule: LoanAmortizationScheduleResolver
}
},
{
path: 'transactions',
data: { title: 'Loans Account Transactions', breadcrumb: 'Transactions', routeParamBreadcrumb: false },
Expand Down Expand Up @@ -449,7 +465,8 @@ const routes: Routes = [
LoanTermVariationsResolver,
LoanDeferredIncomeDataResolver,
LoanBuyDownFeesDataResolver,
LoanProductsResolver
LoanProductsResolver,
LoanAmortizationScheduleResolver
]
})
export class LoansRoutingModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@ <h3>{{ 'labels.heading.Loan Details' | translate }}</h3>
<span class="flex-50">{{ loanDetails.transactionProcessingStrategyName | translateKey: 'catalogs' }}</span>
</div>
}
<div class="flex-fill layout-row">
<span class="flex-50">{{ 'labels.inputs.Currency' | translate }}</span>
<span class="flex-50">{{ loanDetails.currency?.code }}</span>
</div>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@if (loanProductService.isWorkingCapital) {
<div class="flex-fill layout-row">
<span class="flex-50"> {{ 'labels.inputs.Amortization Type' | translate }} </span>
<span class="flex-50"> {{ loanDetails.product.amortizationType?.value | translateKey: 'catalogs' }} </span>
</div>
Comment on lines +19 to +28
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search translation asset files for the two new keys.
fd -e json . src/assets/i18n --exec grep -l "labels.inputs" {} \; | head -5 | xargs -I{} grep -n '"Currency"\|"Amortization Type"' {}

Repository: openMF/web-app

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Find translation-related files and directories
echo "=== Looking for translation files ==="
find . -type f -name "*translation*" -o -name "*i18n*" -o -name "*.i18n.*" 2>/dev/null | head -20

echo -e "\n=== Checking for language/locale directories ==="
find . -type d -name "i18n" -o -name "locale*" -o -name "translations" -o -name "lang*" 2>/dev/null | head -10

echo -e "\n=== Checking for JSON files in common asset paths ==="
fd -t f -e json . assets 2>/dev/null | head -20

Repository: openMF/web-app

Length of output: 596


🏁 Script executed:

#!/bin/bash
# Explore the translations directory structure
echo "=== Translation directory contents ==="
ls -la src/assets/translations/

echo -e "\n=== Looking for the specific i18n keys ==="
rg "labels\.inputs\.Currency|labels\.inputs\.Amortization Type" src/assets/translations/ -l

echo -e "\n=== Search for labels.inputs. pattern ==="
rg "labels\.inputs\." src/assets/translations/ --no-filename | head -30

Repository: openMF/web-app

Length of output: 1108


🏁 Script executed:

#!/bin/bash
# Check if the keys exist in any form or if they're simply missing
echo "=== Searching for 'Currency' key in translation files ==="
rg "Currency" src/assets/translations/ --max-count 5

echo -e "\n=== Searching for 'Amortization' in translation files ==="
rg "Amortization" src/assets/translations/ --max-count 5

echo -e "\n=== Checking the component file to verify the keys being used ==="
cat -n src/app/loans/loans-view/account-details/account-details.component.html | sed -n '19,28p'

echo -e "\n=== Checking if there's a translation extraction configuration ==="
find . -name "*.json" -o -name "*.config.*" | xargs grep -l "translations:extract" 2>/dev/null | head -5

Repository: openMF/web-app

Length of output: 12611


🏁 Script executed:

#!/bin/bash
# Check the translation extraction script in package.json
grep -A 5 "translations:extract" package.json

echo -e "\n=== Verify that labels.inputs keys are missing ==="
rg "labels\.inputs\." src/assets/translations/ | wc -l

echo -e "\n=== Show existing structure of labels.inputs in code ==="
rg "labels\.inputs\." src/ --max-count 10

Repository: openMF/web-app

Length of output: 50371


🏁 Script executed:

#!/bin/bash
# Double-check the exact keys in the component and search for them in translation files with proper escaping
echo "=== Verify exact keys in component ==="
grep -n "labels\.inputs\." src/app/loans/loans-view/account-details/account-details.component.html

echo -e "\n=== Final verification: Search for exact keys in ALL translation files ==="
rg "labels\.inputs\.Currency|labels\.inputs\.Amortization" src/assets/translations/ | wc -l

Repository: openMF/web-app

Length of output: 5733


The translation keys labels.inputs.Currency and labels.inputs.Amortization Type are missing from all translation files.

These keys are used in the component (lines 20 and 26) but do not exist in any of the 13 supported locale files in src/assets/translations/. Run npm run translations:extract to generate the missing keys in all locale JSON files, per the coding guidelines for i18n strings.

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

In `@src/app/loans/loans-view/account-details/account-details.component.html`
around lines 19 - 28, The template uses missing translation keys
'labels.inputs.Currency' and 'labels.inputs.Amortization Type' (referenced where
loanDetails.currency.code and loanDetails.product.amortizationType.value are
displayed and inside the loanProductService.isWorkingCapital conditional); run
the extraction tool (npm run translations:extract), merge/commit the generated
keys into all locale files under src/assets/translations/, add appropriate
default values for each locale, and verify the updated JSONs include both keys
so the translate pipe and translateKey pipe resolve at runtime.

Comment on lines +25 to +28
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 | ⚡ Quick win

Add optional chaining on the product.amortizationType chain to prevent a runtime crash.

loanDetails.product.amortizationType.value introduces a 3-level deep access through a product sub-object — a new pattern not present elsewhere in this template. If either product or amortizationType is absent in the working capital API response, this throws without any UI feedback.

🛡️ Proposed fix
-        <span class="flex-50"> {{ loanDetails.product.amortizationType.value | translateKey: 'catalogs' }} </span>
+        <span class="flex-50"> {{ loanDetails.product?.amortizationType?.value | translateKey: 'catalogs' }} </span>
📝 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
<div class="flex-fill layout-row">
<span class="flex-50"> {{ 'labels.inputs.Amortization Type' | translate }} </span>
<span class="flex-50"> {{ loanDetails.product.amortizationType.value | translateKey: 'catalogs' }} </span>
</div>
<div class="flex-fill layout-row">
<span class="flex-50"> {{ 'labels.inputs.Amortization Type' | translate }} </span>
<span class="flex-50"> {{ loanDetails.product?.amortizationType?.value | translateKey: 'catalogs' }} </span>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/loans/loans-view/account-details/account-details.component.html`
around lines 25 - 28, The template accesses a deep property
loanDetails.product.amortizationType.value which can throw if product or
amortizationType is missing; update the binding to use optional chaining (e.g.,
loanDetails.product?.amortizationType?.value) and provide a safe fallback (empty
string or a translated "N/A") so the translateKey pipe and UI won't crash when
product or amortizationType is undefined; update the span that currently binds
{{ loanDetails.product.amortizationType.value | translateKey: 'catalogs' }}
accordingly.


<div class="flex-fill layout-row">
<span class="flex-50">{{ 'labels.inputs.Period Payment Frequency' | translate }}</span>
<span class="flex-50">
Expand All @@ -33,9 +42,21 @@ <h3>{{ 'labels.heading.Loan Details' | translate }}</h3>
</div>
}

@if (loanDetails.discount) {
<div class="flex-fill layout-row">
<span class="flex-50"> {{ 'labels.inputs.Discount' | translate }} </span>
@if (loanDetails.discountProposed > 0) {
<div class="flex-contents">
<span class="flex-50">{{ 'labels.inputs.Proposed Discount' | translate }}:</span>
<span class="flex-50">{{ loanDetails.discountProposed | formatNumber }}</span>
</div>
}
@if (loanDetails.discountApproved > 0) {
<div class="flex-contents">
<span class="flex-50">{{ 'labels.inputs.Approved Discount' | translate }}:</span>
<span class="flex-50">{{ loanDetails.discountApproved | formatNumber }}</span>
</div>
}
@if (loanDetails.discount > 0) {
<div class="flex-contents">
<span class="flex-50">{{ 'labels.inputs.Discount' | translate }}:</span>
<span class="flex-50">{{ loanDetails.discount | formatNumber }}</span>
</div>
}
Expand Down
77 changes: 64 additions & 13 deletions src/app/loans/loans-view/loans-view.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
</tbody>
</table>
</div>
@if (loanDisplayArrearsDelinquency !== 1) {
@if (loanProductService.isLoanProduct && loanDisplayArrearsDelinquency !== 1) {
<div>
<table class="account-overview">
<tbody>
Expand Down Expand Up @@ -113,6 +113,29 @@
</table>
</div>
}
@if (
loanProductService.isWorkingCapital &&
loanDetailsData?.collectionData &&
loanDetailsData.collectionData.delinquentDays > 0
) {
<div>
<table class="account-overview">
<tbody>
<tr>
<td>{{ 'labels.inputs.Delinquency Classification' | translate }} :</td>
<td>
<span class="m-r-3"><i [ngClass]="loanDelinquencyClassificationStyle"></i></span>
{{ loanDetailsData?.delinquencyBucket?.name }}
</td>
</tr>
<tr>
<td>{{ 'labels.inputs.Delinquent Days' | translate }} :</td>
<td>{{ loanDetailsData?.collectionData.delinquentDays | formatNumber }}</td>
</tr>
</tbody>
</table>
</div>
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>

@if (loanDetailsData.summary) {
Expand Down Expand Up @@ -287,18 +310,46 @@ <h3>{{ 'labels.heading.Account Overview' | translate }}</h3>
{{ 'labels.inputs.General' | translate }}
</a>
}
<a
mat-tab-link
[routerLink]="['./repayment-schedule']"
[queryParams]="{
productType: loanProductService.productType.value
}"
routerLinkActive
#repaymentSchedule="routerLinkActive"
[active]="repaymentSchedule.isActive"
>
{{ 'labels.inputs.Repayment Schedule' | translate }}
</a>
@if (loanProductService.isLoanProduct) {
<a
mat-tab-link
[routerLink]="['./repayment-schedule']"
[queryParams]="{
productType: loanProductService.productType.value
}"
routerLinkActive
#repaymentSchedule="routerLinkActive"
[active]="repaymentSchedule.isActive"
>
{{ 'labels.inputs.Repayment Schedule' | translate }}
</a>
}
@if (loanProductService.isWorkingCapital) {
<a
mat-tab-link
[routerLink]="['./balances']"
[queryParams]="{
productType: loanProductService.productType.value
}"
routerLinkActive
#balances="routerLinkActive"
[active]="balances.isActive"
>
{{ 'labels.inputs.Balances' | translate }}
</a>
<a
mat-tab-link
[routerLink]="['./amortization-schedule']"
[queryParams]="{
productType: loanProductService.productType.value
}"
routerLinkActive
#amortizationSchedule="routerLinkActive"
[active]="amortizationSchedule.isActive"
>
{{ 'labels.inputs.Amortization Schedule' | translate }}
</a>
}
@if (loanDetailsData.transactions) {
<a
mat-tab-link
Expand Down
32 changes: 22 additions & 10 deletions src/app/loans/loans-view/loans-view.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,14 +524,20 @@ export class LoansViewComponent extends LoanProductBaseComponent implements OnIn
if (!this.loanDetailsData) {
return '';
}
if (this.loanDetailsData.chargedOff) {
return 'loanStatusType.chargeoff';
}
if (this.isContractTermination(this.loanSubStatus)) {
return 'loanSubStatusType.contractTermination';
}
if (this.loanDetailsData.inArrears) {
return 'loanStatusType.activeOverdue';
if (this.loanProductService.isLoanProduct) {
if (this.loanDetailsData.chargedOff) {
return 'loanStatusType.chargeoff';
}
if (this.isContractTermination(this.loanSubStatus)) {
return 'loanSubStatusType.contractTermination';
}
if (this.loanDetailsData.inArrears) {
return 'loanStatusType.activeOverdue';
}
} else if (this.loanProductService.isWorkingCapital) {
if (this.loanDetailsData.collectionData?.delinquentDays > 0) {
return 'loanStatusType.activeOverdue';
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return this.loanDetailsData.status?.code;
}
Expand All @@ -543,8 +549,14 @@ export class LoansViewComponent extends LoanProductBaseComponent implements OnIn
if (this.loanDetailsData.chargedOff) {
return 'Chargeoff';
}
if (this.loanDetailsData.inArrears) {
return 'activeOverdue';
if (this.loanProductService.isWorkingCapital) {
if (this.loanDetailsData.collectionData?.delinquentDays > 0) {
return 'activeOverdue';
}
} else {
if (this.loanDetailsData.inArrears) {
return 'activeOverdue';
}
}
return this.loanDetailsData.status?.code;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<!--
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">
<div class="layout-row m-t-20 align-end align-items-center">
<button mat-raised-button color="primary" (click)="exportToPDF()">
<fa-icon icon="download" class="m-r-10"></fa-icon>{{ 'labels.buttons.Export to PDF' | translate }}
</button>
</div>

<table mat-table [dataSource]="amortizationSchedule?.payments" id="amortizationSchedule">
<ng-container matColumnDef="number">
<th mat-header-cell class="center mat-header-cell" *matHeaderCellDef>#</th>
<td mat-cell class="right" *matCellDef="let item">{{ item.paymentNo }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="paymentDate">
<th mat-header-cell class="center mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Payment Date' | translate }}
</th>
<td mat-cell class="m-r-5" *matCellDef="let item">{{ item.paymentDate | dateFormat }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="expectedPaymentAmount">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Expected Payment Amount' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.expectedPaymentAmount | formatNumber }}</td>
<td mat-footer-cell class="r-amount" *matFooterCellDef>
<b>{{ amortizationSchedule?.totalPaymentValue | currency: currencyCode : 'symbol-narrow' : '1.2-2' }}</b>
</td>
</ng-container>

<ng-container matColumnDef="discountFactor">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Discount Factor' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.discountFactor | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="npvValue">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.NPV Value' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.npvValue | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="deferredBalance">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Deferred Balance' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.deferredBalance | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="forecastPaymentAmount">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Forecast Payment Amount' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.forecastPaymentAmount | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>
Comment on lines +65 to +71
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 | ⚡ Quick win

Optional columns (forecastPaymentAmount, netAmortizationAmount, incomeModification) are defined in the template but will never render.

buildDisplayedColumns() is commented out in the component (line 101 of the .ts file), so displayedColumns is always the static baseColumns array. These three ng-container column definitions are unreachable dead code until that call is restored.

If these columns are deferred intentionally for this PR, consider either removing the dead template code or restoring the buildDisplayedColumns() call and uncommenting it.

Also applies to: 81-95

🤖 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/loans-view/working-capital/loan-amortization-schedule-tab/loan-amortization-schedule-tab.component.html`
around lines 65 - 71, The template defines optional columns
(forecastPaymentAmount, netAmortizationAmount, incomeModification) that never
render because buildDisplayedColumns() is commented out and displayedColumns
stays as baseColumns; either remove these dead <ng-container> blocks from
loan-amortization-schedule-tab.component.html or restore and call
buildDisplayedColumns() in loan-amortization-schedule-tab.component.ts so
displayedColumns includes those optional keys; search for
buildDisplayedColumns(), displayedColumns, and baseColumns to decide whether to
re-enable dynamic column building or delete the unused column templates.


<ng-container matColumnDef="expectedAmortizationAmount">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Expected Amortization Amount' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.expectedAmortizationAmount | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="netAmortizationAmount">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Net Amortization Amount' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.netAmortizationAmount | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="incomeModification">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Income Modification' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.incomeModification | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<ng-container matColumnDef="balance">
<th mat-header-cell class="r-amount mat-header-cell" *matHeaderCellDef>
{{ 'labels.inputs.Balance' | translate }}
</th>
<td mat-cell class="r-amount" *matCellDef="let item">{{ item.balance | formatNumber }}</td>
<td mat-footer-cell *matFooterCellDef></td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row"></tr>
<tr mat-footer-row *matFooterRowDef="displayedColumns"></tr>
</table>
</div>
Loading