summaryrefslogtreecommitdiff
path: root/src/filters.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2010-06-02 01:55:55 -0400
committerJohn Wiegley <johnw@newartisans.com>2010-06-02 01:55:55 -0400
commitcda19829bd1100d6563b48ddb121f2afc1c55d95 (patch)
tree5b6ab0d1eb7ec1b0b3eccf817678d0aa9a3feb47 /src/filters.cc
parent017492ef5e80003073c5d053252d4a68a44260ae (diff)
parentfb7cafa8965c89bbd66b09f827bd5989a87c983b (diff)
downloadfork-ledger-cda19829bd1100d6563b48ddb121f2afc1c55d95.tar.gz
fork-ledger-cda19829bd1100d6563b48ddb121f2afc1c55d95.tar.bz2
fork-ledger-cda19829bd1100d6563b48ddb121f2afc1c55d95.zip
Merge branch 'next'
Diffstat (limited to 'src/filters.cc')
-rw-r--r--src/filters.cc272
1 files changed, 221 insertions, 51 deletions
diff --git a/src/filters.cc b/src/filters.cc
index 0c45d356..ad4b88a0 100644
--- a/src/filters.cc
+++ b/src/filters.cc
@@ -39,8 +39,51 @@
namespace ledger {
+void post_splitter::print_title(const value_t& val)
+{
+ if (! report.HANDLED(no_titles)) {
+ std::ostringstream buf;
+ val.print(buf);
+ post_chain->title(buf.str());
+ }
+}
+
+void post_splitter::flush()
+{
+ foreach (value_to_posts_map::value_type pair, posts_map) {
+ preflush_func(pair.first);
+
+ foreach (post_t * post, pair.second)
+ (*post_chain)(*post);
+
+ post_chain->flush();
+ post_chain->clear();
+
+ if (postflush_func)
+ (*postflush_func)(pair.first);
+ }
+}
+
+void post_splitter::operator()(post_t& post)
+{
+ bind_scope_t bound_scope(report, post);
+ value_t result(group_by_expr.calc(bound_scope));
+
+ if (! result.is_null()) {
+ value_to_posts_map::iterator i = posts_map.find(result);
+ if (i != posts_map.end()) {
+ (*i).second.push_back(&post);
+ } else {
+ std::pair<value_to_posts_map::iterator, bool> inserted
+ = posts_map.insert(value_to_posts_map::value_type(result, posts_list()));
+ assert(inserted.second);
+ (*inserted.first).second.push_back(&post);
+ }
+ }
+}
+
pass_down_posts::pass_down_posts(post_handler_ptr handler,
- posts_iterator& iter)
+ posts_iterator& iter)
: item_handler<post_t>(handler)
{
TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator");
@@ -312,7 +355,7 @@ namespace {
if (functor)
(*functor)(post);
- DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount);
+ DEBUG("filters.changed_value.rounding", "post.amount = " << post.amount);
(*handler)(post);
@@ -355,7 +398,7 @@ void collapse_posts::report_subtotal()
xact.payee = last_xact->payee;
xact._date = (is_valid(earliest_date) ?
earliest_date : last_xact->_date);
- DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date);
+ DEBUG("filters.collapse", "Pseudo-xact date = " << *xact._date);
handle_value(subtotal, &totals_account, &xact, temps, handler);
}
@@ -420,10 +463,12 @@ void related_posts::flush()
changed_value_posts::changed_value_posts(post_handler_ptr handler,
report_t& _report,
bool _for_accounts_report,
- bool _show_unrealized)
+ bool _show_unrealized,
+ bool _show_rounding)
: item_handler<post_t>(handler), report(_report),
for_accounts_report(_for_accounts_report),
- show_unrealized(_show_unrealized), last_post(NULL),
+ show_unrealized(_show_unrealized),
+ show_rounding(_show_rounding), last_post(NULL),
revalued_account(temps.create_account(_("<Revalued>"))),
rounding_account(temps.create_account(_("<Rounding>")))
{
@@ -458,6 +503,8 @@ changed_value_posts::changed_value_posts(post_handler_ptr handler,
void changed_value_posts::flush()
{
if (last_post && last_post->date() <= report.terminus.date()) {
+ if (! for_accounts_report)
+ output_intermediate_prices(*last_post, report.terminus.date());
output_revaluation(*last_post, report.terminus.date());
last_post = NULL;
}
@@ -469,7 +516,6 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
if (is_valid(date))
post.xdata().date = date;
- value_t repriced_total;
try {
bind_scope_t bound_scope(report, post);
repriced_total = total_expr.calc(bound_scope);
@@ -480,14 +526,14 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
}
post.xdata().date = date_t();
- DEBUG("filter.changed_value",
- "output_revaluation(last_balance) = " << last_total);
- DEBUG("filter.changed_value",
+ DEBUG("filters.changed_value",
+ "output_revaluation(last_total) = " << last_total);
+ DEBUG("filters.changed_value",
"output_revaluation(repriced_total) = " << repriced_total);
if (! last_total.is_null()) {
if (value_t diff = repriced_total - last_total) {
- DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = "
+ DEBUG("filters.changed_value", "output_revaluation(strip(diff)) = "
<< diff.strip_annotations(report.what_to_keep()));
xact_t& xact = temps.create_xact();
@@ -505,9 +551,10 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
/* total= */ repriced_total,
/* direct_amount= */ false,
/* mark_visited= */ false,
- /* functor= */ (optional<post_functor_t>
+ /* functor= */ (show_rounding ?
+ optional<post_functor_t>
(bind(&changed_value_posts::output_rounding,
- this, _1))));
+ this, _1)) : none));
}
else if (show_unrealized) {
handle_value
@@ -527,29 +574,146 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
}
}
+void changed_value_posts::output_intermediate_prices(post_t& post,
+ const date_t& current)
+{
+ // To fix BZ#199, examine the balance of last_post and determine whether the
+ // price of that amount changed after its date and before the new post's
+ // date. If so, generate an output_revaluation for that price change.
+ // Mostly this is only going to occur if the user has a series of pricing
+ // entries, since a posting-based revaluation would be seen here as a post.
+
+ value_t display_total(last_total);
+
+ if (display_total.type() == value_t::SEQUENCE) {
+ xact_t& xact(temps.create_xact());
+
+ xact.payee = _("Commodities revalued");
+ xact._date = is_valid(current) ? current : post.date();
+
+ post_t& temp(temps.copy_post(post, xact));
+ temp.add_flags(ITEM_GENERATED);
+
+ post_t::xdata_t& xdata(temp.xdata());
+ if (is_valid(current))
+ xdata.date = current;
+
+ DEBUG("filters.revalued", "intermediate last_total = " << last_total);
+
+ switch (last_total.type()) {
+ case value_t::BOOLEAN:
+ case value_t::INTEGER:
+ last_total.in_place_cast(value_t::AMOUNT);
+ // fall through...
+
+ case value_t::AMOUNT:
+ temp.amount = last_total.as_amount();
+ break;
+
+ case value_t::BALANCE:
+ case value_t::SEQUENCE:
+ xdata.compound_value = last_total;
+ xdata.add_flags(POST_EXT_COMPOUND);
+ break;
+
+ case value_t::DATETIME:
+ case value_t::DATE:
+ default:
+ assert(false);
+ break;
+ }
+
+ bind_scope_t inner_scope(report, temp);
+ display_total = display_total_expr.calc(inner_scope);
+
+ DEBUG("filters.revalued", "intermediate display_total = " << display_total);
+ }
+
+ switch (display_total.type()) {
+ case value_t::VOID:
+ case value_t::INTEGER:
+ case value_t::SEQUENCE:
+ break;
+
+ case value_t::AMOUNT:
+ display_total.in_place_cast(value_t::BALANCE);
+ // fall through...
+
+ case value_t::BALANCE: {
+ commodity_t::history_map all_prices;
+
+ foreach (const balance_t::amounts_map::value_type& amt_comm,
+ display_total.as_balance().amounts) {
+ if (optional<commodity_t::varied_history_t&> hist =
+ amt_comm.first->varied_history()) {
+ foreach
+ (const commodity_t::history_by_commodity_map::value_type& comm_hist,
+ hist->histories) {
+ foreach (const commodity_t::history_map::value_type& price,
+ comm_hist.second.prices) {
+ if (price.first.date() > post.date() &&
+ price.first.date() < current) {
+ DEBUG("filters.revalued", post.date() << " < "
+ << price.first.date() << " < " << current);
+ DEBUG("filters.revalued", "inserting "
+ << price.second << " at " << price.first.date());
+ all_prices.insert(price);
+ }
+ }
+ }
+ }
+ }
+
+ // Choose the last price from each day as the price to use
+ typedef std::map<const date_t, bool> date_map;
+ date_map pricing_dates;
+
+ BOOST_REVERSE_FOREACH
+ (const commodity_t::history_map::value_type& price, all_prices) {
+ // This insert will fail if a later price has already been inserted
+ // for that date.
+ DEBUG("filters.revalued",
+ "re-inserting " << price.second << " at " << price.first.date());
+ pricing_dates.insert(date_map::value_type(price.first.date(), true));
+ }
+
+ // Go through the time-sorted prices list, outputting a revaluation for
+ // each price difference.
+ foreach (const date_map::value_type& price, pricing_dates) {
+ output_revaluation(post, price.first);
+ last_total = repriced_total;
+ }
+ break;
+ }
+ default:
+ assert(false);
+ break;
+ }
+}
+
void changed_value_posts::output_rounding(post_t& post)
{
bind_scope_t bound_scope(report, post);
value_t new_display_total(display_total_expr.calc(bound_scope));
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.new_display_total = " << new_display_total);
if (! last_display_total.is_null()) {
if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) {
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.repriced_amount = " << repriced_amount);
value_t precise_display_total(new_display_total.truncated() -
repriced_amount.truncated());
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.precise_display_total = " << precise_display_total);
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.last_display_total = " << last_display_total);
if (value_t diff = precise_display_total - last_display_total) {
- DEBUG("filter.changed_value.rounding",
+ DEBUG("filters.changed_value.rounding",
"rounding.diff = " << diff);
xact_t& xact = temps.create_xact();
@@ -566,13 +730,16 @@ void changed_value_posts::output_rounding(post_t& post)
void changed_value_posts::operator()(post_t& post)
{
- if (last_post)
+ if (last_post) {
+ if (! for_accounts_report)
+ output_intermediate_prices(*last_post, post.date());
output_revaluation(*last_post, post.date());
+ }
if (changed_values_only)
post.xdata().add_flags(POST_EXT_DISPLAYED);
- if (! for_accounts_report)
+ if (! for_accounts_report && show_rounding)
output_rounding(post);
item_handler<post_t>::operator()(post);
@@ -793,41 +960,43 @@ void transfer_details::operator()(post_t& post)
bind_scope_t bound_scope(scope, temp);
value_t substitute(expr.calc(bound_scope));
- switch (which_element) {
- case SET_DATE:
- temp.xdata().date = substitute.to_date();
- break;
+ if (! substitute.is_null()) {
+ switch (which_element) {
+ case SET_DATE:
+ temp.xdata().date = substitute.to_date();
+ break;
- case SET_ACCOUNT: {
- string account_name = substitute.to_string();
- if (! account_name.empty() &&
- account_name[account_name.length() - 1] != ':') {
- account_t * prev_account = temp.account;
- temp.account->remove_post(&temp);
-
- account_name += ':';
- account_name += prev_account->fullname();
-
- std::list<string> account_names;
- split_string(account_name, ':', account_names);
- temp.account = create_temp_account_from_path(account_names, temps,
- xact.journal->master);
- temp.account->add_post(&temp);
-
- temp.account->add_flags(prev_account->flags());
- if (prev_account->has_xdata())
- temp.account->xdata().add_flags(prev_account->xdata().flags());
+ case SET_ACCOUNT: {
+ string account_name = substitute.to_string();
+ if (! account_name.empty() &&
+ account_name[account_name.length() - 1] != ':') {
+ account_t * prev_account = temp.account;
+ temp.account->remove_post(&temp);
+
+ account_name += ':';
+ account_name += prev_account->fullname();
+
+ std::list<string> account_names;
+ split_string(account_name, ':', account_names);
+ temp.account = create_temp_account_from_path(account_names, temps,
+ xact.journal->master);
+ temp.account->add_post(&temp);
+
+ temp.account->add_flags(prev_account->flags());
+ if (prev_account->has_xdata())
+ temp.account->xdata().add_flags(prev_account->xdata().flags());
+ }
+ break;
}
- break;
- }
- case SET_PAYEE:
- xact.payee = substitute.to_string();
- break;
+ case SET_PAYEE:
+ xact.payee = substitute.to_string();
+ break;
- default:
- assert(false);
- break;
+ default:
+ assert(false);
+ break;
+ }
}
item_handler<post_t>::operator()(temp);
@@ -869,7 +1038,8 @@ void budget_posts::report_budget_items(const date_t& date)
optional<date_t> begin = pair.first.start;
if (! begin) {
if (! pair.first.find_period(date))
- throw_(std::runtime_error, _("Something odd has happened"));
+ throw_(std::runtime_error,
+ _("Something odd has happened at date %1") << date);
begin = pair.first.start;
}
assert(begin);