diff --git a/src/app/transaction/new.html b/src/app/transaction/new.html
index ba1a78e..648daae 100644
--- a/src/app/transaction/new.html
+++ b/src/app/transaction/new.html
@@ -1,28 +1,10 @@
New Transaction
+
+
+
+ Choose Account
+
+
+ {{account.label | slice:0:30}}
+
+
+
+
+
-
+
-
-
- Choose Account
-
-
- {{account.label | slice:0:30}}
-
-
-
-
-
+
@@ -157,17 +140,47 @@
- Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon
- officia
- aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon
- tempor,
- sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh
- helvetica,
- craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo.
- Leggings
- occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them
- accusamus
- labore sustainable VHS.
+
+
+
+
Account
+
+
+
Debit
+
+
+
Credit
+
+
+
+
+
+
+
+ {{account.fullName | slice:0:50}}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/transaction/new.scss b/src/app/transaction/new.scss
index eeaa310..d2dadba 100644
--- a/src/app/transaction/new.scss
+++ b/src/app/transaction/new.scss
@@ -13,4 +13,7 @@
color: $blue;
}
}
+ h3 {
+ font-size: 1rem;
+ }
}
\ No newline at end of file
diff --git a/src/app/transaction/new.ts b/src/app/transaction/new.ts
index 263496c..1a3186d 100644
--- a/src/app/transaction/new.ts
+++ b/src/app/transaction/new.ts
@@ -1,18 +1,22 @@
-import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
+import { Component, ViewEncapsulation, ViewChild, ElementRef } from '@angular/core';
import { Router } from '@angular/router';
-import {
+import {
FormGroup,
FormControl,
Validators,
FormBuilder,
+ FormArray,
AbstractControl,
ValidationErrors
} from '@angular/forms';
import { AccountService } from '../core/account.service';
+import { TransactionService } from '../core/transaction.service';
import { OrgService } from '../core/org.service';
import { Account, AccountApi, AccountTree } from '../shared/account';
import { Util } from '../shared/util';
import { AppError } from '../shared/error';
+import { Transaction, Split } from '../shared/transaction';
+import { Logger } from '../core/logger';
@Component({
selector: 'app-txnew',
@@ -38,109 +42,240 @@ export class NewTransactionPage {
public incomeAccountsAll: any[] = [];
public paymentAccountsAll: any[] = [];
public assetAccountsAll: any[] = [];
+ public openingBalances: Account;
private accountTree: AccountTree;
+ public accountMap: any;
@ViewChild('acc') acc: any;
+ @ViewChild('amount') amount: ElementRef
+
+ // TODO This code needs to be cleaned up
constructor(
private router: Router,
private accountService: AccountService,
+ private txService: TransactionService,
private orgService: OrgService,
- private fb: FormBuilder) {
+ private fb: FormBuilder,
+ private log: Logger) {
this.numAccountsShown = 3;
let org = this.orgService.getCurrentOrg();
let dateString = Util.getLocalDateString(new Date());
- this.form = fb.group({
- 'type': ['', Validators.required],
- 'firstAccountPrimary': [null, Validators.required],
- 'firstAccountSecondary': [null],
- 'secondAccountPrimary': [null, Validators.required],
- 'secondAccountSecondary': [null],
- 'amount': [null, Validators.required],
- 'description': [''],
- 'date': [dateString, Validators.required]
+ this.form = this.fb.group({
+ type: ['', Validators.required],
+ firstAccountPrimary: [null, Validators.required],
+ firstAccountSecondary: [null],
+ secondAccountPrimary: [null, Validators.required],
+ secondAccountSecondary: [null],
+ amount: [null, Validators.required],
+ description: [''],
+ date: [dateString, Validators.required],
+ splits: fb.array([]),
});
- this.accountService.getAccountTree().subscribe(tree => {
+ this.addSplit();
+ this.addSplit();
+
+ this.accountService.getAccountTree().take(1).subscribe(tree => {
this.accountTree = tree;
+ this.accountMap = tree.accountMap;
this.selectAccounts = tree.getFlattenedAccounts().filter(account => {
- let isAsset = account.fullName.match(/^Assets/);
- let isLiability = account.fullName.match(/^Liabilities/);
- return !account.children.length && (isAsset || isLiability);
+ return !account.children.length;
});
this.getExpenseAccounts();
this.getIncomeAccounts();
this.getPaymentAccounts();
this.getAssetAccounts();
+
+ this.openingBalances = tree.getAccountByName('Opening Balances', 2);
});
- this.form.get('type').valueChanges.subscribe(val => {
- if(val === 'openingBalance') {
- this.acc.collapse('toggle-1');
- this.acc.expand('toggle-2');
- this.form.patchValue({description: 'Opening Balance'});
- }
- })
-
this.form.get('firstAccountPrimary').valueChanges.subscribe(val => {
- if(val === 'other') {
+ if (val === 'other') {
return;
}
this.acc.collapse('toggle-1');
this.acc.expand('toggle-2');
+
+ let splits = this.form.get('splits') as FormArray;
+
+ if (splits.length > 0) {
+ splits.at(0).patchValue({
+ accountId: val
+ });
+ }
});
this.form.get('firstAccountSecondary').valueChanges.subscribe(val => {
+ if (this.form.value.type === 'openingBalance') {
+ this.acc.collapse('toggle-1');
+ this.acc.expand('toggle-3');
+ this.acc.expand('toggle-4');
+ this.form.patchValue({
+ description: 'Opening Balance',
+ firstAccountPrimary: 'other',
+ secondAccountPrimary: this.openingBalances.id
+ });
+ this.focusAmount();
+ let splits = this.form.get('splits') as FormArray;
+
+ if (splits.length > 1) {
+ let firstAccount = this.getFirstAccount();
+ let secondAccount = this.openingBalances;
+ splits.at(0).patchValue({
+ accountId: firstAccount.id
+ });
+ splits.at(1).patchValue({
+ accountId: secondAccount.id
+ });
+ }
+ return;
+ }
+
this.acc.collapse('toggle-1');
this.acc.expand('toggle-2');
+
+ let splits = this.form.get('splits') as FormArray;
+
+ if (splits.length > 0) {
+ let account = this.getFirstAccount();
+ splits.at(0).patchValue({
+ accountId: account.id
+ });
+ }
});
this.form.get('secondAccountPrimary').valueChanges.subscribe(val => {
- if(val === 'other') {
+ if (val === 'other') {
return;
}
this.acc.collapse('toggle-2');
this.acc.expand('toggle-3');
this.acc.expand('toggle-4');
+ this.focusAmount();
+
+ let splits = this.form.get('splits') as FormArray;
+
+ if (splits.length > 1) {
+ splits.at(1).patchValue({
+ accountId: val
+ });
+ }
});
this.form.get('secondAccountSecondary').valueChanges.subscribe(val => {
this.acc.collapse('toggle-2');
this.acc.expand('toggle-3');
this.acc.expand('toggle-4');
+ this.focusAmount();
+
+ let splits = this.form.get('splits') as FormArray;
+
+ if (splits.length > 1) {
+ splits.at(1).patchValue({
+ accountId: val
+ });
+ }
+ });
+
+ this.form.get('amount').valueChanges.subscribe(amount => {
+ let type = this.form.get('type').value;
+ let splits = this.form.get('splits') as FormArray;
+
+ if (type === 'expense') {
+ splits.at(0).patchValue({
+ debit: amount
+ });
+ splits.at(1).patchValue({
+ credit: amount
+ });
+ } else if (type === 'income') {
+ splits.at(0).patchValue({
+ credit: amount
+ });
+ splits.at(1).patchValue({
+ debit: amount
+ });
+ } else if (type === 'openingBalance') {
+ let firstAccount = this.getFirstAccount();
+
+ if (firstAccount.debitBalance) {
+ splits.at(0).patchValue({
+ debit: amount
+ });
+ splits.at(1).patchValue({
+ credit: amount
+ });
+ } else {
+ splits.at(0).patchValue({
+ credit: amount
+ });
+ splits.at(1).patchValue({
+ debit: amount
+ });
+ }
+ }
});
}
onSubmit() {
- let account = new AccountApi(this.form.value);
- account.id = Util.newGuid();
- let parentAccount = this.accountTree.accountMap[account.parent];
+ this.error = null;
- if(!parentAccount) {
- this.error = new AppError('Invalid parent account');
- return;
+ let date = new Date();
+ let formDate = Util.getDateFromLocalDateString(this.form.value.date);
+
+ if (formDate.getTime()) {
+ // make the time be at the very end of the day
+ formDate.setHours(23, 59, 59, 999);
}
- let org = this.orgService.getCurrentOrg();
- account.orgId = org.id;
- account.debitBalance = parentAccount.debitBalance;
- account.currency = account.currency || parentAccount.currency;
- account.precision = account.precision !== null ? account.precision : parentAccount.precision;
+ let sameDay = formDate.getFullYear() === date.getFullYear() &&
+ formDate.getMonth() === date.getMonth() &&
+ formDate.getDate() === date.getDate();
- this.accountService.newAccount(account)
- .subscribe(
- account => {
- this.router.navigate(['/accounts']);
- },
- err => {
- this.error = err;
- }
- );
+ if (formDate.getTime() && !sameDay) {
+ date = formDate;
+ }
+
+ let tx = new Transaction({
+ id: Util.newGuid(),
+ description: this.form.value.description,
+ date: date,
+ splits: []
+ });
+
+ for (let i = 0; i < this.form.value.splits.length; i++) {
+ let split = this.form.value.splits[i];
+ let account = this.accountTree.accountMap[split.accountId];
+
+ if (!account) {
+ this.error = new AppError('Invalid account');
+ return;
+ }
+
+ let amount = split.debit ? parseFloat(split.debit) : -parseFloat(split.credit);
+ amount = Math.round(amount * Math.pow(10, account.precision));
+
+ tx.splits.push(new Split({
+ accountId: split.accountId,
+ amount: amount,
+ nativeAmount: amount
+ }));
+ }
+
+ this.log.debug(tx);
+
+ this.txService.newTransaction(tx)
+ .subscribe(tx => {
+ this.router.navigate(['/dashboard']);
+ }, error => {
+ this.error = error;
+ });
}
getExpenseAccounts() {
@@ -199,7 +334,7 @@ export class NewTransactionPage {
id: account.id,
name: account.name,
label: this.accountTree.getAccountLabel(account, depth),
- hidden: i < this.numAccountsShown ? false: true
+ hidden: i < this.numAccountsShown ? false : true
}
});
@@ -210,19 +345,19 @@ export class NewTransactionPage {
let aAlpha = a.label.charCodeAt(0) >= 65 && a.label.charCodeAt(0) <= 122;
let bAlpha = b.label.charCodeAt(0) >= 65 && b.label.charCodeAt(0) <= 122;
- if(!aAlpha && bAlpha) {
+ if (!aAlpha && bAlpha) {
return 1;
}
- if(aAlpha && !bAlpha) {
+ if (aAlpha && !bAlpha) {
return -1;
}
- if(a.label > b.label) {
+ if (a.label > b.label) {
return 1;
}
- if(a.label < b.label) {
+ if (a.label < b.label) {
return -1;
}
@@ -240,9 +375,9 @@ export class NewTransactionPage {
let type = this.form.value.type;
- if(type) {
+ if (type) {
str += type.charAt(0).toUpperCase() + type.substr(1);
- if(account) {
+ if (account) {
str += ' (' + account.name + ')';
}
}
@@ -250,12 +385,12 @@ export class NewTransactionPage {
return str;
}
- getTitle() {
+ getToggle2Title() {
let account = this.getSecondAccount();
-
- if(this.form.value.type === 'income') {
+
+ if (this.form.value.type === 'income') {
return 'Where was the money sent? ' + (account ? account.name : '');
- } else if(this.form.value.type === 'openingBalance') {
+ } else if (this.form.value.type === 'openingBalance') {
return 'What account? ' + (account ? account.name : '');
}
@@ -263,30 +398,119 @@ export class NewTransactionPage {
}
getFirstAccount() {
- if(!this.accountTree) {
- return null;
- }
-
- if(this.form.value.firstAccountPrimary && this.form.value.firstAccountPrimary !== 'other') {
+ if (this.form.value.firstAccountPrimary && this.form.value.firstAccountPrimary !== 'other') {
return this.accountTree.accountMap[this.form.value.firstAccountPrimary];
}
return this.accountTree.accountMap[this.form.value.firstAccountSecondary];
}
- getSecondAccount() {
- if(!this.accountTree) {
- return null;
- }
+ getFirstAccountAmount() {
+ let account = this.getFirstAccount();
- if(this.form.value.secondAccountPrimary && this.form.value.secondAccountPrimary !== 'other') {
+ switch (this.form.value.type) {
+ case 'expense':
+ return this.form.value.amount * Math.pow(10, account.precision);
+ case 'income':
+ return -this.form.value.amount * Math.pow(10, account.precision);
+ case 'openingBalance':
+ return this.form.value.amount * Math.pow(10, account.precision)
+ * (account.debitBalance ? 1 : -1);
+ }
+ }
+
+ getSecondAccount() {
+ if (this.form.value.secondAccountPrimary && this.form.value.secondAccountPrimary !== 'other') {
return this.accountTree.accountMap[this.form.value.secondAccountPrimary];
}
return this.accountTree.accountMap[this.form.value.secondAccountSecondary];
}
+ getSecondAccountAmount() {
+ let firstAccount = this.getFirstAccount();
+ let secondAccount = this.getSecondAccount();
+ switch (this.form.value.type) {
+ case 'expense':
+ return -this.form.value.amount * Math.pow(10, secondAccount.precision);
+ case 'income':
+ return this.form.value.amount * Math.pow(10, secondAccount.precision);
+ case 'openingBalance':
+ return this.form.value.amount * Math.pow(10, secondAccount.precision)
+ * (firstAccount.debitBalance ? -1 : 1);
+ }
+ }
+
+ addSplit() {
+ let splits = this.form.get('splits') as FormArray;
+
+ let control = new FormGroup({
+ accountId: new FormControl(),
+ debit: new FormControl(),
+ credit: new FormControl()
+ }, { updateOn: 'blur' });
+
+ control.valueChanges.subscribe(val => {
+ this.fillEmptySplit();
+ });
+ splits.push(control);
+
+ this.fillEmptySplit();
+ }
+
+ fillEmptySplit() {
+ // Total up splits and fill in any empty split with the leftover value
+ let splits = this.form.get('splits') as FormArray;
+ let amount = 0;
+ let emptySplit: AbstractControl;
+ for (let i = 0; i < splits.length; i++) {
+ let split = splits.at(i);
+ amount += parseFloat(split.get('debit').value) || 0;
+ amount -= parseFloat(split.get('credit').value) || 0;
+
+ if (!split.get('debit').value && !split.get('credit').value) {
+ emptySplit = split;
+ }
+ }
+
+ if (emptySplit) {
+ let precision = 2;
+
+ let accountId = emptySplit.get('accountId').value;
+ let account = null;
+
+ if (this.accountTree && accountId) {
+ account = this.accountTree.accountMap[emptySplit.get('accountId').value];
+ }
+
+ if (account) {
+ precision = account.precision;
+ }
+
+ amount = this.round(-amount, precision);
+
+ if (amount) {
+ emptySplit.patchValue({
+ debit: amount >= 0 ? amount : '',
+ credit: amount < 0 ? -amount : ''
+ });
+ }
+ }
+ }
+
+ round(amount, precision) {
+ return Math.round(amount * Math.pow(10, precision)) / Math.pow(10, precision);
+ }
+
+ focusAmount() {
+ // TODO Not sure how to get rid of this hack
+ setTimeout(() => {
+ if (this.amount) {
+ this.amount.nativeElement.focus();
+ }
+ }, 1);
+ }
}
\ No newline at end of file
diff --git a/src/sass/styles.scss b/src/sass/styles.scss
index 3727141..a281b4a 100644
--- a/src/sass/styles.scss
+++ b/src/sass/styles.scss
@@ -86,4 +86,12 @@ a:hover {
.content > * > .description {
padding: 0 0.5rem;
}
+}
+
+.positive {
+ color: $positive;
+}
+
+.negative {
+ color: $negative;
}
\ No newline at end of file