initial commit

This commit is contained in:
Patrick Nagurny
2018-10-19 11:28:08 -04:00
commit 5ff09d328d
139 changed files with 23448 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
<h1>Balance Sheet<br>{{date | date:"shortDate"}} (<a [routerLink]="" (click)="toggleShowOptionsForm()">options</a>)</h1>
<div class="section">
<div *ngIf="showOptionsForm" class="card card-body">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-group row">
<label for="name" class="col-sm-3 col-form-label">Date</label>
<div class="col-sm-9">
<input formControlName="date" type="date" class="form-control" id="date">
</div>
</div>
<div class="form-group row">
<label for="priceSource" class="col-sm-3 col-form-label">Price Source</label>
<div class="col-sm-9">
<select class="form-control" id="priceSource" formControlName="priceSource">
<option value="cost">Cost</option>
<option value="price">Nearest In Time</option>
</select>
</div>
</div>
<p *ngIf="error">{{error.message}}</p>
<button class="btn btn-primary" type="submit" [disabled]="!form.valid">Update</button>
</form>
</div>
<div *ngIf="assetAccount" class="container-fluid report">
<div class="row" depth="1">
<div class="col-8">
<h4>Assets</h4>
</div>
<div class="col-4 amount">
<h4 *ngIf="priceSource === 'price'">{{+assetAccount.totalNativeBalancePrice | currencyFormat:org.precision}}</h4>
<h4 *ngIf="priceSource === 'cost'">{{+assetAccount.totalNativeBalanceCost | currencyFormat:org.precision}}</h4>
</div>
</div>
<div class="row" *ngFor="let account of assetAccounts" [attr.depth]="account.depth">
<div class="col-8 name" *ngIf="account.totalNativeBalancePrice || account.totalNativeBalanceCost">
<span *ngIf="account.children.length">{{account.name}}</span>
<span *ngIf="!account.children.length"><a [routerLink]="'/accounts/' + account.id + '/transactions'">{{account.name | slice:0:30}}</a></span>
</div>
<div class="col-4 amount" *ngIf="priceSource === 'price' && account.totalNativeBalancePrice">
{{+account.totalNativeBalancePrice | currencyFormat:org.precision}}
</div>
<div class="col-4 amount" *ngIf="priceSource === 'cost' && account.totalNativeBalanceCost">
{{+account.totalNativeBalanceCost | currencyFormat:org.precision}}
</div>
</div>
<div class="row">
<div class="col-12">
<hr/>
</div>
</div>
<div class="row" depth="1">
<div class="col-8">
<h4>Liabilities</h4>
</div>
<div class="col-4 amount">
<h4 *ngIf="priceSource === 'price'">{{-liabilityAccount.totalNativeBalancePrice | currencyFormat:org.precision}}</h4>
<h4 *ngIf="priceSource === 'cost'">{{-liabilityAccount.totalNativeBalanceCost | currencyFormat:org.precision}}</h4>
</div>
</div>
<div class="row" *ngFor="let account of liabilityAccounts" [attr.depth]="account.depth">
<div class="col-8 name" *ngIf="account.totalNativeBalancePrice || account.totalNativeBalanceCost">
<span *ngIf="account.children.length">{{account.name}}</span>
<span *ngIf="!account.children.length"><a [routerLink]="'/accounts/' + account.id + '/transactions'">{{account.name | slice:0:30}}</a></span>
</div>
<div class="col-4 amount" *ngIf="priceSource === 'price' && account.totalNativeBalancePrice">
{{-account.totalNativeBalancePrice | currencyFormat:org.precision}}
</div>
<div class="col-4 amount" *ngIf="priceSource === 'cost' && account.totalNativeBalanceCost">
{{-account.totalNativeBalanceCost | currencyFormat:org.precision}}
</div>
</div>
<div class="row" depth="1">
<div class="col-8">
<h4>Equity</h4>
</div>
<div class="col-4 amount">
<h4 *ngIf="priceSource === 'price'">{{-equityAccount.totalNativeBalancePrice | currencyFormat:org.precision}}</h4>
<h4 *ngIf="priceSource === 'cost'">{{-equityAccount.totalNativeBalanceCost | currencyFormat:org.precision}}</h4>
</div>
</div>
<div class="row" *ngFor="let account of equityAccounts" [attr.depth]="account.depth">
<div class="col-8 name" *ngIf="account.totalNativeBalancePrice || account.totalNativeBalanceCost">
<span *ngIf="account.children.length">{{account.name}}</span>
<span *ngIf="!account.children.length"><a [routerLink]="'/accounts/' + account.id + '/transactions'">{{account.name | slice:0:30}}</a></span>
</div>
<div class="col-4 amount" *ngIf="priceSource === 'price' && account.totalNativeBalancePrice">
{{-account.totalNativeBalancePrice | currencyFormat:org.precision}}
</div>
<div class="col-4 amount" *ngIf="priceSource === 'cost' && account.totalNativeBalanceCost">
{{-account.totalNativeBalanceCost | currencyFormat:org.precision}}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,159 @@
import { Component } from '@angular/core';
import { AccountService } from '../core/account.service';
import { OrgService } from '../core/org.service';
import { ConfigService } from '../core/config.service';
import { SessionService } from '../core/session.service';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { Account } from '../shared/account';
import { Org } from '../shared/org';
import { TxListPage } from '../transaction/list';
import {
FormGroup,
FormControl,
Validators,
FormBuilder,
AbstractControl,
ValidationErrors
} from '@angular/forms';
import { AppError } from '../shared/error';
import { Util } from '../shared/util';
@Component({
selector: 'app-balancesheet',
templateUrl: 'balancesheet.html',
styleUrls: ['./reports.scss']
})
export class BalanceSheetReport {
public org: Org;
public date: Date;
public assetAccount: Account;
public assetAccounts: Account[] = [];
public liabilityAccount: Account;
public liabilityAccounts: Account[] = [];
public equityAccount: Account;
public equityAccounts: Account[] = [];
public amounts: any = {};
public form: FormGroup;
public error: AppError;
public showOptionsForm: boolean = false;
private treeSubscription: Subscription;
private priceSource: string;
constructor(
private fb: FormBuilder,
private accountService: AccountService,
private orgService: OrgService,
private configService: ConfigService,
private sessionService: SessionService) {
this.date = new Date();
this.priceSource = 'price';
let reportData = this.configService.get('reportData');
if(reportData && reportData.balanceSheet) {
let reportConfig = reportData.balanceSheet;
if(reportConfig.date) {
this.date = new Date(reportConfig.date);
}
if(reportConfig.priceSource) {
this.priceSource = reportConfig.priceSource;
}
}
this.form = fb.group({
date: [Util.getLocalDateString(this.date), Validators.required],
priceSource: [this.priceSource, Validators.required]
});
}
ngOnInit() {
this.sessionService.setLoading(true);
this.org = this.orgService.getCurrentOrg();
this.amounts = {};
this.assetAccount = null;
this.treeSubscription = this.accountService.getAccountTreeAtDate(this.date)
.subscribe(tree => {
this.sessionService.setLoading(false);
this.assetAccount = tree.getAccountByName('Assets', 1);
this.assetAccounts = tree.getFlattenedAccounts(this.assetAccount);
this.liabilityAccount = tree.getAccountByName('Liabilities', 1);
this.liabilityAccounts = tree.getFlattenedAccounts(this.liabilityAccount);
this.equityAccount = tree.getAccountByName('Equity', 1);
this.equityAccounts = tree.getFlattenedAccounts(this.equityAccount);
let incomeAccount = tree.getAccountByName('Income', 1);
let expenseAccount = tree.getAccountByName('Expenses', 1);
let retainedEarnings = new Account({
id: 'Retained Earnings',
name: 'Retained Earnings',
depth: 2,
children: [null], // hack to fool template into not displaying a link
totalNativeBalanceCost: incomeAccount.totalNativeBalanceCost +
expenseAccount.totalNativeBalanceCost,
totalNativeBalancePrice: incomeAccount.totalNativeBalancePrice +
expenseAccount.totalNativeBalancePrice
});
let unrealizedGains = new Account({
id: 'Unrealized Gains',
name: 'Unrealized Gains',
depth: 2,
children: [null], // hack to fool template into not displaying a link
totalNativeBalanceCost: -(this.assetAccount.totalNativeBalanceCost +
this.liabilityAccount.totalNativeBalanceCost +
this.equityAccount.totalNativeBalanceCost +
retainedEarnings.totalNativeBalanceCost),
totalNativeBalancePrice: -(this.assetAccount.totalNativeBalancePrice +
this.liabilityAccount.totalNativeBalancePrice +
this.equityAccount.totalNativeBalancePrice +
retainedEarnings.totalNativeBalancePrice)
});
this.equityAccounts.push(retainedEarnings);
this.equityAccounts.push(unrealizedGains);
// TODO is this modifying a tree that might be used elsewhere?
// Not all functions are pure...
this.equityAccount.totalNativeBalanceCost = -this.assetAccount.totalNativeBalanceCost
- this.liabilityAccount.totalNativeBalanceCost;
this.equityAccount.totalNativeBalancePrice = -this.assetAccount.totalNativeBalancePrice
- this.liabilityAccount.totalNativeBalancePrice;
// this.dataService.setLoading(false);
});
}
onSubmit() {
this.treeSubscription.unsubscribe();
//this.dataService.setLoading(true);
this.showOptionsForm = false;
this.date = Util.getDateFromLocalDateString(this.form.value.date);
this.priceSource = this.form.value.priceSource;
let reportData = this.configService.get('reportData');
if(!reportData) {
reportData = {};
}
reportData.balanceSheet = {
date: this.date,
priceSource: this.priceSource
}
this.configService.put('reportData', reportData);
this.ngOnInit();
}
toggleShowOptionsForm() {
this.showOptionsForm = !this.showOptionsForm;
}
}

View File

@@ -0,0 +1,65 @@
<h1>Income Statement<br>{{startDate | date:"shortDate"}} - {{endDate.getTime() - 1 | date:"shortDate"}} (<a [routerLink]="" (click)="toggleShowDateForm()">edit</a>)</h1>
<div class="section">
<form *ngIf="showDateForm" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-group row">
<label for="name" class="col-sm-3 col-form-label">Start Date</label>
<div class="col-sm-9">
<input formControlName="startDate" type="date" class="form-control" id="startDate">
</div>
</div>
<div class="form-group row">
<label for="name" class="col-sm-3 col-form-label">End Date</label>
<div class="col-sm-9">
<input formControlName="endDate" type="date" class="form-control" id="endDate">
</div>
</div>
<p *ngIf="error">{{error.message}}</p>
<button class="btn btn-primary" type="submit" [disabled]="!form.valid">Update</button>
</form>
<div *ngIf="incomeAccount" class="container-fluid report">
<div class="row" depth="1">
<div class="col-8">
<h4>Income</h4>
</div>
<div class="col-4 amount">
<h4>{{-incomeAccount.totalNativeBalanceCost | currencyFormat:org.precision}}</h4>
</div>
</div>
<div class="row" *ngFor="let account of incomeAccounts" [attr.depth]="account.depth">
<div class="col-8 name" *ngIf="account.totalNativeBalanceCost">
<span *ngIf="account.children.length">{{account.name}}</span>
<span *ngIf="!account.children.length"><a [routerLink]="'/accounts/' + account.id + '/transactions'">{{account.name | slice:0:30}}</a></span>
</div>
<div class="col-4 amount" *ngIf="account.totalNativeBalanceCost">
{{-account.totalNativeBalanceCost | currencyFormat:org.precision}}
</div>
</div>
<div class="row" depth="1">
<div class="col-8">
<h4>Expenses</h4>
</div>
<div class="col-4 amount">
<h4>{{expenseAccount.totalNativeBalanceCost | currencyFormat:org.precision}}</h4>
</div>
</div>
<div class="row" *ngFor="let account of expenseAccounts" [attr.depth]="account.depth">
<div class="col-8 name" *ngIf="account.totalNativeBalanceCost">
<span *ngIf="account.children.length">{{account.name}}</span>
<span *ngIf="!account.children.length"><a [routerLink]="'/accounts/' + account.id + '/transactions'">{{account.name | slice:0:30}}</a></span>
</div>
<div class="col-4 amount" *ngIf="account.totalNativeBalanceCost">
{{account.totalNativeBalanceCost | currencyFormat:org.precision}}
</div>
</div>
<div class="row" depth="1">
<div class="col-8">
<h4>Net Income</h4>
</div>
<div class="col-4 amount">
<h4>{{-incomeAccount.totalNativeBalanceCost - expenseAccount.totalNativeBalanceCost | currencyFormat:org.precision}}</h4>
</div>
</div>
</div>
</div>

113
src/app/reports/income.ts Normal file
View File

@@ -0,0 +1,113 @@
import { Component } from '@angular/core';
import { AccountService } from '../core/account.service';
import { OrgService } from '../core/org.service';
import { ConfigService } from '../core/config.service';
import { SessionService } from '../core/session.service';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/observable/zip';
import { Account, AccountTree } from '../shared/account';
import { Org } from '../shared/org';
import { TxListPage } from '../transaction/list';
import {
FormGroup,
FormControl,
Validators,
FormBuilder,
AbstractControl,
ValidationErrors
} from '@angular/forms';
import { AppError } from '../shared/error';
import { Util } from '../shared/util';
@Component({
selector: 'app-income',
templateUrl: 'income.html',
styleUrls: ['./reports.scss']
})
export class IncomeReport {
public org: Org;
public startDate: Date;
public endDate: Date;
public incomeAccount: Account;
public incomeAccounts: Account[] = [];
public expenseAccount: Account;
public expenseAccounts: Account[] = [];
public form: FormGroup;
public error: AppError;
public showDateForm: boolean = false;
private treeSubscription: Subscription;
constructor(
private fb: FormBuilder,
private accountService: AccountService,
private orgService: OrgService,
private configService: ConfigService,
private sessionService: SessionService) {
this.startDate = new Date();
this.startDate.setDate(1);
this.startDate.setHours(0, 0, 0, 0);
this.endDate = new Date(this.startDate);
this.endDate.setMonth(this.startDate.getMonth() + 1);
let reportData = this.configService.get('reportData');
if(reportData && reportData.income) {
let reportConfig = reportData.income;
if(reportConfig.startDate) {
this.startDate = new Date(reportConfig.startDate);
}
if(reportConfig.endDate) {
this.endDate = new Date(reportConfig.endDate);
}
}
this.form = fb.group({
startDate: [Util.getLocalDateString(this.startDate), Validators.required],
endDate: [Util.getLocalDateString(new Date(this.endDate.getTime() - 1)), Validators.required]
});
}
ngOnInit() {
this.sessionService.setLoading(true);
this.org = this.orgService.getCurrentOrg();
this.treeSubscription = this.accountService.getAccountTreeWithPeriodBalance(this.startDate, this.endDate)
.subscribe(tree => {
this.sessionService.setLoading(false);
this.incomeAccount = tree.getAccountByName('Income', 1);
this.incomeAccounts = tree.getFlattenedAccounts(this.incomeAccount);
this.expenseAccount = tree.getAccountByName('Expenses', 1);
this.expenseAccounts = tree.getFlattenedAccounts(this.expenseAccount);
});
}
toggleShowDateForm() {
this.showDateForm = !this.showDateForm;
}
onSubmit() {
this.treeSubscription.unsubscribe();
//this.dataService.setLoading(true);
this.showDateForm = false;
this.startDate = Util.getDateFromLocalDateString(this.form.value.startDate);
this.endDate = Util.getDateFromLocalDateString(this.form.value.endDate);
this.endDate.setDate(this.endDate.getDate() + 1);
let reportData = this.configService.get('reportData');
if(!reportData) {
reportData = {};
}
reportData.income = {
startDate: this.startDate,
endDate: this.endDate
}
this.configService.put('reportData', reportData);
this.ngOnInit();
}
}

View File

@@ -0,0 +1,8 @@
<h1>Reports</h1>
<div class="section">
<ul>
<li *ngFor="let report of reports">
<a [routerLink]="report.url">{{report.title}}</a>
</li>
</ul>
</div>

View File

@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReportsPage } from './reports';
import { IncomeReport } from './income';
import { BalanceSheetReport } from './balancesheet';
import { ReactiveFormsModule } from '@angular/forms';
import { AppRoutingModule } from '../app-routing.module';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
ReportsPage,
IncomeReport,
BalanceSheetReport
],
imports: [
BrowserModule,
ReactiveFormsModule,
AppRoutingModule,
SharedModule
],
providers: []
})
export class ReportsModule { }

View File

@@ -0,0 +1,51 @@
.title {
text-align: center;
}
.report {
h4 {
font-size: 1.3rem
}
.total {
font-weight: bold
}
> .row[depth="1"]:first-child {
padding-top: 0px;
}
.row[depth="1"] {
padding-top: 20px;
}
.row[depth="1"] div:first-child,
.row[depth="2"] .name {
padding-left: 0px;
}
.row[depth="3"] .name {
padding-left: 20px;
}
.row[depth="4"] .name {
padding-left: 40px;
}
.row[depth="5"] .name {
padding-left: 60px;
}
.row .name {
padding-left: 60px;
}
.row[depth="1"] div:last-child,
.row[depth="2"] .amount {
padding-right: 0px
}
.row[depth="3"] .amount {
padding-right: 100px;
}
.row[depth="4"] .amount {
padding-right: 200px;
}
.row[depth="5"] .amount {
padding-right: 300px;
}
.row .amount {
padding-right: 300px;
text-align: right;
}
}

View File

@@ -0,0 +1,18 @@
import { Component } from '@angular/core';
@Component({
selector: 'page-reports',
templateUrl: 'reports.html'
})
export class ReportsPage {
reports: Array<{title: string, url: string}>;
constructor() {
this.reports = [
{ title: 'Income Statement', url: '/reports/income' },
{ title: 'Balance Sheet', url: '/reports/balancesheet'}
];
}
}