/* * 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 "entry.h" #include "journal.h" #include "format.h" #include "session.h" #include "report.h" namespace ledger { entry_base_t::entry_base_t(const entry_base_t& e) : item_t(), journal(NULL) { TRACE_CTOR(entry_base_t, "copy"); #if 0 xacts.insert(xacts.end(), e.xacts.begin(), e.xacts.end()); #endif } entry_base_t::~entry_base_t() { TRACE_DTOR(entry_base_t); foreach (xact_t * xact, xacts) { // If the transaction is a temporary, it will be destructed when the // temporary is. if (! xact->has_flags(ITEM_TEMP)) checked_delete(xact); } } item_t::state_t entry_base_t::state() const { bool first = true; state_t result = CLEARED; foreach (xact_t * xact, xacts) { if (xact->_state == UNCLEARED) return UNCLEARED; else if (xact->_state == PENDING) result = PENDING; } return result; } void entry_base_t::add_xact(xact_t * xact) { xacts.push_back(xact); } bool entry_base_t::remove_xact(xact_t * xact) { xacts.remove(xact); xact->entry = NULL; return true; } bool entry_base_t::finalize() { // Scan through and compute the total balance for the entry. This is used // for auto-calculating the value of entries with no cost, and the per-unit // price of unpriced commodities. // (let ((balance 0) // null-xact) value_t balance; xact_t * null_xact = NULL; foreach (xact_t * xact, xacts) { if (xact->must_balance()) { amount_t& p(xact->cost ? *xact->cost : xact->amount); DEBUG("entry.finalize", "xact must balance = " << p); if (! p.is_null()) { if (p.keep_precision()) { // 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.rounded()); } else { add_or_set_value(balance, p); } } else { if (null_xact) throw_(std::logic_error, "Only one xact with null amount allowed per entry"); else null_xact = xact; } } } assert(balance.valid()); DEBUG("entry.finalize", "initial balance = " << balance); // If there is only one xact, balance against the default account if // one has been set. if (journal && journal->basket && xacts.size() == 1 && ! balance.is_null()) { // jww (2008-07-24): Need to make the rest of the code aware of what to do // when it sees a generated xact. null_xact = new xact_t(journal->basket, ITEM_GENERATED); null_xact->_state = (*xacts.begin())->_state; add_xact(null_xact); } if (null_xact != NULL) { // If one xact has no value at all, its value will become the // inverse of the rest. If multiple commodities are involved, multiple // xacts are generated to balance them all. 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_xact->amount = pair.second.negate(); first = false; } else { add_xact(new xact_t(null_xact->account, pair.second.negate(), ITEM_GENERATED)); } } } else if (balance.is_amount()) { null_xact->amount = balance.as_amount().negate(); null_xact->add_flags(XACT_CALCULATED); } else if (! balance.is_null() && ! balance.is_realzero()) { throw_(balance_error, "Entry does not balance"); } balance = NULL_VALUE; } else if (balance.is_balance() && balance.as_balance().amounts.size() == 2) { // When an entry involves two different commodities (regardless of how // many xacts 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 xact for both // commodities. const balance_t& bal(balance.as_balance()); balance_t::amounts_map::const_iterator a = bal.amounts.begin(); const amount_t& x((*a++).second); const amount_t& y((*a++).second); if (! y.is_realzero()) { amount_t per_unit_cost = (x / y).abs(); commodity_t& comm(x.commodity()); foreach (xact_t * xact, xacts) { const amount_t& amt(xact->amount); if (! (xact->cost || ! xact->must_balance() || amt.commodity() == comm)) { balance -= amt; xact->cost = per_unit_cost * amt; balance += *xact->cost; } } } DEBUG("entry.finalize", "resolved balance = " << balance); } // Now that the xact list has its final form, calculate the balance // once more in terms of total cost, accounting for any possible gain/loss // amounts. foreach (xact_t * xact, xacts) { if (xact->cost) { if (xact->amount.commodity() == xact->cost->commodity()) throw_(balance_error, "Transaction's cost must be of a different commodity"); commodity_t::cost_breakdown_t breakdown = commodity_t::exchange(xact->amount, *xact->cost); if (xact->amount.is_annotated()) add_or_set_value(balance, (breakdown.basis_cost - breakdown.final_cost).rounded()); else xact->amount = breakdown.amount; } } DEBUG("entry.finalize", "final balance = " << balance); if (! balance.is_null() && ! balance.is_zero()) { add_error_context(item_context(*this, "While balancing entry")); add_error_context("Unbalanced remainder is:"); add_error_context(value_context(balance)); throw_(balance_error, "Entry does not balance"); } // Add the final calculated totals each to their related account if (dynamic_cast(this)) { bool all_null = true; foreach (xact_t * xact, xacts) { if (! xact->amount.is_null()) { all_null = false; // jww (2008-08-09): For now, this feature only works for // non-specific commodities. add_or_set_value(xact->account->xdata().value, xact->amount); DEBUG("entry.finalize.totals", "Total for " << xact->account->fullname() << " + " << xact->amount << ": " << xact->account->xdata().value); } } if (all_null) return false; // ignore this entry completely } return true; } entry_t::entry_t(const entry_t& e) : entry_base_t(e), code(e.code), payee(e.payee) { TRACE_CTOR(entry_t, "copy"); #if 0 foreach (xact_t * xact, xacts) xact->entry = this; #endif } void entry_t::add_xact(xact_t * xact) { xact->entry = this; entry_base_t::add_xact(xact); } namespace { value_t get_code(entry_t& entry) { if (entry.code) return string_value(*entry.code); else return string_value(empty_string); } value_t get_payee(entry_t& entry) { return string_value(entry.payee); } template value_t get_wrapper(call_scope_t& scope) { return (*Func)(find_scope(scope)); } } expr_t::ptr_op_t entry_t::lookup(const string& name) { switch (name[0]) { case 'c': if (name == "code") return WRAP_FUNCTOR(get_wrapper<&get_code>); break; case 'p': if (name[1] == '\0' || name == "payee") return WRAP_FUNCTOR(get_wrapper<&get_payee>); break; } return item_t::lookup(name); } bool entry_t::valid() const { if (! _date || ! journal) { DEBUG("ledger.validate", "entry_t: ! _date || ! journal"); return false; } foreach (xact_t * xact, xacts) if (xact->entry != this || ! xact->valid()) { DEBUG("ledger.validate", "entry_t: xact not valid"); return false; } return true; } void auto_entry_t::extend_entry(entry_base_t& entry, bool post) { xacts_list initial_xacts(entry.xacts.begin(), entry.xacts.end()); foreach (xact_t * initial_xact, initial_xacts) { if (predicate(*initial_xact)) { foreach (xact_t * xact, xacts) { amount_t amt; assert(xact->amount); if (! xact->amount.commodity()) { if (! post) continue; assert(initial_xact->amount); amt = initial_xact->amount * xact->amount; } else { if (post) continue; amt = xact->amount; } IF_DEBUG("entry.extend") { DEBUG("entry.extend", "Initial xact on line " << initial_xact->beg_line << ": " << "amount " << initial_xact->amount << " (precision " << initial_xact->amount.precision() << ")"); if (initial_xact->amount.keep_precision()) DEBUG("entry.extend", " precision is kept"); DEBUG("entry.extend", "Transaction on line " << xact->beg_line << ": " << "amount " << xact->amount << ", amt " << amt << " (precision " << xact->amount.precision() << " != " << amt.precision() << ")"); if (xact->amount.keep_precision()) DEBUG("entry.extend", " precision is kept"); if (amt.keep_precision()) DEBUG("entry.extend", " amt precision is kept"); } account_t * account = xact->account; string fullname = account->fullname(); assert(! fullname.empty()); if (fullname == "$account" || fullname == "@account") account = initial_xact->account; // Copy over details so that the resulting xact is a mirror of // the automated entry's one. xact_t * new_xact = new xact_t(account, amt); new_xact->copy_details(*xact); new_xact->add_flags(XACT_AUTO); entry.add_xact(new_xact); } } } } void extend_entry_base(journal_t * journal, entry_base_t& base, bool post) { foreach (auto_entry_t * entry, journal->auto_entries) entry->extend_entry(base, post); } } // namespace ledger