diff --git a/src/app/loans/common-resolvers/loan-reschedule-preview.resolver.spec.ts b/src/app/loans/common-resolvers/loan-reschedule-preview.resolver.spec.ts new file mode 100644 index 0000000000..c07807b0fa --- /dev/null +++ b/src/app/loans/common-resolvers/loan-reschedule-preview.resolver.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { LoanReschedulePreviewResolver } from './loan-reschedule-preview.resolver'; + +describe('LoanReschedulePreviewResolver', () => { + let resolver: LoanReschedulePreviewResolver; + + beforeEach(() => { + TestBed.configureTestingModule({}); + resolver = TestBed.inject(LoanReschedulePreviewResolver); + }); + + it('should be created', () => { + expect(resolver).toBeTruthy(); + }); +}); diff --git a/src/app/loans/common-resolvers/loan-reschedule-preview.resolver.ts b/src/app/loans/common-resolvers/loan-reschedule-preview.resolver.ts new file mode 100644 index 0000000000..a3b2ffafee --- /dev/null +++ b/src/app/loans/common-resolvers/loan-reschedule-preview.resolver.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { + Router, Resolve, + RouterStateSnapshot, + ActivatedRouteSnapshot +} from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { LoansService } from '../loans.service'; + +@Injectable({ + providedIn: 'root' +}) +export class LoanReschedulePreviewResolver implements Resolve { + + /** + * @param {LoansService} LoansService Loans service. + */ + constructor(private loansService: LoansService) { } + + /** + * Returns the Loans data. + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const rescheduleId = route.paramMap.get('rescheduleId') || route.parent.paramMap.get('rescheduleId'); + return this.loansService.getLoanReschedulePreview(rescheduleId); + } + +} diff --git a/src/app/loans/loans-routing.module.ts b/src/app/loans/loans-routing.module.ts index d21e3dd94d..0ee4fa078b 100644 --- a/src/app/loans/loans-routing.module.ts +++ b/src/app/loans/loans-routing.module.ts @@ -28,6 +28,7 @@ import { ViewRecieptComponent } from './loans-view/transactions/view-reciept/vie import { ExportTransactionsComponent } from './loans-view/transactions/export-transactions/export-transactions.component'; import { GlimAccountComponent } from './glim-account/glim-account.component'; import { CreateGlimAccountComponent } from './glim-account/create-glim-account/create-glim-account.component'; +import { ReschedulePreviewComponent } from './loans-view/reschedule-preview/reschedule-preview.component'; /** Custom Resolvers */ import { LoanDetailsResolver } from './common-resolvers/loan-details.resolver'; @@ -60,6 +61,7 @@ import { ExternalAssetOwnerActiveTransferResolver } from './common-resolvers/ext import { LoanCollateralsResolver } from './common-resolvers/loan-collaterals.resolver'; import { LoanDelinquencyDataResolver } from './common-resolvers/loan-delinquency-data.resolver'; import { LoanDelinquencyActionsResolver } from './common-resolvers/loan-delinquency-actions.resolver'; +import { LoanReschedulePreviewResolver } from './common-resolvers/loan-reschedule-preview.resolver'; /** Loans Route. */ const routes: Routes = [ @@ -146,7 +148,7 @@ const routes: Routes = [ }, { path: 'loan-reschedules', - data: {}, + data: { breadcrumb: 'Reschedules' }, resolve: { loanRescheduleData: LoanReschedulesResolver }, @@ -154,7 +156,20 @@ const routes: Routes = [ { path: '', component: RescheduleLoanTabComponent - } + }, + { + path: ':rescheduleId', + data: { routeParamBreadcrumb: 'rescheduleId' }, + resolve: { + loanRescheduleData: LoanReschedulePreviewResolver + }, + children: [ + { + path: '', + component: ReschedulePreviewComponent + } + ] + }, ] }, { diff --git a/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.html b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.html index b86142c2e9..25904d8b99 100644 --- a/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.html +++ b/src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.html @@ -44,15 +44,21 @@ {{"labels.inputs.Actions" | translate}} - - + - + diff --git a/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.html b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.html new file mode 100644 index 0000000000..265275b816 --- /dev/null +++ b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.html @@ -0,0 +1,171 @@ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
# {{ ele.period }}   {{"labels.inputs.Days" | translate}} {{ ele.daysInPeriod }} Total {{"labels.inputs.Date" | translate}} {{ ele.dueDate | dateFormat}} + {{"labels.inputs.Paid Date" | translate}} {{ ele.obligationsMetOnDate | + dateFormat}}   + + {{"labels.inputs.Balance Of Loan" | translate}} {{ + ele.principalLoanBalanceOutstanding | formatNumber }}   {{"labels.inputs.Principal Due" | translate}} {{ ele.principalDue | + formatNumber }} {{ + repaymentScheduleDetails.totalPrincipalExpected | currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Interest" | translate}} {{ ele.interestOriginalDue + | formatNumber}} {{ repaymentScheduleDetails.totalInterestCharged | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Fees" | translate}} {{ ele.feeChargesDue | + formatNumber}} {{ repaymentScheduleDetails.totalFeeChargesCharged | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Penalties" | translate}} {{ ele.penaltyChargesDue | + formatNumber}} {{ repaymentScheduleDetails.totalPenaltyChargesCharged + | currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Due" | translate}} {{ ele.totalDueForPeriod | + formatNumber}} {{ repaymentScheduleDetails.totalRepaymentExpected | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Paid" | translate}} {{ ele.totalPaidForPeriod | + formatNumber}} {{ repaymentScheduleDetails.totalRepayment | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.In advance" | translate}} {{ + ele.totalPaidInAdvanceForPeriod | formatNumber}} {{ repaymentScheduleDetails.totalPaidInAdvance | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Late" | translate}} {{ + ele.totalPaidLateForPeriod | formatNumber}} {{ repaymentScheduleDetails.totalPaidLate | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Waived" | translate}} {{ + ele.totalWaivedForPeriod | formatNumber}} {{ repaymentScheduleDetails.totalWaived | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Outstanding" | translate}} {{ ele.totalOutstandingForPeriod | formatNumber}} {{ repaymentScheduleDetails.totalOutstanding | + currency:currencyCode:'symbol-narrow':'1.2-2' }} {{"labels.inputs.Loan Amount and Balance" + | translate}} {{"labels.inputs.Total Cost of Loan" | + translate}} {{"labels.inputs.Installment Totals" | + translate}}
+ +
+ +
+ +
diff --git a/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.scss b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.scss new file mode 100644 index 0000000000..2c802f68d7 --- /dev/null +++ b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.scss @@ -0,0 +1,38 @@ +@import "assets/styles/helper"; + +table { + width: 100%; + margin: 2% 0%; +} + +.container { + padding-bottom: 2%; +} + +.check { + padding-left: 15px; +} + +.amount-changed { + color: $status-approved; +} + +.additional { + color: $status-approved; +} + +.downpayment { + color: $status-downpayment; +} + +.paid { + color: $status-paid; +} + +.current { + color: $status-active; +} + +.overdued { + color: $status-matured; +} diff --git a/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.spec.ts b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.spec.ts new file mode 100644 index 0000000000..3e45ffbbb3 --- /dev/null +++ b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ReschedulePreviewComponent } from './reschedule-preview.component'; + +describe('ReschedulePreviewComponent', () => { + let component: ReschedulePreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ReschedulePreviewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ReschedulePreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.ts b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.ts new file mode 100644 index 0000000000..ec0b960432 --- /dev/null +++ b/src/app/loans/loans-view/reschedule-preview/reschedule-preview.component.ts @@ -0,0 +1,117 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Dates } from 'app/core/utils/dates'; +import { RepaymentSchedulePeriod } from 'app/loans/models/loan-account.model'; +import { SettingsService } from 'app/settings/settings.service'; + +import {jsPDF, jsPDFOptions} from 'jspdf'; +import autoTable from 'jspdf-autotable'; + +@Component({ + selector: 'mifosx-reschedule-preview', + templateUrl: './reschedule-preview.component.html', + styleUrls: ['./reschedule-preview.component.scss'] +}) +export class ReschedulePreviewComponent implements OnInit { + + /** Currency Code */ + @Input() currencyCode: string; + /** Loan Repayment Schedule Details Data */ + @Input() repaymentScheduleDetails: any = null; + loanDetailsDataRepaymentSchedule: any = { periods: [] }; + + /** Stores if there is any waived amount */ + isWaived: boolean; + /** Columns to be displayed in original schedule table. */ + displayedColumns: string[] = ['number', 'days', 'date', 'paiddate', 'check', 'balanceOfLoan', 'principalDue', 'interest', 'fees', 'penalties', 'due', 'paid', 'inadvance', 'late', 'waived', 'outstanding']; + /** Columns to be displayed in editable schedule table. */ + displayedColumnsEdit: string[] = ['number', 'date', 'balanceOfLoan', 'principalDue', 'interest', 'fees', 'due']; + + /** Form functions event */ + @Output() editPeriod = new EventEmitter(); + + businessDate: Date = new Date(); + + /** + * Retrieves the loans with associations data from `resolve`. + * @param {ActivatedRoute} route Activated Route. + */ + constructor(private route: ActivatedRoute, + private settingsService: SettingsService, + private dates: Dates + ) { + this.route.parent.data.subscribe((data: any) => { + + if (data.currency) { + this.currencyCode = data.loanRescheduleData.currency.code; + } + this.loanDetailsDataRepaymentSchedule = data.loanRescheduleData; + }); + this.businessDate = this.settingsService.businessDate; + } + + ngOnInit() { + if (this.repaymentScheduleDetails == null) { + this.repaymentScheduleDetails = this.loanDetailsDataRepaymentSchedule; + } + this.isWaived = this.repaymentScheduleDetails.totalWaived > 0; + } + + installmentStyle(installment: RepaymentSchedulePeriod): string { + if (installment.complete) { + return 'paid'; + } + const isCurrent: string = this.isCurrent(installment); + if (isCurrent !== '') { + return isCurrent; + } + if (installment.isAdditional) { + return 'additional'; + } else if (installment.downPaymentPeriod) { + return 'downpayment'; + } + return ''; + } + + isCurrent(installment: RepaymentSchedulePeriod): string { + if (!installment.fromDate) { + return ''; + } else { + const fromDate = this.dates.parseDate(installment.fromDate); + const dueDate = this.dates.parseDate(installment.dueDate); + if (fromDate <= this.businessDate && this.businessDate < dueDate) { + return 'current'; + } + if (this.businessDate > dueDate) { + return 'overdued'; + } + } + return ''; + } + + exportToPDF() { + const businessDate = this.dates.formatDate(this.settingsService.businessDate, Dates.DEFAULT_DATEFORMAT); + const fileName = `repaymentschedule-${businessDate}.pdf`; + + const options: jsPDFOptions = { + orientation: 'l', + unit: 'in', + format: 'letter', + precision: 2, + compress: true, + putOnlyUsedFonts: true + }; + const pdf = new jsPDF(options); + + autoTable(pdf, { + html: '#repaymentSchedule', + bodyStyles: {lineColor: [0, 0, 0]}, + styles: { + fontSize: 8, + cellWidth: 'auto', + halign: 'center' + }}); + pdf.save(fileName); + } + +} diff --git a/src/app/loans/loans.module.ts b/src/app/loans/loans.module.ts index 314b41ba17..33463b77c5 100644 --- a/src/app/loans/loans.module.ts +++ b/src/app/loans/loans.module.ts @@ -82,6 +82,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { LoanDelinquencyActionDialogComponent } from './custom-dialog/loan-delinquency-action-dialog/loan-delinquency-action-dialog.component'; import { LoanReagingComponent } from './loans-view/loan-account-actions/loan-reaging/loan-reaging.component'; import { LoanReamortizeComponent } from './loans-view/loan-account-actions/loan-reamortize/loan-reamortize.component'; +import { ReschedulePreviewComponent } from './loans-view/reschedule-preview/reschedule-preview.component'; /** * Loans Module @@ -121,6 +122,7 @@ import { LoanReamortizeComponent } from './loans-view/loan-account-actions/loan- LoanTrancheDetailsComponent, CloseAsRescheduledComponent, LoanRescheduleComponent, + ReschedulePreviewComponent, LoanCollateralTabComponent, CreateLoansAccountComponent, LoansAccountDetailsStepComponent, diff --git a/src/app/loans/loans.service.ts b/src/app/loans/loans.service.ts index a2508a58d9..bd552162e0 100644 --- a/src/app/loans/loans.service.ts +++ b/src/app/loans/loans.service.ts @@ -321,6 +321,14 @@ export class LoansService { return this.http.get('/rescheduleloans', { params: httpParams }); } + /** + * Returns the Loan Reschedule Preview + */ + getLoanReschedulePreview(rescheduleId: any, ) { + const httpParams = new HttpParams().set('command', 'previewLoanReschedule'); + return this.http.get(`/rescheduleloans/${rescheduleId}`, { params: httpParams }); + } + /** * Returns the Loan Reschedule request */ diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html index b5b03356f5..fcb40e375a 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component.html @@ -40,12 +40,12 @@ {{'labels.inputs.Reschedule Request' | translate}}# - {{loan.id}} + {{request.id}} {{'labels.inputs.Loan Account' | translate}}# - {{loan.loanAccountNumber}} + {{loan.loanAccountNumber}} diff --git a/src/assets/translations/en-US.json b/src/assets/translations/en-US.json index 77fb63b15b..2e105a02ff 100644 --- a/src/assets/translations/en-US.json +++ b/src/assets/translations/en-US.json @@ -3424,6 +3424,7 @@ "View Link": "View Link", "View Loan Account": "View Loan Account", "View Receipts": "View Receipts", + "View Reschedule": "View Reschedule", "View Standing Instruction": "View Standing Instruction", "View Transactions": "View Transactions", "Waive Charge": "Waive Charge",