Rewrote the equity command, which is working again

The old implementation used an account formatter, and was very
specialized.  The new is done as a transaction filter, and works along
with everything else, eliminating bugs special to the equity report.
This commit is contained in:
John Wiegley 2009-02-20 02:53:54 -04:00
parent f2f52066d2
commit c1b25fcf86
8 changed files with 110 additions and 140 deletions

View file

@ -113,7 +113,9 @@ xact_handler_ptr chain_xact_handlers(report_t& report,
//
// dow_xacts is like period_xacts, except that it reports all the xacts
// that fall on each subsequent day of the week.
if (report.HANDLED(subtotal))
if (report.HANDLED(equity))
handler.reset(new xacts_as_equity(handler, expr));
else if (report.HANDLED(subtotal))
handler.reset(new subtotal_xacts(handler, expr));
}

View file

@ -516,7 +516,7 @@ void interval_xacts::operator()(xact_t& xact)
xact_temps.push_back(xact_t(&empty_account));
xact_t& null_xact = xact_temps.back();
null_xact.add_flags(ITEM_TEMP);
null_xact.add_flags(ITEM_TEMP | XACT_CALCULATED);
null_xact.amount = 0L;
null_entry.add_xact(&null_xact);
@ -536,6 +536,49 @@ void interval_xacts::operator()(xact_t& xact)
last_xact = &xact;
}
void xacts_as_equity::report_subtotal()
{
date_t finish;
foreach (xact_t * xact, component_xacts) {
date_t date = xact->reported_date();
if (! is_valid(finish) || date > finish)
finish = date;
}
component_xacts.clear();
entry_temps.push_back(entry_t());
entry_t& entry = entry_temps.back();
entry.payee = "Opening Balances";
entry._date = finish;
value_t total = 0L;
foreach (values_map::value_type& pair, values) {
handle_value(pair.second.value, pair.second.account, &entry, 0,
xact_temps, *handler);
total += pair.second.value;
}
values.clear();
if (total.is_balance()) {
foreach (balance_t::amounts_map::value_type pair,
total.as_balance().amounts) {
xact_temps.push_back(xact_t(balance_account));
xact_t& balance_xact = xact_temps.back();
balance_xact.add_flags(ITEM_TEMP);
balance_xact.amount = - pair.second;
entry.add_xact(&balance_xact);
(*handler)(balance_xact);
}
} else {
xact_temps.push_back(xact_t(balance_account));
xact_t& balance_xact = xact_temps.back();
balance_xact.add_flags(ITEM_TEMP);
balance_xact.amount = - total.to_amount();
entry.add_xact(&balance_xact);
(*handler)(balance_xact);
}
}
by_payee_xacts::~by_payee_xacts()
{
TRACE_DTOR(by_payee_xacts);

View file

@ -611,6 +611,34 @@ public:
virtual void operator()(xact_t& xact);
};
class xacts_as_equity : public subtotal_xacts
{
interval_t interval;
xact_t * last_xact;
account_t equity_account;
account_t * balance_account;
xacts_as_equity();
public:
xacts_as_equity(xact_handler_ptr _handler, expr_t& amount_expr)
: subtotal_xacts(_handler, amount_expr),
equity_account(NULL, "Equity") {
TRACE_CTOR(xacts_as_equity, "xact_handler_ptr, expr_t&");
balance_account = equity_account.find_account("Opening Balances");
}
virtual ~xacts_as_equity() throw() {
TRACE_DTOR(xacts_as_equity);
}
void report_subtotal();
virtual void flush() {
report_subtotal();
subtotal_xacts::flush();
}
};
/**
* @brief Brief
*

View file

@ -339,7 +339,7 @@ void global_scope_t::normalize_session_options()
function_t global_scope_t::look_for_precommand(scope_t& scope,
const string& verb)
{
if (expr_t::ptr_op_t def = scope.lookup(string("precmd_") + verb))
if (expr_t::ptr_op_t def = scope.lookup(string(PRECMD_PREFIX) + verb))
return def->as_function();
else
return function_t();
@ -348,7 +348,7 @@ function_t global_scope_t::look_for_precommand(scope_t& scope,
function_t global_scope_t::look_for_command(scope_t& scope,
const string& verb)
{
if (expr_t::ptr_op_t def = scope.lookup(string("cmd_") + verb))
if (expr_t::ptr_op_t def = scope.lookup(string(CMD_PREFIX) + verb))
return def->as_function();
else
return function_t();
@ -361,7 +361,7 @@ void global_scope_t::normalize_report_options(const string& verb)
report_t& rep(report());
// jww (2009-02-09): These global are a hack, but hard to avoid
// jww (2009-02-09): These global are a hack, but hard to avoid.
item_t::use_effective_date = rep.HANDLED(effective);
if (rep.HANDLED(date_format_)) {
@ -369,14 +369,15 @@ void global_scope_t::normalize_report_options(const string& verb)
output_date_format = rep.HANDLER(date_format_).str();
}
// jww (2008-08-14): This code really needs to be rationalized away
// for 3.0.
// jww (2008-08-14): This code really needs to be rationalized away for 3.0.
// I might be able to do it with command objects, like register_t, which
// each know how to adjust the report based on its current option settings.
if (verb == "print" || verb == "entry" || verb == "dump") {
rep.HANDLER(related).on_only();
rep.HANDLER(related_all).on_only();
}
else if (verb == "equity") {
rep.HANDLER(subtotal).on_only();
rep.HANDLER(equity).on_only();
}
else if (rep.HANDLED(related)) {
if (verb[0] == 'r') {

View file

@ -304,82 +304,4 @@ void format_accounts::operator()(account_t& account)
posted_accounts.push_back(&account);
}
format_equity::format_equity(report_t& _report, const string& _format)
: format_accounts(_report)
{
const char * f = _format.c_str();
if (const char * p = std::strstr(f, "%/")) {
first_line_format.parse(string(f, 0, p - f));
next_lines_format.parse(string(p + 2));
} else {
first_line_format.parse(_format);
next_lines_format.parse(_format);
}
entry_t header_entry;
header_entry.payee = "Opening Balances";
header_entry._date = CURRENT_DATE();
bind_scope_t bound_scope(report, header_entry);
first_line_format.format(report.output_stream, bound_scope);
}
void format_equity::flush()
{
account_t summary(NULL, "Equity:Opening Balances");
account_t::xdata_t& xdata(summary.xdata());
std::ostream& out(report.output_stream);
xdata.value = total.negate();
if (total.type() >= value_t::BALANCE) {
const balance_t * bal;
if (total.is_type(value_t::BALANCE))
bal = &(total.as_balance());
else
assert(false);
foreach (balance_t::amounts_map::value_type pair, bal->amounts) {
xdata.value = pair.second;
xdata.value.negate();
bind_scope_t bound_scope(report, summary);
next_lines_format.format(out, bound_scope);
}
} else {
bind_scope_t bound_scope(report, summary);
next_lines_format.format(out, bound_scope);
}
out.flush();
}
void format_equity::post_account(account_t& account)
{
std::ostream& out(report.output_stream);
if (! account.has_flags(ACCOUNT_EXT_MATCHING))
return;
value_t val = account.xdata().value;
if (val.type() >= value_t::BALANCE) {
const balance_t * bal;
if (val.is_type(value_t::BALANCE))
bal = &(val.as_balance());
else
assert(false);
foreach (balance_t::amounts_map::value_type pair, bal->amounts) {
account.xdata().value = pair.second;
bind_scope_t bound_scope(report, account);
next_lines_format.format(out, bound_scope);
}
account.xdata().value = val;
} else {
bind_scope_t bound_scope(report, account);
next_lines_format.format(out, bound_scope);
}
total += val;
}
} // namespace ledger

View file

@ -192,29 +192,6 @@ public:
virtual void operator()(account_t& account);
};
/**
* @brief Brief
*
* Long.
*/
class format_equity : public format_accounts
{
format_t first_line_format;
format_t next_lines_format;
mutable value_t total;
public:
format_equity(report_t& _report,
const string& _format);
virtual ~format_equity() {
TRACE_DTOR(format_equity);
}
virtual void post_account(account_t& account);
virtual void flush();
};
} // namespace ledger
#endif // _OUTPUT_H

View file

@ -356,7 +356,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
OPT(effective);
else OPT(empty);
else OPT_(end_);
else OPT(equity_format_);
else OPT(equity);
break;
case 'f':
OPT(flat);
@ -475,10 +475,10 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
break;
case 'c':
if (WANT_CMD()) { p += CMD_PREFIX_LEN;
switch (*p) {
if (WANT_CMD()) { const char * q = p + CMD_PREFIX_LEN;
switch (*q) {
case 'b':
if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance"))
if (*(q + 1) == '\0' || is_eq(q, "bal") || is_eq(q, "balance"))
return expr_t::op_t::wrap_functor
(reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
(new format_accounts(*this, report_format(HANDLER(balance_format_)),
@ -486,7 +486,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
break;
case 'c':
if (is_eq(p, "csv"))
if (is_eq(q, "csv"))
return WRAP_FUNCTOR
(reporter<>
(new format_xacts(*this, report_format(HANDLER(csv_format_))),
@ -494,30 +494,30 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
break;
case 'e':
if (is_eq(p, "equity"))
return expr_t::op_t::wrap_functor
(reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
(new format_equity(*this, report_format(HANDLER(print_format_))),
if (is_eq(q, "equity"))
return WRAP_FUNCTOR
(reporter<>
(new format_xacts(*this, report_format(HANDLER(print_format_))),
*this));
else if (is_eq(p, "entry"))
else if (is_eq(q, "entry"))
return WRAP_FUNCTOR(entry_command);
else if (is_eq(p, "emacs"))
else if (is_eq(q, "emacs"))
return WRAP_FUNCTOR
(reporter<>(new format_emacs_xacts(output_stream), *this));
break;
case 'p':
if (*(p + 1) == '\0' || is_eq(p, "print"))
if (*(q + 1) == '\0' || is_eq(q, "print"))
return WRAP_FUNCTOR
(reporter<>
(new format_xacts(*this, report_format(HANDLER(print_format_))),
*this));
else if (is_eq(p, "prices"))
else if (is_eq(q, "prices"))
return expr_t::op_t::wrap_functor
(reporter<xact_t, xact_handler_ptr, &report_t::commodities_report>
(new format_xacts(*this, report_format(HANDLER(prices_format_))),
*this));
else if (is_eq(p, "pricesdb"))
else if (is_eq(q, "pricesdb"))
return expr_t::op_t::wrap_functor
(reporter<xact_t, xact_handler_ptr, &report_t::commodities_report>
(new format_xacts(*this, report_format(HANDLER(pricesdb_format_))),
@ -525,17 +525,17 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
break;
case 'r':
if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register"))
if (*(q + 1) == '\0' || is_eq(q, "reg") || is_eq(q, "register"))
return WRAP_FUNCTOR
(reporter<>
(new format_xacts(*this, report_format(HANDLER(register_format_))),
*this));
else if (is_eq(p, "reload"))
else if (is_eq(q, "reload"))
return MAKE_FUNCTOR(report_t::reload_command);
break;
case 's':
if (is_eq(p, "stats") || is_eq(p, "stat"))
if (is_eq(q, "stats") || is_eq(q, "stat"))
return WRAP_FUNCTOR(reporter<>(new gather_statistics(*this), *this));
break;
}
@ -565,8 +565,8 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
break;
case 'o':
if (WANT_OPT()) { p += OPT_PREFIX_LEN;
if (option_t<report_t> * handler = lookup_option(p))
if (WANT_OPT()) { const char * q = p + OPT_PREFIX_LEN;
if (option_t<report_t> * handler = lookup_option(q))
return MAKE_OPT_HANDLER(report_t, handler);
}
else if (is_eq(p, "options")) {
@ -575,28 +575,28 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
break;
case 'p':
if (WANT_PRECMD()) { p += PRECMD_PREFIX_LEN;
switch (*p) {
if (WANT_PRECMD()) { const char * q = p + PRECMD_PREFIX_LEN;
switch (*q) {
case 'a':
if (is_eq(p, "args"))
if (is_eq(q, "args"))
return WRAP_FUNCTOR(args_command);
break;
case 'e':
if (is_eq(p, "eval"))
if (is_eq(q, "eval"))
return WRAP_FUNCTOR(eval_command);
break;
case 'f':
if (is_eq(p, "format"))
if (is_eq(q, "format"))
return WRAP_FUNCTOR(format_command);
break;
case 'p':
if (is_eq(p, "parse"))
if (is_eq(q, "parse"))
return WRAP_FUNCTOR(parse_command);
else if (is_eq(p, "period"))
else if (is_eq(q, "period"))
return WRAP_FUNCTOR(period_command);
break;
case 't':
if (is_eq(p, "template"))
if (is_eq(q, "template"))
return WRAP_FUNCTOR(template_command);
break;
}

View file

@ -354,10 +354,7 @@ public:
#endif
});
OPTION__(report_t, equity_format_, CTOR(report_t, equity_format_) {
on("\n%D %Y%C%P\n%/ %-34W %12t\n");
});
OPTION(report_t, equity);
OPTION(report_t, flat);
OPTION(report_t, forecast_);
OPTION(report_t, format_); // -F