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,3 +1,4 @@
<!-- TODO: Responsiveness/design enhancement and reduction in form loading time -->
<div class="container">
<mat-card>
@@ -9,11 +10,15 @@
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="88%">
<mat-select placeholder="Office" required formControlName="officeId">
<mat-label>Office</mat-label>
<mat-select required formControlName="officeId">
<mat-option *ngFor="let office of officeData" [value]="office.id">
{{ office.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="openingBalancesForm.controls.officeId.hasError('required')">
Office is <strong>required</strong>
</mat-error>
</mat-form-field>
<button mat-button type="button" color="primary" fxFlex="4%" (click)="retrieveOpeningBalances()">Retrieve</button>
@@ -25,17 +30,25 @@
<label fxFlex="98%" class="content"><span class="header">Opening Balances Contra Account:</span>&nbsp;&nbsp;{{ openingBalancesData.contraAccount.name + ' (' + openingBalancesData.contraAccount.glCode + ')' }}</label>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Currency" required formControlName="currencyCode">
<mat-label>Currency</mat-label>
<mat-select required formControlName="currencyCode">
<mat-option *ngFor="let currency of currencyData" [value]="currency.code">
{{ currency.displayLabel }}
</mat-option>
</mat-select>
<mat-error *ngIf="openingBalancesForm.controls.currencyCode.hasError('required')">
Currency is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="openingBalancesDatePicker" placeholder="Opening Balances Date" required formControlName="transactionDate">
<mat-label>Opening Balances Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="openingBalancesDatePicker" required formControlName="transactionDate">
<mat-datepicker-toggle matSuffix [for]="openingBalancesDatePicker"></mat-datepicker-toggle>
<mat-datepicker #openingBalancesDatePicker></mat-datepicker>
<mat-error *ngIf="openingBalancesForm.controls.transactionDate.hasError('required')">
Opening Balances Date is <strong>required</strong>
</mat-error>
</mat-form-field>
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" class="content" fxFlex="100%">
@@ -63,7 +76,7 @@
</div>
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" class="content" formArrayName="glAccountEntries"
*ngFor="let credit of getFormArrayData('glAccountEntries').controls; let i = index;">
*ngFor="let credit of glAccountEntries.controls; let i = index;">
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
@@ -81,13 +94,15 @@
<div fxFlex="23%">
<mat-form-field>
<input matInput type="number" placeholder="Debit" formControlName="debit">
<mat-label>Debit</mat-label>
<input matInput type="number" formControlName="debit">
</mat-form-field>
</div>
<div fxFlex="23%">
<mat-form-field>
<input matInput type="number" placeholder="Credit" formControlName="credit">
<mat-label>Credit</mat-label>
<input matInput type="number" formControlName="credit">
</mat-form-field>
</div>
@@ -100,8 +115,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px" *ngIf="openingBalancesData">
<button type="button" mat-raised-button [routerLink]="['/accounting']">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]="!openingBalancesForm.valid">Submit</button>
</mat-card-actions>
</form>
@@ -2,9 +2,9 @@
div {
margin: 1rem 0;
word-wrap: break-word;
}
.header {
font-weight: 500;
&.header {
font-weight: 500;
}
}
}
@@ -1,14 +1,17 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormArray, ValidatorFn, ValidationErrors } from '@angular/forms';
import { FormGroup, FormBuilder, Validators, FormArray } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../accounting.service';
const onlyOneOfTheFieldsIsRequiredValidator: ValidatorFn = (glAccountEntriesForm: FormGroup): ValidationErrors | null => {
const debit = glAccountEntriesForm.controls.debit.value;
const credit = glAccountEntriesForm.controls.credit.value;
return ((debit || credit) && !(debit && credit)) ? null : { 'error': true };
};
/** Custom Validators */
import { onlyOneOfTheFieldsIsRequiredValidator } from './only-one-of-the-fields-is-required.validator';
/**
* Migrate opening balances component.
*/
@Component({
selector: 'mifosx-migrate-opening-balances',
templateUrl: './migrate-opening-balances.component.html',
@@ -16,37 +19,53 @@ const onlyOneOfTheFieldsIsRequiredValidator: ValidatorFn = (glAccountEntriesForm
})
export class MigrateOpeningBalancesComponent implements OnInit {
/** Minimum opening balances date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum opening balances date allowed. */
maxDate = new Date();
/** Opening balances form. */
openingBalancesForm: FormGroup;
/** Opening balances data. */
openingBalancesData: any;
/** Office data. */
officeData: any;
/** Currency data. */
currencyData: any;
/** Sum total of debits. */
debitsSum = 0;
/** Sum total of credits. */
creditsSum = 0;
constructor(private accountingService: AccountingService,
private formBuilder: FormBuilder) { }
/**
* Retrieves the offices and currencies 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: {
offices: any,
currencies: any
}) => {
this.officeData = data.offices;
this.currencyData = data.currencies.selectedCurrencyOptions;
});
}
/**
* Creates the opening balances form. (initially retrieves gl accounts on the basis of specified office)
*/
ngOnInit() {
this.createOpeningBalancesForm();
this.getOffices();
this.getCurrencies();
}
getOffices() {
this.accountingService.getOffices().subscribe(officeData => {
this.officeData = officeData;
});
}
getCurrencies() {
this.accountingService.getCurrencies().subscribe(currencyData => {
this.currencyData = currencyData.selectedCurrencyOptions;
});
}
/**
* Creates the opening balances form.
*/
createOpeningBalancesForm() {
this.openingBalancesForm = this.formBuilder.group({
'officeId': ['', Validators.required],
@@ -56,6 +75,11 @@ export class MigrateOpeningBalancesComponent implements OnInit {
});
}
/**
* Creates the gl account entry form.
* @param glAccount GL Account for which form is returned.
* @returns {FormGroup} GL Account entry form.
*/
createGLAccountEntryForm(glAccount: any): FormGroup {
return this.formBuilder.group({
'glAccountId': [glAccount.glAccountId],
@@ -64,19 +88,32 @@ export class MigrateOpeningBalancesComponent implements OnInit {
}, { validator: onlyOneOfTheFieldsIsRequiredValidator });
}
getFormArrayData(type: string): FormArray {
return <FormArray>this.openingBalancesForm.get(type);
/**
* Gets the gl account entries form array.
* @returns {FormArray} GL Account entries form array.
*/
get glAccountEntries(): FormArray {
return this.openingBalancesForm.get('glAccountEntries') as FormArray;
}
/**
* Retrieves gl accounts on the basis of specified office.
*/
retrieveOpeningBalances() {
this.accountingService.retrieveOpeningBalances(this.openingBalancesForm.value.officeId)
.subscribe((openingBalancesData: any) => {
const entry = this.openingBalancesForm.get('glAccountEntries') as FormArray;
openingBalancesData.glAccounts = openingBalancesData.assetAccountOpeningBalances
.concat(openingBalancesData.liabityAccountOpeningBalances, openingBalancesData.equityAccountOpeningBalances, openingBalancesData.incomeAccountOpeningBalances, openingBalancesData.expenseAccountOpeningBalances);
.concat(openingBalancesData.liabityAccountOpeningBalances,
openingBalancesData.equityAccountOpeningBalances,
openingBalancesData.incomeAccountOpeningBalances,
openingBalancesData.expenseAccountOpeningBalances);
openingBalancesData.glAccounts.forEach((glAccount: any) => {
entry.push(this.createGLAccountEntryForm(glAccount));
});
this.openingBalancesData = openingBalancesData;
entry.valueChanges.subscribe(() => {
@@ -90,32 +127,41 @@ export class MigrateOpeningBalancesComponent implements OnInit {
});
}
/**
* Submits the opening balances form and defines opening balances,
* if successful redirects to view created transaction.
*/
submit() {
// TODO: Validate
if (this.openingBalancesForm.status !== 'INVALID') {
const openingBalances = this.openingBalancesForm.value;
// TODO: Update once language and date settings are setup
openingBalances.locale = 'en';
openingBalances.dateFormat = 'dd MMMM yyyy';
openingBalances.transactionDate = '07 August 2018';
openingBalances.debits = [];
openingBalances.credits = [];
this.openingBalancesForm.value.glAccountEntries.forEach((entry: any) => {
if (entry.debit) {
openingBalances.debits.push({ glAccountId: entry.glAccountId, amount: entry.debit });
}
if (entry.credit) {
openingBalances.credits.push({ glAccountId: entry.glAccountId, amount: entry.credit });
}
});
delete openingBalances.glAccountEntries;
this.accountingService.defineOpeningBalances(openingBalances)
.subscribe((response: any) => {
console.log(response);
});
} else {
console.log('Errors exist in form!');
const openingBalances = this.openingBalancesForm.value;
// TODO: Update once language and date settings are setup
openingBalances.locale = 'en';
openingBalances.dateFormat = 'yyyy-MM-dd';
if (openingBalances.transactionDate instanceof Date) {
let day = openingBalances.transactionDate.getDate();
let month = openingBalances.transactionDate.getMonth() + 1;
const year = openingBalances.transactionDate.getFullYear();
if (day < 10) {
day = `0${day}`;
}
if (month < 10) {
month = `0${month}`;
}
openingBalances.transactionDate = `${year}-${month}-${day}`;
}
openingBalances.debits = [];
openingBalances.credits = [];
this.openingBalancesForm.value.glAccountEntries.forEach((entry: any) => {
if (entry.debit) {
openingBalances.debits.push({ glAccountId: entry.glAccountId, amount: entry.debit });
}
if (entry.credit) {
openingBalances.credits.push({ glAccountId: entry.glAccountId, amount: entry.credit });
}
});
delete openingBalances.glAccountEntries;
this.accountingService.defineOpeningBalances(openingBalances).subscribe((response: any) => {
this.router.navigate(['/accounting/journal-entries/transactions/view', response.transactionId]);
});
}
}
@@ -0,0 +1,9 @@
/** Angular Imports */
import { FormGroup, ValidatorFn, ValidationErrors } from '@angular/forms';
/** Validates that atleast one of the fields debit or credit is filled (but not both) for a given gl account. */
export const onlyOneOfTheFieldsIsRequiredValidator: ValidatorFn = (glAccountEntriesForm: FormGroup): ValidationErrors | null => {
const debit = glAccountEntriesForm.controls.debit.value;
const credit = glAccountEntriesForm.controls.credit.value;
return ((debit || credit) && !(debit && credit)) ? null : { 'error': true };
};