From e4274c55bb95d71906d51a45adf948a85405376e Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Thu, 14 Mar 2024 23:50:04 -0600 Subject: [PATCH] Fixed Length configuration in Loan Products --- .../loan-product-summary.component.html | 5 ++ .../loan-product-summary.component.ts | 5 ++ .../create-loan-product.component.html | 16 ++-- .../edit-loan-product.component.html | 16 ++-- .../loan-product-settings-step.component.ts | 28 +++--- .../loan-product-terms-step.component.html | 31 +++++-- .../loan-product-terms-step.component.scss | 4 + .../loan-product-terms-step.component.ts | 86 +++++++++++++++---- .../services/processing-strategy.service.ts | 19 ++++ 9 files changed, 157 insertions(+), 53 deletions(-) create mode 100644 src/app/products/loan-products/services/processing-strategy.service.ts diff --git a/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.html b/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.html index c590803e1..cbb2f9216 100644 --- a/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.html +++ b/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.html @@ -290,6 +290,11 @@ Repay Every: {{ loanProduct.repaymentEvery + ' ' + loanProduct.repaymentFrequencyType.value }} + +
+ Fixed Length: + {{ loanProduct.fixedLength + ' ' + loanProduct.repaymentFrequencyType.value }} +
Minimum days between disbursal and first repayment date: diff --git a/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.ts b/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.ts index e2c97beed..8dbaf0472 100644 --- a/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.ts +++ b/src/app/products/loan-products/common/loan-product-summary/loan-product-summary.component.ts @@ -154,6 +154,11 @@ export class LoanProductSummaryComponent implements OnInit, OnChanges { optionValue = this.optionDataLookUp(this.loanProduct.interestCalculationPeriodType, this.loanProductsTemplate.interestCalculationPeriodTypeOptions); this.loanProduct.interestCalculationPeriodType = optionValue; + if (!this.loanProduct.repaymentFrequencyType || !this.loanProduct.repaymentFrequencyType.value) { + optionValue = this.optionDataLookUp(this.loanProduct.repaymentFrequencyType, this.loanProductsTemplate.repaymentFrequencyTypeOptions); + this.loanProduct.repaymentFrequencyType = optionValue; + } + optionValue = this.optionDataLookUp(this.loanProduct.daysInMonthType, this.loanProductsTemplate.daysInMonthTypeOptions); this.loanProduct.daysInMonthType = optionValue; optionValue = this.optionDataLookUp(this.loanProduct.daysInYearType, this.loanProductsTemplate.daysInYearTypeOptions); diff --git a/src/app/products/loan-products/create-loan-product/create-loan-product.component.html b/src/app/products/loan-products/create-loan-product/create-loan-product.component.html index b278329bc..f1d0cc6b0 100644 --- a/src/app/products/loan-products/create-loan-product/create-loan-product.component.html +++ b/src/app/products/loan-products/create-loan-product/create-loan-product.component.html @@ -38,14 +38,6 @@ - - - TERMS - - - - - SETTINGS @@ -79,6 +71,14 @@ + + + TERMS + + + + + CHARGES diff --git a/src/app/products/loan-products/edit-loan-product/edit-loan-product.component.html b/src/app/products/loan-products/edit-loan-product/edit-loan-product.component.html index c4f18073b..6073b91fd 100644 --- a/src/app/products/loan-products/edit-loan-product/edit-loan-product.component.html +++ b/src/app/products/loan-products/edit-loan-product/edit-loan-product.component.html @@ -38,14 +38,6 @@ - - - TERMS - - - - - SETTINGS @@ -79,6 +71,14 @@ + + + TERMS + + + + + CHARGES diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts index 22b74676d..21086bb5d 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts @@ -4,6 +4,7 @@ import { LoanProducts } from '../../loan-products'; import { rangeValidator } from 'app/shared/validators/percentage.validator'; import { GlobalConfiguration } from 'app/system/configurations/global-configurations-tab/configuration.model'; import { CodeName, OptionData } from 'app/shared/models/option-data.model'; +import { ProcessingStrategyService } from '../../services/processing-strategy.service'; @Component({ @@ -46,7 +47,8 @@ export class LoanProductSettingsStepComponent implements OnInit { /** Values to Days for Repayments */ defaultConfigValues: GlobalConfiguration[] = []; - constructor(private formBuilder: UntypedFormBuilder) { + constructor(private formBuilder: UntypedFormBuilder, + private processingStrategyService: ProcessingStrategyService) { this.createLoanProductSettingsForm(); this.setConditionalControls(); } @@ -117,11 +119,8 @@ export class LoanProductSettingsStepComponent implements OnInit { }); this.isAdvancedTransactionProcessingStrategy = LoanProducts.isAdvancedPaymentAllocationStrategy(transactionProcessingStrategyCode); - if (this.isAdvancedTransactionProcessingStrategy) { - this.loanProductSettingsForm.addControl('loanScheduleProcessingType', new UntypedFormControl( - this.loanProductsTemplate.loanScheduleProcessingType.code || LoanProducts.LOAN_SCHEDULE_PROCESSING_TYPE_HORIZONTAL, - [Validators.required ])); - } + this.processingStrategyService.initialize(this.isAdvancedTransactionProcessingStrategy); + this.validateAdvancedPaymentStrategyControls(); if (this.loanProductsTemplate.dueDaysForRepaymentEvent != null && this.loanProductsTemplate.overDueDaysForRepaymentEvent != null) { @@ -405,12 +404,8 @@ export class LoanProductSettingsStepComponent implements OnInit { .subscribe((transactionProcessingStrategyCode: string) => { this.advancePaymentStrategy.emit(transactionProcessingStrategyCode); this.isAdvancedTransactionProcessingStrategy = LoanProducts.isAdvancedPaymentAllocationStrategy(transactionProcessingStrategyCode); - if (this.isAdvancedTransactionProcessingStrategy) { - this.loanProductSettingsForm.addControl('loanScheduleProcessingType', new UntypedFormControl( - this.loanProductsTemplate.loanScheduleProcessingType.code || LoanProducts.LOAN_SCHEDULE_PROCESSING_TYPE_HORIZONTAL, [Validators.required])); - } else { - this.loanProductSettingsForm.removeControl('loanScheduleProcessingType'); - } + this.processingStrategyService.initialize(this.isAdvancedTransactionProcessingStrategy); + this.validateAdvancedPaymentStrategyControls(); }); this.loanProductSettingsForm.get('allowAttributeConfiguration').valueChanges @@ -484,6 +479,7 @@ export class LoanProductSettingsStepComponent implements OnInit { }); this.isAdvancedTransactionProcessingStrategy = true; } + this.processingStrategyService.initialize(this.isAdvancedTransactionProcessingStrategy); }); } @@ -519,4 +515,12 @@ export class LoanProductSettingsStepComponent implements OnInit { return productSettings; } + private validateAdvancedPaymentStrategyControls(): void { + if (this.isAdvancedTransactionProcessingStrategy) { + this.loanProductSettingsForm.addControl('loanScheduleProcessingType', new UntypedFormControl( + this.loanProductsTemplate.loanScheduleProcessingType.code || LoanProducts.LOAN_SCHEDULE_PROCESSING_TYPE_HORIZONTAL, [Validators.required])); + } else { + this.loanProductSettingsForm.removeControl('loanScheduleProcessingType'); + } + } } diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.html index b078429c9..32732a771 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.html +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.html @@ -87,7 +87,11 @@

Interest Rates

- + + Is Zero Interest? + + + Is Linked to floating interest rates? @@ -97,7 +101,7 @@ Minimum - + Minimum Value must be greater than 0 @@ -105,7 +109,7 @@ Default - + Default nominal interest rate is required @@ -113,7 +117,7 @@ Maximum - + Maximum Value must be greater equal to than 0 and must be greater than Minimum Principal @@ -121,7 +125,7 @@ Frequency - + {{ interestRateFrequencyType.value }} @@ -133,7 +137,7 @@
-
+

Floating interest rate

@@ -390,9 +394,9 @@ -

Repaid every

+

Repaid every

- + Frequency @@ -400,7 +404,7 @@ - + Type @@ -412,6 +416,15 @@ +

Fixed Length

+ + + Fixed Length + + + {{ loanProductTermsForm.value.repaymentFrequencyType | find:repaymentFrequencyTypeData:'id':'value' }} + Minimum days between disbursal and first repayment date diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss index 8381fd426..a7c4a3409 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss @@ -21,3 +21,7 @@ mat-divider { .margin-t { margin-top: 1em; } + +.label-field { + margin-top: 32px; +} diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.ts index 91778c3ae..4a31e2d7b 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.ts +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormArray, UntypedFormControl } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { TooltipPosition } from '@angular/material/tooltip'; @@ -9,18 +9,23 @@ import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.co import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base'; import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base'; import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base'; +import { LoanProducts } from '../../loan-products'; +import { ProcessingStrategyService } from '../../services/processing-strategy.service'; @Component({ selector: 'mifosx-loan-product-terms-step', templateUrl: './loan-product-terms-step.component.html', styleUrls: ['./loan-product-terms-step.component.scss'] }) -export class LoanProductTermsStepComponent implements OnInit { +export class LoanProductTermsStepComponent implements OnInit, OnChanges { @Input() loanProductsTemplate: any; loanProductTermsForm: UntypedFormGroup; + /** Zero Interest control. */ + zeroInterest = new UntypedFormControl(false); + valueConditionTypeData: any; floatingRateData: any; interestRateFrequencyTypeData: any; @@ -29,9 +34,11 @@ export class LoanProductTermsStepComponent implements OnInit { repaymentStartDateTypeOptions: any; displayedColumns: string[] = ['valueConditionType', 'borrowerCycleNumber', 'minValue', 'defaultValue', 'maxValue', 'actions']; + isAdvancedTransactionProcessingStrategy = false; constructor(private formBuilder: UntypedFormBuilder, - public dialog: MatDialog) { + private processingStrategyService: ProcessingStrategyService, + private dialog: MatDialog) { this.createLoanProductTermsForm(); this.setConditionalControls(); } @@ -83,6 +90,13 @@ export class LoanProductTermsStepComponent implements OnInit { this.formBuilder.array(this.loanProductsTemplate.numberOfRepaymentVariationsForBorrowerCycle.map((variation: any) => ({ ...variation, valueConditionType: variation.valueConditionType.id })))); this.loanProductTermsForm.setControl('interestRateVariationsForBorrowerCycle', this.formBuilder.array(this.loanProductsTemplate.interestRateVariationsForBorrowerCycle.map((variation: any) => ({ ...variation, valueConditionType: variation.valueConditionType.id })))); + + this.zeroInterest.patchValue((this.loanProductsTemplate.minInterestRatePerPeriod === 0 && this.loanProductsTemplate.interestRatePerPeriod === 0 && this.loanProductsTemplate.maxInterestRatePerPeriod === 0)); + + this.processingStrategyService.advancedTransactionProcessingStrategy.subscribe((value: boolean) => { + this.isAdvancedTransactionProcessingStrategy = value; + }); + this.validateAdvancedPaymentStrategyControls(); } createLoanProductTermsForm() { @@ -103,10 +117,15 @@ export class LoanProductTermsStepComponent implements OnInit { 'repaymentEvery': ['', Validators.required], 'repaymentFrequencyType': ['', Validators.required], 'minimumDaysBetweenDisbursalAndFirstRepayment': [''], - 'repaymentStartDateType': [1] + 'repaymentStartDateType': [1], + 'fixedLength': [null] }); } + ngOnChanges(changes: SimpleChanges): void { + this.validateAdvancedPaymentStrategyControls(); + } + setConditionalControls() { this.loanProductTermsForm.get('allowApprovedDisbursedAmountsOverApplied').valueChanges .subscribe(allowApprovedDisbursedAmountsOverApplied => { @@ -149,18 +168,37 @@ export class LoanProductTermsStepComponent implements OnInit { } }); - this.loanProductTermsForm.get('useBorrowerCycle').valueChanges - .subscribe(useBorrowerCycle => { - if (useBorrowerCycle) { - this.loanProductTermsForm.addControl('principalVariationsForBorrowerCycle', this.formBuilder.array([])); - this.loanProductTermsForm.addControl('numberOfRepaymentVariationsForBorrowerCycle', this.formBuilder.array([])); - this.loanProductTermsForm.addControl('interestRateVariationsForBorrowerCycle', this.formBuilder.array([])); - } else { - this.loanProductTermsForm.removeControl('principalVariationsForBorrowerCycle'); - this.loanProductTermsForm.removeControl('numberOfRepaymentVariationsForBorrowerCycle'); - this.loanProductTermsForm.removeControl('interestRateVariationsForBorrowerCycle'); - } - }); + this.loanProductTermsForm.get('useBorrowerCycle').valueChanges + .subscribe(useBorrowerCycle => { + if (useBorrowerCycle) { + this.loanProductTermsForm.addControl('principalVariationsForBorrowerCycle', this.formBuilder.array([])); + this.loanProductTermsForm.addControl('numberOfRepaymentVariationsForBorrowerCycle', this.formBuilder.array([])); + this.loanProductTermsForm.addControl('interestRateVariationsForBorrowerCycle', this.formBuilder.array([])); + } else { + this.loanProductTermsForm.removeControl('principalVariationsForBorrowerCycle'); + this.loanProductTermsForm.removeControl('numberOfRepaymentVariationsForBorrowerCycle'); + this.loanProductTermsForm.removeControl('interestRateVariationsForBorrowerCycle'); + } + }); + + this.zeroInterest.valueChanges.subscribe(zeroInterest => { + if (zeroInterest) { + this.loanProductTermsForm.get('minInterestRatePerPeriod').patchValue(0); + this.loanProductTermsForm.get('minInterestRatePerPeriod').disable(); + this.loanProductTermsForm.get('interestRatePerPeriod').patchValue(0); + this.loanProductTermsForm.get('interestRatePerPeriod').disable(); + this.loanProductTermsForm.get('maxInterestRatePerPeriod').patchValue(0); + this.loanProductTermsForm.get('maxInterestRatePerPeriod').disable(); + } else { + this.loanProductTermsForm.get('minInterestRatePerPeriod').patchValue(this.loanProductsTemplate.minInterestRatePerPeriod); + this.loanProductTermsForm.get('minInterestRatePerPeriod').enable(); + this.loanProductTermsForm.get('interestRatePerPeriod').patchValue(this.loanProductsTemplate.interestRatePerPeriod); + this.loanProductTermsForm.get('interestRatePerPeriod').enable(); + this.loanProductTermsForm.get('maxInterestRatePerPeriod').patchValue(this.loanProductsTemplate.maxInterestRatePerPeriod); + this.loanProductTermsForm.get('maxInterestRatePerPeriod').enable(); + } + this.validateAdvancedPaymentStrategyControls(); + }); } get principalVariationsForBorrowerCycle(): UntypedFormArray { @@ -271,4 +309,20 @@ export class LoanProductTermsStepComponent implements OnInit { return this.loanProductTermsForm.value; } + isZeroInterest(): boolean { + return this.zeroInterest.value; + } + + allowFixedLength(): boolean { + return this.isAdvancedTransactionProcessingStrategy && this.isZeroInterest(); + } + + private validateAdvancedPaymentStrategyControls(): void { + if (this.allowFixedLength()) { + this.loanProductTermsForm.get('fixedLength').patchValue(this.loanProductsTemplate.fixedLength || null); + } else { + this.loanProductTermsForm.get('fixedLength').patchValue(null); + } + } + } diff --git a/src/app/products/loan-products/services/processing-strategy.service.ts b/src/app/products/loan-products/services/processing-strategy.service.ts new file mode 100644 index 000000000..197545022 --- /dev/null +++ b/src/app/products/loan-products/services/processing-strategy.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ProcessingStrategyService { + + advancedTransactionProcessingStrategy = new BehaviorSubject(false); + + constructor() { } + + initialize(value: boolean) { + this.advancedTransactionProcessingStrategy.next(value); + } + + get isAdvancedTransactionProcessingStrategy(): boolean { return this.advancedTransactionProcessingStrategy.value; } + +}