From 11cbe7a155517db623b708227d68005a41530955 Mon Sep 17 00:00:00 2001 From: Muskan Khedia <36980003+muskankhedia@users.noreply.github.com> Date: Sun, 31 May 2020 22:49:21 +0530 Subject: [PATCH] feat: add loans view page (#661) - Add loan account page - Add general tab - Add account details tab Fixes: #219 --- src/app/clients/clients-routing.module.ts | 5 + .../general-tab/general-tab.component.html | 2 +- .../general-tab/general-tab.component.scss | 5 + .../loan-details-general.resolver.ts | 31 +++ .../common-resolvers/loan-details.resolver.ts | 31 +++ src/app/loans/loans-routing.module.ts | 32 ++- .../account-details.component.html | 161 ++++++++++++++ .../account-details.component.scss | 7 + .../account-details.component.spec.ts | 25 +++ .../account-details.component.ts | 26 +++ src/app/loans/loans-view/button-config.ts | 173 +++++++++++++++ .../general-tab/general-tab.component.html | 200 ++++++++++++++++++ .../general-tab/general-tab.component.scss | 22 ++ .../general-tab/general-tab.component.spec.ts | 25 +++ .../general-tab/general-tab.component.ts | 169 +++++++++++++++ .../loans-view.component-theme.scss | 13 ++ .../loans-view/loans-view.component.html | 61 ++++++ .../loans-view/loans-view.component.scss | 33 +++ .../loans-view/loans-view.component.spec.ts | 25 +++ .../loans/loans-view/loans-view.component.ts | 111 ++++++++++ src/app/loans/loans.module.ts | 22 +- src/app/loans/loans.service.ts | 6 +- src/theme/custom/denim-yellowgreen.scss | 2 + src/theme/custom/pictonblue-yellowgreen.scss | 2 + 24 files changed, 1178 insertions(+), 11 deletions(-) create mode 100644 src/app/loans/common-resolvers/loan-details-general.resolver.ts create mode 100644 src/app/loans/common-resolvers/loan-details.resolver.ts create mode 100644 src/app/loans/loans-view/account-details/account-details.component.html create mode 100644 src/app/loans/loans-view/account-details/account-details.component.scss create mode 100644 src/app/loans/loans-view/account-details/account-details.component.spec.ts create mode 100644 src/app/loans/loans-view/account-details/account-details.component.ts create mode 100644 src/app/loans/loans-view/button-config.ts create mode 100644 src/app/loans/loans-view/general-tab/general-tab.component.html create mode 100644 src/app/loans/loans-view/general-tab/general-tab.component.scss create mode 100644 src/app/loans/loans-view/general-tab/general-tab.component.spec.ts create mode 100644 src/app/loans/loans-view/general-tab/general-tab.component.ts create mode 100644 src/app/loans/loans-view/loans-view.component-theme.scss create mode 100644 src/app/loans/loans-view/loans-view.component.html create mode 100644 src/app/loans/loans-view/loans-view.component.scss create mode 100644 src/app/loans/loans-view/loans-view.component.spec.ts create mode 100644 src/app/loans/loans-view/loans-view.component.ts diff --git a/src/app/clients/clients-routing.module.ts b/src/app/clients/clients-routing.module.ts index 2d790d4f4..21f40c5ab 100644 --- a/src/app/clients/clients-routing.module.ts +++ b/src/app/clients/clients-routing.module.ts @@ -160,6 +160,11 @@ const routes: Routes = [ }, ] }, + { + path: 'loans', + data: { title: extract('Loans'), breadcrumb: 'loans' }, + loadChildren: '../loans/loans.module#LoansModule' + } ]) ]; diff --git a/src/app/clients/clients-view/general-tab/general-tab.component.html b/src/app/clients/clients-view/general-tab/general-tab.component.html index 4646c1042..8aeb01c87 100644 --- a/src/app/clients/clients-view/general-tab/general-tab.component.html +++ b/src/app/clients/clients-view/general-tab/general-tab.component.html @@ -133,7 +133,7 @@ - + diff --git a/src/app/clients/clients-view/general-tab/general-tab.component.scss b/src/app/clients/clients-view/general-tab/general-tab.component.scss index 4ea3dfdcc..16f4183cc 100644 --- a/src/app/clients/clients-view/general-tab/general-tab.component.scss +++ b/src/app/clients/clients-view/general-tab/general-tab.component.scss @@ -18,5 +18,10 @@ margin: 4px; line-height: 25px; } + + .select-row:hover { + cursor: pointer; + } } + } diff --git a/src/app/loans/common-resolvers/loan-details-general.resolver.ts b/src/app/loans/common-resolvers/loan-details-general.resolver.ts new file mode 100644 index 000000000..592f84a20 --- /dev/null +++ b/src/app/loans/common-resolvers/loan-details-general.resolver.ts @@ -0,0 +1,31 @@ +/** Angular Imports */ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { LoansService } from '../loans.service'; + +/** + * Clients data resolver. + */ +@Injectable() +export class LoanDetailsGeneralResolver implements Resolve { + + /** + * @param {LoansService} LoansService Loans service. + */ + constructor(private loansService: LoansService) { } + + /** + * Returns the Loans data. + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const loanId = route.parent.paramMap.get('loanId'); + return this.loansService.getLoanAccountDetails(loanId); + } + +} diff --git a/src/app/loans/common-resolvers/loan-details.resolver.ts b/src/app/loans/common-resolvers/loan-details.resolver.ts new file mode 100644 index 000000000..f4968d082 --- /dev/null +++ b/src/app/loans/common-resolvers/loan-details.resolver.ts @@ -0,0 +1,31 @@ +/** Angular Imports */ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { LoansService } from '../loans.service'; + +/** + * Clients data resolver. + */ +@Injectable() +export class LoanDetailsResolver implements Resolve { + + /** + * @param {LoansService} LoansService Loans service. + */ + constructor(private loansService: LoansService) { } + + /** + * Returns the Loans data. + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const loanId = route.paramMap.get('loanId'); + return this.loansService.getLoanAccountDetails(loanId); + } + +} diff --git a/src/app/loans/loans-routing.module.ts b/src/app/loans/loans-routing.module.ts index 2806d5a22..943a958d7 100644 --- a/src/app/loans/loans-routing.module.ts +++ b/src/app/loans/loans-routing.module.ts @@ -7,9 +7,14 @@ import { extract } from '../core/i18n/i18n.service'; /** Custom Components */ import { AddLoanChargeComponent } from './add-loan-charge/add-loan-charge.component'; +import { LoansViewComponent } from './loans-view/loans-view.component'; +import { GeneralTabComponent } from './loans-view/general-tab/general-tab.component'; +import { AccountDetailsComponent } from './loans-view/account-details/account-details.component'; /** Custom Resolvers */ import { LoanChargeTemplateResolver } from './common-resolvers/loan-charge-template.resolver'; +import { LoanDetailsResolver } from './common-resolvers/loan-details.resolver'; +import { LoanDetailsGeneralResolver } from './common-resolvers/loan-details-general.resolver'; const routes: Routes = [ { @@ -18,8 +23,28 @@ const routes: Routes = [ children: [{ path: ':loanId', data: { title: extract('Loan View'), routeParamBreadcrumb: 'loanId' }, + component: LoansViewComponent, + resolve: { + loanDetailsData: LoanDetailsResolver + }, // Component For Loan View Comes Here children: [ + { + path: 'general', + component: GeneralTabComponent, + data: { title: extract('General'), breadcrumb: 'General', routeParamBreadcrumb: false }, + resolve: { + loanDetailsData: LoanDetailsGeneralResolver + } + }, + { + path: 'accountdetail', + component: AccountDetailsComponent, + data: { title: extract('Account Detail'), breadcrumb: 'Account Detail', routeParamBreadcrumb: false }, + resolve: { + loanDetailsData: LoanDetailsGeneralResolver + } + }, { path: 'add-loan-charge', component: AddLoanChargeComponent, @@ -36,6 +61,11 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], declarations: [], - providers: [LoanChargeTemplateResolver] + providers: [ + LoanChargeTemplateResolver, + LoanDetailsGeneralResolver, + LoanDetailsResolver + ] }) + export class LoansRoutingModule { } diff --git a/src/app/loans/loans-view/account-details/account-details.component.html b/src/app/loans/loans-view/account-details/account-details.component.html new file mode 100644 index 000000000..7567830cf --- /dev/null +++ b/src/app/loans/loans-view/account-details/account-details.component.html @@ -0,0 +1,161 @@ +
+ +
+ +
+ Repayment Strategy: + {{ loanDetails.transactionProcessingStrategyName }} +
+ +
+ Repayments: + {{loanDetails.numberOfRepayments}} every {{loanDetails.repaymentEvery}} {{loanDetails.repaymentFrequencyType.value}} + +  on {{loanDetails.repaymentFrequencyNthDayType?.value}} {{loanDetails.repaymentFrequencyDayOfWeekType?.value}} + + +
+ +
+ Amortization + {{loanDetails.amortizationType.value}} +
+ +
+ Equal Amortization + {{loanDetails.isEqualAmortization}} +
+ +
+ Interest + {{loanDetails.annualInterestRate}} per annum ({{loanDetails.interestRatePerPeriod}}%  {{loanDetails.interestRateFrequencyType.value}}) - {{loanDetails.interestType.value}} +
+ +
+ Grace: On Principal Payment + {{loanDetails.graceOnPrincipalPayment}} +
+ +
+ Grace: On Interest Payment + {{loanDetails.graceOnInterestPayment}} +
+ +
+ Grace on Arrears Ageing + {{loanDetails.graceOnArrearsAgeing}} +
+ +
+ Fund Source + {{loanDetails.fundName}} +
+ +
+ Interest Free Period + {{loanDetails.graceOnInterestCharged}} +
+ +
+ Interest Calculation Period + {{loanDetails.interestCalculationPeriodType.value}} +
+ +
+ Allow Partial Interest Calculation with same as repayment + {{loanDetails.allowPartialPeriodInterestCalcualtion}} +
+ +
+ Interest Type + {{loanDetails.interestType.value}} +
+ +
+ Submitted on + {{loanDetails.timeline.submittedOnDate | date }} +
+ +
+ Approved on + {{loanDetails.timeline.approvedOnDate | date }} +
+ +
+ Disbursed on + {{loanDetails.timeline.actualDisbursementDate | date }} +
+ +
+ Matures on + {{loanDetails.timeline.expectedMaturityDate | date }} +
+ +
+ Fixed EMI amount + {{loanDetails.fixedEmiAmount | number}} +
+ +
+ Is Topup Loan? + {{loanDetails.isTopup}} +
+ +
+ Loan closed with Topup + {{loanDetails.closureLoanAccountNo}} +
+ +
+ Topup closure amount + {{loanDetails.topupAmount| number}} +
+ +
+ Recalculate Interest based on new terms + {{loanDetails.isInterestRecalculationEnabled ? 'Yes' : 'No' }} +
+ +
+ Days in year + {{loanDetails.daysInYearType.value}} +
+ +
+ Days in month + {{loanDetails.daysInMonthType.value }} +
+ +
+ Interest recalculation compounding on + {{loanDetails.interestRecalculationData.interestRecalculationCompoundingType.value}} +
+ +
+ Advance payments adjustment type + {{loanDetails.interestRecalculationData.rescheduleStrategyType.value}} +
+ +
+ Frequency for recalculate Outstanding Principal + {{loanDetails.interestRecalculationData.calendarData.humanReadable}} +
+ +
+ Frequency for compounding + {{loanDetails.interestRecalculationData.compoundingCalendarData.humanReadable}} +
+ +
+ Variable Installments Allowed + {{loanDetails.isVariableInstallmentsAllowed}} +
+ +
+ Gap between Installments:(Min + {{loanDetails.minimumGap}} Days , Max:{{loanDetails.maximumGap}} Days) +
+ +
+ +
diff --git a/src/app/loans/loans-view/account-details/account-details.component.scss b/src/app/loans/loans-view/account-details/account-details.component.scss new file mode 100644 index 000000000..3d66aafb3 --- /dev/null +++ b/src/app/loans/loans-view/account-details/account-details.component.scss @@ -0,0 +1,7 @@ +table { + width: 100%; +} + +span { + margin: 0.5em 0; +} diff --git a/src/app/loans/loans-view/account-details/account-details.component.spec.ts b/src/app/loans/loans-view/account-details/account-details.component.spec.ts new file mode 100644 index 000000000..ab7581370 --- /dev/null +++ b/src/app/loans/loans-view/account-details/account-details.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AccountDetailsComponent } from './account-details.component'; + +describe('AccountDetailsComponent', () => { + let component: AccountDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AccountDetailsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AccountDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loans/loans-view/account-details/account-details.component.ts b/src/app/loans/loans-view/account-details/account-details.component.ts new file mode 100644 index 000000000..3c3195646 --- /dev/null +++ b/src/app/loans/loans-view/account-details/account-details.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'mifosx-account-details', + templateUrl: './account-details.component.html', + styleUrls: ['./account-details.component.scss'] +}) +export class AccountDetailsComponent implements OnInit { + + loanDetails: any; + dataObject: { + property: string, + value: string + }[]; + + constructor(private route: ActivatedRoute) { + this.route.data.subscribe((data: { loanDetailsData: any, }) => { + this.loanDetails = data.loanDetailsData; + }); + } + + ngOnInit() { + } + +} diff --git a/src/app/loans/loans-view/button-config.ts b/src/app/loans/loans-view/button-config.ts new file mode 100644 index 000000000..40a7483b1 --- /dev/null +++ b/src/app/loans/loans-view/button-config.ts @@ -0,0 +1,173 @@ +export const ButtonConfig = { + 'Submitted and pending approval': { + singlebuttons: [ + { + name: 'Add Loan Charge', + icon: 'fa fa-plus', + taskPermissionName: 'CREATE_LOANCHARGE', + }, + { + name: 'Approve', + icon: 'fa fa-check', + taskPermissionName: 'APPROVE_LOAN', + }, + { + name: 'Modify Application', + icon: 'fa fa-pincel-square-o', + taskPermissionName: 'UPDATE_LOAN', + }, + { + name: 'Reject', + icon: 'fa fa-times', + taskPermissionName: 'REJECT_LOAN', + }, + ], + options: [ + { + name: 'Withdrawn by client', + taskPermissionName: 'WITHDRAW_LOAN', + }, + { + name: 'Delete', + taskPermissionName: 'DELETE_LOAN', + }, + { + name: 'Add Collateral', + taskPermissionName: 'CREATE_COLLATERAL', + }, + { + name: 'View Guarantors', + taskPermissionName: 'READ_GUARANTOR', + }, + { + name: 'Create Guarantor', + taskPermissionName: 'CREATE_GUARANTOR', + }, + { + name: 'Loan Screen Reports', + taskPermissionName: 'READ_LOAN', + }, + ], + }, + + Approved: { + singlebuttons: [ + { + name: 'Disburse', + icon: 'fa fa-flag', + taskPermissionName: 'DISBURSE_LOAN', + }, + { + name: 'Disburse to Savings', + icon: 'fa fa-flag', + taskPermissionName: 'DISBURSETOSAVINGS_LOAN', + }, + { + name: 'Undo Approval', + icon: 'fa fa-undo', + taskPermissionName: 'APPROVALUNDO_LOAN', + }, + ], + options: [ + { + name: 'Add Loan Charge', + taskPermissionName: 'CREATE_LOANCHARGE', + }, + { + name: 'List Guarantor', + taskPermissionName: 'READ_GUARANTOR', + }, + { + name: 'Create Guarantor', + taskPermissionName: 'CREATE_GUARANTOR', + }, + { + name: 'Loan Screen Report', + taskPermissionName: 'READ_LOAN', + }, + ], + }, + + Active: { + singlebuttons: [ + { + name: 'Add Loan Charge', + icon: 'fa fa-plus', + taskPermissionName: 'CREATE_LOANCHARGE', + }, + { + name: 'Foreclosure', + icon: 'icon-dollar', + taskPermissionName: 'FORECLOSURE_LOAN', + }, + { + name: 'Make Repayment', + icon: 'fa fa-dollar', + taskPermissionName: 'REPAYMENT_LOAN', + }, + { + name: 'Undo Disbursal', + icon: 'fa fa-undo', + taskPermissionName: 'DISBURSALUNDO_LOAN', + }, + ], + options: [ + { + name: 'Waive Interest', + taskPermissionName: 'WAIVEINTERESTPORTION_LOAN', + }, + { + name: 'Reschedule', + taskPermissionName: 'CREATE_RESCHEDULELOAN', + }, + { + name: 'Write Off', + taskPermissionName: 'WRITEOFF_LOAN', + }, + { + name: 'Close (as Rescheduled)', + taskPermissionName: 'CLOSEASRESCHEDULED_LOAN', + }, + { + name: 'Close', + taskPermissionName: 'CLOSE_LOAN', + }, + { + name: 'Loan Screen Report', + taskPermissionName: 'READ_LOAN', + }, + { + name: 'List Guarantor', + taskPermissionName: 'READ_GUARANTOR', + }, + { + name: 'Create Guarantor', + taskPermissionName: 'CREATE_GUARANTOR', + }, + { + name: 'Recover Guarantee', + taskPermissionName: 'RECOVERGUARANTEES_LOAN', + }, + ], + }, + + Overpaid: { + singlebuttons: [ + { + name: 'Transfer Funds', + icon: 'fa fa-exchange', + taskPermissionName: 'CREATE_ACCOUNTTRANSFER', + }, + ], + }, + + 'Closed (written off)': { + singlebuttons: [ + { + name: 'Recovery Payment', + icon: 'fa fa-briefcase', + taskPermissionName: 'RECOVERYPAYMENT_LOAN', + }, + ], + }, +}; diff --git a/src/app/loans/loans-view/general-tab/general-tab.component.html b/src/app/loans/loans-view/general-tab/general-tab.component.html new file mode 100644 index 000000000..484fb9962 --- /dev/null +++ b/src/app/loans/loans-view/general-tab/general-tab.component.html @@ -0,0 +1,200 @@ +
+ + +

Performance History

+
+

+ # of Repayments :{{loanDetails?.numberOfRepayments}} +

+ Maturity Date :{{loanDetails?.timeline.expectedMaturityDate | date}} +

+
+
+ +
+ +

Loan Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ ele.property }} Original {{ ele.original | number }} Paid {{ ele.paid | number }} Waived {{ ele.waived | number }} Written Off {{ ele.writtenOff | number }} Outstanding {{ ele.outstanding | number }} Over Due {{ ele.overdue | number }}
+ +
+ +
+ +

Loan Details

+ + + + + + + + + + + + + + + +
Key {{ ele.key }} Value + + {{loanDetails.timeline.actualDisbursementDate | date}} + Not Available + + + + {{loanDetails.loanPurchaseName}} + Not Available + + + + Unassigned + {{loanDetails.loanOfficerName}}   + + + + + + + + {{loanDetails.currency.name }} {{loanDetails.currency.code}} + + + + {{loanDetails.externalId}} + Not Available + + + + {{ ele.value }} + +
+ +
+ +
+ +

Loan Details

+ + + + + + + + + + + + + + + +
Key {{ ele.key }} Value + + {{loanDetails.timeline.actualDisbursementDate | date}} + Not Available + + + + Unassigned + {{loanDetails.loanOfficerName}}   + + + + + + + + {{loanDetails.currency.name }} + + + + {{loanDetails.externalId}} + Not Available + + +
+
+ +
+

Loan Purpose

+
+
+ +
+ Loan Purpose: + + {{ loanDetails.loanPurposeName }} + + + Not Provided + +
+ +
+ Proposed Amount: + {{ loanDetails.proposedPrincipal | number}} +
+ +
+ Approved Amount: + {{ loanDetails.approvedPrincipal | number }} +
+ +
+ Disburse Amount: + {{ loanDetails.principal | number }} +
+ +
+ Arrears By: + Not Provided +
+ +
+
+
+ +
diff --git a/src/app/loans/loans-view/general-tab/general-tab.component.scss b/src/app/loans/loans-view/general-tab/general-tab.component.scss new file mode 100644 index 000000000..38c0d86ff --- /dev/null +++ b/src/app/loans/loans-view/general-tab/general-tab.component.scss @@ -0,0 +1,22 @@ +.tab-container { + padding: 1%; + margin: 1%; + + h3{ + margin:1% auto; + } + .performance-history-container { + border: 1px solid; + padding: 1%; + margin-bottom: 20px ; + } + + table { + width: 100%; + margin-bottom: 20px; + } +} + +span { + margin: 0.5em 0; +} diff --git a/src/app/loans/loans-view/general-tab/general-tab.component.spec.ts b/src/app/loans/loans-view/general-tab/general-tab.component.spec.ts new file mode 100644 index 000000000..68ff6503e --- /dev/null +++ b/src/app/loans/loans-view/general-tab/general-tab.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GeneralTabComponent } from './general-tab.component'; + +describe('GeneralTabComponent', () => { + let component: GeneralTabComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ GeneralTabComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GeneralTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loans/loans-view/general-tab/general-tab.component.ts b/src/app/loans/loans-view/general-tab/general-tab.component.ts new file mode 100644 index 000000000..6e95cd02e --- /dev/null +++ b/src/app/loans/loans-view/general-tab/general-tab.component.ts @@ -0,0 +1,169 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { MatTableDataSource } from '@angular/material'; + +@Component({ + selector: 'mifosx-general-tab', + templateUrl: './general-tab.component.html', + styleUrls: ['./general-tab.component.scss'] +}) +export class GeneralTabComponent implements OnInit { + + loanDetails: any; + status: any; + loanSummaryColumns: string[] = ['Empty', 'Original', 'Paid', 'Waived', 'Written Off', 'Outstanding', 'Over Due']; + loanDetailsColumns: string[] = ['Key', 'Value']; + loanSummaryTableData: { + 'property': string, + 'original': string, + 'paid': string, + 'waived': string, + 'writtenOff': string, + 'outstanding': string, + 'overdue': string + }[]; + loanDetailsTableData: { + 'key': string, + 'value'?: string + }[]; + + /** Data source for loans summary table. */ + dataSource: MatTableDataSource; + detailsDataSource: MatTableDataSource; + + constructor(private route: ActivatedRoute) { + this.route.data.subscribe((data: { loanDetailsData: any, }) => { + this.loanDetails = data.loanDetailsData; + }); + } + + ngOnInit() { + this.status = this.loanDetails.value; + if (this.loanDetails.summary) { + this.setloanSummaryTableData(); + this.setloanDetailsTableData(); + } else { + this.setloanNonDetailsTableData(); + } + } + + setloanSummaryTableData() { + this.loanSummaryTableData = [ + { + 'property': 'Principal', + 'original': this.loanDetails.summary.principalDisbursed, + 'paid': this.loanDetails.summary.principalPaid, + 'waived': this.loanDetails.summary.principalWrittenOff, + 'writtenOff': this.loanDetails.summary.principalOutstanding, + 'outstanding': this.loanDetails.summary.principalOutstanding, + 'overdue': this.loanDetails.summary.principalOverdue, + + }, + { + 'property': 'Interest', + 'original': this.loanDetails.summary.interestCharged, + 'paid': this.loanDetails.summary.interestPaid, + 'waived': this.loanDetails.summary.interestWaived, + 'writtenOff': this.loanDetails.summary.interestWrittenOff, + 'outstanding': this.loanDetails.summary.interestOutstanding, + 'overdue': this.loanDetails.summary.interestOverdue, + }, + { + 'property': 'Fees', + 'original': this.loanDetails.summary.feeChargesCharged, + 'paid': this.loanDetails.summary.feeChargesPaid, + 'waived': this.loanDetails.summary.feeChargesWaived, + 'writtenOff': this.loanDetails.summary.feeChargesWrittenOff, + 'outstanding': this.loanDetails.summary.feeChargesOutstanding, + 'overdue': this.loanDetails.summary.feeChargesOverdue, + }, + { + 'property': 'Penalties', + 'original': this.loanDetails.summary.penaltyChargesCharged, + 'paid': this.loanDetails.summary.penaltyChargesPaid, + 'waived': this.loanDetails.summary.penaltyChargesWaived, + 'writtenOff': this.loanDetails.summary.penaltyChargesWrittenOff, + 'outstanding': this.loanDetails.summary.penaltyChargesOutstanding, + 'overdue': this.loanDetails.summary.penaltyChargesOverdue, + }, + { + 'property': 'Total', + 'original': this.loanDetails.summary.totalExpectedRepayment, + 'paid': this.loanDetails.summary.totalRepayment, + 'waived': this.loanDetails.summary.totalWaived, + 'writtenOff': this.loanDetails.summary.totalWrittenOff, + 'outstanding': this.loanDetails.summary.totalOutstanding, + 'overdue': this.loanDetails.summary.totalOverdue, + } + ]; + this.dataSource = new MatTableDataSource(this.loanSummaryTableData); + } + + setloanDetailsTableData() { + + this.loanDetailsTableData = [ + { + 'key': 'Disbursement Date' + }, + { + 'key': 'Loan Purpose' + }, + { + 'key': 'Loan Officer' + }, + { + 'key': 'Currency' + }, + { + 'key': 'External Id' + }, + { + 'key': 'Proposed Amount', + 'value': this.loanDetails.proposedPrincipal, + }, + { + 'key': 'Approved Amount', + 'value': this.loanDetails.approvedPrincipal, + }, + { + 'key': 'Disburse Amount', + 'value': this.loanDetails.principal, + }, + ]; + this.detailsDataSource = new MatTableDataSource(this.loanDetailsTableData); + + } + + setloanNonDetailsTableData() { + this.loanDetailsTableData = [ + { + 'key': 'Disbursement Date' + }, + { + 'key': 'Currency' + }, + { + 'key': 'Loan Officer' + }, + { + 'key': 'External Id' + } + ]; + this.detailsDataSource = new MatTableDataSource(this.loanDetailsTableData); + } + + showApprovedAmountBasedOnStatus() { + if (this.status === 'Submitted and pending approval' || this.status === 'Withdrawn by applicant' || this.status === 'Rejected') { + return false; + } + return true; + } + + showDisbursedAmountBasedOnStatus = function() { + if (this.status === 'Submitted and pending approval' || this.status === 'Withdrawn by applicant' || this.status === 'Rejected' || + this.status === 'Approved') { + return false; + } + return true; + }; +} diff --git a/src/app/loans/loans-view/loans-view.component-theme.scss b/src/app/loans/loans-view/loans-view.component-theme.scss new file mode 100644 index 000000000..83745bd5b --- /dev/null +++ b/src/app/loans/loans-view/loans-view.component-theme.scss @@ -0,0 +1,13 @@ +@import "~@angular/material/theming"; + +@mixin loans-view-component-theme($theme) { + $primary: map-get($theme, primary); + + mifosx-loans-view { + .loan-card { + .header { + background-color: mat-color($primary, 500); + } + } + } +} diff --git a/src/app/loans/loans-view/loans-view.component.html b/src/app/loans/loans-view/loans-view.component.html new file mode 100644 index 000000000..19bb41eb2 --- /dev/null +++ b/src/app/loans/loans-view/loans-view.component.html @@ -0,0 +1,61 @@ + + + + +
+ +
+
+

+ + Loan Product: {{loanDetailsData.loanProductName}}(#{{loanDetailsData.accountNo}}) +
+ Client Name: {{loanDetailsData.clientName}}
+ Group Name: {{loanDetailsData.group.name}} +

+
+ +
+

Loan Account OverView

+ Current Balance: {{loanDetailsData.currency.displaySymbol}} {{loanDetailsData.summary.totalOutstanding | number}}
+ Arrears By: {{loanDetailsData.summary.totalOverdue | number}} + Not Provided +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
diff --git a/src/app/loans/loans-view/loans-view.component.scss b/src/app/loans/loans-view/loans-view.component.scss new file mode 100644 index 000000000..4a99956cf --- /dev/null +++ b/src/app/loans/loans-view/loans-view.component.scss @@ -0,0 +1,33 @@ +.loan-card { + margin: 0 auto; + max-width: 80rem; + width: 90%; + padding: 0; + .header { + padding: 1%; + .header-title-group { + .loan-card-title { + color: white; + width: 90%; + p { + color: white; + } + } + } + .loan-actions { + align-self: flex-end; + margin: 0 1%; + // min-width: 72%; + } + .loan-span { + margin: 0 0.5%; + } + } + .navigation-tabs { + overflow: auto; + } +} + +.loansOverview{ + font-size: 14px; +} diff --git a/src/app/loans/loans-view/loans-view.component.spec.ts b/src/app/loans/loans-view/loans-view.component.spec.ts new file mode 100644 index 000000000..f3ab00e5f --- /dev/null +++ b/src/app/loans/loans-view/loans-view.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoansViewComponent } from './loans-view.component'; + +describe('LoansViewComponent', () => { + let component: LoansViewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoansViewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoansViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loans/loans-view/loans-view.component.ts b/src/app/loans/loans-view/loans-view.component.ts new file mode 100644 index 000000000..ff72d78bf --- /dev/null +++ b/src/app/loans/loans-view/loans-view.component.ts @@ -0,0 +1,111 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { LoansService } from '../loans.service'; + +// Custom Imports +import { ButtonConfig } from './button-config'; + +@Component({ + selector: 'mifosx-loans-view', + templateUrl: './loans-view.component.html', + styleUrls: ['./loans-view.component.scss'] +}) +export class LoansViewComponent implements OnInit { + + loanDetailsData: any; + recalculateInterest: any; + status: string; + buttons: { + singlebuttons: { + name: string, + icon: string, + taskPermissionName: string, + }[], + options?: { + name: string, + taskPermissionName: string + }[] + }; + + constructor(private route: ActivatedRoute) { + this.route.data.subscribe((data: { loanDetailsData: any, }) => { + this.loanDetailsData = data.loanDetailsData; + console.log('loandata:: ', this.loanDetailsData); + }); + } + + ngOnInit() { + + this.recalculateInterest = this.loanDetailsData.recalculateInterest || true; + this.status = this.loanDetailsData.status.value; + + console.log('status: ', this.status); + // Defines the buttons based on the status of the loan account + if (this.status === 'Submitted and pending approval') { + this.buttons = ButtonConfig['Submitted and pending approval']; + + this.buttons.options.splice(1, 0, { + name: (this.loanDetailsData.loanOfficerName ? 'Change Loan Officer' : 'Assign Loan Officer' ), + taskPermissionName: 'DISBURSE_LOAN' + }); + + if (this.loanDetailsData.isVariableInstallmentsAllowed) { + this.buttons.options.push({ + name: 'Edit Repayment Schedule', + taskPermissionName: 'ADJUST_REPAYMENT_SCHEDULE' + }) ; + } + + } else if (this.status === 'Approved') { + this.buttons = ButtonConfig['Approved']; + + this.buttons.singlebuttons.splice(1, 0, { + name: (this.loanDetailsData.loanOfficerName ? 'Change Loan Officer' : 'Assign Loan Officer' ), + icon: 'fa fa-user', + taskPermissionName: 'DISBURSE_LOAN' + }); + + } else if (this.status === 'Active') { + this.buttons = ButtonConfig['Active']; + + if (this.loanDetailsData.canDisburse) { + this.buttons.singlebuttons.splice(1, 0, { + name: 'Disburse', + icon: 'fa fa-flag', + taskPermissionName: 'DISBURSE_LOAN' + }); + this.buttons.singlebuttons.splice(1, 0, { + name: 'Disburse To Savings', + icon: 'fa fa-flag', + taskPermissionName: 'DISBURSETOSAVINGS_LOAN' + }); + } + + // loan officer not assigned to loan, below logic + // helps to display otherwise not + if (!this.loanDetailsData.loanOfficerName) { + this.buttons.singlebuttons.splice(1, 0, { + name: 'Assign Loan Officer', + icon: 'fa fa-user', + taskPermissionName: 'UPDATELOANOFFICER_LOAN' + }); + } + + if (this.recalculateInterest) { + this.buttons.singlebuttons.splice(1, 0, { + name: 'Prepay Loan', + icon: 'fa fa-money', + taskPermissionName: 'REPAYMENT_LOAN' + }); + } + + } else if (this.status === 'Overpaid') { + this.buttons = ButtonConfig['Overpaid']; + } else if (this.status === 'Closed (written off)') { + this.buttons = ButtonConfig['Closed (written off)']; + } + console.log('this.buttons: ', this.buttons); + + } + +} diff --git a/src/app/loans/loans.module.ts b/src/app/loans/loans.module.ts index ec8baa558..cb81e8bfc 100644 --- a/src/app/loans/loans.module.ts +++ b/src/app/loans/loans.module.ts @@ -1,13 +1,18 @@ /** Angular Imports */ import { NgModule } from '@angular/core'; -import { DatePipe} from '@angular/common'; +import { DatePipe } from '@angular/common'; +import { DirectivesModule } from '../directives/directives.module'; /** Custom Modules */ import { LoansRoutingModule } from './loans-routing.module'; import { SharedModule } from 'app/shared/shared.module'; +import { PipesModule } from '../pipes/pipes.module'; /** Custom Components */ import { AddLoanChargeComponent } from './add-loan-charge/add-loan-charge.component'; +import { LoansViewComponent } from './loans-view/loans-view.component'; +import { GeneralTabComponent } from './loans-view/general-tab/general-tab.component'; +import { AccountDetailsComponent } from './loans-view/account-details/account-details.component'; /** * Loans Module @@ -15,12 +20,13 @@ import { AddLoanChargeComponent } from './add-loan-charge/add-loan-charge.compon * All components related to loan functions should be declared here. */ @NgModule({ - imports: [ - SharedModule, - LoansRoutingModule + imports: [SharedModule, LoansRoutingModule, DirectivesModule, PipesModule], + declarations: [ + AddLoanChargeComponent, + LoansViewComponent, + GeneralTabComponent, + AccountDetailsComponent, ], - declarations: [AddLoanChargeComponent], - providers: [DatePipe] - + providers: [DatePipe], }) -export class LoansModule { } +export class LoansModule {} diff --git a/src/app/loans/loans.service.ts b/src/app/loans/loans.service.ts index c33fac775..a979e0f9c 100644 --- a/src/app/loans/loans.service.ts +++ b/src/app/loans/loans.service.ts @@ -12,7 +12,7 @@ import { Observable } from 'rxjs'; providedIn: 'root' }) export class LoansService { - constructor(private http: HttpClient) {} + constructor(private http: HttpClient) { } /** * @param {string} loanId loanId of the loan. * @returns {Observable} @@ -29,4 +29,8 @@ export class LoansService { return this.http.post(`/loans/${loanId}/${resourceType}`, loanCharge); } + getLoanAccountDetails(loanId: string) { + return this.http.get(`/loans/${loanId}`); + } + } diff --git a/src/theme/custom/denim-yellowgreen.scss b/src/theme/custom/denim-yellowgreen.scss index fddc863f6..221893882 100644 --- a/src/theme/custom/denim-yellowgreen.scss +++ b/src/theme/custom/denim-yellowgreen.scss @@ -4,6 +4,7 @@ @import '../../app/groups/groups-view/groups-view.component-theme.scss'; @import '../../app/centers/centers-view/centers-view.component-theme.scss'; @import '../../app/home/dashboard/dashboard.component-theme.scss'; +@import '../../app/loans/loans-view/loans-view.component-theme.scss'; // Custom material palette $mat-denim: ( @@ -53,3 +54,4 @@ $mifosx-app-theme: mat-light-theme($mifosx-app-primary, $mifosx-app-accent, $mif @include groups-view-component-theme($mifosx-app-theme); @include centers-view-component-theme($mifosx-app-theme); @include dashboard-component-theme($mifosx-app-theme); +@include loans-view-component-theme($mifosx-app-theme); diff --git a/src/theme/custom/pictonblue-yellowgreen.scss b/src/theme/custom/pictonblue-yellowgreen.scss index 24d857b0c..1d3067e79 100644 --- a/src/theme/custom/pictonblue-yellowgreen.scss +++ b/src/theme/custom/pictonblue-yellowgreen.scss @@ -4,6 +4,7 @@ @import '../../app/groups/groups-view/groups-view.component-theme.scss'; @import '../../app/centers/centers-view/centers-view.component-theme.scss'; @import '../../app/home/dashboard/dashboard.component-theme.scss'; +@import '../../app/loans/loans-view/loans-view.component-theme.scss'; // Custom material palette $mat-picton-blue: ( @@ -53,3 +54,4 @@ $mifosx-app-theme: mat-light-theme($mifosx-app-primary, $mifosx-app-accent, $mif @include groups-view-component-theme($mifosx-app-theme); @include centers-view-component-theme($mifosx-app-theme); @include dashboard-component-theme($mifosx-app-theme); +@include loans-view-component-theme($mifosx-app-theme);