Skip to content

Commit ade44ca

Browse files
WEB-657: Working Capital product near breach configuration
1 parent 2cd4264 commit ade44ca

48 files changed

Lines changed: 2619 additions & 126 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ <h3 class="mat-h3 flex-fill">{{ 'labels.heading.Terms' | translate }}</h3>
111111
<span class="flex-40">{{ 'labels.inputs.Breach' | translate }}:</span>
112112
<span class="flex-60"><mifosx-breach-display [singleRow]="false" [breach]="loanProduct.breach" /></span>
113113
</div>
114+
<div class="flex-fill layout-row">
115+
<span class="flex-40">{{ 'labels.inputs.Enable Near Breach' | translate }}:</span>
116+
<span class="flex-60">{{ enableNearBreach() | yesNo }}</span>
117+
</div>
118+
@if (enableNearBreach()) {
119+
<div class="flex-fill layout-row">
120+
<span class="flex-40">{{ 'labels.inputs.Near Breach' | translate }}:</span>
121+
<span class="flex-60"
122+
><mifosx-breach-display [singleRow]="false" [nearBreach]="loanProduct.nearBreach"
123+
/></span>
124+
</div>
125+
}
114126
}
115127
@if (loanProductService.isLoanProduct) {
116128
<div class="flex-fill layout-row">

src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,16 @@ export class LoanProductSummaryComponent extends LoanProductBaseComponent implem
538538
};
539539
}
540540
}
541+
542+
if (this.loanProductService.isWorkingCapital) {
543+
/*
544+
let optionValue: OptionData = this.optionDataLookUp(
545+
this.loanProduct.nearBreach.frequencyType,
546+
this.loanProductsTemplate.periodFrequencyTypeOptions
547+
);
548+
this.loanProduct.nearBreachEvalFrequencyType = optionValue;
549+
*/
550+
}
541551
}
542552
}
543553

@@ -675,6 +685,10 @@ export class LoanProductSummaryComponent extends LoanProductBaseComponent implem
675685
);
676686
}
677687

688+
enableNearBreach(): boolean {
689+
return this.loanProductService.isWorkingCapital && 'nearBreach' in this.loanProduct;
690+
}
691+
678692
getAccountingRuleName(value: string): string {
679693
return this.loanProductService.isWorkingCapital ? '' : this.accounting.getAccountRuleName(value.toUpperCase());
680694
}

src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.html

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,70 @@
9393
</button>
9494
}
9595
</mat-form-field>
96+
97+
@if (loanProductSettingsForm.value.breachId) {
98+
<mat-form-field class="flex-48">
99+
<mat-label>{{ 'labels.inputs.Near Breach' | translate }}</mat-label>
100+
<mat-select formControlName="nearBreachId" class="breach-id-select" panelClass="breach-select-panel">
101+
<mat-select-trigger>
102+
@if (selectedNearBreach) {
103+
<mifosx-breach-display [singleRow]="true" [nearBreach]="selectedNearBreach" />
104+
}
105+
</mat-select-trigger>
106+
@for (nearBreach of nearBreachOptions; track nearBreach) {
107+
<mat-option [value]="nearBreach.id">
108+
<mifosx-breach-display [singleRow]="true" [nearBreach]="nearBreach" />
109+
</mat-option>
110+
}
111+
</mat-select>
112+
@if (loanProductSettingsForm.controls.nearBreachId) {
113+
<button matSuffix mat-icon-button aria-label="Clear" (click)="clearProperty($event, 'nearBreachId')">
114+
<fa-icon icon="close" size="md"></fa-icon>
115+
</button>
116+
}
117+
</mat-form-field>
118+
}
119+
120+
@if (loanProductSettingsForm.value.enableNearBreach) {
121+
<mat-form-field class="flex-23">
122+
<mat-label>{{ 'labels.inputs.Near Breach Threshold' | translate }} %</mat-label>
123+
<input
124+
type="number"
125+
matInput
126+
required
127+
mifosxPositiveNumber
128+
formControlName="nearBreachThreshold"
129+
min="0.01"
130+
max="100.00"
131+
step="0.01"
132+
/>
133+
@if (loanProductSettingsForm.controls.nearBreachThreshold.hasError('required')) {
134+
<mat-error>
135+
{{ 'labels.inputs.Near Breach Threshold' | translate }} {{ 'labels.commons.is' | translate }}
136+
<strong>{{ 'labels.commons.required' | translate }}</strong>
137+
</mat-error>
138+
}
139+
</mat-form-field>
140+
141+
<mifosx-input-positive-integer
142+
class="flex-23"
143+
[inputFormControl]="loanProductSettingsForm.controls.nearBreachEvalFrequency"
144+
[inputLabel]="'Near Breach Evaluation Frequency'"
145+
[isRequired]="true"
146+
[minVal]="'1'"
147+
></mifosx-input-positive-integer>
148+
149+
<mat-form-field class="flex-23">
150+
<mat-label>{{ 'labels.inputs.Near Breach Evaluation Frequency Type' | translate }}</mat-label>
151+
<mat-select formControlName="nearBreachEvalFrequencyType" required>
152+
@for (nearBreachEvalFrequencyType of frequencyTypesOptions; track nearBreachEvalFrequencyType) {
153+
<mat-option [value]="nearBreachEvalFrequencyType.id">
154+
{{ nearBreachEvalFrequencyType.value | translateKey: 'catalogs' }}
155+
</mat-option>
156+
}
157+
</mat-select>
158+
</mat-form-field>
159+
}
96160
<mat-divider class="flex-98"></mat-divider>
97161
} @else if (loanProductService.isLoanProduct) {
98162
<mat-form-field class="flex-30">

src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
2121
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
2222
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
2323
import { LoanProductBaseComponent } from '../../common/loan-product-base.component';
24-
import { Breach } from '../../models/loan-product.model';
24+
import { Breach, NearBreach } from '../../models/loan-product.model';
2525
import { BreachDisplayComponent } from 'app/shared/loan/breach-display/breach-display.component';
2626
import { MatSelectTrigger } from '@angular/material/select';
27+
import { InputPositiveIntegerComponent } from 'app/shared/input-positive-integer/input-positive-integer.component';
2728

2829
@Component({
2930
selector: 'mifosx-loan-product-settings-step',
@@ -39,7 +40,8 @@ import { MatSelectTrigger } from '@angular/material/select';
3940
MatStepperPrevious,
4041
MatStepperNext,
4142
MatSelectTrigger,
42-
BreachDisplayComponent
43+
BreachDisplayComponent,
44+
InputPositiveIntegerComponent
4345
]
4446
})
4547
export class LoanProductSettingsStepComponent extends LoanProductBaseComponent implements OnInit {
@@ -87,6 +89,7 @@ export class LoanProductSettingsStepComponent extends LoanProductBaseComponent i
8789

8890
delinquencyStartTypeOptions: StringEnumOptionData[] = [];
8991
breachOptions: Breach[] = [];
92+
nearBreachOptions: NearBreach[] = [];
9093

9194
frequencyTypesOptions: StringEnumOptionData[] = [];
9295

@@ -178,6 +181,7 @@ export class LoanProductSettingsStepComponent extends LoanProductBaseComponent i
178181
if (this.loanProductService.isWorkingCapital) {
179182
this.frequencyTypesOptions = this.loanProductsTemplate.periodFrequencyTypeOptions ?? [];
180183
this.breachOptions = this.loanProductsTemplate.breachOptions ?? [];
184+
this.nearBreachOptions = this.loanProductsTemplate.nearBreachOptions ?? [];
181185
this.delinquencyStartTypeOptions = this.loanProductsTemplate.delinquencyStartTypeOptions;
182186
this.loanProductSettingsForm.patchValue({
183187
amortizationType: this.loanProductsTemplate.amortizationType
@@ -189,16 +193,8 @@ export class LoanProductSettingsStepComponent extends LoanProductBaseComponent i
189193
? this.loanProductsTemplate.delinquencyStartType.id
190194
: null,
191195
breachId: this.loanProductsTemplate.breach?.id ?? null,
192-
enableNearBreach: this.loanProductsTemplate.enableNearBreach || false
196+
nearBreachId: this.loanProductsTemplate.nearBreach?.id ?? null
193197
});
194-
195-
if (this.loanProductsTemplate.enableNearBreach) {
196-
this.loanProductSettingsForm.patchValue({
197-
nearBreachEvalFrequency: this.loanProductsTemplate.nearBreachEvalFrequency || '',
198-
nearBreachEvalFrequencyType: this.loanProductsTemplate.nearBreachEvalFrequencyType?.id || '',
199-
nearBreachThreshold: this.loanProductsTemplate.nearBreachThreshold || ''
200-
});
201-
}
202198
}
203199

204200
this.isAdvancedTransactionProcessingStrategy = LoanProducts.isAdvancedPaymentAllocationStrategy(
@@ -469,7 +465,7 @@ export class LoanProductSettingsStepComponent extends LoanProductBaseComponent i
469465
],
470466
delinquencyStartType: [''],
471467
breachId: [''],
472-
enableNearBreach: [false]
468+
nearBreachId: ['']
473469
});
474470
}
475471
}
@@ -870,31 +866,6 @@ export class LoanProductSettingsStepComponent extends LoanProductBaseComponent i
870866
});
871867
}
872868
});
873-
874-
this.loanProductSettingsForm.get('enableNearBreach').valueChanges.subscribe((enableNearBreach: any) => {
875-
if (enableNearBreach) {
876-
this.loanProductSettingsForm.addControl(
877-
'nearBreachEvalFrequency',
878-
new UntypedFormControl('', Validators.required)
879-
);
880-
this.loanProductSettingsForm.addControl(
881-
'nearBreachEvalFrequencyType',
882-
new UntypedFormControl('', Validators.required)
883-
);
884-
this.loanProductSettingsForm.addControl(
885-
'nearBreachThreshold',
886-
new UntypedFormControl('', [
887-
Validators.required,
888-
Validators.min(0.01),
889-
Validators.max(100.0)
890-
])
891-
);
892-
} else {
893-
this.loanProductSettingsForm.removeControl('nearBreachEvalFrequency');
894-
this.loanProductSettingsForm.removeControl('nearBreachEvalFrequencyType');
895-
this.loanProductSettingsForm.removeControl('nearBreachThreshold');
896-
}
897-
});
898869
}
899870
}
900871

@@ -956,6 +927,15 @@ export class LoanProductSettingsStepComponent extends LoanProductBaseComponent i
956927
delinquencyBucketId: ''
957928
});
958929
}
930+
} else if (propertyName === 'breachId') {
931+
this.loanProductSettingsForm.patchValue({
932+
breachId: ''
933+
});
934+
this.loanProductSettingsForm.removeControl('nearBreachId');
935+
} else if (propertyName === 'nearBreachId') {
936+
this.loanProductSettingsForm.patchValue({
937+
nearBreachId: ''
938+
});
959939
}
960940
this.loanProductSettingsForm.markAsDirty();
961941
$event.stopPropagation();
@@ -966,6 +946,11 @@ export class LoanProductSettingsStepComponent extends LoanProductBaseComponent i
966946
return this.breachOptions ? this.breachOptions.find((b) => b.id === id) : undefined;
967947
}
968948

949+
get selectedNearBreach(): NearBreach | undefined {
950+
const id = this.loanProductSettingsForm.get('nearBreachId')?.value;
951+
return id ? (this.nearBreachOptions ? this.nearBreachOptions.find((b) => b.id === id) : undefined) : undefined;
952+
}
953+
969954
get loanProductSettings() {
970955
const productSettings = this.loanProductSettingsForm.value;
971956
if (this.loanProductSettingsForm.value.useDueForRepaymentsConfigurations) {

src/app/products/loan-products/models/loan-product.model.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,7 @@ export interface LoanProduct {
188188
writeOffReasonsToExpenseMappings?: ChargeOffReasonToExpenseAccountMapping[];
189189

190190
// Working Capital attributes
191-
enableNearBreach?: boolean;
192-
nearBreachEvalFrequency?: number;
193-
nearBreachEvalFrequencyType?: OptionData;
194-
nearBreachThreshold?: number;
191+
nearBreach?: NearBreach;
195192
}
196193

197194
export interface AllowAttributeOverrides {
@@ -246,8 +243,17 @@ export interface AccountingMappingDTO {
246243

247244
export interface Breach {
248245
id: number;
246+
name: string;
249247
breachFrequency: number;
250248
breachFrequencyType: StringEnumOptionData;
251249
breachAmountCalculationType: StringEnumOptionData;
252250
breachAmount: number;
253251
}
252+
253+
export interface NearBreach {
254+
id: number;
255+
name: string;
256+
frequency: number;
257+
frequencyType: StringEnumOptionData;
258+
threshold: number;
259+
}

src/app/products/loan-products/working-capital/breach-configuration/breach-configuration.component.html

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,17 @@
3333
<td mat-cell *matCellDef="let breach">{{ breach.id }}</td>
3434
</ng-container>
3535

36-
<ng-container matColumnDef="breachFrequency">
37-
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Frequency' | translate }}</th>
38-
<td mat-cell *matCellDef="let breach">{{ breach.breachFrequency | formatNumber: '' : 0 }}</td>
36+
<ng-container matColumnDef="name">
37+
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Name' | translate }}</th>
38+
<td mat-cell *matCellDef="let breach">{{ breach.name }}</td>
3939
</ng-container>
4040

41-
<ng-container matColumnDef="breachFrequencyType">
42-
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Frequency Type' | translate }}</th>
43-
<td mat-cell *matCellDef="let breach">{{ breach.breachFrequencyType.code | translateKey: 'catalogs' }}</td>
41+
<ng-container matColumnDef="breachFrequency">
42+
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Frequency' | translate }}</th>
43+
<td mat-cell *matCellDef="let breach">
44+
{{ breach.breachFrequency | formatNumber: '' : 0 }}
45+
{{ breach.breachFrequencyType.code | translateKey: 'catalogs' }}
46+
</td>
4447
</ng-container>
4548

4649
<ng-container matColumnDef="breachAmountCalculationType">

src/app/products/loan-products/working-capital/breach-configuration/breach-configuration.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ export class BreachConfigurationComponent implements OnInit {
6464
/** Columns to be displayed in breaches table. */
6565
displayedColumns: string[] = [
6666
'id',
67+
'name',
6768
'breachFrequency',
68-
'breachFrequencyType',
6969
'breachAmountCalculationType',
7070
'breachAmount',
7171
'actions'
@@ -88,6 +88,7 @@ export class BreachConfigurationComponent implements OnInit {
8888
this.dataSource.filterPredicate = (data: Breach, filter: string) =>
8989
[
9090
data.id,
91+
data.name,
9192
data.breachFrequency,
9293
data.breachFrequencyType?.code,
9394
data.breachAmountCalculationType?.code,

src/app/products/loan-products/working-capital/breach-configuration/create-breach-configuration/create-breach-configuration.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@
1111
<form [formGroup]="breachForm" (ngSubmit)="submit()">
1212
<mat-card-content>
1313
<div class="layout-column">
14+
<mat-form-field class="flex-100">
15+
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
16+
<input matInput required formControlName="name" />
17+
@if (breachForm.controls.name.hasError('required')) {
18+
<mat-error>
19+
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
20+
<strong>{{ 'labels.commons.required' | translate }}</strong>
21+
</mat-error>
22+
}
23+
</mat-form-field>
1424
<mifosx-input-positive-integer
1525
class="flex-48"
1626
[inputFormControl]="breachForm.controls.breachFrequency"

src/app/products/loan-products/working-capital/breach-configuration/create-breach-configuration/create-breach-configuration.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ export class CreateBreachConfigurationComponent implements OnInit {
4949

5050
ngOnInit(): void {
5151
this.breachForm = this.formBuilder.group({
52+
name: [
53+
'',
54+
[
55+
Validators.required
56+
]
57+
],
5258
breachFrequency: [
5359
'',
5460
[

src/app/products/loan-products/working-capital/breach-configuration/edit-breach-configuration/edit-breach-configuration.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@
1111
<form [formGroup]="breachForm" (ngSubmit)="submit()">
1212
<mat-card-content>
1313
<div class="layout-column">
14+
<mat-form-field class="flex-100">
15+
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
16+
<input matInput required formControlName="name" />
17+
@if (breachForm.controls.name.hasError('required')) {
18+
<mat-error>
19+
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
20+
<strong>{{ 'labels.commons.required' | translate }}</strong>
21+
</mat-error>
22+
}
23+
</mat-form-field>
1424
<mifosx-input-positive-integer
1525
class="flex-48"
1626
[inputFormControl]="breachForm.controls.breachFrequency"

0 commit comments

Comments
 (0)