got new transaction working

This commit is contained in:
Patrick Nagurny
2018-11-27 16:46:00 -05:00
parent 72eac48f42
commit 70a20b6611
4 changed files with 359 additions and 111 deletions

View File

@@ -1,28 +1,10 @@
<h1>New Transaction</h1>
<div class="section">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<form [formGroup]="form" (ngSubmit)="onSubmit()" *ngIf="accountTree">
<ngb-accordion #acc="ngbAccordion" activeIds="toggle-1">
<ngb-panel id="toggle-1" [title]="getToggle1Title()">
<ng-template ngbPanelContent>
<!-- <input type="radio" class="btn btn-outline-primary mr-2" id="type-expense" value="Expense">
<input type="radio" class="btn btn-outline-primary mr-2" id="type-income" value="Income">
<input type="radio" class="btn btn-outline-primary mr-2" id="type-ob" value="Opening Balance">
<input type="radio" class="btn btn-outline-primary mr-2" id="type-other" value="Other"> -->
<div>
<!-- <button type="button" class="btn mr-2" (click)="setType('expense')" [ngClass]="{'btn-outline-primary': type !== 'expense', 'btn-primary': type === 'expense'}">
Expense
</button>
<button type="button" class="btn mr-2" (click)="setType('income')" [ngClass]="{'btn-outline-primary': type !== 'income', 'btn-primary': type === 'income'}">
Income
</button>
<button type="button" class="btn mr-2" (click)="setType('openingBalance')" [ngClass]="{'btn-outline-primary': type !== 'openingBalance', 'btn-primary': type === 'openingBalance'}">
Opening Balance
</button>
<button type="button" class="btn mr-2" (click)="setType('other')" [ngClass]="{'btn-outline-primary': type !== 'other', 'btn-primary': type === 'other'}">
Other
</button> -->
<div class="btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="type">
<label ngbButtonLabel class="btn-primary mr-2">
<input ngbButton type="radio" value="expense"> Expense
@@ -30,14 +12,13 @@
<label ngbButtonLabel class="btn-primary mr-2">
<input ngbButton type="radio" value="income"> Income
</label>
<label ngbButtonLabel class="btn-primary mr-2">
<label ngbButtonLabel class="btn-primary mr-2" *ngIf="openingBalances">
<input ngbButton type="radio" value="openingBalance"> Opening Balance
</label>
<label ngbButtonLabel class="btn-primary mr-2">
<input ngbButton type="radio" value="other"> Other
</label>
</div>
</div>
<div id="firstAccountPrimary" *ngIf="form.value?.type === 'expense'" class="mt-3">
<div class="btn-group-toggle" ngbRadioGroup name="radioBasic2" formControlName="firstAccountPrimary">
<label *ngFor="let account of expenseAccounts" ngbButtonLabel class="btn-primary mr-2">
@@ -78,9 +59,21 @@
</div>
</div>
</div>
<div id="firstAccountPrimary" *ngIf="form.value?.type === 'openingBalance'" class="mt-3">
<div id="firstAccountSelect" class="mt-3">
<div class="form-group">
<label for="firstAccountSecondary" class="col-sm-3 col-form-label">Choose Account</label>
<select class="form-control" id="account" formControlName="firstAccountSecondary">
<option *ngFor="let account of paymentAccountsAll" [value]="account.id">
{{account.label | slice:0:30}}
</option>
</select>
</div>
</div>
</div>
</ng-template>
</ngb-panel>
<ngb-panel id="toggle-2" [title]="getTitle()">
<ngb-panel id="toggle-2" [title]="getToggle2Title()" *ngIf="form.value?.type !== 'openingBalance'">
<ng-template ngbPanelContent>
<div id="secondAccountPrimary" *ngIf="form.value?.type === 'expense'" class="mt-3">
<div class="btn-group-toggle" ngbRadioGroup name="secondAccountPrimary" formControlName="secondAccountPrimary">
@@ -122,22 +115,12 @@
</div>
</div>
</div>
<div id="secondAccountSelect" *ngIf="form.value?.type === 'openingBalance'" class="mt-3">
<div class="form-group">
<label for="secondAccountSecondary" class="col-sm-3 col-form-label">Choose Account</label>
<select class="form-control" id="account" formControlName="secondAccountSecondary">
<option *ngFor="let account of paymentAccountsAll" [value]="account.id">
{{account.label | slice:0:30}}
</option>
</select>
</div>
</div>
</ng-template>
</ngb-panel>
<ngb-panel id="toggle-3" title="Amount">
<ng-template ngbPanelContent>
<div class="form-group">
<input type="number" class="form-control" id="amount" formControlName="amount">
<input #amount type="number" class="form-control" id="amount" formControlName="amount">
</div>
</ng-template>
</ngb-panel>
@@ -157,17 +140,47 @@
</ngb-panel>
<ngb-panel id="toggle-6" title="Advanced">
<ng-template ngbPanelContent>
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.
<div class="container-fluid" formArrayName="splits">
<div class="row">
<div class="col-8">
<h3>Account</h3>
</div>
<div class="col-2">
<h3>Debit</h3>
</div>
<div class="col-2">
<h3>Credit</h3>
</div>
</div>
<div class="row" *ngFor="let split of form.get('splits').controls; let i=index" [formGroup]="split">
<div class="col-8">
<div class="form-group">
<select class="form-control" id="account" formControlName="accountId">
<option *ngFor="let account of selectAccounts" [value]="account.id">
{{account.fullName | slice:0:50}}
</option>
</select>
</div>
</div>
<div class="col-2">
<div class="form-group">
<input type="number" class="form-control" formControlName="debit" [ngClass]="{'positive': accountMap[split.value.accountId]?.debitBalance, 'negative': !accountMap[split.value.accountId]?.debitBalance}"/>
</div>
</div>
<div class="col-2">
<div class="form-group">
<input type="number" class="form-control" formControlName="credit" [ngClass]="{'positive': !accountMap[split.value.accountId]?.debitBalance, 'negative': accountMap[split.value.accountId]?.debitBalance}"/>
</div>
</div>
</div>
<div class="row">
<div class="col-8">
<p>
<a (click)="addSplit()">Add Split</a>
</p>
</div>
</div>
</div>
</ng-template>
</ngb-panel>
</ngb-accordion>

View File

@@ -13,4 +13,7 @@
color: $blue;
}
}
h3 {
font-size: 1rem;
}
}

View File

@@ -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 {
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,52 +42,56 @@ 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.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.openingBalances = tree.getAccountByName('Opening Balances', 2);
});
this.form.get('firstAccountPrimary').valueChanges.subscribe(val => {
if (val === 'other') {
@@ -92,11 +100,53 @@ export class NewTransactionPage {
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 => {
@@ -107,40 +157,125 @@ export class NewTransactionPage {
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');
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 sameDay = formDate.getFullYear() === date.getFullYear() &&
formDate.getMonth() === date.getMonth() &&
formDate.getDate() === date.getDate();
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 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 amount = split.debit ? parseFloat(split.debit) : -parseFloat(split.credit);
amount = Math.round(amount * Math.pow(10, account.precision));
this.accountService.newAccount(account)
.subscribe(
account => {
this.router.navigate(['/accounts']);
},
err => {
this.error = err;
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() {
@@ -250,7 +385,7 @@ export class NewTransactionPage {
return str;
}
getTitle() {
getToggle2Title() {
let account = this.getSecondAccount();
if (this.form.value.type === 'income') {
@@ -263,10 +398,6 @@ export class NewTransactionPage {
}
getFirstAccount() {
if(!this.accountTree) {
return null;
}
if (this.form.value.firstAccountPrimary && this.form.value.firstAccountPrimary !== 'other') {
return this.accountTree.accountMap[this.form.value.firstAccountPrimary];
}
@@ -274,11 +405,21 @@ export class NewTransactionPage {
return this.accountTree.accountMap[this.form.value.firstAccountSecondary];
}
getSecondAccount() {
if(!this.accountTree) {
return null;
getFirstAccountAmount() {
let account = this.getFirstAccount();
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];
}
@@ -286,7 +427,90 @@ export class NewTransactionPage {
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);
}
}

View File

@@ -87,3 +87,11 @@ a:hover {
padding: 0 0.5rem;
}
}
.positive {
color: $positive;
}
.negative {
color: $negative;
}