Skip to content

Commit 46cd085

Browse files
WEB-870: Delinquency management configuration for WC
1 parent d115e23 commit 46cd085

27 files changed

Lines changed: 729 additions & 233 deletions
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 { inject } from '@angular/core';
10+
import { ActivatedRoute } from '@angular/router';
11+
import { BehaviorSubject } from 'rxjs';
12+
import { DELINQUENCY_BUCKET_TYPE, DelinquencyBucketType } from './models/delinquency-models';
13+
14+
export abstract class DelinquencyBucketBaseComponent {
15+
protected route = inject(ActivatedRoute);
16+
17+
delinquencyBucketType = new BehaviorSubject<DelinquencyBucketType>(DELINQUENCY_BUCKET_TYPE.REGULAR);
18+
19+
constructor() {
20+
this.initialize(this.route.snapshot.queryParamMap.get('bucketType') || 'regular');
21+
}
22+
23+
initialize(productType: string): void {
24+
if (productType === 'regular') {
25+
this.delinquencyBucketType.next(DELINQUENCY_BUCKET_TYPE.REGULAR);
26+
} else if (productType === 'workingcapital') {
27+
this.delinquencyBucketType.next(DELINQUENCY_BUCKET_TYPE.WORKING_CAPITAL);
28+
}
29+
}
30+
31+
get isWorkingCapitalBucket(): boolean {
32+
return DELINQUENCY_BUCKET_TYPE.WORKING_CAPITAL === this.delinquencyBucketType.value;
33+
}
34+
35+
get isRegularBucket(): boolean {
36+
return DELINQUENCY_BUCKET_TYPE.REGULAR === this.delinquencyBucketType.value;
37+
}
38+
39+
bucketTypeLabel(bucketType: number): string {
40+
if (bucketType === 1) {
41+
return 'Regular';
42+
}
43+
if (bucketType === 2) {
44+
return 'Working Capital';
45+
}
46+
return '';
47+
}
48+
49+
bucketType(bucketType: number): string {
50+
if (bucketType === 1) {
51+
return 'regular';
52+
}
53+
if (bucketType === 2) {
54+
return 'workingcapital';
55+
}
56+
return '';
57+
}
58+
59+
getCatalogLabel(inputText: string): string {
60+
const datas = inputText.split('.');
61+
return this.camalize(datas[1]);
62+
}
63+
64+
camalize(word: string) {
65+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
66+
}
67+
}

src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.html

Lines changed: 109 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,57 +21,122 @@
2121
</mat-error>
2222
}
2323
</mat-form-field>
24+
@if (isRegularBucket) {
25+
<h3 class="mat-h3 flex-40">{{ 'labels.heading.Delinquency Ranges' | translate }}</h3>
2426

25-
<h3 class="mat-h3 flex-40">{{ 'labels.heading.Delinquency Ranges' | translate }}</h3>
27+
<div class="flex-40 layout-row align-start-center">
28+
<button type="button" mat-raised-button color="primary" (click)="addDelinquencyRange()">
29+
<fa-icon icon="plus" class="m-r-10"></fa-icon>
30+
{{ 'labels.buttons.Add' | translate }}
31+
</button>
32+
</div>
2633

27-
<div class="flex-40 layout-row align-start-center">
28-
<button type="button" mat-raised-button color="primary" (click)="addDelinquencyRange()">
29-
<fa-icon icon="plus" class="m-r-10"></fa-icon>
30-
{{ 'labels.buttons.Add' | translate }}
31-
</button>
32-
</div>
34+
<table mat-table [dataSource]="rangesDataSource" [hidden]="rangesDataSource.length === 0">
35+
<ng-container matColumnDef="classification">
36+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Classification' | translate }}</th>
37+
<td mat-cell *matCellDef="let row">
38+
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'classification' }}
39+
</td>
40+
</ng-container>
3341

34-
<table mat-table [dataSource]="rangesDataSource" [hidden]="rangesDataSource.length === 0">
35-
<ng-container matColumnDef="classification">
36-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Classification' | translate }}</th>
37-
<td mat-cell *matCellDef="let row">
38-
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'classification' }}
39-
</td>
40-
</ng-container>
42+
<ng-container matColumnDef="minimumAgeDays">
43+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days From' | translate }}</th>
44+
<td mat-cell *matCellDef="let row">
45+
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'minimumAgeDays' }}
46+
</td>
47+
</ng-container>
4148

42-
<ng-container matColumnDef="minimumAgeDays">
43-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days From' | translate }}</th>
44-
<td mat-cell *matCellDef="let row">
45-
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'minimumAgeDays' }}
46-
</td>
47-
</ng-container>
49+
<ng-container matColumnDef="maximumAgeDays">
50+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days Till' | translate }}</th>
51+
<td mat-cell *matCellDef="let row">
52+
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'maximumAgeDays' }}
53+
</td>
54+
</ng-container>
4855

49-
<ng-container matColumnDef="maximumAgeDays">
50-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days Till' | translate }}</th>
51-
<td mat-cell *matCellDef="let row">
52-
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'maximumAgeDays' }}
53-
</td>
54-
</ng-container>
56+
<ng-container matColumnDef="actions">
57+
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
58+
<td mat-cell *matCellDef="let row; let rowIndex = index">
59+
<button
60+
type="button"
61+
mat-icon-button
62+
color="warn"
63+
(click)="deleteDelinquencyRange(rowIndex)"
64+
matTooltip="{{ 'tooltips.Delete' | translate }}"
65+
matTooltipPosition="left"
66+
>
67+
<fa-icon icon="trash"></fa-icon>
68+
</button>
69+
</td>
70+
</ng-container>
5571

56-
<ng-container matColumnDef="actions">
57-
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
58-
<td mat-cell *matCellDef="let row; let rowIndex = index">
59-
<button
60-
type="button"
61-
mat-icon-button
62-
color="warn"
63-
(click)="deleteDelinquencyRange(rowIndex)"
64-
matTooltip="{{ 'tooltips.Delete' | translate }}"
65-
matTooltipPosition="left"
66-
>
67-
<fa-icon icon="trash"></fa-icon>
68-
</button>
69-
</td>
70-
</ng-container>
72+
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
73+
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
74+
</table>
75+
}
76+
@if (isWorkingCapitalBucket) {
77+
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Delinquency Payment Rule' | translate }}</h3>
78+
<mat-form-field class="flex-100">
79+
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
80+
<input matInput type="number" required formControlName="frequency" min="1" step="1" />
81+
@if (bucketForm.controls.frequency.hasError('required')) {
82+
<mat-error>
83+
{{ 'labels.inputs.Frequency' | translate }} {{ 'labels.commons.is' | translate }}
84+
<strong>{{ 'labels.commons.required' | translate }}</strong>
85+
</mat-error>
86+
}
87+
@if (bucketForm.controls.frequency.hasError('pattern')) {
88+
<mat-error> {{ 'labels.inputs.Frequency' | translate }} must be a positive number. </mat-error>
89+
}
90+
</mat-form-field>
7191

72-
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
73-
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
74-
</table>
92+
<mat-form-field class="flex-100">
93+
<mat-label>{{ 'labels.inputs.Frequency Type' | translate }}</mat-label>
94+
<mat-select required formControlName="frequencyType">
95+
@for (frequencyType of frequencyTypeOptions; track frequencyType) {
96+
<mat-option [value]="frequencyType.id">
97+
{{ getCatalogLabel(frequencyType.name) | translateKey: 'catalogs' }}
98+
</mat-option>
99+
}
100+
</mat-select>
101+
@if (bucketForm.controls.frequencyType.hasError('required')) {
102+
<mat-error>
103+
{{ 'labels.inputs.Frequency Type' | translate }} {{ 'labels.commons.is' | translate }}
104+
<strong>{{ 'labels.inputs.required' | translate }}</strong>
105+
</mat-error>
106+
}
107+
</mat-form-field>
108+
109+
<mat-form-field class="flex-100">
110+
<mat-label>{{ 'labels.inputs.Minimum Payment' | translate }}</mat-label>
111+
<input matInput type="number" required formControlName="minimumPayment" min="0.01" />
112+
@if (bucketForm.controls.minimumPayment.hasError('required')) {
113+
<mat-error>
114+
{{ 'labels.inputs.Minimum Payment' | translate }} {{ 'labels.commons.is' | translate }}
115+
<strong>{{ 'labels.commons.required' | translate }}</strong>
116+
</mat-error>
117+
}
118+
@if (bucketForm.controls.minimumPayment.hasError('pattern')) {
119+
<mat-error> {{ 'labels.inputs.Minimum Payment' | translate }} must be a positive number. </mat-error>
120+
}
121+
</mat-form-field>
122+
123+
<mat-form-field class="flex-100">
124+
<mat-label>{{ 'labels.inputs.Minimum Payment Type' | translate }}</mat-label>
125+
<mat-select required formControlName="minimumPaymentType">
126+
@for (minimumPaymentType of minimumPaymentOptions; track minimumPaymentType) {
127+
<mat-option [value]="minimumPaymentType.id">
128+
{{ getCatalogLabel(minimumPaymentType.name) | translateKey: 'catalogs' }}
129+
</mat-option>
130+
}
131+
</mat-select>
132+
@if (bucketForm.controls.minimumPaymentType.hasError('required')) {
133+
<mat-error>
134+
{{ 'labels.inputs.Minimum Payment Type' | translate }} {{ 'labels.commons.is' | translate }}
135+
<strong>{{ 'labels.inputs.required' | translate }}</strong>
136+
</mat-error>
137+
}
138+
</mat-form-field>
139+
}
75140
</div>
76141
</mat-card-content>
77142

0 commit comments

Comments
 (0)