Skip to content

Commit 9f33846

Browse files
authored
Merge pull request #2574 from oleksii-novikov-onix/WEB-263/add-buy-down-fees-tab
WEB-263: Add Buy Down Fees tabs
2 parents 1a3c16e + c1128f4 commit 9f33846

21 files changed

+397
-27
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Injectable } from '@angular/core';
2+
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
3+
import { Observable } from 'rxjs';
4+
import { LoansService } from '../loans.service';
5+
6+
@Injectable()
7+
export class LoanBuyDownFeesDataResolver implements Resolve<Object> {
8+
constructor(private loansService: LoansService) {}
9+
10+
resolve(route: ActivatedRouteSnapshot): Observable<any> {
11+
const loanId = route.paramMap.get('loanId') || route.parent.paramMap.get('loanId');
12+
13+
if (!loanId) {
14+
console.error('LoanBuyDownFeesDataResolver: Could not find loanId in route parameters');
15+
return new Observable((observer) => {
16+
observer.next([]);
17+
observer.complete();
18+
});
19+
}
20+
21+
return this.loansService.getBuyDownFeeData(loanId);
22+
}
23+
}

src/app/loans/loans-routing.module.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ViewRecieptComponent } from './loans-view/transactions/view-reciept/vie
2828
import { ExportTransactionsComponent } from './loans-view/transactions/export-transactions/export-transactions.component';
2929
import { GlimAccountComponent } from './glim-account/glim-account.component';
3030
import { CreateGlimAccountComponent } from './glim-account/create-glim-account/create-glim-account.component';
31+
import { LoanBuyDownFeesTabComponent } from './loans-view/loan-buy-down-fees-tab/loan-buy-down-fees-tab.component';
3132

3233
/** Custom Resolvers */
3334
import { LoanDetailsResolver } from './common-resolvers/loan-details.resolver';
@@ -64,6 +65,7 @@ import { LoanTermVariationsTabComponent } from './loans-view/loan-term-variation
6465
import { LoanTermVariationsResolver } from './common-resolvers/loan-term-variations.resolver';
6566
import { LoanDeferredIncomeTabComponent } from './loans-view/loan-deferred-income-tab/loan-deferred-income-tab.component';
6667
import { LoanDeferredIncomeDataResolver } from './common-resolvers/loan-deferred-income-data.resolver';
68+
import { LoanBuyDownFeesDataResolver } from './common-resolvers/loan-buy-down-fees-data.resolver';
6769

6870
/** Loans Route. */
6971
const routes: Routes = [
@@ -260,6 +262,14 @@ const routes: Routes = [
260262
}
261263
}
262264
]
265+
},
266+
{
267+
path: 'buy-down-fees',
268+
component: LoanBuyDownFeesTabComponent,
269+
data: { title: 'Buy Down Fees', breadcrumb: 'Buy Down Fees', routeParamBreadcrumb: false },
270+
resolve: {
271+
loanBuyDownFeesData: LoanBuyDownFeesDataResolver
272+
}
263273
}
264274
]
265275
},
@@ -407,7 +417,8 @@ const routes: Routes = [
407417
ExternalAssetOwnerResolver,
408418
LoanDelinquencyDataResolver,
409419
LoanTermVariationsResolver,
410-
LoanDeferredIncomeDataResolver
420+
LoanDeferredIncomeDataResolver,
421+
LoanBuyDownFeesDataResolver
411422
]
412423
})
413424
export class LoansRoutingModule {}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<div class="container">
2+
<h3>{{ 'labels.heading.Buy Down Fees' | translate }}</h3>
3+
4+
<div *ngIf="isLoading" class="loading-indicator">
5+
<mat-spinner diameter="30"></mat-spinner>
6+
</div>
7+
8+
<div *ngIf="!isLoading">
9+
<div *ngIf="buyDownFeeData.length === 0" class="no-data">
10+
{{ 'labels.messages.No Data Found' | translate }}
11+
</div>
12+
13+
<table mat-table [dataSource]="buyDownFeeData" *ngIf="buyDownFeeData.length > 0">
14+
<ng-container matColumnDef="date">
15+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Date' | translate }}</th>
16+
<td mat-cell *matCellDef="let item">
17+
{{ item.date | date: 'mediumDate' }}
18+
</td>
19+
</ng-container>
20+
21+
<ng-container matColumnDef="buyDownFeeAmount">
22+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Fee Amount' | translate }}</th>
23+
<td mat-cell *matCellDef="let item">
24+
{{ item.buyDownFeeAmount | formatNumber: '0.00' }}
25+
</td>
26+
</ng-container>
27+
28+
<ng-container matColumnDef="amortizedAmount">
29+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Amortized Amount' | translate }}</th>
30+
<td mat-cell *matCellDef="let item">
31+
{{ item.amortizedAmount | formatNumber: '0.00' }}
32+
</td>
33+
</ng-container>
34+
35+
<ng-container matColumnDef="notYetAmortizedAmount">
36+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Not Yet Amortized Amount' | translate }}</th>
37+
<td mat-cell *matCellDef="let item">
38+
{{ item.notYetAmortizedAmount | formatNumber: '0.00' }}
39+
</td>
40+
</ng-container>
41+
42+
<ng-container matColumnDef="adjustedAmount">
43+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Adjusted Amount' | translate }}</th>
44+
<td mat-cell *matCellDef="let item">
45+
{{ item.adjustedAmount | formatNumber: '0.00' }}
46+
</td>
47+
</ng-container>
48+
49+
<ng-container matColumnDef="chargedOffAmount">
50+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Charged Off Amount' | translate }}</th>
51+
<td mat-cell *matCellDef="let item">
52+
{{ item.chargedOffAmount | formatNumber: '0.00' }}
53+
</td>
54+
</ng-container>
55+
56+
<tr mat-header-row *matHeaderRowDef="buyDownFeeColumns"></tr>
57+
<tr mat-row *matRowDef="let row; columns: buyDownFeeColumns"></tr>
58+
</table>
59+
</div>
60+
</div>

src/app/loans/loans-view/loan-buy-down-fees-tab/loan-buy-down-fees-tab.component.scss

Whitespace-only changes.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import {
3+
MatCell,
4+
MatCellDef,
5+
MatColumnDef,
6+
MatHeaderCell,
7+
MatHeaderCellDef,
8+
MatHeaderRow,
9+
MatHeaderRowDef,
10+
MatRow,
11+
MatRowDef,
12+
MatTable
13+
} from '@angular/material/table';
14+
import { ActivatedRoute } from '@angular/router';
15+
import { FormatNumberPipe } from '@pipes/format-number.pipe';
16+
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
17+
import { LoansService } from '../../loans.service';
18+
import { BuyDownFeeAmortizationDetails } from '../../models/loan-account.model';
19+
import { DatePipe } from '@angular/common';
20+
21+
@Component({
22+
selector: 'mifosx-loan-buy-down-fees-tab',
23+
templateUrl: './loan-buy-down-fees-tab.component.html',
24+
styleUrl: './loan-buy-down-fees-tab.component.scss',
25+
imports: [
26+
...STANDALONE_SHARED_IMPORTS,
27+
MatTable,
28+
MatColumnDef,
29+
MatHeaderCellDef,
30+
MatHeaderCell,
31+
MatCellDef,
32+
MatCell,
33+
MatHeaderRowDef,
34+
MatHeaderRow,
35+
MatRowDef,
36+
MatRow,
37+
FormatNumberPipe,
38+
DatePipe
39+
]
40+
})
41+
export class LoanBuyDownFeesTabComponent implements OnInit {
42+
buyDownFeeData: BuyDownFeeAmortizationDetails[] = [];
43+
loanId: string;
44+
isLoading = true;
45+
46+
buyDownFeeColumns: string[] = [
47+
'date',
48+
'buyDownFeeAmount',
49+
'amortizedAmount',
50+
'notYetAmortizedAmount',
51+
'adjustedAmount',
52+
'chargedOffAmount'
53+
];
54+
55+
constructor(
56+
private route: ActivatedRoute,
57+
private loansService: LoansService
58+
) {}
59+
60+
ngOnInit(): void {
61+
this.getLoanId();
62+
this.loadBuyDownFees();
63+
}
64+
65+
private getLoanId(): void {
66+
if (this.route.snapshot.data && this.route.snapshot.data['loanId']) {
67+
this.loanId = this.route.snapshot.data['loanId'];
68+
return;
69+
}
70+
71+
let currentRoute = this.route;
72+
while (currentRoute) {
73+
if (currentRoute.snapshot.paramMap.has('loanId')) {
74+
this.loanId = currentRoute.snapshot.paramMap.get('loanId');
75+
return;
76+
}
77+
if (currentRoute.parent) {
78+
currentRoute = currentRoute.parent;
79+
} else {
80+
break;
81+
}
82+
}
83+
84+
console.error('Could not find loanId in route parameters');
85+
}
86+
87+
private loadBuyDownFees(): void {
88+
if (!this.loanId) {
89+
console.error('Cannot load buy down fees: loanId is undefined');
90+
this.isLoading = false;
91+
return;
92+
}
93+
94+
this.isLoading = true;
95+
this.loansService.getBuyDownFeeData(this.loanId).subscribe({
96+
next: (data: BuyDownFeeAmortizationDetails[]) => {
97+
this.buyDownFeeData = data || [];
98+
this.isLoading = false;
99+
},
100+
error: (error) => {
101+
console.error('Error loading buy down fees:', error);
102+
this.isLoading = false;
103+
}
104+
});
105+
}
106+
}

src/app/loans/loans-view/loans-view.component.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,17 @@ <h3>{{ 'labels.heading.Account Overview' | translate }}</h3>
219219
{{ 'labels.inputs.Deferred income' | translate }}
220220
</a>
221221
</ng-container>
222+
<ng-container *ngIf="loanDetailsData.enableBuyDownFee">
223+
<a
224+
mat-tab-link
225+
[routerLink]="['./buy-down-fees']"
226+
routerLinkActive
227+
#buyDownFees="routerLinkActive"
228+
[active]="buyDownFees.isActive"
229+
>
230+
{{ 'labels.heading.Buy Down Fees' | translate }}
231+
</a>
232+
</ng-container>
222233
<ng-container *ngIf="loanDetailsData.status.active">
223234
<a
224235
mat-tab-link

src/app/loans/loans.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ export class LoansService {
104104
return this.http.get(`/loans/${loanId}/deferredincome`);
105105
}
106106

107+
getBuyDownFeeData(loanId: string): Observable<any> {
108+
return this.http.get(`/loans/${loanId}/buydownfees`);
109+
}
110+
107111
/**
108112
* Returns the loan template data with specific condition
109113
* @param loanId Loan Id

src/app/loans/models/loan-account.model.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,15 @@ export interface LoanCapitalizedIncomeData {
133133
unrecognizedAmount?: number;
134134
amountAdjustment?: number;
135135
}
136+
137+
export interface BuyDownFeeAmortizationDetails {
138+
id: number;
139+
loanId: number;
140+
transactionId: number;
141+
date: string;
142+
buyDownFeeAmount: number;
143+
amortizedAmount: number;
144+
notYetAmortizedAmount: number;
145+
adjustedAmount: number;
146+
chargedOffAmount: number;
147+
}

src/assets/translations/cs-CS.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,7 +1164,14 @@
11641164
"Grace On Principal": "Grace na ředitelce",
11651165
"Insert Installment": "Vložit splátku",
11661166
"Interest Rate": "Úroková sazba",
1167-
"Principal Amount": "Částka jistiny"
1167+
"Principal Amount": "Částka jistiny",
1168+
"Buy Down Fees": "Poplatky za odkup",
1169+
"Fee Amount": "Částka poplatku",
1170+
"Amortized Amount": "Amortizovaná částka",
1171+
"Not Yet Amortized Amount": "Dosud neamortizovana castka",
1172+
"Adjusted Amount": "Upravená částka",
1173+
"Charged Off Amount": "Odepsaná částka",
1174+
"Date": "Datum"
11681175
},
11691176
"inputs": {
11701177
"accounting": {
@@ -3303,12 +3310,16 @@
33033310
"Mandatory": "Povinné",
33043311
"Voluntary": "Dobrovolný",
33053312
"Write a note": "Napište poznámku",
3306-
"“Maker-Checker” principle requires every tasks": "Princip „Maker-Checker“ vyžaduje, aby každý úkol prováděli dva lidé, aby se snížila pravděpodobnost chyb a zneužití. Jedna osoba proces iniciuje a druhá jej dokončí."
3313+
"“Maker-Checker” principle requires every tasks": "Princip „Maker-Checker“ vyžaduje, aby každý úkol prováděli dva lidé, aby se snížila pravděpodobnost chyb a zneužití. Jedna osoba proces iniciuje a druhá jej dokončí.",
3314+
"Buy Down Fees": "Poplatky za odkup"
33073315
},
33083316
"titles": {
33093317
"Dashboard": "Přístrojová deska",
33103318
"Home": "Domov",
33113319
"Login": "Přihlásit se"
3320+
},
3321+
"messages": {
3322+
"No Data Found": "Nebyla nalezena žádná data"
33123323
}
33133324
},
33143325
"languages": {

src/assets/translations/de-DE.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,14 @@
11651165
"Grace On Principal": "Grace On Principal",
11661166
"Insert Installment": "Ratenzahlung einfügen",
11671167
"Interest Rate": "Zinssatz",
1168-
"Principal Amount": "Hauptbetrag"
1168+
"Principal Amount": "Hauptbetrag",
1169+
"Buy Down Fees": "Kaufgebühren",
1170+
"Fee Amount": "Gebührenbetrag",
1171+
"Amortized Amount": "Amortisierter Betrag",
1172+
"Not Yet Amortized Amount": "Noch nicht amortisierter Betrag",
1173+
"Adjusted Amount": "Angepasster Betrag",
1174+
"Charged Off Amount": "Abgeschriebener Betrag",
1175+
"Date": "Datum"
11691176
},
11701177
"inputs": {
11711178
"accounting": {
@@ -3302,12 +3309,16 @@
33023309
"Mandatory": "Obligatorisch",
33033310
"Voluntary": "Freiwillig",
33043311
"Write a note": "Schreib eine Notitz",
3305-
"“Maker-Checker” principle requires every tasks": "Das „Maker-Checker“-Prinzip erfordert, dass jede Aufgabe von zwei Personen erledigt wird, um das Risiko von Fehlern und Missbrauch zu verringern. Eine Person leitet den Prozess ein und die zweite Person schließt ihn ab."
3312+
"“Maker-Checker” principle requires every tasks": "Das „Maker-Checker“-Prinzip erfordert, dass jede Aufgabe von zwei Personen erledigt wird, um das Risiko von Fehlern und Missbrauch zu verringern. Eine Person leitet den Prozess ein und die zweite Person schließt ihn ab.",
3313+
"Buy Down Fees": "Kaufgebühren"
33063314
},
33073315
"titles": {
33083316
"Dashboard": "Armaturenbrett",
33093317
"Home": "Heim",
33103318
"Login": "Anmeldung"
3319+
},
3320+
"messages": {
3321+
"No Data Found": "Keine Daten gefunden"
33113322
}
33123323
},
33133324
"languages": {

0 commit comments

Comments
 (0)