feat(accounting): migrate opening balances (#277)
Add migrate opening balances component to set the opening balances for GL Accounts for a branch or entire organisation.
This commit is contained in:
@@ -35,6 +35,7 @@ import { ProvisioningEntriesComponent } from './provisioning-entries/provisionin
|
|||||||
import { CreateProvisioningEntryComponent } from './provisioning-entries/create-provisioning-entry/create-provisioning-entry.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 { 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 { ViewProvisioningJournalEntriesComponent } from './provisioning-entries/view-provisioning-journal-entries/view-provisioning-journal-entries.component';
|
||||||
|
import { MigrateOpeningBalancesComponent } from './migrate-opening-balances/migrate-opening-balances.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
Route.withShell([
|
Route.withShell([
|
||||||
@@ -241,6 +242,11 @@ const routes: Routes = [
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'migrate-opening-balances',
|
||||||
|
component: MigrateOpeningBalancesComponent,
|
||||||
|
data: { title: extract('Migrate Opening Balances'), breadcrumb: 'Migrate Opening Balances' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,9 @@
|
|||||||
<p matLine>List of Financial Activities and GL Account Mappings</p>
|
<p matLine>List of Financial Activities and GL Account Mappings</p>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
|
|
||||||
<mat-list-item>
|
<mat-list-item [routerLink]="['/accounting/migrate-opening-balances']">
|
||||||
<mat-icon matListIcon>monetization_on</mat-icon>
|
<mat-icon matListIcon>monetization_on</mat-icon>
|
||||||
<a matLine href="https://www.google.com">
|
<h4 matLine>Migrate Opening Balances (Office-wise)</h4>
|
||||||
Migrate Opening Balances (Office-wise)
|
|
||||||
</a>
|
|
||||||
<p matLine>Set or update office-level opening balances for GL accounts</p>
|
<p matLine>Set or update office-level opening balances for GL accounts</p>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { ProvisioningEntriesComponent } from './provisioning-entries/provisionin
|
|||||||
import { CreateProvisioningEntryComponent } from './provisioning-entries/create-provisioning-entry/create-provisioning-entry.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 { 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 { ViewProvisioningJournalEntriesComponent } from './provisioning-entries/view-provisioning-journal-entries/view-provisioning-journal-entries.component';
|
||||||
|
import { MigrateOpeningBalancesComponent } from './migrate-opening-balances/migrate-opening-balances.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -70,7 +71,8 @@ import { ViewProvisioningJournalEntriesComponent } from './provisioning-entries/
|
|||||||
ProvisioningEntriesComponent,
|
ProvisioningEntriesComponent,
|
||||||
CreateProvisioningEntryComponent,
|
CreateProvisioningEntryComponent,
|
||||||
ViewProvisioningEntryComponent,
|
ViewProvisioningEntryComponent,
|
||||||
ViewProvisioningJournalEntriesComponent
|
ViewProvisioningJournalEntriesComponent,
|
||||||
|
MigrateOpeningBalancesComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
RevertTransactionComponent,
|
RevertTransactionComponent,
|
||||||
|
|||||||
@@ -209,4 +209,16 @@ export class AccountingService {
|
|||||||
return this.http.post(`/provisioningentries/${provisioningEntryId}`, {}, { params: httpParams });
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<mat-card>
|
||||||
|
|
||||||
|
<form [formGroup]="openingBalancesForm" (ngSubmit)="submit()">
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
|
||||||
|
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
|
||||||
|
|
||||||
|
<mat-form-field fxFlex="88%">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<button mat-button type="button" color="primary" fxFlex="4%" (click)="retrieveOpeningBalances()">Retrieve</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" *ngIf="openingBalancesData">
|
||||||
|
|
||||||
|
<label fxFlex="98%" class="content"><span class="header">Opening Balances Contra Account:</span> {{ openingBalancesData.contraAccount.name + ' (' + openingBalancesData.contraAccount.glCode + ')' }}</label>
|
||||||
|
|
||||||
|
<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%">
|
||||||
|
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="openingBalancesDatePicker" placeholder="Opening Balances Date" required formControlName="transactionDate">
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="openingBalancesDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #openingBalancesDatePicker></mat-datepicker>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" class="content" fxFlex="100%">
|
||||||
|
|
||||||
|
<div class="header" fxFlex="8%">
|
||||||
|
Type
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header" fxFlex="18%">
|
||||||
|
GL Code
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header" fxFlex="18%">
|
||||||
|
Account
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header" fxFlex="23%">
|
||||||
|
Total Debit: {{ debitsSum }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header" fxFlex="23%">
|
||||||
|
Total Credit: {{ creditsSum }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxFlexFill fxLayout="row wrap" fxLayout.lt-md="column" class="content" formArrayName="glAccountEntries"
|
||||||
|
*ngFor="let credit of getFormArrayData('glAccountEntries').controls; let i = index;">
|
||||||
|
|
||||||
|
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" [formGroupName]="i">
|
||||||
|
|
||||||
|
<div fxFlex="8%">
|
||||||
|
{{ (openingBalancesData.glAccounts[i].glAccountType.value !== openingBalancesData.glAccounts[i-1]?.glAccountType.value) ? openingBalancesData.glAccounts[i].glAccountType.value : '' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxFlex="18%">
|
||||||
|
{{ openingBalancesData.glAccounts[i].glAccountCode }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxFlex="18%">
|
||||||
|
{{ openingBalancesData.glAccounts[i].glAccountName }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxFlex="23%">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="number" placeholder="Debit" formControlName="debit">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxFlex="23%">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="number" placeholder="Credit" formControlName="credit">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</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>
|
||||||
|
</mat-card-actions>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.content {
|
||||||
|
div {
|
||||||
|
margin: 1rem 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MigrateOpeningBalancesComponent } from './migrate-opening-balances.component';
|
||||||
|
|
||||||
|
describe('MigrateOpeningBalancesComponent', () => {
|
||||||
|
let component: MigrateOpeningBalancesComponent;
|
||||||
|
let fixture: ComponentFixture<MigrateOpeningBalancesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ MigrateOpeningBalancesComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MigrateOpeningBalancesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormGroup, FormBuilder, Validators, FormArray, ValidatorFn, ValidationErrors } from '@angular/forms';
|
||||||
|
|
||||||
|
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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-migrate-opening-balances',
|
||||||
|
templateUrl: './migrate-opening-balances.component.html',
|
||||||
|
styleUrls: ['./migrate-opening-balances.component.scss']
|
||||||
|
})
|
||||||
|
export class MigrateOpeningBalancesComponent implements OnInit {
|
||||||
|
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
maxDate = new Date();
|
||||||
|
openingBalancesForm: FormGroup;
|
||||||
|
openingBalancesData: any;
|
||||||
|
officeData: any;
|
||||||
|
currencyData: any;
|
||||||
|
|
||||||
|
debitsSum = 0;
|
||||||
|
creditsSum = 0;
|
||||||
|
|
||||||
|
constructor(private accountingService: AccountingService,
|
||||||
|
private formBuilder: FormBuilder) { }
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpeningBalancesForm() {
|
||||||
|
this.openingBalancesForm = this.formBuilder.group({
|
||||||
|
'officeId': ['', Validators.required],
|
||||||
|
'currencyCode': ['', Validators.required],
|
||||||
|
'transactionDate': ['', Validators.required],
|
||||||
|
'glAccountEntries': this.formBuilder.array([])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createGLAccountEntryForm(glAccount: any): FormGroup {
|
||||||
|
return this.formBuilder.group({
|
||||||
|
'glAccountId': [glAccount.glAccountId],
|
||||||
|
'debit': [null],
|
||||||
|
'credit': [null]
|
||||||
|
}, { validator: onlyOneOfTheFieldsIsRequiredValidator });
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormArrayData(type: string): FormArray {
|
||||||
|
return <FormArray>this.openingBalancesForm.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
openingBalancesData.glAccounts.forEach((glAccount: any) => {
|
||||||
|
entry.push(this.createGLAccountEntryForm(glAccount));
|
||||||
|
});
|
||||||
|
this.openingBalancesData = openingBalancesData;
|
||||||
|
|
||||||
|
entry.valueChanges.subscribe(() => {
|
||||||
|
this.debitsSum = 0;
|
||||||
|
this.creditsSum = 0;
|
||||||
|
entry.controls.forEach(value => {
|
||||||
|
this.debitsSum += value.value.debit;
|
||||||
|
this.creditsSum += value.value.credit;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user