1 /*
2  * Copyright (c) 2003-2018, John Wiegley.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * - Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  *
11  * - Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in the
13  *   documentation and/or other materials provided with the distribution.
14  *
15  * - Neither the name of New Artisans LLC nor the names of its
16  *   contributors may be used to endorse or promote products derived from
17  *   this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <system.hh>
33 
34 #include "xact.h"
35 #include "post.h"
36 #include "account.h"
37 #include "journal.h"
38 #include "context.h"
39 #include "format.h"
40 #include "pool.h"
41 
42 namespace ledger {
43 
xact_base_t(const xact_base_t & xact_base)44 xact_base_t::xact_base_t(const xact_base_t& xact_base)
45   : item_t(xact_base), journal(xact_base.journal)
46 {
47   TRACE_CTOR(xact_base_t, "copy");
48 }
49 
~xact_base_t()50 xact_base_t::~xact_base_t()
51 {
52   TRACE_DTOR(xact_base_t);
53 
54   if (! has_flags(ITEM_TEMP)) {
55     foreach (post_t * post, posts) {
56       // If the posting is a temporary, it will be destructed when the
57       // temporary is.
58       assert(! post->has_flags(ITEM_TEMP));
59 
60       if (post->account)
61         post->account->remove_post(post);
62       checked_delete(post);
63     }
64   }
65 }
66 
add_post(post_t * post)67 void xact_base_t::add_post(post_t * post)
68 {
69 #if !NO_ASSERTS
70   // You can add temporary postings to transactions, but not real postings to
71   // temporary transactions.
72   if (! post->has_flags(ITEM_TEMP))
73     assert(! has_flags(ITEM_TEMP));
74 #endif
75 
76   posts.push_back(post);
77 }
78 
remove_post(post_t * post)79 bool xact_base_t::remove_post(post_t * post)
80 {
81   posts.remove(post);
82   post->xact = NULL;
83   return true;
84 }
85 
has_xdata()86 bool xact_base_t::has_xdata()
87 {
88   foreach (post_t * post, posts)
89     if (post->has_xdata())
90       return true;
91 
92   return false;
93 }
94 
clear_xdata()95 void xact_base_t::clear_xdata()
96 {
97   foreach (post_t * post, posts)
98     if (! post->has_flags(ITEM_TEMP))
99       post->clear_xdata();
100 }
101 
magnitude() const102 value_t xact_base_t::magnitude() const
103 {
104   value_t halfbal = 0L;
105   foreach (const post_t * post, posts) {
106     if (post->amount.sign() > 0) {
107       if (post->cost)
108         halfbal += *post->cost;
109       else
110         halfbal += post->amount;
111     }
112   }
113   return halfbal;
114 }
115 
116 namespace {
account_ends_with_special_char(const string & name)117   inline bool account_ends_with_special_char(const string& name) {
118     string::size_type len(name.length());
119     return (std::isdigit(name[len - 1]) || name[len - 1] == ')' ||
120             name[len - 1] == '}' || name[len - 1] == ']');
121   }
122 
123   struct add_balancing_post
124   {
125     bool         first;
126     xact_base_t& xact;
127     post_t *     null_post;
128 
add_balancing_postledger::__anon4236974b0111::add_balancing_post129     explicit add_balancing_post(xact_base_t& _xact, post_t * _null_post)
130       : first(true), xact(_xact), null_post(_null_post) {
131       TRACE_CTOR(add_balancing_post, "xact_base_t&, post_t *");
132     }
add_balancing_postledger::__anon4236974b0111::add_balancing_post133     add_balancing_post(const add_balancing_post& other)
134       : first(other.first), xact(other.xact), null_post(other.null_post) {
135       TRACE_CTOR(add_balancing_post, "copy");
136     }
~add_balancing_postledger::__anon4236974b0111::add_balancing_post137     ~add_balancing_post() throw() {
138       TRACE_DTOR(add_balancing_post);
139     }
140 
operator ()ledger::__anon4236974b0111::add_balancing_post141     void operator()(const amount_t& amount) {
142       if (first) {
143         null_post->amount = amount.negated();
144         null_post->add_flags(POST_CALCULATED);
145         first = false;
146       } else {
147         unique_ptr<post_t> p(new post_t(null_post->account, amount.negated(),
148                                         null_post->flags() | ITEM_GENERATED | POST_CALCULATED));
149         p->set_state(null_post->state());
150         xact.add_post(p.release());
151       }
152     }
153   };
154 }
155 
finalize()156 bool xact_base_t::finalize()
157 {
158   // Scan through and compute the total balance for the xact.  This is used
159   // for auto-calculating the value of xacts with no cost, and the per-unit
160   // price of unpriced commodities.
161 
162   value_t  balance;
163   post_t * null_post = NULL;
164 
165   foreach (post_t * post, posts) {
166     if (! post->must_balance())
167       continue;
168 
169     amount_t& p(post->cost ? *post->cost : post->amount);
170     if (! p.is_null()) {
171       DEBUG("xact.finalize", "post must balance = " << p.reduced());
172       // If the amount was a cost, it very likely has the
173       // "keep_precision" flag set, meaning commodity display precision
174       // is ignored when displaying the amount.  We never want this set
175       // for the balance, so we must clear the flag in a temporary to
176       // avoid it propagating into the balance.
177       add_or_set_value(balance, p.keep_precision() ?
178                        p.rounded().reduced() : p.reduced());
179     }
180     else if (null_post) {
181       bool post_account_bad =
182         account_ends_with_special_char(post->account->fullname());
183       bool null_post_account_bad =
184         account_ends_with_special_char(null_post->account->fullname());
185 
186       if (post_account_bad || null_post_account_bad)
187         throw_(std::logic_error,
188                _f("Posting with null amount's account may be misspelled:\n  \"%1%\"")
189                % (post_account_bad ? post->account->fullname() :
190                    null_post->account->fullname()));
191       else
192         throw_(std::logic_error,
193                _("Only one posting with null amount allowed per transaction"));
194     }
195     else {
196       null_post = post;
197     }
198   }
199   VERIFY(balance.valid());
200 
201 #if DEBUG_ON
202   DEBUG("xact.finalize", "initial balance = " << balance);
203   DEBUG("xact.finalize", "balance is " << balance.label());
204   if (balance.is_balance())
205     DEBUG("xact.finalize", "balance commodity count = "
206           << balance.as_balance().amounts.size());
207 #endif
208 
209   // If there is only one post, balance against the default account if one has
210   // been set.
211 
212   if (journal && journal->bucket && posts.size() == 1 && ! balance.is_null()) {
213     null_post = new post_t(journal->bucket, ITEM_GENERATED);
214     null_post->_state = (*posts.begin())->_state;
215     add_post(null_post);
216   }
217 
218   if (! null_post && balance.is_balance() &&
219       balance.as_balance().amounts.size() == 2) {
220     // When an xact involves two different commodities (regardless of how
221     // many posts there are) determine the conversion ratio by dividing the
222     // total value of one commodity by the total value of the other.  This
223     // establishes the per-unit cost for this post for both commodities.
224 
225     DEBUG("xact.finalize",
226           "there were exactly two commodities, and no null post");
227 
228     bool     saw_cost = false;
229     post_t * top_post = NULL;
230 
231     foreach (post_t * post, posts) {
232       if (! post->amount.is_null() && post->must_balance()) {
233         if (post->amount.has_annotation())
234           top_post = post;
235         else if (! top_post)
236           top_post = post;
237       }
238 
239       if (post->cost && ! post->has_flags(POST_COST_CALCULATED)) {
240         saw_cost = true;
241         break;
242       }
243     }
244 
245     if (! saw_cost && top_post) {
246       const balance_t& bal(balance.as_balance());
247 
248       DEBUG("xact.finalize", "there were no costs, and a valid top_post");
249 
250       balance_t::amounts_map::const_iterator a = bal.amounts.begin();
251 
252       const amount_t * x = &(*a++).second;
253       const amount_t * y = &(*a++).second;
254 
255       if (*x && *y) {
256         if (x->commodity() != top_post->amount.commodity())
257           std::swap(x, y);
258 
259         DEBUG("xact.finalize", "primary   amount = " << *x);
260         DEBUG("xact.finalize", "secondary amount = " << *y);
261 
262         commodity_t& comm(x->commodity());
263         amount_t     per_unit_cost = (*y / *x).abs().unrounded();
264 
265         DEBUG("xact.finalize", "per_unit_cost = " << per_unit_cost);
266 
267         foreach (post_t * post, posts) {
268           const amount_t& amt(post->amount);
269 
270           if (post->must_balance() && amt.commodity() == comm) {
271             balance -= amt;
272             post->cost = per_unit_cost * amt;
273             post->add_flags(POST_COST_CALCULATED);
274             balance += *post->cost;
275 
276             DEBUG("xact.finalize", "set post->cost to = " << *post->cost);
277           }
278         }
279       }
280     }
281   }
282 
283   posts_list copy(posts);
284 
285   if (has_date()) {
286     foreach (post_t * post, copy) {
287       if (! post->cost)
288         continue;
289 
290       if (post->amount.commodity() == post->cost->commodity())
291         throw_(balance_error,
292                _("A posting's cost must be of a different commodity than its amount"));
293 
294       cost_breakdown_t breakdown =
295         commodity_pool_t::current_pool->exchange(
296           post->amount, *post->cost, false, ! post->has_flags(POST_COST_VIRTUAL),
297           datetime_t(date(), time_duration(0, 0, 0, 0)));
298 
299       if (post->amount.has_annotation() && post->amount.annotation().price) {
300         if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {
301           DEBUG("xact.finalize", "breakdown.basis_cost = " << breakdown.basis_cost);
302           DEBUG("xact.finalize", "breakdown.final_cost = " << breakdown.final_cost);
303           if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {
304             DEBUG("xact.finalize", "gain_loss = " << gain_loss);
305             gain_loss.in_place_round();
306             DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);
307             if (post->must_balance())
308               add_or_set_value(balance, gain_loss.reduced());
309 #if 0
310             account_t * account;
311             if (gain_loss.sign() > 0)
312               account = journal->find_account(_("Equity:Capital Gains"));
313             else
314               account = journal->find_account(_("Equity:Capital Losses"));
315 
316             post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);
317             p->set_state(post->state());
318             if (post->has_flags(POST_VIRTUAL)) {
319               DEBUG("xact.finalize", "gain_loss came from a virtual post");
320               p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));
321             }
322             add_post(p);
323 #else
324             *post->cost += gain_loss;
325 #endif
326             DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
327           } else {
328             DEBUG("xact.finalize", "gain_loss would have displayed as zero");
329           }
330         }
331       } else {
332         post->amount =
333           breakdown.amount.has_annotation() ?
334           amount_t(breakdown.amount,
335                    annotation_t(breakdown.amount.annotation().price,
336                                 breakdown.amount.annotation().date,
337                                 post->amount.has_annotation() ?
338                                 post->amount.annotation().tag :
339                                 breakdown.amount.annotation().tag,
340                                 breakdown.amount.annotation().value_expr)) :
341           breakdown.amount;
342         DEBUG("xact.finalize", "added breakdown, balance = " << balance);
343       }
344 
345       if (post->has_flags(POST_COST_FIXATED) &&
346           post->amount.has_annotation() && post->amount.annotation().price) {
347         DEBUG("xact.finalize", "fixating annotation price");
348         post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);
349       }
350     }
351   }
352 
353   if (null_post != NULL) {
354     // If one post has no value at all, its value will become the inverse of
355     // the rest.  If multiple commodities are involved, multiple posts are
356     // generated to balance them all.
357 
358     DEBUG("xact.finalize", "there was a null posting");
359     add_balancing_post post_adder(*this, null_post);
360 
361     if (balance.is_balance())
362       balance.as_balance_lval().map_sorted_amounts(post_adder);
363     else if (balance.is_amount())
364       post_adder(balance.as_amount_lval());
365     else if (balance.is_long())
366       post_adder(balance.to_amount());
367     else if (! balance.is_null() && ! balance.is_realzero())
368       throw_(balance_error, _("Transaction does not balance"));
369 
370     balance = NULL_VALUE;
371 
372   }
373   DEBUG("xact.finalize", "resolved balance = " << balance);
374 
375   if (! balance.is_null() && ! balance.is_zero()) {
376     add_error_context(item_context(*this, _("While balancing transaction")));
377     add_error_context(_("Unbalanced remainder is:"));
378     add_error_context(value_context(balance));
379     add_error_context(_("Amount to balance against:"));
380     add_error_context(value_context(magnitude()));
381     throw_(balance_error, _("Transaction does not balance"));
382   }
383 
384   // Add a pointer to each posting to their related accounts
385 
386   if (dynamic_cast<xact_t *>(this)) {
387     bool all_null  = true;
388     bool some_null = false;
389 
390     foreach (post_t * post, posts) {
391       assert(post->account);
392 
393       if (! post->amount.is_null()) {
394         all_null = false;
395         post->amount.in_place_reduce();
396       } else {
397         some_null = true;
398       }
399 
400       if (post->has_flags(POST_DEFERRED)) {
401         if (!post->amount.is_null())
402           post->account->add_deferred_post(id(), post);
403       } else {
404         post->account->add_post(post);
405       }
406 
407       post->xdata().add_flags(POST_EXT_VISITED);
408       post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);
409     }
410 
411     if (all_null)
412       return false;             // ignore this xact completely
413     else if (some_null)
414       throw_(balance_error,
415              _("There cannot be null amounts after balancing a transaction"));
416   }
417 
418   VERIFY(valid());
419 
420   return true;
421 }
422 
verify()423 bool xact_base_t::verify()
424 {
425   // Scan through and compute the total balance for the xact.
426 
427   value_t  balance;
428 
429   foreach (post_t * post, posts) {
430     if (! post->must_balance())
431       continue;
432 
433     amount_t& p(post->cost ? *post->cost : post->amount);
434     assert(! p.is_null());
435 
436     // If the amount was a cost, it very likely has the "keep_precision" flag
437     // set, meaning commodity display precision is ignored when displaying the
438     // amount.  We never want this set for the balance, so we must clear the
439     // flag in a temporary to avoid it propagating into the balance.
440     add_or_set_value(balance, p.keep_precision() ?
441                      p.rounded().reduced() : p.reduced());
442   }
443   VERIFY(balance.valid());
444 
445   // Now that the post list has its final form, calculate the balance once
446   // more in terms of total cost, accounting for any possible gain/loss
447   // amounts.
448 
449   foreach (post_t * post, posts) {
450     if (! post->cost)
451       continue;
452 
453     if (post->amount.commodity() == post->cost->commodity())
454       throw_(amount_error,
455              _("A posting's cost must be of a different commodity than its amount"));
456   }
457 
458   if (! balance.is_null() && ! balance.is_zero()) {
459     add_error_context(item_context(*this, _("While balancing transaction")));
460     add_error_context(_("Unbalanced remainder is:"));
461     add_error_context(value_context(balance));
462     add_error_context(_("Amount to balance against:"));
463     add_error_context(value_context(magnitude()));
464     throw_(balance_error, _("Transaction does not balance"));
465   }
466 
467   VERIFY(valid());
468 
469   return true;
470 }
471 
xact_t(const xact_t & e)472 xact_t::xact_t(const xact_t& e)
473   : xact_base_t(e), code(e.code), payee(e.payee)
474 #if DOCUMENT_MODEL
475     , data(NULL)
476 #endif
477 {
478   TRACE_CTOR(xact_t, "copy");
479 }
480 
add_post(post_t * post)481 void xact_t::add_post(post_t * post)
482 {
483   post->xact = this;
484   xact_base_t::add_post(post);
485 }
486 
487 namespace {
get_magnitude(xact_t & xact)488   value_t get_magnitude(xact_t& xact) {
489     return xact.magnitude();
490   }
491 
get_code(xact_t & xact)492   value_t get_code(xact_t& xact) {
493     if (xact.code)
494       return string_value(*xact.code);
495     else
496       return NULL_VALUE;
497   }
498 
get_payee(xact_t & xact)499   value_t get_payee(xact_t& xact) {
500     return string_value(xact.payee);
501   }
502 
503   template <value_t (*Func)(xact_t&)>
get_wrapper(call_scope_t & scope)504   value_t get_wrapper(call_scope_t& scope) {
505     return (*Func)(find_scope<xact_t>(scope));
506   }
507 
fn_any(call_scope_t & args)508   value_t fn_any(call_scope_t& args)
509   {
510     post_t& post(args.context<post_t>());
511     expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
512 
513     foreach (post_t * p, post.xact->posts) {
514       bind_scope_t bound_scope(args, *p);
515       if (expr->calc(bound_scope, args.locus, args.depth).to_boolean())
516         return true;
517     }
518     return false;
519   }
520 
fn_all(call_scope_t & args)521   value_t fn_all(call_scope_t& args)
522   {
523     post_t& post(args.context<post_t>());
524     expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
525 
526     foreach (post_t * p, post.xact->posts) {
527       bind_scope_t bound_scope(args, *p);
528       if (! expr->calc(bound_scope, args.locus, args.depth).to_boolean())
529         return false;
530     }
531     return true;
532   }
533 }
534 
lookup(const symbol_t::kind_t kind,const string & name)535 expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
536                                 const string& name)
537 {
538   if (kind != symbol_t::FUNCTION)
539     return item_t::lookup(kind, name);
540 
541   switch (name[0]) {
542   case 'a':
543     if (name == "any")
544       return WRAP_FUNCTOR(&fn_any);
545     else if (name == "all")
546       return WRAP_FUNCTOR(&fn_all);
547     break;
548 
549   case 'c':
550     if (name == "code")
551       return WRAP_FUNCTOR(get_wrapper<&get_code>);
552     break;
553 
554   case 'm':
555     if (name == "magnitude")
556       return WRAP_FUNCTOR(get_wrapper<&get_magnitude>);
557     break;
558 
559   case 'p':
560     if (name[1] == '\0' || name == "payee")
561       return WRAP_FUNCTOR(get_wrapper<&get_payee>);
562     break;
563   }
564 
565   return item_t::lookup(kind, name);
566 }
567 
valid() const568 bool xact_t::valid() const
569 {
570   if (! _date) {
571     DEBUG("ledger.validate", "xact_t: ! _date");
572     return false;
573   }
574 
575   foreach (post_t * post, posts)
576     if (post->xact != this || ! post->valid()) {
577       DEBUG("ledger.validate", "xact_t: post not valid");
578       return false;
579     }
580 
581   return true;
582 }
583 
584 namespace {
post_pred(expr_t::ptr_op_t op,post_t & post)585   bool post_pred(expr_t::ptr_op_t op, post_t& post)
586   {
587     switch (op->kind) {
588     case expr_t::op_t::VALUE:
589       return op->as_value().to_boolean();
590 
591     case expr_t::op_t::O_MATCH:
592       if (op->left()->kind == expr_t::op_t::IDENT &&
593           op->left()->as_ident() == "account" &&
594           op->right()->kind == expr_t::op_t::VALUE &&
595           op->right()->as_value().is_mask())
596         return op->right()->as_value().as_mask()
597           .match(post.reported_account()->fullname());
598       else
599         break;
600 
601     case expr_t::op_t::O_EQ:
602       return post_pred(op->left(), post) == post_pred(op->right(), post);
603 
604     case expr_t::op_t::O_NOT:
605       return ! post_pred(op->left(), post);
606 
607     case expr_t::op_t::O_AND:
608       return post_pred(op->left(), post) && post_pred(op->right(), post);
609 
610     case expr_t::op_t::O_OR:
611       return post_pred(op->left(), post) || post_pred(op->right(), post);
612 
613     case expr_t::op_t::O_QUERY:
614       if (post_pred(op->left(), post))
615         return post_pred(op->right()->left(), post);
616       else
617         return post_pred(op->right()->right(), post);
618 
619     default:
620       break;
621     }
622 
623     throw_(calc_error, _("Unhandled operator"));
624     return false;
625   }
626 }
627 
apply_format(const string & str,scope_t & scope)628 static string apply_format(const string& str, scope_t& scope)
629 {
630   if (contains(str, "%(")) {
631     format_t str_format(str);
632     std::ostringstream buf;
633     buf << str_format(scope);
634     return buf.str();
635   } else {
636     return str;
637   }
638 }
639 
extend_xact(xact_base_t & xact,parse_context_t & context)640 void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)
641 {
642   posts_list initial_posts(xact.posts.begin(), xact.posts.end());
643 
644   try {
645 
646   bool needs_further_verification = false;
647 
648   foreach (post_t * initial_post, initial_posts) {
649     if (initial_post->has_flags(ITEM_GENERATED))
650       continue;
651 
652     bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);
653 
654     bool matches_predicate = false;
655     if (try_quick_match) {
656       try {
657         bool found_memoized_result = false;
658         if (! memoized_results.empty()) {
659           std::map<string, bool>::iterator i =
660             memoized_results.find(initial_post->account->fullname());
661           if (i != memoized_results.end()) {
662             found_memoized_result = true;
663             matches_predicate = (*i).second;
664           }
665         }
666 
667         // Since the majority of people who use automated transactions simply
668         // match against account names, try using a *much* faster version of
669         // the predicate evaluator.
670         if (! found_memoized_result) {
671           matches_predicate = post_pred(predicate.get_op(), *initial_post);
672           memoized_results.insert
673             (std::pair<string, bool>(initial_post->account->fullname(),
674                                      matches_predicate));
675         }
676       }
677       catch (...) {
678         DEBUG("xact.extend.fail",
679               "The quick matcher failed, going back to regular eval");
680         try_quick_match   = false;
681         matches_predicate = predicate(bound_scope);
682       }
683     } else {
684       matches_predicate = predicate(bound_scope);
685     }
686 
687     if (matches_predicate) {
688       if (deferred_notes) {
689         foreach (deferred_tag_data_t& data, *deferred_notes) {
690           if (data.apply_to_post == NULL)
691             initial_post->append_note(
692               apply_format(data.tag_data, bound_scope).c_str(),
693               bound_scope, data.overwrite_existing);
694         }
695       }
696 
697       if (check_exprs) {
698         foreach (expr_t::check_expr_pair& pair, *check_exprs) {
699           if (pair.second == expr_t::EXPR_GENERAL) {
700             pair.first.calc(bound_scope);
701           }
702           else if (! pair.first.calc(bound_scope).to_boolean()) {
703             if (pair.second == expr_t::EXPR_ASSERTION)
704               throw_(parse_error,
705                      _f("Transaction assertion failed: %1%") % pair.first);
706             else
707               context.warning(_f("Transaction check failed: %1%") % pair.first);
708           }
709         }
710       }
711 
712       foreach (post_t * post, posts) {
713         amount_t post_amount;
714         if (post->amount.is_null()) {
715           if (! post->amount_expr)
716             throw_(amount_error,
717                    _("Automated transaction's posting has no amount"));
718 
719           value_t result(post->amount_expr->calc(bound_scope));
720           if (result.is_long()) {
721             post_amount = result.to_amount();
722           } else {
723             if (! result.is_amount())
724               throw_(amount_error,
725                      _("Amount expressions must result in a simple amount"));
726             post_amount = result.as_amount();
727           }
728         } else {
729           post_amount = post->amount;
730         }
731 
732         amount_t amt;
733         if (! post_amount.commodity())
734           amt = initial_post->amount * post_amount;
735         else
736           amt = post_amount;
737 
738 #if DEBUG_ON
739         IF_DEBUG("xact.extend") {
740           DEBUG("xact.extend",
741                 "Initial post on line " << initial_post->pos->beg_line << ": "
742                 << "amount " << initial_post->amount << " (precision "
743                 << initial_post->amount.precision() << ")");
744 
745           if (initial_post->amount.keep_precision())
746             DEBUG("xact.extend", "  precision is kept");
747 
748           DEBUG("xact.extend",
749                 "Posting on line " << post->pos->beg_line << ": "
750                 << "amount " << post_amount << ", amt " << amt
751                 << " (precision " << post_amount.precision()
752                 << " != " << amt.precision() << ")");
753 
754           if (post_amount.keep_precision())
755             DEBUG("xact.extend", "  precision is kept");
756           if (amt.keep_precision())
757             DEBUG("xact.extend", "  amt precision is kept");
758         }
759 #endif // DEBUG_ON
760 
761         account_t * account  = post->account;
762         string fullname = account->fullname();
763         assert(! fullname.empty());
764 
765         if (contains(fullname, "$account")) {
766           fullname = regex_replace(fullname, regex("\\$account\\>"),
767                                    initial_post->account->fullname());
768           while (account->parent)
769             account = account->parent;
770           account = account->find_account(fullname);
771         }
772         else if (contains(fullname, "%(")) {
773           format_t account_name(fullname);
774           std::ostringstream buf;
775           buf << account_name(bound_scope);
776           while (account->parent)
777             account = account->parent;
778           account = account->find_account(buf.str());
779         }
780 
781         // Copy over details so that the resulting post is a mirror of
782         // the automated xact's one.
783         post_t * new_post = new post_t(account, amt);
784         new_post->copy_details(*post);
785 
786         // A Cleared transaction implies all of its automatic posting are cleared
787         // CPR 2012/10/23
788         if (xact.state() == item_t::CLEARED) {
789           DEBUG("xact.extend.cleared", "CLEARED");
790           new_post->set_state(item_t::CLEARED);
791         }
792 
793         new_post->add_flags(ITEM_GENERATED);
794         new_post->account =
795           journal->register_account(account->fullname(), new_post,
796                                     journal->master);
797 
798         if (deferred_notes) {
799           foreach (deferred_tag_data_t& data, *deferred_notes) {
800             if (! data.apply_to_post || data.apply_to_post == post) {
801               new_post->append_note(
802                 apply_format(data.tag_data, bound_scope).c_str(),
803                 bound_scope, data.overwrite_existing);
804             }
805           }
806         }
807 
808         extend_post(*new_post, *journal);
809 
810         xact.add_post(new_post);
811         new_post->account->add_post(new_post);
812 
813         // Add flags so this post updates the account balance
814         new_post->xdata().add_flags(POST_EXT_VISITED);
815         new_post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);
816 
817         if (new_post->must_balance())
818           needs_further_verification = true;
819       }
820     }
821   }
822 
823   if (needs_further_verification)
824     xact.verify();
825 
826   }
827   catch (const std::exception&) {
828     add_error_context(item_context(*this, _("While applying automated transaction")));
829     add_error_context(item_context(xact, _("While extending transaction")));
830     throw;
831   }
832 }
833 
put_xact(property_tree::ptree & st,const xact_t & xact)834 void put_xact(property_tree::ptree& st, const xact_t& xact)
835 {
836   if (xact.state() == item_t::CLEARED)
837     st.put("<xmlattr>.state", "cleared");
838   else if (xact.state() == item_t::PENDING)
839     st.put("<xmlattr>.state", "pending");
840 
841   if (xact.has_flags(ITEM_GENERATED))
842     st.put("<xmlattr>.generated", "true");
843 
844   if (xact._date)
845     put_date(st.put("date", ""), *xact._date);
846   if (xact._date_aux)
847     put_date(st.put("aux-date", ""), *xact._date_aux);
848 
849   if (xact.code)
850     st.put("code", *xact.code);
851 
852   st.put("payee", xact.payee);
853 
854   if (xact.note)
855     st.put("note", *xact.note);
856 
857   if (xact.metadata)
858     put_metadata(st.put("metadata", ""),  *xact.metadata);
859 }
860 
861 } // namespace ledger
862