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
+6 -1
View File
@@ -23,7 +23,12 @@
"selector-class-pattern": "[a-z-]+",
"selector-id-pattern": "[a-z-]+",
"selector-max-id": 1,
"selector-no-qualifying-type": true,
"selector-no-qualifying-type": [
true,
{
"ignore": ["class"]
}
],
"selector-max-universal": 0,
"selector-type-no-unknown": [
true,
+198 -69
View File
@@ -1,3 +1,6 @@
/** TODO: Separate routing into feature modules for cleaner accounting module. */
/** Angular Imports */
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
@@ -7,24 +10,21 @@ import { Route } from '../core/route/route.service';
/** Translation Imports */
import { extract } from '../core/i18n/i18n.service';
/** Custom Components */
import { AccountingComponent } from './accounting.component';
import { FrequentPostingsComponent } from './frequent-postings/frequent-postings.component';
import { ViewTransactionComponent } from './view-transaction/view-transaction.component';
import { CreateJournalEntryComponent } from './create-journal-entry/create-journal-entry.component';
import { SearchJournalEntryComponent } from './search-journal-entry/search-journal-entry.component';
import { ViewTransactionComponent } from './view-transaction/view-transaction.component';
import { FinancialActivityMappingsComponent } from './financial-activity-mappings/financial-activity-mappings.component';
import { CreateFinancialActivityMappingComponent } from './financial-activity-mappings/create-financial-activity-mapping/create-financial-activity-mapping.component';
import { ViewFinancialActivityMappingComponent } from './financial-activity-mappings/view-financial-activity-mapping/view-financial-activity-mapping.component';
import { EditFinancialActivityMappingComponent } from './financial-activity-mappings/edit-financial-activity-mapping/edit-financial-activity-mapping.component';
import { MigrateOpeningBalancesComponent } from './migrate-opening-balances/migrate-opening-balances.component';
import { ChartOfAccountsComponent } from './chart-of-accounts/chart-of-accounts.component';
import { ChartOfAccountsResolver } from './chart-of-accounts/chart-of-accounts.resolver';
import { CreateGlAccountComponent } from './chart-of-accounts/create-gl-account/create-gl-account.component';
import { ViewGlAccountComponent } from './chart-of-accounts/view-gl-account/view-gl-account.component';
import { ViewGlAccountResolver } from './chart-of-accounts/view-gl-account/view-gl-account.resolver';
import { EditGlAccountComponent } from './chart-of-accounts/edit-gl-account/edit-gl-account.component';
import { ClosingEntriesComponent } from './closing-entries/closing-entries.component';
import { CreateClosureComponent } from './closing-entries/create-closure/create-closure.component';
import { ViewClosureComponent } from './closing-entries/view-closure/view-closure.component';
@@ -32,15 +32,40 @@ import { EditClosureComponent } from './closing-entries/edit-closure/edit-closur
import { AccountingRulesComponent } from './accounting-rules/accounting-rules.component';
import { CreateRuleComponent } from './accounting-rules/create-rule/create-rule.component';
import { ViewRuleComponent } from './accounting-rules/view-rule/view-rule.component';
import { ViewAccountingRuleResolver } from './accounting-rules/view-rule/view-rule.resolver';
import { EditRuleComponent } from './accounting-rules/edit-rule/edit-rule.component';
import { PeriodicAccrualsComponent } from './periodic-accruals/periodic-accruals.component';
import { ProvisioningEntriesComponent } from './provisioning-entries/provisioning-entries.component';
import { CreateProvisioningEntryComponent } from './provisioning-entries/create-provisioning-entry/create-provisioning-entry.component';
import { ViewProvisioningEntryComponent } from './provisioning-entries/view-provisioning-entry/view-provisioning-entry.component';
import { ViewProvisioningJournalEntriesComponent } from './provisioning-entries/view-provisioning-journal-entries/view-provisioning-journal-entries.component';
import { MigrateOpeningBalancesComponent } from './migrate-opening-balances/migrate-opening-balances.component';
/** Custom Resolvers */
import { OfficesResolver } from './common-resolvers/offices.resolver';
import { AccountingRulesAssociationsResolver } from './common-resolvers/accounting-rules-associations.resolver';
import { CurrenciesResolver } from './common-resolvers/currencies.resolver';
import { PaymentTypesResolver } from './common-resolvers/payment-types.resolver';
import { GlAccountsResolver } from './common-resolvers/gl-accounts.resolver';
import { TransactionResolver } from './view-transaction/transaction.resolver';
import { FinancialActivityMappingsResolver } from './financial-activity-mappings/financial-activity-mappings.resolver';
import { FinancialActivityMappingsTemplateResolver } from './financial-activity-mappings/create-financial-activity-mapping/financial-activity-mappings-template.resolver';
import { FinancialActivityMappingResolver } from './financial-activity-mappings/view-financial-activity-mapping/financial-activity-mapping.resolver';
import { FinancialActivityMappingAndTemplateResolver } from './financial-activity-mappings/edit-financial-activity-mapping/financial-activity-mapping-and-template.resolver';
import { ChartOfAccountsResolver } from './chart-of-accounts/chart-of-accounts.resolver';
import { ChartOfAccountsTemplateResolver } from './chart-of-accounts/create-gl-account/chart-of-accounts-template.resolver';
import { GlAccountAndChartOfAccountsTemplateResolver } from './chart-of-accounts/gl-account-and-chart-of-accounts-template.resolver';
import { ClosingEntriesResolver } from './closing-entries/closing-entries.resolver';
import { ClosingEntryResolver } from './closing-entries/closing-entry.resolver';
import { AccountingRulesResolver } from './accounting-rules/accounting-rules.resolver';
import { AccountingRulesTemplateResolver } from './accounting-rules/accounting-rules-template.resolver';
import { AccountingRuleResolver } from './accounting-rules/accounting-rule.resolver';
import { ProvisioningEntriesResolver } from './provisioning-entries/provisioning-entries.resolver';
import { ProvisioningEntryResolver } from './provisioning-entries/view-provisioning-entry/provisioning-entry.resolver';
import { ProvisioningEntryEntriesResolver } from './provisioning-entries/view-provisioning-entry/provisioning-entry-entries.resolver';
import { LoanProductsResolver } from './common-resolvers/loan-products.resolver';
import { ProvisioningCategoriesResolver } from './common-resolvers/provisioning-categories.resolver';
import { ProvisioningJournalEntriesResolver } from './provisioning-entries/view-provisioning-journal-entries/provisioning-journal-entries.resolver';
/** Accounting Routes */
const routes: Routes = [
Route.withShell([
{
@@ -52,43 +77,73 @@ const routes: Routes = [
component: AccountingComponent
},
{
path: 'frequent-postings',
component: FrequentPostingsComponent,
data: { title: extract('Frequent Postings'), breadcrumb: 'Frequent Postings' }
},
{
path: 'transactions',
data: { title: extract('Transactions'), breadcrumb: 'Transactions' },
path: 'journal-entries',
data: { title: extract('Search Journal Entry'), breadcrumb: 'Journal Entries' },
children: [
{
path: 'view/:id',
component: ViewTransactionComponent,
data: { title: extract('View Transaction'), routeParamBreadcrumb: 'id' }
}
path: '',
component: SearchJournalEntryComponent,
resolve: {
offices: OfficesResolver,
glAccounts: GlAccountsResolver
},
},
{
path: 'frequent-postings',
component: FrequentPostingsComponent,
data: { title: extract('Frequent Postings'), breadcrumb: 'Frequent Postings' },
resolve: {
offices: OfficesResolver,
accountingRules: AccountingRulesAssociationsResolver,
currencies: CurrenciesResolver,
paymentTypes: PaymentTypesResolver
}
},
{
path: 'create',
component: CreateJournalEntryComponent,
data: { title: extract('Create Journal Entry'), breadcrumb: 'Create' },
resolve: {
offices: OfficesResolver,
currencies: CurrenciesResolver,
paymentTypes: PaymentTypesResolver,
glAccounts: GlAccountsResolver
}
},
{
path: 'transactions',
data: { title: extract('Transactions'), breadcrumb: 'Transactions', addBreadcrumbLink: false },
children: [
{
path: 'view/:id',
component: ViewTransactionComponent,
data: { title: extract('View Transaction'), routeParamBreadcrumb: 'id' },
resolve: {
transaction: TransactionResolver
}
}
]
},
]
},
{
path: 'journal-entries',
component: SearchJournalEntryComponent,
data: { title: extract('Search Journal Entry'), breadcrumb: 'Search Journal Entry' }
},
{
path: 'journal-entries/create',
component: CreateJournalEntryComponent,
data: { title: extract('Create Journal Entry'), breadcrumb: 'Create Journal Entry' }
},
{
path: 'financial-activity-mappings',
data: { title: extract('Financial Activity Mappings'), breadcrumb: 'Financial Activity Mappings' },
children: [
{
path: '',
component: FinancialActivityMappingsComponent
component: FinancialActivityMappingsComponent,
resolve: {
financialActivityAccounts: FinancialActivityMappingsResolver
}
},
{
path: 'create',
component: CreateFinancialActivityMappingComponent,
data: { title: extract('Create Financial Activity Mapping'), breadcrumb: 'Create' },
resolve: {
financialActivityAccountsTemplate: FinancialActivityMappingsTemplateResolver
}
},
{
path: 'view/:id',
@@ -96,52 +151,72 @@ const routes: Routes = [
children: [
{
path: '',
component: ViewFinancialActivityMappingComponent
component: ViewFinancialActivityMappingComponent,
resolve: {
financialActivityAccount: FinancialActivityMappingResolver
}
},
{
path: 'edit',
component: EditFinancialActivityMappingComponent,
data: { title: extract('Edit Financial Activity Mapping'), breadcrumb: 'Edit', routeParamBreadcrumb: false }
data: { title: extract('Edit Financial Activity Mapping'), breadcrumb: 'Edit', routeParamBreadcrumb: false },
resolve: {
financialActivityAccountAndTemplate: FinancialActivityMappingAndTemplateResolver
}
}
]
}
]
},
{
path: 'migrate-opening-balances',
component: MigrateOpeningBalancesComponent,
data: { title: extract('Migrate Opening Balances'), breadcrumb: 'Migrate Opening Balances' },
resolve: {
offices: OfficesResolver,
currencies: CurrenciesResolver
}
},
{
path: 'chart-of-accounts',
data: { title: extract('Chart of Accounts'), breadcrumb: 'Chart of Accounts' },
resolve: {
glAccountData: ChartOfAccountsResolver
},
children: [
{
path: '',
component: ChartOfAccountsComponent,
resolve: {
glAccountData: ChartOfAccountsResolver
chartOfAccounts: ChartOfAccountsResolver
}
},
{
path: 'gl-accounts/create',
component: CreateGlAccountComponent,
data: { title: extract('Create GL Account'), breadcrumb: 'Create GL Account' },
},
{
path: 'gl-accounts/view/:id',
data: { title: extract('View GL Account'), routeResolveBreadcrumb: ['glAccount', 'name'] },
resolve: {
glAccount: ViewGlAccountResolver
},
runGuardsAndResolvers: 'always',
path: 'gl-accounts',
children: [
{
path: '',
component: ViewGlAccountComponent
path: 'create',
component: CreateGlAccountComponent,
data: { title: extract('Create GL Account'), breadcrumb: 'Create GL Account' },
resolve: {
chartOfAccountsTemplate: ChartOfAccountsTemplateResolver
}
},
{
path: 'edit',
component: EditGlAccountComponent,
data: { title: extract('Edit GL Account'), breadcrumb: 'Edit', routeResolveBreadcrumb: false }
path: 'view/:id',
data: { title: extract('View GL Account'), routeResolveBreadcrumb: ['glAccountAndChartOfAccountsTemplate', 'name'] },
resolve: {
glAccountAndChartOfAccountsTemplate: GlAccountAndChartOfAccountsTemplateResolver
},
runGuardsAndResolvers: 'always',
children: [
{
path: '',
component: ViewGlAccountComponent
},
{
path: 'edit',
component: EditGlAccountComponent,
data: { title: extract('Edit GL Account'), breadcrumb: 'Edit', routeResolveBreadcrumb: false }
}
]
}
]
}
@@ -153,16 +228,27 @@ const routes: Routes = [
children: [
{
path: '',
component: ClosingEntriesComponent
component: ClosingEntriesComponent,
resolve: {
offices: OfficesResolver,
glAccountClosures: ClosingEntriesResolver
}
},
{
path: 'create',
component: CreateClosureComponent,
data: { title: extract('Create Accounting Closure'), breadcrumb: 'Create' }
data: { title: extract('Create Accounting Closure'), breadcrumb: 'Create' },
resolve: {
offices: OfficesResolver
}
},
{
path: 'view/:id',
data: { title: extract('View Accounting Closure'), routeParamBreadcrumb: 'id' },
resolve: {
glAccountClosure: ClosingEntryResolver
},
runGuardsAndResolvers: 'always',
children: [
{
path: '',
@@ -183,18 +269,24 @@ const routes: Routes = [
children: [
{
path: '',
component: AccountingRulesComponent
component: AccountingRulesComponent,
resolve: {
accountingRules: AccountingRulesResolver
}
},
{
path: 'create',
component: CreateRuleComponent,
data: { title: extract('Create Accounting Rule'), breadcrumb: 'Create' }
data: { title: extract('Create Accounting Rule'), breadcrumb: 'Create' },
resolve: {
accountingRulesTemplate: AccountingRulesTemplateResolver
}
},
{
path: 'view/:id',
data: { title: extract('View Accounting Rule'), routeResolveBreadcrumb: ['accountingRule', 'name'] },
resolve: {
accountingRule: ViewAccountingRuleResolver
accountingRule: AccountingRuleResolver
},
runGuardsAndResolvers: 'always',
children: [
@@ -205,7 +297,10 @@ const routes: Routes = [
{
path: 'edit',
component: EditRuleComponent,
data: { title: extract('Edit Accounting Rules'), breadcrumb: 'Edit', routeResolveBreadcrumb: false }
data: { title: extract('Edit Accounting Rules'), breadcrumb: 'Edit', routeResolveBreadcrumb: false },
resolve: {
accountingRulesTemplate: AccountingRulesTemplateResolver
}
}
]
}
@@ -222,7 +317,10 @@ const routes: Routes = [
children: [
{
path: '',
component: ProvisioningEntriesComponent
component: ProvisioningEntriesComponent,
resolve: {
provisioningEntries: ProvisioningEntriesResolver
}
},
{
path: 'create',
@@ -232,38 +330,69 @@ const routes: Routes = [
{
path: 'view/:id',
component: ViewProvisioningEntryComponent,
data: { title: extract('View Provisioning Entry'), routeParamBreadcrumb: 'id' }
data: { title: extract('View Provisioning Entry'), routeParamBreadcrumb: 'id' },
resolve: {
provisioningEntry: ProvisioningEntryResolver,
provisioningEntryEntries: ProvisioningEntryEntriesResolver,
office: OfficesResolver,
loanProducts: LoanProductsResolver,
provisioningCategories: ProvisioningCategoriesResolver
}
},
{
path: 'journal-entries',
data: { title: extract('View Provisioning Journal Entry'), breadcrumb: 'Journal Entries', addBreadcrumbLink: false },
data: { breadcrumb: 'Journal Entries', addBreadcrumbLink: false },
children: [
{
path: 'view/:id',
component: ViewProvisioningJournalEntriesComponent,
data: { title: extract('View Provisioning Journal Entry'), routeParamBreadcrumb: 'id' }
data: { title: extract('View Provisioning Journal Entry'), routeParamBreadcrumb: 'id' },
resolve: {
provisioningJournalEntries: ProvisioningJournalEntriesResolver
}
}
]
}
]
},
{
path: 'migrate-opening-balances',
component: MigrateOpeningBalancesComponent,
data: { title: extract('Migrate Opening Balances'), breadcrumb: 'Migrate Opening Balances' }
}
]
}
])
];
/**
* Accounting Routing Module
*
* Configures the accounting routes.
*/
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
OfficesResolver,
AccountingRulesAssociationsResolver,
CurrenciesResolver,
PaymentTypesResolver,
GlAccountsResolver,
TransactionResolver,
FinancialActivityMappingsResolver,
FinancialActivityMappingsTemplateResolver,
FinancialActivityMappingResolver,
FinancialActivityMappingAndTemplateResolver,
ChartOfAccountsResolver,
ViewGlAccountResolver,
ViewAccountingRuleResolver
ChartOfAccountsTemplateResolver,
GlAccountAndChartOfAccountsTemplateResolver,
ClosingEntriesResolver,
ClosingEntryResolver,
AccountingRulesResolver,
AccountingRulesTemplateResolver,
AccountingRuleResolver,
ProvisioningEntriesResolver,
ProvisioningEntryResolver,
ProvisioningEntryEntriesResolver,
LoanProductsResolver,
ProvisioningCategoriesResolver,
ProvisioningJournalEntriesResolver
]
})
export class AccountingRoutingModule { }
@@ -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 { AccountingService } from '../accounting.service';
/**
* Accounting rule data resolver.
*/
@Injectable()
export class AccountingRuleResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the accounting rule data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.accountingService.getAccountingRule(id);
}
}
@@ -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';
/**
* Accounting rules template data resolver.
*/
@Injectable()
export class AccountingRulesTemplateResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the accounting rules template data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getAccountingRulesTemplate();
}
}
@@ -1,14 +1,16 @@
<div class="container m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
<button mat-raised-button color="primary" [routerLink]="['/accounting/accounting-rules/create']">
<mat-icon>add</mat-icon>&nbsp;&nbsp;Add Rule
<button mat-raised-button color="primary" [routerLink]="['create']">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Add Rule
</button>
</div>
<div class="container">
<div fxLayout="row" fxLayoutGap="20px">
<div fxLayout="row">
<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>
@@ -47,7 +49,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/accounting/accounting-rules/view', row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['view', row.id]" class="select-row"></tr>
</table>
@@ -1,3 +1,7 @@
table {
width: 100%;
.select-row:hover {
cursor: pointer;
}
}
@@ -1,7 +1,11 @@
/** Angular Imports */
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { AccountingService } from '../accounting.service';
import { ActivatedRoute } from '@angular/router';
/**
* Accounting rules component.
*/
@Component({
selector: 'mifosx-accounting-rules',
templateUrl: './accounting-rules.component.html',
@@ -9,37 +13,59 @@ import { AccountingService } from '../accounting.service';
})
export class AccountingRulesComponent implements OnInit {
/** Accounting rule data. */
accountingRuleData: any;
/** Columns to be displayed in accounting rules table. */
displayedColumns: string[] = ['name', 'officeName', 'debitTags', 'debitAccount', 'creditTags', 'creditAccount'];
dataSource: any;
/** Data source for accounting rules table. */
dataSource: MatTableDataSource<any>;
/** Paginator for accounting rules table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for accounting rules table. */
@ViewChild(MatSort) sort: MatSort;
constructor(private accountingService: AccountingService) { }
ngOnInit() {
this.getAccountingRules();
}
getAccountingRules() {
this.accountingService.getAccountingRules().subscribe(accountingRules => {
accountingRules.forEach((accountingRule: any) => {
accountingRule.debitTags = accountingRule.debitTags ? accountingRule.debitTags.map((debitTag: any) => debitTag.tag.name).join(', ') : '';
accountingRule.creditTags = accountingRule.creditTags ? accountingRule.creditTags.map((creditTag: any) => creditTag.tag.name).join(', ') : '';
});
this.dataSource = new MatTableDataSource(accountingRules);
this.dataSource.paginator = this.paginator;
this.dataSource.sortingDataAccessor = (accountingRule: any, property: any) => {
switch (property) {
case 'debitAccount': return accountingRule.debitAccounts[0].name;
case 'creditAccount': return accountingRule.creditAccounts[0].name;
default: return accountingRule[property];
}
};
this.dataSource.sort = this.sort;
/**
* Retrieves the accounting rules data from `resolve`.
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private route: ActivatedRoute) {
this.route.data.subscribe((data: { accountingRules: any }) => {
this.accountingRuleData = data.accountingRules;
});
}
/**
* Sets the accounting rules table.
*/
ngOnInit() {
this.setAccountingRules();
}
/**
* Initializes the data source, paginator and sorter for accounting rules table.
*/
setAccountingRules() {
this.accountingRuleData.forEach((accountingRule: any) => {
accountingRule.debitTags = accountingRule.debitTags ? accountingRule.debitTags.map((debitTag: any) => debitTag.tag.name).join(', ') : '';
accountingRule.creditTags = accountingRule.creditTags ? accountingRule.creditTags.map((creditTag: any) => creditTag.tag.name).join(', ') : '';
});
this.dataSource = new MatTableDataSource(this.accountingRuleData);
this.dataSource.paginator = this.paginator;
this.dataSource.sortingDataAccessor = (accountingRule: any, property: any) => {
switch (property) {
case 'debitAccount': return accountingRule.debitAccounts[0].name;
case 'creditAccount': return accountingRule.creditAccounts[0].name;
default: return accountingRule[property];
}
};
this.dataSource.sort = this.sort;
}
/**
* Filters data in accounting rules table based on passed value.
* @param {string} filterValue Value to filter data.
*/
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
@@ -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';
/**
* Accounting rules data resolver.
*/
@Injectable()
export class AccountingRulesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the accounting rules data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getAccountingRules();
}
}
@@ -9,19 +9,27 @@
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="48%">
<input matInput placeholder="Accounting Rule Name" required formControlName="name">
<mat-label>Accounting Rule Name</mat-label>
<input matInput required formControlName="name">
<mat-error *ngIf="accountingRuleForm.controls.name.hasError('required')">
Accounting Rule Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<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="accountingRuleForm.controls.officeId.hasError('required')">
Office is <strong>required</strong>
</mat-error>
</mat-form-field>
<div fxFlex="48%" fxLayout="row" fxLayout.lt-md="column" class="rule-wrapper">
<label fxFlex="50%">Affected GL Entry (Debit) Rule Type *</label>
<mat-label fxFlex="50%">Affected GL Entry (Debit) Rule Type *</mat-label>
<mat-radio-group fxFlex="50%" fxLayout="row" fxLayoutGap="5%" formControlName="debitRuleType">
<mat-radio-button value="fixedAccount">Fixed Account</mat-radio-button>
<mat-radio-button value="listOfAccounts">List of Accounts</mat-radio-button>
@@ -29,7 +37,8 @@
</div>
<mat-form-field fxFlex="48%" *ngIf="accountingRuleForm.controls.debitRuleType.value === 'fixedAccount'">
<mat-select placeholder="Account to Debit" required formControlName="accountToDebit">
<mat-label>Account to Debit</mat-label>
<mat-select formControlName="accountToDebit">
<mat-option *ngFor="let glAccount of glAccountData" [value]="glAccount.id">
{{ glAccount.name }}
</mat-option>
@@ -38,22 +47,23 @@
<div fxFlex="48%" fxLayout="row" fxLayoutGap="2%" fxLayout.lt-md="column" *ngIf="accountingRuleForm.controls.debitRuleType.value === 'listOfAccounts'">
<mat-form-field fxFlex="50%">
<mat-select placeholder="Debit Tags" required formControlName="debitTags" multiple>
<mat-label>Debit Tags</mat-label>
<mat-select formControlName="debitTags" multiple>
<mat-option *ngFor="let debitTag of debitTagData" [value]="debitTag.id">
{{ debitTag.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="50%" class="entries-wrapper">
<mat-checkbox labelPosition="before" required formControlName="allowMultipleDebitEntries" class="entries">
<div fxFlex="50%" ngClass.gt-md="entries-wrapper">
<mat-checkbox labelPosition="before" formControlName="allowMultipleDebitEntries" class="entries">
Multiple Debit Entries Allowed
</mat-checkbox>
</div>
</div>
<div fxFlex="48%" fxLayout="row" fxLayout.lt-md="column" class="rule-wrapper">
<label fxFlex="50%">Affected GL Entry (Credit) Rule Type *</label>
<mat-label fxFlex="50%">Affected GL Entry (Credit) Rule Type *</mat-label>
<mat-radio-group fxFlex="50%" fxLayout="row" fxLayoutGap="5%" formControlName="creditRuleType">
<mat-radio-button value="fixedAccount">Fixed Account</mat-radio-button>
<mat-radio-button value="listOfAccounts">List of Accounts</mat-radio-button>
@@ -61,7 +71,8 @@
</div>
<mat-form-field fxFlex="48%" *ngIf="accountingRuleForm.controls.creditRuleType.value === 'fixedAccount'">
<mat-select placeholder="Account to Credit" required formControlName="accountToCredit">
<mat-label>Account to Credit</mat-label>
<mat-select formControlName="accountToCredit">
<mat-option *ngFor="let glAccount of glAccountData" [value]="glAccount.id">
{{ glAccount.name }}
</mat-option>
@@ -70,22 +81,24 @@
<div fxFlex="48%" fxLayout="row" fxLayoutGap="2%" fxLayout.lt-md="column" *ngIf="accountingRuleForm.controls.creditRuleType.value === 'listOfAccounts'">
<mat-form-field fxFlex="50%">
<mat-select placeholder="Credit Tags" required formControlName="creditTags" multiple>
<mat-label>Credit Tags</mat-label>
<mat-select formControlName="creditTags" multiple>
<mat-option *ngFor="let creditTag of creditTagData" [value]="creditTag.id">
{{ creditTag.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="50%" class="entries-wrapper">
<mat-checkbox labelPosition="before" required formControlName="allowMultipleCreditEntries" class="entries">
<div fxFlex="50%" ngClass.gt-md="entries-wrapper">
<mat-checkbox labelPosition="before" formControlName="allowMultipleCreditEntries" class="entries">
Multiple Credit Entries Allowed
</mat-checkbox>
</div>
</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>
@@ -93,8 +106,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/accounting-rules']">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]="!accountingRuleForm.valid">Submit</button>
</mat-card-actions>
</form>
@@ -2,11 +2,6 @@
margin: 17.5px 0 0 0;
}
.additional-selection {
opacity: 0.75;
font-size: 0.75em;
}
.entries-wrapper {
position: relative;
@@ -1,17 +1,17 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, ValidatorFn, ValidationErrors } from '@angular/forms';
import { Router } from '@angular/router';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
const oneOfTheFieldsIsRequiredValidator: ValidatorFn = (accountingRuleForm: FormGroup): ValidationErrors | null => {
const accountToDebit = accountingRuleForm.controls.accountToDebit.value;
const debitTags = accountingRuleForm.controls.debitTags.value;
const accountToCredit = accountingRuleForm.controls.accountToCredit.value;
const creditTags = accountingRuleForm.controls.creditTags.value;
return ((accountToDebit || debitTags) && (accountToCredit || creditTags)) ? null : { 'error': true };
};
/** Custom Validators */
import { oneOfTheFieldsIsRequiredValidator } from '../one-of-the-fields-is-required.validator';
/**
* Create accounting rule component.
*/
@Component({
selector: 'mifosx-create-rule',
templateUrl: './create-rule.component.html',
@@ -19,23 +19,48 @@ const oneOfTheFieldsIsRequiredValidator: ValidatorFn = (accountingRuleForm: Form
})
export class CreateRuleComponent implements OnInit {
/** Accounting rule form. */
accountingRuleForm: FormGroup;
/** Office data. */
officeData: any;
/** GL Account data. */
glAccountData: any;
/** Debit tag data. */
debitTagData: any;
/** Credit tag data. */
creditTagData: any;
/**
* Retrieves the offices, gl accounts, debit tags and credit tags 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 router: Router) { }
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: { accountingRulesTemplate: any }) => {
this.officeData = data.accountingRulesTemplate.allowedOffices;
this.glAccountData = data.accountingRulesTemplate.allowedAccounts;
this.debitTagData = data.accountingRulesTemplate.allowedDebitTagOptions;
this.creditTagData = data.accountingRulesTemplate.allowedCreditTagOptions;
});
}
/**
* Creates and sets accounting rules form.
*/
ngOnInit() {
this.createAccountingRulesForm();
this.getAccountingRulesTemplate();
this.createAccountingRuleForm();
this.setAccountingRulesForm();
}
createAccountingRulesForm() {
/**
* Creates accounting rule form.
*/
createAccountingRuleForm() {
this.accountingRuleForm = this.formBuilder.group({
'name': ['', Validators.required],
'officeId': ['', Validators.required],
@@ -51,40 +76,52 @@ export class CreateRuleComponent implements OnInit {
}, { validator: oneOfTheFieldsIsRequiredValidator });
}
/**
* Sets accounting rule form for selected accounting rule type.
*/
setAccountingRulesForm() {
this.accountingRuleForm.get('debitRuleType').valueChanges.subscribe(debitRuleType => {
this.accountingRuleForm.get('debitRuleType').valueChanges.subscribe((debitRuleType: string) => {
if (debitRuleType === 'fixedAccount') {
this.accountingRuleForm.get('debitTags').reset();
this.accountingRuleForm.get('allowMultipleDebitEntries').reset();
} else {
this.accountingRuleForm.get('accountToDebit').reset();
this.accountingRuleForm.get('allowMultipleDebitEntries').setValue(false);
}
});
this.accountingRuleForm.get('creditRuleType').valueChanges.subscribe(creditRuleType => {
this.accountingRuleForm.get('creditRuleType').valueChanges.subscribe((creditRuleType: string) => {
if (creditRuleType === 'fixedAccount') {
this.accountingRuleForm.get('creditTags').reset();
this.accountingRuleForm.get('allowMultipleCreditEntries').reset();
} else {
this.accountingRuleForm.get('accountToCredit').reset();
this.accountingRuleForm.get('allowMultipleCreditEntries').setValue(false);
}
});
}
getAccountingRulesTemplate() {
this.accountingService.getAccountingRulesTemplate().subscribe((accountingRulesData: any) => {
this.officeData = accountingRulesData.allowedOffices;
this.glAccountData = accountingRulesData.allowedAccounts;
this.debitTagData = accountingRulesData.allowedDebitTagOptions;
this.creditTagData = accountingRulesData.allowedCreditTagOptions;
});
}
/**
* Submits the accounting rule form and creates accounting rule,
* if successful redirects to view created rule.
*/
submit() {
const accountingRule = this.accountingRuleForm.value;
if (accountingRule.debitRuleType === 'fixedAccount') {
delete accountingRule.debitTags;
delete accountingRule.allowMultipleDebitEntries;
} else {
delete accountingRule.accountToDebit;
}
if (accountingRule.creditRuleType === 'fixedAccount') {
delete accountingRule.creditTags;
delete accountingRule.allowMultipleCreditEntries;
} else {
delete accountingRule.accountToCredit;
}
delete accountingRule.debitRuleType;
delete accountingRule.creditRuleType;
this.accountingService.createAccountingRule(accountingRule).subscribe((response: any) => {
this.router.navigate(['/accounting/accounting-rules/view', response.resourceId]);
this.router.navigate(['../view', response.resourceId], { relativeTo: this.route });
});
}
@@ -9,19 +9,27 @@
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="48%">
<input matInput placeholder="Accounting Rule Name" required formControlName="name">
<mat-label>Accounting Rule Name</mat-label>
<input matInput required formControlName="name">
<mat-error *ngIf="accountingRuleForm.controls.name.hasError('required')">
Accounting Rule Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<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="accountingRuleForm.controls.officeId.hasError('required')">
Office is <strong>required</strong>
</mat-error>
</mat-form-field>
<div fxFlex="48%" fxLayout="row" fxLayout.lt-md="column" class="rule-wrapper">
<label fxFlex="50%">Affected GL Entry (Debit) Rule Type *</label>
<mat-label fxFlex="50%">Affected GL Entry (Debit) Rule Type *</mat-label>
<mat-radio-group fxFlex="50%" fxLayout="row" fxLayoutGap="5%" formControlName="debitRuleType">
<mat-radio-button value="fixedAccount">Fixed Account</mat-radio-button>
<mat-radio-button value="listOfAccounts">List of Accounts</mat-radio-button>
@@ -29,7 +37,8 @@
</div>
<mat-form-field fxFlex="48%" *ngIf="accountingRuleForm.controls.debitRuleType.value === 'fixedAccount'">
<mat-select placeholder="Account to Debit" required formControlName="accountToDebit">
<mat-label>Account to Debit</mat-label>
<mat-select formControlName="accountToDebit">
<mat-option *ngFor="let glAccount of glAccountData" [value]="glAccount.id">
{{ glAccount.name }}
</mat-option>
@@ -38,22 +47,23 @@
<div fxFlex="48%" fxLayout="row" fxLayoutGap="2%" fxLayout.lt-md="column" *ngIf="accountingRuleForm.controls.debitRuleType.value === 'listOfAccounts'">
<mat-form-field fxFlex="50%">
<mat-select placeholder="Debit Tags" required formControlName="debitTags" multiple>
<mat-label>Debit Tags</mat-label>
<mat-select formControlName="debitTags" multiple>
<mat-option *ngFor="let debitTag of debitTagData" [value]="debitTag.id">
{{ debitTag.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="50%" class="entries-wrapper">
<mat-checkbox labelPosition="before" required formControlName="allowMultipleDebitEntries" class="entries">
<div fxFlex="50%" ngClass.gt-md="entries-wrapper">
<mat-checkbox labelPosition="before" formControlName="allowMultipleDebitEntries" class="entries">
Multiple Debit Entries Allowed
</mat-checkbox>
</div>
</div>
<div fxFlex="48%" fxLayout="row" fxLayout.lt-md="column" class="rule-wrapper">
<label fxFlex="50%">Affected GL Entry (Credit) Rule Type *</label>
<mat-label fxFlex="50%">Affected GL Entry (Credit) Rule Type *</mat-label>
<mat-radio-group fxFlex="50%" fxLayout="row" fxLayoutGap="5%" formControlName="creditRuleType">
<mat-radio-button value="fixedAccount">Fixed Account</mat-radio-button>
<mat-radio-button value="listOfAccounts">List of Accounts</mat-radio-button>
@@ -61,7 +71,8 @@
</div>
<mat-form-field fxFlex="48%" *ngIf="accountingRuleForm.controls.creditRuleType.value === 'fixedAccount'">
<mat-select placeholder="Account to Credit" required formControlName="accountToCredit">
<mat-label>Account to Credit</mat-label>
<mat-select formControlName="accountToCredit">
<mat-option *ngFor="let glAccount of glAccountData" [value]="glAccount.id">
{{ glAccount.name }}
</mat-option>
@@ -70,22 +81,24 @@
<div fxFlex="48%" fxLayout="row" fxLayoutGap="2%" fxLayout.lt-md="column" *ngIf="accountingRuleForm.controls.creditRuleType.value === 'listOfAccounts'">
<mat-form-field fxFlex="50%">
<mat-select placeholder="Credit Tags" required formControlName="creditTags" multiple>
<mat-label>Credit Tags</mat-label>
<mat-select formControlName="creditTags" multiple>
<mat-option *ngFor="let creditTag of creditTagData" [value]="creditTag.id">
{{ creditTag.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="50%" class="entries-wrapper">
<mat-checkbox labelPosition="before" required formControlName="allowMultipleCreditEntries" class="entries">
<div fxFlex="50%" ngClass.gt-md="entries-wrapper">
<mat-checkbox labelPosition="before" formControlName="allowMultipleCreditEntries" class="entries">
Multiple Credit Entries Allowed
</mat-checkbox>
</div>
</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>
@@ -93,8 +106,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/accounting-rules/view', accountingRule?.id]">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]="accountingRuleForm.pristine || !accountingRuleForm.valid">Submit</button>
</mat-card-actions>
</form>
@@ -2,11 +2,6 @@
margin: 17.5px 0 0 0;
}
.additional-selection {
opacity: 0.75;
font-size: 0.75em;
}
.entries-wrapper {
position: relative;
@@ -1,17 +1,17 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, ValidatorFn, ValidationErrors } from '@angular/forms';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
const oneOfTheFieldsIsRequiredValidator: ValidatorFn = (accountingRuleForm: FormGroup): ValidationErrors | null => {
const accountToDebit = accountingRuleForm.controls.accountToDebit.value;
const debitTags = accountingRuleForm.controls.debitTags.value;
const accountToCredit = accountingRuleForm.controls.accountToCredit.value;
const creditTags = accountingRuleForm.controls.creditTags.value;
return ((accountToDebit || debitTags) && (accountToCredit || creditTags)) ? null : { 'error': true };
};
/** Custom Validators */
import { oneOfTheFieldsIsRequiredValidator } from '../one-of-the-fields-is-required.validator';
/**
* Edit accounting rule component.
*/
@Component({
selector: 'mifosx-edit-rule',
templateUrl: './edit-rule.component.html',
@@ -19,29 +19,54 @@ const oneOfTheFieldsIsRequiredValidator: ValidatorFn = (accountingRuleForm: Form
})
export class EditRuleComponent implements OnInit {
/** Accounting rule form. */
accountingRuleForm: FormGroup;
/** Accounting rule. */
accountingRule: any;
/** Office data. */
officeData: any;
/** GL Account data. */
glAccountData: any;
/** Debit tag data. */
debitTagData: any;
/** Credit tag data. */
creditTagData: any;
constructor(private route: ActivatedRoute,
private formBuilder: FormBuilder,
/**
* Retrieves the offices, gl accounts, debit tags, credit tags and accounting rule 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: { accountingRule: any }) => {
this.accountingRule = data.accountingRule;
});
this.route.data.subscribe((data: {
accountingRulesTemplate: any,
accountingRule: any
}) => {
this.officeData = data.accountingRulesTemplate.allowedOffices;
this.glAccountData = data.accountingRulesTemplate.allowedAccounts;
this.debitTagData = data.accountingRulesTemplate.allowedDebitTagOptions;
this.creditTagData = data.accountingRulesTemplate.allowedCreditTagOptions;
this.accountingRule = data.accountingRule;
});
}
/**
* Creates and sets accounting rule form.
*/
ngOnInit() {
this.createAccountingRulesForm();
this.getAccountingRulesTemplate();
this.createAccountingRuleForm();
this.setAccountingRulesForm();
}
createAccountingRulesForm() {
/**
* Creates accounting rule form.
*/
createAccountingRuleForm() {
this.accountingRuleForm = this.formBuilder.group({
'name': [this.accountingRule.name, Validators.required],
'officeId': [this.accountingRule.officeId, Validators.required],
@@ -57,12 +82,16 @@ export class EditRuleComponent implements OnInit {
}, { validator: oneOfTheFieldsIsRequiredValidator });
}
/**
* Sets accounting rule form for selected accounting rule type.
*/
setAccountingRulesForm() {
this.accountingRuleForm.get('debitRuleType').valueChanges.subscribe(debitRuleType => {
if (debitRuleType === 'fixedAccount') {
this.accountingRuleForm.get('debitTags').reset();
this.accountingRuleForm.get('allowMultipleDebitEntries').reset();
} else {
this.accountingRuleForm.get('accountToDebit').reset();
this.accountingRuleForm.get('allowMultipleDebitEntries').setValue(false);
}
});
@@ -71,6 +100,7 @@ export class EditRuleComponent implements OnInit {
this.accountingRuleForm.get('creditTags').reset();
this.accountingRuleForm.get('allowMultipleCreditEntries').reset();
} else {
this.accountingRuleForm.get('accountToCredit').reset();
this.accountingRuleForm.get('allowMultipleCreditEntries').setValue(false);
}
});
@@ -94,21 +124,28 @@ export class EditRuleComponent implements OnInit {
}
}
getAccountingRulesTemplate() {
this.accountingService.getAccountingRulesTemplate().subscribe((accountingRulesData: any) => {
this.officeData = accountingRulesData.allowedOffices;
this.glAccountData = accountingRulesData.allowedAccounts;
this.debitTagData = accountingRulesData.allowedDebitTagOptions;
this.creditTagData = accountingRulesData.allowedCreditTagOptions;
});
}
/**
* Submits the accounting rule form and updates accounting rule,
* if successful redirects to view updated rule.
*/
submit() {
const accountingRule = this.accountingRuleForm.value;
if (accountingRule.debitRuleType === 'fixedAccount') {
delete accountingRule.debitTags;
delete accountingRule.allowMultipleDebitEntries;
} else {
delete accountingRule.accountToDebit;
}
if (accountingRule.creditRuleType === 'fixedAccount') {
delete accountingRule.creditTags;
delete accountingRule.allowMultipleCreditEntries;
} else {
delete accountingRule.accountToCredit;
}
delete accountingRule.debitRuleType;
delete accountingRule.creditRuleType;
this.accountingService.updateAccountingRule(this.accountingRule.id, accountingRule).subscribe((response: any) => {
this.router.navigate(['/accounting/accounting-rules/view', response.resourceId]);
this.router.navigate(['../../', response.resourceId], { relativeTo: this.route });
});
}
@@ -0,0 +1,11 @@
/** Angular Imports */
import { FormGroup, ValidatorFn, ValidationErrors } from '@angular/forms';
/** Validates that one of the fields for debit or credit is filled for a given accounting rule type. */
export const oneOfTheFieldsIsRequiredValidator: ValidatorFn = (accountingRuleForm: FormGroup): ValidationErrors | null => {
const accountToDebit = accountingRuleForm.controls.accountToDebit.value;
const debitTags = accountingRuleForm.controls.debitTags.value;
const accountToCredit = accountingRuleForm.controls.accountToCredit.value;
const creditTags = accountingRuleForm.controls.creditTags.value;
return ((accountToDebit || debitTags) && (accountToCredit || creditTags)) ? null : { 'error': true };
};
@@ -1,9 +1,11 @@
<div fxLayout="row" fxLayoutAlign="end" fxLayoutGap="2%" fxLayout.lt-md="column" class="container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['/accounting/accounting-rules/view', accountingRule?.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" (click)="deleteAccountingRule()">
<mat-icon>delete</mat-icon>&nbsp;&nbsp;Delete
<fa-icon icon="trash"></fa-icon>&nbsp;&nbsp;
Delete
</button>
</div>
@@ -25,11 +27,11 @@
{{ accountingRule.officeName }}
</div>
<div fxFlex="50%" class="header">
<div fxFlex="50%" class="header" *ngIf="accountingRule.description">
Description
</div>
<div fxFlex="50%">
<div fxFlex="50%" *ngIf="accountingRule.description">
{{ accountingRule.description }}
</div>
@@ -2,9 +2,9 @@
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 Services */
import { AccountingService } from '../../accounting.service';
/** Custom Components */
import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component';
/**
* View accounting rule component.
*/
@Component({
selector: 'mifosx-view-rule',
templateUrl: './view-rule.component.html',
@@ -12,11 +19,19 @@ import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dial
})
export class ViewRuleComponent implements OnInit {
/** Accounting rule. */
accountingRule: any;
constructor(private route: ActivatedRoute,
/**
* Retrieves the accounting rule 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,
private accountingService: AccountingService,
public dialog: MatDialog) {
this.route.data.subscribe((data: { accountingRule: any }) => {
this.accountingRule = data.accountingRule;
@@ -26,6 +41,9 @@ export class ViewRuleComponent implements OnInit {
ngOnInit() {
}
/**
* Deletes the accounting rule and redirects to accounting rules.
*/
deleteAccountingRule() {
const deleteAccountingRuleDialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: `accounting rule ${this.accountingRule.id}` }
@@ -1,19 +0,0 @@
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AccountingService } from '../../accounting.service';
@Injectable()
export class ViewAccountingRuleResolver implements Resolve<Object> {
constructor(private accountingService: AccountingService) {}
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.accountingService.getAccountingRule(id);
}
}
+94 -71
View File
@@ -1,84 +1,107 @@
<mat-card class="container">
<div class="container">
<div fxLayout="row" fxLayout.lt-md="column">
<mat-card>
<div fxFlex="50%">
<div fxLayout="row" fxLayout.lt-md="column">
<mat-nav-list>
<div fxFlex="50%">
<mat-list-item [routerLink]="['/accounting/frequent-postings']">
<mat-icon matListIcon>repeat</mat-icon>
<h4 matLine>Frequent Postings</h4>
<p matLine>These are predefined postings</p>
</mat-list-item>
<mat-nav-list>
<mat-list-item [routerLink]="['/accounting/journal-entries/create']">
<mat-icon matListIcon>add</mat-icon>
<h4 matLine>Create Journal Entries</h4>
<p matLine>Manual journal entry transactions recorded in a journal</p>
</mat-list-item>
<mat-list-item [routerLink]="['journal-entries/frequent-postings']">
<mat-icon matListIcon>
<fa-icon icon="sync" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Frequent Postings</h4>
<p matLine>These are predefined postings</p>
</mat-list-item>
<mat-list-item [routerLink]="['/accounting/journal-entries']">
<mat-icon matListIcon>search</mat-icon>
<h4 matLine>Search Journal Entries</h4>
<p matLine>Advanced search option for journal entries</p>
</mat-list-item>
<mat-list-item [routerLink]="['journal-entries/create']">
<mat-icon matListIcon>
<fa-icon icon="plus" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Create Journal Entries</h4>
<p matLine>Manual journal entry transactions recorded in a journal</p>
</mat-list-item>
<mat-list-item [routerLink]="['/accounting/financial-activity-mappings']">
<mat-icon matListIcon>link</mat-icon>
<h4 matLine>Accounts Linked to Financial Activities</h4>
<p matLine>List of Financial Activities and GL Account Mappings</p>
</mat-list-item>
<mat-list-item [routerLink]="['journal-entries']">
<mat-icon matListIcon>
<fa-icon icon="search" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Search Journal Entries</h4>
<p matLine>Advanced search option for journal entries</p>
</mat-list-item>
<mat-list-item [routerLink]="['/accounting/migrate-opening-balances']">
<mat-icon matListIcon>monetization_on</mat-icon>
<h4 matLine>Migrate Opening Balances (Office-wise)</h4>
<p matLine>Set or update office-level opening balances for GL accounts</p>
</mat-list-item>
<mat-list-item [routerLink]="['financial-activity-mappings']">
<mat-icon matListIcon>
<fa-icon icon="link" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Accounts Linked to Financial Activities</h4>
<p matLine>List of Financial Activities and GL Account Mappings</p>
</mat-list-item>
</mat-nav-list>
<mat-list-item [routerLink]="['migrate-opening-balances']">
<mat-icon matListIcon>
<fa-icon icon="hand-holding-usd" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Migrate Opening Balances (Office-wise)</h4>
<p matLine>Set or update office-level opening balances for GL accounts</p>
</mat-list-item>
</mat-nav-list>
</div>
<div fxFlex="50%">
<mat-nav-list>
<mat-list-item [routerLink]="['chart-of-accounts']">
<mat-icon matListIcon>
<fa-icon icon="sitemap" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Chart of Accounts</h4>
<p matLine>List of accounts used by the organisation</p>
</mat-list-item>
<mat-list-item [routerLink]="['closing-entries']">
<mat-icon matListIcon>
<fa-icon icon="archive" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Closing Entries</h4>
<p matLine>Journal entries made at the end of accounting period</p>
</mat-list-item>
<mat-list-item [routerLink]="['accounting-rules']">
<mat-icon matListIcon>
<fa-icon icon="list" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Accounting Rules</h4>
<p matLine>List of all accounting rules</p>
</mat-list-item>
<mat-list-item [routerLink]="['periodic-accruals']">
<mat-icon matListIcon>
<fa-icon icon="calendar" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Accruals</h4>
<p matLine>Accrues income, expenses and liabilities as on the provided date</p>
</mat-list-item>
<mat-list-item [routerLink]="['provisioning-entries']">
<mat-icon matListIcon>
<fa-icon icon="cogs" size="sm"></fa-icon>
</mat-icon>
<h4 matLine>Provisioning Entries</h4>
<p matLine>Create provisioning entries</p>
</mat-list-item>
</mat-nav-list>
</div>
</div>
<div fxFlex="50%">
<mat-nav-list>
<mat-list-item [routerLink]="['/accounting/chart-of-accounts']">
<mat-icon matListIcon>device_hub</mat-icon>
<h4 matLine>Chart of Accounts</h4>
<p matLine>List of accounts used by the organisation</p>
</mat-list-item>
<mat-list-item [routerLink]="['/accounting/closing-entries']">
<mat-icon matListIcon>archive</mat-icon>
<h4 matLine>Closing Entries</h4>
<p matLine>Journal entries made at the end of accounting period</p>
</mat-list-item>
<mat-list-item [routerLink]="['/accounting/accounting-rules']">
<mat-icon matListIcon>list</mat-icon>
<h4 matLine>Accounting Rules</h4>
<p matLine>List of all accounting rules</p>
</mat-list-item>
<mat-list-item [routerLink]="['/accounting/periodic-accruals']">
<mat-icon matListIcon>calendar_today</mat-icon>
<h4 matLine>Accruals</h4>
<p matLine>Accrues income, expenses and liabilities as on the provided date</p>
</mat-list-item>
<mat-list-item [routerLink]="['/accounting/provisioning-entries']">
<mat-icon matListIcon>settings_backup_restore</mat-icon>
<h4 matLine>Provisioning Entries</h4>
<p matLine>Create provisioning entries</p>
</mat-list-item>
</mat-nav-list>
</div>
</div>
</mat-card>
</mat-card>
</div>
@@ -1,5 +1,9 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
/**
* Accounting component.
*/
@Component({
selector: 'mifosx-accounting',
templateUrl: './accounting.component.html',
+19 -16
View File
@@ -1,22 +1,24 @@
/** Angular Imports */
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CoreModule } from '../core/core.module';
import { AccountingComponent } from './accounting.component';
/** Custom Modules */
import { SharedModule } from '../shared/shared.module';
import { AccountingRoutingModule } from './accounting-routing.module';
/** Custom Components */
import { AccountingComponent } from './accounting.component';
import { FrequentPostingsComponent } from './frequent-postings/frequent-postings.component';
import { CreateJournalEntryComponent } from './create-journal-entry/create-journal-entry.component';
import { SearchJournalEntryComponent } from './search-journal-entry/search-journal-entry.component';
import { ViewTransactionComponent } from './view-transaction/view-transaction.component';
import { RevertTransactionComponent } from './revert-transaction/revert-transaction.component';
import { ViewJournalEntryComponent } from './view-journal-entry/view-journal-entry.component';
import { CreateJournalEntryComponent } from './create-journal-entry/create-journal-entry.component';
import { SearchJournalEntryComponent } from './search-journal-entry/search-journal-entry.component';
import { FinancialActivityMappingsComponent } from './financial-activity-mappings/financial-activity-mappings.component';
import { CreateFinancialActivityMappingComponent } from './financial-activity-mappings/create-financial-activity-mapping/create-financial-activity-mapping.component';
import { ViewFinancialActivityMappingComponent } from './financial-activity-mappings/view-financial-activity-mapping/view-financial-activity-mapping.component';
import { EditFinancialActivityMappingComponent } from './financial-activity-mappings/edit-financial-activity-mapping/edit-financial-activity-mapping.component';
import { MigrateOpeningBalancesComponent } from './migrate-opening-balances/migrate-opening-balances.component';
import { ChartOfAccountsComponent } from './chart-of-accounts/chart-of-accounts.component';
import { CreateGlAccountComponent } from './chart-of-accounts/create-gl-account/create-gl-account.component';
import { ViewGlAccountComponent } from './chart-of-accounts/view-gl-account/view-gl-account.component';
import { EditGlAccountComponent } from './chart-of-accounts/edit-gl-account/edit-gl-account.component';
@@ -33,28 +35,30 @@ import { ProvisioningEntriesComponent } from './provisioning-entries/provisionin
import { CreateProvisioningEntryComponent } from './provisioning-entries/create-provisioning-entry/create-provisioning-entry.component';
import { ViewProvisioningEntryComponent } from './provisioning-entries/view-provisioning-entry/view-provisioning-entry.component';
import { ViewProvisioningJournalEntriesComponent } from './provisioning-entries/view-provisioning-journal-entries/view-provisioning-journal-entries.component';
import { MigrateOpeningBalancesComponent } from './migrate-opening-balances/migrate-opening-balances.component';
/**
* Accounting Module
*
* All components related to accounting functions should be declared here.
*/
@NgModule({
imports: [
CoreModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
SharedModule,
AccountingRoutingModule
],
declarations: [
AccountingComponent,
FrequentPostingsComponent,
CreateJournalEntryComponent,
SearchJournalEntryComponent,
ViewTransactionComponent,
RevertTransactionComponent,
ViewJournalEntryComponent,
CreateJournalEntryComponent,
SearchJournalEntryComponent,
FinancialActivityMappingsComponent,
CreateFinancialActivityMappingComponent,
ViewFinancialActivityMappingComponent,
EditFinancialActivityMappingComponent,
MigrateOpeningBalancesComponent,
ChartOfAccountsComponent,
CreateGlAccountComponent,
ViewGlAccountComponent,
@@ -71,8 +75,7 @@ import { MigrateOpeningBalancesComponent } from './migrate-opening-balances/migr
ProvisioningEntriesComponent,
CreateProvisioningEntryComponent,
ViewProvisioningEntryComponent,
ViewProvisioningJournalEntriesComponent,
MigrateOpeningBalancesComponent
ViewProvisioningJournalEntriesComponent
],
entryComponents: [
RevertTransactionComponent,
+251 -74
View File
@@ -1,19 +1,38 @@
/** TODO: Separate services for feature modules for cleaner accounting service. */
/** Angular Imports */
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs';
/**
* Accounting service.
*/
@Injectable({
providedIn: 'root'
})
export class AccountingService {
/**
* @param {HttpClient} http Http Client to send requests.
*/
constructor(private http: HttpClient) { }
/**
* @returns {Observable<any>} Offices data ordered by id.
*/
getOffices(): Observable<any> {
return this.http.get('/offices?orderBy=id');
const httpParams = new HttpParams().set('orderBy', 'id');
return this.http.get('/offices', { params: httpParams });
}
getAccountingRules(useHttpParams?: boolean): Observable<any> {
/**
* @param {boolean} useHttpParams True if http params should be used.
* @returns {Observable<any>} Accounting rules data.
*/
getAccountingRules(useHttpParams: boolean = false): Observable<any> {
let httpParams = new HttpParams();
if (useHttpParams) {
httpParams = httpParams.set('associations', 'all');
@@ -21,45 +40,80 @@ export class AccountingService {
return this.http.get('/accountingrules', { params: httpParams });
}
/**
* @returns {Observable<any>} Currencies data.
*/
getCurrencies(): Observable<any> {
return this.http.get('/currencies');
}
/**
* @returns {Observable<any>} Payment types data.
*/
getPaymentTypes(): Observable<any> {
return this.http.get('/paymenttypes');
}
/**
* @param {any} journalEntry Journal entry to be created.
* @returns {Observable<any>}
*/
createJournalEntry(journalEntry: any): Observable<any> {
return this.http.post('/journalentries', journalEntry);
}
getJournalEntry(transactionId: string) {
let httpParams = new HttpParams();
httpParams = httpParams.set('transactionId', transactionId);
httpParams = httpParams.set('transactionDetails', 'true');
/**
* @returns {Observable<any>} GL Accounts.
*/
getGlAccounts(): Observable<any> {
const httpParams = new HttpParams()
.set('manualEntriesAllowed', 'true')
.set('usage', '1')
.set('disabled', 'false');
return this.http.get(`/glaccounts`, { params: httpParams });
}
/**
* @param transactionId Transaction ID of journal entries.
* @returns {Observable<any>} Journal Entries.
*/
getJournalEntry(transactionId: string): Observable<any> {
const httpParams = new HttpParams()
.set('transactionId', transactionId)
.set('transactionDetails', 'true');
return this.http.get(`/journalentries`, { params: httpParams });
}
// filterBy: officeId, glAccountId, manualEntriesOnly, fromDate, toDate, transactionId
getJournalEntries(filterBy: any, orderBy: string, sortOrder: string, offset: number, limit: number) {
/**
* @param {any} filterBy Properties by which entries should be filtered.
* @param {string} orderBy Property by which entries should be sorted.
* @param {string} sortOrder Sort order: ascending or descending.
* @param {number} offset Page offset.
* @param {number} limit Number of entries within the page.
* @returns {Observable<any>} Journal Entries.
*/
getJournalEntries(filterBy: any, orderBy: string, sortOrder: string, offset: number, limit: number): Observable<any> {
let httpParams = new HttpParams()
.set('offset', offset.toString())
.set('limit', limit.toString())
.set('sortOrder', sortOrder)
.set('orderBy', orderBy);
// filterBy: officeId, glAccountId, manualEntriesOnly, fromDate, toDate, transactionId
filterBy.forEach(function (filter: any) {
if (filter.value) {
httpParams = httpParams.set(filter.type, filter.value);
}
});
return this.http.get('/journalentries', {
params: httpParams
});
return this.http.get('/journalentries', { params: httpParams });
}
revertTransaction(transactionId: string, comments: string) {
let httpParams = new HttpParams();
httpParams = httpParams.set('command', 'reverse');
/**
* @param {string} transactionId Transaction ID to be reverted.
* @param {string} comments Any comments.
* @returns {Observable<any>}
*/
revertTransaction(transactionId: string, comments: string): Observable<any> {
const httpParams = new HttpParams().set('command', 'reverse');
let data = {};
if (comments) {
data = { comments: comments };
@@ -67,158 +121,281 @@ export class AccountingService {
return this.http.post(`/journalentries/${transactionId}`, data, { params: httpParams });
}
getGlAccounts() {
let httpParams = new HttpParams();
httpParams = httpParams.set('manualEntriesAllowed', 'true');
httpParams = httpParams.set('usage', '1');
httpParams = httpParams.set('disabled', 'false');
return this.http.get(`/glaccounts`, { params: httpParams });
}
getFinancialActivityAccounts() {
/**
* @returns {Observable<any>} Financial activity accounts.
*/
getFinancialActivityAccounts(): Observable<any> {
return this.http.get('/financialactivityaccounts');
}
getFinancialActivityAccountsTemplate() {
/**
* @returns {Observable<any>} Financial activity accounts template.
*/
getFinancialActivityAccountsTemplate(): Observable<any> {
return this.http.get('/financialactivityaccounts/template');
}
createFinancialActivityAccount(financialActivityAccount: any) {
/**
* @param {any} financialActivityAccount Financial activity account to be created.
* @returns {Observable<any>}
*/
createFinancialActivityAccount(financialActivityAccount: any): Observable<any> {
return this.http.post('/financialactivityaccounts', financialActivityAccount);
}
getFinancialActivityAccount(financialActivityAccountId: number, template: boolean) {
/**
* @param {string} financialActivityAccountId Financial activity account ID of financial activity account.
* @param {boolean} template True if template is required.
* @returns {Observable<any>} Financial activity account.
*/
getFinancialActivityAccount(financialActivityAccountId: string, template: boolean = false): Observable<any> {
const httpParams = new HttpParams().set('template', template.toString());
return this.http.get(`/financialactivityaccounts/${financialActivityAccountId}`, { params: httpParams });
}
updateFinancialActivityAccount(financialActivityAccountId: number, financialActivityAccount: any) {
/**
* @param {string} financialActivityAccountId Financial activity account ID of financial activity account to be updated.
* @param {any} financialActivityAccount Financial activity account to be updated.
* @returns {Observable<any>}
*/
updateFinancialActivityAccount(financialActivityAccountId: string, financialActivityAccount: any): Observable<any> {
return this.http.put(`/financialactivityaccounts/${financialActivityAccountId}`, financialActivityAccount);
}
deleteFinancialActivityAccount(financialActivityAccountId: number) {
/**
* @param {string} financialActivityAccountId Financial activity account ID of financial activity account to be deleted.
* @returns {Observable<any>}
*/
deleteFinancialActivityAccount(financialActivityAccountId: string): Observable<any> {
return this.http.delete(`/financialactivityaccounts/${financialActivityAccountId}`);
}
getChartOfAccounts() {
/**
* @param {string} officeId Office ID to retrive opening balances accounts for.
* @returns {Observable<any>}
*/
retrieveOpeningBalances(officeId: string): Observable<any> {
const httpParams = new HttpParams().set('officeId', officeId);
return this.http.get('/journalentries/openingbalance', { params: httpParams });
}
/**
* @param {any} openingBalances Opening balances for office accounts to be defined.
* @returns {Observable<any>}
*/
defineOpeningBalances(openingBalances: any): Observable<any> {
const httpParams = new HttpParams().set('command', 'defineOpeningBalance');
return this.http.post(`/journalentries`, openingBalances, { params: httpParams });
}
/**
* @returns {Observable<any>} GL accounts.
*/
getChartOfAccounts(): Observable<any> {
return this.http.get('/glaccounts');
}
getChartOfAccountsTemplate() {
/**
* @returns {Observable<any>} GL accounts template.
*/
getChartOfAccountsTemplate(): Observable<any> {
return this.http.get('/glaccounts/template');
}
createGlAccount(glAccount: any) {
/**
* @param {any} glAccount GL account to be created.
* @returns {Observable<any>}
*/
createGlAccount(glAccount: any): Observable<any> {
return this.http.post('/glaccounts', glAccount);
}
getGlAccount(glAccountId: string, template: boolean = false) {
/**
* @param {string} glAccountId GL account ID of gl account.
* @param {boolean} template True if template is required.
* @returns {Observable<any>} GL account.
*/
getGlAccount(glAccountId: string, template: boolean = false): Observable<any> {
const httpParams = new HttpParams().set('template', template.toString());
return this.http.get(`/glaccounts/${glAccountId}`, { params: httpParams });
}
updateGlAccount(glAccountId: number, glAccount: any) {
/**
* @param {string} glAccountId GL account ID of gl account.
* @param {any} glAccount GL account to be updated.
* @returns {Observable<any>}
*/
updateGlAccount(glAccountId: string, glAccount: any): Observable<any> {
return this.http.put(`/glaccounts/${glAccountId}`, glAccount);
}
deleteGlAccount(glAccountId: number) {
/**
* @param glAccountId GL account ID of gl account to be deleted.
* @returns {Observable<any>}
*/
deleteGlAccount(glAccountId: string): Observable<any> {
return this.http.delete(`/glaccounts/${glAccountId}`);
}
getAccountingClosures() {
/**
* @returns {Observable<any>} GL account closures.
*/
getAccountingClosures(): Observable<any> {
return this.http.get('/glclosures');
}
createAccountingClosure(accountingClosure: any) {
/**
* @param {any} accountingClosure GL closure to be created.
* @returns {Observable<any>}
*/
createAccountingClosure(accountingClosure: any): Observable<any> {
return this.http.post('/glclosures', accountingClosure);
}
getAccountingClosure(accountingClosureId: number) {
/**
* @param {string} accountingClosureId Accounting closure ID of gl closure.
* @returns {Observable<any>} Accounting closure.
*/
getAccountingClosure(accountingClosureId: string): Observable<any> {
return this.http.get(`/glclosures/${accountingClosureId}`);
}
deleteAccountingClosure(accountingClosureId: number) {
return this.http.delete(`/glclosures/${accountingClosureId}`);
}
updateAccountingClosure(accountingClosureId: number, accountingClosure: any) {
/**
* @param {string} accountingClosureId Accounting closure ID of gl closure to be updated.
* @param {any} accountingClosure Accounting closure to be updated.
* @returns {Observable<any>}
*/
updateAccountingClosure(accountingClosureId: string, accountingClosure: any): Observable<any> {
return this.http.put(`/glclosures/${accountingClosureId}`, accountingClosure);
}
getAccountingRulesTemplate() {
/**
* @param {string} accountingClosureId Accounting closure ID of gl closure to be deleted.
* @returns {Observable<any>}
*/
deleteAccountingClosure(accountingClosureId: string): Observable<any> {
return this.http.delete(`/glclosures/${accountingClosureId}`);
}
/**
* @returns {Observable<any>} Accounting rules template.
*/
getAccountingRulesTemplate(): Observable<any> {
return this.http.get('/accountingrules/template');
}
createAccountingRule(accountingRule: any) {
/**
* @param {any} accountingRule Accounting rule to be created.
* @returns {Observable<any>}
*/
createAccountingRule(accountingRule: any): Observable<any> {
return this.http.post('/accountingrules', accountingRule);
}
getAccountingRule(accountingRuleId: string) {
/**
* @param {string} accountingRuleId Accounting rule ID of accounting rule.
* @returns {Observable<any>} Accounting rule.
*/
getAccountingRule(accountingRuleId: string): Observable<any> {
return this.http.get(`/accountingrules/${accountingRuleId}`);
}
deleteAccountingRule(accountingRuleId: number) {
return this.http.delete(`/accountingrules/${accountingRuleId}`);
}
updateAccountingRule(accoutingRuleId: number, accountingRule: any) {
/**
* @param {string} accoutingRuleId Accounting rule ID of accounting rule to be updated.
* @param {any} accountingRule Accounting rule to be updated.
* @returns {Observable<any>}
*/
updateAccountingRule(accoutingRuleId: string, accountingRule: any): Observable<any> {
return this.http.put(`/accountingrules/${accoutingRuleId}`, accountingRule);
}
executePeriodicAccruals(periodicAccruals: any) {
/**
* @param {string} accountingRuleId Accounting rule ID of accounting rule to be deleted.
* @returns {Observable<any>}
*/
deleteAccountingRule(accountingRuleId: string): Observable<any> {
return this.http.delete(`/accountingrules/${accountingRuleId}`);
}
/**
* @param {any} periodicAccruals Accruals to be executed.
* @returns {Observable<any>}
*/
executePeriodicAccruals(periodicAccruals: any): Observable<any> {
return this.http.post('/runaccruals', periodicAccruals);
}
getProvisioningEntries() {
/**
* @returns {Observable<any>} Provisioning entries.
*/
getProvisioningEntries(): Observable<any> {
return this.http.get('/provisioningentries');
}
createProvisioningEntry(provisioningEntry: any) {
/**
* @param {any} provisioningEntry Provisioning entry to be created.
* @returns {Observable<any>}
*/
createProvisioningEntry(provisioningEntry: any): Observable<any> {
return this.http.post('/provisioningentries', provisioningEntry);
}
getProvisioningEntry(provisioningEntryId: string) {
/**
* @param {string} provisioningEntryId Provisioning entry ID of provisioning entry.
* @returns {Observable<any>} Provisioning entry.
*/
getProvisioningEntry(provisioningEntryId: string): Observable<any> {
return this.http.get(`/provisioningentries/${provisioningEntryId}`);
}
getLoanProducts() {
/**
* @returns {Observable<any>} Loan products data.
*/
getLoanProducts(): Observable<any> {
return this.http.get('/loanproducts');
}
getProvisioningCategories() {
/**
* @returns {Observable<any>} Provisioning categories data.
*/
getProvisioningCategories(): Observable<any> {
return this.http.get('/provisioningcategory');
}
getProvisioningEntryEntries(entryId: string) {
const httpParams = new HttpParams().set('entryId', entryId);
/**
* @param {string} provisioningEntryId Provisioning entry ID of provisioning entry.
* @returns {Observable<any>} Provisioning entry entries.
*/
getProvisioningEntryEntries(provisioningEntryId: string): Observable<any> {
const httpParams = new HttpParams().set('entryId', provisioningEntryId);
return this.http.get('/provisioningentries/entries', { params: httpParams });
}
getProvisioningJournalEntries(entryId: string) {
const httpParams = new HttpParams().set('entryId', entryId);
/**
* @param {string} provisioningEntryId Provisioning entry ID of provisioning entry.
* @returns {Observable<any>} Provisioning journal entries.
*/
getProvisioningJournalEntries(provisioningEntryId: string): Observable<any> {
const httpParams = new HttpParams().set('entryId', provisioningEntryId);
return this.http.get('/journalentries/provisioning', { params: httpParams });
}
createProvisioningJournalEntries(provisioningEntryId: string) {
/**
* @param {string} provisioningEntryId Provisioning entry ID for which journal entries need to be created.
* @returns {Observable<any>}
*/
createProvisioningJournalEntries(provisioningEntryId: string): Observable<any> {
const httpParams = new HttpParams().set('command', 'createjournalentry');
return this.http.post(`/provisioningentries/${provisioningEntryId}`, {}, { params: httpParams });
}
recreateProvisioningEntries(provisioningEntryId: string) {
/**
* @param {string} provisioningEntryId Provisioning entry ID for which provisioning entry needs to be recreated.
* @returns {Observable<any>}
*/
recreateProvisioningEntries(provisioningEntryId: string): Observable<any> {
const httpParams = new HttpParams().set('command', 'recreateprovisioningentry');
return this.http.post(`/provisioningentries/${provisioningEntryId}`, {}, { params: httpParams });
}
retrieveOpeningBalances(officeId: number) {
let httpParams = new HttpParams();
httpParams = httpParams.set('officeId', officeId.toString());
return this.http.get('/journalentries/openingbalance', { params: httpParams });
}
defineOpeningBalances(openingBalances: any) {
let httpParams = new HttpParams();
httpParams = httpParams.set('command', 'defineOpeningBalance');
return this.http.post(`/journalentries`, openingBalances, { params: httpParams });
}
}
@@ -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;
}));
}
}
@@ -1,12 +1,14 @@
<div class="container m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
<button mat-raised-button color="primary" [routerLink]="['/accounting/closing-entries/create']">
<mat-icon>add</mat-icon>&nbsp;&nbsp;Create Closure
<button mat-raised-button color="primary" [routerLink]="['create']">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Create Closure
</button>
</div>
<div class="container" fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<div class="container" fxLayout="row">
<mat-form-field fxFlex>
<input matInput placeholder="Office Name" [formControl]="officeName" [matAutocomplete]="officeNameAutocomplete">
<mat-label>Office Name</mat-label>
<input matInput [formControl]="officeName" [matAutocomplete]="officeNameAutocomplete">
</mat-form-field>
</div>
@@ -44,7 +46,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/accounting/closing-entries/view', row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['view', row.id]"></tr>
</table>
@@ -1,10 +1,15 @@
/** Angular Imports */
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { FormControl } from '@angular/forms';
import { startWith, map, tap } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router';
import { AccountingService } from '../accounting.service';
/** rxjs Imports */
import { startWith, map } from 'rxjs/operators';
/**
* Closing entries component.
*/
@Component({
selector: 'mifosx-closing-entries',
templateUrl: './closing-entries.component.html',
@@ -12,52 +17,84 @@ import { AccountingService } from '../accounting.service';
})
export class ClosingEntriesComponent implements OnInit {
/** Columns to be displayed in closing entries table. */
displayedColumns: string[] = ['officeName', 'closingDate', 'comments', 'createdByUsername'];
dataSource: any;
/** Data source for closing entries table. */
dataSource: MatTableDataSource<any>;
/** Office name filter form control. */
officeName = new FormControl();
/** Office data. */
officeData: any;
/** Filtered office data for autocomplete. */
filteredOfficeData: any;
/** GL Account closure data. */
glAccountClosureData: any;
/** Paginator for closing entries table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for closing entries table. */
@ViewChild(MatSort) sort: MatSort;
constructor(private accountingService: AccountingService) { }
ngOnInit() {
this.getOffices();
this.applyFilter();
this.getAccountingClosures();
/**
* Retrieves the offices and gl account closures data from `resolve`.
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private route: ActivatedRoute) {
this.route.data.subscribe((data: {
offices: any,
glAccountClosures: any
}) => {
this.officeData = data.offices;
this.glAccountClosureData = data.glAccountClosures;
});
}
/**
* Sets the filter and closing entries table.
*/
ngOnInit() {
this.applyFilter();
this.setFilteredOffices();
this.setAccountingClosures();
}
/**
* Filters data in closing entries table based on office name.
*/
applyFilter() {
this.officeName.valueChanges.subscribe((filterValue: string) => {
this.dataSource.filter = filterValue.trim().toLowerCase();
});
}
getAccountingClosures() {
this.accountingService.getAccountingClosures().subscribe((glAccountClosureData: any) => {
this.dataSource = new MatTableDataSource(glAccountClosureData);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
/**
* Initializes the data source, paginator and sorter for closing entries table.
*/
setAccountingClosures() {
this.dataSource = new MatTableDataSource(this.glAccountClosureData);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
getOffices() {
this.accountingService.getOffices().subscribe(officeData => {
this.officeData = officeData;
this.filteredOfficeData = this.officeName.valueChanges
.pipe(
startWith(''),
map((office: any) => typeof office === 'string' ? office : office.name),
map((officeName: string) => officeName ? this.filterOfficeAutocompleteData(this.officeData, officeName) : this.officeData)
);
});
/**
* Sets filtered offices for autocomplete.
*/
setFilteredOffices() {
this.filteredOfficeData = this.officeName.valueChanges
.pipe(
startWith(''),
map((office: any) => typeof office === 'string' ? office : office.name),
map((officeName: string) => officeName ? this.filterOfficeAutocompleteData(officeName) : this.officeData)
);
}
private filterOfficeAutocompleteData(officeData: any, officeName: string): any {
return officeData.filter((office: any) => office.name.toLowerCase().includes(officeName.toLowerCase()));
/**
* Filters offices.
* @param {string} officeName Office name to filter office by.
* @returns {any} Filtered offices.
*/
private filterOfficeAutocompleteData(officeName: string): any {
return this.officeData.filter((office: any) => office.name.toLowerCase().includes(officeName.toLowerCase()));
}
}
@@ -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';
/**
* Closing entries data resolver.
*/
@Injectable()
export class ClosingEntriesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the gl account closures data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getAccountingClosures();
}
}
@@ -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 { AccountingService } from '../accounting.service';
/**
* Closing entry data resolver.
*/
@Injectable()
export class ClosingEntryResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the gl account closure data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const glAccountClosureId = route.paramMap.get('id');
return this.accountingService.getAccountingClosure(glAccountClosureId);
}
}
@@ -9,21 +9,30 @@
<div fxLayout="column">
<mat-form-field>
<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="accountingClosureForm.controls.officeId.hasError('required')">
Office is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="closingDatePicker" placeholder="Closing Date" required formControlName="closingDate">
<mat-label>Closing Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="closingDatePicker" required formControlName="closingDate">
<mat-datepicker-toggle matSuffix [for]="closingDatePicker"></mat-datepicker-toggle>
<mat-datepicker #closingDatePicker></mat-datepicker>
<mat-error *ngIf="accountingClosureForm.controls.closingDate.hasError('required')">
Closing Date is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="Comments" formControlName="comments"></textarea>
<mat-label>Comments</mat-label>
<textarea matInput formControlName="comments"></textarea>
</mat-form-field>
</div>
@@ -31,8 +40,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/closing-entries']">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]="!accountingClosureForm.valid">Submit</button>
</mat-card-actions>
</form>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Create closure component.
*/
@Component({
selector: 'mifosx-create-closure',
templateUrl: './create-closure.component.html',
@@ -11,22 +16,41 @@ import { AccountingService } from '../../accounting.service';
})
export class CreateClosureComponent implements OnInit {
// TODO: Validations, Hints
/** Minimum closing date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum closing date allowed. */
maxDate = new Date();
/** Accounting closure form. */
accountingClosureForm: FormGroup;
/** Office data. */
officeData: any;
/**
* Retrieves the offices 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 router: Router) { }
ngOnInit() {
this.createAccountingClosureForm();
this.getOffices();
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: { offices: any }) => {
this.officeData = data.offices;
});
}
/**
* Creates the accounting closure form.
*/
ngOnInit() {
this.createAccountingClosureForm();
}
/**
* Creates the accounting closure form.
*/
createAccountingClosureForm() {
this.accountingClosureForm = this.formBuilder.group({
'officeId': ['', Validators.required],
@@ -35,20 +59,29 @@ export class CreateClosureComponent implements OnInit {
});
}
getOffices() {
this.accountingService.getOffices().subscribe(officeData => {
this.officeData = officeData;
});
}
/**
* Submits the accounting closure form and creates accounting closure,
* if successful redirects to view created closure.
*/
submit() {
const accountingClosure = this.accountingClosureForm.value;
// TODO: Update once language and date settings are setup
accountingClosure.locale = 'en';
accountingClosure.dateFormat = 'dd MMMM yyyy';
accountingClosure.closingDate = '4 August 2018';
accountingClosure.dateFormat = 'yyyy-MM-dd';
if (accountingClosure.closingDate instanceof Date) {
let day = accountingClosure.closingDate.getDate();
let month = accountingClosure.closingDate.getMonth() + 1;
const year = accountingClosure.closingDate.getFullYear();
if (day < 10) {
day = `0${day}`;
}
if (month < 10) {
month = `0${month}`;
}
accountingClosure.closingDate = `${year}-${month}-${day}`;
}
this.accountingService.createAccountingClosure(accountingClosure).subscribe((response: any) => {
this.router.navigate(['/accounting/closing-entries/view', response.resourceId]);
this.router.navigate(['../view', response.resourceId], { relativeTo: this.route });
});
}
@@ -9,7 +9,8 @@
<div fxLayout="column">
<mat-form-field>
<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>
@@ -17,13 +18,15 @@
</mat-form-field>
<mat-form-field>
<input matInput [matDatepicker]="closingDatePicker" placeholder="Closing Date" required formControlName="closingDate">
<mat-label>Closing Date</mat-label>
<input matInput [matDatepicker]="closingDatePicker" required formControlName="closingDate">
<mat-datepicker-toggle matSuffix [for]="closingDatePicker"></mat-datepicker-toggle>
<mat-datepicker #closingDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="Comments" formControlName="comments"></textarea>
<mat-label>Comments</mat-label>
<textarea matInput formControlName="comments"></textarea>
</mat-form-field>
</div>
@@ -31,8 +34,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/closing-entries/view', accountingClosure?.id]">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]="accountingClosureForm.pristine">Submit</button>
</mat-card-actions>
</form>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Edit closure component.
*/
@Component({
selector: 'mifosx-edit-closure',
templateUrl: './edit-closure.component.html',
@@ -11,35 +16,40 @@ import { AccountingService } from '../../accounting.service';
})
export class EditClosureComponent implements OnInit {
// TODO: Update once language and date settings are setup
/** Accounting closure form. */
accountingClosureForm: FormGroup;
accountingClosureId: number;
accountingClosure: any;
/** GL Account closure. */
glAccountClosure: any;
/** Office data. */
officeData: any;
/**
* Retrieves the gl account closure 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 router: Router,
private route: ActivatedRoute) { }
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: { glAccountClosure: any }) => {
this.glAccountClosure = data.glAccountClosure;
});
}
/**
* Creates and sets accounting closure form.
*/
ngOnInit() {
this.accountingClosureId = Number(this.route.snapshot.paramMap.get('id'));
this.createAccountingClosureForm();
this.getAccountingClosure();
this.getOffices();
}
getAccountingClosure() {
this.accountingService.getAccountingClosure(this.accountingClosureId)
.subscribe((accountingClosure: any) => {
this.accountingClosure = accountingClosure;
this.accountingClosureForm.get('officeId').setValue(this.accountingClosure.officeId);
this.accountingClosureForm.get('closingDate').setValue(new Date());
this.accountingClosureForm.get('comments').setValue(this.accountingClosure.comments);
});
this.setAccountingClosure();
}
/**
* Creates accounting closure form.
*/
createAccountingClosureForm() {
this.accountingClosureForm = this.formBuilder.group({
'officeId': [{ value: '', disabled: true }, Validators.required],
@@ -48,16 +58,26 @@ export class EditClosureComponent implements OnInit {
});
}
getOffices() {
this.accountingService.getOffices().subscribe(officeData => {
this.officeData = officeData;
});
/**
* Sets accounting closure form.
*/
setAccountingClosure() {
this.officeData = [{ id: this.glAccountClosure.officeId, name: this.glAccountClosure.officeName }];
this.accountingClosureForm.get('officeId').setValue(this.glAccountClosure.officeId);
this.accountingClosureForm.get('closingDate').setValue(new Date(this.glAccountClosure.closingDate));
this.accountingClosureForm.get('comments').setValue(this.glAccountClosure.comments);
}
/**
* Submits the accounting closure form and updates accounting closure,
* if successful redirects to view updated closure.
*/
submit() {
this.accountingService.updateAccountingClosure(this.accountingClosureId, { comments: this.accountingClosureForm.value.comments })
this.accountingService.updateAccountingClosure(this.glAccountClosure.id,
{ comments: this.accountingClosureForm.value.comments })
.subscribe((response: any) => {
this.router.navigate(['/accounting/closing-entries/view', response.resourceId]);
this.router.navigate(['../../', response.resourceId], { relativeTo: this.route });
});
}
@@ -1,13 +1,15 @@
<div fxLayout="row" fxLayoutAlign="end" fxLayoutGap="2%" fxLayout.lt-md="column" class="container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['/accounting/closing-entries/view', accountingClosure?.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" (click)="deleteAccountingClosure()">
<mat-icon>delete</mat-icon>&nbsp;&nbsp;Delete
<fa-icon icon="trash"></fa-icon>&nbsp;&nbsp;
Delete
</button>
</div>
<div class="container" *ngIf="accountingClosure">
<div class="container" *ngIf="glAccountClosure">
<mat-card>
@@ -20,7 +22,7 @@
</div>
<div fxFlex="50%">
{{ accountingClosure.officeName }}
{{ glAccountClosure.officeName }}
</div>
<div fxFlex="50%" class="header">
@@ -28,7 +30,7 @@
</div>
<div fxFlex="50%">
{{ accountingClosure.closingDate }}
{{ glAccountClosure.closingDate }}
</div>
<div fxFlex="50%" class="header">
@@ -36,7 +38,7 @@
</div>
<div fxFlex="50%">
{{ accountingClosure.createdByUsername }}
{{ glAccountClosure.createdByUsername }}
</div>
<div fxFlex="50%" class="header">
@@ -44,7 +46,7 @@
</div>
<div fxFlex="50%">
{{ accountingClosure.lastUpdatedByUsername }}
{{ glAccountClosure.lastUpdatedByUsername }}
</div>
<div fxFlex="50%" class="header">
@@ -52,7 +54,7 @@
</div>
<div fxFlex="50%">
{{ accountingClosure.lastUpdatedDate }}
{{ glAccountClosure.lastUpdatedDate }}
</div>
<div fxFlex="50%" class="header">
@@ -60,7 +62,7 @@
</div>
<div fxFlex="50%">
{{ accountingClosure.createdDate }}
{{ glAccountClosure.createdDate }}
</div>
<div fxFlex="50%" class="header">
@@ -68,7 +70,7 @@
</div>
<div fxFlex="50%">
{{ accountingClosure.comments }}
{{ glAccountClosure.comments }}
</div>
</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 Services */
import { AccountingService } from '../../accounting.service';
/** Custom Components */
import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component';
/**
* View closure component.
*/
@Component({
selector: 'mifosx-view-closure',
templateUrl: './view-closure.component.html',
@@ -12,33 +19,38 @@ import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dial
})
export class ViewClosureComponent implements OnInit {
accountingClosureId: number;
accountingClosure: any;
/** GL Account closure. */
glAccountClosure: any;
/**
* Retrieves the gl account closure 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) { }
public dialog: MatDialog) {
this.route.data.subscribe((data: { glAccountClosure: any }) => {
this.glAccountClosure = data.glAccountClosure;
});
}
ngOnInit() {
this.accountingClosureId = Number(this.route.snapshot.paramMap.get('id'));
this.getAccountingClosure();
}
getAccountingClosure() {
this.accountingService.getAccountingClosure(this.accountingClosureId)
.subscribe((accountingClosure: any) => {
this.accountingClosure = accountingClosure;
});
}
/**
* Deletes the gl account closure and redirects to closing entries.
*/
deleteAccountingClosure() {
const deleteAccountingClosureDialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: `accounting closure ${this.accountingClosureId}` }
data: { deleteContext: `accounting closure ${this.glAccountClosure.id}` }
});
deleteAccountingClosureDialogRef.afterClosed().subscribe((response: any) => {
if (response.delete) {
this.accountingService.deleteAccountingClosure(this.accountingClosureId)
this.accountingService.deleteAccountingClosure(this.glAccountClosure.id)
.subscribe(() => {
this.router.navigate(['/accounting/closing-entries']);
});
@@ -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';
/**
* Accounting rules associations data resolver.
*/
@Injectable()
export class AccountingRulesAssociationsResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the accounting rules associations data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getAccountingRules(true);
}
}
@@ -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';
/**
* Currencies data resolver.
*/
@Injectable()
export class CurrenciesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the currencies data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getCurrencies();
}
}
@@ -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';
/**
* GL accounts data resolver.
*/
@Injectable()
export class GlAccountsResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the gl accounts data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getGlAccounts();
}
}
@@ -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';
/**
* Loan products data resolver.
*/
@Injectable()
export class LoanProductsResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the loan products data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getLoanProducts();
}
}
@@ -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';
/**
* Offices data resolver.
*/
@Injectable()
export class OfficesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the offices data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getOffices();
}
}
@@ -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';
/**
* Payment types data resolver.
*/
@Injectable()
export class PaymentTypesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the payment types data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getPaymentTypes();
}
}
@@ -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';
/**
* Provisioning categories data resolver.
*/
@Injectable()
export class ProvisioningCategoriesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the Provisioning categories data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getProvisioningCategories();
}
}
@@ -1,131 +1,178 @@
<mat-card class="container">
<div class="container">
<form [formGroup]="journalEntryForm" (ngSubmit)="submit()">
<mat-card>
<mat-card-content>
<form [formGroup]="journalEntryForm" (ngSubmit)="submit()">
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-card-content>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Office" required formControlName="officeId">
<mat-option *ngFor="let office of officeData" [value]="office.id">
{{ office.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="48%">
<mat-select placeholder="Currency" required formControlName="currencyCode">
<mat-option *ngFor="let currency of currencyData" [value]="currency.code">
{{ currency.displayLabel }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="48%">
<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="journalEntryForm.controls.officeId.hasError('required')">
Office is <strong>required</strong>
</mat-error>
</mat-form-field>
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="debits"
*ngFor="let debit of getFormArrayData('debits').controls; let i = index;">
<mat-form-field fxFlex="48%">
<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="journalEntryForm.controls.currencyCode.hasError('required')">
Currency is <strong>required</strong>
</mat-error>
</mat-form-field>
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="debits"
*ngFor="let debit of debits.controls; let i = index;">
<mat-form-field fxFlex="48%">
<mat-select placeholder="Affected GL Entry (Debit)" required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryDebit of glAccountData" [value]="affectedGLEntryDebit.id">
{{ affectedGLEntryDebit.name + ' (' + affectedGLEntryDebit.glCode + ')' }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<mat-form-field fxFlex="43%">
<input type="number" matInput placeholder="Debit Amount" required formControlName="amount">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Affected GL Entry (Debit)</mat-label>
<mat-select required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryDebit of glAccountData" [value]="affectedGLEntryDebit.id">
{{ affectedGLEntryDebit.name + ' (' + affectedGLEntryDebit.glCode + ')' }}
</mat-option>
</mat-select>
<mat-error *ngIf="debits.at(i).controls.glAccountId.hasError('required')">
Affected GL Entry (Debit) is <strong>required</strong>
</mat-error>
</mat-form-field>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(journalEntryForm.get('debits'), i)"><mat-icon>remove_circle</mat-icon></button>
<button *ngIf="i === 0" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(journalEntryForm.get('debits'))"><mat-icon>add_circle</mat-icon></button>
</span>
<mat-form-field fxFlex="43%">
<mat-label>Debit Amount</mat-label>
<input type="number" matInput required formControlName="amount">
<mat-error *ngIf="debits.at(i).controls.amount.hasError('required')">
Debit Amount is <strong>required</strong>
</mat-error>
</mat-form-field>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(debits, i)">
<fa-icon icon="minus-circle" size="lg"></fa-icon>
</button>
<button *ngIf="i === 0" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(debits)">
<fa-icon icon="plus-circle" size="lg"></fa-icon>
</button>
</span>
</div>
</div>
</div>
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="credits"
*ngFor="let credit of credits.controls; let i = index;">
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="credits"
*ngFor="let credit of getFormArrayData('credits').controls; let i = index;">
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<mat-form-field fxFlex="48%">
<mat-label>Affected GL Entry (Credit)</mat-label>
<mat-select required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryCredit of glAccountData" [value]="affectedGLEntryCredit.id">
{{ affectedGLEntryCredit.name + ' (' + affectedGLEntryCredit.glCode + ')' }}
</mat-option>
</mat-select>
<mat-error *ngIf="credits.at(i).controls.glAccountId.hasError('required')">
Affected GL Entry (Credit) is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Affected GL Entry (Credit)" required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryCredit of glAccountData" [value]="affectedGLEntryCredit.id">
{{ affectedGLEntryCredit.name + ' (' + affectedGLEntryCredit.glCode + ')' }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="43%">
<mat-label>Credit Amount</mat-label>
<input type="number" matInput required formControlName="amount">
<mat-error *ngIf="credits.at(i).controls.amount.hasError('required')">
Credit Amount is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="43%">
<input type="number" matInput placeholder="Credit Amount" required formControlName="amount">
</mat-form-field>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(credits, i)">
<fa-icon icon="minus-circle" size="lg"></fa-icon>
</button>
<button *ngIf="i === 0" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(credits)">
<fa-icon icon="plus-circle" size="lg"></fa-icon>
</button>
</span>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(journalEntryForm.get('credits'), i)"><mat-icon>remove_circle</mat-icon></button>
<button *ngIf="i === 0 glAccountData" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(journalEntryForm.get('credits'))"><mat-icon>add_circle</mat-icon></button>
</span>
</div>
</div>
<mat-form-field fxFlex="48%">
<mat-label>Reference Number</mat-label>
<input matInput formControlName="referenceNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Transaction Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDatePicker" required formControlName="transactionDate">
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transactionDatePicker></mat-datepicker>
<mat-error *ngIf="journalEntryForm.controls.transactionDate.hasError('required')">
Transaction Date is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Payment Type</mat-label>
<mat-select formControlName="paymentTypeId">
<mat-option *ngFor="let paymentType of paymentTypeData" [value]="paymentType.id">
{{ paymentType.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Account Number</mat-label>
<input matInput formControlName="accountNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Cheque Number</mat-label>
<input matInput formControlName="checkNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Routing Code</mat-label>
<input matInput formControlName="routingCode">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Receipt Number</mat-label>
<input matInput formControlName="receiptNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Bank Number</mat-label>
<input matInput formControlName="bankNumber">
</mat-form-field>
<mat-form-field fxFlex="98%">
<mat-label>Comments</mat-label>
<textarea matInput formControlName="comments"></textarea>
</mat-form-field>
</div>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Reference Number" formControlName="referenceNumber">
</mat-form-field>
</mat-card-content>
<mat-form-field fxFlex="48%">
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDatePicker" placeholder="Transaction Date" required formControlName="transactionDate">
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transactionDatePicker></mat-datepicker>
</mat-form-field>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['../']">Cancel</button>
<button mat-raised-button color="primary" [disabled]="!journalEntryForm.valid">Submit</button>
</mat-card-actions>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Payment Type" formControlName="paymentTypeId">
<mat-option *ngFor="let paymentType of paymentTypeData" [value]="paymentType.id">
{{ paymentType.name }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Account Number" formControlName="accountNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Cheque Number" formControlName="checkNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Routing Code" formControlName="routingCode">
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Receipt Number" formControlName="receiptNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Bank Number" formControlName="bankNumber">
</mat-form-field>
<mat-form-field fxFlex="98%">
<textarea matInput placeholder="Comments" formControlName="comments"></textarea>
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting']">Cancel</button>
<button mat-raised-button color="primary">Submit</button>
</mat-card-actions>
</form>
</mat-card>
</mat-card>
</div>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormArray } from '@angular/forms';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../accounting.service';
/**
* Create Journal Entry component.
*/
@Component({
selector: 'mifosx-create-journal-entry',
templateUrl: './create-journal-entry.component.html',
@@ -11,28 +16,55 @@ import { AccountingService } from '../accounting.service';
})
export class CreateJournalEntryComponent implements OnInit {
// TODO: Validations
/** Minimum transaction date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum transaction date allowed. */
maxDate = new Date();
/** Journal entry form. */
journalEntryForm: FormGroup;
/** Office data. */
officeData: any;
/** Currency data. */
currencyData: any;
/** Payment type data. */
paymentTypeData: any;
/** Gl Account data. */
glAccountData: any;
/**
* Retrieves the offices, currencies, payment types and gl 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 router: Router) { }
ngOnInit() {
this.createJournalEntryForm();
this.getOffices();
this.getCurrencies();
this.getPaymentTypes();
this.getGlAccounts();
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: {
offices: any,
currencies: any,
paymentTypes: any,
glAccounts: any
}) => {
this.officeData = data.offices;
this.currencyData = data.currencies.selectedCurrencyOptions;
this.paymentTypeData = data.paymentTypes;
this.glAccountData = data.glAccounts;
});
}
/**
* Creates the journal entry form.
*/
ngOnInit() {
this.createJournalEntryForm();
}
/**
* Creates the journal entry form.
*/
createJournalEntryForm() {
this.journalEntryForm = this.formBuilder.group({
'officeId': ['', Validators.required],
@@ -51,6 +83,10 @@ export class CreateJournalEntryComponent implements OnInit {
});
}
/**
* Creates the affected gl entry form.
* @returns {FormGroup} Affected gl entry form.
*/
createAffectedGLEntryForm(): FormGroup {
return this.formBuilder.group({
'glAccountId': ['', Validators.required],
@@ -58,50 +94,62 @@ export class CreateJournalEntryComponent implements OnInit {
});
}
getFormArrayData(type: string): FormArray {
return <FormArray>this.journalEntryForm.get(type);
/**
* Gets the affected gl entry (debits) form array.
* @returns {FormArray} Debits form array.
*/
get debits(): FormArray {
return this.journalEntryForm.get('debits') as FormArray;
}
/**
* Gets the affected gl entry (credits) form array.
* @returns {FormArray} Credits form array.
*/
get credits(): FormArray {
return this.journalEntryForm.get('credits') as FormArray;
}
/**
* Adds the affected gl entry form to given affected gl entry form array.
* @param {FormArray} affectedGLEntryFormArray Given affected gl entry form array (debit/credit).
*/
addAffectedGLEntry(affectedGLEntryFormArray: FormArray) {
affectedGLEntryFormArray.push(this.createAffectedGLEntryForm());
}
/**
* Removes the affected gl entry form from given affected gl entry form array at given index.
* @param {FormArray} affectedGLEntryFormArray Given affected gl entry form array (debit/credit).
* @param {number} index Array index from where affected gl entry form needs to be removed.
*/
removeAffectedGLEntry(affectedGLEntryFormArray: FormArray, index: number) {
affectedGLEntryFormArray.removeAt(index);
}
getOffices() {
this.accountingService.getOffices().subscribe(officeData => {
this.officeData = officeData;
});
}
getCurrencies() {
this.accountingService.getCurrencies().subscribe(currencyData => {
this.currencyData = currencyData.selectedCurrencyOptions;
});
}
getPaymentTypes() {
this.accountingService.getPaymentTypes().subscribe(paymentTypeData => {
this.paymentTypeData = paymentTypeData;
});
}
getGlAccounts() {
this.accountingService.getGlAccounts().subscribe(glAccountData => {
this.glAccountData = glAccountData;
});
}
/**
* Submits the journal entries form and creates journal entry,
* if successful redirects to view created transaction.
*/
submit() {
const journalEntry = this.journalEntryForm.value;
// TODO: Update once language and date settings are setup
journalEntry.locale = 'en';
journalEntry.dateFormat = 'dd MMMM yyyy';
journalEntry.transactionDate = '20 July 2018';
journalEntry.dateFormat = 'yyyy-MM-dd';
if (journalEntry.transactionDate instanceof Date) {
let day = journalEntry.transactionDate.getDate();
let month = journalEntry.transactionDate.getMonth() + 1;
const year = journalEntry.transactionDate.getFullYear();
if (day < 10) {
day = `0${day}`;
}
if (month < 10) {
month = `0${month}`;
}
journalEntry.transactionDate = `${year}-${month}-${day}`;
}
this.accountingService.createJournalEntry(journalEntry).subscribe(response => {
this.router.navigate(['/accounting/transactions/view', response.transactionId]);
this.router.navigate(['../transactions/view', response.transactionId], { relativeTo: this.route });
});
}
@@ -1,7 +1,7 @@
<div class="container">
<mat-card>
<form [formGroup]="financialActivityMappingForm" (ngSubmit)="submit()">
<mat-card-content>
@@ -9,19 +9,27 @@
<div fxLayout="column">
<mat-form-field>
<mat-select placeholder="Financial Activity" required formControlName="financialActivityId">
<mat-label>Financial Activity</mat-label>
<mat-select required formControlName="financialActivityId">
<mat-option *ngFor="let financialActivity of financialActivityData" [value]="financialActivity.id">
{{ financialActivity.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="financialActivityMappingForm.controls.financialActivityId.hasError('required')">
Financial Activity is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="Account" required formControlName="glAccountId">
<mat-label>Account</mat-label>
<mat-select required formControlName="glAccountId">
<mat-option *ngFor="let glAccount of glAccountData" [value]="glAccount.id">
{{ glAccount.name + ' (' + glAccount.glCode + ')' }}
</mat-option>
</mat-select>
<mat-error *ngIf="financialActivityMappingForm.controls.glAccountId.hasError('required')">
Account is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
@@ -29,8 +37,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/financial-activity-mappings']">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]="!financialActivityMappingForm.valid">Submit</button>
</mat-card-actions>
</form>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Create financial activity mapping component.
*/
@Component({
selector: 'mifosx-create-financial-activity-mapping',
templateUrl: './create-financial-activity-mapping.component.html',
@@ -11,56 +16,79 @@ import { AccountingService } from '../../accounting.service';
})
export class CreateFinancialActivityMappingComponent implements OnInit {
/** Financial activity mapping form. */
financialActivityMappingForm: FormGroup;
/** GL Account options. */
glAccountOptions: any;
/** GL Account data. */
glAccountData: any;
/** Financial activity data. */
financialActivityData: any;
constructor(private formBuider: FormBuilder,
/**
* Retrieves the gl account options and financial activity 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 router: Router) { }
ngOnInit() {
this.createFinancialActivityMappingForm();
this.getFinancialActivityAccountsTemplate();
this.setGLAccountData();
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: { financialActivityAccountsTemplate: any }) => {
this.glAccountOptions = data.financialActivityAccountsTemplate.glAccountOptions;
this.financialActivityData = data.financialActivityAccountsTemplate.financialActivityOptions;
});
}
/**
* Creates the financial activity mapping form and sets the gl account data.
*/
ngOnInit() {
this.createFinancialActivityMappingForm();
this.setGlAccountData();
}
/**
* Creates the financial activity mapping form.
*/
createFinancialActivityMappingForm() {
this.financialActivityMappingForm = this.formBuider.group({
this.financialActivityMappingForm = this.formBuilder.group({
'financialActivityId': ['', Validators.required],
'glAccountId': ['', Validators.required]
});
}
getFinancialActivityAccountsTemplate() {
this.accountingService.getFinancialActivityAccountsTemplate().subscribe((financialActivityAccountData: any) => {
this.glAccountOptions = financialActivityAccountData.glAccountOptions;
this.financialActivityData = financialActivityAccountData.financialActivityOptions;
});
}
setGLAccountData() {
this.financialActivityMappingForm.get('financialActivityId').valueChanges.subscribe(financialActivityId => {
switch (financialActivityId) {
case 100:
case 101:
case 102:
case 103: this.glAccountData = this.glAccountOptions.assetAccountOptions;
break;
case 200:
case 201: this.glAccountData = this.glAccountOptions.liabilityAccountOptions;
break;
case 300: this.glAccountData = this.glAccountOptions.equityAccountOptions;
break;
}
});
/**
* Sets the gl account data on the basis of selected financial activity.
*/
setGlAccountData() {
this.financialActivityMappingForm.get('financialActivityId').valueChanges
.subscribe(financialActivityId => {
switch (financialActivityId) {
case 100:
case 101:
case 102:
case 103: this.glAccountData = this.glAccountOptions.assetAccountOptions;
break;
case 200:
case 201: this.glAccountData = this.glAccountOptions.liabilityAccountOptions;
break;
case 300: this.glAccountData = this.glAccountOptions.equityAccountOptions;
break;
}
});
}
/**
* Submits the financial activity mapping form and creates financial activity account,
* if successful redirects to view created account.
*/
submit() {
this.accountingService.createFinancialActivityAccount(this.financialActivityMappingForm.value)
.subscribe((response: any) => {
this.router.navigate(['/accounting/financial-activity-mappings/view', response.resourceId]);
this.router.navigate(['../view', response.resourceId], { relativeTo: this.route });
});
}
@@ -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';
/**
* Financial activity mappings template data resolver.
*/
@Injectable()
export class FinancialActivityMappingsTemplateResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the financial activity mappings template data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getFinancialActivityAccountsTemplate();
}
}
@@ -9,7 +9,8 @@
<div fxLayout="column">
<mat-form-field>
<mat-select placeholder="Financial Activity" required formControlName="financialActivityId">
<mat-label>Financial Activity</mat-label>
<mat-select required formControlName="financialActivityId">
<mat-option *ngFor="let financialActivity of financialActivityData" [value]="financialActivity.id">
{{ financialActivity.name }}
</mat-option>
@@ -17,7 +18,8 @@
</mat-form-field>
<mat-form-field>
<mat-select placeholder="Account" required formControlName="glAccountId">
<mat-label>Account</mat-label>
<mat-select required formControlName="glAccountId">
<mat-option *ngFor="let glAccount of glAccountData" [value]="glAccount.id">
{{ glAccount.name + ' (' + glAccount.glCode + ')' }}
</mat-option>
@@ -29,8 +31,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/financial-activity-mappings/view', financialActivityAccountId]">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]="financialActivityMappingForm.pristine">Submit</button>
</mat-card-actions>
</form>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Edit financial activity mapping component.
*/
@Component({
selector: 'mifosx-edit-financial-activity-mapping',
templateUrl: './edit-financial-activity-mapping.component.html',
@@ -11,24 +16,54 @@ import { AccountingService } from '../../accounting.service';
})
export class EditFinancialActivityMappingComponent implements OnInit {
/** Financial activity mapping form. */
financialActivityMappingForm: FormGroup;
/** GL Account options. */
glAccountOptions: any;
/** GL Account data. */
glAccountData: any;
financialActivityAccountId: any;
/** Financial activity data. */
financialActivityData: any;
/** Financial activity account ID. */
financialActivityAccountId: string;
/** Financial activity ID. */
financialActivityId: number;
/** GL Account ID. */
glAccountId: number;
constructor(private route: ActivatedRoute,
private formBuider: FormBuilder,
/**
* Retrieves the gl account options, financial activity and financial activity account 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 formBuider: FormBuilder,
private accountingService: AccountingService,
private router: Router) { }
ngOnInit() {
this.financialActivityAccountId = this.route.snapshot.paramMap.get('id');
this.createFinancialActivityMappingForm();
this.setGLAccountData();
this.getFinancialActivityAccount();
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: { financialActivityAccountAndTemplate: any }) => {
this.financialActivityAccountId = data.financialActivityAccountAndTemplate.id;
this.financialActivityId = data.financialActivityAccountAndTemplate.financialActivityData.id;
this.glAccountId = data.financialActivityAccountAndTemplate.glAccountData.id;
this.glAccountOptions = data.financialActivityAccountAndTemplate.glAccountOptions;
this.financialActivityData = data.financialActivityAccountAndTemplate.financialActivityOptions;
});
}
/**
* Creates and sets the financial activity mapping form and sets the gl account data.
*/
ngOnInit() {
this.createFinancialActivityMappingForm();
this.setGLAccountData();
this.financialActivityMappingForm.get('financialActivityId').setValue(this.financialActivityId);
this.financialActivityMappingForm.get('glAccountId').setValue(this.glAccountId);
}
/**
* Creates the financial activity mapping form.
*/
createFinancialActivityMappingForm() {
this.financialActivityMappingForm = this.formBuider.group({
'financialActivityId': ['', Validators.required],
@@ -36,37 +71,35 @@ export class EditFinancialActivityMappingComponent implements OnInit {
});
}
getFinancialActivityAccount() {
this.accountingService.getFinancialActivityAccount(this.financialActivityAccountId, true)
.subscribe((financialActivityAccountData: any) => {
this.glAccountOptions = financialActivityAccountData.glAccountOptions;
this.financialActivityData = financialActivityAccountData.financialActivityOptions;
this.financialActivityMappingForm.get('financialActivityId').setValue(financialActivityAccountData.financialActivityData.id);
this.financialActivityMappingForm.get('glAccountId').setValue(financialActivityAccountData.glAccountData.id);
/**
* Sets the gl account data on the basis of selected financial activity.
*/
setGLAccountData() {
this.financialActivityMappingForm.get('financialActivityId').valueChanges
.subscribe(financialActivityId => {
switch (financialActivityId) {
case 100:
case 101:
case 102:
case 103: this.glAccountData = this.glAccountOptions.assetAccountOptions;
break;
case 200:
case 201: this.glAccountData = this.glAccountOptions.liabilityAccountOptions;
break;
case 300: this.glAccountData = this.glAccountOptions.equityAccountOptions;
break;
}
});
}
setGLAccountData() {
this.financialActivityMappingForm.get('financialActivityId').valueChanges.subscribe(financialActivityId => {
switch (financialActivityId) {
case 100:
case 101:
case 102:
case 103: this.glAccountData = this.glAccountOptions.assetAccountOptions;
break;
case 200:
case 201: this.glAccountData = this.glAccountOptions.liabilityAccountOptions;
break;
case 300: this.glAccountData = this.glAccountOptions.equityAccountOptions;
break;
}
});
}
/**
* Submits the financial activity mapping form and updates financial activity account,
* if successful redirects to view updated account.
*/
submit() {
this.accountingService.updateFinancialActivityAccount(this.financialActivityAccountId, this.financialActivityMappingForm.value)
.subscribe((response: any) => {
this.router.navigate(['/accounting/financial-activity-mappings/view', response.resourceId]);
this.router.navigate(['../../', response.resourceId], { relativeTo: this.route });
});
}
@@ -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 { AccountingService } from '../../accounting.service';
/**
* Financial activity mapping and template data resolver.
*/
@Injectable()
export class FinancialActivityMappingAndTemplateResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the financial activity mapping and template data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const financialActivityAccountId = route.paramMap.get('id');
return this.accountingService.getFinancialActivityAccount(financialActivityAccountId, true);
}
}
@@ -1,6 +1,6 @@
<div fxLayout="row" fxLayoutAlign="end" class="container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['/accounting/financial-activity-mappings/create']">
<mat-icon>add</mat-icon>&nbsp;&nbsp;Define New Mapping
<button mat-raised-button color="primary" [routerLink]="['create']">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;Define New Mapping
</button>
</div>
@@ -24,7 +24,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/accounting/financial-activity-mappings/view', row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['view', row.id]" class="select-row"></tr>
</table>
@@ -1,3 +1,7 @@
table {
width: 100%;
.select-row:hover {
cursor: pointer;
}
}
@@ -1,8 +1,11 @@
/** Angular Imports */
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material';
import { ActivatedRoute } from '@angular/router';
import { AccountingService } from '../accounting.service';
/**
* Financial activity mappings component.
*/
@Component({
selector: 'mifosx-financial-activity-mappings',
templateUrl: './financial-activity-mappings.component.html',
@@ -10,32 +13,50 @@ import { AccountingService } from '../accounting.service';
})
export class FinancialActivityMappingsComponent implements OnInit {
/** Financial activity account data. */
financialActivityAccountData: any;
/** Columns to be displayed in financial activity mappings table. */
displayedColumns: string[] = ['financialActivity', 'glAccountName', 'glAccountCode'];
dataSource: any;
/** Data source for financial activity mappings table. */
dataSource: MatTableDataSource<any>;
/** Paginator for financial activity mappings table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for financial activity mappings table. */
@ViewChild(MatSort) sort: MatSort;
constructor(private accountingService: AccountingService) { }
ngOnInit() {
this.getFinancialActivityAccounts();
}
getFinancialActivityAccounts() {
this.accountingService.getFinancialActivityAccounts().subscribe((financialActivityAccounts: any) => {
this.dataSource = new MatTableDataSource(financialActivityAccounts);
this.dataSource.paginator = this.paginator;
this.dataSource.sortingDataAccessor = (financialActivityAccount: any, property: any) => {
switch (property) {
case 'financialActivity': return financialActivityAccount.financialActivityData.name;
case 'glAccountName': return financialActivityAccount.glAccountData.name;
case 'glAccountCode': return financialActivityAccount.glAccountData.glCode;
default: return financialActivityAccount[property];
}
};
this.dataSource.sort = this.sort;
/**
* Retrieves the financial activity accounts data from `resolve`.
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private route: ActivatedRoute) {
this.route.data.subscribe(( data: { financialActivityAccounts: any }) => {
this.financialActivityAccountData = data.financialActivityAccounts;
});
}
/**
* Sets the financial activity mappings table.
*/
ngOnInit() {
this.setFinancialActivityAccounts();
}
/**
* Initializes the data source, paginator and sorter for financial activity mappings table.
*/
setFinancialActivityAccounts() {
this.dataSource = new MatTableDataSource(this.financialActivityAccountData);
this.dataSource.paginator = this.paginator;
this.dataSource.sortingDataAccessor = (financialActivityAccount: any, property: any) => {
switch (property) {
case 'financialActivity': return financialActivityAccount.financialActivityData.name;
case 'glAccountName': return financialActivityAccount.glAccountData.name;
case 'glAccountCode': return financialActivityAccount.glAccountData.glCode;
default: return financialActivityAccount[property];
}
};
this.dataSource.sort = this.sort;
}
}
@@ -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';
/**
* Financial activity mappings data resolver.
*/
@Injectable()
export class FinancialActivityMappingsResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the financial activity mappings data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getFinancialActivityAccounts();
}
}
@@ -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 { AccountingService } from '../../accounting.service';
/**
* Financial activity mapping data resolver.
*/
@Injectable()
export class FinancialActivityMappingResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the financial activity mapping data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const financialActivityAccountId = route.paramMap.get('id');
return this.accountingService.getFinancialActivityAccount(financialActivityAccountId, false);
}
}
@@ -1,9 +1,11 @@
<div fxLayout="row" fxLayoutAlign="end" fxLayoutGap="2%" class="container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['/accounting/financial-activity-mappings/view', financialActivityAccountId, '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" (click)="deleteFinancialActivityAccount()">
<mat-icon>delete</mat-icon>&nbsp;&nbsp;Delete
<fa-icon icon="trash"></fa-icon>&nbsp;&nbsp;
Delete
</button>
</div>
@@ -20,7 +22,7 @@
</div>
<div fxFlex="50%">
{{ financialActivityAccountData?.financialActivityData.name }}
{{ financialActivityAccount?.financialActivityData.name }}
</div>
<div fxFlex="50%" class="header">
@@ -28,7 +30,7 @@
</div>
<div fxFlex="50%">
{{ financialActivityAccountData?.glAccountData.name }}
{{ financialActivityAccount?.glAccountData.name }}
</div>
</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 Services */
import { AccountingService } from '../../accounting.service';
/** Custom Components */
import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component';
/**
* View financial activity mapping component.
*/
@Component({
selector: 'mifosx-view-financial-activity-mapping',
templateUrl: './view-financial-activity-mapping.component.html',
@@ -12,26 +19,34 @@ import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dial
})
export class ViewFinancialActivityMappingComponent implements OnInit {
/** Financial activity account ID. */
financialActivityAccountId: any;
financialActivityAccountData: any;
/** Financial activity account data. */
financialActivityAccount: any;
constructor(private route: ActivatedRoute,
/**
* Retrieves the financial activity 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,
private accountingService: AccountingService,
public dialog: MatDialog) { }
public dialog: MatDialog) {
this.route.data.subscribe((data: { financialActivityAccount: any }) => {
this.financialActivityAccount = data.financialActivityAccount;
this.financialActivityAccountId = data.financialActivityAccount.id;
});
}
ngOnInit() {
this.financialActivityAccountId = this.route.snapshot.paramMap.get('id');
this.getFinancialActivityAccount();
}
getFinancialActivityAccount() {
this.accountingService.getFinancialActivityAccount(this.financialActivityAccountId, false)
.subscribe((financialActivityAccountData: any) => {
this.financialActivityAccountData = financialActivityAccountData;
});
}
/**
* Deletes the financial activity account and redirects to financial activity mappings.
*/
deleteFinancialActivityAccount() {
const deleteFinancialActivityAccountDialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: `financial activity mapping ${this.financialActivityAccountId}` }
@@ -1,140 +1,192 @@
<mat-card class="container">
<div class="container">
<form [formGroup]="frequentPostingsForm" (ngSubmit)="submit()">
<mat-card>
<mat-card-content>
<form [formGroup]="frequentPostingsForm" (ngSubmit)="submit()">
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-card-content>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Office" required formControlName="officeId">
<mat-option *ngFor="let office of officeData" [value]="office.id">
{{ office.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="48%">
<mat-select placeholder="Accounting Rule" required formControlName="accountingRule">
<mat-option *ngFor="let accountingRule of accountingRuleData" [value]="accountingRule">
{{ accountingRule.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="48%">
<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="frequentPostingsForm.controls.officeId.hasError('required')">
Office is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Currency" required formControlName="currencyCode">
<mat-option *ngFor="let currency of currencyData" [value]="currency.code">
{{ currency.displayLabel }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Accounting Rule</mat-label>
<mat-select required formControlName="accountingRule">
<mat-option *ngFor="let accountingRule of accountingRuleData" [value]="accountingRule">
{{ accountingRule.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="frequentPostingsForm.controls.accountingRule.hasError('required')">
Accounting Rule is <strong>required</strong>
</mat-error>
</mat-form-field>
<span fxFlex="48%" fxHide.lt-md></span>
<mat-form-field fxFlex="48%">
<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="frequentPostingsForm.controls.currencyCode.hasError('required')">
Currency is <strong>required</strong>
</mat-error>
</mat-form-field>
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="debits"
*ngFor="let debit of getFormArrayData('debits').controls; let i = index;">
<span fxFlex="48%" fxHide.lt-md></span>
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="debits"
*ngFor="let debit of debits.controls; let i = index;">
<mat-form-field fxFlex="48%">
<mat-select placeholder="Affected GL Entry (Debit)" required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryDebit of debitAccountData" [value]="affectedGLEntryDebit.id">
{{ affectedGLEntryDebit.name }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<mat-form-field fxFlex="43%">
<input type="number" matInput placeholder="Debit Amount" required formControlName="amount">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Affected GL Entry (Debit)</mat-label>
<mat-select required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryDebit of debitAccountData" [value]="affectedGLEntryDebit.id">
{{ affectedGLEntryDebit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="debits.at(i).controls.glAccountId.hasError('required')">
Affected GL Entry (Debit) is <strong>required</strong>
</mat-error>
</mat-form-field>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(frequentPostingsForm.get('debits'), i)"><mat-icon>remove_circle</mat-icon></button>
<button *ngIf="i === 0 && allowMultipleDebitEntries" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(frequentPostingsForm.get('debits'))"><mat-icon>add_circle</mat-icon></button>
</span>
<mat-form-field fxFlex="43%">
<mat-label>Debit Amount</mat-label>
<input type="number" matInput required formControlName="amount">
<mat-error *ngIf="debits.at(i).controls.amount.hasError('required')">
Debit Amount is <strong>required</strong>
</mat-error>
</mat-form-field>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(debits, i)">
<fa-icon icon="minus-circle" size="lg"></fa-icon>
</button>
<button *ngIf="i === 0 && allowMultipleDebitEntries" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(debits)">
<fa-icon icon="plus-circle" size="lg"></fa-icon>
</button>
</span>
</div>
</div>
</div>
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="credits"
*ngFor="let credit of credits.controls; let i = index;">
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" formArrayName="credits"
*ngFor="let credit of getFormArrayData('credits').controls; let i = index;">
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
<mat-form-field fxFlex="48%">
<mat-label>Affected GL Entry (Credit)</mat-label>
<mat-select required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryCredit of creditAccountData" [value]="affectedGLEntryCredit.id">
{{ affectedGLEntryCredit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="credits.at(i).controls.glAccountId.hasError('required')">
Affected GL Entry (Credit) is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Affected GL Entry (Credit)" required formControlName="glAccountId">
<mat-option *ngFor="let affectedGLEntryCredit of creditAccountData" [value]="affectedGLEntryCredit.id">
{{ affectedGLEntryCredit.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="43%">
<mat-label>Credit Amount</mat-label>
<input type="number" matInput required formControlName="amount">
<mat-error *ngIf="credits.at(i).controls.amount.hasError('required')">
Credit Amount is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="43%">
<input type="number" matInput placeholder="Credit Amount" required formControlName="amount">
</mat-form-field>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(credits, i)">
<fa-icon icon="minus-circle" size="lg"></fa-icon>
</button>
<button *ngIf="i === 0 && allowMultipleCreditEntries" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(credits)">
<fa-icon icon="plus-circle" size="lg"></fa-icon>
</button>
</span>
<span fxFlex>
<button *ngIf="i !== 0" type="button" mat-icon-button (click)="removeAffectedGLEntry(frequentPostingsForm.get('credits'), i)"><mat-icon>remove_circle</mat-icon></button>
<button *ngIf="i === 0 && allowMultipleCreditEntries" type="button" mat-icon-button color="primary" (click)="addAffectedGLEntry(frequentPostingsForm.get('credits'))"><mat-icon>add_circle</mat-icon></button>
</span>
</div>
</div>
<mat-form-field fxFlex="48%">
<mat-label>Reference Number</mat-label>
<input matInput formControlName="referenceNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Transaction Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDatePicker" required formControlName="transactionDate">
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transactionDatePicker></mat-datepicker>
<mat-error *ngIf="frequentPostingsForm.controls.transactionDate.hasError('required')">
Transaction Date is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Payment Type</mat-label>
<mat-select formControlName="paymentTypeId">
<mat-option *ngFor="let paymentType of paymentTypeData" [value]="paymentType.id">
{{ paymentType.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Account Number</mat-label>
<input matInput formControlName="accountNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Cheque Number</mat-label>
<input matInput formControlName="checkNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Routing Code</mat-label>
<input matInput formControlName="routingCode">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Receipt Number</mat-label>
<input matInput formControlName="receiptNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Bank Number</mat-label>
<input matInput formControlName="bankNumber">
</mat-form-field>
<mat-form-field fxFlex="98%">
<mat-label>Comments</mat-label>
<textarea matInput formControlName="comments"></textarea>
</mat-form-field>
</div>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Reference Number" formControlName="referenceNumber">
</mat-form-field>
</mat-card-content>
<mat-form-field fxFlex="48%">
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDatePicker" placeholder="Transaction Date" required formControlName="transactionDate">
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transactionDatePicker></mat-datepicker>
</mat-form-field>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['../']">Cancel</button>
<button mat-raised-button color="primary" [disabled]="!frequentPostingsForm.valid">Submit</button>
</mat-card-actions>
<mat-form-field fxFlex="48%">
<mat-select placeholder="Payment Type" formControlName="paymentTypeId">
<mat-option *ngFor="let paymentType of paymentTypeData" [value]="paymentType.id">
{{ paymentType.name }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Account Number" formControlName="accountNumber">
</mat-form-field>
</mat-card>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Cheque Number" formControlName="checkNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Routing Code" formControlName="routingCode">
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Receipt Number" formControlName="receiptNumber">
</mat-form-field>
<mat-form-field fxFlex="48%">
<input matInput placeholder="Bank Number" formControlName="bankNumber">
</mat-form-field>
<mat-form-field fxFlex="98%">
<textarea matInput placeholder="Comments" formControlName="comments"></textarea>
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting']">Cancel</button>
<button mat-raised-button color="primary">Submit</button>
</mat-card-actions>
</form>
</mat-card>
</div>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormArray } from '@angular/forms';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../accounting.service';
/**
* Frequent Postings component.
*/
@Component({
selector: 'mifosx-frequent-postings',
templateUrl: './frequent-postings.component.html',
@@ -11,35 +16,64 @@ import { AccountingService } from '../accounting.service';
})
export class FrequentPostingsComponent implements OnInit {
// TODO: Validations
// https://github.com/openMF/incubator-fineract/blob/master/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryInvalidException.java
/** Minimum transaction date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum transaction date allowed. */
maxDate = new Date();
/** Frequent postings form. */
frequentPostingsForm: FormGroup;
/** Office data. */
officeData: any;
/** Accounting rule data. */
accountingRuleData: any;
/** Currency data. */
currencyData: any;
/** Payment type data. */
paymentTypeData: any;
/** Debit account data. */
debitAccountData: any;
/** Credit account data. */
creditAccountData: any;
/** True if multiple credit entries are allowed. */
allowMultipleCreditEntries: boolean;
/** True if multiple debit entries are allowed. */
allowMultipleDebitEntries: boolean;
/**
* Retrieves the offices, accounting rules, currencies and payment types 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 router: Router) { }
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: {
offices: any,
accountingRules: any,
currencies: any,
paymentTypes: any
}) => {
this.officeData = data.offices;
this.accountingRuleData = data.accountingRules;
this.currencyData = data.currencies.selectedCurrencyOptions;
this.paymentTypeData = data.paymentTypes;
});
}
/**
* Creates the frequent postings form and sets the affected gl entry form array.
*/
ngOnInit() {
this.createFrequentPostingsForm();
this.setAffectedGLEntryForm();
this.getOffices();
this.getAccountingRules();
this.getCurrencies();
this.getPaymentTypes();
}
/**
* Creates the frequent postings form.
*/
createFrequentPostingsForm() {
this.frequentPostingsForm = this.formBuilder.group({
'officeId': ['', Validators.required],
@@ -59,25 +93,30 @@ export class FrequentPostingsComponent implements OnInit {
});
}
/**
* Sets the affected gl entry form array.
*/
setAffectedGLEntryForm() {
this.frequentPostingsForm.get('accountingRule').valueChanges.subscribe(accountingRule => {
const debits = this.frequentPostingsForm.get('debits') as FormArray;
const credits = this.frequentPostingsForm.get('credits') as FormArray;
while (debits.length) {
debits.removeAt(0);
while (this.debits.length) {
this.debits.removeAt(0);
}
while (credits.length) {
credits.removeAt(0);
while (this.credits.length) {
this.credits.removeAt(0);
}
this.allowMultipleDebitEntries = accountingRule.allowMultipleDebitEntries;
this.allowMultipleCreditEntries = accountingRule.allowMultipleCreditEntries;
this.debitAccountData = accountingRule.debitAccounts;
this.creditAccountData = accountingRule.creditAccounts;
this.addAffectedGLEntry(debits);
this.addAffectedGLEntry(credits);
this.addAffectedGLEntry(this.debits);
this.addAffectedGLEntry(this.credits);
});
}
/**
* Creates the affected gl entry form.
* @returns {FormGroup} Affected gl entry form.
*/
createAffectedGLEntryForm(): FormGroup {
return this.formBuilder.group({
'glAccountId': ['', Validators.required],
@@ -85,51 +124,63 @@ export class FrequentPostingsComponent implements OnInit {
});
}
getFormArrayData(type: string): FormArray {
return <FormArray>this.frequentPostingsForm.get(type);
/**
* Gets the affected gl entry (debits) form array.
* @returns {FormArray} Debits form array.
*/
get debits(): FormArray {
return this.frequentPostingsForm.get('debits') as FormArray;
}
/**
* Gets the affected gl entry (credits) form array.
* @returns {FormArray} Credits form array.
*/
get credits(): FormArray {
return this.frequentPostingsForm.get('credits') as FormArray;
}
/**
* Adds the affected gl entry form to given affected gl entry form array.
* @param {FormArray} affectedGLEntryFormArray Given affected gl entry form array (debit/credit).
*/
addAffectedGLEntry(affectedGLEntryFormArray: FormArray) {
affectedGLEntryFormArray.push(this.createAffectedGLEntryForm());
}
/**
* Removes the affected gl entry form from given affected gl entry form array at given index.
* @param {FormArray} affectedGLEntryFormArray Given affected gl entry form array (debit/credit).
* @param {number} index Array index from where affected gl entry form needs to be removed.
*/
removeAffectedGLEntry(affectedGLEntryFormArray: FormArray, index: number) {
affectedGLEntryFormArray.removeAt(index);
}
getOffices() {
this.accountingService.getOffices().subscribe(officeData => {
this.officeData = officeData;
});
}
getAccountingRules() {
this.accountingService.getAccountingRules(true).subscribe(accountingRuleData => {
this.accountingRuleData = accountingRuleData;
});
}
getCurrencies() {
this.accountingService.getCurrencies().subscribe(currencyData => {
this.currencyData = currencyData.selectedCurrencyOptions;
});
}
getPaymentTypes() {
this.accountingService.getPaymentTypes().subscribe(paymentTypeData => {
this.paymentTypeData = paymentTypeData;
});
}
/**
* Submits the frequent postings form and creates journal entry,
* if successful redirects to view created transaction.
*/
submit() {
const journalEntry = this.frequentPostingsForm.value;
journalEntry.accountingRule = journalEntry.accountingRule.id;
// TODO: Update once language and date settings are setup
journalEntry.locale = 'en';
journalEntry.dateFormat = 'dd MMMM yyyy';
journalEntry.transactionDate = '20 July 2018';
journalEntry.dateFormat = 'yyyy-MM-dd';
if (journalEntry.transactionDate instanceof Date) {
let day = journalEntry.transactionDate.getDate();
let month = journalEntry.transactionDate.getMonth() + 1;
const year = journalEntry.transactionDate.getFullYear();
if (day < 10) {
day = `0${day}`;
}
if (month < 10) {
month = `0${month}`;
}
journalEntry.transactionDate = `${year}-${month}-${day}`;
}
this.accountingService.createJournalEntry(journalEntry).subscribe(response => {
this.router.navigate(['/accounting/transactions/view', response.transactionId]);
this.router.navigate(['../transactions/view', response.transactionId], { relativeTo: this.route });
});
}
@@ -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 };
};
@@ -9,9 +9,13 @@
<div fxLayout="row">
<mat-form-field fxFlex>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="tillDatePicker" placeholder="Accrue Till Date" required formControlName="tillDate">
<mat-label>Accrue Till Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="tillDatePicker" required formControlName="tillDate">
<mat-datepicker-toggle matSuffix [for]="tillDatePicker"></mat-datepicker-toggle>
<mat-datepicker #tillDatePicker></mat-datepicker>
<mat-error *ngIf="periodicAccrualsForm.controls.tillDate.hasError('required')">
Accrue Date is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
@@ -19,8 +23,11 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting']">Cancel</button>
<button mat-raised-button color="primary"><mat-icon>play_arrow</mat-icon>&nbsp;&nbsp;Run Periodic Accruals</button>
<button type="button" mat-raised-button [routerLink]="['../']">Cancel</button>
<button mat-raised-button color="primary" [disabled]="!periodicAccrualsForm.valid">
<fa-icon icon="play"></fa-icon>&nbsp;&nbsp;
Run Periodic Accruals
</button>
</mat-card-actions>
</form>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../accounting.service';
/**
* Periodic accruals component.
*/
@Component({
selector: 'mifosx-periodic-accruals',
templateUrl: './periodic-accruals.component.html',
@@ -11,32 +16,63 @@ import { AccountingService } from '../accounting.service';
})
export class PeriodicAccrualsComponent implements OnInit {
/** Minimum accrue date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum accrue date allowed. */
maxDate = new Date();
/** Periodic accruals form. */
periodicAccrualsForm: FormGroup;
/**
* @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) { }
/**
* Creates periodic accruals form.
*/
ngOnInit() {
this.createPeriodicAccrualsForm();
}
/**
* Creates periodic accruals form.
*/
createPeriodicAccrualsForm() {
this.periodicAccrualsForm = this.formBuilder.group({
'tillDate': ['', Validators.required]
});
}
/**
* Submits the periodic accruals form and executes periodic accruals,
* if successful redirects to accounting.
*/
submit() {
const periodicAccruals = this.periodicAccrualsForm.value;
// TODO: Update once language and date settings are setup
periodicAccruals.locale = 'en';
periodicAccruals.dateFormat = 'dd MMMM yyyy';
periodicAccruals.tillDate = '06 August 2018';
this.accountingService.executePeriodicAccruals(periodicAccruals).subscribe(response => {
this.router.navigate(['/accounting']);
periodicAccruals.dateFormat = 'yyyy-MM-dd';
if (periodicAccruals.tillDate instanceof Date) {
let day = periodicAccruals.tillDate.getDate();
let month = periodicAccruals.tillDate.getMonth() + 1;
const year = periodicAccruals.tillDate.getFullYear();
if (day < 10) {
day = `0${day}`;
}
if (month < 10) {
month = `0${month}`;
}
periodicAccruals.tillDate = `${year}-${month}-${day}`;
}
this.accountingService.executePeriodicAccruals(periodicAccruals).subscribe(() => {
this.router.navigate(['../'], { relativeTo: this.route });
});
}
@@ -2,16 +2,20 @@
<mat-card>
<form [formGroup]="provisioningEntriesForm" (ngSubmit)="submit()">
<form [formGroup]="provisioningEntryForm" (ngSubmit)="submit()">
<mat-card-content>
<div fxLayout="column">
<mat-form-field fxFlex>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="datePicker" placeholder="Date" required formControlName="date">
<mat-label>Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="datePicker" required formControlName="date">
<mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle>
<mat-datepicker #datePicker></mat-datepicker>
<mat-error *ngIf="provisioningEntryForm.controls.date.hasError('required')">
Date is <strong>required</strong>
</mat-error>
</mat-form-field>
<div fxFlex>
@@ -25,8 +29,8 @@
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/accounting/provisioning-entries']">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]="!provisioningEntryForm.valid">Submit</button>
</mat-card-actions>
</form>
@@ -1,8 +1,14 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* Create provisioning entry component.
*/
@Component({
selector: 'mifosx-create-provisioning-entry',
templateUrl: './create-provisioning-entry.component.html',
@@ -10,33 +16,65 @@ import { AccountingService } from '../../accounting.service';
})
export class CreateProvisioningEntryComponent implements OnInit {
/** Minimum provisioning date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum provisioning date allowed. */
maxDate = new Date();
provisioningEntriesForm: FormGroup;
/** Provisioning entry form. */
provisioningEntryForm: FormGroup;
/**
* @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 accountingService: AccountingService,
private route: ActivatedRoute,
private router: Router) { }
/**
* Creates the provisioning entry form.
*/
ngOnInit() {
this.createProvisioningEntriesForm();
this.createProvisioningEntryForm();
}
createProvisioningEntriesForm() {
this.provisioningEntriesForm = this.formBuilder.group({
/**
* Creates the provisioning entry form.
*/
createProvisioningEntryForm() {
this.provisioningEntryForm = this.formBuilder.group({
'date': ['', Validators.required],
'createjournalentries': [false]
});
}
/**
* Submits the provisioning entry form and creates provisioning entry,
* if successful redirects to view created entry.
*/
submit() {
const provisioningEntry = this.provisioningEntriesForm.value;
const provisioningEntry = this.provisioningEntryForm.value;
// TODO: Update once language and date settings are setup
provisioningEntry.locale = 'en';
provisioningEntry.dateFormat = 'dd MMMM yyyy';
provisioningEntry.date = '06 August 2018';
provisioningEntry.dateFormat = 'yyyy-MM-dd';
if (provisioningEntry.date instanceof Date) {
let day = provisioningEntry.date.getDate();
let month = provisioningEntry.date.getMonth() + 1;
const year = provisioningEntry.date.getFullYear();
if (day < 10) {
day = `0${day}`;
}
if (month < 10) {
month = `0${month}`;
}
provisioningEntry.date = `${year}-${month}-${day}`;
}
this.accountingService.createProvisioningEntry(provisioningEntry)
.subscribe((response: any) => {
console.log(response);
this.router.navigate(['../view', response.resourceId], { relativeTo: this.route });
});
}
@@ -1,14 +1,16 @@
<div class="container m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
<button mat-raised-button color="primary" [routerLink]="['/accounting/provisioning-entries/create']">
<mat-icon>add</mat-icon>&nbsp;&nbsp;Create Provisioning Entry
<button mat-raised-button color="primary" [routerLink]="['create']">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Create Provisioning Entry
</button>
</div>
<div class="container">
<div fxLayout="row" fxLayoutGap="20px">
<div fxLayout="row">
<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>
@@ -36,9 +38,8 @@
<ng-container matColumnDef="viewReport">
<th mat-header-cell *matHeaderCellDef> View Report </th>
<td mat-cell *matCellDef="let provisioningEntry">
<button mat-button (click)="viewReport($event, provisioningEntry.id)" [disabled]="true" color="primary">
<!-- Tenant Customizable -->
<mat-icon>remove_red_eye</mat-icon>&nbsp;&nbsp;View
<button mat-button color="primary" [routerLink]="['view', provisioningEntry.id]">
<fa-icon icon="eye"></fa-icon>&nbsp;&nbsp;View
</button>
</td>
</ng-container>
@@ -47,7 +48,7 @@
<th mat-header-cell *matHeaderCellDef> Recreate Provisioning </th>
<td mat-cell *matCellDef="let provisioningEntry">
<button mat-button (click)="recreateProvisioning($event, provisioningEntry.id)" [disabled]="provisioningEntry.journalEntry" color="primary">
<mat-icon>edit</mat-icon>&nbsp;&nbsp;Recreate
<fa-icon icon="edit"></fa-icon>&nbsp;&nbsp;Recreate
</button>
</td>
</ng-container>
@@ -56,13 +57,13 @@
<th mat-header-cell *matHeaderCellDef> View Journal Entry </th>
<td mat-cell *matCellDef="let provisioningEntry">
<button mat-button (click)="viewJournalEntry($event, provisioningEntry.id)" [disabled]="!provisioningEntry.journalEntry" color="primary">
<mat-icon>remove_red_eye</mat-icon>&nbsp;&nbsp;View
<fa-icon icon="eye"></fa-icon>&nbsp;&nbsp;View
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/accounting/provisioning-entries/view', row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
@@ -1,9 +1,14 @@
/** Angular Imports */
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatSort, MatPaginator, MatTableDataSource } from '@angular/material';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
/** Custom Services */
import { AccountingService } from '../accounting.service';
/**
* Provisioning entries component.
*/
@Component({
selector: 'mifosx-provisioning-entries',
templateUrl: './provisioning-entries.component.html',
@@ -11,46 +16,76 @@ import { AccountingService } from '../accounting.service';
})
export class ProvisioningEntriesComponent implements OnInit {
/** Provisioning entry data. */
provisioningEntryData: any;
/** Columns to be displayed in provisioning entries table. */
displayedColumns: string[] = ['createdUser', 'createdDate', 'journalEntry', 'viewReport', 'recreateProvisioning', 'viewJournalEntry'];
dataSource: any;
/** Data source for provisioning entries table. */
dataSource: MatTableDataSource<any>;
/** Paginator for provisioning entries table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for provisioning entries table. */
@ViewChild(MatSort) sort: MatSort;
/**
* Retrieves the provisioning entries data from `resolve`.
* @param {AccountingService} accountingService Accounting Service.
* @param {ActivatedRoute} route Activated Route.
* @param {Router} router Router for navigation.
*/
constructor(private accountingService: AccountingService,
private router: Router) { }
ngOnInit() {
this.getProvisioningEntries();
}
getProvisioningEntries() {
this.accountingService.getProvisioningEntries().subscribe((provisioningEntryData: any) => {
this.dataSource = new MatTableDataSource(provisioningEntryData.pageItems);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: { provisioningEntries: any }) => {
this.provisioningEntryData = data.provisioningEntries.pageItems;
});
}
/**
* Sets the provisioning entries table.
*/
ngOnInit() {
this.setProvisioningEntries();
}
/**
* Initializes the data source, paginator and sorter for provisioning entries table.
*/
setProvisioningEntries() {
this.dataSource = new MatTableDataSource(this.provisioningEntryData);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
/**
* Filters data in provisioning entries table based on passed value.
* @param {string} filterValue Value to filter data.
*/
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
viewReport($event: any, provisioningEntryId: number) {
// Tenant Customizable
$event.stopPropagation();
}
recreateProvisioning($event: any, provisioningEntryId: string) {
/**
* Recreates provisioning entry.
* @param {Event} $event Event to stop propagation.
* @param {string} provisioningEntryId Provisioning entry id.
*/
recreateProvisioning($event: Event, provisioningEntryId: string) {
this.accountingService.recreateProvisioningEntries(provisioningEntryId)
.subscribe((response: any) => {
this.router.navigate(['/accounting/provisioning-entries/view', response.resourceId]);
this.router.navigate(['view', response.resourceId], { relativeTo: this.route });
});
$event.stopPropagation();
}
viewJournalEntry($event: any, provisioningEntryId: number) {
this.router.navigate(['/accounting/provisioning-entries/journal-entries/view', provisioningEntryId]);
/**
* View journal entries for the provisioning entry.
* @param {Event} $event Event to stop propagation.
* @param {number} provisioningEntryId Provisioning entry id.
*/
viewJournalEntry($event: Event, provisioningEntryId: number) {
this.router.navigate(['journal-entries/view', provisioningEntryId], { relativeTo: this.route });
$event.stopPropagation();
}
@@ -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';
/**
* Provisioning entries data resolver.
*/
@Injectable()
export class ProvisioningEntriesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the provisioning entries data.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.accountingService.getProvisioningEntries();
}
}
@@ -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 { AccountingService } from '../../accounting.service';
/**
* Provisioning entry entries data resolver.
*/
@Injectable()
export class ProvisioningEntryEntriesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the provisioning entry entries data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const provisioningEntryId = route.paramMap.get('id');
return this.accountingService.getProvisioningEntryEntries(provisioningEntryId);
}
}
@@ -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 { AccountingService } from '../../accounting.service';
/**
* Provisioning entry data resolver.
*/
@Injectable()
export class ProvisioningEntryResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the provisioning entry data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const provisioningEntryId = route.paramMap.get('id');
return this.accountingService.getProvisioningEntry(provisioningEntryId);
}
}
@@ -2,7 +2,8 @@
<div class="m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
<button mat-raised-button color="primary" (click)="createProvisioningJournalEntries()" [disabled]="provisioningEntry.journalEntry">
<mat-icon>add</mat-icon>&nbsp;&nbsp;Create Journal Entries
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Create Journal Entries
</button>
</div>
@@ -48,15 +49,18 @@
<div class="container m-t-20" fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="31%">
<input matInput placeholder="Office Name" [formControl]="officeName" [matAutocomplete]="officeNameAutocomplete">
<mat-label>Office Name</mat-label>
<input matInput [formControl]="officeName" [matAutocomplete]="officeNameAutocomplete">
</mat-form-field>
<mat-form-field fxFlex="31%">
<input matInput placeholder="Loan Product" [formControl]="loanProduct" [matAutocomplete]="loanProductAutocomplete">
<mat-label>Loan Product</mat-label>
<input matInput [formControl]="loanProduct" [matAutocomplete]="loanProductAutocomplete">
</mat-form-field>
<mat-form-field fxFlex="31%">
<input matInput placeholder="Provisioning Category" [formControl]="provisioningCategory" [matAutocomplete]="provisioningCategoryAutocomplete">
<mat-label>Provisioning Category</mat-label>
<input matInput [formControl]="provisioningCategory" [matAutocomplete]="provisioningCategoryAutocomplete">
</mat-form-field>
</div>
@@ -122,7 +126,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/accounting/provisioning-entries/view', row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
@@ -6,9 +6,9 @@ table {
div {
margin: 1rem 0;
word-wrap: break-word;
}
.header {
font-weight: 500;
&.header {
font-weight: 500;
}
}
}
@@ -1,12 +1,18 @@
/** Angular Imports */
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormControl } from '@angular/forms';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
/** rxjs Imports */
import { startWith, map, debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
/** Custom Services */
import { AccountingService } from '../../accounting.service';
/**
* View provisioning entry component.
*/
@Component({
selector: 'mifosx-view-provisioning-entry',
templateUrl: './view-provisioning-entry.component.html',
@@ -14,41 +20,87 @@ import { AccountingService } from '../../accounting.service';
})
export class ViewProvisioningEntryComponent implements OnInit, AfterViewInit {
/** Provisioning entry id. */
provisioningEntryId: string;
/** Provisioning entry. */
provisioningEntry: any;
/** Provisioning entry entries. */
provisioningEntryEntries: any;
/** Office name filter form control. */
officeName = new FormControl();
/** Office data. */
officeData: any;
/** Filtered office data for autocomplete. */
filteredOfficeData: any;
/** Loan product filter form control. */
loanProduct = new FormControl();
/** Loan product data. */
loanProductData: any;
/** Filtered loan product data for autocomplete. */
filteredLoanProductData: any;
/** Provisioning category filter form control. */
provisioningCategory = new FormControl();
/** Provisioning category data. */
provisioningCategoryData: any;
/** Filtered provisioning category data for autocomplete. */
filteredProvisioningCategoryData: any;
/** Columns to be displayed in provisioning entry entries table. */
displayedColumns: string[] = ['officeName', 'productName', 'currencyCode', 'categoryName', 'amountreserved', 'liabilityAccountName', 'expenseAccountName'];
dataSource: any;
filterValue: any = { officeName: '', productName: '', categoryName: '' };
/** Data source for provisioning entry entries table. */
dataSource: MatTableDataSource<any>;
/** Provisioning entry entries filter. */
filterValue: any = {
officeName: '',
productName: '',
categoryName: ''
};
/** Paginator for provisioning entry entries table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for provisioning entry entries table. */
@ViewChild(MatSort) sort: MatSort;
officeName = new FormControl();
officeData: any;
filteredOfficeData: any;
loanProduct = new FormControl();
loanProductData: any;
filteredLoanProductData: any;
provisioningCategory = new FormControl();
provisioningCategoryData: any;
filteredProvisioningCategoryData: any;
constructor(private route: ActivatedRoute,
private router: Router,
private accountingService: AccountingService) { }
ngOnInit() {
this.provisioningEntryId = this.route.snapshot.paramMap.get('id');
this.getProvisioningEntry();
this.getProvisioningEntryEntries();
this.getOffices();
this.getLoanProducts();
this.getProvisioningCategories();
/**
* Retrieves the provisioning entry, provisioning entry entries, offices,
* loan products, provisioning categories data from `resolve`.
* @param {AccountingService} accountingService Accounting Service.
* @param {ActivatedRoute} route Activated Route.
* @param {Router} router Router for navigation.
*/
constructor(private accountingService: AccountingService,
private route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: {
provisioningEntry: any,
provisioningEntryEntries: any,
offices: any,
loanProducts: any,
provisiningCategories: any
}) => {
this.provisioningEntryId = data.provisioningEntry.id;
this.provisioningEntry = data.provisioningEntry;
this.provisioningEntryEntries = data.provisioningEntryEntries;
this.officeData = data.offices;
this.loanProductData = data.loanProducts;
this.provisioningCategoryData = data.provisiningCategories;
});
}
/**
* Sets filtered offices, loan products and provisioning categories for autocomplete
* and provisioning entry entries table.
*/
ngOnInit() {
this.setProvisioningEntryEntries();
this.setFilteredOffices();
this.setFilteredLoanProducts();
this.setFilteredProvisioningCategories();
}
/**
* Subscribes to all search filters:
* Office Name, Loan Product, Provisioning categories.
*/
ngAfterViewInit() {
this.officeName.valueChanges
.pipe(
@@ -84,86 +136,109 @@ export class ViewProvisioningEntryComponent implements OnInit, AfterViewInit {
.subscribe();
}
/**
* Filter predicate for provisioning entry entries table data.
* @param {any} data Table data.
* @param {any} filterValue Values to filter data by.
*/
filterPredicate(data: any, filterValue: any) {
return data.officeName.toLowerCase().indexOf(filterValue['officeName']) !== -1
&& data.productName.toLowerCase().indexOf(filterValue['productName']) !== -1
&& data.categoryName.toLowerCase().indexOf(filterValue['categoryName']) !== -1;
}
applyFilter(filterValue: string, property: string) {
/**
* Initializes the data source, paginator, sorter and filter predicate for
* provisioning entry entries table.
*/
setProvisioningEntryEntries() {
this.dataSource = new MatTableDataSource(this.provisioningEntryEntries.pageItems);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.dataSource.filterPredicate = this.filterPredicate;
}
/**
* Filters data in provisioning entry entries table based on passed value.
* @param {string} filterValue Value to filter data.
* @param {string} property Property to filter data.
*/
applyFilter(filterValue: string, property: string) {
this.filterValue[property] = filterValue;
this.dataSource.filter = this.filterValue;
}
getProvisioningEntry() {
this.accountingService.getProvisioningEntry(this.provisioningEntryId)
.subscribe((provisioningEntry: any) => {
this.provisioningEntry = provisioningEntry;
});
/**
* Sets filtered offices for autocomplete.
*/
setFilteredOffices() {
this.filteredOfficeData = this.officeName.valueChanges
.pipe(
startWith(''),
map((office: any) => typeof office === 'string' ? office : office.name),
map((officeName: string) => officeName ? this.filterOfficeAutocompleteData(officeName) : this.officeData)
);
}
getProvisioningEntryEntries() {
this.accountingService.getProvisioningEntryEntries(this.provisioningEntryId)
.subscribe((provisioningEntryEntries: any) => {
this.dataSource = new MatTableDataSource(provisioningEntryEntries.pageItems);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.dataSource.filterPredicate = this.filterPredicate;
});
/**
* Sets filtered loan products for autocomplete.
*/
setFilteredLoanProducts() {
this.filteredLoanProductData = this.loanProduct.valueChanges
.pipe(
startWith(''),
map((loanProduct: any) => typeof loanProduct === 'string' ? loanProduct : loanProduct.name),
map((loanProductName: string) => loanProductName ? this.filterLoanProductAutocompleteData(loanProductName) : this.loanProductData)
);
}
getOffices() {
this.accountingService.getOffices().subscribe(officeData => {
this.officeData = officeData;
this.filteredOfficeData = this.officeName.valueChanges
.pipe(
startWith(''),
map((office: any) => typeof office === 'string' ? office : office.name),
map((officeName: string) => officeName ? this.filterOfficeAutocompleteData(this.officeData, officeName) : this.officeData)
);
});
/**
* Sets filtered provisioning categories for autocomplete.
*/
setFilteredProvisioningCategories() {
this.filteredProvisioningCategoryData = this.provisioningCategory.valueChanges
.pipe(
startWith(''),
map((provisioningCategory: any) => typeof provisioningCategory === 'string' ? provisioningCategory : provisioningCategory.categoryName),
map((provisioningCategoryName: string) => provisioningCategoryName ? this.filterProvisioningCategoryAutocompleteData(provisioningCategoryName) : this.provisioningCategoryData)
);
}
getLoanProducts() {
this.accountingService.getLoanProducts().subscribe(loanProductData => {
this.loanProductData = loanProductData;
this.filteredLoanProductData = this.loanProduct.valueChanges
.pipe(
startWith(''),
map((loanProduct: any) => typeof loanProduct === 'string' ? loanProduct : loanProduct.name),
map((loanProductName: string) => loanProductName ? this.filterLoanProductAutocompleteData(this.loanProductData, loanProductName) : this.loanProductData)
);
});
/**
* Filters offices.
* @param {string} officeName Office name to filter office by.
* @returns {any} Filtered offices.
*/
private filterOfficeAutocompleteData(officeName: string): any {
return this.officeData.filter((office: any) => office.name.toLowerCase().includes(officeName.toLowerCase()));
}
getProvisioningCategories() {
this.accountingService.getProvisioningCategories().subscribe(provisioningCategoryData => {
this.provisioningCategoryData = provisioningCategoryData;
this.filteredProvisioningCategoryData = this.provisioningCategory.valueChanges
.pipe(
startWith(''),
map((provisioningCategory: any) => typeof provisioningCategory === 'string' ? provisioningCategory : provisioningCategory.categoryName),
map((provisioningCategoryName: string) => provisioningCategoryName ? this.filterProvisioningCategoryAutocompleteData(this.provisioningCategoryData, provisioningCategoryName) : this.provisioningCategoryData)
);
});
/**
* Filters loan products.
* @param {string} loanProductName Loan product name to filter loan products by.
* @returns {any} Filtered loan products.
*/
private filterLoanProductAutocompleteData(loanProductName: string): any {
return this.loanProductData.filter((loanProduct: any) => loanProduct.name.toLowerCase().includes(loanProductName.toLocaleLowerCase()));
}
private filterOfficeAutocompleteData(officeData: any, officeName: string): any {
return officeData.filter((office: any) => office.name.toLowerCase().includes(officeName.toLowerCase()));
}
private filterLoanProductAutocompleteData(loanProductData: any, loanProductName: string): any {
return loanProductData.filter((loanProduct: any) => loanProduct.name.toLowerCase().includes(loanProductName.toLocaleLowerCase()));
}
private filterProvisioningCategoryAutocompleteData(provisioningCategoryData: any, provisioningCategoryName: string): any {
return provisioningCategoryData.filter((provisioningCategory: any) => provisioningCategory.categoryName.toLowerCase().includes(provisioningCategoryName.toLocaleLowerCase()));
/**
* Filters provisioning categories.
* @param {string} provisioningCategoryName Provisioning category name to filter provisioning categories by.
* @returns {any} Filtered provisioning categories.
*/
private filterProvisioningCategoryAutocompleteData(provisioningCategoryName: string): any {
return this.provisioningCategoryData.filter((provisioningCategory: any) => provisioningCategory.categoryName.toLowerCase().includes(provisioningCategoryName.toLocaleLowerCase()));
}
/**
* Creates provisioning journal entries
* and redirects to created entries.
*/
createProvisioningJournalEntries() {
this.accountingService.createProvisioningJournalEntries(this.provisioningEntryId)
.subscribe((response: any) => {
this.router.navigate(['/accounting/provisioning-entries/journal-entries/view', response.resourceId]);
this.router.navigate(['../../journal-entries/view', response.resourceId], { relativeTo: this.route });
});
}
@@ -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 { AccountingService } from '../../accounting.service';
/**
* Provisioning journal entries data resolver.
*/
@Injectable()
export class ProvisioningJournalEntriesResolver implements Resolve<Object> {
/**
* @param {AccountingService} accountingService Accounting service.
*/
constructor(private accountingService: AccountingService) {}
/**
* Returns the provisioning journal entries data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.accountingService.getProvisioningJournalEntries(id);
}
}
@@ -2,7 +2,8 @@
<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>
@@ -1,9 +1,11 @@
/** Angular Imports */
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { AccountingService } from '../../accounting.service';
/**
* View provisioning journal entries component.
*/
@Component({
selector: 'mifosx-view-provisioning-journal-entries',
templateUrl: './view-provisioning-journal-entries.component.html',
@@ -11,39 +13,56 @@ import { AccountingService } from '../../accounting.service';
})
export class ViewProvisioningJournalEntriesComponent implements OnInit {
provisioningEntryId: string;
/** Provisioning journal entry data. */
provisioningJournalEntryData: any;
/** Columns to be displayed in provisioning journal entries table. */
displayedColumns: string[] = ['id', 'officeName', 'transactionDate', 'transactionId', 'glAccountType', 'createdByUserName', 'glAccountCode', 'glAccountName', 'debit', 'credit'];
dataSource: any;
/** Data source for provisioning journal entries table. */
dataSource: MatTableDataSource<any>;
/** Paginator for provisioning journal entries table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for provisioning journal entries table. */
@ViewChild(MatSort) sort: MatSort;
constructor(private route: ActivatedRoute,
private accountingService: AccountingService) { }
/**
* Retrieves the provisioning journal entries data from `resolve`.
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private route: ActivatedRoute) {
this.route.data.subscribe((data: { provisioningJournalEntry: any }) => {
this.provisioningJournalEntryData = data.provisioningJournalEntry.pageItems;
});
}
/**
* Sets the provisioning journal entries table.
*/
ngOnInit() {
this.provisioningEntryId = this.route.snapshot.paramMap.get('id');
this.getProvisioningJournalEntry();
this.setProvisioningJournalEntry();
}
getProvisioningJournalEntry() {
this.accountingService.getProvisioningJournalEntries(this.provisioningEntryId)
.subscribe((provisioningJournalEntryData: any) => {
this.dataSource = new MatTableDataSource(provisioningJournalEntryData.pageItems);
this.dataSource.paginator = this.paginator;
this.dataSource.sortingDataAccessor = (transaction: any, property: any) => {
switch (property) {
case 'glAccountType': return transaction.glAccountType.value;
case 'debit': return transaction.amount;
case 'credit': return transaction.amount;
default: return transaction[property];
}
};
this.dataSource.sort = this.sort;
});
/**
* Initializes the data source, paginator and sorter for provisioning journal entries table.
*/
setProvisioningJournalEntry() {
this.dataSource = new MatTableDataSource(this.provisioningJournalEntryData);
this.dataSource.paginator = this.paginator;
this.dataSource.sortingDataAccessor = (transaction: any, property: any) => {
switch (property) {
case 'glAccountType': return transaction.glAccountType.value;
case 'debit': return transaction.amount;
case 'credit': return transaction.amount;
default: return transaction[property];
}
};
this.dataSource.sort = this.sort;
}
/**
* Filters data in provisioning journal entries table based on passed value.
* @param {string} filterValue Value to filter data.
*/
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
@@ -4,7 +4,8 @@
<div mat-dialog-content>
<p>Are you sure you want to revert this transaction?</p>
<mat-form-field fxFlexFill>
<textarea matInput placeholder="Comments" [formControl]="comments"></textarea>
<mat-label>Comments</mat-label>
<textarea matInput [formControl]="comments"></textarea>
</mat-form-field>
</div>
<mat-dialog-actions align="end">
@@ -1,7 +1,11 @@
/** Angular Imports */
import { Component, OnInit, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { FormControl } from '@angular/forms';
/**
* Revert transaction dialog component.
*/
@Component({
selector: 'mifosx-revert-transaction',
templateUrl: './revert-transaction.component.html',
@@ -9,8 +13,13 @@ import { FormControl } from '@angular/forms';
})
export class RevertTransactionComponent implements OnInit {
/** Comments input form control. */
comments = new FormControl('');
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides comments or reverted transaction ID.
*/
constructor(public dialogRef: MatDialogRef<RevertTransactionComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
@@ -1,32 +1,57 @@
/** Angular Imports */
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
/** rxjs Imports */
import { Observable, BehaviorSubject } from 'rxjs';
/** Custom Services */
import { AccountingService } from '../accounting.service';
/**
* Journal entries custom data source to implement server side filtering, pagination and sorting.
*/
export class JournalEntriesDataSource implements DataSource<any> {
/** Journal entries behavior subject to represent loaded journal entries page. */
private journalEntriesSubject = new BehaviorSubject<any[]>([]);
/** Records subject to represent total number of filtered journal entry records. */
private recordsSubject = new BehaviorSubject<number>(0);
/** Records observable which can be subscribed to get the value of total number of filtered journal entry records. */
public records$ = this.recordsSubject.asObservable();
/**
* @param {AccountingService} accountingService Accounting Service
*/
constructor(private accountingService: AccountingService) { }
getJournalEntries(filterBy: any, orderBy: string = '', sortOrder: string = '', offset: number = 0, limit: number = 10) {
/**
* Gets journal entries on the basis of provided parameters and emits the value.
* @param {any} filterBy Properties by which entries should be filtered.
* @param {string} orderBy Property by which entries should be sorted.
* @param {string} sortOrder Sort order: ascending or descending.
* @param {number} pageIndex Page number.
* @param {number} limit Number of entries within the page.
*/
getJournalEntries(filterBy: any, orderBy: string = '', sortOrder: string = '', pageIndex: number = 0, limit: number = 10) {
this.journalEntriesSubject.next([]);
orderBy = (orderBy === 'debit' || orderBy === 'credit') ? 'amount' : orderBy;
this.accountingService.getJournalEntries(filterBy, orderBy, sortOrder, offset, limit)
this.accountingService.getJournalEntries(filterBy, orderBy, sortOrder, pageIndex * limit, limit)
.subscribe((journalEntries: any) => {
this.recordsSubject.next(journalEntries.totalFilteredRecords);
this.journalEntriesSubject.next(journalEntries.pageItems);
});
}
/**
* @param {CollectionViewer} collectionViewer
*/
connect(collectionViewer: CollectionViewer): Observable<any[]> {
return this.journalEntriesSubject.asObservable();
}
/**
* @param {CollectionViewer} collectionViewer
*/
disconnect(collectionViewer: CollectionViewer): void {
this.journalEntriesSubject.complete();
this.recordsSubject.complete();
@@ -1,15 +1,18 @@
<div class="container" fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="31%">
<input matInput placeholder="Office Name" [formControl]="officeName" [matAutocomplete]="officeNameAutocomplete">
<mat-label>Office Name</mat-label>
<input matInput [formControl]="officeName" [matAutocomplete]="officeNameAutocomplete">
</mat-form-field>
<mat-form-field fxFlex="31%">
<input matInput placeholder="GL Account Name or Code" [formControl]="glAccount" [matAutocomplete]="glAccountAutocomplete">
<mat-label>GL Account Name or Code</mat-label>
<input matInput [formControl]="glAccount" [matAutocomplete]="glAccountAutocomplete">
</mat-form-field>
<mat-form-field fxFlex="31%">
<mat-select placeholder="Filter" [formControl]="filter" (selectionChange)="applyFilter($event.value, 'manualEntriesOnly')">
<mat-label>Filter</mat-label>
<mat-select [formControl]="entryTypeFilter" (selectionChange)="applyFilter($event.value, 'manualEntriesOnly')">
<mat-option *ngFor="let filter of entryTypeFilterData" [value]="filter.value">
{{ filter.option }}
</mat-option>
@@ -17,19 +20,22 @@
</mat-form-field>
<mat-form-field fxFlex="31%">
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDateFromPicker" placeholder="Transaction Date From" [formControl]="transactionDateFrom">
<mat-label>Transaction Date From</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDateFromPicker" [formControl]="transactionDateFrom">
<mat-datepicker-toggle matSuffix [for]="transactionDateFromPicker"></mat-datepicker-toggle>
<mat-datepicker #transactionDateFromPicker></mat-datepicker>
</mat-form-field>
<mat-form-field fxFlex="31%">
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDateToPicker" placeholder="Transaction Date To" [formControl]="transactionDateTo">
<mat-label>Transaction Date To</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="transactionDateToPicker" [formControl]="transactionDateTo">
<mat-datepicker-toggle matSuffix [for]="transactionDateToPicker"></mat-datepicker-toggle>
<mat-datepicker #transactionDateToPicker></mat-datepicker>
</mat-form-field>
<mat-form-field fxFlex="31%">
<input matInput placeholder="Transaction ID" [formControl]="transactionId">
<mat-label>Transaction ID</mat-label>
<input matInput [formControl]="transactionId">
</mat-form-field>
</div>
@@ -104,7 +110,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="viewTransaction(row)" class="select"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['transactions/view', row.transactionId]" class="select-row"></tr>
</table>

Some files were not shown because too many files have changed in this diff Show More