From 8632abb34ce5121ba364c0c217308e8ed10093e5 Mon Sep 17 00:00:00 2001 From: Felix van Hove Date: Sun, 16 Mar 2025 21:49:49 +0100 Subject: [PATCH] fix: Add possibility to create GLIM account applications (#2323) This is a first step to support GLIM account applications. It enables a user to create a GLIM account application and provides a starting point for a discussion of the feature, which has not even been working for the community app and is insufficiently documented. This is part of a larger effort to improve group account management. Partially addresses: WEB-9 --- .../create-glim-account.component.html | 5 + .../create-glim-account.component.ts | 190 +++--- .../glim-charges-step.component.html | 51 +- .../glim-charges-step.component.scss | 3 + .../glim-charges-step.component.ts | 98 +++- .../glim-details-step.component.html | 31 +- .../glim-details-step.component.ts | 79 ++- .../glim-preview-step.component.html | 41 +- .../glim-preview-step.component.ts | 15 +- .../glim-terms-step.component.html | 554 ++++++++++++++++-- .../glim-terms-step.component.scss | 3 + .../glim-terms-step.component.ts | 440 ++++++++++++-- src/app/loans/loans.service.ts | 27 +- 13 files changed, 1292 insertions(+), 245 deletions(-) diff --git a/src/app/loans/glim-account/create-glim-account/create-glim-account.component.html b/src/app/loans/glim-account/create-glim-account/create-glim-account.component.html index 0407e0545..ac11c8cd3 100644 --- a/src/app/loans/glim-account/create-glim-account/create-glim-account.component.html +++ b/src/app/loans/glim-account/create-glim-account/create-glim-account.component.html @@ -27,6 +27,7 @@ [loansAccountTemplate]="loansAccountTemplate" (loansAccountProductTemplate)="setTemplate($event)" [gsimData]="gsimData" + #loansAccountDetailsForm > @@ -35,8 +36,10 @@ {{ 'labels.inputs.TERMS' | translate }} @@ -45,8 +48,10 @@ {{ 'labels.inputs.CHARGES' | translate }} diff --git a/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts b/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts index 02a856642..24bc7b806 100644 --- a/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts +++ b/src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts @@ -1,9 +1,12 @@ -import { Component, ViewChild } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +/** Angular Imports */ +import { Component, ViewChild, /* Felix: added: */ QueryList, ViewChildren } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { Dates } from 'app/core/utils/dates'; + +/** Custom Services */ import { LoansService } from 'app/loans/loans.service'; import { SettingsService } from 'app/settings/settings.service'; + +/** Step Components */ import { GlimChargesStepComponent } from './glim-account-stepper/glim-charges-step/glim-charges-step.component'; import { GlimDetailsStepComponent } from './glim-account-stepper/glim-details-step/glim-details-step.component'; import { GlimTermsStepComponent } from './glim-account-stepper/glim-terms-step/glim-terms-step.component'; @@ -14,7 +17,7 @@ import { GlimTermsStepComponent } from './glim-account-stepper/glim-terms-step/g styleUrls: ['./create-glim-account.component.scss'] }) export class CreateGlimAccountComponent { - /** Imports all the step component */ + /** Imports all the step components */ @ViewChild(GlimDetailsStepComponent, { static: true }) loansAccountDetailsStep: GlimDetailsStepComponent; @ViewChild(GlimTermsStepComponent, { static: true }) loansAccountTermsStep: GlimTermsStepComponent; @ViewChild(GlimChargesStepComponent, { static: true }) loansAccountChargesStep: GlimChargesStepComponent; @@ -22,38 +25,39 @@ export class CreateGlimAccountComponent { /** Loans Account Template */ loansAccountTemplate: any; /** Loans Account Product Template */ - loansAccountProductTemplate: any; - /** Collateral Options */ - collateralOptions: any; + loansAccountProductTemplate: any | null = null; /** Multi Disburse Loan */ multiDisburseLoan: any; /** Principal Amount */ principal: any; + /** Currency Code */ + currencyCode: string; - accountLinkingOptions: any; + // accountLinkingOptions: any; - totalLoan: any; + // totalLoan: any; - /** Table Data Source */ - dataSource: any; - /** Selected Members */ + // /** Table Data Source */ + // dataSource: any; + // /** Selected Members */ selectedMembers: any; - /** Selected Members */ + // /** Selected Members */ activeClientMembers: any; gsimData: any; + /** - * Sets loans account details form. - * @param {FormBuilder} formBuilder Form Builder. - * @param {LoansService} loansService Loans Service. - * @param {SettingsService} settingsService SettingsService + * Sets loans account create form. + * @param {route} ActivatedRoute Activated Route. + * @param {router} Router Router. + * @param {loansService} LoansService Loans Service + * @param {SettingsService} settingsService Settings Service + * @param {ClientsService} clientService Client Service */ constructor( private route: ActivatedRoute, private router: Router, private loansService: LoansService, - private settingsService: SettingsService, - public dialog: MatDialog, - private dateUtils: Dates + private settingsService: SettingsService ) { this.route.data.subscribe((data: { loansAccountTemplate: any; gsimData: any; groupsData: any }) => { this.loansAccountTemplate = data.loansAccountTemplate; @@ -63,11 +67,19 @@ export class CreateGlimAccountComponent { } /** - * Sets loans account product template and collateral template + * Sets glim account product template and collateral template * @param {any} $event API response */ setTemplate($event: any) { this.loansAccountProductTemplate = $event; + this.currencyCode = this.loansAccountProductTemplate.currency.code; + const entityId = this.loansAccountTemplate.clientId + ? this.loansAccountTemplate.clientId + : this.loansAccountTemplate.group.id; + const productId = this.loansAccountProductTemplate.loanProductId; + this.loansService.getLoansAccountTemplateResource(entityId, true, productId).subscribe((response: any) => { + this.multiDisburseLoan = response.multiDisburseLoan; + }); } /** Get Loans Account Details Form Data */ @@ -80,86 +92,102 @@ export class CreateGlimAccountComponent { return this.loansAccountTermsStep.loansAccountTermsForm; } - /** Checks wheter all the forms in different steps are valid or not */ + /** Checks whether all the forms in different steps are valid or not */ get loansAccountFormValid() { - return this.loansAccountDetailsForm.valid && this.loansAccountTermsForm.valid; + return ( + this.loansAccountDetailsForm.valid && this.loansAccountTermsForm.valid && this.loansAccountChargesStep.isValid + ); } /** Gets principal Amount */ get loanPrincipal() { - return this.loansAccountTermsStep.loansAccountTermsForm.value.principal; + return this.loansAccountChargesStep.selectedClientMembers.selectedMembers.reduce( + (acc: number, member: any) => acc + member.principal, + 0 + ); } /** Retrieves Data of all forms except Currency to submit the data */ get loansAccount() { - this.selectedMembers = this.loansAccountChargesStep.selectedClientMembers; return { ...this.loansAccountDetailsStep.loansAccountDetails, ...this.loansAccountTermsStep.loansAccountTerms, - ...this.loansAccountChargesStep.loansAccountCharges + ...this.loansAccountChargesStep.loansAccountCharges, + ...this.loansAccountTermsStep.disbursementData }; } - setData(client: any): any { - const locale = this.settingsService.language.code; - const dateFormat = this.settingsService.dateFormat; - // const monthDayFormat = 'dd MMMM'; - const data = { - ...this.loansAccount, - charges: this.loansAccount.charges.map((charge: any) => ({ - chargeId: charge.id, - amount: charge.amount - })), - clientId: client.id, - totalLoan: this.totalLoan, - loanType: 'glim', - amortizationType: 1, - isParentAccount: true, - principal: client.principal, - syncDisbursementWithMeeting: false, - expectedDisbursementDate: this.dateUtils.formatDate(this.loansAccount.expectedDisbursementDate, dateFormat), - submittedOnDate: this.dateUtils.formatDate(this.loansAccount.submittedOnDate, dateFormat), - dateFormat, - // monthDayFormat, - locale - }; - data.groupId = this.loansAccountTemplate.group.id; + // setData(client: any): any { + // const locale = this.settingsService.language.code; + // const dateFormat = this.settingsService.dateFormat; + // // const monthDayFormat = 'dd MMMM'; + // const data = { + // ...this.loansAccount, + // charges: this.loansAccount.charges.map((charge: any) => ({ + // chargeId: charge.id, + // amount: charge.amount + // })), + // clientId: client.id, + // totalLoan: this.totalLoan, + // loanType: 'glim', + // amortizationType: 1, + // isParentAccount: true, + // principal: client.principal, + // syncDisbursementWithMeeting: false, + // expectedDisbursementDate: this.dateUtils.formatDate(this.loansAccount.expectedDisbursementDate, dateFormat), + // submittedOnDate: this.dateUtils.formatDate(this.loansAccount.submittedOnDate, dateFormat), + // dateFormat, + // // monthDayFormat, + // locale + // }; + // data.groupId = this.loansAccountTemplate.group.id; - return JSON.stringify(data); - } - - /** Request Body Data */ - buildRequestData(): any[] { - const requestData = []; - const memberSelected = this.selectedMembers.selectedMembers; - this.totalLoanAmount(); - for (let index = 0; index < memberSelected.length; index++) { - requestData.push({ - requestId: index.toString(), - method: 'POST', - relativeUrl: 'loans', - body: this.setData(memberSelected[index]) - }); - } - return requestData; - } - - totalLoanAmount(): any { - let total = 0; - const memberSelected = this.selectedMembers.selectedMembers; - for (let index = 0; index < memberSelected.length; index++) { - total += memberSelected[index].principal; - } - this.totalLoan = total; - } + // return JSON.stringify(data); + // } /** - * Creates a new GSIM account. + * Creates a new GLIM account. */ submit() { - const data = this.buildRequestData(); - this.loansService.createGlimAccount(data).subscribe((response: any) => { - this.router.navigate(['../../../'], { relativeTo: this.route }); + const locale = this.settingsService.language.code; + const dateFormat = this.settingsService.dateFormat; + + const totalLoan = this.loanPrincipal; + const payloadData: any[] = []; + this.loansAccountChargesStep.selectedClientMembers.selectedMembers.forEach((member: any, index: number) => { + this.loansAccountTemplate.clientId = member.id; + this.loansAccountTemplate.loanPurposeId = member.loanPurposeId; + this.loansAccountTemplate.principal = member.principal; + this.loansAccountTemplate.totalLoan = totalLoan; + + const payload = this.loansService.buildLoanRequestPayload( + this.loansAccount, + this.loansAccountTemplate, + this.loansAccountProductTemplate.calendarOptions, + locale, + dateFormat + ); + + payloadData.push(this.getWrapper(payload, index)); + }); + + this.loansService.createGlimAccount(payloadData).subscribe((response: any) => { + this.router.navigate( + [ + '../', + JSON.parse(response[0].body).glimId + ], + { relativeTo: this.route } + ); }); } + + getWrapper(payload: any, requestId: number): any { + return { + body: JSON.stringify(payload), + method: 'POST', + relativeUrl: 'loans', + requestId: requestId + }; + } } diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.html b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.html index 8bdc35a5d..d411cac88 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.html +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.html @@ -91,21 +91,6 @@ - - {{ 'labels.inputs.Repayments Every' | translate }} - - {{ charge.feeInterval || 'Not Provided' }} - - - - {{ 'labels.inputs.Actions' | translate }} @@ -115,8 +100,35 @@ - - + + + + +

{{ 'labels.heading.Overdue Charges' | translate }}

+ + + + + + + + + + + + + + + + + + + + + + + +
{{ 'labels.inputs.name' | translate }}{{ charge.name }},{{ charge.currency.displaySymbol }}{{ 'labels.inputs.Type' | translate }}{{ charge.chargeCalculationType.value }}{{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType.value }}
@@ -179,8 +191,11 @@ {{ 'labels.buttons.Previous' | translate }} - +
diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.scss b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.scss index e69de29bb..b66af2377 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.scss +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.scss @@ -0,0 +1,3 @@ +.margin-t { + margin-top: 2em; +} diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.ts b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.ts index eba57fe25..f258ec13b 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.ts +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-charges-step/glim-charges-step.component.ts @@ -1,13 +1,23 @@ +/** Angular Imports */ import { Component, Input, OnInit, OnChanges } from '@angular/core'; -import { FormControl } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { MatTableDataSource } from '@angular/material/table'; -import { Dates } from 'app/core/utils/dates'; + +/** Dialog Components */ +import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; + +/** Custom Services */ import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base'; import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base'; import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base'; +import { SettingsService } from 'app/settings/settings.service'; +import { Dates } from 'app/core/utils/dates'; +import { ActivatedRoute } from '@angular/router'; +/** + * GLIM Account Charges Step + */ @Component({ selector: 'mifosx-glim-charges-step', templateUrl: './glim-charges-step.component.html', @@ -18,29 +28,34 @@ export class GlimChargesStepComponent implements OnInit, OnChanges { @Input() loansAccountProductTemplate: any; /** Savings Account Template */ @Input() loansAccountTemplate: any; - /** Currency Code */ - // @Input() currencyCode: FormControl; - // /** active Client Members in case of GLIM Account */ + // @Input() loansAccountFormValid: LoansAccountFormValid + @Input() loansAccountFormValid: boolean; + // active Client Members in case of GLIM Account @Input() activeClientMembers?: any; /** Charge Data */ chargeData: any = []; /** Charges Data Source */ chargesDataSource: {}[] = []; - /** Component is pristine if there has been no changes by user interaction */ - pristine = true; - /** For Edit Savings Account Form */ - isChargesPatched = false; + /** Overdue Charges Data Source */ + overDueChargesDataSource: {}[] = []; /** Display columns for charges table */ - displayedColumns: string[] = [ + chargesDisplayedColumns: string[] = [ 'name', 'chargeCalculationType', 'amount', 'chargeTimeType', 'date', - 'repaymentsEvery', 'action' ]; + /** Columns to be displayed in overdue charges table. */ + overdueChargesDisplayedColumns: string[] = [ + 'name', + 'type', + 'amount', + 'collectedon' + ]; + /** Table Data Source */ dataSource: any; /** Check for select all the Clients List */ @@ -55,22 +70,53 @@ export class GlimChargesStepComponent implements OnInit, OnChanges { 'purpose', 'amount' ]; + /** Component is pristine if there has been no changes by user interaction */ + pristine = true; + loanId: any = null; /** - * @param {MatDialog} dialog Mat Dialog + * GLIM Account Charges Form Step + * @param {dialog} MatDialog Mat Dialog + * @param {Dates} dateUtils Date Utils + * @param {ActivatedRoute} route Activated Route + * @param {SettingsService} settingsService Settings Service */ constructor( public dialog: MatDialog, - private dateUtils: Dates - ) {} + private dateUtils: Dates, + private route: ActivatedRoute, + private settingsService: SettingsService + ) { + this.loanId = this.route.snapshot.params['loanId']; + } ngOnInit() { + // TODO: This might still need adjustments due to GLIM this.dataSource = new MatTableDataSource(this.activeClientMembers); + + // Loan Account code: + if (this.loansAccountTemplate.charges) { + this.chargesDataSource = + this.loansAccountTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || []; + } } ngOnChanges() { if (this.loansAccountProductTemplate) { + this.loanPurposeOptions = this.loansAccountProductTemplate.loanPurposeOptions; + this.chargeData = this.loansAccountProductTemplate.chargeOptions; + if (this.loansAccountProductTemplate.overdueCharges) { + this.overDueChargesDataSource = this.loansAccountProductTemplate.overdueCharges; + } + if ( + this.loansAccountProductTemplate.charges && + this.loansAccountProductTemplate.charges.length > 0 && + this.chargesDataSource.length === 0 + ) { + this.chargesDataSource = + this.loansAccountProductTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || []; + } } } @@ -139,7 +185,7 @@ export class GlimChargesStepComponent implements OnInit, OnChanges { editNoteDialogRef.afterClosed().subscribe((response: any) => { if (response.data) { let newCharge: any; - const dateFormat = 'dd MMMM yyyy'; + const dateFormat = this.settingsService.dateFormat; const date = this.dateUtils.formatDate(response.data.value.date, dateFormat); switch (charge.chargeTimeType.value) { case 'Specified due date': @@ -193,9 +239,16 @@ export class GlimChargesStepComponent implements OnInit, OnChanges { * @param {any} charge Charge */ deleteCharge(charge: any) { - this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1); - this.chargesDataSource = this.chargesDataSource.concat([]); - this.pristine = false; + const deleteChargeDialogRef = this.dialog.open(DeleteDialogComponent, { + data: { deleteContext: `charge ${charge.name}` } + }); + deleteChargeDialogRef.afterClosed().subscribe((response: any) => { + if (response.delete) { + this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1); + this.chargesDataSource = this.chargesDataSource.concat([]); + this.pristine = false; + } + }); } /** @@ -209,14 +262,19 @@ export class GlimChargesStepComponent implements OnInit, OnChanges { return { selectedMembers: this.activeClientMembers.filter((item: any) => item.selected) }; } - /** Toggle all checks */ + get isValid() { + const sum = this.activeClientMembers + ?.filter((item: any) => item.selected) + .reduce((acc: number, member: any) => acc + member.principal, 0); + return sum > 0; + } + toggleSelects() { for (const member of this.activeClientMembers) { member.selected = this.selectAllItems; } } - /** Check if all the checks are selected */ toggleSelect() { const len = this.activeClientMembers.length; this.selectAllItems = diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.html b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.html index de9eb3b9c..a4a122e08 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.html +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.html @@ -1,9 +1,16 @@
- + {{ 'labels.inputs.Product Name' | translate }} - - + + + + + {{ product.name }} @@ -12,10 +19,15 @@ {{ 'labels.commons.required' | translate }} +
+
{{ 'labels.inputs.Loan officer' | translate }} - + {{ loanOfficer.displayName }} @@ -38,6 +50,7 @@ [min]="minDate" [max]="maxDate" [matDatepicker]="submitPicker" + matTooltip="{{ 'tooltips.Date the loan account application' | translate }}" required formControlName="submittedOnDate" /> @@ -56,6 +69,7 @@ [min]="loansAccountDetailsForm.value.submittedOnDate" [max]="maxDate" [matDatepicker]="disbursementPicker" + matTooltip="{{ 'tooltips.Date that the loan account disbursed' | translate }}" required formControlName="expectedDisbursementDate" /> @@ -67,15 +81,11 @@ - - -

{{ 'labels.heading.Savings Linkage' | translate }}

- {{ 'labels.inputs.Link savings' | translate }} - {{ savingaccount.accountNumber }} + {{ savingaccount.accountNumber }} {{ savingaccount.productName }} @@ -90,5 +100,8 @@ {{ 'labels.buttons.Next' | translate }} +
diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.ts b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.ts index 39398ff59..8f4f51b16 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.ts +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-details-step/glim-details-step.component.ts @@ -1,14 +1,28 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { LoansService } from 'app/loans/loans.service'; +/** Angular Imports */ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormControl } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; import { SettingsService } from 'app/settings/settings.service'; +/** Custom Services */ +import { LoansService } from '../../../../loans.service'; +import { Commons } from 'app/core/utils/commons'; +import { takeUntil } from 'rxjs/operators'; +import { ReplaySubject, Subject } from 'rxjs'; + +// Felix: TODO's: +// - gsimData was already declared, when I found this file. Apparently, +// glim accounts can only be linked to gsim accounts. Needs to be tested +// as soon as gsim accounts are implemented. +/** + * GLIM Account Details Step + */ @Component({ selector: 'mifosx-glim-details-step', templateUrl: './glim-details-step.component.html', styleUrls: ['./glim-details-step.component.scss'] }) -export class GlimDetailsStepComponent implements OnInit { +export class GlimDetailsStepComponent implements OnInit, OnDestroy { /** Loans Account Template */ @Input() loansAccountTemplate: any; @Input() gsimData: any; @@ -17,7 +31,7 @@ export class GlimDetailsStepComponent implements OnInit { /** Maximum date allowed. */ maxDate = new Date(2100, 0, 1); /** Product Data */ - productData: any; + productList: any; /** Loan Officer Data */ loanOfficerOptions: any; /** Loan Purpose Options */ @@ -31,6 +45,16 @@ export class GlimDetailsStepComponent implements OnInit { /** Loans Account Details Form */ loansAccountDetailsForm: UntypedFormGroup; + loanId: any = null; + + loanProductSelected = false; + /** Currency data. */ + protected productData: ReplaySubject = new ReplaySubject(1); + /** control for the filter select */ + protected filterFormCtrl: UntypedFormControl = new UntypedFormControl(''); + /** Subject that emits when the component has been destroyed. */ + protected _onDestroy = new Subject(); + /** Loans Account Template with product data */ @Output() loansAccountProductTemplate = new EventEmitter(); /** @@ -42,17 +66,21 @@ export class GlimDetailsStepComponent implements OnInit { constructor( private formBuilder: UntypedFormBuilder, private loansService: LoansService, - private settingsService: SettingsService + private route: ActivatedRoute, + private settingsService: SettingsService, + private commons: Commons ) { - this.createLoansAccountDetailsForm(); + this.loanId = this.route.snapshot.params['loanId']; } ngOnInit(): void { - this.maxDate = this.settingsService.businessDate; + this.createLoansAccountDetailsForm(); + this.maxDate = this.settingsService.maxFutureDate; // Felix: originally: businessDate; this.buildDependencies(); if (this.loansAccountTemplate) { - this.productData = this.loansAccountTemplate.productOptions; + this.productList = this.loansAccountTemplate.productOptions.sort(this.commons.dynamicSort('name')); if (this.loansAccountTemplate.loanProductId) { + console.log('this.loansAccountTemplate.loanOfficerId: ', this.loansAccountTemplate.loanOfficerId); this.loansAccountDetailsForm.patchValue({ productId: this.loansAccountTemplate.loanProductId, submittedOnDate: @@ -68,6 +96,31 @@ export class GlimDetailsStepComponent implements OnInit { }); } } + this.filterFormCtrl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => { + this.searchItem(); + }); + this.productData.next(this.productList.slice()); + } + + ngOnDestroy(): void { + this._onDestroy.next(); + this._onDestroy.complete(); + } + + searchItem(): void { + if (this.productList) { + const search: string = this.filterFormCtrl.value.toLowerCase(); + + if (!search) { + this.productData.next(this.productList.slice()); + } else { + this.productData.next( + this.productList.filter((option: any) => { + return option['name'].toLowerCase().indexOf(search) >= 0; + }) + ); + } + } } /** @@ -80,6 +133,7 @@ export class GlimDetailsStepComponent implements OnInit { Validators.required ], loanOfficerId: [''], + // Felix: found: loanPurposeId: [''], - not sure, where we add this list... fundId: [''], submittedOnDate: [ new Date(), @@ -89,7 +143,9 @@ export class GlimDetailsStepComponent implements OnInit { '', Validators.required ], - linkAccountId: [''] + externalId: [''], + linkAccountId: [''], + createStandingInstructionAtDisbursement: [''] }); } @@ -105,6 +161,7 @@ export class GlimDetailsStepComponent implements OnInit { this.loanPurposeOptions = response.loanPurposeOptions; this.fundOptions = response.fundOptions; this.accountLinkingOptions = response.accountLinkingOptions; + this.loanProductSelected = true; }); }); } @@ -113,6 +170,6 @@ export class GlimDetailsStepComponent implements OnInit { * Returns loans account details form value. */ get loansAccountDetails() { - return this.loansAccountDetailsForm.value; + return this.loansAccountDetailsForm.getRawValue(); // Felix: was: value; } } diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-preview-step/glim-preview-step.component.html b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-preview-step/glim-preview-step.component.html index 98712759a..134c04b53 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-preview-step/glim-preview-step.component.html +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-preview-step/glim-preview-step.component.html @@ -1,8 +1,6 @@

{{ 'labels.heading.Details' | translate }}

- -
{{ 'labels.inputs.Product' | translate }}: {{ loansAccount.productId | find: loansAccountTemplate.productOptions : 'id' : 'name' }} @@ -50,11 +48,16 @@
{{ 'labels.inputs.Principal' | translate }}: - {{ loansAccount.principal }} {{ loansAccountProductTemplate.currency.DisplaySymbol }} + + {{ + loansAccount.principalAmount | currency: loansAccountProductTemplate.currency.code : 'symbol-narrow' : '1.2-2' + }} + {{ loansAccountProductTemplate.currency.code }} +
- {{ 'labels.inputs.Loan term' | translate }}: + {{ 'labels.inputs.Loan Term' | translate }}: {{ loansAccount.loanTermFrequency }} {{ @@ -86,6 +89,11 @@ >
+
+ {{ 'labels.inputs.Enable Down Payments' | translate }}: + {{ loansAccount.enableDownPayment | yesNo }} +
+
{{ 'labels.inputs.First repayment on' | translate }}: {{ loansAccount.repaymentsStartingFromDate | dateFormat }} @@ -137,9 +145,7 @@
{{ 'labels.inputs.Arrears tolerance' | translate }}: - {{ loansAccount.inArrearsTolerance }} {{ loansAccountProductTemplate.currency.displaySymbol }} + {{ loansAccount.inArrearsTolerance }}
@@ -151,14 +157,24 @@ {{ 'labels.inputs.Repayment strategy' | translate }}: {{ loansAccount.transactionProcessingStrategyCode - | find: loansAccountTemplate.transactionProcessingStrategyOptions : 'code' : 'name' + | find: loansAccountProductTemplate.transactionProcessingStrategyOptions : 'code' : 'name' }}
-

{{ 'labels.heading.Moratorium' | translate }}

+
+ {{ 'labels.inputs.Installment Amount' | translate }} + {{ loansAccount.fixedEmiAmount | formatNumber }} +
+ +
+ {{ 'labels.inputs.Balloon Repayment Amount' | translate }} + {{ loansAccount.balloonRepaymentAmount | formatNumber }} +
+

{{ 'labels.heading.Moratorium' | translate }}

+
{{ 'labels.inputs.On principal payment' | translate }}: {{ loansAccount.graceOnPrincipalPayment }} @@ -174,6 +190,11 @@ {{ loansAccount.graceOnArrearsAgeing }}
+
+ {{ 'labels.inputs.Enable installment level Delinquency' | translate }} + {{ loansAccount.enableInstallmentLevelDelinquency | yesNo }} +
+
{{ 'labels.inputs.Is Topup Loan' | translate }}? {{ loansAccount.isTopup }} @@ -291,7 +312,7 @@ {{ 'labels.buttons.Previous' | translate }} - + +
+
+

+ {{ 'labels.heading.Loan Tranche Details are not allowed for this Loan Product' | translate }} +

+
+ + + + + + + + + + + + + + + + + + + +
{{ 'labels.inputs.Eexpected Disbursement Date' | translate }} + {{ row.expectedDisbursementDate | dateFormat }} + {{ 'labels.inputs.Principal' | translate }} + {{ row.principal }} + {{ 'labels.inputs.Actions' | translate }} + +
+ + + +
+ {{ 'labels.inputs.Frequency for compounding' | translate }} + {{ loansAccountTermsData.interestRecalculationData.recalculationCompoundingFrequencyType.value }} + + on {{ loansAccountTermsData.interestRecalculationData.recalculationCompoundingFrequencyWeekday.value }} + + on day + {{ loansAccountTermsData.interestRecalculationData.recalculationCompoundingFrequencyOnDay }} + + on + {{ loansAccountTermsData.interestRecalculationData.recalculationCompoundingFrequencyNthDay.value }} + {{ loansAccountTermsData.interestRecalculationData.recalculationCompoundingFrequencyWeekday.value }} + + +
+
+ +
+ {{ 'labels.inputs.Frequency Interval for compounding' | translate }} + {{ + loansAccountTermsData.interestRecalculationData.recalculationCompoundingFrequencyInterval + }} +
+
+
diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.scss b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.scss index e69de29bb..b66af2377 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.scss +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.scss @@ -0,0 +1,3 @@ +.margin-t { + margin-top: 2em; +} diff --git a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.ts b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.ts index 6e5cfac16..76c19446d 100644 --- a/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.ts +++ b/src/app/loans/glim-account/create-glim-account/glim-account-stepper/glim-terms-step/glim-terms-step.component.ts @@ -1,17 +1,45 @@ +/** Angular Imports */ import { Component, Input, OnInit, OnChanges } from '@angular/core'; -import { UntypedFormGroup, UntypedFormBuilder, Validators, FormArray } from '@angular/forms'; -import { SettingsService } from 'app/settings/settings.service'; +import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormControl } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute } from '@angular/router'; +/** Custom Services */ +// import { LoansAccountAddCollateralDialogComponent } from 'app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component'; +import { LoanProducts } from 'app/products/loan-products/loan-products'; +import { LoanProduct } from 'app/products/loan-products/models/loan-product.model'; +import { SettingsService } from 'app/settings/settings.service'; +import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; +import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; +import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base'; +import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base'; +import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base'; +// import { Currency } from 'app/shared/models/general.model'; +import { CodeName, OptionData } from 'app/shared/models/option-data.model'; + +/** + * Create GLIM Terms Step + */ @Component({ selector: 'mifosx-glim-terms-step', templateUrl: './glim-terms-step.component.html', styleUrls: ['./glim-terms-step.component.scss'] }) export class GlimTermsStepComponent implements OnInit, OnChanges { + /** Loans Product Options */ + @Input() loansProductOptions: any; /** Loans Account Product Template */ @Input() loansAccountProductTemplate: any; /** Loans Account Template */ @Input() loansAccountTemplate: any; + loansAccountTermsData: any; + + /** Is Multi Disburse Loan */ + multiDisburseLoan: any; + // @Input() loansAccountFormValid: LoansAccountFormValid + @Input() loansAccountFormValid: boolean; + // @Input loanPrincipal: Loan Principle + @Input() loanPrincipal: any; /** Minimum date allowed. */ minDate = new Date(2000, 0, 1); @@ -31,86 +59,292 @@ export class GlimTermsStepComponent implements OnInit, OnChanges { amortizationTypeData: any; /** Interest Calculation Period Type Data */ interestCalculationPeriodTypeData: any; - /** Transaction Processing Strategy Data */ - transactionProcessingStrategyData: any; /** Client Active Loan Data */ clientActiveLoanData: any; + /** Multi Disbursement Data */ + disbursementDataSource: {}[] = []; + /** Loan repayment strategies */ + transactionProcessingStrategyOptions: any = []; + repaymentStrategyDisabled = false; + /** Disbursement Data Displayed Columns */ + disbursementDisplayedColumns: string[] = [ + 'expectedDisbursementDate', + 'principal', + 'actions' + ]; + /** Multi Disbursement Control */ + totalMultiDisbursed: any = 0; + isMultiDisbursedCompleted = false; + + /** Component is pristine if there has been no changes by user interaction */ + pristine = true; + + loanId: any = null; + loanScheduleType: OptionData | null = null; + loanProduct: LoanProduct | null = null; + interestRateFrequencyTypeData: any[] = []; + // currency: Currency; + + productEnableDownPayment = false; + isProgressive = false; /** * Create Loans Account Terms Form * @param formBuilder FormBuilder * @param {SettingsService} settingsService SettingsService + * @param {ActivatedRoute} route ActivatedRoute + * @param {MatDialog} dialog MatDialog */ constructor( private formBuilder: UntypedFormBuilder, - private settingsService: SettingsService + private settingsService: SettingsService, + private route: ActivatedRoute, + public dialog: MatDialog ) { - this.createloansAccountTermsForm(); + this.loanId = this.route.snapshot.params['loanId']; + this.createGLIMTermsForm(); } /** * Executes on change of input values */ ngOnChanges() { if (this.loansAccountProductTemplate) { + // this.currency = this.loansAccountProductTemplate.currency; + + this.loansAccountTermsData = this.loansAccountProductTemplate; + if (this.loanId != null && this.loansAccountTemplate.accountNo) { + this.loansAccountTermsData = this.loansAccountTemplate; + } + this.productEnableDownPayment = this.loansAccountTermsData.product.enableDownPayment; + this.isProgressive = + this.loansAccountTermsData.loanScheduleType.code == LoanProducts.LOAN_SCHEDULE_TYPE_PROGRESSIVE; + if (this.loansAccountTermsData.product) { + this.loanProduct = this.loansAccountTermsData.product; + } + + this.interestRateFrequencyTypeData = this.loansAccountTermsData.interestRateFrequencyTypeOptions; + this.loansAccountTermsForm.patchValue({ - principal: this.loansAccountProductTemplate.principal, - loanTermFrequency: this.loansAccountProductTemplate.termFrequency, - loanTermFrequencyType: this.loansAccountProductTemplate.termPeriodFrequencyType.id, - numberOfRepayments: this.loansAccountProductTemplate.numberOfRepayments, - repaymentEvery: this.loansAccountProductTemplate.repaymentEvery, - repaymentFrequencyType: this.loansAccountProductTemplate.repaymentFrequencyType.id, - interestRatePerPeriod: this.loansAccountProductTemplate.interestRatePerPeriod, - interestType: this.loansAccountProductTemplate.interestType.id, - interestCalculationPeriodType: this.loansAccountProductTemplate.interestCalculationPeriodType.id, - transactionProcessingStrategyCode: this.loansAccountProductTemplate.transactionProcessingStrategyCode + principalAmount: this.loansAccountTermsData.principal, + loanTermFrequency: this.loansAccountTermsData.termFrequency, + loanTermFrequencyType: this.loansAccountTermsData.termPeriodFrequencyType.id, + numberOfRepayments: this.loansAccountTermsData.numberOfRepayments, + repaymentEvery: this.loansAccountTermsData.repaymentEvery, + repaymentFrequencyType: this.loansAccountTermsData.repaymentFrequencyType.id, + amortizationType: this.loansAccountTermsData.amortizationType.id, + isEqualAmortization: this.loansAccountTermsData.isEqualAmortization, + interestType: this.loansAccountTermsData.interestType.id, + isFloatingInterestRate: this.loansAccountTermsData.isLoanProductLinkedToFloatingRate ? false : '', + interestCalculationPeriodType: this.loansAccountTermsData.interestCalculationPeriodType.id, + allowPartialPeriodInterestCalculation: this.loansAccountTermsData.allowPartialPeriodInterestCalculation, + inArrearsTolerance: this.loansAccountTermsData.inArrearsTolerance, + graceOnInterestCharged: this.loansAccountTermsData.graceOnInterestCharged, + fixedEmiAmount: this.loansAccountTermsData.fixedEmiAmount, + maxOutstandingLoanBalance: this.loansAccountTermsData.maxOutstandingLoanBalance, + transactionProcessingStrategyCode: this.loansAccountTermsData.transactionProcessingStrategyCode, + interestRateDifferential: this.loansAccountTermsData.interestRateDifferential, + multiDisburseLoan: this.loansAccountTermsData.multiDisburseLoan, + interestRateFrequencyType: this.loansAccountTermsData.interestRateFrequencyType.id, + balloonRepaymentAmount: this.loansAccountTermsData.balloonRepaymentAmount, + interestRecognitionOnDisbursementDate: this.loansAccountTermsData.interestRecognitionOnDisbursementDate || false }); + + this.setAdvancedPaymentStrategyControls(); + + if (this.loansAccountTermsData.loanScheduleType.code == LoanProducts.LOAN_SCHEDULE_TYPE_CUMULATIVE) { + this.loansAccountTermsForm.removeControl('interestRecognitionOnDisbursementDate'); + } + + if (this.loansAccountTermsData.isLoanProductLinkedToFloatingRate) { + this.loansAccountTermsForm.removeControl('interestRatePerPeriod'); + } + + this.multiDisburseLoan = this.loansAccountTermsData.multiDisburseLoan; + if (this.loansAccountTermsData.disbursementDetails) { + this.disbursementDataSource = this.loansAccountTermsData.disbursementDetails; + this.totalMultiDisbursed = 0; + this.disbursementDataSource.forEach((item: any) => { + this.totalMultiDisbursed += item.principal; + }); + } + if (this.isDelinquencyEnabled()) { + this.loansAccountTermsForm.addControl( + 'enableInstallmentLevelDelinquency', + new UntypedFormControl( + this.loansAccountTermsData.enableInstallmentLevelDelinquency || + this.loanProduct.enableInstallmentLevelDelinquency + ) + ); + } + if (this.productEnableDownPayment) { + const enableDownPayment = this.loansAccountTermsData['enableDownPayment'] === false ? false : true; + this.loansAccountTermsForm.addControl('enableDownPayment', new UntypedFormControl(enableDownPayment)); + } + + const allowAttributeOverrides = this.loansAccountTermsData.product.allowAttributeOverrides; + if (!allowAttributeOverrides.repaymentEvery) { + this.loansAccountTermsForm.controls.repaymentEvery.disable(); + this.loansAccountTermsForm.controls.repaymentFrequencyType.disable(); + } + if (!allowAttributeOverrides.interestType) { + this.loansAccountTermsForm.controls.interestType.disable(); + } + if (!allowAttributeOverrides.amortizationType) { + this.loansAccountTermsForm.controls.amortizationType.disable(); + } + if (!allowAttributeOverrides.interestCalculationPeriodType) { + this.loansAccountTermsForm.controls.interestCalculationPeriodType.disable(); + this.loansAccountTermsForm.controls.allowPartialPeriodInterestCalculation.disable(); + } + if (!allowAttributeOverrides.inArrearsTolerance) { + this.loansAccountTermsForm.controls.inArrearsTolerance.disable(); + } + if (!allowAttributeOverrides.transactionProcessingStrategyCode) { + this.loansAccountTermsForm.controls.transactionProcessingStrategyCode.disable(); + } this.setOptions(); } } ngOnInit() { - this.maxDate = this.settingsService.businessDate; - if (this.loansAccountTemplate) { - if (this.loansAccountTemplate.loanProductId) { + this.maxDate = this.settingsService.maxFutureDate; + this.loansAccountTermsData = this.loansAccountProductTemplate; + if (this.loanId != null && this.loansAccountTemplate.accountNo) { + this.loansAccountTermsData = this.loansAccountTemplate; + } + + if (this.loansAccountTermsData) { + if (this.loansAccountTermsData.loanProductId) { this.loansAccountTermsForm.patchValue({ repaymentsStartingFromDate: - this.loansAccountTemplate.expectedFirstRepaymentOnDate && - new Date(this.loansAccountTemplate.expectedFirstRepaymentOnDate) + this.loansAccountTermsData.expectedFirstRepaymentOnDate && + new Date(this.loansAccountTermsData.expectedFirstRepaymentOnDate) }); } + this.loansAccountTermsForm.patchValue({ + principalAmount: this.loansAccountTermsData.principal, + loanTermFrequency: this.loansAccountTermsData.termFrequency, + loanTermFrequencyType: this.loansAccountTermsData.termPeriodFrequencyType.id, + numberOfRepayments: this.loansAccountTermsData.numberOfRepayments, + repaymentEvery: this.loansAccountTermsData.repaymentEvery, + repaymentFrequencyType: this.loansAccountTermsData.repaymentFrequencyType.id, + amortizationType: this.loansAccountTermsData.amortizationType.id, + isEqualAmortization: this.loansAccountTermsData.isEqualAmortization, + interestType: this.loansAccountTermsData.interestType.id, + isFloatingInterestRate: this.loansAccountTermsData.isLoanProductLinkedToFloatingRate ? false : '', + interestCalculationPeriodType: this.loansAccountTermsData.interestCalculationPeriodType.id, + allowPartialPeriodInterestCalculation: this.loansAccountTermsData.allowPartialPeriodInterestCalculation, + inArrearsTolerance: this.loansAccountTermsData.inArrearsTolerance, + graceOnInterestCharged: this.loansAccountTermsData.graceOnInterestCharged, + fixedEmiAmount: this.loansAccountTermsData.fixedEmiAmount, + maxOutstandingLoanBalance: this.loansAccountTermsData.maxOutstandingLoanBalance, + transactionProcessingStrategyCode: this.loansAccountTermsData.transactionProcessingStrategyCode, + interestRateDifferential: this.loansAccountTermsData.interestRateDifferential, + multiDisburseLoan: this.loansAccountTermsData.multiDisburseLoan, + interestRateFrequencyType: this.loansAccountTermsData.interestRateFrequencyType.id, + balloonRepaymentAmount: this.loansAccountTermsData.balloonRepaymentAmount, + interestRecognitionOnDisbursementDate: this.loansAccountTermsData.interestRecognitionOnDisbursementDate || false + }); } - this.createloansAccountTermsForm(); - this.setCustomValidators(); + this.createGLIMTermsForm(); + this.setAdvancedPaymentStrategyControls(); + // this.setCustomValidators(); + this.setLoanTermListener(); + } + + allowAddDisbursementDetails() { + return this.multiDisburseLoan && !this.loansAccountTermsData.disallowExpectedDisbursements; } /** Custom Validators for the form */ - setCustomValidators() { - const repaymentFrequencyNthDayType = this.loansAccountTermsForm.get('repaymentFrequencyNthDayType'); - const repaymentFrequencyDayOfWeekType = this.loansAccountTermsForm.get('repaymentFrequencyDayOfWeekType'); + // setCustomValidators() { + // const repaymentFrequencyNthDayType = this.loansAccountTermsForm.get('repaymentFrequencyNthDayType'); + // const repaymentFrequencyDayOfWeekType = this.loansAccountTermsForm.get('repaymentFrequencyDayOfWeekType'); - this.loansAccountTermsForm.get('repaymentFrequencyType').valueChanges.subscribe((repaymentFrequencyType) => { - if (repaymentFrequencyType === 2) { - repaymentFrequencyNthDayType.setValidators([Validators.required]); - repaymentFrequencyDayOfWeekType.setValidators([Validators.required]); + // this.loansAccountTermsForm.get('repaymentFrequencyType').valueChanges.subscribe((repaymentFrequencyType) => { + // if (repaymentFrequencyType === 2) { + // repaymentFrequencyNthDayType.setValidators([Validators.required]); + // repaymentFrequencyDayOfWeekType.setValidators([Validators.required]); + // } else { + // repaymentFrequencyNthDayType.setValidators(null); + // repaymentFrequencyDayOfWeekType.setValidators(null); + // } + + // repaymentFrequencyNthDayType.updateValueAndValidity(); + // repaymentFrequencyDayOfWeekType.updateValueAndValidity(); + // }); + // } + + /** Custom Listeners for the form to calculate Loan Term */ + setLoanTermListener() { + this.loansAccountTermsForm.get('numberOfRepayments').valueChanges.subscribe((numberOfRepayments) => { + const repaymentEvery: number = this.loansAccountTermsForm.value.repaymentEvery; + this.calculateLoanTerm(numberOfRepayments, repaymentEvery); + }); + + this.loansAccountTermsForm.get('repaymentEvery').valueChanges.subscribe((repaymentEvery) => { + const numberOfRepayments: number = this.loansAccountTermsForm.value.numberOfRepayments; + this.calculateLoanTerm(numberOfRepayments, repaymentEvery); + }); + + this.loansAccountTermsForm.get('loanTermFrequencyType').valueChanges.subscribe((loanTermFrequencyType) => { + this.loansAccountTermsForm.patchValue({ repaymentFrequencyType: loanTermFrequencyType }); + }); + + this.loansAccountTermsForm.get('amortizationType').valueChanges.subscribe((amortizationType) => { + if (amortizationType === 0) { + // Equal Principal Payments + this.loansAccountTermsForm.addControl('fixedPrincipalPercentagePerInstallment', new UntypedFormControl('')); } else { - repaymentFrequencyNthDayType.setValidators(null); - repaymentFrequencyDayOfWeekType.setValidators(null); + // Equal Installments + this.loansAccountTermsForm.removeControl('fixedPrincipalPercentagePerInstallment'); } - - repaymentFrequencyNthDayType.updateValueAndValidity(); - repaymentFrequencyDayOfWeekType.updateValueAndValidity(); }); } + setAdvancedPaymentStrategyControls(): void { + // Fixed Length validation + if (this.loansAccountTermsData) { + this.loansAccountTermsForm.removeControl('interestRatePerPeriod'); + this.loansAccountTermsForm.removeControl('fixedLength'); + if (this.loansAccountTermsData.product.fixedLength) { + this.loansAccountTermsForm.addControl( + 'interestRatePerPeriod', + new UntypedFormControl({ value: 0, disabled: true }, Validators.required) + ); + this.loansAccountTermsForm.addControl( + 'fixedLength', + new UntypedFormControl(this.loansAccountTermsData.product.fixedLength) + ); + } else { + this.loansAccountTermsForm.addControl( + 'interestRatePerPeriod', + new UntypedFormControl(this.loansAccountTermsData.interestRatePerPeriod, Validators.required) + ); + } + } + } + + hasFixedLength(): boolean { + if (this.loansAccountTermsData) { + return this.loansAccountTermsData.product?.fixedLength ? true : false; + } + return false; + } + + isEqualPrincipalPayments(): boolean { + return this.loansAccountTermsForm.value.amortizationType === 0; + } + /** Create Loans Account Terms Form */ - createloansAccountTermsForm() { + createGLIMTermsForm() { this.loansAccountTermsForm = this.formBuilder.group({ - principal: [ + principalAmount: [ '', Validators.required ], loanTermFrequency: [ - '', + { value: '', disabled: true }, Validators.required ], loanTermFrequencyType: [ @@ -126,7 +360,7 @@ export class GlimTermsStepComponent implements OnInit, OnChanges { Validators.required ], repaymentFrequencyType: [ - '', + { value: '', disabled: true }, Validators.required ], repaymentFrequencyNthDayType: [''], @@ -135,8 +369,109 @@ export class GlimTermsStepComponent implements OnInit, OnChanges { interestChargedFromDate: [''], interestRatePerPeriod: [''], interestType: [''], + isFloatingInterestRate: [''], + isEqualAmortization: [''], + amortizationType: [ + '', + Validators.required + ], interestCalculationPeriodType: [''], - transactionProcessingStrategyCode: [''] + allowPartialPeriodInterestCalculation: [''], + inArrearsTolerance: [''], + graceOnInterestCharged: [''], + loanIdToClose: [''], + fixedEmiAmount: [''], + isTopup: [''], + maxOutstandingLoanBalance: [''], + interestRateDifferential: [''], + transactionProcessingStrategyCode: [ + '', + Validators.required + ], + multiDisburseLoan: [false], + interestRateFrequencyType: [''], + balloonRepaymentAmount: [''], + interestRecognitionOnDisbursementDate: [false] + }); + } + + calculateLoanTerm(numberOfRepayments: number, repaymentEvery: number): void { + const loanTerm = numberOfRepayments * repaymentEvery; + this.loansAccountTermsForm.patchValue({ loanTermFrequency: loanTerm }); + } + + /** + * Gets the Disbursement Data array. + * @returns {Array} Disbursement Data array. + */ + get disbursementData() { + return { + disbursementData: this.disbursementDataSource + }; + } + + /** + * Adds the Disbursement Data entry form to given Disbursement Data entry. + */ + addDisbursementDataEntry() { + const currentPrincipalAmount = this.loansAccountTermsForm.get('principalAmount').value; + const formfields: FormfieldBase[] = [ + new DatepickerBase({ + controlName: 'expectedDisbursementDate', + label: 'Expected Disbursement Date', + value: new Date() || '', + type: 'datetime-local', + minDate: this.minDate, + maxDate: this.maxDate, + required: true, + order: 1 + }), + new InputBase({ + controlName: 'principal', + label: `Principal(It should be less than equal to the ${currentPrincipalAmount})`, + value: '', + type: 'number', + required: true, + order: 2 + }) + + ]; + const data = { + title: 'Add Disbursement Details', + layout: { addButtonText: 'Add' }, + formfields: formfields + }; + const disbursementDialogRef = this.dialog.open(FormDialogComponent, { data }); + disbursementDialogRef.afterClosed().subscribe((response: any) => { + if (response.data) { + const principal = response.data.value.principal * 1; + if (this.totalMultiDisbursed + principal <= currentPrincipalAmount) { + this.disbursementDataSource = this.disbursementDataSource.concat(response.data.value); + this.totalMultiDisbursed += principal; + this.isMultiDisbursedCompleted = this.totalMultiDisbursed === currentPrincipalAmount; + this.pristine = false; + } + } + }); + } + + /** + * Removes the Disbursement Data entry form from given Disbursement Data entry form array at given index. + * @param {number} index Array index from where Disbursement Data entry form needs to be removed. + */ + removeDisbursementDataEntry(index: number) { + const currentPrincipalAmount = this.loansAccountTermsForm.get('principalAmount').value; + const dialogRef = this.dialog.open(DeleteDialogComponent, { + data: { deleteContext: `this` } + }); + dialogRef.afterClosed().subscribe((response: any) => { + if (response.delete) { + const principal = this.disbursementDataSource[index]['principal'] * 1; + this.disbursementDataSource.splice(index, 1); + this.disbursementDataSource = this.disbursementDataSource.concat([]); + this.totalMultiDisbursed -= principal; + this.isMultiDisbursedCompleted = this.totalMultiDisbursed === currentPrincipalAmount; + } }); } @@ -151,14 +486,35 @@ export class GlimTermsStepComponent implements OnInit, OnChanges { this.interestTypeData = this.loansAccountProductTemplate.interestTypeOptions; this.amortizationTypeData = this.loansAccountProductTemplate.amortizationTypeOptions; this.interestCalculationPeriodTypeData = this.loansAccountProductTemplate.interestCalculationPeriodTypeOptions; - this.transactionProcessingStrategyData = this.loansAccountProductTemplate.transactionProcessingStrategyOptions; this.clientActiveLoanData = this.loansAccountProductTemplate.clientActiveLoanOptions; + this.loanScheduleType = this.loansAccountProductTemplate.loanScheduleType; + this.transactionProcessingStrategyOptions = []; + if (this.loanScheduleType.code === LoanProducts.LOAN_SCHEDULE_TYPE_CUMULATIVE) { + // Filter Advanced Payment Allocation Strategy + this.transactionProcessingStrategyOptions = + this.loansAccountProductTemplate.transactionProcessingStrategyOptions.filter( + (cn: CodeName) => !LoanProducts.isAdvancedPaymentAllocationStrategy(cn.code) + ); + this.repaymentStrategyDisabled = false; + } else { + // Only Advanced Payment Allocation Strategy + this.loansAccountProductTemplate.transactionProcessingStrategyOptions.some((cn: CodeName) => { + if (LoanProducts.isAdvancedPaymentAllocationStrategy(cn.code)) { + this.transactionProcessingStrategyOptions.push(cn); + } + }); + this.repaymentStrategyDisabled = true; + } + } + + isDelinquencyEnabled(): boolean { + return !!this.loanProduct?.delinquencyBucket?.name; } /** * Returns loans account terms form value. */ get loansAccountTerms() { - return this.loansAccountTermsForm.value; + return this.loansAccountTermsForm.getRawValue(); } } diff --git a/src/app/loans/loans.service.ts b/src/app/loans/loans.service.ts index 63394e17b..93c0c2ee4 100644 --- a/src/app/loans/loans.service.ts +++ b/src/app/loans/loans.service.ts @@ -563,13 +563,15 @@ export class LoansService { getGLIMLoanAccountTemplate(groupId: any): Observable { const httpParams = new HttpParams() .set('groupId', groupId) - .set('lendingStrategy', '300') + // Commenting parameter, because it doesn't exist: + // https://localhost:8443/fineract-provider/swagger-ui/index.html#/Loans/template_10 + // .set('lendingStrategy', '300') .set('templateType', 'jlgbulk'); return this.http.get('/loans/template', { params: httpParams }); } - createGlimAccount(glimAccount: any): Observable { - return this.http.post('/batches?enclosingTransaction=true', glimAccount); + createGlimAccount(payload: any): Observable { + return this.http.post('/batches?enclosingTransaction=true', payload); } calculateLoanSchedule(payload: any): Observable { @@ -598,10 +600,6 @@ export class LoansService { amount: charge.amount, dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat) })), - collateral: loansAccount.collateral.map((collateralEle: any) => ({ - clientCollateralId: collateralEle.type.collateralId, - quantity: collateralEle.value - })), disbursementData: loansAccount.disbursementData.map((item: any) => ({ expectedDisbursementDate: this.dateUtils.formatDate(item.expectedDisbursementDate, dateFormat), principal: item.principal @@ -613,7 +611,19 @@ export class LoansService { dateFormat, locale }; - if (loansAccountTemplate.clientId) { + + if (loansAccount.collateral) { + loansAccountData.collateral = loansAccount.collateral.map((collateralEle: any) => ({ + clientCollateralId: collateralEle.type.collateralId, + quantity: collateralEle.value + })); + } + + if (loansAccountTemplate.clientId && loansAccountTemplate.group.id) { + loansAccountData.clientId = loansAccountTemplate.clientId; + loansAccountData.groupId = loansAccountTemplate.group.id; + loansAccountData.loanType = 'glim'; + } else if (loansAccountTemplate.clientId) { loansAccountData.clientId = loansAccountTemplate.clientId; loansAccountData.loanType = 'individual'; } else { @@ -651,7 +661,6 @@ export class LoansService { // allowPartialPeriodInterestCalculation. Until that is fixed, we need to replace the field name in the payload. loansAccountData.allowPartialPeriodInterestCalcualtion = loansAccountData.allowPartialPeriodInterestCalculation; delete loansAccountData.allowPartialPeriodInterestCalculation; - return loansAccountData; } }