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 "account.h"
35 #include "post.h"
36 #include "xact.h"
37
38 namespace ledger {
39
~account_t()40 account_t::~account_t()
41 {
42 TRACE_DTOR(account_t);
43
44 foreach (accounts_map::value_type& pair, accounts) {
45 if (! pair.second->has_flags(ACCOUNT_TEMP) ||
46 has_flags(ACCOUNT_TEMP)) {
47 checked_delete(pair.second);
48 }
49 }
50 }
51
find_account(const string & acct_name,const bool auto_create)52 account_t * account_t::find_account(const string& acct_name,
53 const bool auto_create)
54 {
55 accounts_map::const_iterator i = accounts.find(acct_name);
56 if (i != accounts.end())
57 return (*i).second;
58
59 char buf[8192];
60
61 string::size_type sep = acct_name.find(':');
62 assert(sep < 256|| sep == string::npos);
63
64 const char * first, * rest;
65 if (sep == string::npos) {
66 first = acct_name.c_str();
67 rest = NULL;
68 } else {
69 std::strncpy(buf, acct_name.c_str(), sep);
70 buf[sep] = '\0';
71
72 first = buf;
73 rest = acct_name.c_str() + sep + 1;
74 }
75
76 account_t * account;
77
78 i = accounts.find(first);
79 if (i == accounts.end()) {
80 if (! auto_create)
81 return NULL;
82
83 account = new account_t(this, first);
84
85 // An account created within a temporary or generated account is itself
86 // temporary or generated, so that the whole tree has the same status.
87 if (has_flags(ACCOUNT_TEMP))
88 account->add_flags(ACCOUNT_TEMP);
89 if (has_flags(ACCOUNT_GENERATED))
90 account->add_flags(ACCOUNT_GENERATED);
91
92 #if DEBUG_ON
93 std::pair<accounts_map::iterator, bool> result =
94 #endif
95 accounts.insert(accounts_map::value_type(first, account));
96 #if DEBUG_ON
97 assert(result.second);
98 #endif
99 } else {
100 account = (*i).second;
101 }
102
103 if (rest)
104 account = account->find_account(rest, auto_create);
105
106 return account;
107 }
108
109 namespace {
find_account_re_(account_t * account,const mask_t & regexp)110 account_t * find_account_re_(account_t * account, const mask_t& regexp)
111 {
112 if (regexp.match(account->fullname()))
113 return account;
114
115 foreach (accounts_map::value_type& pair, account->accounts)
116 if (account_t * a = find_account_re_(pair.second, regexp))
117 return a;
118
119 return NULL;
120 }
121 }
122
find_account_re(const string & regexp)123 account_t * account_t::find_account_re(const string& regexp)
124 {
125 return find_account_re_(this, mask_t(regexp));
126 }
127
add_post(post_t * post)128 void account_t::add_post(post_t * post)
129 {
130 posts.push_back(post);
131
132 // Adding a new post changes the possible totals that may have been
133 // computed before.
134 if (xdata_) {
135 xdata_->self_details.gathered = false;
136 xdata_->self_details.calculated = false;
137 xdata_->family_details.gathered = false;
138 xdata_->family_details.calculated = false;
139 if (! xdata_->family_details.total.is_null()) {
140 xdata_->family_details.total = ledger::value_t();
141 }
142 account_t *ancestor = this;
143 while (ancestor->parent) {
144 ancestor = ancestor->parent;
145 if (ancestor->has_xdata()) {
146 xdata_t &xdata = ancestor->xdata();
147 xdata.family_details.gathered = false;
148 xdata.family_details.calculated = false;
149 xdata.family_details.total = ledger::value_t();
150 }
151 }
152 }
153 }
154
add_deferred_post(const string & uuid,post_t * post)155 void account_t::add_deferred_post(const string& uuid, post_t * post)
156 {
157 if (! deferred_posts)
158 deferred_posts = deferred_posts_map_t();
159
160 deferred_posts_map_t::iterator i = deferred_posts->find(uuid);
161 if (i == deferred_posts->end()) {
162 posts_list lst;
163 lst.push_back(post);
164 deferred_posts->insert(deferred_posts_map_t::value_type(uuid, lst));
165 } else {
166 (*i).second.push_back(post);
167 }
168 }
169
apply_deferred_posts()170 void account_t::apply_deferred_posts()
171 {
172 if (deferred_posts) {
173 foreach (deferred_posts_map_t::value_type& pair, *deferred_posts) {
174 foreach (post_t * post, pair.second)
175 post->account->add_post(post);
176 }
177 deferred_posts = none;
178 }
179
180 // Also apply in child accounts
181 foreach (const accounts_map::value_type& pair, accounts)
182 pair.second->apply_deferred_posts();
183 }
184
remove_post(post_t * post)185 bool account_t::remove_post(post_t * post)
186 {
187 // It's possible that 'post' wasn't yet in this account, but try to
188 // remove it anyway. This can happen if there is an error during
189 // parsing, when the posting knows what it's account is, but
190 // xact_t::finalize has not yet added that posting to the account.
191 posts.remove(post);
192 post->account = NULL;
193 return true;
194 }
195
fullname() const196 string account_t::fullname() const
197 {
198 if (! _fullname.empty()) {
199 return _fullname;
200 } else {
201 const account_t * first = this;
202 string fullname = name;
203
204 while (first->parent) {
205 first = first->parent;
206 if (! first->name.empty())
207 fullname = first->name + ":" + fullname;
208 }
209
210 _fullname = fullname;
211
212 return fullname;
213 }
214 }
215
partial_name(bool flat) const216 string account_t::partial_name(bool flat) const
217 {
218 string pname = name;
219
220 for (const account_t * acct = parent;
221 acct && acct->parent;
222 acct = acct->parent) {
223 if (! flat) {
224 std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
225 assert(count > 0);
226 if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
227 break;
228 }
229 pname = acct->name + ":" + pname;
230 }
231 return pname;
232 }
233
operator <<(std::ostream & out,const account_t & account)234 std::ostream& operator<<(std::ostream& out, const account_t& account)
235 {
236 out << account.fullname();
237 return out;
238 }
239
240 namespace {
get_partial_name(call_scope_t & args)241 value_t get_partial_name(call_scope_t& args)
242 {
243 return string_value(args.context<account_t>()
244 .partial_name(args.has<bool>(0) &&
245 args.get<bool>(0)));
246 }
247
get_account(call_scope_t & args)248 value_t get_account(call_scope_t& args) { // this gets the name
249 account_t& account(args.context<account_t>());
250 if (args.has<string>(0)) {
251 account_t * acct = account.parent;
252 for (; acct && acct->parent; acct = acct->parent) ;
253 if (args[0].is_string())
254 return scope_value(acct->find_account(args.get<string>(0), false));
255 else if (args[0].is_mask())
256 return scope_value(acct->find_account_re(args.get<mask_t>(0).str()));
257 else
258 return NULL_VALUE;
259 }
260 else if (args.type_context() == value_t::SCOPE) {
261 return scope_value(&account);
262 }
263 else {
264 return string_value(account.fullname());
265 }
266 }
267
get_account_base(account_t & account)268 value_t get_account_base(account_t& account) {
269 return string_value(account.name);
270 }
271
get_amount(account_t & account)272 value_t get_amount(account_t& account) {
273 return SIMPLIFIED_VALUE_OR_ZERO(account.amount());
274 }
275
get_total(account_t & account)276 value_t get_total(account_t& account) {
277 return SIMPLIFIED_VALUE_OR_ZERO(account.total());
278 }
279
get_subcount(account_t & account)280 value_t get_subcount(account_t& account) {
281 return long(account.self_details().posts_count);
282 }
283
get_count(account_t & account)284 value_t get_count(account_t& account) {
285 return long(account.family_details().posts_count);
286 }
get_cost(account_t &)287 value_t get_cost(account_t&) {
288 throw_(calc_error, _("An account does not have a 'cost' value"));
289 return false;
290 }
291
get_depth(account_t & account)292 value_t get_depth(account_t& account) {
293 return long(account.depth);
294 }
295
get_note(account_t & account)296 value_t get_note(account_t& account) {
297 return account.note ? string_value(*account.note) : NULL_VALUE;
298 }
299
ignore(account_t &)300 value_t ignore(account_t&) {
301 return false;
302 }
303
get_true(account_t &)304 value_t get_true(account_t&) {
305 return true;
306 }
307
get_addr(account_t & account)308 value_t get_addr(account_t& account) {
309 return long(&account);
310 }
311
get_depth_parent(account_t & account)312 value_t get_depth_parent(account_t& account)
313 {
314 std::size_t depth = 0;
315 for (const account_t * acct = account.parent;
316 acct && acct->parent;
317 acct = acct->parent) {
318 std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
319 assert(count > 0);
320 if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
321 depth++;
322 }
323 return long(depth);
324 }
325
get_depth_spacer(account_t & account)326 value_t get_depth_spacer(account_t& account)
327 {
328 std::size_t depth = 0;
329 for (const account_t * acct = account.parent;
330 acct && acct->parent;
331 acct = acct->parent) {
332 std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
333 assert(count > 0);
334 if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
335 depth++;
336 }
337
338 std::ostringstream out;
339 for (std::size_t i = 0; i < depth; i++)
340 out << " ";
341
342 return string_value(out.str());
343 }
344
get_latest_cleared(account_t & account)345 value_t get_latest_cleared(account_t& account)
346 {
347 return account.self_details().latest_cleared_post;
348 }
349
get_earliest(account_t & account)350 value_t get_earliest(account_t& account)
351 {
352 return account.self_details().earliest_post;
353 }
get_earliest_checkin(account_t & account)354 value_t get_earliest_checkin(account_t& account)
355 {
356 return (! account.self_details().earliest_checkin.is_not_a_date_time() ?
357 value_t(account.self_details().earliest_checkin) : NULL_VALUE);
358 }
359
get_latest(account_t & account)360 value_t get_latest(account_t& account)
361 {
362 return account.self_details().latest_post;
363 }
get_latest_checkout(account_t & account)364 value_t get_latest_checkout(account_t& account)
365 {
366 return (! account.self_details().latest_checkout.is_not_a_date_time() ?
367 value_t(account.self_details().latest_checkout) : NULL_VALUE);
368 }
get_latest_checkout_cleared(account_t & account)369 value_t get_latest_checkout_cleared(account_t& account)
370 {
371 return account.self_details().latest_checkout_cleared;
372 }
373
374 template <value_t (*Func)(account_t&)>
get_wrapper(call_scope_t & args)375 value_t get_wrapper(call_scope_t& args) {
376 return (*Func)(args.context<account_t>());
377 }
378
get_parent(account_t & account)379 value_t get_parent(account_t& account) {
380 return scope_value(account.parent);
381 }
382
fn_any(call_scope_t & args)383 value_t fn_any(call_scope_t& args)
384 {
385 account_t& account(args.context<account_t>());
386 expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
387
388 foreach (post_t * p, account.posts) {
389 bind_scope_t bound_scope(args, *p);
390 if (expr->calc(bound_scope, args.locus, args.depth).to_boolean())
391 return true;
392 }
393 return false;
394 }
395
fn_all(call_scope_t & args)396 value_t fn_all(call_scope_t& args)
397 {
398 account_t& account(args.context<account_t>());
399 expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
400
401 foreach (post_t * p, account.posts) {
402 bind_scope_t bound_scope(args, *p);
403 if (! expr->calc(bound_scope, args.locus, args.depth).to_boolean())
404 return false;
405 }
406 return true;
407 }
408 }
409
lookup(const symbol_t::kind_t kind,const string & fn_name)410 expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
411 const string& fn_name)
412 {
413 if (kind != symbol_t::FUNCTION)
414 return NULL;
415
416 switch (fn_name[0]) {
417 case 'a':
418 if (fn_name[1] == '\0' || fn_name == "amount")
419 return WRAP_FUNCTOR(get_wrapper<&get_amount>);
420 else if (fn_name == "account")
421 return WRAP_FUNCTOR(&get_account);
422 else if (fn_name == "account_base")
423 return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
424 else if (fn_name == "addr")
425 return WRAP_FUNCTOR(get_wrapper<&get_addr>);
426 else if (fn_name == "any")
427 return WRAP_FUNCTOR(&fn_any);
428 else if (fn_name == "all")
429 return WRAP_FUNCTOR(&fn_all);
430 break;
431
432 case 'c':
433 if (fn_name == "count")
434 return WRAP_FUNCTOR(get_wrapper<&get_count>);
435 else if (fn_name == "cost")
436 return WRAP_FUNCTOR(get_wrapper<&get_cost>);
437 break;
438
439 case 'd':
440 if (fn_name == "depth")
441 return WRAP_FUNCTOR(get_wrapper<&get_depth>);
442 else if (fn_name == "depth_parent")
443 return WRAP_FUNCTOR(get_wrapper<&get_depth_parent>);
444 else if (fn_name == "depth_spacer")
445 return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>);
446 break;
447
448 case 'e':
449 if (fn_name == "earliest")
450 return WRAP_FUNCTOR(get_wrapper<&get_earliest>);
451 else if (fn_name == "earliest_checkin")
452 return WRAP_FUNCTOR(get_wrapper<&get_earliest_checkin>);
453 break;
454
455 case 'i':
456 if (fn_name == "is_account")
457 return WRAP_FUNCTOR(get_wrapper<&get_true>);
458 else if (fn_name == "is_index")
459 return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
460 break;
461
462 case 'l':
463 if (fn_name[1] == '\0')
464 return WRAP_FUNCTOR(get_wrapper<&get_depth>);
465 else if (fn_name == "latest_cleared")
466 return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>);
467 else if (fn_name == "latest")
468 return WRAP_FUNCTOR(get_wrapper<&get_latest>);
469 else if (fn_name == "latest_checkout")
470 return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout>);
471 else if (fn_name == "latest_checkout_cleared")
472 return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout_cleared>);
473 break;
474
475 case 'n':
476 if (fn_name[1] == '\0')
477 return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
478 else if (fn_name == "note")
479 return WRAP_FUNCTOR(get_wrapper<&get_note>);
480 break;
481
482 case 'p':
483 if (fn_name == "partial_account")
484 return WRAP_FUNCTOR(get_partial_name);
485 else if (fn_name == "parent")
486 return WRAP_FUNCTOR(get_wrapper<&get_parent>);
487 break;
488
489 case 's':
490 if (fn_name == "subcount")
491 return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
492 break;
493
494 case 't':
495 if (fn_name == "total")
496 return WRAP_FUNCTOR(get_wrapper<&get_total>);
497 break;
498
499 case 'u':
500 if (fn_name == "use_direct_amount")
501 return WRAP_FUNCTOR(get_wrapper<&ignore>);
502 break;
503
504 case 'N':
505 if (fn_name[1] == '\0')
506 return WRAP_FUNCTOR(get_wrapper<&get_count>);
507 break;
508
509 case 'O':
510 if (fn_name[1] == '\0')
511 return WRAP_FUNCTOR(get_wrapper<&get_total>);
512 break;
513 }
514
515 return NULL;
516 }
517
valid() const518 bool account_t::valid() const
519 {
520 if (depth > 256) {
521 DEBUG("ledger.validate", "account_t: depth > 256");
522 return false;
523 }
524
525 foreach (const accounts_map::value_type& pair, accounts) {
526 if (this == pair.second) {
527 DEBUG("ledger.validate", "account_t: parent refers to itself!");
528 return false;
529 }
530
531 if (! pair.second->valid()) {
532 DEBUG("ledger.validate", "account_t: child not valid");
533 return false;
534 }
535 }
536
537 return true;
538 }
539
children_with_xdata() const540 bool account_t::children_with_xdata() const
541 {
542 foreach (const accounts_map::value_type& pair, accounts)
543 if (pair.second->has_xdata() ||
544 pair.second->children_with_xdata())
545 return true;
546
547 return false;
548 }
549
children_with_flags(xdata_t::flags_t flags) const550 std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const
551 {
552 std::size_t count = 0;
553 bool grandchildren_visited = false;
554
555 foreach (const accounts_map::value_type& pair, accounts)
556 if (pair.second->has_xflags(flags) ||
557 pair.second->children_with_flags(flags))
558 count++;
559
560 // Although no immediately children were visited, if any progeny at all were
561 // visited, it counts as one.
562 if (count == 0 && grandchildren_visited)
563 count = 1;
564
565 return count;
566 }
567
568 account_t::xdata_t::details_t&
operator +=(const details_t & other)569 account_t::xdata_t::details_t::operator+=(const details_t& other)
570 {
571 posts_count += other.posts_count;
572 posts_virtuals_count += other.posts_virtuals_count;
573 posts_cleared_count += other.posts_cleared_count;
574 posts_last_7_count += other.posts_last_7_count;
575 posts_last_30_count += other.posts_last_30_count;
576 posts_this_month_count += other.posts_this_month_count;
577
578 if (! is_valid(earliest_post) ||
579 (is_valid(other.earliest_post) &&
580 other.earliest_post < earliest_post))
581 earliest_post = other.earliest_post;
582 if (! is_valid(earliest_cleared_post) ||
583 (is_valid(other.earliest_cleared_post) &&
584 other.earliest_cleared_post < earliest_cleared_post))
585 earliest_cleared_post = other.earliest_cleared_post;
586
587 if (! is_valid(latest_post) ||
588 (is_valid(other.latest_post) &&
589 other.latest_post > latest_post))
590 latest_post = other.latest_post;
591 if (! is_valid(latest_cleared_post) ||
592 (is_valid(other.latest_cleared_post) &&
593 other.latest_cleared_post > latest_cleared_post))
594 latest_cleared_post = other.latest_cleared_post;
595
596 filenames.insert(other.filenames.begin(), other.filenames.end());
597 accounts_referenced.insert(other.accounts_referenced.begin(),
598 other.accounts_referenced.end());
599 payees_referenced.insert(other.payees_referenced.begin(),
600 other.payees_referenced.end());
601 return *this;
602 }
603
clear_xdata()604 void account_t::clear_xdata()
605 {
606 xdata_ = none;
607
608 foreach (accounts_map::value_type& pair, accounts)
609 if (! pair.second->has_flags(ACCOUNT_TEMP))
610 pair.second->clear_xdata();
611 }
612
amount(const optional<bool> real_only,const optional<expr_t &> & expr) const613 value_t account_t::amount(const optional<bool> real_only, const optional<expr_t&>& expr) const
614 {
615 DEBUG("account.amount", "real only: " << real_only);
616
617 if (xdata_ && xdata_->has_flags(ACCOUNT_EXT_VISITED)) {
618 posts_list::const_iterator i;
619 if (xdata_->self_details.last_post)
620 i = *xdata_->self_details.last_post;
621 else
622 i = posts.begin();
623
624 for (; i != posts.end(); i++) {
625 if ((*i)->xdata().has_flags(POST_EXT_VISITED)) {
626 if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) {
627 if (! (*i)->has_flags(POST_VIRTUAL)) {
628 (*i)->add_to_value(xdata_->self_details.real_total, expr);
629 }
630
631 (*i)->add_to_value(xdata_->self_details.total, expr);
632 (*i)->xdata().add_flags(POST_EXT_CONSIDERED);
633 }
634 }
635 xdata_->self_details.last_post = i;
636 }
637
638 if (xdata_->self_details.last_reported_post)
639 i = *xdata_->self_details.last_reported_post;
640 else
641 i = xdata_->reported_posts.begin();
642
643 for (; i != xdata_->reported_posts.end(); i++) {
644 if ((*i)->xdata().has_flags(POST_EXT_VISITED)) {
645 if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) {
646 if (! (*i)->has_flags(POST_VIRTUAL)) {
647 (*i)->add_to_value(xdata_->self_details.real_total, expr);
648 }
649
650 (*i)->add_to_value(xdata_->self_details.total, expr);
651 (*i)->xdata().add_flags(POST_EXT_CONSIDERED);
652 }
653 }
654 xdata_->self_details.last_reported_post = i;
655 }
656
657 if (real_only == true) {
658 return xdata_->self_details.real_total;
659 } else {
660 return xdata_->self_details.total;
661 }
662 } else {
663 return NULL_VALUE;
664 }
665 }
666
total(const optional<expr_t &> & expr) const667 value_t account_t::total(const optional<expr_t&>& expr) const
668 {
669 if (! (xdata_ && xdata_->family_details.calculated)) {
670 const_cast<account_t&>(*this).xdata().family_details.calculated = true;
671
672 value_t temp;
673 foreach (const accounts_map::value_type& pair, accounts) {
674 temp = pair.second->total(expr);
675 if (! temp.is_null())
676 add_or_set_value(xdata_->family_details.total, temp);
677 }
678
679 temp = amount(false, expr);
680 if (! temp.is_null())
681 add_or_set_value(xdata_->family_details.total, temp);
682 }
683 return xdata_->family_details.total;
684 }
685
686 const account_t::xdata_t::details_t&
self_details(bool gather_all) const687 account_t::self_details(bool gather_all) const
688 {
689 if (! (xdata_ && xdata_->self_details.gathered)) {
690 const_cast<account_t&>(*this).xdata().self_details.gathered = true;
691
692 foreach (const post_t * post, posts)
693 xdata_->self_details.update(const_cast<post_t&>(*post), gather_all);
694 }
695 return xdata_->self_details;
696 }
697
698 const account_t::xdata_t::details_t&
family_details(bool gather_all) const699 account_t::family_details(bool gather_all) const
700 {
701 if (! (xdata_ && xdata_->family_details.gathered)) {
702 const_cast<account_t&>(*this).xdata().family_details.gathered = true;
703
704 foreach (const accounts_map::value_type& pair, accounts)
705 xdata_->family_details += pair.second->family_details(gather_all);
706
707 xdata_->family_details += self_details(gather_all);
708 }
709 return xdata_->family_details;
710 }
711
update(post_t & post,bool gather_all)712 void account_t::xdata_t::details_t::update(post_t& post,
713 bool gather_all)
714 {
715 posts_count++;
716
717 if (post.has_flags(POST_VIRTUAL))
718 posts_virtuals_count++;
719
720 if (gather_all && post.pos)
721 filenames.insert(post.pos->pathname);
722
723 date_t date = post.date();
724
725 if (date.year() == CURRENT_DATE().year() &&
726 date.month() == CURRENT_DATE().month())
727 posts_this_month_count++;
728
729 if ((CURRENT_DATE() - date).days() <= 30)
730 posts_last_30_count++;
731 if ((CURRENT_DATE() - date).days() <= 7)
732 posts_last_7_count++;
733
734 if (! is_valid(earliest_post) || post.date() < earliest_post)
735 earliest_post = post.date();
736 if (! is_valid(latest_post) || post.date() > latest_post)
737 latest_post = post.date();
738
739 if (post.checkin && (earliest_checkin.is_not_a_date_time() ||
740 *post.checkin < earliest_checkin))
741 earliest_checkin = *post.checkin;
742
743 if (post.checkout && (latest_checkout.is_not_a_date_time() ||
744 *post.checkout > latest_checkout)) {
745 latest_checkout = *post.checkout;
746 latest_checkout_cleared = post.state() == item_t::CLEARED;
747 }
748
749 if (post.state() == item_t::CLEARED) {
750 posts_cleared_count++;
751
752 if (! is_valid(earliest_cleared_post) ||
753 post.date() < earliest_cleared_post)
754 earliest_cleared_post = post.date();
755 if (! is_valid(latest_cleared_post) ||
756 post.date() > latest_cleared_post)
757 latest_cleared_post = post.date();
758 }
759
760 if (gather_all) {
761 accounts_referenced.insert(post.account->fullname());
762 payees_referenced.insert(post.payee());
763 }
764 }
765
put_account(property_tree::ptree & st,const account_t & acct,function<bool (const account_t &)> pred)766 void put_account(property_tree::ptree& st, const account_t& acct,
767 function<bool(const account_t&)> pred)
768 {
769 if (pred(acct)) {
770 std::ostringstream buf;
771 buf.width(sizeof(unsigned long) * 2);
772 buf.fill('0');
773 buf << std::hex << reinterpret_cast<unsigned long>(&acct);
774
775 st.put("<xmlattr>.id", buf.str());
776
777 st.put("name", acct.name);
778 st.put("fullname", acct.fullname());
779
780 value_t total = acct.amount();
781 if (! total.is_null())
782 put_value(st.put("account-amount", ""), total);
783
784 total = acct.total();
785 if (! total.is_null())
786 put_value(st.put("account-total", ""), total);
787
788 foreach (const accounts_map::value_type& pair, acct.accounts)
789 put_account(st.add("account", ""), *pair.second, pred);
790 }
791 }
792
793 } // namespace ledger
794