From 3123f3715558835358beb7f31c0a5fd8f25d622a Mon Sep 17 00:00:00 2001 From: Karan Takalkar Date: Thu, 11 Jun 2020 16:03:00 +0530 Subject: [PATCH] feat: add view savings account page (#862) - Transactions Tab - Charges Tab - Standing Instructions Tab Fixes: #221 --- src/app/clients/clients-routing.module.ts | 11 +- .../general-tab/general-tab.component.html | 2 +- src/app/groups/groups-routing.module.ts | 73 +++-- .../general-tab/general-tab.component.html | 2 +- .../groups-view/groups-view.component.html | 2 +- .../groups-view/groups-view.component.scss | 2 +- .../client-trends-bar.component.scss | 3 +- .../savings-account-view.resolver.ts | 32 ++ .../charges-tab/charges-tab.component.html | 106 +++++++ .../charges-tab/charges-tab.component.scss | 19 ++ .../charges-tab/charges-tab.component.spec.ts | 25 ++ .../charges-tab/charges-tab.component.ts | 81 +++++ .../savings-account-view.component-theme.scss | 16 + .../savings-account-view.component.html | 289 ++++++++++++++++++ .../savings-account-view.component.scss | 53 ++++ .../savings-account-view.component.spec.ts | 25 ++ .../savings-account-view.component.ts | 84 +++++ .../savings-buttons.config.ts | 142 +++++++++ .../standing-instructions-tab.component.html | 70 +++++ .../standing-instructions-tab.component.scss | 19 ++ ...tanding-instructions-tab.component.spec.ts | 25 ++ .../standing-instructions-tab.component.ts | 70 +++++ .../transactions-tab.component.html | 63 ++++ .../transactions-tab.component.scss | 19 ++ .../transactions-tab.component.spec.ts | 25 ++ .../transactions-tab.component.ts | 60 ++++ src/app/savings/savings-routing.module.ts | 36 ++- src/app/savings/savings.module.ts | 14 +- src/app/savings/savings.service.ts | 35 ++- .../images/savings_account_placeholder.png | Bin 0 -> 27274 bytes src/theme/custom/denim-yellowgreen.scss | 2 + src/theme/custom/pictonblue-yellowgreen.scss | 2 + 32 files changed, 1353 insertions(+), 54 deletions(-) create mode 100644 src/app/savings/common-resolvers/savings-account-view.resolver.ts create mode 100644 src/app/savings/savings-account-view/charges-tab/charges-tab.component.html create mode 100644 src/app/savings/savings-account-view/charges-tab/charges-tab.component.scss create mode 100644 src/app/savings/savings-account-view/charges-tab/charges-tab.component.spec.ts create mode 100644 src/app/savings/savings-account-view/charges-tab/charges-tab.component.ts create mode 100644 src/app/savings/savings-account-view/savings-account-view.component-theme.scss create mode 100644 src/app/savings/savings-account-view/savings-account-view.component.html create mode 100644 src/app/savings/savings-account-view/savings-account-view.component.scss create mode 100644 src/app/savings/savings-account-view/savings-account-view.component.spec.ts create mode 100644 src/app/savings/savings-account-view/savings-account-view.component.ts create mode 100644 src/app/savings/savings-account-view/savings-buttons.config.ts create mode 100644 src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.html create mode 100644 src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.scss create mode 100644 src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.spec.ts create mode 100644 src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.ts create mode 100644 src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.html create mode 100644 src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.scss create mode 100644 src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.spec.ts create mode 100644 src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.ts create mode 100644 src/assets/images/savings_account_placeholder.png diff --git a/src/app/clients/clients-routing.module.ts b/src/app/clients/clients-routing.module.ts index 26a9bd070..9e3de6735 100644 --- a/src/app/clients/clients-routing.module.ts +++ b/src/app/clients/clients-routing.module.ts @@ -49,11 +49,6 @@ const routes: Routes = [ path: '', component: ClientsComponent, }, - { - path: ':clientId/savingsaccounts', - data: { routeParamBreadcrumb: 'clientId'}, - loadChildren: '../savings/savings.module#SavingsModule' - }, { path: ':clientId', component: ClientsViewComponent, @@ -177,7 +172,11 @@ const routes: Routes = [ path: 'loans', data: { title: extract('Loans'), breadcrumb: 'loans', routeParamBreadcrumb: false }, loadChildren: '../loans/loans.module#LoansModule' - } + }, + { + path: 'savingsaccounts', + loadChildren: '../savings/savings.module#SavingsModule' + }, ] } ] 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 8478ff410..62e427624 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 @@ -243,7 +243,7 @@ - + diff --git a/src/app/groups/groups-routing.module.ts b/src/app/groups/groups-routing.module.ts index 6f9c7314e..8949ef702 100644 --- a/src/app/groups/groups-routing.module.ts +++ b/src/app/groups/groups-routing.module.ts @@ -47,48 +47,57 @@ const routes: Routes = [ }, { path: ':groupId', - component: GroupsViewComponent, data: { title: extract('View Group'), routeParamBreadcrumb: 'groupId' }, - resolve: { - groupViewData: GroupViewResolver, - groupDatatables: GroupDatatablesResolver - }, children: [ { - path: 'general', - component: GeneralTabComponent, - data: { title: extract('General'), breadcrumb: 'General', routeParamBreadcrumb: false }, + path: '', + component: GroupsViewComponent, resolve: { - groupAccountsData: GroupAccountsResolver, - groupSummary: GroupSummaryResolver - } - }, - { - path: 'notes', - component: NotesTabComponent, - data: { title: extract('Notes'), breadcrumb: 'Notes', routeParamBreadcrumb: false }, - resolve: { - groupNotes: GroupNotesResolver - } - }, - { - path: 'committee', - component: CommitteeTabComponent, - data: { title: extract('Committee'), breadcrumb: 'Committee', routeParamBreadcrumb: false } - }, - { - path: 'datatables', + groupViewData: GroupViewResolver, + groupDatatables: GroupDatatablesResolver + }, children: [ { - path: ':datatableName', - component: DatatableTabsComponent, - data: { title: extract('View Data Table'), routeParamBreadcrumb: 'datatableName' }, + path: 'general', + component: GeneralTabComponent, + data: { title: extract('General'), breadcrumb: 'General', routeParamBreadcrumb: false }, resolve: { - groupDatatable: GroupDatatableResolver + groupAccountsData: GroupAccountsResolver, + groupSummary: GroupSummaryResolver } + }, + { + path: 'notes', + component: NotesTabComponent, + data: { title: extract('Notes'), breadcrumb: 'Notes', routeParamBreadcrumb: false }, + resolve: { + groupNotes: GroupNotesResolver + } + }, + { + path: 'committee', + component: CommitteeTabComponent, + data: { title: extract('Committee'), breadcrumb: 'Committee', routeParamBreadcrumb: false } + }, + { + path: 'datatables', + children: [ + { + path: ':datatableName', + component: DatatableTabsComponent, + data: { title: extract('View Data Table'), routeParamBreadcrumb: 'datatableName' }, + resolve: { + groupDatatable: GroupDatatableResolver + } + } + ] } ] - } + }, + { + path: 'savingsaccounts', + loadChildren: '../savings/savings.module#SavingsModule' + }, ] } ] diff --git a/src/app/groups/groups-view/general-tab/general-tab.component.html b/src/app/groups/groups-view/general-tab/general-tab.component.html index 669b2ac33..715c53f4d 100644 --- a/src/app/groups/groups-view/general-tab/general-tab.component.html +++ b/src/app/groups/groups-view/general-tab/general-tab.component.html @@ -255,7 +255,7 @@ - + diff --git a/src/app/groups/groups-view/groups-view.component.html b/src/app/groups/groups-view/groups-view.component.html index c2b97022c..3012d916e 100644 --- a/src/app/groups/groups-view/groups-view.component.html +++ b/src/app/groups/groups-view/groups-view.component.html @@ -7,7 +7,7 @@
+ src="assets/images/group_placeholder.png">
diff --git a/src/app/groups/groups-view/groups-view.component.scss b/src/app/groups/groups-view/groups-view.component.scss index b3bd5c5cb..35c51f1fa 100644 --- a/src/app/groups/groups-view/groups-view.component.scss +++ b/src/app/groups/groups-view/groups-view.component.scss @@ -29,7 +29,7 @@ background-color: #0a6aac; p { color: white; - padding-inline: 10px; + padding: 0 10px; } } } diff --git a/src/app/home/dashboard/client-trends-bar/client-trends-bar.component.scss b/src/app/home/dashboard/client-trends-bar/client-trends-bar.component.scss index 8f272bcbc..4b7f4704d 100644 --- a/src/app/home/dashboard/client-trends-bar/client-trends-bar.component.scss +++ b/src/app/home/dashboard/client-trends-bar/client-trends-bar.component.scss @@ -7,7 +7,6 @@ } } #timescale { - margin-block: 2%; - margin-inline: 35%; + margin: 2% 35%; } } diff --git a/src/app/savings/common-resolvers/savings-account-view.resolver.ts b/src/app/savings/common-resolvers/savings-account-view.resolver.ts new file mode 100644 index 000000000..b01f4346c --- /dev/null +++ b/src/app/savings/common-resolvers/savings-account-view.resolver.ts @@ -0,0 +1,32 @@ +/** Angular Imports */ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { SavingsService } from '../savings.service'; + +/** + * Savings Account data resolver. + */ +@Injectable() +export class SavingsAccountViewResolver implements Resolve { + + /** + * @param {SavingsService} SavingsService Savings service. + */ + constructor(private savingsService: SavingsService) { } + + /** + * Returns the Savings Account data. + * @param {ActivatedRouteSnapshot} route Route Snapshot + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const savingAccountId = route.paramMap.get('savingAccountId'); + return this.savingsService.getSavingsAccountData(savingAccountId); + } + +} diff --git a/src/app/savings/savings-account-view/charges-tab/charges-tab.component.html b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.html new file mode 100644 index 000000000..126e58e58 --- /dev/null +++ b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.html @@ -0,0 +1,106 @@ +
+ +
+
+

All Charges

+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name {{ charge.name }} Fee/Penalty {{ charge.penalty === true ? 'Penalty' : 'Fee' }} Payment Due At {{ charge.chargeTimeType.value }} Due As Of {{ charge.dueDate | date }} Repeats On (M/d) {{ charge.feeOnMonthDay ? charge.feeOnMonthDay.join('/') : '' }} Calculation Type {{charge.chargeCalculationType.value}} Due {{charge.currency.displaySymbol}} {{charge.amount}} Paid {{charge.currency.displaySymbol}} {{charge.amountPaid}} Waived {{charge.currency.displaySymbol}} {{charge.amountWaived}} Outstanding {{charge.currency.displaySymbol}} {{charge.amountOutstanding}} Actions + + + + + + + + + + + +
+ +
+ +
diff --git a/src/app/savings/savings-account-view/charges-tab/charges-tab.component.scss b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.scss new file mode 100644 index 000000000..aaafd7b18 --- /dev/null +++ b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.scss @@ -0,0 +1,19 @@ +.tab-container { + padding: 1%; + margin: 1%; + h3 { + margin:1% auto; + } + .action-button { + margin-left: auto; + } + table { + width: 100%; + .account-action-button{ + min-width: 26px; + padding: 0 6px; + margin: 4px; + line-height: 25px; + } + } +} diff --git a/src/app/savings/savings-account-view/charges-tab/charges-tab.component.spec.ts b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.spec.ts new file mode 100644 index 000000000..1ef118215 --- /dev/null +++ b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ChargesTabComponent } from './charges-tab.component'; + +describe('ChargesTabComponent', () => { + let component: ChargesTabComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ChargesTabComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ChargesTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/savings/savings-account-view/charges-tab/charges-tab.component.ts b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.ts new file mode 100644 index 000000000..fd725011b --- /dev/null +++ b/src/app/savings/savings-account-view/charges-tab/charges-tab.component.ts @@ -0,0 +1,81 @@ +/** Angular Imports */ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatTableDataSource, MatTable } from '@angular/material'; +import { ActivatedRoute } from '@angular/router'; + +/** + * Charges Tab Component + */ +@Component({ + selector: 'mifosx-charges-tab', + templateUrl: './charges-tab.component.html', + styleUrls: ['./charges-tab.component.scss'] +}) +export class ChargesTabComponent implements OnInit { + + /** Savings Account Data */ + savingsAccountData: any; + /** Charges Data */ + chargesData: any[]; + /** Data source for charges table. */ + dataSource: MatTableDataSource; + /** Toggles Charges Table */ + showInactiveCharges = false; + /** Columns to be displayed in charges table. */ + displayedColumns: string[] = [ + 'name', + 'feeOrPenalty', + 'paymentDueAt', + 'dueAsOf', + 'repeatsOn', + 'calculationType', + 'due', + 'paid', + 'waived', + 'outstanding', + 'actions' + ]; + + /** Charges Table Reference */ + @ViewChild('chargesTable') chargesTableRef: MatTable; + + /** + * Retrieves Savings Account Data from `resolve`. + * @param {ActivatedRoute} route Activated Route. + */ + constructor(private route: ActivatedRoute) { + this.route.parent.data.subscribe((data: { savingsAccountData: any }) => { + this.savingsAccountData = data.savingsAccountData; + this.chargesData = this.savingsAccountData.charges; + }); + } + + ngOnInit() { + const activeCharges = this.chargesData ? this.chargesData.filter(charge => charge.isActive) : []; + this.dataSource = new MatTableDataSource(activeCharges); + } + + /** + * Toggles datasource for active/inactive charges. + */ + toggleCharges() { + this.showInactiveCharges = !this.showInactiveCharges; + if (!this.showInactiveCharges) { + const activeCharges = this.chargesData.filter(charge => charge.isActive); + this.dataSource.data = activeCharges; + } else { + const inActiveCharges = this.chargesData.filter(charge => !charge.isActive); + this.dataSource.data = inActiveCharges; + } + this.chargesTableRef.renderRows(); + } + + /** + * Checks if charge is recurring. + * @param {any} charge Charge + */ + isRecurringCharge(charge: any) { + return charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee' || charge.chargeTimeType.value === 'Weekly Fee'; + } + +} diff --git a/src/app/savings/savings-account-view/savings-account-view.component-theme.scss b/src/app/savings/savings-account-view/savings-account-view.component-theme.scss new file mode 100644 index 000000000..c72c270e7 --- /dev/null +++ b/src/app/savings/savings-account-view/savings-account-view.component-theme.scss @@ -0,0 +1,16 @@ +@import "~@angular/material/theming"; + +@mixin savings-account-view-component-theme($theme) { + $primary: map-get($theme, primary); + mifosx-savings-account-view { + .savings-account-card { + .header { + background-color: mat-color($primary, 500); + } + .table-headers { + background-color: mat-color($primary, 500); + color: mat-contrast($primary, 500); + } + } + } +} diff --git a/src/app/savings/savings-account-view/savings-account-view.component.html b/src/app/savings/savings-account-view/savings-account-view.component.html new file mode 100644 index 000000000..59ba0b0fd --- /dev/null +++ b/src/app/savings/savings-account-view/savings-account-view.component.html @@ -0,0 +1,289 @@ + diff --git a/src/app/savings/savings-account-view/savings-account-view.component.scss b/src/app/savings/savings-account-view/savings-account-view.component.scss new file mode 100644 index 000000000..56beb2cfc --- /dev/null +++ b/src/app/savings/savings-account-view/savings-account-view.component.scss @@ -0,0 +1,53 @@ +.savings-account-card { + margin: 0 auto; + max-width: 80rem; + width: 90%; + padding: 0; + .header { + padding: 1%; + .header-title-group { + .account-card-title { + color: white; + width: 90%; + p { + color: white; + } + } + } + .profile-image-container { + margin: 1%; + .profile-image { + object-fit: cover; + border-radius: 20px; + } + } + .account-actions { + align-self: flex-end; + margin: 0 1%; + i { + margin-bottom: 2px; + margin-right: 4px; + } + } + } + .navigation-tabs { + background-color: #f2f2f2; + overflow: auto; + } + .content { + .savings-account-tables { + padding: 1%; + margin: 1%; + .table-headers { + margin: 0; + padding: 6px; + } + td { + padding: 3px; + } + } + } + i, img:hover { + cursor: pointer; + } +} diff --git a/src/app/savings/savings-account-view/savings-account-view.component.spec.ts b/src/app/savings/savings-account-view/savings-account-view.component.spec.ts new file mode 100644 index 000000000..74fa6f85d --- /dev/null +++ b/src/app/savings/savings-account-view/savings-account-view.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingsAccountViewComponent } from './savings-account-view.component'; + +describe('SavingsAccountViewComponent', () => { + let component: SavingsAccountViewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingsAccountViewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingsAccountViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/savings/savings-account-view/savings-account-view.component.ts b/src/app/savings/savings-account-view/savings-account-view.component.ts new file mode 100644 index 000000000..2dd2a2f46 --- /dev/null +++ b/src/app/savings/savings-account-view/savings-account-view.component.ts @@ -0,0 +1,84 @@ +/** Angular Imports */ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { SavingsButtonsConfiguration } from './savings-buttons.config'; + +/** + * Savings Account View Component + */ +@Component({ + selector: 'mifosx-savings-account-view', + templateUrl: './savings-account-view.component.html', + styleUrls: ['./savings-account-view.component.scss'] +}) +export class SavingsAccountViewComponent implements OnInit { + + /** Savings Account Data */ + savingsAccountData: any; + /** Button Configurations */ + buttonConfig: SavingsButtonsConfiguration; + + /** + * Fetches savings account data from `resolve` + * @param {ActivatedRoute} route Activated Route + */ + constructor(private route: ActivatedRoute) { + this.route.data.subscribe((data: { savingsAccountData: any }) => { + this.savingsAccountData = data.savingsAccountData; + }); + } + + ngOnInit() { + this.setConditionalButtons(); + } + + /** + * Adds options to button config. conditionaly. + */ + setConditionalButtons() { + const status = this.savingsAccountData.status.value; + this.buttonConfig = new SavingsButtonsConfiguration(status); + if (this.savingsAccountData.clientId) { + this.buttonConfig.addOption({ + name: 'Transfer Funds', + taskPermissionName: 'CREATE_ACCOUNTTRANSFER' + }); + } + if (!this.savingsAccountData.fieldOfficerId) { + this.buttonConfig.addOption({ + name: 'Assign Staff', + taskPermissionName: 'UPDATESAVINGSOFFICER_SAVINGSACCOUNT' + }); + } else { + this.buttonConfig.addOption({ + name: 'Unassign Staff', + taskPermissionName: 'REMOVESAVINGSOFFICER_SAVINGSACCOUNT' + }); + } + if (this.savingsAccountData.charges) { + const charges: any[] = this.savingsAccountData.charges; + charges.forEach((charge: any) => { + if (charge.name === 'Annual fee - INR') { + this.buttonConfig.addOption({ + name: 'Apply Anuual Fees', + taskPermissionName: 'APPLYANNUALFEE_SAVINGSACCOUNT' + }); + } + }); + } + if (this.savingsAccountData.taxGroup) { + if (this.savingsAccountData.withHoldTax) { + this.buttonConfig.addOption({ + name: 'Disable Withhold Tax', + taskPermissionName: 'UPDATEWITHHOLDTAX_SAVINGSACCOUNT' + }); + } else { + this.buttonConfig.addOption({ + name: 'Enable Withhold Tax', + taskPermissionName: 'UPDATEWITHHOLDTAX_SAVINGSACCOUNT' + }); + } + } + } + +} diff --git a/src/app/savings/savings-account-view/savings-buttons.config.ts b/src/app/savings/savings-account-view/savings-buttons.config.ts new file mode 100644 index 000000000..85eea50b4 --- /dev/null +++ b/src/app/savings/savings-account-view/savings-buttons.config.ts @@ -0,0 +1,142 @@ +/** Savings Account Buttons Configuration */ +export class SavingsButtonsConfiguration { + + optionArray: { + name: string, + taskPermissionName: string, + action?: Function + }[]; + + buttonsArray: { + name: string, + icon: string, + taskPermissionName: string, + action?: Function + }[]; + + constructor(status: string) { + this.setOptions(status); + this.setButtons(status); + } + + get singleButtons() { + return this.buttonsArray; + } + + get options() { + return this.optionArray; + } + + setButtons(status: string) { + switch (status) { + case 'Active': + this.buttonsArray = [ + { + name: 'Post Interest As On', + icon: 'fa fa-arrow-right', + taskPermissionName: 'POSTINTEREST_SAVINGSACCOUNT', + }, + { + name: 'Deposit', + icon: 'fa fa-arrow-up', + taskPermissionName: 'DEPOSIT_SAVINGSACCOUNT' + }, + { + name: 'Withdraw', + icon: 'fa fa-arrow-down', + taskPermissionName: 'WITHDRAW_SAVINGSACCOUNT' + }, + { + name: 'Calculate Interest', + icon: 'fa fa-table', + taskPermissionName: 'CALCULATEINTEREST_SAVINGSACCOUNT' + } + ]; + break; + case 'Submitted and pending approval': + this.buttonsArray = [ + { + name: 'Modify Application', + icon: 'fa fa-pencil ', + taskPermissionName: 'UPDATE_SAVINGSACCOUNT' + }, + { + name: 'Approve', + icon: 'fa fa-check', + taskPermissionName: 'APPROVE_SAVINGSACCOUNT' + } + ]; + break; + case 'Approved': + this.buttonsArray = [ + { + name: 'Undo Approval', + icon: 'fa fa-undo', + taskPermissionName: 'APPROVALUNDO_SAVINGSACCOUNT' + }, + { + name: 'Activate', + icon: 'fa fa-check', + taskPermissionName: 'ACTIVATE_SAVINGSACCOUNT' + }, + { + name: 'Add Charge', + icon: 'fa fa-plus', + taskPermissionName: 'CREATE_SAVINGSACCOUNTCHARGE' + } + ]; + break; + default: + this.buttonsArray = []; + } + } + + setOptions(status: string) { + switch (status) { + case 'Active': + this.optionArray = [ + { + name: 'Post Interest', + taskPermissionName: 'POSTINTEREST_SAVINGSACCOUNT' + }, + { + name: 'Add Charge', + taskPermissionName: 'CREATE_SAVINGSACCOUNTCHARGE' + }, + { + name: 'Close', + taskPermissionName: 'CLOSE_SAVINGSACCOUNT' + } + ]; + break; + case 'Submitted and pending approval': + this.optionArray = [ + { + name: 'Reject', + taskPermissionName: 'REJECT_SAVINGSACCOUNT' + }, + { + name: 'Withdraw', + taskPermissionName: 'WITHDRAW_SAVINGSACCOUNT' + }, + { + name: 'Add Charge', + taskPermissionName: 'CREATE_SAVINGSACCOUNTCHARGE' + }, + { + name: 'Delete', + taskPermissionName: 'DELETE_SAVINGSACCOUNT' + } + ]; + break; + case 'Approved': + default: + this.optionArray = []; + } + } + + addOption(option: {name: string, taskPermissionName: string}) { + this.optionArray.push(option); + } + +} diff --git a/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.html b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.html new file mode 100644 index 000000000..709466062 --- /dev/null +++ b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.html @@ -0,0 +1,70 @@ +
+ +
+

All Standing Instructions

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Client {{instruction.fromClient.displayName}}-{{instruction.fromClient.id}} From Account {{instruction.fromAccount.accountNo}} ({{instruction.fromAccountType.value}}) Beneficiary {{instruction.toClient.displayName}} To Account {{instruction.toAccount.accountNo}} ({{instruction.toAccountType.value}}) Amount {{instruction.instructionType.value}}/{{instruction.amount}} Validity {{instruction.validFrom | date}} to {{instruction.validTill | date}} Actions + + + + + + + +
+ +
+ +
diff --git a/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.scss b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.scss new file mode 100644 index 000000000..aaafd7b18 --- /dev/null +++ b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.scss @@ -0,0 +1,19 @@ +.tab-container { + padding: 1%; + margin: 1%; + h3 { + margin:1% auto; + } + .action-button { + margin-left: auto; + } + table { + width: 100%; + .account-action-button{ + min-width: 26px; + padding: 0 6px; + margin: 4px; + line-height: 25px; + } + } +} diff --git a/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.spec.ts b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.spec.ts new file mode 100644 index 000000000..f3997e094 --- /dev/null +++ b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StandingInstructionsTabComponent } from './standing-instructions-tab.component'; + +describe('StandingInstructionsTabComponent', () => { + let component: StandingInstructionsTabComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StandingInstructionsTabComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StandingInstructionsTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.ts b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.ts new file mode 100644 index 000000000..5d6421466 --- /dev/null +++ b/src/app/savings/savings-account-view/standing-instructions-tab/standing-instructions-tab.component.ts @@ -0,0 +1,70 @@ +/** Angular Imports */ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatTableDataSource, MatTable } from '@angular/material'; +import { ActivatedRoute } from '@angular/router'; + +/** Custom Services */ +import { SavingsService } from 'app/savings/savings.service'; + +/** + * Savings Standing Instructions Tab + */ +@Component({ + selector: 'mifosx-standing-instructions-tab', + templateUrl: './standing-instructions-tab.component.html', + styleUrls: ['./standing-instructions-tab.component.scss'] +}) +export class StandingInstructionsTabComponent implements OnInit { + + /** Savings Data */ + savingsData: any; + /** Instructions Data */ + instructionsData: any[]; + /** Data source for instructions table. */ + dataSource = new MatTableDataSource(); + /** Columns to be displayed in instructions table. */ + displayedColumns: string[] = [ + 'client', + 'fromAccount', + 'beneficiary', + 'toAccount', + 'amount', + 'validity', + 'actions' + ]; + + /** Instruction Table Reference */ + @ViewChild('instructionsTable') instructionTableRef: MatTable; + + /** + * Retrieves Savings Account Data from `resolve`. + * @param {ActivatedRoute} route Activated Route. + */ + constructor(private route: ActivatedRoute, + private savingsService: SavingsService) { + this.route.parent.data.subscribe((data: { savingsAccountData: any }) => { + this.savingsData = data.savingsAccountData; + }); + } + + ngOnInit() { + this.getStandingInstructions(); + } + + /** + * Retrieves standing instructions and initializes instructions table. + */ + getStandingInstructions() { + const clientId = this.savingsData.clientId; + const clientName = this.savingsData.clientName; + const accountId = this.savingsData.id; + const locale = 'en'; + const dateFormat = 'dd MMMM yyyy'; + this.savingsService.getStandingInstructions(clientId, clientName, accountId, locale, dateFormat).subscribe((response: any) => { + this.instructionsData = response.pageItems; + this.dataSource.data = this.instructionsData; + this.instructionTableRef.renderRows(); + }); + } + +} diff --git a/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.html b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.html new file mode 100644 index 000000000..1be786c3b --- /dev/null +++ b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.html @@ -0,0 +1,63 @@ +
+ +
+
+

All Transactions

+
+
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID {{ transaction.id }} Transaction Date {{ transaction.date | date }} Transaction Type {{ transaction.transactionType.value }} Debit {{ isDebit(transaction.transactionType) ? transaction.amount : 'N/A'}} Credit {{ !isDebit(transaction.transactionType) ? transaction.amount : 'N/A' }} Balance {{ transaction.runningBalance }} View Reciept + +
+ +
+ +
diff --git a/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.scss b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.scss new file mode 100644 index 000000000..ad2bc9bd7 --- /dev/null +++ b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.scss @@ -0,0 +1,19 @@ +.tab-container { + padding: 1%; + margin: 1%; + h3 { + margin:1% auto; + } + .action-button { + margin-left: auto; + } + table { + width: 100%; + .account-action-button{ + min-width: 26px; + padding: 0 0 3px 0; + margin: 0 10%; + line-height: 25px; + } + } +} diff --git a/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.spec.ts b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.spec.ts new file mode 100644 index 000000000..a67fc2279 --- /dev/null +++ b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TransactionsTabComponent } from './transactions-tab.component'; + +describe('TransactionsTabComponent', () => { + let component: TransactionsTabComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TransactionsTabComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TransactionsTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.ts b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.ts new file mode 100644 index 000000000..d6d68d3ae --- /dev/null +++ b/src/app/savings/savings-account-view/transactions-tab/transactions-tab.component.ts @@ -0,0 +1,60 @@ +/** Angular Imports */ +import { Component, OnInit } from '@angular/core'; +import { MatTableDataSource } from '@angular/material'; +import { ActivatedRoute } from '@angular/router'; + +/** + * Transactions Tab Component. + */ +@Component({ + selector: 'mifosx-transactions-tab', + templateUrl: './transactions-tab.component.html', + styleUrls: ['./transactions-tab.component.scss'] +}) +export class TransactionsTabComponent implements OnInit { + + /** Savings Account Status */ + status: any; + /** Transactions Data */ + transactionsData: any; + /** Columns to be displayed in transactions table. */ + displayedColumns: string[] = ['id', 'transactionDate', 'transactionType', 'debit', 'credit', 'balance', 'viewReciept']; + /** Data source for transactions table. */ + dataSource: MatTableDataSource; + + /** + * Retrieves savings account data from `resolve`. + * @param {ActivatedRoute} route Activated Route. + */ + constructor(private route: ActivatedRoute) { + this.route.parent.data.subscribe((data: { savingsAccountData: any }) => { + this.transactionsData = data.savingsAccountData.transactions; + this.status = data.savingsAccountData.status.value; + }); + } + + ngOnInit() { + this.dataSource = new MatTableDataSource(this.transactionsData); + } + + /** + * Checks if transaction is debit. + * @param {any} transactionType Transaction Type + */ + isDebit(transactionType: any) { + return transactionType.withdrawal === true || transactionType.feeDeduction === true + || transactionType.overdraftInterest === true || transactionType.withholdTax === true; + } + + /** + * Checks transaction status. + */ + checkStatus() { + if (this.status === 'Active' || this.status === 'Closed' || this.status === 'Transfer in progress' || + this.status === 'Transfer on hold' || this.status === 'Premature Closed' || this.status === 'Matured') { + return true; + } + return false; + } + +} diff --git a/src/app/savings/savings-routing.module.ts b/src/app/savings/savings-routing.module.ts index 387d93dd7..f1159ae59 100644 --- a/src/app/savings/savings-routing.module.ts +++ b/src/app/savings/savings-routing.module.ts @@ -6,32 +6,50 @@ import { Routes, RouterModule } from '@angular/router'; import { extract } from '../core/i18n/i18n.service'; /** Custom Components */ +import { SavingsAccountViewComponent } from './savings-account-view/savings-account-view.component'; +import { TransactionsTabComponent } from './savings-account-view/transactions-tab/transactions-tab.component'; import { SavingAccountActionsComponent } from './saving-account-actions/saving-account-actions.component'; import { AddSavingsChargeComponent } from './add-savings-charge/add-savings-charge.component'; +import { ChargesTabComponent } from './savings-account-view/charges-tab/charges-tab.component'; +import { StandingInstructionsTabComponent } from './savings-account-view/standing-instructions-tab/standing-instructions-tab.component'; /** Custom Resolvers */ import { SavingAccountTransactionTemplateResolver } from './common-resolvers/saving-transaction-template.resolver'; import { SavingsChargeTemplateResolver } from './common-resolvers/savings-charge-template.resolver'; +import { SavingsAccountViewResolver } from './common-resolvers/savings-account-view.resolver'; const routes: Routes = [ { path: '', data: { title: extract('All Savings'), breadcrumb: 'Savings', routeParamBreadcrumb: false }, - // Component to view all saving accounts Comes Here children: [ { path: ':savingAccountId', data: { title: extract('Saving Account View'), routeParamBreadcrumb: 'savingAccountId' }, - // Component For Saving Account View Comes Here + component: SavingsAccountViewComponent, + resolve: { + savingsAccountData: SavingsAccountViewResolver + }, children: [ + { + path: 'transactions', + component: TransactionsTabComponent, + data: { title: extract('Savings Account Transactions'), breadcrumb: 'Transactions', routeParamBreadcrumb: false } + }, + { + path: 'charges', + component: ChargesTabComponent, + data: { title: extract('Savings Account Charges'), breadcrumb: 'Charges', routeParamBreadcrumb: false } + }, + { + path: 'standing-instructions', + component: StandingInstructionsTabComponent, + data: { title: extract('Savings Account SIH'), breadcrumb: 'Standing Instructions', routeParamBreadcrumb: false } + }, { path: 'add-savings-charge', component: AddSavingsChargeComponent, - data: { - title: extract('Add Savings Charge'), - breadcrumb: 'Add Savings Charge', - routeParamBreadcrumb: false - }, + data: { title: extract('Add Savings Charge'), breadcrumb: 'Add Savings Charge', routeParamBreadcrumb: false }, resolve: { savingsChargeTemplate: SavingsChargeTemplateResolver } @@ -53,6 +71,8 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], declarations: [], - providers: [SavingAccountTransactionTemplateResolver, SavingsChargeTemplateResolver] + providers: [SavingAccountTransactionTemplateResolver, + SavingsChargeTemplateResolver, + SavingsAccountViewResolver] }) export class SavingsRoutingModule {} diff --git a/src/app/savings/savings.module.ts b/src/app/savings/savings.module.ts index 3cabcde2d..5d616427f 100644 --- a/src/app/savings/savings.module.ts +++ b/src/app/savings/savings.module.ts @@ -5,11 +5,17 @@ import { DatePipe } from '@angular/common'; /** Custom Modules */ import { SavingsRoutingModule } from './savings-routing.module'; import { SharedModule } from 'app/shared/shared.module'; +import { PipesModule } from '../pipes/pipes.module'; +import { DirectivesModule } from '../directives/directives.module'; /** Custom Components */ import { SavingAccountActionsComponent } from './saving-account-actions/saving-account-actions.component'; import { SavingsAccountTransactionsComponent } from './saving-account-actions/savings-account-transactions/savings-account-transactions.component'; import { AddSavingsChargeComponent } from './add-savings-charge/add-savings-charge.component'; +import { TransactionsTabComponent } from './savings-account-view/transactions-tab/transactions-tab.component'; +import { SavingsAccountViewComponent } from './savings-account-view/savings-account-view.component'; +import { ChargesTabComponent } from './savings-account-view/charges-tab/charges-tab.component'; +import { StandingInstructionsTabComponent } from './savings-account-view/standing-instructions-tab/standing-instructions-tab.component'; /** * Savings Module @@ -19,12 +25,18 @@ import { AddSavingsChargeComponent } from './add-savings-charge/add-savings-char @NgModule({ imports: [ SharedModule, + PipesModule, + DirectivesModule, SavingsRoutingModule ], declarations: [ SavingAccountActionsComponent, SavingsAccountTransactionsComponent, - AddSavingsChargeComponent + AddSavingsChargeComponent, + TransactionsTabComponent, + SavingsAccountViewComponent, + ChargesTabComponent, + StandingInstructionsTabComponent ], providers: [DatePipe] }) diff --git a/src/app/savings/savings.service.ts b/src/app/savings/savings.service.ts index 59cc011b9..4b3db6dd9 100644 --- a/src/app/savings/savings.service.ts +++ b/src/app/savings/savings.service.ts @@ -1,6 +1,6 @@ /** Angular Imports */ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; /** rxjs Imports */ import { Observable } from 'rxjs'; @@ -12,7 +12,9 @@ import { Observable } from 'rxjs'; providedIn: 'root' }) export class SavingsService { + constructor(private http: HttpClient) {} + /** * @param {string} savingAccountId is saving account's Id. * @returns {Observable} @@ -55,4 +57,35 @@ export class SavingsService { const params = { template: 'true' }; return this.http.get(`/charges/${chargeId}`, { params: params }); } + + /** + * @param groupId Group Id of group to get data for. + * @returns {Observable} Group data. + */ + getSavingsAccountData(accountId: string): Observable { + const httpParams = new HttpParams().set('associations', 'all'); + return this.http.get(`/savingsaccounts/${accountId}`, { params: httpParams }); + } + + /** + * @param clientId Client Id + * @param clientName Client Name + * @param fromAccountId Account Id + * @param locale Locale + * @param dateFormat Date Format + * @returns {Observable} Standing Instructions + */ + getStandingInstructions( + clientId: string, clientName: string, fromAccountId: string, + locale: string, dateFormat: string): Observable { + const httpParams = new HttpParams() + .set('clientId', clientId) + .set('clientName', clientName) + .set('fromAccountId', fromAccountId) + .set('fromAccountType', '2') + .set('locale', locale) + .set('dateFormat', dateFormat); + return this.http.get(`/standinginstructions`, { params: httpParams }); + } + } diff --git a/src/assets/images/savings_account_placeholder.png b/src/assets/images/savings_account_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..b45fe9be0c90b2a88b8508085c4563199c153d4c GIT binary patch literal 27274 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