Files
openaccounting-web/src/app/reconcile/reconcile.ts

295 lines
8.7 KiB
TypeScript
Raw Normal View History

2019-06-06 13:28:23 -04:00
import { Component, ViewChild, ElementRef } from '@angular/core';
2018-10-19 11:28:08 -04:00
import { Logger } from '../core/logger';
import { Router } from '@angular/router';
import {
FormGroup,
FormControl,
Validators,
FormBuilder,
AbstractControl,
ValidationErrors
} from '@angular/forms';
import { AccountService } from '../core/account.service';
import { OrgService } from '../core/org.service';
import { TransactionService } from '../core/transaction.service';
import { Account, AccountApi, AccountTree } from '../shared/account';
import { Transaction } from '../shared/transaction';
2019-06-27 14:11:05 -04:00
import { Org } from '../shared/org';
2018-10-19 11:28:08 -04:00
import { AppError } from '../shared/error';
import { Util } from '../shared/util';
import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap';
import { ReconcileModal } from './reconcile-modal';
import { Reconciliation } from './reconciliation';
2019-06-06 13:28:23 -04:00
import { SessionService } from '../core/session.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/mergeMap';
2018-10-19 11:28:08 -04:00
@Component({
selector: 'app-reconcile',
templateUrl: 'reconcile.html'
})
export class ReconcilePage {
public accountForm: FormGroup;
public newReconcile: FormGroup;
public selectAccounts: any[];
public account: Account;
public pastReconciliations: Reconciliation[];
public unreconciledTxs: Transaction[];
public error: AppError;
2019-06-27 14:11:05 -04:00
public org: Org;
2018-10-19 11:28:08 -04:00
private accountTree: AccountTree;
2019-06-06 13:28:23 -04:00
@ViewChild('confirmDeleteModal') confirmDeleteModal: ElementRef;
2018-10-19 11:28:08 -04:00
constructor(
private router: Router,
private log: Logger,
private accountService: AccountService,
private orgService: OrgService,
private txService: TransactionService,
private fb: FormBuilder,
2019-06-06 13:28:23 -04:00
private modalService: NgbModal,
private sessionService: SessionService) {
2018-10-19 11:28:08 -04:00
2019-06-27 14:11:05 -04:00
this.org = this.orgService.getCurrentOrg();
2018-10-19 11:28:08 -04:00
this.accountForm = fb.group({
'accountId': [null, Validators.required]
});
this.newReconcile = fb.group({
'startDate': ['', Validators.required],
'startBalance': [{value: 0, disabled: true}, Validators.required],
'endDate': ['', Validators.required],
'endBalance': [0, Validators.required]
});
this.accountService.getAccountTree().subscribe(tree => {
this.accountTree = tree;
this.selectAccounts = tree.getFlattenedAccounts();
});
}
onChooseAccount() {
let account = this.accountTree.accountMap[this.accountForm.value.accountId];
if(!account) {
this.error = new AppError('Invalid account');
return;
}
this.account = account;
this.processTransactions();
}
startReconcile() {
let value = this.newReconcile.getRawValue();
let rec = new Reconciliation();
2019-06-27 14:11:05 -04:00
rec.startDate = Util.getDateFromLocalDateString(value.startDate, this.org.timezone);
rec.endDate = Util.getDateFromLocalDateString(value.endDate, this.org.timezone);
2018-10-19 11:28:08 -04:00
rec.startBalance = Math.round(parseFloat(value.startBalance) * Math.pow(10, this.account.precision));
rec.endBalance = Math.round(parseFloat(value.endBalance) * Math.pow(10, this.account.precision));
this.log.debug(rec);
let modal = this.modalService.open(ReconcileModal, {size: 'lg'});
modal.componentInstance.setData(this.account, rec, this.unreconciledTxs);
2019-06-06 13:28:23 -04:00
modal.result.then((txs) => {
2018-10-19 11:28:08 -04:00
this.log.debug('reconcile modal save');
2019-06-06 13:28:23 -04:00
rec.txs = txs;
2018-10-19 11:28:08 -04:00
this.pastReconciliations.unshift(rec);
this.newReconcile.patchValue(
{
2019-06-27 14:11:05 -04:00
startDate: Util.getLocalDateString(rec.endDate, this.org.timezone),
2018-10-19 11:28:08 -04:00
startBalance: rec.endBalance / Math.pow(10, this.account.precision),
endBalance: 0,
endDate: ''
}
);
}, (reason) => {
this.log.debug('cancel reconcile modal');
});
}
processTransactions() {
// Get all transactions for account
// Figure out reconciliations
// startDate is date of first transaction
// add up reconciled splits for given endDate to get endBalance
// sort by most recent first
// most recent endDate is used for startDate
// most recent endBalance is used for startBalance
// guess at next endDate
this.unreconciledTxs = [];
this.pastReconciliations = [];
this.txService.getTransactionsByAccount(this.account.id).subscribe(txs => {
let reconcileMap: {[date: number]: Reconciliation} = {};
let firstStartDate: Date = null;
let firstEndDate: Date = null;
txs.forEach(tx => {
if(!firstStartDate || (!firstEndDate && tx.date < firstStartDate)) {
firstStartDate = tx.date;
}
let data = tx.getData();
if(!data.reconciledSplits) {
this.unreconciledTxs.push(tx);
return;
}
let reconciled = true;
let splitIndexes = Object.keys(data.reconciledSplits).map(index => parseInt(index));
tx.splits.forEach((split, index) => {
if(split.accountId !== this.account.id) {
return;
}
if(splitIndexes.indexOf(index) === -1) {
reconciled = false;
return;
}
let endDate = new Date(data.reconciledSplits[index]);
if(!firstEndDate || endDate < firstEndDate) {
firstEndDate = endDate;
firstStartDate = new Date(tx.date);
}
if(endDate.getTime() === firstEndDate.getTime() && tx.date < firstStartDate) {
firstStartDate = new Date(tx.date);
}
if(!reconcileMap[endDate.getTime()]) {
reconcileMap[endDate.getTime()] = new Reconciliation();
reconcileMap[endDate.getTime()].endDate = endDate;
reconcileMap[endDate.getTime()].net = 0;
}
let r = reconcileMap[endDate.getTime()];
2019-06-06 13:28:23 -04:00
r.txs.push(tx);
2018-10-19 11:28:08 -04:00
if(this.account.debitBalance) {
r.net += split.amount;
} else {
r.net -= split.amount;
}
});
if(!reconciled) {
this.unreconciledTxs.push(tx);
}
});
// Figure out starting date, beginning balance and ending balance
let dates = Object.keys(reconcileMap).sort((a, b) => {
return parseInt(a) - parseInt(b);
}).map(time => {
return new Date(parseInt(time));
});
if(!dates.length) {
if(firstStartDate) {
2019-06-27 14:11:05 -04:00
this.newReconcile.patchValue({startDate: Util.getLocalDateString(firstStartDate, this.org.timezone)});
2018-10-19 11:28:08 -04:00
}
return;
}
let firstRec = reconcileMap[dates[0].getTime()];
firstRec.startDate = firstStartDate;
firstRec.startBalance = 0;
firstRec.endBalance = firstRec.net;
this.pastReconciliations.unshift(firstRec);
let lastRec = firstRec;
for(let i = 1; i < dates.length; i++) {
let rec = reconcileMap[dates[i].getTime()];
rec.startDate = new Date(lastRec.endDate);
rec.startBalance = lastRec.endBalance;
rec.endBalance = rec.startBalance + rec.net;
this.pastReconciliations.unshift(rec);
lastRec = rec;
}
this.newReconcile.patchValue(
{
2019-06-27 14:11:05 -04:00
startDate: Util.getLocalDateString(lastRec.endDate, this.org.timezone),
2018-10-19 11:28:08 -04:00
startBalance: lastRec.endBalance / Math.pow(10, this.account.precision)
}
);
});
}
2019-06-06 13:28:23 -04:00
delete() {
this.modalService.open(this.confirmDeleteModal).result.then((result) => {
this.sessionService.setLoading(true);
let rec = this.pastReconciliations[0];
Observable.from(rec.txs).mergeMap(tx => {
let oldId = tx.id;
tx.id = Util.newGuid();
let data = tx.getData();
let newSplits = {};
for(let splitId in data.reconciledSplits) {
if(tx.splits[splitId].accountId !== this.account.id) {
newSplits[splitId] = tx.splits[splitId];
}
}
data.reconciledSplits = newSplits;
tx.setData(data);
return this.txService.putTransaction(oldId, tx);
}, 8).subscribe(tx => {
this.log.debug('Saved tx ' + tx.id);
}, err => {
this.error = err;
this.sessionService.setLoading(false);
}, () => {
this.pastReconciliations.shift();
let lastRec = this.pastReconciliations[0];
if(lastRec) {
this.newReconcile.patchValue(
{
2019-06-27 14:11:05 -04:00
startDate: Util.getLocalDateString(lastRec.endDate, this.org.timezone),
2019-06-06 13:28:23 -04:00
startBalance: lastRec.endBalance / Math.pow(10, this.account.precision)
}
);
} else {
this.newReconcile.patchValue(
{
startDate: null,
startBalance: 0
}
);
}
this.sessionService.setLoading(false);
});
}, (reason) => {
this.log.debug('cancel delete');
});
}
2018-10-19 11:28:08 -04:00
}