diff options
Diffstat (limited to 'src/xact.cc')
-rw-r--r-- | src/xact.cc | 197 |
1 files changed, 106 insertions, 91 deletions
diff --git a/src/xact.cc b/src/xact.cc index 596b39fa..226fd5ab 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, 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 @@ -35,6 +35,7 @@ #include "post.h" #include "account.h" #include "journal.h" +#include "context.h" #include "pool.h" namespace ledger { @@ -54,6 +55,9 @@ xact_base_t::~xact_base_t() // If the posting is a temporary, it will be destructed when the // temporary is. assert(! post->has_flags(ITEM_TEMP)); + + if (post->account) + post->account->remove_post(post); checked_delete(post); } } @@ -108,6 +112,46 @@ value_t xact_base_t::magnitude() const return halfbal; } +namespace { + inline bool account_ends_with_special_char(const string& name) { + string::size_type len(name.length()); + return (std::isdigit(name[len - 1]) || name[len - 1] == ')' || + name[len - 1] == '}' || name[len - 1] == ']'); + } + + struct add_balancing_post + { + bool first; + xact_base_t& xact; + post_t * null_post; + + explicit add_balancing_post(xact_base_t& _xact, post_t * _null_post) + : first(true), xact(_xact), null_post(_null_post) { + TRACE_CTOR(add_balancing_post, "xact_base_t&, post_t *"); + } + add_balancing_post(const add_balancing_post& other) + : first(other.first), xact(other.xact), null_post(other.null_post) { + TRACE_CTOR(add_balancing_post, "copy"); + } + ~add_balancing_post() throw() { + TRACE_DTOR(add_balancing_post); + } + + void operator()(const amount_t& amount) { + if (first) { + null_post->amount = amount.negated(); + null_post->add_flags(POST_CALCULATED); + first = false; + } else { + unique_ptr<post_t> p(new post_t(null_post->account, amount.negated(), + ITEM_GENERATED | POST_CALCULATED)); + p->set_state(null_post->state()); + xact.add_post(p.release()); + } + } + }; +} + bool xact_base_t::finalize() { // Scan through and compute the total balance for the xact. This is used @@ -133,8 +177,19 @@ bool xact_base_t::finalize() p.rounded().reduced() : p.reduced()); } else if (null_post) { - throw_(std::logic_error, - _("Only one posting with null amount allowed per transaction")); + bool post_account_bad = + account_ends_with_special_char(post->account->fullname()); + bool null_post_account_bad = + account_ends_with_special_char(null_post->account->fullname()); + + if (post_account_bad || null_post_account_bad) + throw_(std::logic_error, + _("Posting with null amount's account may be mispelled:\n \"%1\"") + << (post_account_bad ? post->account->fullname() : + null_post->account->fullname())); + else + throw_(std::logic_error, + _("Only one posting with null amount allowed per transaction")); } else { null_post = post; @@ -266,11 +321,13 @@ bool xact_base_t::finalize() cost_breakdown_t breakdown = commodity_pool_t::current_pool->exchange - (post->amount, *post->cost, false, + (post->amount, *post->cost, false, ! post->has_flags(POST_COST_VIRTUAL), datetime_t(date(), time_duration(0, 0, 0, 0))); if (post->amount.has_annotation() && post->amount.annotation().price) { if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) { + DEBUG("xact.finalize", "breakdown.basis_cost = " << breakdown.basis_cost); + DEBUG("xact.finalize", "breakdown.final_cost = " << breakdown.final_cost); if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) { DEBUG("xact.finalize", "gain_loss = " << gain_loss); gain_loss.in_place_round(); @@ -323,43 +380,17 @@ bool xact_base_t::finalize() // generated to balance them all. DEBUG("xact.finalize", "there was a null posting"); - - if (balance.is_balance()) { - const balance_t& bal(balance.as_balance()); - typedef std::map<string, amount_t> sorted_amounts_map; - sorted_amounts_map samp; - foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { - std::pair<sorted_amounts_map::iterator, bool> result = - samp.insert(sorted_amounts_map::value_type(pair.first->mapping_key(), - pair.second)); - assert(result.second); - } - - bool first = true; - foreach (sorted_amounts_map::value_type& pair, samp) { - if (first) { - null_post->amount = pair.second.negated(); - null_post->add_flags(POST_CALCULATED); - first = false; - } else { - post_t * p = new post_t(null_post->account, pair.second.negated(), - ITEM_GENERATED | POST_CALCULATED); - p->set_state(null_post->state()); - add_post(p); - } - } - } - else if (balance.is_amount()) { - null_post->amount = balance.as_amount().negated(); - null_post->add_flags(POST_CALCULATED); - } - else if (balance.is_long()) { - null_post->amount = amount_t(- balance.as_long()); - null_post->add_flags(POST_CALCULATED); - } - else if (! balance.is_null() && ! balance.is_realzero()) { + add_balancing_post post_adder(*this, null_post); + + if (balance.is_balance()) + balance.as_balance_lval().map_sorted_amounts(post_adder); + else if (balance.is_amount()) + post_adder(balance.as_amount_lval()); + else if (balance.is_long()) + post_adder(balance.to_amount()); + else if (! balance.is_null() && ! balance.is_realzero()) throw_(balance_error, _("Transaction does not balance")); - } + balance = NULL_VALUE; } @@ -457,6 +488,9 @@ bool xact_base_t::verify() xact_t::xact_t(const xact_t& e) : xact_base_t(e), code(e.code), payee(e.payee) +#ifdef DOCUMENT_MODEL + , data(NULL) +#endif { TRACE_CTOR(xact_t, "copy"); } @@ -467,30 +501,10 @@ void xact_t::add_post(post_t * post) xact_base_t::add_post(post); } -string xact_t::idstring() const -{ - std::ostringstream buf; - buf << format_date(*_date, FMT_WRITTEN); - buf << payee; - magnitude().number().print(buf); - return buf.str(); -} - -string xact_t::id() const -{ - return sha1sum(idstring()); -} - namespace { value_t get_magnitude(xact_t& xact) { return xact.magnitude(); } - value_t get_idstring(xact_t& xact) { - return string_value(xact.idstring()); - } - value_t get_id(xact_t& xact) { - return string_value(xact.id()); - } value_t get_code(xact_t& xact) { if (xact.code) @@ -554,13 +568,6 @@ expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_wrapper<&get_code>); break; - case 'i': - if (name == "id") - return WRAP_FUNCTOR(get_wrapper<&get_id>); - else if (name == "idstring") - return WRAP_FUNCTOR(get_wrapper<&get_idstring>); - break; - case 'm': if (name == "magnitude") return WRAP_FUNCTOR(get_wrapper<&get_magnitude>); @@ -592,7 +599,6 @@ bool xact_t::valid() const } namespace { - bool post_pred(expr_t::ptr_op_t op, post_t& post) { switch (op->kind) { @@ -609,6 +615,9 @@ namespace { else break; + case expr_t::op_t::O_EQ: + return post_pred(op->left(), post) == post_pred(op->right(), post); + case expr_t::op_t::O_NOT: return ! post_pred(op->left(), post); @@ -631,10 +640,9 @@ namespace { throw_(calc_error, _("Unhandled operator")); return false; } +} -} // unnamed namespace - -void auto_xact_t::extend_xact(xact_base_t& xact) +void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) { posts_list initial_posts(xact.posts.begin(), xact.posts.end()); @@ -646,6 +654,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact) if (initial_post->has_flags(ITEM_GENERATED)) continue; + bind_scope_t bound_scope(*scope_t::default_scope, *initial_post); + bool matches_predicate = false; if (try_quick_match) { try { @@ -673,14 +683,13 @@ void auto_xact_t::extend_xact(xact_base_t& xact) DEBUG("xact.extend.fail", "The quick matcher failed, going back to regular eval"); try_quick_match = false; - matches_predicate = predicate(*initial_post); + matches_predicate = predicate(bound_scope); } } else { - matches_predicate = predicate(*initial_post); + matches_predicate = predicate(bound_scope); } - if (matches_predicate) { - bind_scope_t bound_scope(*scope_t::default_scope, *initial_post); + if (matches_predicate) { if (deferred_notes) { foreach (deferred_tag_data_t& data, *deferred_notes) { if (data.apply_to_post == NULL) @@ -688,18 +697,19 @@ void auto_xact_t::extend_xact(xact_base_t& xact) data.overwrite_existing); } } + if (check_exprs) { - foreach (check_expr_pair& pair, *check_exprs) { - if (pair.second == auto_xact_t::EXPR_GENERAL) { + foreach (expr_t::check_expr_pair& pair, *check_exprs) { + if (pair.second == expr_t::EXPR_GENERAL) { pair.first.calc(bound_scope); } else if (! pair.first.calc(bound_scope).to_boolean()) { - if (pair.second == auto_xact_t::EXPR_ASSERTION) { + if (pair.second == expr_t::EXPR_ASSERTION) throw_(parse_error, _("Transaction assertion failed: %1") << pair.first); - } else { - warning_(_("Transaction check failed: %1") << pair.first); - } + else + context.warning(STR(_("Transaction check failed: %1") + << pair.first)); } } } @@ -770,20 +780,25 @@ void auto_xact_t::extend_xact(xact_base_t& xact) post_t * new_post = new post_t(account, amt); new_post->copy_details(*post); new_post->add_flags(ITEM_GENERATED); - - xact.add_post(new_post); - new_post->account->add_post(new_post); - - if (new_post->must_balance()) - needs_further_verification = true; + new_post->account = + journal->register_account(account->fullname(), new_post, + journal->master); if (deferred_notes) { foreach (deferred_tag_data_t& data, *deferred_notes) { - if (data.apply_to_post == post) + if (! data.apply_to_post || data.apply_to_post == post) new_post->parse_tags(data.tag_data.c_str(), bound_scope, data.overwrite_existing); } } + + extend_post(*new_post, *journal); + + xact.add_post(new_post); + new_post->account->add_post(new_post); + + if (new_post->must_balance()) + needs_further_verification = true; } } } @@ -817,9 +832,9 @@ void to_xml(std::ostream& out, const xact_t& xact) push_xml y(out, "date"); to_xml(out, *xact._date, false); } - if (xact._date_eff) { - push_xml y(out, "effective-date"); - to_xml(out, *xact._date_eff, false); + if (xact._date_aux) { + push_xml y(out, "aux-date"); + to_xml(out, *xact._date_aux, false); } if (xact.code) { |