feat: add loans view page (#661)

- Add loan account page
- Add general tab
- Add account details tab

Fixes: #219
This commit is contained in:
Muskan Khedia
2020-05-31 22:49:21 +05:30
committed by GitHub
parent 1539180a3a
commit 11cbe7a155
24 changed files with 1178 additions and 11 deletions
@@ -160,6 +160,11 @@ const routes: Routes = [
},
]
},
{
path: 'loans',
data: { title: extract('Loans'), breadcrumb: 'loans' },
loadChildren: '../loans/loans.module#LoansModule'
}
])
];
@@ -133,7 +133,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="openLoansColumns"></tr>
<tr mat-row *matRowDef="let row; columns: openLoansColumns;"></tr>
<tr mat-row *matRowDef="let row; columns: openLoansColumns;" [routerLink]="['/loans', row.id, 'general']" class="select-row"></tr>
</table>
<!-- Closed Loan Accounts -->
@@ -18,5 +18,10 @@
margin: 4px;
line-height: 25px;
}
.select-row:hover {
cursor: pointer;
}
}
}
@@ -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<Object> {
/**
* @param {LoansService} LoansService Loans service.
*/
constructor(private loansService: LoansService) { }
/**
* Returns the Loans data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const loanId = route.parent.paramMap.get('loanId');
return this.loansService.getLoanAccountDetails(loanId);
}
}
@@ -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<Object> {
/**
* @param {LoansService} LoansService Loans service.
*/
constructor(private loansService: LoansService) { }
/**
* Returns the Loans data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const loanId = route.paramMap.get('loanId');
return this.loansService.getLoanAccountDetails(loanId);
}
}
+31 -1
View File
@@ -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 { }
@@ -0,0 +1,161 @@
<div class="container">
<div fxLayout="row wrap" fxLayout.lt-md="column">
<div fxFlexFill>
<span fxFlex="50%">Repayment Strategy:</span>
<span fxFlex="50%">{{ loanDetails.transactionProcessingStrategyName }}</span>
</div>
<div fxFlexFill>
<span fxFlex="50%">Repayments:</span>
<span fxFlex="50%">{{loanDetails.numberOfRepayments}} every {{loanDetails.repaymentEvery}}&nbsp;{{loanDetails.repaymentFrequencyType.value}}
<span *ngIf="loanDetails.repaymentFrequencyType?.id == 2 && loanDetails.repaymentFrequencyNthDayType?.id != 0 && loanDetails.repaymentFrequencyDayOfWeekType?.id != 0">
&nbsp;on&nbsp;{{loanDetails.repaymentFrequencyNthDayType?.value}}&nbsp;{{loanDetails.repaymentFrequencyDayOfWeekType?.value}}
</span>
</span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Amortization </span>
<span fxFlex="50%"> {{loanDetails.amortizationType.value}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Equal Amortization </span>
<span fxFlex="50%"> {{loanDetails.isEqualAmortization}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Interest </span>
<span fxFlex="50%"> {{loanDetails.annualInterestRate}} per annum ({{loanDetails.interestRatePerPeriod}}%&nbsp; {{loanDetails.interestRateFrequencyType.value}}) - {{loanDetails.interestType.value}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Grace: On Principal Payment</span>
<span fxFlex="50%"> {{loanDetails.graceOnPrincipalPayment}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Grace: On Interest Payment</span>
<span fxFlex="50%"> {{loanDetails.graceOnInterestPayment}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Grace on Arrears Ageing</span>
<span fxFlex="50%"> {{loanDetails.graceOnArrearsAgeing}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Fund Source</span>
<span fxFlex="50%"> {{loanDetails.fundName}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Interest Free Period</span>
<span fxFlex="50%"> {{loanDetails.graceOnInterestCharged}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Interest Calculation Period</span>
<span fxFlex="50%"> {{loanDetails.interestCalculationPeriodType.value}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Allow Partial Interest Calculation with same as repayment</span>
<span fxFlex="50%"> {{loanDetails.allowPartialPeriodInterestCalcualtion}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Interest Type</span>
<span fxFlex="50%"> {{loanDetails.interestType.value}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Submitted on</span>
<span fxFlex="50%"> {{loanDetails.timeline.submittedOnDate | date }} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Approved on</span>
<span fxFlex="50%"> {{loanDetails.timeline.approvedOnDate | date }} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Disbursed on</span>
<span fxFlex="50%"> {{loanDetails.timeline.actualDisbursementDate | date }} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Matures on</span>
<span fxFlex="50%"> {{loanDetails.timeline.expectedMaturityDate | date }} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.canDefineInstallmentAmount">
<span fxFlex="50%"> Fixed EMI amount</span>
<span fxFlex="50%"> {{loanDetails.fixedEmiAmount | number}} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isTopup">
<span fxFlex="50%"> Is Topup Loan?</span>
<span fxFlex="50%"> {{loanDetails.isTopup}} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isTopup">
<span fxFlex="50%"> Loan closed with Topup </span>
<span fxFlex="50%"> <a href="#">{{loanDetails.closureLoanAccountNo}}</a> </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isTopup">
<span fxFlex="50%"> Topup closure amount</span>
<span fxFlex="50%"> {{loanDetails.topupAmount| number}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Recalculate Interest based on new terms</span>
<span fxFlex="50%"> {{loanDetails.isInterestRecalculationEnabled ? 'Yes' : 'No' }} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Days in year</span>
<span fxFlex="50%"> {{loanDetails.daysInYearType.value}} </span>
</div>
<div fxFlexFill>
<span fxFlex="50%"> Days in month</span>
<span fxFlex="50%"> {{loanDetails.daysInMonthType.value }} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isInterestRecalculationEnabled">
<span fxFlex="50%"> Interest recalculation compounding on</span>
<span fxFlex="50%"> {{loanDetails.interestRecalculationData.interestRecalculationCompoundingType.value}} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isInterestRecalculationEnabled">
<span fxFlex="50%"> Advance payments adjustment type</span>
<span fxFlex="50%"> {{loanDetails.interestRecalculationData.rescheduleStrategyType.value}} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isInterestRecalculationEnabled">
<span fxFlex="50%"> Frequency for recalculate Outstanding Principal</span>
<span fxFlex="50%"> {{loanDetails.interestRecalculationData.calendarData.humanReadable}} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isInterestRecalculationEnabled && loanDetails.interestRecalculationData.interestRecalculationCompoundingType.id !=0">
<span fxFlex="50%"> Frequency for compounding</span>
<span fxFlex="50%"> {{loanDetails.interestRecalculationData.compoundingCalendarData.humanReadable}} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isVariableInstallmentsAllowed">
<span fxFlex="50%"> Variable Installments Allowed</span>
<span fxFlex="50%"> {{loanDetails.isVariableInstallmentsAllowed}} </span>
</div>
<div fxFlexFill *ngIf="loanDetails.isVariableInstallmentsAllowed">
<span fxFlex="50%"> Gap between Installments:(Min</span>
<span fxFlex="50%"> {{loanDetails.minimumGap}}&nbsp;Days&nbsp;, Max:{{loanDetails.maximumGap}}&nbsp;Days) </span>
</div>
</div>
</div>
@@ -0,0 +1,7 @@
table {
width: 100%;
}
span {
margin: 0.5em 0;
}
@@ -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<AccountDetailsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AccountDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AccountDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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() {
}
}
+173
View File
@@ -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',
},
],
},
};
@@ -0,0 +1,200 @@
<div class="tab-container mat-typography">
<ng-container *ngIf="loanDetails.summary">
<h3>Performance History</h3>
<div fxLayout="row" fxLayoutGap="32px" class="performance-history-container">
<p>
# of Repayments :{{loanDetails?.numberOfRepayments}}
<p>
Maturity Date :{{loanDetails?.timeline.expectedMaturityDate | date}}
</p>
</div>
</ng-container>
<div *ngIf="loanDetails.summary">
<h3> Loan Summary </h3>
<table mat-table [dataSource]="dataSource">
<ng-container matColumnDef="Empty">
<th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let ele"> {{ ele.property }} </td>
</ng-container>
<ng-container matColumnDef="Original">
<th mat-header-cell *matHeaderCellDef> Original </th>
<td mat-cell *matCellDef="let ele"> {{ ele.original | number }} </td>
</ng-container>
<ng-container matColumnDef="Paid">
<th mat-header-cell *matHeaderCellDef> Paid </th>
<td mat-cell *matCellDef="let ele"> {{ ele.paid | number }} </td>
</ng-container>
<ng-container matColumnDef="Waived">
<th mat-header-cell *matHeaderCellDef> Waived </th>
<td mat-cell *matCellDef="let ele"> {{ ele.waived | number }} </td>
</ng-container>
<ng-container matColumnDef="Written Off">
<th mat-header-cell *matHeaderCellDef> Written Off </th>
<td mat-cell *matCellDef="let ele"> {{ ele.writtenOff | number }} </td>
</ng-container>
<ng-container matColumnDef="Outstanding">
<th mat-header-cell *matHeaderCellDef> Outstanding </th>
<td mat-cell *matCellDef="let ele"> {{ ele.outstanding | number }} </td>
</ng-container>
<ng-container matColumnDef="Over Due">
<th mat-header-cell *matHeaderCellDef> Over Due </th>
<td mat-cell *matCellDef="let ele"> {{ ele.overdue | number }} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="loanSummaryColumns"></tr>
<tr mat-row *matRowDef="let row; columns: loanSummaryColumns;"></tr>
</table>
</div>
<div *ngIf="loanDetails.summary">
<h3> Loan Details</h3>
<table mat-table [dataSource]="detailsDataSource">
<ng-container matColumnDef="Key">
<th mat-header-cell *matHeaderCellDef> Key </th>
<td mat-cell *matCellDef="let ele"> {{ ele.key }} </td>
</ng-container>
<ng-container matColumnDef="Value">
<th mat-header-cell *matHeaderCellDef> Value </th>
<td mat-cell *matCellDef="let ele">
<ng-container *ngIf="ele.key === 'Disbursement Date'" >
<span *ngIf="loanDetails.timeline.actualDisbursementDate"> {{loanDetails.timeline.actualDisbursementDate | date}} </span>
<span *ngIf="!loanDetails.timeline.actualDisbursementDate"> Not Available </span>
</ng-container>
<ng-container *ngIf="ele.key === 'Loan Purpose'">
<span *ngIf="loanDetails.loanPurchaseName"> {{loanDetails.loanPurchaseName}} </span>
<span *ngIf="!loanDetails.loanPurchaseName"> Not Available </span>
</ng-container>
<ng-container *ngIf="ele.key === 'Loan Officer'">
<span *ngIf="!loanDetails.loanOfficerName"> Unassigned </span>
<span *ngIf="loanDetails.loanOfficerName"> {{loanDetails.loanOfficerName}} &nbsp;
<span *ngIf="loanDetails.loanOfficerName">
<!-- <a ng-click="clickEvent('unassignloanofficer', loanDetails.id)" *mifosxHasPermission = "'REMOVELOANOFFICER_LOAN'"><i
class="fa fa-times"></i></a> -->
</span>
</span>
</ng-container>
<ng-container *ngIf="ele.key === 'Currency'">
<span> {{loanDetails.currency.name }} {{loanDetails.currency.code}} </span>
</ng-container>
<ng-container *ngIf="ele.key === 'External Id'">
<span *ngIf="loanDetails.externalId"> {{loanDetails.externalId}} </span>
<span *ngIf="!loanDetails.externalId"> Not Available </span>
</ng-container>
<ng-container *ngIf="ele.key === 'Proposed Amount' || ele.key === 'Approved Amount' || ele.key === 'Disburse Amount'">
{{ ele.value }}
</ng-container>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="loanDetailsColumns"></tr>
<tr mat-row *matRowDef="let row; columns: loanDetailsColumns;"></tr>
</table>
</div>
<div *ngIf="!loanDetails.summary">
<h3> Loan Details </h3>
<table mat-table [dataSource]="detailsDataSource">
<ng-container matColumnDef="Key">
<th mat-header-cell *matHeaderCellDef> Key </th>
<td mat-cell *matCellDef="let ele"> {{ ele.key }} </td>
</ng-container>
<ng-container matColumnDef="Value">
<th mat-header-cell *matHeaderCellDef> Value </th>
<td mat-cell *matCellDef="let ele">
<ng-container *ngIf="ele.key === 'Disbursement Date'" >
<span *ngIf="loanDetails.timeline.actualDisbursementDate"> {{loanDetails.timeline.actualDisbursementDate | date}} </span>
<span *ngIf="!loanDetails.timeline.actualDisbursementDate"> Not Available </span>
</ng-container>
<ng-container *ngIf="ele.key === 'Loan Officer'">
<span *ngIf="!loanDetails.loanOfficerName"> Unassigned </span>
<span *ngIf="loanDetails.loanOfficerName"> {{loanDetails.loanOfficerName}} &nbsp;
<span *ngIf="loanDetails.loanOfficerName">
<!-- <a ng-click="clickEvent('unassignloanofficer', loanDetails.id)" *mifosxHasPermission = "'REMOVELOANOFFICER_LOAN'"><i
class="fa fa-times"></i></a> -->
</span>
</span>
</ng-container>
<ng-container *ngIf="ele.key === 'Currency'">
<span> {{loanDetails.currency.name }} </span>
</ng-container>
<ng-container *ngIf="ele.key === 'External Id'">
<span *ngIf="loanDetails.externalId"> {{loanDetails.externalId}} </span>
<span *ngIf="!loanDetails.externalId"> Not Available </span>
</ng-container>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="loanDetailsColumns"></tr>
<tr mat-row *matRowDef="let row; columns: loanDetailsColumns;"></tr>
</table>
</div>
<div *ngIf="!loanDetails.summary">
<h3> Loan Purpose </h3>
<div class="container">
<div fxLayout="row wrap" fxLayout.lt-md="column">
<div fxFlexFill>
<span fxFlex="50%">Loan Purpose:</span>
<span fxFlex="50%" *ngIf="loanDetails.loanPurposeName">
{{ loanDetails.loanPurposeName }}
</span>
<span fxFlex="50%" *ngIf="!loanDetails.loanPurposeName">
Not Provided
</span>
</div>
<div fxFlexFill>
<span fxFlex="50%">Proposed Amount:</span>
<span fxFlex="50%">{{ loanDetails.proposedPrincipal | number}}</span>
</div>
<div fxFlexFill *ngIf="showApprovedAmountBasedOnStatus()">
<span fxFlex="50%">Approved Amount:</span>
<span fxFlex="50%">{{ loanDetails.approvedPrincipal | number }}</span>
</div>
<div fxFlexFill *ngIf="showDisbursedAmountBasedOnStatus()">
<span fxFlex="50%">Disburse Amount:</span>
<span fxFlex="50%">{{ loanDetails.principal | number }}</span>
</div>
<div fxFlexFill>
<span fxFlex="50%">Arrears By:</span>
<span fxFlex="50%"> Not Provided</span>
</div>
</div>
</div>
</div>
</div>
@@ -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;
}
@@ -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<GeneralTabComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ GeneralTabComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(GeneralTabComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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<any>;
detailsDataSource: MatTableDataSource<any>;
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;
};
}
@@ -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);
}
}
}
}
@@ -0,0 +1,61 @@
<mat-card class="loan-card">
<mat-card-header fxLayout="column" class="header">
<mat-card-title-group class="header-title-group">
<div class="mat-typography loan-card-title">
<mat-card-title>
<div fxLayout="row" fxLayout.lt-md="column">
<div fxFlex="50%">
<h3>
<i class="fa fa-stop" matTooltip="{{ loanDetailsData.status.value }}" [ngClass]="loanDetailsData.inArrears?'status-active-overdue':(loanDetailsData.status.code|statusLookup)"></i>
Loan Product: {{loanDetailsData.loanProductName}}(#{{loanDetailsData.accountNo}})
<br/>
<span *ngIf="loanDetailsData.clientName" > Client Name: {{loanDetailsData.clientName}} </span> <br/>
<span *ngIf="loanDetailsData.group" > Group Name: {{loanDetailsData.group.name}} </span>
</h3>
</div>
<div *ngIf="loanDetailsData.summary" class="loansOverview mat-typography" fxFlex="50%">
<h3> Loan Account OverView </h3>
<span>Current Balance: {{loanDetailsData.currency.displaySymbol}} {{loanDetailsData.summary.totalOutstanding | number}}</span><br>
<span>Arrears By: {{loanDetailsData.summary.totalOverdue | number}}
<span *ngIf="!(loanDetailsData.summary.totalOverdue>=0)">Not Provided</span>
</span><br>
</div>
</div>
</mat-card-title>
</div>
</mat-card-title-group>
<mat-card-actions class="loan-actions">
<ng-container *ngFor="let item of buttons.singlebuttons" class="loan-span">
<button mat-raised-button *mifosxHasPermission="item.taskPermissionName">
<i class="{{item.icon}}"></i> {{item.name}} </button>
</ng-container>
<ng-container *ngIf="!(buttons.options === undefined)" class="loan-span">
<button mat-raised-button [matMenuTriggerFor]="More">More</button>
<mat-menu #More="matMenu">
<span *ngFor="let item of buttons.options">
<button mat-menu-item *mifosxHasPermission="item.taskPermissionName">{{item.name}}</button>
</span>
</mat-menu>
</ng-container>
</mat-card-actions>
</mat-card-header>
<mat-card-content>
<nav mat-tab-nav-bar class="navigation-tabs">
<a mat-tab-link [routerLink]="['./general']" routerLinkActive #general="routerLinkActive" [active]="general.isActive">
General
</a>
<a mat-tab-link [routerLink]="['./accountdetail']" routerLinkActive #accountdetail="routerLinkActive" [active]="accountdetail.isActive">
Account Details
</a>
</nav>
<router-outlet></router-outlet>
</mat-card-content>
</mat-card>
@@ -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;
}
@@ -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<LoansViewComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoansViewComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoansViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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);
}
}
+14 -8
View File
@@ -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 {}
+5 -1
View File
@@ -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<any>}
@@ -29,4 +29,8 @@ export class LoansService {
return this.http.post(`/loans/${loanId}/${resourceType}`, loanCharge);
}
getLoanAccountDetails(loanId: string) {
return this.http.get(`/loans/${loanId}`);
}
}
+2
View File
@@ -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);
@@ -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);