Skip to content

Commit b33757e

Browse files
WEB-813: Working Capital loan delinquency actions
1 parent d0f15c4 commit b33757e

10 files changed

Lines changed: 287 additions & 80 deletions

File tree

src/app/core/utils/dates.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ export class Dates {
5252
}
5353
}
5454

55+
public isBefore(date1: Date, date2: Date): boolean {
56+
return date1 < date2;
57+
}
58+
59+
public isAfter(date1: Date, date2: Date): boolean {
60+
return date1 > date2;
61+
}
62+
5563
public parseDatetime(value: any): Date {
5664
return moment(value).toDate();
5765
}

src/app/loans/common-resolvers/loan-delinquency-actions.resolver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export class LoanDelinquencyActionsResolver extends LoanBaseResolver {
2929
resolve(route: ActivatedRouteSnapshot): Observable<any> {
3030
this.initialize(route);
3131
const loanId = route.paramMap.get('loanId') || route.parent.paramMap.get('loanId');
32-
return this.loansService.getDelinquencyActions(this.loanAccountPath, loanId);
32+
if (!isNaN(+loanId)) {
33+
return this.loansService.getDelinquencyActions(this.loanAccountPath, loanId);
34+
}
3335
}
3436
}

src/app/loans/common-resolvers/loan-delinquency-data.resolver.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export class LoanDelinquencyDataResolver extends LoanBaseResolver {
2727
constructor() {
2828
super();
2929
}
30-
3130
/**
3231
* Returns the Loans with Association data.
3332
* @returns {Observable<any>}
@@ -36,9 +35,9 @@ export class LoanDelinquencyDataResolver extends LoanBaseResolver {
3635
this.initialize(route);
3736
const loanId = route.paramMap.get('loanId') || route.parent.paramMap.get('loanId');
3837
if (!isNaN(+loanId)) {
39-
if (this.isLoanProduct) {
40-
return this.loansService.getDelinquencyData(loanId);
41-
}
38+
return this.isLoanProduct
39+
? this.loansService.getDelinquencyData(loanId)
40+
: this.loansService.getWorkingCapitalLoanDetails(loanId);
4241
}
4342
}
4443
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!--
2+
Copyright since 2025 Mifos Initiative
3+
4+
This Source Code Form is subject to the terms of the Mozilla Public
5+
License, v. 2.0. If a copy of the MPL was not distributed with this
6+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
-->
8+
9+
<h2 mat-dialog-title>{{ 'labels.heading.Loan Delinquency Actions' | translate }}</h2>
10+
11+
<div mat-dialog-content [formGroup]="delinquencyActionForm" class="layout-column">
12+
<mat-form-field class="flex-28">
13+
<mat-label>{{ 'labels.inputs.Minimum Payment' | translate }}</mat-label>
14+
<input type="number" matInput formControlName="minimumPayment" />
15+
</mat-form-field>
16+
17+
<mat-form-field class="flex-23">
18+
<mat-label>{{ 'labels.inputs.Period Payment Frequency' | translate }}</mat-label>
19+
<input
20+
type="number"
21+
matInput
22+
required
23+
formControlName="frequency"
24+
matTooltip="{{ 'tooltips.Fields are input to calculating the repayment schedule' | translate }}"
25+
/>
26+
@if (delinquencyActionForm.controls.frequency.hasError('required')) {
27+
<mat-error>
28+
{{ 'labels.inputs.Period Payment Frequency' | translate }} {{ 'labels.commons.is' | translate }}
29+
<strong>{{ 'labels.commons.required' | translate }}</strong>
30+
</mat-error>
31+
}
32+
</mat-form-field>
33+
34+
<mat-form-field class="flex-23">
35+
<mat-label>{{ 'labels.inputs.Period Payment Frequency Type' | translate }}</mat-label>
36+
<mat-select formControlName="frequencyType" required>
37+
@for (frequencyType of frequencyTypeOptions; track frequencyType) {
38+
<mat-option [value]="frequencyType.id">
39+
{{ frequencyType.value | translateKey: 'catalogs' }}
40+
</mat-option>
41+
}
42+
</mat-select>
43+
</mat-form-field>
44+
</div>
45+
46+
<mat-dialog-actions class="layout-row layout-xs-column layout-align-center gap-2percent">
47+
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
48+
<button
49+
mat-raised-button
50+
color="primary"
51+
[mat-dialog-close]="{ data: delinquencyActionForm }"
52+
[disabled]="!delinquencyActionForm.valid || delinquencyActionForm.pristine"
53+
>
54+
{{ 'labels.buttons.Reschedule' | translate }}
55+
</button>
56+
</mat-dialog-actions>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Copyright since 2025 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Copyright since 2025 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
*/
8+
9+
import { Component, inject } from '@angular/core';
10+
import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
11+
import {
12+
MAT_DIALOG_DATA,
13+
MatDialogRef,
14+
MatDialogTitle,
15+
MatDialogContent,
16+
MatDialogActions,
17+
MatDialogClose
18+
} from '@angular/material/dialog';
19+
import { CdkScrollable } from '@angular/cdk/scrolling';
20+
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
21+
import { MatTooltip } from '@angular/material/tooltip';
22+
import { StringEnumOptionData } from 'app/shared/models/option-data.model';
23+
24+
@Component({
25+
selector: 'mifosx-loan-delinquency-action-reschedule-dialog',
26+
templateUrl: './loan-delinquency-action-reschedule-dialog.component.html',
27+
styleUrl: './loan-delinquency-action-reschedule-dialog.component.scss',
28+
imports: [
29+
...STANDALONE_SHARED_IMPORTS,
30+
MatDialogTitle,
31+
CdkScrollable,
32+
MatDialogContent,
33+
MatDialogActions,
34+
MatDialogClose,
35+
MatTooltip
36+
]
37+
})
38+
export class LoanDelinquencyActionRescheduleDialogComponent {
39+
dialogRef = inject<MatDialogRef<LoanDelinquencyActionRescheduleDialogComponent>>(MatDialogRef);
40+
data = inject(MAT_DIALOG_DATA);
41+
private formBuilder = inject(UntypedFormBuilder);
42+
43+
delinquencyActionForm: UntypedFormGroup;
44+
45+
frequencyTypeOptions: StringEnumOptionData[] = [];
46+
47+
constructor() {
48+
this.createDelinquencyActionForm();
49+
this.frequencyTypeOptions = this.data.frequencyTypeOptions;
50+
}
51+
52+
createDelinquencyActionForm() {
53+
this.delinquencyActionForm = this.formBuilder.group({
54+
minimumPayment: [
55+
'',
56+
Validators.required
57+
],
58+
frequency: [
59+
'',
60+
Validators.required
61+
],
62+
frequencyType: [
63+
'',
64+
Validators.required
65+
]
66+
});
67+
}
68+
}

src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ <h4 class="mat-h4 flex-98">
5959
/>
6060
@if (loansAccountTermsForm.controls.repaymentEvery.hasError('required')) {
6161
<mat-error>
62-
{{ 'labels.inputs.Repaid every' | translate }} {{ 'labels.commons.is' | translate }}
62+
{{ 'labels.inputs.Period Payment Frequency' | translate }} {{ 'labels.commons.is' | translate }}
6363
<strong>{{ 'labels.commons.required' | translate }}</strong>
6464
</mat-error>
6565
}

src/app/loans/loans-view/loan-delinquency-tags-tab/loan-delinquency-tags-tab.component.html

Lines changed: 71 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,73 +7,75 @@
77
-->
88

99
<div class="container">
10-
<h3>{{ 'labels.heading.Loan Delinquency Tags' | translate }}</h3>
10+
@if (loanProductService.isLoanProduct) {
11+
<h3>{{ 'labels.heading.Loan Delinquency Tags' | translate }}</h3>
1112

12-
@if (loanDelinquencyTags.length > 0) {
13-
<table mat-table [dataSource]="loanDelinquencyTags">
14-
<ng-container matColumnDef="classification">
15-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Delinquency Classification' | translate }}</th>
16-
<td mat-cell *matCellDef="let item">
17-
{{ item.delinquencyRange.classification }}
18-
@if (item.delinquencyRange.maximumAgeDays) {
19-
<span>( {{ item.delinquencyRange.minimumAgeDays }} - {{ item.delinquencyRange.maximumAgeDays }} )</span>
20-
}
21-
@if (!item.delinquencyRange.maximumAgeDays) {
22-
<span>( {{ item.delinquencyRange.minimumAgeDays }} )</span>
23-
}
24-
</td>
25-
</ng-container>
26-
<ng-container matColumnDef="addedOn">
27-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Added On' | translate }}</th>
28-
<td mat-cell *matCellDef="let item">
29-
@if (item.addedOnDate) {
30-
<span>
31-
{{ item.addedOnDate | dateFormat }}
32-
</span>
33-
}
34-
</td>
35-
</ng-container>
36-
<ng-container matColumnDef="liftedOn">
37-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Lifted On' | translate }}</th>
38-
<td mat-cell *matCellDef="let item">
39-
@if (item.liftedOnDate) {
40-
<span>
41-
{{ item.liftedOnDate | dateFormat }}
42-
</span>
43-
}
44-
</td>
45-
</ng-container>
46-
<tr mat-header-row *matHeaderRowDef="loanDelinquencyTagsColumns"></tr>
47-
<tr mat-row *matRowDef="let row; columns: loanDelinquencyTagsColumns"></tr>
48-
</table>
49-
}
50-
51-
@if (installmentLevelDelinquency.length > 0) {
52-
<div>
53-
<h3>{{ 'labels.heading.Loan Delinquency Installment Tags' | translate }}</h3>
54-
<table mat-table [dataSource]="installmentLevelDelinquency">
13+
@if (loanDelinquencyTags.length > 0) {
14+
<table mat-table [dataSource]="loanDelinquencyTags">
5515
<ng-container matColumnDef="classification">
5616
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Delinquency Classification' | translate }}</th>
57-
<td mat-cell *matCellDef="let item">{{ item.classification }}</td>
17+
<td mat-cell *matCellDef="let item">
18+
{{ item.delinquencyRange.classification }}
19+
@if (item.delinquencyRange.maximumAgeDays) {
20+
<span>( {{ item.delinquencyRange.minimumAgeDays }} - {{ item.delinquencyRange.maximumAgeDays }} )</span>
21+
}
22+
@if (!item.delinquencyRange.maximumAgeDays) {
23+
<span>( {{ item.delinquencyRange.minimumAgeDays }} )</span>
24+
}
25+
</td>
5826
</ng-container>
59-
<ng-container matColumnDef="minimumAgeDays">
60-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days' | translate }}</th>
61-
<td mat-cell *matCellDef="let item">{{ item.minimumAgeDays | formatNumber }}</td>
27+
<ng-container matColumnDef="addedOn">
28+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Added On' | translate }}</th>
29+
<td mat-cell *matCellDef="let item">
30+
@if (item.addedOnDate) {
31+
<span>
32+
{{ item.addedOnDate | dateFormat }}
33+
</span>
34+
}
35+
</td>
6236
</ng-container>
63-
<ng-container matColumnDef="amount">
64-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
37+
<ng-container matColumnDef="liftedOn">
38+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Lifted On' | translate }}</th>
6539
<td mat-cell *matCellDef="let item">
66-
{{ item.delinquentAmount | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
40+
@if (item.liftedOnDate) {
41+
<span>
42+
{{ item.liftedOnDate | dateFormat }}
43+
</span>
44+
}
6745
</td>
6846
</ng-container>
69-
<tr mat-header-row *matHeaderRowDef="installmentDelinquencyTagsColumns"></tr>
70-
<tr mat-row *matRowDef="let row; columns: installmentDelinquencyTagsColumns"></tr>
47+
<tr mat-header-row *matHeaderRowDef="loanDelinquencyTagsColumns"></tr>
48+
<tr mat-row *matRowDef="let row; columns: loanDelinquencyTagsColumns"></tr>
7149
</table>
72-
</div>
50+
}
51+
52+
@if (installmentLevelDelinquency.length > 0) {
53+
<div>
54+
<h3>{{ 'labels.heading.Loan Delinquency Installment Tags' | translate }}</h3>
55+
<table mat-table [dataSource]="installmentLevelDelinquency">
56+
<ng-container matColumnDef="classification">
57+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Delinquency Classification' | translate }}</th>
58+
<td mat-cell *matCellDef="let item">{{ item.classification }}</td>
59+
</ng-container>
60+
<ng-container matColumnDef="minimumAgeDays">
61+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days' | translate }}</th>
62+
<td mat-cell *matCellDef="let item">{{ item.minimumAgeDays | formatNumber }}</td>
63+
</ng-container>
64+
<ng-container matColumnDef="amount">
65+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
66+
<td mat-cell *matCellDef="let item">
67+
{{ item.delinquentAmount | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
68+
</td>
69+
</ng-container>
70+
<tr mat-header-row *matHeaderRowDef="installmentDelinquencyTagsColumns"></tr>
71+
<tr mat-row *matRowDef="let row; columns: installmentDelinquencyTagsColumns"></tr>
72+
</table>
73+
</div>
74+
}
7375
}
7476

75-
@if (allowPause) {
76-
<div class="layout-row m-t-20 m-b-10 align-end align-items-center">
77+
<div class="layout-row m-t-20 m-b-10 align-end align-items-center gap-5px">
78+
@if (allowPause) {
7779
<button
7880
mat-raised-button
7981
color="primary"
@@ -83,8 +85,18 @@ <h3>{{ 'labels.heading.Loan Delinquency Installment Tags' | translate }}</h3>
8385
<fa-icon icon="pause" class="m-r-10"></fa-icon
8486
>{{ 'labels.buttons.Pause Delinquency Classification' | translate }}
8587
</button>
86-
</div>
87-
}
88+
}
89+
@if (loanProductService.isWorkingCapital) {
90+
<button
91+
mat-raised-button
92+
color="primary"
93+
(click)="createDelinquencyActionReschedule()"
94+
*mifosxHasPermission="'CREATE_WC_DELINQUENCY_ACTION'"
95+
>
96+
<fa-icon icon="calendar" class="m-r-10"></fa-icon>{{ 'labels.buttons.Reschedule' | translate }}
97+
</button>
98+
}
99+
</div>
88100

89101
@if (loanDelinquencyActions.length > 0) {
90102
<div class="m-t-10">
@@ -109,7 +121,7 @@ <h3>{{ 'labels.heading.Loan Delinquency Actions' | translate }}</h3>
109121
<ng-container matColumnDef="actions">
110122
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
111123
<td mat-cell *matCellDef="let item">
112-
@if (isCurrentAndPauseAction(item)) {
124+
@if (loanProductService.isLoanProduct && isCurrentAndPauseAction(item)) {
113125
<span>
114126
<button
115127
mat-button

0 commit comments

Comments
 (0)