Merge pull request #1582 from josehernandezfintecheandomx/enhancement/loan_account_request_repayment_schedule_part1

Add Repayment Info as part of the Loan Account request
This commit is contained in:
Adam Saghy
2022-11-25 20:17:20 +01:00
committed by GitHub
16 changed files with 227 additions and 80 deletions
@@ -53,6 +53,16 @@
</mat-step>
<mat-step state="repayment" *ngIf="loansAccountFormValid" completed>
<ng-template matStepLabel>REPAYMENT SCHEDULE</ng-template>
<mifosx-loans-account-schedule-step [loansAccountTemplate]="loansAccountTemplate"
[loansAccountProductTemplate]="loansAccountProductTemplate" [loansAccount]="loansAccount">
</mifosx-loans-account-schedule-step>
</mat-step>
<mat-step state="preview" *ngIf="loansAccountFormValid" completed>
<ng-template matStepLabel>PREVIEW</ng-template>
@@ -121,59 +121,10 @@ export class CreateLoansAccountComponent implements OnInit {
submit() {
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const loansAccountData = {
...this.loansAccount,
charges: this.loansAccount.charges.map((charge: any) => ({
chargeId: charge.id,
amount: charge.amount,
dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat),
})),
collateral: this.loansAccount.collateral.map((collateralEle: any) => ({
clientCollateralId: collateralEle.type.collateralId,
quantity: collateralEle.value,
})),
disbursementData: this.loansAccount.disbursementData.map((item: any) => ({
expectedDisbursementDate: this.dateUtils.formatDate(item.expectedDisbursementDate, dateFormat),
principal: item.principal
})),
interestChargedFromDate: this.dateUtils.formatDate(this.loansAccount.interestChargedFromDate, dateFormat),
repaymentsStartingFromDate: this.dateUtils.formatDate(this.loansAccount.repaymentsStartingFromDate, dateFormat),
submittedOnDate: this.dateUtils.formatDate(this.loansAccount.submittedOnDate, dateFormat),
expectedDisbursementDate: this.dateUtils.formatDate(this.loansAccount.expectedDisbursementDate, dateFormat),
dateFormat,
locale,
};
if (this.loansAccountTemplate.clientId) {
loansAccountData.clientId = this.loansAccountTemplate.clientId;
loansAccountData.loanType = 'individual';
} else {
loansAccountData.groupId = this.loansAccountTemplate.group.id;
loansAccountData.loanType = 'group';
}
const payload = this.loansService.buildLoanRequestPayload(this.loansAccount, this.loansAccountTemplate,
this.loansAccountProductTemplate.calendarOptions, locale, dateFormat);
if (loansAccountData.syncRepaymentsWithMeeting) {
loansAccountData.calendarId = this.loansAccountProductTemplate.calendarOptions[0].id;
delete loansAccountData.syncRepaymentsWithMeeting;
}
if (loansAccountData.recalculationRestFrequencyDate) {
loansAccountData.recalculationRestFrequencyDate = this.dateUtils.formatDate(this.loansAccount.recalculationRestFrequencyDate, dateFormat);
}
if (loansAccountData.interestCalculationPeriodType === 0) {
loansAccountData.allowPartialPeriodInterestCalcualtion = false;
}
if (!(loansAccountData.isFloatingInterestRate === false)) {
delete loansAccountData.isFloatingInterestRate;
}
if (!(this.multiDisburseLoan)) {
delete loansAccountData.disbursementData;
}
delete loansAccountData.isValid;
loansAccountData.principal = loansAccountData.principalAmount;
delete loansAccountData.principalAmount;
this.loansService.createLoansAccount(loansAccountData).subscribe((response: any) => {
this.loansService.createLoansAccount(payload).subscribe((response: any) => {
this.router.navigate(['../', response.resourceId, 'general'], { relativeTo: this.route });
});
}
@@ -52,6 +52,16 @@
</mat-step>
<mat-step>
<ng-template matStepLabel>REPAYMENT SCHEDULE</ng-template>
<mifosx-loans-account-schedule-step [loansAccountTemplate]="loansAccountAndTemplate"
[loansAccountProductTemplate]="loansAccountProductTemplate" [loansAccount]="loansAccount">
</mifosx-loans-account-schedule-step>
</mat-step>
<mat-step state="preview" *ngIf="loansAccountFormValidAndNotPristine" completed>
<ng-template matStepLabel>PREVIEW</ng-template>
@@ -60,9 +60,11 @@ export class EditLoansAccountComponent implements OnInit {
*/
setTemplate($event: any) {
this.loansAccountProductTemplate = $event;
this.loansService.getLoansCollateralTemplateResource(this.loansAccountProductTemplate.loanProductId).subscribe((response: any) => {
this.collateralOptions = response.loanCollateralOptions;
});
if (this.loansAccountProductTemplate.loanProductId) {
this.loansService.getLoansCollateralTemplateResource(this.loansAccountProductTemplate.loanProductId).subscribe((response: any) => {
this.collateralOptions = response.loanCollateralOptions;
});
}
}
/** Get Loans Account Details Form Data */
@@ -14,7 +14,7 @@
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-form-field fxFlex="48%" *ngIf="loanProductSelected">
<mat-label>Loan officer</mat-label>
<mat-select formControlName="loanOfficerId">
<mat-option *ngFor="let loanOfficer of loanOfficerOptions" [value]="loanOfficer.id">
@@ -22,6 +22,9 @@
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" *ngIf="loanProductSelected">
<mat-form-field fxFlex="48%">
<mat-label>Loan purpose</mat-label>
@@ -84,7 +87,6 @@
<mat-checkbox fxFlex="48%" formControlName="createStandingInstructionAtDisbursement">
<p>Create standing instructions at disbursement</p>
</mat-checkbox>
</div>
<div fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
@@ -41,6 +41,8 @@ export class LoansAccountDetailsStepComponent implements OnInit {
loanId: any = null;
loanProductSelected = false;
/** Loans Account Template with product data */
@Output() loansAccountProductTemplate = new EventEmitter();
/**
@@ -106,6 +108,7 @@ export class LoansAccountDetailsStepComponent implements OnInit {
this.loanPurposeOptions = response.loanPurposeOptions;
this.fundOptions = response.fundOptions;
this.accountLinkingOptions = response.accountLinkingOptions;
this.loanProductSelected = true;
if (response.createStandingInstructionAtDisbursement) {
this.loansAccountDetailsForm.get('createStandingInstructionAtDisbursement').patchValue(response.createStandingInstructionAtDisbursement);
}
@@ -0,0 +1,23 @@
<div fxLayout="row wrap" fxLayout.lt-md="column">
<a mat-button (click)="showRepaymentInfo()">
Repayment Info
</a>
<mifosx-repayment-schedule-tab fxFlex="100%" [repaymentScheduleDetails]="repaymentScheduleDetails"></mifosx-repayment-schedule-tab>
</div>
<div fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left"></fa-icon>&nbsp;&nbsp;
Previous
</button>
<button mat-raised-button matStepperNext>
Next&nbsp;&nbsp;
<fa-icon icon="arrow-right"></fa-icon>
</button>
<button mat-raised-button *ngIf="loanId" [routerLink]="['../', 'general']">
Cancel
</button>
</div>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoansAccountScheduleStepComponent } from './loans-account-schedule-step.component';
describe('LoansAccountScheduleStepComponent', () => {
let component: LoansAccountScheduleStepComponent;
let fixture: ComponentFixture<LoansAccountScheduleStepComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoansAccountScheduleStepComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoansAccountScheduleStepComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,47 @@
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { LoansService } from 'app/loans/loans.service';
import { SettingsService } from 'app/settings/settings.service';
@Component({
selector: 'mifosx-loans-account-schedule-step',
templateUrl: './loans-account-schedule-step.component.html',
styleUrls: ['./loans-account-schedule-step.component.scss']
})
export class LoansAccountScheduleStepComponent implements OnInit {
/** Loans Account Template */
@Input() loansAccountTemplate: any;
/** Loans Account Product Template */
@Input() loansAccountProductTemplate: any;
/** Loans Account Data */
@Input() loansAccount: any;
showRepayment = false;
repaymentScheduleDetails: any = {periods: []};
loanId: any = null;
constructor(private loansService: LoansService,
private settingsService: SettingsService,
private route: ActivatedRoute) {
this.loanId = this.route.snapshot.params['loanId'];
}
ngOnInit(): void { }
showRepaymentInfo(): void {
this.repaymentScheduleDetails = {periods: []};
this.showRepayment = !this.showRepayment;
if (this.showRepayment) {
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const payload = this.loansService.buildLoanRequestPayload(this.loansAccount, this.loansAccountTemplate,
this.loansAccountProductTemplate.calendarOptions, locale, dateFormat);
this.loansService.calculateLoanSchedule(payload).subscribe((response: any) => {
this.repaymentScheduleDetails = response;
});
}
}
}
@@ -163,7 +163,6 @@
</mat-select>
</mat-form-field>
<!-- <mat-checkbox *ngIf="interestCalculationPeriodType != 0" fxFlex="48%" -->
<mat-checkbox fxFlex="48%" formControlName="allowPartialPeriodInterestCalcualtion">
<p>Calculate interest for exact days in partial period</p>
</mat-checkbox>
@@ -192,19 +191,20 @@
<h4 fxFlex="98%" class="mat-h4">Moratorium</h4>
<mat-form-field fxFlex="48%">
<mat-form-field fxFlex="23%">
<mat-label>Grace on principal payment</mat-label>
<input type="number" matInput formControlName="graceOnPrincipalPayment">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-form-field fxFlex="23%">
<mat-label>Grace on interest payment</mat-label>
<input type="number" matInput formControlName="graceOnInterestPayment">
</mat-form-field>
<mat-checkbox fxFlex="23%" labelPosition="before" formControlName="graceOnArrearsAgeing" class="margin-v">
On Arreas Aging
</mat-checkbox>
<mat-form-field fxFlex="48%">
<mat-label>On arrears ageing</mat-label>
<input type="number" matInput formControlName="graceOnArrearsAgeing">
</mat-form-field>
<mat-form-field fxFlex="48%" *ngIf="loansAccountTemplate?.canDefineInstallmentAmount">
<mat-label>Installment Amount</mat-label>
@@ -360,7 +360,6 @@
</span>
</span>
</div>
</ng-container>
<div fxFlexFill
@@ -419,8 +418,8 @@
<tr mat-header-row *matHeaderRowDef="loanCollateralDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: loanCollateralDisplayedColumns;"></tr>
</table>
</div>
<div fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left"></fa-icon>&nbsp;&nbsp;
@@ -10,7 +10,6 @@ import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.componen
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 { any } from 'cypress/types/bluebird';
/**
* Create Loans Account Terms Step
@@ -34,18 +34,18 @@
<td mat-footer-cell *matFooterCellDef> </td>
</ng-container>
<ng-container matColumnDef="principalDue">
<th mat-header-cell class="r-amount" *matHeaderCellDef> Principal Due </th>
<td mat-cell class="check r-amount" *matCellDef="let ele"> {{ ele.principalDue | number }} </td>
<td mat-footer-cell class="check r-amount" *matFooterCellDef> <b> {{ repaymentScheduleDetails.totalPrincipalExpected | currency }}</b> </td>
</ng-container>
<ng-container matColumnDef="balanceOfLoan">
<th mat-header-cell class="r-amount" *matHeaderCellDef> Balance Of Loan </th>
<td mat-cell class="r-amount" *matCellDef="let ele"> {{ ele.principalLoanBalanceOutstanding | number }} </td>
<td mat-footer-cell class="r-amount" *matFooterCellDef> &nbsp; </td>
</ng-container>
<ng-container matColumnDef="principalDue">
<th mat-header-cell class="r-amount" *matHeaderCellDef> Principal Due </th>
<td mat-cell class="check r-amount" *matCellDef="let ele"> {{ ele.principalDue | number }} </td>
<td mat-footer-cell class="check r-amount" *matFooterCellDef> <b> {{ repaymentScheduleDetails.totalPrincipalExpected | currency }}</b> </td>
</ng-container>
<ng-container matColumnDef="interest">
<th mat-header-cell class="r-amount" *matHeaderCellDef> Interest </th>
<td mat-cell class="r-amount" *matCellDef="let ele"> {{ ele.interestOriginalDue | number}} </td>
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
@@ -8,13 +8,14 @@ import { ActivatedRoute } from '@angular/router';
})
export class RepaymentScheduleTabComponent implements OnInit {
loanDetails: any;
/** Loan Repayment Schedule Details Data */
repaymentScheduleDetails: any;
@Input() repaymentScheduleDetails: any = null;
loanDetailsDataRepaymentSchedule: any = [];
/** Stores if there is any waived amount */
isWaived: boolean;
/** Columns to be displayed in original schedule table. */
displayedColumns: string[] = ['number', 'days', 'date', 'paiddate', 'check', 'principalDue', 'balanceOfLoan', 'interest', 'fees', 'penalties', 'due', 'paid', 'inadvance', 'late', 'waived', 'outstanding'];
displayedColumns: string[] = ['number', 'days', 'date', 'paiddate', 'check', 'balanceOfLoan', 'principalDue', 'interest', 'fees', 'penalties', 'due', 'paid', 'inadvance', 'late', 'waived', 'outstanding'];
/**
* Retrieves the loans with associations data from `resolve`.
@@ -22,12 +23,14 @@ export class RepaymentScheduleTabComponent implements OnInit {
*/
constructor(private route: ActivatedRoute) {
this.route.parent.data.subscribe((data: { loanDetailsData: any }) => {
this.loanDetails = data.loanDetailsData;
this.repaymentScheduleDetails = data.loanDetailsData.repaymentSchedule;
this.loanDetailsDataRepaymentSchedule = data.loanDetailsData.repaymentSchedule;
});
}
ngOnInit() {
if (this.repaymentScheduleDetails == null) {
this.repaymentScheduleDetails = this.loanDetailsDataRepaymentSchedule;
}
this.isWaived = this.repaymentScheduleDetails.totalWaived > 0;
}
+3 -1
View File
@@ -72,6 +72,7 @@ import { LoansAccountAddCollateralDialogComponent } from './custom-dialog/loans-
import { LoanAccountLoadDocumentsDialogComponent } from './custom-dialog/loan-account-load-documents-dialog/loan-account-load-documents-dialog.component';
import { LoanCreditBalanceRefundComponent } from './loans-view/loan-account-actions/loan-credit-balance-refund/loan-credit-balance-refund.component';
import { LoanDelinquencyTagsTabComponent } from './loans-view/loan-delinquency-tags-tab/loan-delinquency-tags-tab.component';
import { LoansAccountScheduleStepComponent } from './loans-account-stepper/loans-account-schedule-step/loans-account-schedule-step.component';
/**
* Loans Module
@@ -142,7 +143,8 @@ import { LoanDelinquencyTagsTabComponent } from './loans-view/loan-delinquency-t
GlimChargesStepComponent,
GlimTermsStepComponent,
GlimPreviewStepComponent,
LoanDelinquencyTagsTabComponent
LoanDelinquencyTagsTabComponent,
LoansAccountScheduleStepComponent
],
providers: [ ],
})
+72 -1
View File
@@ -4,6 +4,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs';
import { Dates } from 'app/core/utils/dates';
/**
* Loans service.
@@ -12,7 +13,8 @@ import { Observable } from 'rxjs';
providedIn: 'root'
})
export class LoansService {
constructor(private http: HttpClient) { }
constructor(private http: HttpClient,
private dateUtils: Dates) { }
/**
* @param {string} loanId loanId of the loan.
* @returns {Observable<any>}
@@ -470,4 +472,73 @@ export class LoansService {
return this.http.post('/batches?enclosingTransaction=true', glimAccount);
}
calculateLoanSchedule(payload: any): Observable<any> {
return this.http.post('/loans?command=calculateLoanSchedule', payload);
}
/**
* @param loansAccount Loan account data used for the request
* @param loansAccountTemplate Loan account template for getting product default values
* @param calendarOptions Calendar options
* @param locale String to send to backend
* @param dateFormat String with date format to manage dates
* @returns Object with Loan Account payload data.
*/
buildLoanRequestPayload(loansAccount: any, loansAccountTemplate: any, calendarOptions: any, locale: string, dateFormat: string): any {
const loansAccountData = {
...loansAccount,
charges: loansAccount.charges.map((charge: any) => ({
chargeId: charge.id,
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
})),
interestChargedFromDate: this.dateUtils.formatDate(loansAccount.interestChargedFromDate, dateFormat),
repaymentsStartingFromDate: this.dateUtils.formatDate(loansAccount.repaymentsStartingFromDate, dateFormat),
submittedOnDate: this.dateUtils.formatDate(loansAccount.submittedOnDate, dateFormat),
expectedDisbursementDate: this.dateUtils.formatDate(loansAccount.expectedDisbursementDate, dateFormat),
dateFormat,
locale,
};
if (loansAccountTemplate.clientId) {
loansAccountData.clientId = loansAccountTemplate.clientId;
loansAccountData.loanType = 'individual';
} else {
loansAccountData.groupId = loansAccountTemplate.group.id;
loansAccountData.loanType = 'group';
}
if (loansAccountData.syncRepaymentsWithMeeting) {
loansAccountData.calendarId = calendarOptions[0].id;
delete loansAccountData.syncRepaymentsWithMeeting;
}
if (loansAccountData.recalculationRestFrequencyDate) {
loansAccountData.recalculationRestFrequencyDate = this.dateUtils.formatDate(loansAccount.recalculationRestFrequencyDate, dateFormat);
}
if (loansAccountData.interestCalculationPeriodType === 0) {
loansAccountData.allowPartialPeriodInterestCalcualtion = false;
}
if (!(loansAccountData.isFloatingInterestRate === false)) {
delete loansAccountData.isFloatingInterestRate;
}
if (!(loansAccountData.multiDisburseLoan)) {
delete loansAccountData.disbursementData;
}
delete loansAccountData.isValid;
loansAccountData.principal = loansAccountData.principalAmount;
delete loansAccountData.principalAmount;
return loansAccountData;
}
}