text: /*
   * Copyright (c) 2003-2018, 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 <system.hh>
  
  #include "xact.h"
  #include "post.h"
  #include "account.h"
  #include "journal.h"
  #include "context.h"
  #include "format.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));
  
        if (post->account)
          post->account->remove_post(post);
        checked_delete(post);
      }
    }
  }
  
  void xact_base_t::add_post(post_t * post)
  {
  #if !NO_ASSERTS
    // 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));
  #endif
  
    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::has_xdata()
  {
    foreach (post_t * post, posts)
      if (post->has_xdata())
        return true;
  
    return false;
  }
  
  void xact_base_t::clear_xdata()
  {
    foreach (post_t * post, posts)
      if (! post->has_flags(ITEM_TEMP))
        post->clear_xdata();
  }
  
  value_t xact_base_t::magnitude() const
  {
    value_t halfbal = 0L;
    foreach (const post_t * post, posts) {
      if (post->amount.sign() > 0) {
        if (post->cost)
          halfbal += *post->cost;
        else
          halfbal += post->amount;
      }
    }
    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(),
                                          null_post->flags() | 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
    // 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 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) {
        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,
                 _f("Posting with null amount's account may be misspelled:\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;
      }
    }
    VERIFY(balance.valid());
  
  #if 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->bucket && posts.size() == 1 && ! balance.is_null()) {
      null_post = new post_t(journal->bucket, ITEM_GENERATED);
      null_post->_state = (*posts.begin())->_state;
      add_post(null_post);
    }
  
    if (! null_post && 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, and no null post");
  
      bool     saw_cost = false;
      post_t * top_post = NULL;
  
      foreach (post_t * post, posts) {
        if (! post->amount.is_null() && post->must_balance()) {
          if (post->amount.has_annotation())
            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 && *y) {
          if (x->commodity() != top_post->amount.commodity())
            std::swap(x, y);
  
          DEBUG("xact.finalize", "primary   amount = " << *x);
          DEBUG("xact.finalize", "secondary amount = " << *y);
  
          commodity_t& comm(x->commodity());
          amount_t     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);
            }
          }
        }
      }
    }
  
    posts_list copy(posts);
  
    if (has_date()) {
      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 =
          commodity_pool_t::current_pool->exchange(
            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();
              DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);
              if (post->must_balance())
                add_or_set_value(balance, gain_loss.reduced());
  #if 0
              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());
              if (post->has_flags(POST_VIRTUAL)) {
                DEBUG("xact.finalize", "gain_loss came from a virtual post");
                p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));
              }
              add_post(p);
  #else
              *post->cost += gain_loss;
  #endif
              DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
            } else {
              DEBUG("xact.finalize", "gain_loss would have displayed as zero");
            }
          }
        } else {
          post->amount =
            breakdown.amount.has_annotation() ?
            amount_t(breakdown.amount,
                     annotation_t(breakdown.amount.annotation().price,
                                  breakdown.amount.annotation().date,
                                  post->amount.has_annotation() ?
                                  post->amount.annotation().tag :
                                  breakdown.amount.annotation().tag,
                                  breakdown.amount.annotation().value_expr)) :
            breakdown.amount;
          DEBUG("xact.finalize", "added breakdown, balance = " << balance);
        }
  
        if (post->has_flags(POST_COST_FIXATED) &&
            post->amount.has_annotation() && post->amount.annotation().price) {
          DEBUG("xact.finalize", "fixating annotation price");
          post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);
        }
      }
    }
  
    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");
      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;
  
    }
    DEBUG("xact.finalize", "resolved 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));
      add_error_context(_("Amount to balance against:"));
      add_error_context(value_context(magnitude()));
      throw_(balance_error, _("Transaction does not balance"));
    }
  
    // Add a pointer to each posting to their related accounts
  
    if (dynamic_cast<xact_t *>(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;
        }
  
        if (post->has_flags(POST_DEFERRED))
            post->account->add_deferred_post(id(), post);
          else
            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;
  }
  
  bool xact_base_t::verify()
  {
    // Scan through and compute the total balance for the xact.
  
    value_t  balance;
  
    foreach (post_t * post, posts) {
      if (! post->must_balance())
        continue;
  
      amount_t& p(post->cost ? *post->cost : post->amount);
      assert(! p.is_null());
  
      // 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());
    }
    VERIFY(balance.valid());
  
    // 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.
  
    foreach (post_t * post, posts) {
      if (! post->cost)
        continue;
  
      if (post->amount.commodity() == post->cost->commodity())
        throw_(amount_error,
               _("A posting's cost must be of a different commodity than its amount"));
    }
  
    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));
      add_error_context(_("Amount to balance against:"));
      add_error_context(value_context(magnitude()));
      throw_(balance_error, _("Transaction does not balance"));
    }
  
    VERIFY(valid());
  
    return true;
  }
  
  xact_t::xact_t(const xact_t& e)
    : xact_base_t(e), code(e.code), payee(e.payee)
  #if DOCUMENT_MODEL
      , data(NULL)
  #endif
  {
    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_magnitude(xact_t& xact) {
      return xact.magnitude();
    }
  
    value_t get_code(xact_t& xact) {
      if (xact.code)
        return string_value(*xact.code);
      else
        return NULL_VALUE;
    }
  
    value_t get_payee(xact_t& xact) {
      return string_value(xact.payee);
    }
  
    template <value_t (*Func)(xact_t&)>
    value_t get_wrapper(call_scope_t& scope) {
      return (*Func)(find_scope<xact_t>(scope));
    }
  
    value_t fn_any(call_scope_t& args)
    {
      post_t& post(args.context<post_t>());
      expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
  
      foreach (post_t * p, post.xact->posts) {
        bind_scope_t bound_scope(args, *p);
        if (expr->calc(bound_scope, args.locus, args.depth).to_boolean())
          return true;
      }
      return false;
    }
  
    value_t fn_all(call_scope_t& args)
    {
      post_t& post(args.context<post_t>());
      expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
  
      foreach (post_t * p, post.xact->posts) {
        bind_scope_t bound_scope(args, *p);
        if (! expr->calc(bound_scope, args.locus, args.depth).to_boolean())
          return false;
      }
      return true;
    }
  }
  
  expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
                                  const string& name)
  {
    if (kind != symbol_t::FUNCTION)
      return item_t::lookup(kind, name);
  
    switch (name[0]) {
    case 'a':
      if (name == "any")
        return WRAP_FUNCTOR(&fn_any);
      else if (name == "all")
        return WRAP_FUNCTOR(&fn_all);
      break;
  
    case 'c':
      if (name == "code")
        return WRAP_FUNCTOR(get_wrapper<&get_code>);
      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(kind, 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;
  }
  
  namespace {
    bool post_pred(expr_t::ptr_op_t op, post_t& post)
    {
      switch (op->kind) {
      case expr_t::op_t::VALUE:
        return op->as_value().to_boolean();
  
      case expr_t::op_t::O_MATCH:
        if (op->left()->kind == expr_t::op_t::IDENT &&
            op->left()->as_ident() == "account" &&
            op->right()->kind == expr_t::op_t::VALUE &&
            op->right()->as_value().is_mask())
          return op->right()->as_value().as_mask()
            .match(post.reported_account()->fullname());
        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);
  
      case expr_t::op_t::O_AND:
        return post_pred(op->left(), post) && post_pred(op->right(), post);
  
      case expr_t::op_t::O_OR:
        return post_pred(op->left(), post) || post_pred(op->right(), post);
  
      case expr_t::op_t::O_QUERY:
        if (post_pred(op->left(), post))
          return post_pred(op->right()->left(), post);
        else
          return post_pred(op->right()->right(), post);
  
      default:
        break;
      }
  
      throw_(calc_error, _("Unhandled operator"));
      return false;
    }
  }
  
  static string apply_format(const string& str, scope_t& scope)
  {
    if (contains(str, "%(")) {
      format_t str_format(str);
      std::ostringstream buf;
      buf << str_format(scope);
      return buf.str();
    } else {
      return str;
    }
  }
  
  void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)
  {
    posts_list initial_posts(xact.posts.begin(), xact.posts.end());
  
    try {
  
    bool needs_further_verification = false;
  
    foreach (post_t * initial_post, initial_posts) {
      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 {
          bool found_memoized_result = false;
          if (! memoized_results.empty()) {
            std::map<string, bool>::iterator i =
              memoized_results.find(initial_post->account->fullname());
            if (i != memoized_results.end()) {
              found_memoized_result = true;
              matches_predicate = (*i).second;
            }
          }
  
          // Since the majority of people who use automated transactions simply
          // match against account names, try using a *much* faster version of
          // the predicate evaluator.
          if (! found_memoized_result) {
            matches_predicate = post_pred(predicate.get_op(), *initial_post);
            memoized_results.insert
              (std::pair<string, bool>(initial_post->account->fullname(),
                                       matches_predicate));
          }
        }
        catch (...) {
          DEBUG("xact.extend.fail",
                "The quick matcher failed, going back to regular eval");
          try_quick_match   = false;
          matches_predicate = predicate(bound_scope);
        }
      } else {
        matches_predicate = predicate(bound_scope);
      }
  
      if (matches_predicate) {
        if (deferred_notes) {
          foreach (deferred_tag_data_t& data, *deferred_notes) {
            if (data.apply_to_post == NULL)
              initial_post->append_note(
                apply_format(data.tag_data, bound_scope).c_str(),
                bound_scope, data.overwrite_existing);
          }
        }
  
        if (check_exprs) {
          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 == expr_t::EXPR_ASSERTION)
                throw_(parse_error,
                       _f("Transaction assertion failed: %1%") % pair.first);
              else
                context.warning(_f("Transaction check failed: %1%") % pair.first);
            }
          }
        }
  
        foreach (post_t * post, posts) {
          amount_t post_amount;
          if (post->amount.is_null()) {
            if (! post->amount_expr)
              throw_(amount_error,
                     _("Automated transaction's posting has no amount"));
  
            value_t result(post->amount_expr->calc(bound_scope));
            if (result.is_long()) {
              post_amount = result.to_amount();
            } else {
              if (! result.is_amount())
                throw_(amount_error,
                       _("Amount expressions must result in a simple amount"));
              post_amount = result.as_amount();
            }
          } else {
            post_amount = post->amount;
          }
  
          amount_t amt;
          if (! post_amount.commodity())
            amt = initial_post->amount * post_amount;
          else
            amt = post_amount;
  
  #if DEBUG_ON
          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 (initial_post->amount.keep_precision())
              DEBUG("xact.extend", "  precision is kept");
  
            DEBUG("xact.extend",
                  "Posting on line " << post->pos->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");
          }
  #endif // DEBUG_ON
  
          account_t * account  = post->account;
          string fullname = account->fullname();
          assert(! fullname.empty());
  
          if (contains(fullname, "$account")) {
            fullname = regex_replace(fullname, regex("\\$account\\>"),
                                     initial_post->account->fullname());
            while (account->parent)
              account = account->parent;
            account = account->find_account(fullname);
          }
          else if (contains(fullname, "%(")) {
            format_t account_name(fullname);
            std::ostringstream buf;
            buf << account_name(bound_scope);
            while (account->parent)
              account = account->parent;
            account = account->find_account(buf.str());
          }
  
          // 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);
  
          // A Cleared transaction implies all of its automatic posting are cleared
          // CPR 2012/10/23
          if (xact.state() == item_t::CLEARED) {
            DEBUG("xact.extend.cleared", "CLEARED");
            new_post->set_state(item_t::CLEARED);
          }
  
          new_post->add_flags(ITEM_GENERATED);
          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 || data.apply_to_post == post) {
                new_post->append_note(
                  apply_format(data.tag_data, bound_scope).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;
        }
      }
    }
  
    if (needs_further_verification)
      xact.verify();
  
    }
    catch (const std::exception&) {
      add_error_context(item_context(*this, _("While applying automated transaction")));
      add_error_context(item_context(xact, _("While extending transaction")));
      throw;
    }
  }
  
  void put_xact(property_tree::ptree& st, const xact_t& xact)
  {
    if (xact.state() == item_t::CLEARED)
      st.put("<xmlattr>.state", "cleared");
    else if (xact.state() == item_t::PENDING)
      st.put("<xmlattr>.state", "pending");
  
    if (xact.has_flags(ITEM_GENERATED))
      st.put("<xmlattr>.generated", "true");
  
    if (xact._date)
      put_date(st.put("date", ""), *xact._date);
    if (xact._date_aux)
      put_date(st.put("aux-date", ""), *xact._date_aux);
  
    if (xact.code)
      st.put("code", *xact.code);
  
    st.put("payee", xact.payee);
  
    if (xact.note)
      st.put("note", *xact.note);
  
    if (xact.metadata)
      put_metadata(st.put("metadata", ""),  *xact.metadata);
  }
  
  } // namespace ledger
  