Changed the way that account balances are computed
This commit is contained in:
parent
dd23edd5ce
commit
2728e4d55e
8 changed files with 234 additions and 83 deletions
157
src/account.cc
157
src/account.cc
|
|
@ -30,6 +30,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
|
#include "post.h"
|
||||||
|
#include "xact.h"
|
||||||
#include "interactive.h"
|
#include "interactive.h"
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
@ -166,33 +168,20 @@ namespace {
|
||||||
return string_value(account.name);
|
return string_value(account.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
value_t get_total(account_t& account) {
|
value_t get_amount(account_t& account) {
|
||||||
if (! account.xdata_ || account.xdata_->family_details.total.is_null())
|
return VALUE_OR_ZERO(account.self_total());
|
||||||
return 0L;
|
|
||||||
else
|
|
||||||
return account.xdata_->family_details.total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value_t get_count(account_t& account) {
|
value_t get_total(account_t& account) {
|
||||||
if (account.xdata_)
|
return VALUE_OR_ZERO(account.family_total());
|
||||||
return long(account.xdata_->family_details.posts_count);
|
|
||||||
else
|
|
||||||
return 0L;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value_t get_subcount(account_t& account) {
|
value_t get_subcount(account_t& account) {
|
||||||
if (account.xdata_)
|
return long(account.self_details().posts_count);
|
||||||
return long(account.xdata_->self_details.posts_count);
|
|
||||||
else
|
|
||||||
return 0L;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value_t get_amount(account_t& account) {
|
value_t get_count(account_t& account) {
|
||||||
if (! account.xdata_ ||
|
return long(account.family_details().posts_count);
|
||||||
account.xdata_->self_details.total.is_null())
|
|
||||||
return 0L;
|
|
||||||
else
|
|
||||||
return account.xdata_->self_details.total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value_t get_depth(account_t& account) {
|
value_t get_depth(account_t& account) {
|
||||||
|
|
@ -317,4 +306,132 @@ std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account_t::xdata_t::details_t&
|
||||||
|
account_t::xdata_t::details_t::operator+=(const details_t& other)
|
||||||
|
{
|
||||||
|
// jww (2009-03-05): NYI
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_t account_t::self_total(const optional<expr_t&>& expr) const
|
||||||
|
{
|
||||||
|
if (xdata_ && xdata_->has_flags(ACCOUNT_EXT_VISITED)) {
|
||||||
|
if (! xdata_) xdata_ = xdata_t();
|
||||||
|
|
||||||
|
posts_deque::const_iterator i =
|
||||||
|
posts.begin() + xdata_->self_details.last_size;
|
||||||
|
|
||||||
|
for (; i != posts.end(); i++) {
|
||||||
|
if ((*i)->xdata().has_flags(POST_EXT_VISITED) &&
|
||||||
|
! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) {
|
||||||
|
(*i)->add_to_value(xdata_->self_details.total, expr);
|
||||||
|
(*i)->xdata().add_flags(POST_EXT_CONSIDERED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xdata_->self_details.last_size = posts.size();
|
||||||
|
|
||||||
|
return xdata_->self_details.total;
|
||||||
|
} else {
|
||||||
|
return NULL_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value_t account_t::family_total(const optional<expr_t&>& expr) const
|
||||||
|
{
|
||||||
|
if (! (xdata_ && xdata_->family_details.calculated)) {
|
||||||
|
const_cast<account_t&>(*this).xdata().family_details.calculated = true;
|
||||||
|
|
||||||
|
value_t temp;
|
||||||
|
foreach (const accounts_map::value_type& pair, accounts) {
|
||||||
|
temp = pair.second->family_total(expr);
|
||||||
|
if (! temp.is_null())
|
||||||
|
add_or_set_value(xdata_->family_details.total, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = self_total(expr);
|
||||||
|
if (! temp.is_null())
|
||||||
|
add_or_set_value(xdata_->family_details.total, temp);
|
||||||
|
}
|
||||||
|
return xdata_->family_details.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
const account_t::xdata_t::details_t&
|
||||||
|
account_t::self_details(bool gather_all) const
|
||||||
|
{
|
||||||
|
if (! (xdata_ && xdata_->self_details.gathered)) {
|
||||||
|
const_cast<account_t&>(*this).xdata().self_details.gathered = true;
|
||||||
|
|
||||||
|
foreach (const post_t * post, posts)
|
||||||
|
xdata_->self_details.update(const_cast<post_t&>(*post), gather_all);
|
||||||
|
}
|
||||||
|
return xdata_->self_details;
|
||||||
|
}
|
||||||
|
|
||||||
|
const account_t::xdata_t::details_t&
|
||||||
|
account_t::family_details(bool gather_all) const
|
||||||
|
{
|
||||||
|
if (! (xdata_ && xdata_->family_details.gathered)) {
|
||||||
|
const_cast<account_t&>(*this).xdata().family_details.gathered = true;
|
||||||
|
|
||||||
|
foreach (const accounts_map::value_type& pair, accounts)
|
||||||
|
xdata_->family_details += pair.second->family_details(gather_all);
|
||||||
|
|
||||||
|
xdata_->self_details += self_details(gather_all);
|
||||||
|
}
|
||||||
|
return xdata_->family_details;
|
||||||
|
}
|
||||||
|
|
||||||
|
void account_t::xdata_t::details_t::update(post_t& post,
|
||||||
|
bool gather_all)
|
||||||
|
{
|
||||||
|
if (last_xact != post.xact) {
|
||||||
|
xacts_count++;
|
||||||
|
last_xact = post.xact;
|
||||||
|
}
|
||||||
|
if (last_post == &post)
|
||||||
|
return;
|
||||||
|
|
||||||
|
last_post = &post;
|
||||||
|
|
||||||
|
posts_count++;
|
||||||
|
if (post.has_flags(POST_VIRTUAL))
|
||||||
|
posts_virtuals_count++;
|
||||||
|
|
||||||
|
if (gather_all)
|
||||||
|
filenames.insert(post.pathname);
|
||||||
|
|
||||||
|
date_t date = post.date();
|
||||||
|
|
||||||
|
if (date.year() == CURRENT_DATE().year() &&
|
||||||
|
date.month() == CURRENT_DATE().month())
|
||||||
|
posts_this_month_count++;
|
||||||
|
|
||||||
|
if ((CURRENT_DATE() - date).days() <= 30)
|
||||||
|
posts_last_30_count++;
|
||||||
|
if ((CURRENT_DATE() - date).days() <= 7)
|
||||||
|
posts_last_7_count++;
|
||||||
|
|
||||||
|
if (! is_valid(earliest_post) || post.date() < earliest_post)
|
||||||
|
earliest_post = post.date();
|
||||||
|
if (! is_valid(latest_post) || post.date() > latest_post)
|
||||||
|
latest_post = post.date();
|
||||||
|
|
||||||
|
if (post.state() == item_t::CLEARED) {
|
||||||
|
posts_cleared_count++;
|
||||||
|
|
||||||
|
if (! is_valid(earliest_cleared_post) ||
|
||||||
|
post.date() < earliest_cleared_post)
|
||||||
|
earliest_cleared_post = post.date();
|
||||||
|
if (! is_valid(latest_cleared_post) ||
|
||||||
|
post.date() > latest_cleared_post)
|
||||||
|
latest_cleared_post = post.date();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gather_all) {
|
||||||
|
accounts_referenced.insert(post.account->fullname());
|
||||||
|
payees_referenced.insert(post.xact->payee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,12 @@
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
class account_t;
|
|
||||||
class session_t;
|
class session_t;
|
||||||
|
class account_t;
|
||||||
|
class xact_t;
|
||||||
|
class post_t;
|
||||||
|
|
||||||
|
typedef std::deque<post_t *> posts_deque;
|
||||||
typedef std::map<const string, account_t *> accounts_map;
|
typedef std::map<const string, account_t *> accounts_map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -68,14 +71,15 @@ class account_t : public scope_t
|
||||||
optional<string> note;
|
optional<string> note;
|
||||||
unsigned short depth;
|
unsigned short depth;
|
||||||
accounts_map accounts;
|
accounts_map accounts;
|
||||||
|
posts_deque posts;
|
||||||
bool known;
|
bool known;
|
||||||
|
|
||||||
mutable void * data;
|
mutable void * data;
|
||||||
mutable string _fullname;
|
mutable string _fullname;
|
||||||
|
|
||||||
account_t(account_t * _parent = NULL,
|
account_t(account_t * _parent = NULL,
|
||||||
const string& _name = "",
|
const string& _name = "",
|
||||||
const optional<string>& _note = none)
|
const optional<string>& _note = none)
|
||||||
: scope_t(), parent(_parent), name(_name), note(_note),
|
: scope_t(), parent(_parent), name(_name), note(_note),
|
||||||
depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)),
|
depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)),
|
||||||
known(false), data(NULL) {
|
known(false), data(NULL) {
|
||||||
|
|
@ -112,6 +116,10 @@ class account_t : public scope_t
|
||||||
account_t * find_account(const string& name, bool auto_create = true);
|
account_t * find_account(const string& name, bool auto_create = true);
|
||||||
account_t * find_account_re(const string& regexp);
|
account_t * find_account_re(const string& regexp);
|
||||||
|
|
||||||
|
void add_post(post_t * post) {
|
||||||
|
posts.push_back(post);
|
||||||
|
}
|
||||||
|
|
||||||
virtual expr_t::ptr_op_t lookup(const string& name);
|
virtual expr_t::ptr_op_t lookup(const string& name);
|
||||||
|
|
||||||
bool valid() const;
|
bool valid() const;
|
||||||
|
|
@ -130,14 +138,61 @@ class account_t : public scope_t
|
||||||
|
|
||||||
struct details_t
|
struct details_t
|
||||||
{
|
{
|
||||||
value_t total;
|
value_t total;
|
||||||
|
bool calculated;
|
||||||
|
bool gathered;
|
||||||
|
|
||||||
std::size_t posts_count;
|
// The following are only calculated if --totals is enabled
|
||||||
std::size_t posts_virtuals_count;
|
std::size_t xacts_count;
|
||||||
|
|
||||||
|
std::size_t posts_count;
|
||||||
|
std::size_t posts_virtuals_count;
|
||||||
|
std::size_t posts_cleared_count;
|
||||||
|
std::size_t posts_last_7_count;
|
||||||
|
std::size_t posts_last_30_count;
|
||||||
|
std::size_t posts_this_month_count;
|
||||||
|
|
||||||
|
date_t earliest_post;
|
||||||
|
date_t earliest_cleared_post;
|
||||||
|
date_t latest_post;
|
||||||
|
date_t latest_cleared_post;
|
||||||
|
|
||||||
|
xact_t * last_xact;
|
||||||
|
post_t * last_post;
|
||||||
|
|
||||||
|
std::size_t last_size;
|
||||||
|
|
||||||
|
// The following are only calculated if --gather is enabled
|
||||||
|
std::set<path> filenames;
|
||||||
|
std::set<string> accounts_referenced;
|
||||||
|
std::set<string> payees_referenced;
|
||||||
|
|
||||||
details_t()
|
details_t()
|
||||||
: posts_count(0),
|
: calculated(false),
|
||||||
posts_virtuals_count(0) {}
|
gathered(false),
|
||||||
|
|
||||||
|
xacts_count(0),
|
||||||
|
|
||||||
|
posts_count(0),
|
||||||
|
posts_virtuals_count(0),
|
||||||
|
posts_cleared_count(0),
|
||||||
|
posts_last_7_count(0),
|
||||||
|
posts_last_30_count(0),
|
||||||
|
posts_this_month_count(0),
|
||||||
|
|
||||||
|
earliest_post(0),
|
||||||
|
earliest_cleared_post(0),
|
||||||
|
latest_post(0),
|
||||||
|
latest_cleared_post(0),
|
||||||
|
|
||||||
|
last_xact(NULL),
|
||||||
|
last_post(NULL),
|
||||||
|
|
||||||
|
last_size(0) {}
|
||||||
|
|
||||||
|
details_t& operator+=(const details_t& other);
|
||||||
|
|
||||||
|
void update(post_t& post, bool gather_all = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
details_t self_details;
|
details_t self_details;
|
||||||
|
|
@ -185,6 +240,12 @@ class account_t : public scope_t
|
||||||
return *xdata_;
|
return *xdata_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value_t self_total(const optional<expr_t&>& expr = none) const;
|
||||||
|
value_t family_total(const optional<expr_t&>& expr = none) const;
|
||||||
|
|
||||||
|
const xdata_t::details_t& self_details(bool gather_all = true) const;
|
||||||
|
const xdata_t::details_t& family_details(bool gather_all = true) const;
|
||||||
|
|
||||||
bool has_flags(xdata_t::flags_t flags) const {
|
bool has_flags(xdata_t::flags_t flags) const {
|
||||||
return xdata_ && xdata_->has_flags(flags);
|
return xdata_ && xdata_->has_flags(flags);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,30 +207,8 @@ void calc_posts::operator()(post_t& post)
|
||||||
account_t * acct = post.reported_account();
|
account_t * acct = post.reported_account();
|
||||||
acct->xdata().add_flags(ACCOUNT_EXT_VISITED);
|
acct->xdata().add_flags(ACCOUNT_EXT_VISITED);
|
||||||
|
|
||||||
if (! account_wise) {
|
if (! account_wise)
|
||||||
add_or_set_value(xdata.total, xdata.visited_value);
|
add_or_set_value(xdata.total, xdata.visited_value);
|
||||||
} else {
|
|
||||||
account_t::xdata_t * acct_xdata = &acct->xdata();
|
|
||||||
|
|
||||||
add_or_set_value(acct_xdata->self_details.total, xdata.visited_value);
|
|
||||||
|
|
||||||
acct_xdata->self_details.posts_count++;
|
|
||||||
acct_xdata->self_details.posts_virtuals_count++;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
add_or_set_value(acct_xdata->family_details.total, xdata.visited_value);
|
|
||||||
|
|
||||||
acct_xdata->family_details.posts_count++;
|
|
||||||
if (post.has_flags(POST_VIRTUAL))
|
|
||||||
acct_xdata->family_details.posts_virtuals_count++;
|
|
||||||
|
|
||||||
acct = acct->parent;
|
|
||||||
if (acct)
|
|
||||||
acct_xdata = &acct->xdata();
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item_handler<post_t>::operator()(post);
|
item_handler<post_t>::operator()(post);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -297,17 +297,11 @@ void format_accounts::flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.session.master->has_xdata()) {
|
if (! report.HANDLED(no_total) && top_displayed > 1 &&
|
||||||
account_t::xdata_t& xdata(report.session.master->xdata());
|
report.session.master->family_total()) {
|
||||||
|
bind_scope_t bound_scope(report, *report.session.master);
|
||||||
if (! report.HANDLED(no_total) && top_displayed > 1 &&
|
separator_format.format(out, bound_scope);
|
||||||
xdata.family_details.total) {
|
total_line_format.format(out, bound_scope);
|
||||||
xdata.self_details.total = xdata.family_details.total;
|
|
||||||
|
|
||||||
bind_scope_t bound_scope(report, *report.session.master);
|
|
||||||
separator_format.format(out, bound_scope);
|
|
||||||
total_line_format.format(out, bound_scope);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out.flush();
|
out.flush();
|
||||||
|
|
|
||||||
|
|
@ -231,10 +231,11 @@ namespace {
|
||||||
|
|
||||||
DEBUG("post.account_amount", "Found account: " << account->fullname());
|
DEBUG("post.account_amount", "Found account: " << account->fullname());
|
||||||
|
|
||||||
if (account->xdata().self_details.total.is_null())
|
value_t total = account->self_total();
|
||||||
|
if (total.is_null())
|
||||||
return 0L;
|
return 0L;
|
||||||
else
|
else
|
||||||
return account->xdata().self_details.total.simplified();
|
return total.simplified();
|
||||||
}
|
}
|
||||||
|
|
||||||
value_t get_account_depth(post_t& post) {
|
value_t get_account_depth(post_t& post) {
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,14 @@ std::size_t session_t::read_journal(std::istream& in,
|
||||||
{
|
{
|
||||||
if (! master)
|
if (! master)
|
||||||
master = journal->master;
|
master = journal->master;
|
||||||
|
|
||||||
std::size_t count = journal->parse(in, *this, master, &pathname,
|
std::size_t count = journal->parse(in, *this, master, &pathname,
|
||||||
HANDLED(strict));
|
HANDLED(strict));
|
||||||
clean_accounts(); // remove calculated totals
|
|
||||||
|
// remove calculated totals and flags
|
||||||
|
clean_posts();
|
||||||
|
clean_accounts();
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1013,25 +1013,24 @@ post_t * instance_t::parse_post(char * line,
|
||||||
DEBUG("textual.parse", "line " << linenum << ": "
|
DEBUG("textual.parse", "line " << linenum << ": "
|
||||||
<< "POST assign: parsed amt = " << *post->assigned_amount);
|
<< "POST assign: parsed amt = " << *post->assigned_amount);
|
||||||
|
|
||||||
account_t::xdata_t& xdata(post->account->xdata());
|
amount_t& amt(*post->assigned_amount);
|
||||||
amount_t& amt(*post->assigned_amount);
|
value_t account_total(post->account->self_total(false));
|
||||||
|
|
||||||
DEBUG("post.assign", "line " << linenum << ": "
|
DEBUG("post.assign", "line " << linenum << ": "
|
||||||
"account balance = " << xdata.self_details.total);
|
"account balance = " << account_total);
|
||||||
DEBUG("post.assign", "line " << linenum << ": "
|
DEBUG("post.assign", "line " << linenum << ": "
|
||||||
"post amount = " << amt);
|
"post amount = " << amt);
|
||||||
|
|
||||||
amount_t diff;
|
amount_t diff;
|
||||||
|
|
||||||
switch (xdata.self_details.total.type()) {
|
switch (account_total.type()) {
|
||||||
case value_t::AMOUNT:
|
case value_t::AMOUNT:
|
||||||
diff = amt - xdata.self_details.total.as_amount();
|
diff = amt - account_total.as_amount();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case value_t::BALANCE:
|
case value_t::BALANCE:
|
||||||
if (optional<amount_t> comm_bal =
|
if (optional<amount_t> comm_bal =
|
||||||
xdata.self_details.total.as_balance()
|
account_total.as_balance().commodity_amount(amt.commodity()))
|
||||||
.commodity_amount(amt.commodity()))
|
|
||||||
diff = amt - *comm_bal;
|
diff = amt - *comm_bal;
|
||||||
else
|
else
|
||||||
diff = amt;
|
diff = amt;
|
||||||
|
|
|
||||||
16
src/xact.cc
16
src/xact.cc
|
|
@ -306,7 +306,7 @@ bool xact_base_t::finalize()
|
||||||
throw_(balance_error, _("Transaction does not balance"));
|
throw_(balance_error, _("Transaction does not balance"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the final calculated totals each to their related account
|
// Add a pointer to each posting to their related accounts
|
||||||
|
|
||||||
if (dynamic_cast<xact_t *>(this)) {
|
if (dynamic_cast<xact_t *>(this)) {
|
||||||
bool all_null = true;
|
bool all_null = true;
|
||||||
|
|
@ -314,19 +314,15 @@ bool xact_base_t::finalize()
|
||||||
foreach (post_t * post, posts) {
|
foreach (post_t * post, posts) {
|
||||||
if (! post->amount.is_null()) {
|
if (! post->amount.is_null()) {
|
||||||
all_null = false;
|
all_null = false;
|
||||||
|
|
||||||
post->amount.in_place_reduce();
|
post->amount.in_place_reduce();
|
||||||
|
|
||||||
add_or_set_value(post->account->xdata().self_details.total,
|
|
||||||
post->amount);
|
|
||||||
|
|
||||||
DEBUG("xact.finalize.totals",
|
|
||||||
"Total for " << post->account->fullname() << " + "
|
|
||||||
<< post->amount << ": "
|
|
||||||
<< post->account->xdata().self_details.total);
|
|
||||||
} else {
|
} else {
|
||||||
some_null = true;
|
some_null = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post->account->add_post(post);
|
||||||
|
|
||||||
|
post->xdata().add_flags(POST_EXT_VISITED);
|
||||||
|
post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);
|
||||||
}
|
}
|
||||||
if (all_null)
|
if (all_null)
|
||||||
return false; // ignore this xact completely
|
return false; // ignore this xact completely
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue