Skip to content

Commit 842ee6c

Browse files
WEB-657: Working Capital product near breach configuration
1 parent 33b7290 commit 842ee6c

56 files changed

Lines changed: 2759 additions & 100 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/core/http/error-handler.interceptor.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ const log = new Logger('ErrorHandlerInterceptor');
3232
export class ErrorHandlerInterceptor implements HttpInterceptor {
3333
private alertService = inject(AlertService);
3434
private translate = inject(TranslateService);
35+
private databaseErrorCodes: string[] = [
36+
'error.msg.data.integrity.issue.entity.duplicated',
37+
'error.msg.data.integrity.issue'
38+
];
3539

3640
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
3741
return next.handle(request).pipe(catchError((error) => this.handleError(error, request)));
@@ -63,9 +67,16 @@ export class ErrorHandlerInterceptor implements HttpInterceptor {
6367
let parameterName: string | null = null;
6468
if (response.error.errors) {
6569
if (response.error.errors[0]) {
66-
errorMessage =
67-
response.error.errors[0].defaultUserMessage.replace(/\\./g, ' ') ||
68-
response.error.errors[0].developerMessage.replace(/\\./g, ' ');
70+
if (
71+
response.error.errors[0].userMessageGlobalisationCode &&
72+
this.databaseErrorCodes.indexOf(response.error.errors[0].userMessageGlobalisationCode) > -1
73+
) {
74+
errorMessage = this.translate.instant('errors.error.msg.data.integrity.issue');
75+
} else {
76+
errorMessage =
77+
response.error.errors[0].defaultUserMessage.replace(/\\./g, ' ') ||
78+
response.error.errors[0].developerMessage.replace(/\\./g, ' ');
79+
}
6980
}
7081
if ('parameterName' in response.error.errors[0]) {
7182
parameterName = response.error.errors[0].parameterName;

src/app/loans/create-loans-account/create-loans-account.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export class CreateLoansAccountComponent extends LoanProductBaseComponent implem
123123
this.loansAccountProductTemplate = templateData.loanData;
124124
this.loansAccountProductTemplate.options = {
125125
breachOptions: templateData.breachOptions,
126+
nearBreachOptions: templateData.nearBreachOptions,
126127
delinquencyBucketOptions: templateData.delinquencyBucketOptions,
127128
fundOptions: templateData.fundOptions,
128129
periodFrequencyTypeOptions: templateData.periodFrequencyTypeOptions,

src/app/loans/edit-loans-account/edit-loans-account.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export class EditLoansAccountComponent extends LoanProductBaseComponent {
108108
this.loansAccountProductTemplate = templateData.loanData;
109109
this.loansAccountProductTemplate.options = {
110110
breachOptions: templateData.breachOptions,
111+
nearBreachOptions: templateData.nearBreachOptions,
111112
delinquencyBucketOptions: templateData.delinquencyBucketOptions,
112113
fundOptions: templateData.fundOptions,
113114
periodFrequencyTypeOptions: templateData.periodFrequencyTypeOptions,

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ <h3 class="mat-h3 margin-t flex-fill">{{ 'labels.heading.Terms' | translate }}</
121121
</div>
122122
}
123123

124+
@if (loansAccount.breachId) {
125+
<div class="flex-fill">
126+
<span class="flex-30">{{ 'labels.inputs.Breach' | translate }}:</span>
127+
<span class="flex-60"
128+
><mifosx-breach-display [singleRow]="false" [breach]="getBreach(loansAccount.breachId)"
129+
/></span>
130+
</div>
131+
}
132+
133+
@if (loansAccount.nearBreachId) {
134+
<div class="flex-fill">
135+
<span class="flex-30">{{ 'labels.inputs.Near Breach' | translate }}:</span>
136+
<span class="flex-60"
137+
><mifosx-breach-display [singleRow]="false" [nearBreach]="getNearBreach(loansAccount.nearBreachId)"
138+
/></span>
139+
</div>
140+
}
141+
124142
<div class="flex-fill">
125143
<span class="flex-30">{{ 'labels.inputs.Period Payment Rate' | translate }}:</span>
126144
<span class="flex-70"> {{ loansAccount.periodPaymentRate | formatNumber }} % </span>

src/app/loans/loans-account-stepper/loans-account-preview-step/loans-account-preview-step.component.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
3535
import { LoanProductBaseComponent } from 'app/products/loan-products/common/loan-product-base.component';
3636
import { LoanProductBasicDetails } from 'app/loans/models/loan-product.model';
3737
import { LongTextComponent } from 'app/shared/long-text/long-text.component';
38+
import { Breach, NearBreach } from 'app/products/loan-products/models/loan-product.model';
39+
import { BreachDisplayComponent } from 'app/shared/loan/breach-display/breach-display.component';
3840

3941
/**
4042
* Create Loans Account Preview Step
@@ -65,7 +67,8 @@ import { LongTextComponent } from 'app/shared/long-text/long-text.component';
6567
FormatNumberPipe,
6668
YesnoPipe,
6769
TranslatePipe,
68-
LongTextComponent
70+
LongTextComponent,
71+
BreachDisplayComponent
6972
]
7073
})
7174
export class LoansAccountPreviewStepComponent extends LoanProductBaseComponent implements OnChanges {
@@ -155,4 +158,20 @@ export class LoansAccountPreviewStepComponent extends LoanProductBaseComponent i
155158
camalize(word: string) {
156159
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
157160
}
161+
162+
getBreach(breachId: number | null | undefined): Breach | null {
163+
if (breachId === null || breachId === undefined) {
164+
return null;
165+
}
166+
return this.loansAccountProductTemplate.options.breachOptions?.find((b: Breach) => b.id === breachId) || null;
167+
}
168+
169+
getNearBreach(nearBreachId: number | null | undefined): NearBreach | null {
170+
if (nearBreachId === null || nearBreachId === undefined) {
171+
return null;
172+
}
173+
return (
174+
this.loansAccountProductTemplate.options.nearBreachOptions?.find((b: NearBreach) => b.id === nearBreachId) || null
175+
);
176+
}
158177
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ <h4 class="mat-h4 flex-98">
107107
}
108108
</mat-select>
109109
</mat-form-field>
110+
111+
@if (loansAccountTermsForm.value.breachId) {
112+
<mat-form-field class="flex-48">
113+
<mat-label>{{ 'labels.inputs.Near Breach' | translate }}</mat-label>
114+
<mat-select formControlName="nearBreachId" class="breach-id-select" panelClass="breach-select-panel">
115+
<mat-select-trigger>
116+
@if (selectedNearBreach) {
117+
<mifosx-breach-display [singleRow]="true" [nearBreach]="selectedNearBreach" />
118+
}
119+
</mat-select-trigger>
120+
@for (nearBreach of nearBreachOptions; track nearBreach) {
121+
<mat-option [value]="nearBreach.id">
122+
<mifosx-breach-display [singleRow]="true" [nearBreach]="nearBreach" />
123+
</mat-option>
124+
}
125+
</mat-select>
126+
</mat-form-field>
127+
}
110128
}
111129

112130
@if (loanProductService.isLoanProduct) {

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { MatDialog } from '@angular/material/dialog';
1313
import { ActivatedRoute } from '@angular/router';
1414
import { LoansAccountAddCollateralDialogComponent } from 'app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component';
1515
import { LoanProducts } from 'app/products/loan-products/loan-products';
16-
import { Breach, LoanProduct } from 'app/products/loan-products/models/loan-product.model';
16+
import { Breach, LoanProduct, NearBreach } from 'app/products/loan-products/models/loan-product.model';
1717
import { SettingsService } from 'app/settings/settings.service';
1818
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
1919
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
@@ -178,6 +178,7 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
178178

179179
delinquencyStartTypeOptions: StringEnumOptionData[] = [];
180180
breachOptions: Breach[] = [];
181+
nearBreachOptions: NearBreach[] = [];
181182

182183
constructor() {
183184
super();
@@ -329,6 +330,7 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
329330
this.termFrequencyTypeData = this.loansAccountTermsData.options?.periodFrequencyTypeOptions;
330331
this.delinquencyStartTypeOptions = this.loansAccountTermsData.options?.delinquencyStartTypeOptions;
331332
this.breachOptions = this.loansAccountTermsData.options?.breachOptions ?? [];
333+
this.nearBreachOptions = this.loansAccountTermsData.options?.nearBreachOptions ?? [];
332334
if (this.loanId != null && 'accountNo' in this.loansAccountTemplate) {
333335
this.loansAccountTermsData = this.loansAccountTemplate;
334336
this.loansAccountTermsForm.patchValue({
@@ -340,15 +342,17 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
340342
repaymentFrequencyType: this.loansAccountTermsData.repaymentFrequencyType?.id,
341343
delinquencyGraceDays: this.loansAccountTermsData.delinquencyGraceDays,
342344
delinquencyStartType: this.loansAccountTermsData.delinquencyStartType?.code,
343-
breachId: this.loansAccountTermsData.breach?.id
345+
breachId: this.loansAccountTermsData.breach?.id,
346+
nearBreachId: this.loansAccountTermsData.nearBreach?.id
344347
});
345348
} else {
346349
this.loansAccountTermsForm.patchValue({
347350
discount: this.loansAccountTermsData.product.discount || '',
348351
principalAmount: this.loansAccountTermsData.product.principal,
349352
delinquencyGraceDays: this.loansAccountTermsData.product.delinquencyGraceDays || '',
350353
delinquencyStartType: this.loansAccountTermsData.product.delinquencyStartType?.code || '',
351-
breachId: this.loansAccountTermsData.product.breach?.id || ''
354+
breachId: this.loansAccountTermsData.product.breach?.id || '',
355+
nearBreachId: this.loansAccountTermsData.product.nearBreach?.id || ''
352356
});
353357
}
354358
this.allowAttributeOverrides = this.loansAccountProductTemplate.product.allowAttributeOverrides;
@@ -369,6 +373,7 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
369373
}
370374
if (!this.allowAttributeOverrides.breach || this.allowAttributeOverrides.breach === false) {
371375
this.loansAccountTermsForm.controls.breachId.disable();
376+
this.loansAccountTermsForm.controls.nearBreachId.disable();
372377
}
373378
}
374379
}
@@ -466,7 +471,8 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
466471
delinquencyStartType:
467472
this.loansAccountTermsData.delinquencyStartType?.id ||
468473
this.loansAccountTermsData.product.delinquencyStartType?.id,
469-
breachId: this.loansAccountTermsData.breach?.id || this.loansAccountTermsData.product.breach?.id
474+
breachId: this.loansAccountTermsData.breach?.id || this.loansAccountTermsData.product.breach?.id,
475+
nearBreachId: this.loansAccountTermsData.nearBreach?.id || this.loansAccountTermsData.product.nearBreach?.id
470476
});
471477
}
472478
}
@@ -731,7 +737,8 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
731737
]
732738
],
733739
delinquencyStartType: [''],
734-
breachId: ['']
740+
breachId: [''],
741+
nearBreachId: ['']
735742
});
736743
}
737744
}
@@ -927,4 +934,9 @@ export class LoansAccountTermsStepComponent extends LoanProductBaseComponent imp
927934
const id = this.loansAccountTermsForm.get('breachId')?.value;
928935
return this.breachOptions ? this.breachOptions.find((b) => b.id === id) : undefined;
929936
}
937+
938+
get selectedNearBreach(): NearBreach | undefined {
939+
const id = this.loansAccountTermsForm.get('nearBreachId')?.value;
940+
return this.nearBreachOptions ? this.nearBreachOptions.find((b) => b.id === id) : undefined;
941+
}
930942
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ <h3>{{ 'labels.heading.Loan Details' | translate }}</h3>
6060
<span class="flex-50"><mifosx-breach-display [singleRow]="false" [breach]="loanDetails.breach" /></span>
6161
</div>
6262
}
63+
64+
@if (loanDetails.nearBreach) {
65+
<div class="flex-fill layout-row">
66+
<span class="flex-50"> {{ 'labels.inputs.Near Breach' | translate }} </span>
67+
<span class="flex-50"
68+
><mifosx-breach-display [singleRow]="false" [nearBreach]="loanDetails.nearBreach"
69+
/></span>
70+
</div>
71+
}
6372
}
6473

6574
@if (loanProductService.isLoanProduct) {

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,18 @@ <h3 class="mat-h3 flex-fill">{{ 'labels.heading.Terms' | translate }}</h3>
109109
</div>
110110
<div class="flex-fill layout-row">
111111
<span class="flex-40">{{ 'labels.inputs.Breach' | translate }}:</span>
112-
<span class="flex-60"><mifosx-breach-display [singleRow]="false" [breach]="loanProduct.breach" /></span>
112+
<span class="flex-60"><mifosx-breach-display [singleRow]="false" [breach]="getBreach()" /></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"><mifosx-breach-display [singleRow]="false" [nearBreach]="getNearBreach()" /></span>
122+
</div>
123+
}
114124
}
115125
@if (loanProductService.isLoanProduct) {
116126
<div class="flex-fill layout-row">

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { Component, Input, OnChanges, OnInit, SimpleChanges, inject } from '@angular/core';
10-
import { DelinquencyBucket, LoanProduct } from '../../models/loan-product.model';
10+
import { Breach, DelinquencyBucket, LoanProduct, NearBreach } from '../../models/loan-product.model';
1111
import {
1212
AccountingMapping,
1313
Charge,
@@ -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,11 +685,37 @@ export class LoanProductSummaryComponent extends LoanProductBaseComponent implem
675685
);
676686
}
677687

688+
enableNearBreach(): boolean {
689+
return this.loanProductService.isWorkingCapital && this.getNearBreach() !== null;
690+
}
691+
678692
getAccountingRuleName(value: string): string {
679693
return this.loanProductService.isWorkingCapital ? '' : this.accounting.getAccountRuleName(value.toUpperCase());
680694
}
681695

682696
mapHumanReadableValueStringEnumOptionDataList(incomingParameter: StringEnumOptionData[]): string[] {
683697
return incomingParameter.map((v) => v.value);
684698
}
699+
700+
getBreach(): Breach | null {
701+
if (this.loanProduct.breach) {
702+
return this.loanProduct.breach;
703+
}
704+
if (this.loanProduct.breachId === null || this.loanProduct.breachId === undefined) {
705+
return null;
706+
}
707+
return this.loanProductsTemplate.breachOptions?.find((b: Breach) => b.id === this.loanProduct.breachId) || null;
708+
}
709+
710+
getNearBreach(): NearBreach | null {
711+
if (this.loanProduct.nearBreach) {
712+
return this.loanProduct.nearBreach;
713+
}
714+
if (this.loanProduct.nearBreachId === null || this.loanProduct.nearBreachId === undefined) {
715+
return null;
716+
}
717+
return (
718+
this.loanProductsTemplate.nearBreachOptions?.find((b: Breach) => b.id === this.loanProduct.nearBreachId) || null
719+
);
720+
}
685721
}

0 commit comments

Comments
 (0)