diff options
Diffstat (limited to 'src/xact.cc')
-rw-r--r-- | src/xact.cc | 518 |
1 files changed, 303 insertions, 215 deletions
diff --git a/src/xact.cc b/src/xact.cc index 14464c6f..f62f8c09 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -32,190 +32,290 @@ #include "xact.h" #include "journal.h" #include "account.h" -#include "interactive.h" #include "format.h" namespace ledger { -bool xact_t::has_tag(const string& tag) const +xact_base_t::xact_base_t(const xact_base_t&) + : item_t(), journal(NULL) { - if (item_t::has_tag(tag)) - return true; - if (entry) - return entry->has_tag(tag); - return false; + TRACE_CTOR(xact_base_t, "copy"); } -bool xact_t::has_tag(const mask_t& tag_mask, - const optional<mask_t>& value_mask) const +xact_base_t::~xact_base_t() { - if (item_t::has_tag(tag_mask, value_mask)) - return true; - if (entry) - return entry->has_tag(tag_mask, value_mask); - return false; -} + TRACE_DTOR(xact_base_t); -optional<string> xact_t::get_tag(const string& tag) const -{ - if (optional<string> value = item_t::get_tag(tag)) - return value; - if (entry) - return entry->get_tag(tag); - return none; -} - -optional<string> xact_t::get_tag(const mask_t& tag_mask, - const optional<mask_t>& value_mask) const -{ - if (optional<string> value = item_t::get_tag(tag_mask, value_mask)) - return value; - if (entry) - return entry->get_tag(tag_mask, value_mask); - return none; + foreach (post_t * post, posts) { + // If the posting is a temporary, it will be destructed when the + // temporary is. + if (! post->has_flags(ITEM_TEMP)) + checked_delete(post); + } } -date_t xact_t::date() const +item_t::state_t xact_base_t::state() const { - if (xdata_ && is_valid(xdata_->date)) - return xdata_->date; - - if (item_t::use_effective_date) { - if (_date_eff) - return *_date_eff; - else if (entry && entry->_date_eff) - return *entry->_date_eff; - } + state_t result = CLEARED; - if (! _date) { - assert(entry); - return entry->date(); + foreach (post_t * post, posts) { + if (post->_state == UNCLEARED) + return UNCLEARED; + else if (post->_state == PENDING) + result = PENDING; } - return *_date; + return result; } -optional<date_t> xact_t::effective_date() const +void xact_base_t::add_post(post_t * post) { - optional<date_t> date = item_t::effective_date(); - if (! date && entry) - return entry->effective_date(); - return date; + posts.push_back(post); } -item_t::state_t xact_t::state() const +bool xact_base_t::remove_post(post_t * post) { - if (entry) { - state_t entry_state = entry->state(); - if ((_state == UNCLEARED && entry_state != UNCLEARED) || - (_state == PENDING && entry_state == CLEARED)) - return entry_state; - } - return _state; + posts.remove(post); + post->xact = NULL; + return true; } -namespace { - value_t get_this(xact_t& xact) { - return value_t(static_cast<scope_t *>(&xact)); +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()) { + amount_t& p(post->cost ? *post->cost : post->amount); + DEBUG("xact.finalize", "post 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_post) + throw_(std::logic_error, + "Only one posting with null amount allowed per transaction"); + else + null_post = post; + } + } } + assert(balance.valid()); - value_t get_is_calculated(xact_t& xact) { - return xact.has_flags(XACT_CALCULATED); - } + DEBUG("xact.finalize", "initial balance = " << balance); - value_t get_virtual(xact_t& xact) { - return xact.has_flags(XACT_VIRTUAL); - } + // If there is only one post, balance against the default account if one has + // been set. - value_t get_real(xact_t& xact) { - return ! xact.has_flags(XACT_VIRTUAL); + if (journal && journal->basket && posts.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 post. + null_post = new post_t(journal->basket, ITEM_GENERATED); + null_post->_state = (*posts.begin())->_state; + add_post(null_post); } - value_t get_actual(xact_t& xact) { - return ! xact.has_flags(XACT_AUTO); - } + 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. + + 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(); + first = false; + } else { + add_post(new post_t(null_post->account, pair.second.negated(), + ITEM_GENERATED)); + } + } + } + else if (balance.is_amount()) { + null_post->amount = balance.as_amount().negated(); + null_post->add_flags(POST_CALCULATED); + } + else if (! balance.is_null() && ! balance.is_realzero()) { + throw_(balance_error, "Transaction does not balance"); + } + balance = NULL_VALUE; - value_t get_entry(xact_t& xact) { - return value_t(static_cast<scope_t *>(xact.entry)); } + 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) { + saw_cost = true; + break; + } + } - value_t get_code(xact_t& xact) { - if (xact.entry->code) - return string_value(*xact.entry->code); - else - return string_value(empty_string); - } + 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; + } + + DEBUG("xact.finalize", "primary amount = " << *y); + DEBUG("xact.finalize", "secondary amount = " << *x); + + 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(); + + 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; + balance += *post->cost; + + DEBUG("xact.finalize", "set post->cost to = " << *post->cost); + } + } + } - value_t get_payee(xact_t& xact) { - return string_value(xact.entry->payee); + DEBUG("xact.finalize", "resolved balance = " << balance); } - value_t get_amount(xact_t& xact) { - if (xact.has_xdata() && - xact.xdata().has_flags(XACT_EXT_COMPOUND)) { - return xact.xdata().value; - } else { - return xact.amount; - } - } + // 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. - value_t get_commodity(xact_t& xact) { - return string_value(xact.amount.commodity().symbol()); - } + foreach (post_t * post, posts) { + if (post->cost) { + if (post->amount.commodity() == post->cost->commodity()) + throw_(balance_error, "Posting's cost must be of a different commodity"); - value_t get_commodity_is_primary(xact_t& xact) { - return xact.amount.commodity().has_flags(COMMODITY_PRIMARY); - } + commodity_t::cost_breakdown_t breakdown = + commodity_t::exchange(post->amount, *post->cost, false, + datetime_t(date(), time_duration(0, 0, 0, 0))); - value_t get_cost(xact_t& xact) { - if (xact.has_xdata() && - xact.xdata().has_flags(XACT_EXT_COMPOUND)) { - return xact.xdata().value; - } else { - if (xact.cost) - return *xact.cost; + if (post->amount.is_annotated() && + breakdown.basis_cost.commodity() == + breakdown.final_cost.commodity()) + add_or_set_value(balance, (breakdown.basis_cost - + breakdown.final_cost).rounded()); else - return xact.amount; + post->amount = breakdown.amount; } } - value_t get_total(xact_t& xact) { - if (xact.xdata_ && ! xact.xdata_->total.is_null()) - return xact.xdata_->total; - else - return xact.amount; - } + DEBUG("xact.finalize", "final balance = " << balance); - value_t get_count(xact_t& xact) { - if (xact.xdata_) - return xact.xdata_->count; - else - return 1L; + 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"); } - value_t get_account(call_scope_t& scope) - { - in_context_t<xact_t> env(scope, "&l"); + // Add the final calculated totals each to their related account - string name = env->reported_account()->fullname(); + if (dynamic_cast<xact_t *>(this)) { + bool all_null = true; + foreach (post_t * post, posts) { + if (! post->amount.is_null()) { + all_null = false; - if (env.has(0) && env.get<long>(0) > 2) - name = format_t::truncate(name, env.get<long>(0) - 2, true); + // jww (2008-08-09): For now, this feature only works for non-specific + // commodities. + add_or_set_value(post->account->xdata().value, post->amount); - if (env->has_flags(XACT_VIRTUAL)) { - if (env->must_balance()) - name = string("[") + name + "]"; - else - name = string("(") + name + ")"; + DEBUG("xact.finalize.totals", + "Total for " << post->account->fullname() << " + " + << post->amount << ": " << post->account->xdata().value); + } } - return string_value(name); + if (all_null) + return false; // ignore this xact completely } - value_t get_account_base(xact_t& xact) { - return string_value(xact.reported_account()->name); + 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); +} + +namespace { + value_t get_code(xact_t& xact) { + if (xact.code) + return string_value(*xact.code); + else + return string_value(empty_string); } - value_t get_account_depth(xact_t& xact) { - return long(xact.reported_account()->depth); + value_t get_payee(xact_t& xact) { + return string_value(xact.payee); } template <value_t (*Func)(xact_t&)> @@ -227,65 +327,14 @@ namespace { expr_t::ptr_op_t xact_t::lookup(const string& name) { switch (name[0]) { - case 'a': - if (name[1] == '\0' || name == "amount") - return WRAP_FUNCTOR(get_wrapper<&get_amount>); - else if (name == "account") - return WRAP_FUNCTOR(get_account); - else if (name == "account_base") - return WRAP_FUNCTOR(get_wrapper<&get_account_base>); - else if (name == "actual") - return WRAP_FUNCTOR(get_wrapper<&get_actual>); - break; - case 'c': if (name == "code") return WRAP_FUNCTOR(get_wrapper<&get_code>); - else if (name == "cost") - return WRAP_FUNCTOR(get_wrapper<&get_cost>); - else if (name == "count") - return WRAP_FUNCTOR(get_wrapper<&get_count>); - else if (name == "calculated") - return WRAP_FUNCTOR(get_wrapper<&get_is_calculated>); - else if (name == "commodity") - return WRAP_FUNCTOR(get_wrapper<&get_commodity>); - break; - - case 'd': - if (name == "depth") - return WRAP_FUNCTOR(get_wrapper<&get_account_depth>); - break; - - case 'e': - if (name == "entry") - return WRAP_FUNCTOR(get_wrapper<&get_entry>); - break; - - case 'r': - if (name == "real") - return WRAP_FUNCTOR(get_wrapper<&get_real>); break; case 'p': - if (name == "payee") + if (name[1] == '\0' || name == "payee") return WRAP_FUNCTOR(get_wrapper<&get_payee>); - else if (name == "primary") - return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>); - break; - - case 't': - if (name[1] == '\0' || name == "total") - return WRAP_FUNCTOR(get_wrapper<&get_total>); - break; - - case 'v': - if (name == "virtual") - return WRAP_FUNCTOR(get_wrapper<&get_virtual>); - break; - - case 'x': - if (name == "xact") - return WRAP_FUNCTOR(get_wrapper<&get_this>); break; } @@ -294,46 +343,85 @@ expr_t::ptr_op_t xact_t::lookup(const string& name) bool xact_t::valid() const { - if (! entry) { - DEBUG("ledger.validate", "xact_t: ! entry"); - return false; - } - - xacts_list::const_iterator i = - std::find(entry->xacts.begin(), - entry->xacts.end(), this); - if (i == entry->xacts.end()) { - DEBUG("ledger.validate", "xact_t: ! found"); + if (! _date || ! journal) { + DEBUG("ledger.validate", "xact_t: ! _date || ! journal"); return false; } - if (! account) { - DEBUG("ledger.validate", "xact_t: ! account"); - return false; - } - - if (! amount.valid()) { - DEBUG("ledger.validate", "xact_t: ! amount.valid()"); - return false; - } - - if (cost && ! cost->valid()) { - DEBUG("ledger.validate", "xact_t: cost && ! cost->valid()"); - 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 xact_t::add_to_value(value_t& value, expr_t& expr) +void auto_xact_t::extend_xact(xact_base_t& xact, bool post_handler) { - if (xdata_ && xdata_->has_flags(XACT_EXT_COMPOUND)) { - add_or_set_value(value, xdata_->value); - } - else if (! xdata_ || ! xdata_->has_flags(XACT_EXT_NO_TOTAL)) { - bind_scope_t bound_scope(*expr.get_context(), *this); - add_or_set_value(value, expr.calc(bound_scope)); + posts_list initial_posts(xact.posts.begin(), xact.posts.end()); + + foreach (post_t * initial_post, initial_posts) { + if (! initial_post->has_flags(POST_AUTO) && predicate(*initial_post)) { + foreach (post_t * post, posts) { + amount_t amt; + assert(post->amount); + if (! post->amount.commodity()) { + if (! post_handler) + continue; + assert(initial_post->amount); + 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->beg_line << ": " + << "amount " << initial_post->amount << " (precision " + << initial_post->amount.precision() << ")"); + + if (initial_post->amount.keep_precision()) + DEBUG("xact.extend", " precision is kept"); + + DEBUG("xact.extend", + "Posting on line " << post->beg_line << ": " + << "amount " << post->amount << ", amt " << amt + << " (precision " << post->amount.precision() + << " != " << amt.precision() << ")"); + + if (post->amount.keep_precision()) + DEBUG("xact.extend", " precision is kept"); + if (amt.keep_precision()) + DEBUG("xact.extend", " amt precision is kept"); + } + + 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(POST_AUTO); + + xact.add_post(new_post); + } + } } } +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 |