refractor: accounting module (#300)

* Refractor components.
* Add resolvers.
* Add relative router links.
* Replace mat-icons by font-awesome icons.
* Add comments.

Fixes: #298
This commit is contained in:
Abhay Chawla
2018-08-19 22:49:48 +05:30
committed by GitHub
parent 598f239251
commit 6e552d72eb
114 changed files with 3760 additions and 1432 deletions
@@ -1,25 +1,27 @@
<div class="container m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
<mat-button-toggle-group [formControl]="viewGroup">
<mat-button-toggle value="listView">
<mat-icon>list</mat-icon>
<fa-icon icon="list"></fa-icon>
</mat-button-toggle>
<mat-button-toggle value="treeView">
<mat-icon>device_hub</mat-icon>
<fa-icon icon="sitemap"></fa-icon>
</mat-button-toggle>
</mat-button-toggle-group>
<button mat-raised-button color="primary" [routerLink]="['/accounting/chart-of-accounts/gl-accounts/create']">
<mat-icon>add</mat-icon>&nbsp;&nbsp;Add Account
<button mat-raised-button color="primary" [routerLink]="['gl-accounts/create']">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Add Account
</button>
</div>
<!-- List View -->
<div class="container" *ngIf="viewGroup.value === 'listView'">
<div class="container" [hidden]="viewGroup.value !== 'listView'">
<div fxLayout="row" fxLayoutGap="20px">
<mat-form-field fxFlex>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
<mat-label>Filter</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)">
</mat-form-field>
</div>
@@ -46,7 +48,7 @@
<th mat-header-cell *matHeaderCellDef mat-sort-header> Disabled </th>
<td mat-cell *matCellDef="let glAccount">
<div [className]="glAccount.disabled === true ? 'disabled' : 'enabled'">
<mat-icon matTooltip="{{ glAccount.disabled === true ? 'Disabled' : 'Enabled' }}" matTooltipPosition="right">lens</mat-icon>
<fa-icon matTooltip="{{ glAccount.disabled === true ? 'Disabled' : 'Enabled' }}" matTooltipPosition="right" icon="circle" size="lg"></fa-icon>
</div>
</td>
</ng-container>
@@ -54,8 +56,8 @@
<ng-container matColumnDef="manualEntriesAllowed">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Manual Entries Allowed </th>
<td mat-cell *matCellDef="let glAccount">
<mat-icon *ngIf="glAccount.manualEntriesAllowed" class="manual-entries-allowed" matTooltip="Allowed" matTooltipPosition="right">check_circle</mat-icon>
<mat-icon *ngIf="!glAccount.manualEntriesAllowed" class="manual-entries-not-allowed" matTooltip="Not Allowed" matTooltipPosition="right">cancel</mat-icon>
<fa-icon *ngIf="glAccount.manualEntriesAllowed" class="manual-entries-allowed" matTooltip="Allowed" matTooltipPosition="right" icon="check-circle" size="lg"></fa-icon>
<fa-icon *ngIf="!glAccount.manualEntriesAllowed" class="manual-entries-not-allowed" matTooltip="Not Allowed" matTooltipPosition="right" icon="times-circle" size="lg"></fa-icon>
</td>
</ng-container>
@@ -65,7 +67,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/accounting/chart-of-accounts/gl-accounts/view', row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['gl-accounts/view', row.id]" class="select-row"></tr>
</table>
@@ -78,7 +80,7 @@
<!-- Tree View -->
<div class="container" *ngIf="viewGroup.value === 'treeView'">
<div class="container" [hidden]="viewGroup.value !== 'treeView'">
<div class="m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
<button mat-raised-button (click)="nestedTreeControl.expandAll()"> <!-- Bug(angular-material): https://github.com/angular/material2/issues/12170 -->
@@ -173,9 +175,7 @@
<li>
<div class="mat-tree-node">
<button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name">
<mat-icon class="mat-icon-rtl-mirror">
{{ nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
<fa-icon class="mat-icon-rtl-mirror" icon="{{ nestedTreeControl.isExpanded(node) ? 'chevron-down' : 'chevron-right' }}"></fa-icon>
</button>
<span (click)="viewGLAccountNode(node)">
<span *ngIf="node.glCode">{{ '(' + node.glCode + ')' }}</span>&nbsp;&nbsp;
@@ -1,49 +1,54 @@
table {
width: 100%;
}
.gl-account-tree-invisible {
display: none;
}
.gl-account-tree ul,
.gl-account-tree li {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
.select-row:hover {
cursor: pointer;
}
}
.gl-account-tree {
ul,
li {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
}
li {
span {
cursor: pointer;
}
}
.gl-account-tree-invisible {
display: none;
}
}
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
}
.header {
font-weight: 500;
&.header {
font-weight: 500;
}
}
}
// TODO: CONFIGURE THESE CLASSES GLOBALLY
.enabled {
color: #32cd32;
}
.disabled {
color: red;
}
.enabled {
color: green;
color: #f44366;
}
.manual-entries-allowed {
color: green;
color: #32cd32;
}
.manual-entries-not-allowed {
color: red;
color: #f44366;
}
@@ -1,14 +1,22 @@
/** Angular Imports */
import { Component, ViewChild, OnInit, AfterViewInit } from '@angular/core';
import { MatPaginator, MatSort, MatTableDataSource, MatTreeNestedDataSource } from '@angular/material';
import { NestedTreeControl } from '@angular/cdk/tree';
import { FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
/** rxjs Imports */
import { of } from 'rxjs';
/** Custom Models */
import { GLAccountNode } from './gl-account-node.model';
/** Custom Services */
import { GlAccountTreeService } from './gl-account-tree.service';
/**
* Chart of accounts component.
*/
@Component({
selector: 'mifosx-chart-of-accounts',
templateUrl: './chart-of-accounts.component.html',
@@ -16,37 +24,55 @@ import { GlAccountTreeService } from './gl-account-tree.service';
})
export class ChartOfAccountsComponent implements AfterViewInit, OnInit {
/** Button toggle group form control for type of view. (list/tree) */
viewGroup = new FormControl('listView');
/** GL Account data. */
glAccountData: any;
/** Columns to be displayed in chart of accounts table. */
displayedColumns: string[] = ['name', 'glCode', 'glAccountType', 'disabled', 'manualEntriesAllowed', 'usedAs'];
tableDataSource: any;
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
/** Data source for chart of accounts table. */
tableDataSource: MatTableDataSource<any>;
/** Nested tree control for chart of accounts tree. */
nestedTreeControl: NestedTreeControl<GLAccountNode>;
/** Nested tree data source for chart of accounts tree. */
nestedTreeDataSource: MatTreeNestedDataSource<GLAccountNode>;
/** Selected GL Account. */
glAccount: GLAccountNode;
/** Paginator for chart of accounts table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for chart of accounts table. */
@ViewChild(MatSort) sort: MatSort;
/**
* Retrieves the gl accounts data from `resolve` and initializes(generates) gl accounts tree.
* @param {GlAccountTreeService} glAccountTreeService GL Account tree service.
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private glAccountTreeService: GlAccountTreeService,
private route: ActivatedRoute) {
this.route.data.subscribe((data: { glAccountData: any }) => {
this.glAccountData = data.glAccountData;
this.route.data.subscribe((data: { chartOfAccounts: any }) => {
this.glAccountData = data.chartOfAccounts;
glAccountTreeService.initialize(this.glAccountData);
});
this.nestedTreeControl = new NestedTreeControl<GLAccountNode>(this._getChildren);
this.nestedTreeDataSource = new MatTreeNestedDataSource();
this.nestedTreeDataSource = new MatTreeNestedDataSource<GLAccountNode>();
}
/**
* Initializes the data source for chart of accounts table and tree.
*/
ngOnInit() {
this.tableDataSource = new MatTableDataSource(this.glAccountData);
this.glAccountTreeService.treeDataChange.subscribe((glAccountTreeData: any) => {
this.glAccountTreeService.treeDataChange.subscribe((glAccountTreeData: GLAccountNode[]) => {
this.nestedTreeDataSource.data = glAccountTreeData;
this.nestedTreeControl.expand(this.nestedTreeDataSource.data[0]);
});
}
/**
* Initializes the paginator and sorter for chart of accounts table.
*/
ngAfterViewInit() {
this.tableDataSource.paginator = this.paginator;
this.tableDataSource.sortingDataAccessor = (glAccount: any, property: any) => {
@@ -59,12 +85,18 @@ export class ChartOfAccountsComponent implements AfterViewInit, OnInit {
this.tableDataSource.sort = this.sort;
}
/**
* Filters data in chart of accounts table based on passed value.
* @param {string} filterValue Value to filter data.
*/
applyFilter(filterValue: string) {
this.tableDataSource.filter = filterValue.trim().toLowerCase();
}
hasNestedChild = (_: number, node: GLAccountNode) => node.children.length;
/**
* View selected gl account.
* @param {GLAccountNode} glAccount GL Account to be viewed.
*/
viewGLAccountNode(glAccount: GLAccountNode) {
if (glAccount.glCode) {
this.glAccount = glAccount;
@@ -73,6 +105,14 @@ export class ChartOfAccountsComponent implements AfterViewInit, OnInit {
}
}
/**
* Checks if selected node in tree has children.
*/
hasNestedChild = (_: number, node: GLAccountNode) => node.children.length;
/**
* Gets the children of selected node in tree.
*/
private _getChildren = (node: GLAccountNode) => of(node.children);
}
@@ -1,14 +1,28 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { AccountingService } from '../accounting.service';
/**
* Chart of acocunts data resolver.
*/
@Injectable()
export class ChartOfAccountsResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the chart of accounts data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getChartOfAccounts();
}
@@ -0,0 +1,30 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Chart of accounts template data resolver.
*/
@Injectable()
export class ChartOfAccountsTemplateResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the chart of accounts template data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getChartOfAccountsTemplate();
}
}
@@ -9,31 +9,48 @@
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="48%">
<mat-select placeholder="Account Type" required formControlName="type">
<mat-label>Account Type</mat-label>
<mat-select required formControlName="type">
<mat-option *ngFor="let accountType of accountTypeData" [value]="accountType.id">
{{ accountType.value }}
</mat-option>
</mat-select>
<mat-error *ngIf="glAccountForm.controls.type.hasError('required')">
Account Type is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Account Name" required formControlName="name">
<mat-label>Account Name</mat-label>
<input matInput required formControlName="name">
<mat-error *ngIf="glAccountForm.controls.name.hasError('required')">
Account Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Account Usage" required formControlName="usage">
<mat-label>Account Usage</mat-label>
<mat-select required formControlName="usage">
<mat-option *ngFor="let accountUsage of accountUsageData" [value]="accountUsage.id">
{{ accountUsage.value }}
</mat-option>
</mat-select>
<mat-error *ngIf="glAccountForm.controls.usage.hasError('required')">
Account Usage is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="GL Code" required formControlName="glCode">
<mat-label>GL Code</mat-label>
<input matInput required formControlName="glCode">
<mat-error *ngIf="glAccountForm.controls.glCode.hasError('required')">
GL Code is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Parent" formControlName="parentId">
<mat-label>Parent</mat-label>
<mat-select formControlName="parentId">
<mat-option *ngFor="let parent of parentData" [value]="parent.id">
{{ parent.name }}
</mat-option>
@@ -41,21 +58,23 @@
</mat-form-field>
<mat-form-field fxFlex="28%">
<mat-select placeholder="Tag" formControlName="tagId">
<mat-label>Tag</mat-label>
<mat-select formControlName="tagId">
<mat-option *ngFor="let tag of tagData" [value]="tag.id">
{{ tag.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="18%" class="manualEntriesAllowedWrapper">
<mat-checkbox labelPosition="before" required formControlName="manualEntriesAllowed" class="manualEntriesAllowed">
<div fxFlex="18%" class="manual-entries-allowed-wrapper">
<mat-checkbox labelPosition="before" required formControlName="manualEntriesAllowed" class="manual-entries-allowed">
Manual Entries Allowed
</mat-checkbox>
</div>
<mat-form-field fxFlex="98%">
<textarea matInput placeholder="Description" formControlName="description"></textarea>
<mat-label>Description</mat-label>
<textarea matInput formControlName="description"></textarea>
</mat-form-field>
</div>
@@ -64,7 +83,7 @@
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="[cancelRoute]">Cancel</button>
<button mat-raised-button color="primary">Submit</button>
<button mat-raised-button color="primary" [disabled]="!glAccountForm.valid">Submit</button>
</mat-card-actions>
</form>
@@ -1,7 +1,7 @@
.manualEntriesAllowedWrapper {
.manual-entries-allowed-wrapper {
position: relative;
.manualEntriesAllowed {
.manual-entries-allowed {
padding: 0 0 17.5px 0;
position: absolute;
bottom: 0;
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Create gl account component.
*/
@Component({
selector: 'mifosx-create-gl-account',
templateUrl: './create-gl-account.component.html',
@@ -11,18 +16,32 @@ import { AccountingService } from '../../accounting.service';
})
export class CreateGlAccountComponent implements OnInit {
/** GL account form. */
glAccountForm: FormGroup;
/** Chart of accounts data. */
chartOfAccountsData: any;
/** Account type data. */
accountTypeData: any;
parentData: any;
/** Account usage data. */
accountUsageData: any;
/** Parent data. */
parentData: any;
/** Tag data. */
tagData: any;
/** Account type id. (for creation of sub-ledger account) */
accountTypeId: number;
/** Parent id. (for creation of sub-ledger account) */
parentId: number;
/** Cancel route. (depending on creation of gl account or sub-ledger account) */
cancelRoute = '../../';
cancelRoute = '/accounting/chart-of-accounts';
/**
* Retrieves the chart of accounts data from `resolve`.
* @param {FormBuilder} formBuilder Form Builder.
* @param {AccountingService} accountingService Accounting Service.
* @param {ActivatedRoute} route Activated Route.
* @param {Router} router Router for navigation.
*/
constructor(private formBuilder: FormBuilder,
private accountingService: AccountingService,
private route: ActivatedRoute,
@@ -31,39 +50,44 @@ export class CreateGlAccountComponent implements OnInit {
this.accountTypeId = Number(params.get('accountType'));
this.parentId = Number(params.get('parent'));
if (this.parentId) {
this.cancelRoute = `/accounting/chart-of-accounts/gl-accounts/view/${this.parentId}`;
this.cancelRoute = `../view/${this.parentId}`;
}
});
this.route.data.subscribe((data: { chartOfAccountsTemplate: any }) => {
this.chartOfAccountsData = data.chartOfAccountsTemplate;
this.accountTypeData = data.chartOfAccountsTemplate.accountTypeOptions;
this.accountUsageData = data.chartOfAccountsTemplate.usageOptions;
});
}
/**
* Creates and sets gl account form.
*/
ngOnInit() {
this.createGlAccountForm();
this.getChartOfAccountsTemplate();
this.setGLAccountForm();
}
/**
* Creates gl account form.
*/
createGlAccountForm() {
this.glAccountForm = this.formBuilder.group({
'type': ['', Validators.required],
'name': ['', Validators.required],
'usage': ['', Validators.required],
'glCode': ['', Validators.required],
'parentId': [this.parentId],
'parentId': [this.parentId || undefined],
'tagId': [''],
'manualEntriesAllowed': [true, Validators.required],
'description': ['']
});
}
getChartOfAccountsTemplate() {
this.accountingService.getChartOfAccountsTemplate().subscribe((chartOfAccountsData: any) => {
this.chartOfAccountsData = chartOfAccountsData;
this.accountTypeData = chartOfAccountsData.accountTypeOptions;
this.accountUsageData = chartOfAccountsData.usageOptions;
this.glAccountForm.get('type').setValue(this.accountTypeId);
});
}
/**
* Sets gl account form for selected account type.
*/
setGLAccountForm() {
this.glAccountForm.get('type').valueChanges.subscribe(accountTypeId => {
switch (accountTypeId) {
@@ -84,11 +108,17 @@ export class CreateGlAccountComponent implements OnInit {
break;
}
});
this.glAccountForm.get('type').setValue(this.accountTypeId);
}
/**
* Submits the gl account form and creates gl account,
* if successful redirects to view created account.
*/
submit() {
this.accountingService.createGlAccount(this.glAccountForm.value).subscribe((response: any) => {
this.router.navigate(['/accounting/chart-of-accounts/gl-accounts/view', response.resourceId]);
this.router.navigate(['../view', response.resourceId], { relativeTo: this.route });
});
}
@@ -9,31 +9,48 @@
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="48%">
<mat-select placeholder="Account Type" required formControlName="type">
<mat-label>Account Type</mat-label>
<mat-select required formControlName="type">
<mat-option *ngFor="let accountType of accountTypeData" [value]="accountType.id">
{{ accountType.value }}
</mat-option>
</mat-select>
<mat-error *ngIf="glAccountForm.controls.type.hasError('required')">
Account Type is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Account Name" required formControlName="name">
<mat-label>Account Name</mat-label>
<input matInput required formControlName="name">
<mat-error *ngIf="glAccountForm.controls.name.hasError('required')">
Account Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Account Usage" required formControlName="usage">
<mat-label>Account Usage</mat-label>
<mat-select required formControlName="usage">
<mat-option *ngFor="let accountUsage of accountUsageData" [value]="accountUsage.id">
{{ accountUsage.value }}
</mat-option>
</mat-select>
<mat-error *ngIf="glAccountForm.controls.usage.hasError('required')">
Account Usage is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="GL Code" required formControlName="glCode">
<mat-label>GL Code</mat-label>
<input matInput required formControlName="glCode">
<mat-error *ngIf="glAccountForm.controls.glCode.hasError('required')">
GL Code is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Parent" formControlName="parentId">
<mat-label>Parent</mat-label>
<mat-select formControlName="parentId">
<mat-option *ngFor="let parent of parentData" [value]="parent.id">
{{ parent.name }}
</mat-option>
@@ -41,21 +58,23 @@
</mat-form-field>
<mat-form-field fxFlex="28%">
<mat-select placeholder="Tag" formControlName="tagId">
<mat-label>Tag</mat-label>
<mat-select formControlName="tagId">
<mat-option *ngFor="let tag of tagData" [value]="tag.id">
{{ tag.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="18%" class="manualEntriesAllowedWrapper">
<mat-checkbox labelPosition="before" required formControlName="manualEntriesAllowed" class="manualEntriesAllowed">
<div fxFlex="18%" class="manual-entries-allowed-wrapper">
<mat-checkbox labelPosition="before" required formControlName="manualEntriesAllowed" class="manual-entries-allowed">
Manual Entries Allowed
</mat-checkbox>
</div>
<mat-form-field fxFlex="98%">
<textarea matInput placeholder="Description" formControlName="description"></textarea>
<mat-label>Description</mat-label>
<textarea matInput formControlName="description"></textarea>
</mat-form-field>
</div>
@@ -63,8 +82,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/chart-of-accounts']">Cancel</button>
<button mat-raised-button color="primary">Submit</button>
<button type="button" mat-raised-button [routerLink]="['../']">Cancel</button>
<button mat-raised-button color="primary" [disabled]="glAccountForm.pristine">Submit</button>
</mat-card-actions>
</form>
@@ -0,0 +1,10 @@
.manual-entries-allowed-wrapper {
position: relative;
.manual-entries-allowed {
padding: 0 0 17.5px 0;
position: absolute;
bottom: 0;
right: 0;
}
}
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Edit gl account component.
*/
@Component({
selector: 'mifosx-edit-gl-account',
templateUrl: './edit-gl-account.component.html',
@@ -11,27 +16,46 @@ import { AccountingService } from '../../accounting.service';
})
export class EditGlAccountComponent implements OnInit {
/** GL account form. */
glAccountForm: FormGroup;
/** GL account and chart of accounts data. */
glAccount: any;
/** Account type data. */
accountTypeData: any;
/** Account usage data. */
accountUsageData: any;
/** Parent data. */
parentData: any;
/** Tag data. */
tagData: any;
constructor(private route: ActivatedRoute,
private formBuilder: FormBuilder,
/**
* Retrieves the chart of accounts data from `resolve`.
* @param {FormBuilder} formBuilder Form Builder.
* @param {AccountingService} accountingService Accounting Service.
* @param {ActivatedRoute} route Activated Route.
* @param {Router} router Router for navigation.
*/
constructor(private formBuilder: FormBuilder,
private accountingService: AccountingService,
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: { glAccount: any }) => {
this.glAccount = data.glAccount;
this.route.data.subscribe((data: { glAccountAndChartOfAccountsTemplate: any }) => {
this.glAccount = data.glAccountAndChartOfAccountsTemplate;
});
}
/**
* Creates and sets gl account form.
*/
ngOnInit() {
this.createGlAccountForm();
this.setGLAccountForm();
}
/**
* Creates gl account form.
*/
createGlAccountForm() {
this.glAccountForm = this.formBuilder.group({
'type': ['', Validators.required],
@@ -45,6 +69,9 @@ export class EditGlAccountComponent implements OnInit {
});
}
/**
* Sets gl account form for selected account type.
*/
setGLAccountForm() {
this.accountTypeData = this.glAccount.accountTypeOptions;
this.accountUsageData = this.glAccount.usageOptions;
@@ -67,13 +94,18 @@ export class EditGlAccountComponent implements OnInit {
break;
}
});
this.glAccountForm.get('type').setValue(this.glAccount.type.id);
}
/**
* Submits the gl account form and updates gl account,
* if successful redirects to view updated account.
*/
submit() {
this.accountingService.updateGlAccount(this.glAccount.id, this.glAccountForm.value)
.subscribe((response: any) => {
this.router.navigate(['/accounting/chart-of-accounts/gl-accounts/view', response.resourceId]);
this.router.navigate(['../../', response.resourceId], { relativeTo: this.route });
});
}
@@ -0,0 +1,53 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
/** Custom Services */
import { AccountingService } from '../accounting.service';
/**
* GL Account and chart of accounts template data resolver.
*/
@Injectable()
export class GlAccountAndChartOfAccountsTemplateResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the gl account and chart of accounts template data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.accountingService.getGlAccount(id, true).pipe(
map((glAccountData: any) => {
let accountOptions = [];
switch (glAccountData.type.value) {
case 'ASSET': accountOptions = glAccountData.assetHeaderAccountOptions;
break;
case 'EQUITY': accountOptions = glAccountData.equityHeaderAccountOptions;
break;
case 'EXPENSE': accountOptions = glAccountData.expenseHeaderAccountOptions;
break;
case 'INCOME': accountOptions = glAccountData.incomeHeaderAccountOptions;
break;
case 'LIABILITY': accountOptions = glAccountData.liabilityHeaderAccountOptions;
break;
}
glAccountData.parent = accountOptions.find((accountOption: any) => {
return accountOption.id === glAccountData.parentId;
});
return glAccountData;
})
);
}
}
@@ -1,11 +1,18 @@
/**
* GL Account Node model.
*/
export class GLAccountNode {
/** GL Account Node children. */
children: GLAccountNode[];
name: string;
glCode: string;
type: string;
usage: string;
manualEntriesAllowed: boolean;
description: string;
constructor(name: string,
glCode: string = '',
type: string = '',
@@ -20,4 +27,5 @@ export class GLAccountNode {
this.description = description;
this.children = [];
}
}
@@ -1,30 +1,50 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
/** rxjs Imports */
import { BehaviorSubject } from 'rxjs';
/** Custom Components */
import { GLAccountNode } from './gl-account-node.model';
/**
* GL Account tree service.
*/
@Injectable({
providedIn: 'root'
})
export class GlAccountTreeService {
/** GL Account data. */
glAccountData: any;
/** Chart of accounts tree data behavior subject to represent chart of accounts tree nodes. */
treeDataChange = new BehaviorSubject<GLAccountNode[]>([]);
/**
* Gets the chart of accounts tree nodes.
*/
get treeData(): GLAccountNode[] { return this.treeDataChange.value; }
constructor() { }
/**
* Builds the chart of accounts tree and emits the value.
* @param {any} glAccountData Chart of accounts data.
*/
initialize(glAccountData: any) {
const data = this.buildGLAccountTree(glAccountData);
this.treeDataChange.next(data);
const treeData = this.buildGLAccountTree(glAccountData);
this.treeDataChange.next(treeData);
}
/**
* Builds and returns the chart of accounts tree.
* @param {any} glAccountData Chart of accounts data.
* @returns {GLAccountNode[]} Chart of accounts tree nodes.
*/
buildGLAccountTree(glAccountData: any): GLAccountNode[] {
const glAccountTree: GLAccountNode[] = [];
// Header nodes
glAccountTree.push(new GLAccountNode('GL ACCOUNTS'));
glAccountTree[0].children.push(new GLAccountNode('ASSET'));
glAccountTree[0].children.push(new GLAccountNode('EQUITY'));
@@ -32,6 +52,7 @@ export class GlAccountTreeService {
glAccountTree[0].children.push(new GLAccountNode('INCOME'));
glAccountTree[0].children.push(new GLAccountNode('LIABILITY'));
// Sort by parent id (so that child nodes can be added properly)
glAccountData.sort((glAccountOne: any, glAccountTwo: any) => {
if (!glAccountOne.parentId) {
glAccountOne.parentId = 0;
@@ -41,11 +62,14 @@ export class GlAccountTreeService {
const glAccounts: GLAccountNode[] = [];
// Add gl accounts to any array where index for each is denoted by its id
for (const glAccount of glAccountData) {
glAccounts[glAccount.id] =
new GLAccountNode(glAccount.name, glAccount.glCode, glAccount.type.value, glAccount.usage.value, glAccount.manualEntriesAllowed, glAccount.description);
}
// Construct gl account tree by adding all nodes belonging to headers (with parent id = 0) by their type,
// and rest as children to respective parent nodes.
for (const glAccount of glAccountData) {
if (glAccount.parentId === 0) {
if (glAccount.type.value === 'ASSET') {
@@ -1,18 +1,23 @@
<div fxLayout="row" fxLayoutAlign="end" fxLayoutGap="2%" fxLayout.lt-md="column" class="container m-b-20">
<button mat-raised-button color="primary" *ngIf="glAccount.usage.value === 'HEADER'" [routerLink]="['/accounting/chart-of-accounts/gl-accounts/create']" [queryParams]="{ parent: glAccount.id, accountType: glAccount.type.id }">
<mat-icon>add</mat-icon>&nbsp;&nbsp;Subledger Account
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Subledger Account
</button>
<button mat-raised-button color="primary" [routerLink]="['/accounting/chart-of-accounts/gl-accounts/view', glAccount.id, 'edit']">
<mat-icon>edit</mat-icon>&nbsp;&nbsp;Edit
<button mat-raised-button color="primary" [routerLink]="['edit']">
<fa-icon icon="edit"></fa-icon>&nbsp;&nbsp;
Edit
</button>
<button mat-raised-button color="warn" *ngIf="!glAccount.disabled" (click)="changeGlAccountState()">
<mat-icon>lock</mat-icon>&nbsp;&nbsp;Disable
<fa-icon icon="lock"></fa-icon>&nbsp;&nbsp;
Disable
</button>
<button mat-raised-button color="accent" *ngIf="glAccount.disabled" (click)="changeGlAccountState()">
<mat-icon>lock_open</mat-icon>&nbsp;&nbsp;Enable
<fa-icon icon="lock-open"></fa-icon>&nbsp;&nbsp;
Enable
</button>
<button mat-raised-button color="warn" (click)="deleteGlAccount()">
<mat-icon>delete</mat-icon>&nbsp;&nbsp;Delete
<fa-icon icon="trash"></fa-icon>&nbsp;&nbsp;
Delete
</button>
</div>
@@ -29,7 +34,7 @@
</div>
<div fxFlex="50%">
{{ glAccount.type.value }}
{{ glAccount.type?.value }}
</div>
<div fxFlex="50%" class="header">
@@ -40,19 +45,19 @@
{{ glAccount.glCode }}
</div>
<div fxFlex="50%" class="header">
<div fxFlex="50%" class="header" *ngIf="glAccount.parent">
Parent Account Name
</div>
<div fxFlex="50%">
<div fxFlex="50%" *ngIf="glAccount.parent">
{{ glAccount.parent.name }}
</div>
<div fxFlex="50%" class="header">
<div fxFlex="50%" class="header" *ngIf="glAccount.tagId.id">
Tag
</div>
<div fxFlex="50%">
<div fxFlex="50%" *ngIf="glAccount.tagId.id">
{{ glAccount.tagId.name }}
</div>
@@ -61,7 +66,7 @@
</div>
<div fxFlex="50%">
{{ glAccount.usage.value }}
{{ glAccount.usage?.value }}
</div>
<div fxFlex="50%" class="header">
@@ -72,11 +77,11 @@
{{ glAccount.manualEntriesAllowed }}
</div>
<div fxFlex="50%" class="header">
<div fxFlex="50%" class="header" *ngIf="glAccount.description">
Description
</div>
<div fxFlex="50%">
<div fxFlex="50%" *ngIf="glAccount.description">
{{ glAccount.description }}
</div>
@@ -1,14 +1,14 @@
.container {
max-width: 37rem;
}
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
}
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
.header {
font-weight: 500;
&.header {
font-weight: 500;
}
}
}
}
@@ -1,10 +1,17 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material';
/** Custom Components */
import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* View gl account component.
*/
@Component({
selector: 'mifosx-view-gl-account',
templateUrl: './view-gl-account.component.html',
@@ -12,22 +19,32 @@ import { AccountingService } from '../../accounting.service';
})
export class ViewGlAccountComponent implements OnInit {
/** GL Account. */
glAccount: any;
constructor(private route: ActivatedRoute,
private accountingService: AccountingService,
/**
* Retrieves the gl account data from `resolve`.
* @param {AccountingService} accountingService Accounting Service.
* @param {ActivatedRoute} route Activated Route.
* @param {Router} router Router for navigation.
* @param {MatDialog} dialog Dialog reference.
*/
constructor(private accountingService: AccountingService,
private route: ActivatedRoute,
private router: Router,
public dialog: MatDialog) {
this.route.data.subscribe((data: { glAccount: any }) => {
this.glAccount = data.glAccount;
this.route.data.subscribe((data: { glAccountAndChartOfAccountsTemplate: any }) => {
this.glAccount = data.glAccountAndChartOfAccountsTemplate;
});
}
ngOnInit() {
}
/**
* Deletes the gl account and redirects to chart of accounts.
*/
deleteGlAccount() {
// TODO: (Validation) gl account cannot be deleted if it has transactions logged against it
const deleteGlAccountDialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: `gl account ${this.glAccount.id}` }
});
@@ -41,6 +58,9 @@ export class ViewGlAccountComponent implements OnInit {
});
}
/**
* Changes state of gl account. (enabled/disabled)
*/
changeGlAccountState() {
this.accountingService.updateGlAccount(this.glAccount.id, { disabled: !this.glAccount.disabled })
.subscribe((response: any) => {
@@ -1,38 +0,0 @@
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AccountingService } from '../../accounting.service';
@Injectable()
export class ViewGlAccountResolver implements Resolve<Object> {
constructor(private accountingService: AccountingService) {}
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.accountingService.getGlAccount(id, true).pipe(map((glAccountData: any) => {
let accountOptions = [];
switch (glAccountData.type.value) {
case 'ASSET': accountOptions = glAccountData.assetHeaderAccountOptions;
break;
case 'EQUITY': accountOptions = glAccountData.equityHeaderAccountOptions;
break;
case 'EXPENSE': accountOptions = glAccountData.expenseHeaderAccountOptions;
break;
case 'INCOME': accountOptions = glAccountData.incomeHeaderAccountOptions;
break;
case 'LIABILITY': accountOptions = glAccountData.liabilityHeaderAccountOptions;
break;
}
glAccountData.parent = accountOptions.find((accountOption: any) => {
return accountOption.id === glAccountData.parentId;
});
return glAccountData;
}));
}
}