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