/* * Copyright (c) 2003-2023, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @addtogroup report */ /** * @file filters.h * @author John Wiegley * * @ingroup report */ #pragma once #include "chain.h" #include "xact.h" #include "post.h" #include "account.h" #include "temps.h" namespace ledger { using namespace boost::placeholders; ////////////////////////////////////////////////////////////////////// // // Posting collector // class post_splitter : public item_handler { public: typedef std::map value_to_posts_map; typedef function custom_flusher_t; protected: value_to_posts_map posts_map; post_handler_ptr post_chain; report_t& report; expr_t& group_by_expr; custom_flusher_t preflush_func; optional postflush_func; public: post_splitter(post_handler_ptr _post_chain, report_t& _report, expr_t& _group_by_expr) : post_chain(_post_chain), report(_report), group_by_expr(_group_by_expr) { preflush_func = bind(&post_splitter::print_title, this, _1); TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t"); } virtual ~post_splitter() { TRACE_DTOR(post_splitter); } void set_preflush_func(custom_flusher_t functor) { preflush_func = functor; } void set_postflush_func(custom_flusher_t functor) { postflush_func = functor; } virtual void print_title(const value_t& val); virtual void flush(); virtual void operator()(post_t& post); virtual void clear() { posts_map.clear(); post_chain->clear(); item_handler::clear(); } }; ////////////////////////////////////////////////////////////////////// // // Posting filters // class ignore_posts : public item_handler { public: virtual void operator()(post_t&) {} }; class collect_posts : public item_handler { public: std::vector posts; collect_posts() : item_handler() { TRACE_CTOR(collect_posts, ""); } virtual ~collect_posts() { TRACE_DTOR(collect_posts); } std::size_t length() const { return posts.size(); } std::vector::iterator begin() { return posts.begin(); } std::vector::iterator end() { return posts.end(); } virtual void flush() {} virtual void operator()(post_t& post) { posts.push_back(&post); } virtual void clear() { posts.clear(); item_handler::clear(); } }; template class pass_down_posts : public item_handler { pass_down_posts(); public: pass_down_posts(post_handler_ptr handler, Iterator& iter) : item_handler(handler) { while (post_t * post = *iter) { try { item_handler::operator()(*post); } catch (const std::exception&) { add_error_context(item_context(*post, _("While handling posting"))); throw; } iter.increment(); } item_handler::flush(); TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); } virtual ~pass_down_posts() { TRACE_DTOR(pass_down_posts); } }; class push_to_posts_list : public item_handler { push_to_posts_list(); public: posts_list& posts; push_to_posts_list(posts_list& _posts) : posts(_posts) { TRACE_CTOR(push_to_posts_list, "posts_list&"); } virtual ~push_to_posts_list() { TRACE_DTOR(push_to_posts_list); } virtual void operator()(post_t& post) { posts.push_back(&post); } }; class truncate_xacts : public item_handler { int head_count; int tail_count; bool completed; posts_list posts; std::size_t xacts_seen; xact_t * last_xact; truncate_xacts(); public: truncate_xacts(post_handler_ptr handler, int _head_count, int _tail_count) : item_handler(handler), head_count(_head_count), tail_count(_tail_count), completed(false), xacts_seen(0), last_xact(NULL) { TRACE_CTOR(truncate_xacts, "post_handler_ptr, int, int"); } virtual ~truncate_xacts() { TRACE_DTOR(truncate_xacts); } virtual void flush(); virtual void operator()(post_t& post); virtual void clear() { completed = false; posts.clear(); xacts_seen = 0; last_xact = NULL; item_handler::clear(); } }; class sort_posts : public item_handler { typedef std::deque posts_deque; posts_deque posts; expr_t sort_order; report_t& report; sort_posts(); public: sort_posts(post_handler_ptr handler, const expr_t& _sort_order, report_t& _report) : item_handler(handler), sort_order(_sort_order), report(_report) { TRACE_CTOR(sort_posts, "post_handler_ptr, const value_expr&, report_t&"); } sort_posts(post_handler_ptr handler, const string& _sort_order, report_t& _report) : item_handler(handler), sort_order(_sort_order), report(_report) { TRACE_CTOR(sort_posts, "post_handler_ptr, const string&, report_t&"); } virtual ~sort_posts() { TRACE_DTOR(sort_posts); } virtual void post_accumulated_posts(); virtual void flush() { post_accumulated_posts(); item_handler::flush(); } virtual void operator()(post_t& post) { posts.push_back(&post); } virtual void clear() { posts.clear(); sort_order.mark_uncompiled(); item_handler::clear(); } }; class sort_xacts : public item_handler { sort_posts sorter; xact_t * last_xact; sort_xacts(); public: sort_xacts(post_handler_ptr handler, const expr_t& _sort_order, report_t& _report) : sorter(handler, _sort_order, _report) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const value_expr&, report_t&"); } sort_xacts(post_handler_ptr handler, const string& _sort_order, report_t& _report) : sorter(handler, _sort_order, _report) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const string&, report_t&"); } virtual ~sort_xacts() { TRACE_DTOR(sort_xacts); } virtual void flush() { sorter.flush(); item_handler::flush(); } virtual void operator()(post_t& post) { if (last_xact && post.xact != last_xact) sorter.post_accumulated_posts(); sorter(post); last_xact = post.xact; } virtual void clear() { sorter.clear(); last_xact = NULL; item_handler::clear(); } }; class filter_posts : public item_handler { predicate_t pred; scope_t& context; filter_posts(); public: filter_posts(post_handler_ptr handler, const predicate_t& predicate, scope_t& _context) : item_handler(handler), pred(predicate), context(_context) { TRACE_CTOR(filter_posts, "post_handler_ptr, predicate_t, scope_t&"); } virtual ~filter_posts() { TRACE_DTOR(filter_posts); } virtual void operator()(post_t& post) { bind_scope_t bound_scope(context, post); if (pred(bound_scope)) { post.xdata().add_flags(POST_EXT_MATCHES); (*handler)(post); } } virtual void clear() { pred.mark_uncompiled(); item_handler::clear(); } }; class anonymize_posts : public item_handler { typedef std::map commodity_index_map; typedef variate_generator > int_generator_t; temporaries_t temps; commodity_index_map comms; std::size_t next_comm_id; xact_t * last_xact; mt19937 rnd_gen; uniform_int<> integer_range; int_generator_t integer_gen; anonymize_posts(); public: anonymize_posts(post_handler_ptr handler) : item_handler(handler), next_comm_id(0), last_xact(NULL), rnd_gen(static_cast(static_cast(std::time(0)))), integer_range(1, 2000000000L), integer_gen(rnd_gen, integer_range) { TRACE_CTOR(anonymize_posts, "post_handler_ptr"); } virtual ~anonymize_posts() { TRACE_DTOR(anonymize_posts); handler.reset(); } void render_commodity(amount_t& amt); virtual void operator()(post_t& post); virtual void clear() { temps.clear(); comms.clear(); last_xact = NULL; item_handler::clear(); } }; class calc_posts : public item_handler { post_t * last_post; expr_t& amount_expr; bool calc_running_total; calc_posts(); public: calc_posts(post_handler_ptr handler, expr_t& _amount_expr, bool _calc_running_total = false) : item_handler(handler), last_post(NULL), amount_expr(_amount_expr), calc_running_total(_calc_running_total) { TRACE_CTOR(calc_posts, "post_handler_ptr, expr_t&, bool"); } virtual ~calc_posts() { TRACE_DTOR(calc_posts); } virtual void operator()(post_t& post); virtual void clear() { last_post = NULL; amount_expr.mark_uncompiled(); item_handler::clear(); } }; class collapse_posts : public item_handler { typedef std::map totals_map; expr_t& amount_expr; predicate_t display_predicate; predicate_t only_predicate; value_t subtotal; std::size_t count; xact_t * last_xact; post_t * last_post; temporaries_t temps; account_t * global_totals_account; totals_map totals; bool only_collapse_if_zero; unsigned short collapse_depth; std::list component_posts; report_t& report; collapse_posts(); public: collapse_posts(post_handler_ptr handler, report_t& _report, expr_t& _amount_expr, predicate_t _display_predicate, predicate_t _only_predicate, bool _only_collapse_if_zero = false, unsigned short _collapse_depth = 0) : item_handler(handler), amount_expr(_amount_expr), display_predicate(_display_predicate), only_predicate(_only_predicate), count(0), last_xact(NULL), last_post(NULL), only_collapse_if_zero(_only_collapse_if_zero), collapse_depth(_collapse_depth), report(_report) { create_accounts(); TRACE_CTOR(collapse_posts, "post_handler_ptr, ..."); } virtual ~collapse_posts() { TRACE_DTOR(collapse_posts); handler.reset(); } void create_accounts() { global_totals_account = &temps.create_account(_("")); } value_t& find_totals(account_t* account); virtual void flush() { report_subtotal(); item_handler::flush(); } void report_subtotal(); virtual void operator()(post_t& post); virtual void clear() { amount_expr.mark_uncompiled(); display_predicate.mark_uncompiled(); only_predicate.mark_uncompiled(); subtotal = value_t(); count = 0; last_xact = NULL; last_post = NULL; temps.clear(); create_accounts(); totals.clear(); component_posts.clear(); item_handler::clear(); } }; class related_posts : public item_handler { posts_list posts; bool also_matching; related_posts(); public: related_posts(post_handler_ptr handler, const bool _also_matching = false) : item_handler(handler), also_matching(_also_matching) { TRACE_CTOR(related_posts, "post_handler_ptr, const bool"); } virtual ~related_posts() throw() { TRACE_DTOR(related_posts); } virtual void flush(); virtual void operator()(post_t& post) { post.xdata().add_flags(POST_EXT_RECEIVED); posts.push_back(&post); } virtual void clear() { posts.clear(); item_handler::clear(); } }; class display_filter_posts : public item_handler { // This filter requires that calc_posts be used at some point // later in the chain. report_t& report; expr_t& display_amount_expr; expr_t& display_total_expr; bool show_rounding; value_t last_display_total; temporaries_t temps; account_t * rounding_account; display_filter_posts(); public: account_t * revalued_account; display_filter_posts(post_handler_ptr handler, report_t& _report, bool _show_rounding); virtual ~display_filter_posts() { TRACE_DTOR(display_filter_posts); handler.reset(); } void create_accounts() { rounding_account = &temps.create_account(_("")); revalued_account = &temps.create_account(_("")); } bool output_rounding(post_t& post); virtual void operator()(post_t& post); virtual void clear() { display_amount_expr.mark_uncompiled(); display_total_expr.mark_uncompiled(); last_display_total = value_t(); temps.clear(); item_handler::clear(); create_accounts(); } }; class changed_value_posts : public item_handler { // This filter requires that calc_posts be used at some point // later in the chain. report_t& report; expr_t& total_expr; expr_t& display_total_expr; bool changed_values_only; bool historical_prices_only; bool for_accounts_report; bool show_unrealized; post_t * last_post; value_t last_total; value_t repriced_total; temporaries_t temps; account_t * revalued_account; account_t * gains_equity_account; account_t * losses_equity_account; display_filter_posts * display_filter; changed_value_posts(); public: changed_value_posts(post_handler_ptr handler, report_t& _report, bool _for_accounts_report, bool _show_unrealized, display_filter_posts * _display_filter); virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); temps.clear(); handler.reset(); } void create_accounts() { revalued_account = (display_filter ? display_filter->revalued_account : &temps.create_account(_(""))); } virtual void flush(); void output_revaluation(post_t& post, const date_t& current); void output_intermediate_prices(post_t& post, const date_t& current); virtual void operator()(post_t& post); virtual void clear() { total_expr.mark_uncompiled(); display_total_expr.mark_uncompiled(); last_post = NULL; last_total = value_t(); temps.clear(); item_handler::clear(); create_accounts(); } }; class subtotal_posts : public item_handler { subtotal_posts(); protected: class acct_value_t { acct_value_t(); public: account_t * account; value_t value; bool is_virtual; bool must_balance; acct_value_t(account_t * a, bool _is_virtual = false, bool _must_balance = false) : account(a), is_virtual(_is_virtual), must_balance(_must_balance) { TRACE_CTOR(acct_value_t, "account_t *, bool, bool"); } acct_value_t(account_t * a, value_t& v, bool _is_virtual = false, bool _must_balance = false) : account(a), value(v), is_virtual(_is_virtual), must_balance(_must_balance) { TRACE_CTOR(acct_value_t, "account_t *, value_t&, bool, bool"); } acct_value_t(const acct_value_t& av) : account(av.account), value(av.value), is_virtual(av.is_virtual), must_balance(av.must_balance) { TRACE_CTOR(acct_value_t, "copy"); } ~acct_value_t() throw() { TRACE_DTOR(acct_value_t); } }; typedef std::map values_map; typedef std::pair values_pair; protected: expr_t& amount_expr; values_map values; optional date_format; temporaries_t temps; std::deque component_posts; public: subtotal_posts(post_handler_ptr handler, expr_t& _amount_expr, const optional& _date_format = none) : item_handler(handler), amount_expr(_amount_expr), date_format(_date_format) { TRACE_CTOR(subtotal_posts, "post_handler_ptr, expr_t&, const optional&"); } virtual ~subtotal_posts() { TRACE_DTOR(subtotal_posts); handler.reset(); } void report_subtotal(const char * spec_fmt = NULL, const optional& interval = none); virtual void flush() { if (values.size() > 0) report_subtotal(); item_handler::flush(); } virtual void operator()(post_t& post); virtual void clear() { amount_expr.mark_uncompiled(); values.clear(); temps.clear(); component_posts.clear(); item_handler::clear(); } }; class interval_posts : public subtotal_posts { date_interval_t start_interval; date_interval_t interval; account_t * empty_account; bool exact_periods; bool generate_empty_posts; std::deque all_posts; interval_posts(); public: interval_posts(post_handler_ptr _handler, expr_t& amount_expr, const date_interval_t& _interval, bool _exact_periods = false, bool _generate_empty_posts = false) : subtotal_posts(_handler, amount_expr), start_interval(_interval), interval(start_interval), exact_periods(_exact_periods), generate_empty_posts(_generate_empty_posts) { create_accounts(); TRACE_CTOR(interval_posts, "post_handler_ptr, expr_t&, date_interval_t, bool, bool"); } virtual ~interval_posts() throw() { TRACE_DTOR(interval_posts); } void create_accounts() { empty_account = &temps.create_account(_("")); } void report_subtotal(const date_interval_t& ival); #if DEBUG_ON void debug_interval(const date_interval_t& ival) { if (ival.start) DEBUG("filters.interval", "start = " << *ival.start); else DEBUG("filters.interval", "no start"); if (ival.finish) DEBUG("filters.interval", "finish = " << *ival.finish); else DEBUG("filters.interval", "no finish"); } #endif virtual void operator()(post_t& post); virtual void flush(); virtual void clear() { interval = start_interval; subtotal_posts::clear(); create_accounts(); } }; class posts_as_equity : public subtotal_posts { report_t& report; post_t * last_post; account_t * equity_account; account_t * balance_account; bool unround; posts_as_equity(); public: posts_as_equity(post_handler_ptr _handler, report_t& _report, expr_t& amount_expr, bool _unround) : subtotal_posts(_handler, amount_expr), report(_report), unround(_unround) { create_accounts(); TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&"); } virtual ~posts_as_equity() throw() { TRACE_DTOR(posts_as_equity); } void create_accounts() { equity_account = &temps.create_account(_("Equity")); balance_account = equity_account->find_account(_("Opening Balances")); } void report_subtotal(); virtual void flush() { report_subtotal(); subtotal_posts::flush(); } virtual void clear() { last_post = NULL; subtotal_posts::clear(); create_accounts(); } }; class by_payee_posts : public item_handler { typedef std::map > payee_subtotals_map; typedef std::pair > payee_subtotals_pair; expr_t& amount_expr; payee_subtotals_map payee_subtotals; by_payee_posts(); public: by_payee_posts(post_handler_ptr handler, expr_t& _amount_expr) : item_handler(handler), amount_expr(_amount_expr) { TRACE_CTOR(by_payee_posts, "post_handler_ptr, expr_t&"); } virtual ~by_payee_posts() { TRACE_DTOR(by_payee_posts); } virtual void flush(); virtual void operator()(post_t& post); virtual void clear() { amount_expr.mark_uncompiled(); payee_subtotals.clear(); item_handler::clear(); } }; class transfer_details : public item_handler { account_t * master; expr_t expr; scope_t& scope; temporaries_t temps; transfer_details(); public: enum element_t { SET_DATE, SET_ACCOUNT, SET_PAYEE } which_element; transfer_details(post_handler_ptr handler, element_t _which_element, account_t * _master, const expr_t& _expr, scope_t& _scope) : item_handler(handler), master(_master), expr(_expr), scope(_scope), which_element(_which_element) { TRACE_CTOR(transfer_details, "post_handler_ptr, element_t, account_t *, expr_t, scope_t&"); } virtual ~transfer_details() { TRACE_DTOR(transfer_details); handler.reset(); } virtual void operator()(post_t& post); virtual void clear() { expr.mark_uncompiled(); temps.clear(); item_handler::clear(); } }; class day_of_week_posts : public subtotal_posts { posts_list days_of_the_week[7]; day_of_week_posts(); public: day_of_week_posts(post_handler_ptr handler, expr_t& amount_expr) : subtotal_posts(handler, amount_expr) { TRACE_CTOR(day_of_week_posts, "post_handler_ptr, bool"); } virtual ~day_of_week_posts() throw() { TRACE_DTOR(day_of_week_posts); } virtual void flush(); virtual void operator()(post_t& post) { days_of_the_week[post.date().day_of_week()].push_back(&post); } virtual void clear() { for (int i = 0; i < 7; i++) days_of_the_week[i].clear(); subtotal_posts::clear(); } }; class generate_posts : public item_handler { generate_posts(); protected: typedef std::pair pending_posts_pair; typedef std::list pending_posts_list; pending_posts_list pending_posts; temporaries_t temps; public: generate_posts(post_handler_ptr handler) : item_handler(handler) { TRACE_CTOR(generate_posts, "post_handler_ptr"); } virtual ~generate_posts() { TRACE_DTOR(generate_posts); handler.reset(); } void add_period_xacts(period_xacts_list& period_xacts); virtual void add_post(const date_interval_t& period, post_t& post); virtual void clear() { pending_posts.clear(); temps.clear(); item_handler::clear(); } }; class budget_posts : public generate_posts { #define BUDGET_NO_BUDGET 0x00 #define BUDGET_BUDGETED 0x01 #define BUDGET_UNBUDGETED 0x02 #define BUDGET_WRAP_VALUES 0x04 uint_least8_t flags; date_t terminus; budget_posts(); public: budget_posts(post_handler_ptr handler, date_t _terminus, uint_least8_t _flags = BUDGET_BUDGETED) : generate_posts(handler), flags(_flags), terminus(_terminus) { TRACE_CTOR(budget_posts, "post_handler_ptr, date_t, uint_least8_t"); } virtual ~budget_posts() throw() { TRACE_DTOR(budget_posts); } void report_budget_items(const date_t& date); virtual void flush(); virtual void operator()(post_t& post); }; class forecast_posts : public generate_posts { predicate_t pred; scope_t& context; const std::size_t forecast_years; public: forecast_posts(post_handler_ptr handler, const predicate_t& predicate, scope_t& _context, const std::size_t _forecast_years) : generate_posts(handler), pred(predicate), context(_context), forecast_years(_forecast_years) { TRACE_CTOR(forecast_posts, "post_handler_ptr, predicate_t, scope_t&, std::size_t"); } virtual ~forecast_posts() throw() { TRACE_DTOR(forecast_posts); } virtual void add_post(const date_interval_t& period, post_t& post); virtual void flush(); virtual void clear() { pred.mark_uncompiled(); generate_posts::clear(); } }; class inject_posts : public item_handler { typedef std::set tag_injected_set; typedef std::pair tag_mapping_pair; typedef std::pair tags_list_pair; std::list tags_list; temporaries_t temps; public: inject_posts(post_handler_ptr handler, const string& tag_list, account_t * master); virtual ~inject_posts() throw() { TRACE_DTOR(inject_posts); handler.reset(); } virtual void operator()(post_t& post); }; ////////////////////////////////////////////////////////////////////// // // Account filters // template class pass_down_accounts : public item_handler { pass_down_accounts(); optional pred; optional context; public: pass_down_accounts(acct_handler_ptr handler, Iterator& iter, const optional& _pred = none, const optional& _context = none) : item_handler(handler), pred(_pred), context(_context) { TRACE_CTOR(pass_down_accounts, "acct_handler_ptr, accounts_iterator, ..."); while (account_t * account = *iter++) { if (! pred) { item_handler::operator()(*account); } else { bind_scope_t bound_scope(*context, *account); if ((*pred)(bound_scope)) item_handler::operator()(*account); } } item_handler::flush(); } virtual ~pass_down_accounts() { TRACE_DTOR(pass_down_accounts); } virtual void clear() { if (pred) pred->mark_uncompiled(); item_handler::clear(); } }; } // namespace ledger