/* * Copyright (c) 2003-2009, 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. */ #include #include "xact.h" #include "post.h" #include "account.h" #include "journal.h" #include "pool.h" namespace ledger { xact_base_t::xact_base_t(const xact_base_t& xact_base) : item_t(xact_base), journal(xact_base.journal) { TRACE_CTOR(xact_base_t, "copy"); } xact_base_t::~xact_base_t() { TRACE_DTOR(xact_base_t); if (! has_flags(ITEM_TEMP)) { foreach (post_t * post, posts) { // If the posting is a temporary, it will be destructed when the // temporary is. assert(! post->has_flags(ITEM_TEMP)); checked_delete(post); } } } void xact_base_t::add_post(post_t * post) { // You can add temporary postings to transactions, but not real postings to // temporary transactions. if (! post->has_flags(ITEM_TEMP)) assert(! has_flags(ITEM_TEMP)); posts.push_back(post); } bool xact_base_t::remove_post(post_t * post) { posts.remove(post); post->xact = NULL; return true; } bool xact_base_t::finalize() { // Scan through and compute the total balance for the xact. This is used // for auto-calculating the value of xacts with no cost, and the per-unit // price of unpriced commodities. value_t balance; post_t * null_post = NULL; foreach (post_t * post, posts) { if (! post->must_balance()) continue; amount_t& p(post->cost ? *post->cost : post->amount); if (! p.is_null()) { DEBUG("xact.finalize", "post must balance = " << p.reduced()); if (! post->cost && post->amount.is_annotated() && post->amount.annotation().price) { // If the amount has no cost, but is annotated with a per-unit // price, use the price times the amount as the cost post->cost = (*post->amount.annotation().price * post->amount).unrounded(); DEBUG("xact.finalize", "annotation price = " << *post->amount.annotation().price); DEBUG("xact.finalize", "amount = " << post->amount); DEBUG("xact.finalize", "priced cost = " << *post->cost); post->add_flags(POST_COST_CALCULATED); add_or_set_value(balance, post->cost->rounded().reduced()); } else { // If the amount was a cost, it very likely has the "keep_precision" // flag set, meaning commodity display precision is ignored when // displaying the amount. We never want this set for the balance, // so we must clear the flag in a temporary to avoid it propagating // into the balance. add_or_set_value(balance, p.keep_precision() ? p.rounded().reduced() : p.reduced()); } } else if (null_post) { throw_(std::logic_error, _("Only one posting with null amount allowed per transaction")); } else { null_post = post; } } VERIFY(balance.valid()); #if defined(DEBUG_ON) DEBUG("xact.finalize", "initial balance = " << balance); DEBUG("xact.finalize", "balance is " << balance.label()); if (balance.is_balance()) DEBUG("xact.finalize", "balance commodity count = " << balance.as_balance().amounts.size()); #endif // If there is only one post, balance against the default account if one has // been set. if (journal && journal->basket && posts.size() == 1 && ! balance.is_null()) { null_post = new post_t(journal->basket, ITEM_GENERATED); null_post->_state = (*posts.begin())->_state; add_post(null_post); } if (null_post != NULL) { // If one post has no value at all, its value will become the inverse of // the rest. If multiple commodities are involved, multiple posts are // generated to balance them all. DEBUG("xact.finalize", "there was a null posting"); if (balance.is_balance()) { bool first = true; const balance_t& bal(balance.as_balance()); foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { 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()) { throw_(balance_error, _("Transaction does not balance")); } balance = NULL_VALUE; } else if (balance.is_balance() && balance.as_balance().amounts.size() == 2) { // When an xact involves two different commodities (regardless of how // many posts there are) determine the conversion ratio by dividing the // total value of one commodity by the total value of the other. This // establishes the per-unit cost for this post for both commodities. DEBUG("xact.finalize", "there were exactly two commodities"); bool saw_cost = false; post_t * top_post = NULL; foreach (post_t * post, posts) { if (! post->amount.is_null()) { if (post->amount.is_annotated()) top_post = post; else if (! top_post) top_post = post; } if (post->cost && ! post->has_flags(POST_COST_CALCULATED)) { saw_cost = true; break; } } if (! saw_cost && top_post) { const balance_t& bal(balance.as_balance()); DEBUG("xact.finalize", "there were no costs, and a valid top_post"); balance_t::amounts_map::const_iterator a = bal.amounts.begin(); const amount_t * x = &(*a++).second; const amount_t * y = &(*a++).second; if (x->commodity() != top_post->amount.commodity()) { const amount_t * t = x; x = y; y = t; } if (*x && *y) { DEBUG("xact.finalize", "primary amount = " << *x); DEBUG("xact.finalize", "secondary amount = " << *y); commodity_t& comm(x->commodity()); amount_t per_unit_cost; amount_t total_cost; foreach (post_t * post, posts) { if (post != top_post && post->must_balance() && ! post->amount.is_null() && post->amount.is_annotated() && post->amount.annotation().price) { amount_t temp = *post->amount.annotation().price * post->amount; if (total_cost.is_null()) { total_cost = temp; y = &total_cost; } else { total_cost += temp; } DEBUG("xact.finalize", "total_cost = " << total_cost); } } per_unit_cost = (*y / *x).abs().unrounded(); DEBUG("xact.finalize", "per_unit_cost = " << per_unit_cost); foreach (post_t * post, posts) { const amount_t& amt(post->amount); if (post->must_balance() && amt.commodity() == comm) { balance -= amt; post->cost = per_unit_cost * amt; post->add_flags(POST_COST_CALCULATED); balance += *post->cost; DEBUG("xact.finalize", "set post->cost to = " << *post->cost); } } } } } // Now that the post list has its final form, calculate the balance once // more in terms of total cost, accounting for any possible gain/loss // amounts. DEBUG("xact.finalize", "resolved balance = " << balance); posts_list copy(posts); foreach (post_t * post, copy) { if (! post->cost) continue; if (post->amount.commodity() == post->cost->commodity()) throw_(balance_error, _("A posting's cost must be of a different commodity than its amount")); cost_breakdown_t breakdown = amount_t::current_pool->exchange(post->amount, *post->cost, false, datetime_t(date(), time_duration(0, 0, 0, 0))); if (post->amount.is_annotated() && breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) { if (amount_t gain_loss = (breakdown.basis_cost - breakdown.final_cost).rounded()) { DEBUG("xact.finalize", "gain_loss = " << gain_loss); add_or_set_value(balance, gain_loss.reduced()); account_t * account; if (gain_loss.sign() > 0) account = journal->find_account(_("Equity:Capital Gains")); else account = journal->find_account(_("Equity:Capital Losses")); post_t * p = new post_t(account, gain_loss, ITEM_GENERATED); p->set_state(post->state()); add_post(p); DEBUG("xact.finalize", "added gain_loss, balance = " << balance); } } else { post->amount = breakdown.amount; DEBUG("xact.finalize", "added breakdown, balance = " << balance); } } DEBUG("xact.finalize", "final balance = " << balance); if (! balance.is_null() && ! balance.is_zero()) { add_error_context(item_context(*this, _("While balancing transaction"))); add_error_context(_("Unbalanced remainder is:")); add_error_context(value_context(balance)); throw_(balance_error, _("Transaction does not balance")); } // Add a pointer to each posting to their related accounts if (dynamic_cast(this)) { bool all_null = true; bool some_null = false; foreach (post_t * post, posts) { if (! post->amount.is_null()) { all_null = false; post->amount.in_place_reduce(); } else { 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) return false; // ignore this xact completely else if (some_null) throw_(balance_error, _("There cannot be null amounts after balancing a transaction")); } VERIFY(valid()); return true; } xact_t::xact_t(const xact_t& e) : xact_base_t(e), code(e.code), payee(e.payee) { TRACE_CTOR(xact_t, "copy"); } void xact_t::add_post(post_t * post) { post->xact = this; xact_base_t::add_post(post); } value_t xact_t::magnitude() const { value_t halfbal = 0L; foreach (const post_t * post, posts) { if (post->amount.sign() > 0) { if (post->cost) halfbal += post->cost->number(); else halfbal += post->amount.number(); } } return halfbal; } string xact_t::idstring() const { std::ostringstream buf; buf << format_date(*_date, FMT_WRITTEN); buf << payee; magnitude().print(buf); return buf.str(); } string xact_t::id() const { SHA1 sha; sha.Reset(); sha << idstring().c_str(); uint_least32_t message_digest[5]; sha.Result(message_digest); return to_hex(message_digest, 5); } 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) return string_value(*xact.code); else return string_value(empty_string); } value_t get_payee(xact_t& xact) { return string_value(xact.payee); } template value_t get_wrapper(call_scope_t& scope) { return (*Func)(find_scope(scope)); } } expr_t::ptr_op_t xact_t::lookup(const string& name) { switch (name[0]) { case 'c': if (name == "code") 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>); break; case 'p': if (name[1] == '\0' || name == "payee") return WRAP_FUNCTOR(get_wrapper<&get_payee>); break; } return item_t::lookup(name); } bool xact_t::valid() const { if (! _date) { DEBUG("ledger.validate", "xact_t: ! _date"); return false; } foreach (post_t * post, posts) if (post->xact != this || ! post->valid()) { DEBUG("ledger.validate", "xact_t: post not valid"); return false; } return true; } void auto_xact_t::extend_xact(xact_base_t& xact, bool post_handler) { posts_list initial_posts(xact.posts.begin(), xact.posts.end()); try { foreach (post_t * initial_post, initial_posts) { if (! initial_post->has_flags(ITEM_GENERATED) && predicate(*initial_post)) { foreach (post_t * post, posts) { amount_t amt; assert(post->amount); if (! post->amount.commodity()) { if (post_handler || initial_post->amount.is_null()) continue; amt = initial_post->amount * post->amount; } else { if (post_handler) continue; amt = post->amount; } IF_DEBUG("xact.extend") { DEBUG("xact.extend", "Initial post on line " << initial_post->pos->beg_line << ": " << "amount " << initial_post->amount << " (precision " << initial_post->amount.precision() << ")"); #if defined(DEBUG_ON) if (initial_post->amount.keep_precision()) DEBUG("xact.extend", " precision is kept"); #endif DEBUG("xact.extend", "Posting on line " << post->pos->beg_line << ": " << "amount " << post->amount << ", amt " << amt << " (precision " << post->amount.precision() << " != " << amt.precision() << ")"); #if defined(DEBUG_ON) if (post->amount.keep_precision()) DEBUG("xact.extend", " precision is kept"); if (amt.keep_precision()) DEBUG("xact.extend", " amt precision is kept"); #endif } account_t * account = post->account; string fullname = account->fullname(); assert(! fullname.empty()); if (fullname == "$account" || fullname == "@account") account = initial_post->account; // Copy over details so that the resulting post is a mirror of // the automated xact's one. 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); } } } } catch (const std::exception& err) { add_error_context(item_context(*this, _("While applying automated transaction"))); add_error_context(item_context(xact, _("While extending transaction"))); throw; } } void extend_xact_base(journal_t * journal, xact_base_t& base, bool post_handler) { foreach (auto_xact_t * xact, journal->auto_xacts) xact->extend_xact(base, post_handler); } } // namespace ledger