~ubuntu-branches/ubuntu/wily/ledger/wily

« back to all changes in this revision

Viewing changes to .pc/0002-replace-sha1.cc-with-boost-uuid-details-sha1.patch/src/filters.cc

  • Committer: Package Import Robot
  • Author(s): David Bremner
  • Date: 2014-10-08 19:20:38 UTC
  • mfrom: (1.1.3) (9.1.7 sid)
  • Revision ID: package-import@ubuntu.com-20141008192038-py5cxm93rdt3x2uz
Tags: 3.1+dfsg1-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright (c) 2003-2014, 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 "filters.h"
35
 
#include "iterators.h"
36
 
#include "journal.h"
37
 
#include "report.h"
38
 
#include "compare.h"
39
 
#include "pool.h"
40
 
 
41
 
namespace ledger {
42
 
 
43
 
void post_splitter::print_title(const value_t& val)
44
 
{
45
 
  if (! report.HANDLED(no_titles)) {
46
 
    std::ostringstream buf;
47
 
    val.print(buf);
48
 
    post_chain->title(buf.str());
49
 
  }
50
 
}
51
 
 
52
 
void post_splitter::flush()
53
 
{
54
 
  foreach (value_to_posts_map::value_type& pair, posts_map) {
55
 
    preflush_func(pair.first);
56
 
 
57
 
    foreach (post_t * post, pair.second)
58
 
      (*post_chain)(*post);
59
 
 
60
 
    post_chain->flush();
61
 
    post_chain->clear();
62
 
 
63
 
    if (postflush_func)
64
 
      (*postflush_func)(pair.first);
65
 
  }
66
 
}
67
 
 
68
 
void post_splitter::operator()(post_t& post)
69
 
{
70
 
  bind_scope_t bound_scope(report, post);
71
 
  value_t      result(group_by_expr.calc(bound_scope));
72
 
 
73
 
  if (! result.is_null()) {
74
 
    value_to_posts_map::iterator i = posts_map.find(result);
75
 
    if (i != posts_map.end()) {
76
 
      (*i).second.push_back(&post);
77
 
    } else {
78
 
      std::pair<value_to_posts_map::iterator, bool> inserted
79
 
        = posts_map.insert(value_to_posts_map::value_type(result, posts_list()));
80
 
      assert(inserted.second);
81
 
      (*inserted.first).second.push_back(&post);
82
 
    }
83
 
  }
84
 
}
85
 
 
86
 
void truncate_xacts::flush()
87
 
{
88
 
  if (! posts.size())
89
 
    return;
90
 
 
91
 
  xact_t * xact = (*posts.begin())->xact;
92
 
 
93
 
  int l = 0;
94
 
  foreach (post_t * post, posts)
95
 
    if (xact != post->xact) {
96
 
      l++;
97
 
      xact = post->xact;
98
 
    }
99
 
  l++;
100
 
 
101
 
  xact = (*posts.begin())->xact;
102
 
 
103
 
  int i = 0;
104
 
  foreach (post_t * post, posts) {
105
 
    if (xact != post->xact) {
106
 
      xact = post->xact;
107
 
      i++;
108
 
    }
109
 
 
110
 
    bool print = false;
111
 
    if (head_count) {
112
 
      if (head_count > 0 && i < head_count)
113
 
        print = true;
114
 
      else if (head_count < 0 && i >= - head_count)
115
 
        print = true;
116
 
    }
117
 
 
118
 
    if (! print && tail_count) {
119
 
      if (tail_count > 0 && l - i <= tail_count)
120
 
        print = true;
121
 
      else if (tail_count < 0 && l - i > - tail_count)
122
 
        print = true;
123
 
    }
124
 
 
125
 
    if (print)
126
 
      item_handler<post_t>::operator()(*post);
127
 
  }
128
 
  posts.clear();
129
 
 
130
 
  item_handler<post_t>::flush();
131
 
}
132
 
 
133
 
void truncate_xacts::operator()(post_t& post)
134
 
{
135
 
  if (completed)
136
 
    return;
137
 
 
138
 
  if (last_xact != post.xact) {
139
 
    if (last_xact)
140
 
      xacts_seen++;
141
 
    last_xact = post.xact;
142
 
  }
143
 
 
144
 
  if (tail_count == 0 && head_count > 0 &&
145
 
      static_cast<int>(xacts_seen) >= head_count) {
146
 
    flush();
147
 
    completed = true;
148
 
    return;
149
 
  }
150
 
 
151
 
  posts.push_back(&post);
152
 
}
153
 
 
154
 
void sort_posts::post_accumulated_posts()
155
 
{
156
 
  std::stable_sort(posts.begin(), posts.end(),
157
 
                   compare_items<post_t>(sort_order));
158
 
 
159
 
  foreach (post_t * post, posts) {
160
 
    post->xdata().drop_flags(POST_EXT_SORT_CALC);
161
 
    item_handler<post_t>::operator()(*post);
162
 
  }
163
 
 
164
 
  posts.clear();
165
 
}
166
 
 
167
 
namespace {
168
 
  void split_string(const string& str, const char ch,
169
 
                    std::list<string>& strings)
170
 
  {
171
 
    const char * b = str.c_str();
172
 
    for (const char * p = b; *p; p++) {
173
 
      if (*p == ch) {
174
 
        strings.push_back(string(b, static_cast<std::string::size_type>(p - b)));
175
 
        b = p + 1;
176
 
      }
177
 
    }
178
 
    strings.push_back(string(b));
179
 
  }
180
 
 
181
 
  account_t * create_temp_account_from_path(std::list<string>& account_names,
182
 
                                            temporaries_t&     temps,
183
 
                                            account_t *        master)
184
 
  {
185
 
    account_t * new_account = NULL;
186
 
    foreach (const string& name, account_names) {
187
 
      if (new_account) {
188
 
        new_account = new_account->find_account(name);
189
 
      } else {
190
 
        new_account = master->find_account(name, false);
191
 
        if (! new_account)
192
 
          new_account = &temps.create_account(name, master);
193
 
      }
194
 
    }
195
 
 
196
 
    assert(new_account != NULL);
197
 
    return new_account;
198
 
  }
199
 
}
200
 
 
201
 
void anonymize_posts::render_commodity(amount_t& amt)
202
 
{
203
 
  commodity_t& comm(amt.commodity());
204
 
 
205
 
  std::size_t id;
206
 
  bool newly_added = false;
207
 
 
208
 
  commodity_index_map::iterator i = comms.find(&comm);
209
 
  if (i == comms.end()) {
210
 
    id = next_comm_id++;
211
 
    newly_added = true;
212
 
    comms.insert(commodity_index_map::value_type(&comm, id));
213
 
  } else {
214
 
    id = (*i).second;
215
 
  }
216
 
 
217
 
  std::ostringstream buf;
218
 
  do {
219
 
    buf << static_cast<char>('A' + (id % 26));
220
 
    id /= 26;
221
 
  }
222
 
  while (id > 0);
223
 
 
224
 
  if (amt.has_annotation())
225
 
    amt.set_commodity
226
 
      (*commodity_pool_t::current_pool->find_or_create(buf.str(),
227
 
                                                       amt.annotation()));
228
 
  else
229
 
    amt.set_commodity
230
 
      (*commodity_pool_t::current_pool->find_or_create(buf.str()));
231
 
 
232
 
  if (newly_added) {
233
 
    amt.commodity().set_flags(comm.flags());
234
 
    amt.commodity().set_precision(comm.precision());
235
 
  }
236
 
}
237
 
 
238
 
void anonymize_posts::operator()(post_t& post)
239
 
{
240
 
  SHA1         sha;
241
 
  unsigned int message_digest[5];
242
 
  bool         copy_xact_details = false;
243
 
 
244
 
  if (last_xact != post.xact) {
245
 
    temps.copy_xact(*post.xact);
246
 
    last_xact = post.xact;
247
 
    copy_xact_details = true;
248
 
  }
249
 
  xact_t& xact = temps.last_xact();
250
 
  xact.code = none;
251
 
 
252
 
  if (copy_xact_details) {
253
 
    xact.copy_details(*post.xact);
254
 
 
255
 
    std::ostringstream buf;
256
 
    buf << reinterpret_cast<uintmax_t>(post.xact->payee.c_str())
257
 
        << integer_gen() << post.xact->payee.c_str();
258
 
 
259
 
    sha.Reset();
260
 
    sha << buf.str().c_str();
261
 
    sha.Result(message_digest);
262
 
 
263
 
    xact.payee = to_hex(message_digest);
264
 
    xact.note  = none;
265
 
  } else {
266
 
    xact.journal = post.xact->journal;
267
 
  }
268
 
 
269
 
  std::list<string> account_names;
270
 
 
271
 
  for (account_t * acct = post.account;
272
 
       acct;
273
 
       acct = acct->parent) {
274
 
    std::ostringstream buf;
275
 
    buf << integer_gen() << acct << acct->fullname();
276
 
 
277
 
    sha.Reset();
278
 
    sha << buf.str().c_str();
279
 
    sha.Result(message_digest);
280
 
 
281
 
    account_names.push_front(to_hex(message_digest));
282
 
  }
283
 
 
284
 
  account_t * new_account =
285
 
    create_temp_account_from_path(account_names, temps, xact.journal->master);
286
 
  post_t& temp = temps.copy_post(post, xact, new_account);
287
 
  temp.note = none;
288
 
  temp.add_flags(POST_ANONYMIZED);
289
 
 
290
 
  render_commodity(temp.amount);
291
 
  if (temp.amount.has_annotation()) {
292
 
    temp.amount.annotation().tag = none;
293
 
    if (temp.amount.annotation().price)
294
 
      render_commodity(*temp.amount.annotation().price);
295
 
  }
296
 
 
297
 
  if (temp.cost)
298
 
    render_commodity(*temp.cost);
299
 
  if (temp.assigned_amount)
300
 
    render_commodity(*temp.assigned_amount);
301
 
 
302
 
  (*handler)(temp);
303
 
}
304
 
 
305
 
void calc_posts::operator()(post_t& post)
306
 
{
307
 
  post_t::xdata_t& xdata(post.xdata());
308
 
 
309
 
  if (last_post) {
310
 
    assert(last_post->has_xdata());
311
 
    if (calc_running_total)
312
 
      xdata.total = last_post->xdata().total;
313
 
    xdata.count = last_post->xdata().count + 1;
314
 
  } else {
315
 
    xdata.count = 1;
316
 
  }
317
 
 
318
 
  post.add_to_value(xdata.visited_value, amount_expr);
319
 
  xdata.add_flags(POST_EXT_VISITED);
320
 
 
321
 
  account_t * acct = post.reported_account();
322
 
  acct->xdata().add_flags(ACCOUNT_EXT_VISITED);
323
 
 
324
 
  if (calc_running_total)
325
 
    add_or_set_value(xdata.total, xdata.visited_value);
326
 
 
327
 
  item_handler<post_t>::operator()(post);
328
 
 
329
 
  last_post = &post;
330
 
}
331
 
 
332
 
namespace {
333
 
  void handle_value(const value_t&   value,
334
 
                    account_t *      account,
335
 
                    xact_t *         xact,
336
 
                    temporaries_t&   temps,
337
 
                    post_handler_ptr handler,
338
 
                    const date_t&    date          = date_t(),
339
 
                    const bool       act_date_p    = true,
340
 
                    const value_t&   total         = value_t(),
341
 
                    const bool       direct_amount = false,
342
 
                    const bool       mark_visited  = false,
343
 
                    const bool       bidir_link    = true)
344
 
  {
345
 
    post_t& post = temps.create_post(*xact, account, bidir_link);
346
 
    post.add_flags(ITEM_GENERATED);
347
 
 
348
 
    // If the account for this post is all virtual, then report the post as
349
 
    // such.  This allows subtotal reports to show "(Account)" for accounts
350
 
    // that contain only virtual posts.
351
 
    if (account && account->has_xdata() &&
352
 
        account->xdata().has_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE)) {
353
 
      if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) {
354
 
        post.add_flags(POST_VIRTUAL);
355
 
        if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS))
356
 
          post.add_flags(POST_MUST_BALANCE);
357
 
      }
358
 
    }
359
 
 
360
 
    post_t::xdata_t& xdata(post.xdata());
361
 
 
362
 
    if (is_valid(date)) {
363
 
      if (act_date_p)
364
 
        xdata.date = date;
365
 
      else
366
 
        xdata.value_date = date;
367
 
    }
368
 
 
369
 
    value_t temp(value);
370
 
 
371
 
    switch (value.type()) {
372
 
    case value_t::BOOLEAN:
373
 
    case value_t::INTEGER:
374
 
      temp.in_place_cast(value_t::AMOUNT);
375
 
      // fall through...
376
 
 
377
 
    case value_t::AMOUNT:
378
 
      post.amount = temp.as_amount();
379
 
      break;
380
 
 
381
 
    case value_t::BALANCE:
382
 
    case value_t::SEQUENCE:
383
 
      xdata.compound_value = temp;
384
 
      xdata.add_flags(POST_EXT_COMPOUND);
385
 
      break;
386
 
 
387
 
    case value_t::DATETIME:
388
 
    case value_t::DATE:
389
 
    default:
390
 
      assert(false);
391
 
      break;
392
 
    }
393
 
 
394
 
    if (! total.is_null())
395
 
      xdata.total = total;
396
 
 
397
 
    if (direct_amount)
398
 
      xdata.add_flags(POST_EXT_DIRECT_AMT);
399
 
 
400
 
    DEBUG("filters.changed_value.rounding", "post.amount = " << post.amount);
401
 
 
402
 
    (*handler)(post);
403
 
 
404
 
    if (mark_visited) {
405
 
      post.xdata().add_flags(POST_EXT_VISITED);
406
 
      post.account->xdata().add_flags(ACCOUNT_EXT_VISITED);
407
 
    }
408
 
  }
409
 
}
410
 
 
411
 
void collapse_posts::report_subtotal()
412
 
{
413
 
  if (! count)
414
 
    return;
415
 
 
416
 
  std::size_t displayed_count = 0;
417
 
  foreach (post_t * post, component_posts) {
418
 
    bind_scope_t bound_scope(report, *post);
419
 
    if (only_predicate(bound_scope) && display_predicate(bound_scope))
420
 
      displayed_count++;
421
 
  }
422
 
 
423
 
  if (displayed_count == 1) {
424
 
    item_handler<post_t>::operator()(*last_post);
425
 
  }
426
 
  else if (only_collapse_if_zero && ! subtotal.is_zero()) {
427
 
    foreach (post_t * post, component_posts)
428
 
      item_handler<post_t>::operator()(*post);
429
 
  }
430
 
  else {
431
 
    date_t earliest_date;
432
 
    date_t latest_date;
433
 
 
434
 
    foreach (post_t * post, component_posts) {
435
 
      date_t date       = post->date();
436
 
      date_t value_date = post->value_date();
437
 
      if (! is_valid(earliest_date) || date < earliest_date)
438
 
        earliest_date = date;
439
 
      if (! is_valid(latest_date) || value_date > latest_date)
440
 
        latest_date = value_date;
441
 
    }
442
 
 
443
 
    xact_t& xact = temps.create_xact();
444
 
    xact.payee     = last_xact->payee;
445
 
    xact._date     = (is_valid(earliest_date) ?
446
 
                      earliest_date : last_xact->_date);
447
 
 
448
 
    DEBUG("filters.collapse", "Pseudo-xact date = " << *xact._date);
449
 
    DEBUG("filters.collapse", "earliest date    = " << earliest_date);
450
 
    DEBUG("filters.collapse", "latest date      = " << latest_date);
451
 
 
452
 
    handle_value(/* value=      */ subtotal,
453
 
                 /* account=    */ totals_account,
454
 
                 /* xact=       */ &xact,
455
 
                 /* temps=      */ temps,
456
 
                 /* handler=    */ handler,
457
 
                 /* date=       */ latest_date,
458
 
                 /* act_date_p= */ false);
459
 
  }
460
 
 
461
 
  component_posts.clear();
462
 
 
463
 
  last_xact = NULL;
464
 
  last_post = NULL;
465
 
  subtotal  = 0L;
466
 
  count     = 0;
467
 
}
468
 
 
469
 
void collapse_posts::operator()(post_t& post)
470
 
{
471
 
  // If we've reached a new xact, report on the subtotal
472
 
  // accumulated thus far.
473
 
 
474
 
  if (last_xact != post.xact && count > 0)
475
 
    report_subtotal();
476
 
 
477
 
  post.add_to_value(subtotal, amount_expr);
478
 
 
479
 
  component_posts.push_back(&post);
480
 
 
481
 
  last_xact = post.xact;
482
 
  last_post = &post;
483
 
  count++;
484
 
}
485
 
 
486
 
void related_posts::flush()
487
 
{
488
 
  if (posts.size() > 0) {
489
 
    foreach (post_t * post, posts) {
490
 
      assert(post->xact);
491
 
      foreach (post_t * r_post, post->xact->posts) {
492
 
        post_t::xdata_t& xdata(r_post->xdata());
493
 
        if (! xdata.has_flags(POST_EXT_HANDLED) &&
494
 
            (! xdata.has_flags(POST_EXT_RECEIVED) ?
495
 
             ! r_post->has_flags(ITEM_GENERATED | POST_VIRTUAL) :
496
 
             also_matching)) {
497
 
          xdata.add_flags(POST_EXT_HANDLED);
498
 
          item_handler<post_t>::operator()(*r_post);
499
 
        }
500
 
      }
501
 
    }
502
 
  }
503
 
 
504
 
  item_handler<post_t>::flush();
505
 
}
506
 
 
507
 
display_filter_posts::display_filter_posts(post_handler_ptr handler,
508
 
                                           report_t&        _report,
509
 
                                           bool             _show_rounding)
510
 
  : item_handler<post_t>(handler), report(_report),
511
 
    display_amount_expr(report.HANDLER(display_amount_).expr),
512
 
    display_total_expr(report.HANDLER(display_total_).expr),
513
 
    show_rounding(_show_rounding)
514
 
{
515
 
  create_accounts();
516
 
  TRACE_CTOR(display_filter_posts, "post_handler_ptr, report_t&, bool");
517
 
}
518
 
 
519
 
bool display_filter_posts::output_rounding(post_t& post)
520
 
{
521
 
  bind_scope_t bound_scope(report, post);
522
 
  value_t      new_display_total;
523
 
 
524
 
  if (show_rounding) {
525
 
    new_display_total = (display_total_expr.calc(bound_scope)
526
 
                         .strip_annotations(report.what_to_keep()));
527
 
 
528
 
    DEBUG("filters.changed_value.rounding",
529
 
          "rounding.new_display_total     = " << new_display_total);
530
 
  }
531
 
 
532
 
  // Allow the posting to be displayed if:
533
 
  //  1. Its display_amount would display as non-zero, or
534
 
  //  2. The --empty option was specified, or
535
 
  //  3. a) The account of the posting is <Revalued>, and
536
 
  //     b) the revalued option is specified, and
537
 
  //     c) the --no-rounding option is not specified.
538
 
 
539
 
  if (post.account == revalued_account) {
540
 
    if (show_rounding)
541
 
      last_display_total = new_display_total;
542
 
    return true;
543
 
  }
544
 
 
545
 
  if (value_t repriced_amount = (display_amount_expr.calc(bound_scope)
546
 
                                 .strip_annotations(report.what_to_keep()))) {
547
 
    if (! last_display_total.is_null()) {
548
 
      DEBUG("filters.changed_value.rounding",
549
 
            "rounding.repriced_amount       = " << repriced_amount);
550
 
 
551
 
      value_t precise_display_total(new_display_total.truncated() -
552
 
                                    repriced_amount.truncated());
553
 
 
554
 
      DEBUG("filters.changed_value.rounding",
555
 
            "rounding.precise_display_total = " << precise_display_total);
556
 
      DEBUG("filters.changed_value.rounding",
557
 
            "rounding.last_display_total    = " << last_display_total);
558
 
 
559
 
      if (value_t diff = precise_display_total - last_display_total) {
560
 
        DEBUG("filters.changed_value.rounding",
561
 
              "rounding.diff                  = " << diff);
562
 
 
563
 
        handle_value(/* value=         */ diff,
564
 
                     /* account=       */ rounding_account,
565
 
                     /* xact=          */ post.xact,
566
 
                     /* temps=         */ temps,
567
 
                     /* handler=       */ handler,
568
 
                     /* date=          */ date_t(),
569
 
                     /* act_date_p=    */ true,
570
 
                     /* total=         */ precise_display_total,
571
 
                     /* direct_amount= */ true,
572
 
                     /* mark_visited=  */ false,
573
 
                     /* bidir_link=    */ false);
574
 
      }
575
 
    }
576
 
    if (show_rounding)
577
 
      last_display_total = new_display_total;
578
 
    return true;
579
 
  } else {
580
 
    return report.HANDLED(empty);
581
 
  }
582
 
}
583
 
 
584
 
void display_filter_posts::operator()(post_t& post)
585
 
{
586
 
  if (output_rounding(post))
587
 
    item_handler<post_t>::operator()(post);
588
 
}
589
 
 
590
 
changed_value_posts::changed_value_posts
591
 
  (post_handler_ptr       handler,
592
 
   report_t&              _report,
593
 
   bool                   _for_accounts_report,
594
 
   bool                   _show_unrealized,
595
 
   display_filter_posts * _display_filter)
596
 
  : item_handler<post_t>(handler), report(_report),
597
 
    total_expr(report.HANDLED(revalued_total_) ?
598
 
               report.HANDLER(revalued_total_).expr :
599
 
               report.HANDLER(display_total_).expr),
600
 
    display_total_expr(report.HANDLER(display_total_).expr),
601
 
    changed_values_only(report.HANDLED(revalued_only)),
602
 
    historical_prices_only(report.HANDLED(historical)),
603
 
    for_accounts_report(_for_accounts_report),
604
 
    show_unrealized(_show_unrealized), last_post(NULL),
605
 
    display_filter(_display_filter)
606
 
{
607
 
  string gains_equity_account_name;
608
 
  if (report.HANDLED(unrealized_gains_))
609
 
    gains_equity_account_name = report.HANDLER(unrealized_gains_).str();
610
 
  else
611
 
    gains_equity_account_name = _("Equity:Unrealized Gains");
612
 
  gains_equity_account =
613
 
    report.session.journal->master->find_account(gains_equity_account_name);
614
 
  gains_equity_account->add_flags(ACCOUNT_GENERATED);
615
 
 
616
 
  string losses_equity_account_name;
617
 
  if (report.HANDLED(unrealized_losses_))
618
 
    losses_equity_account_name = report.HANDLER(unrealized_losses_).str();
619
 
  else
620
 
    losses_equity_account_name = _("Equity:Unrealized Losses");
621
 
  losses_equity_account =
622
 
    report.session.journal->master->find_account(losses_equity_account_name);
623
 
  losses_equity_account->add_flags(ACCOUNT_GENERATED);
624
 
 
625
 
  create_accounts();
626
 
 
627
 
  TRACE_CTOR(changed_value_posts,
628
 
             "post_handler_ptr, report_t&, bool, bool, display_filter_posts *");
629
 
}
630
 
 
631
 
void changed_value_posts::flush()
632
 
{
633
 
  if (last_post && last_post->date() <= report.terminus.date()) {
634
 
    if (! historical_prices_only) {
635
 
      if (! for_accounts_report)
636
 
        output_intermediate_prices(*last_post, report.terminus.date());
637
 
      output_revaluation(*last_post, report.terminus.date());
638
 
    }
639
 
    last_post = NULL;
640
 
  }
641
 
  item_handler<post_t>::flush();
642
 
}
643
 
 
644
 
void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
645
 
{
646
 
  if (is_valid(date))
647
 
    post.xdata().date = date;
648
 
 
649
 
  try {
650
 
    bind_scope_t bound_scope(report, post);
651
 
    repriced_total = total_expr.calc(bound_scope);
652
 
  }
653
 
  catch (...) {
654
 
    post.xdata().date = date_t();
655
 
    throw;
656
 
  }
657
 
  post.xdata().date = date_t();
658
 
 
659
 
  DEBUG("filters.changed_value",
660
 
        "output_revaluation(last_total)     = " << last_total);
661
 
  DEBUG("filters.changed_value",
662
 
        "output_revaluation(repriced_total) = " << repriced_total);
663
 
 
664
 
  if (! last_total.is_null()) {
665
 
    if (value_t diff = repriced_total - last_total) {
666
 
      DEBUG("filters.changed_value", "output_revaluation(strip(diff)) = "
667
 
            << diff.strip_annotations(report.what_to_keep()));
668
 
 
669
 
      xact_t& xact = temps.create_xact();
670
 
      xact.payee = _("Commodities revalued");
671
 
      xact._date = is_valid(date) ? date : post.value_date();
672
 
 
673
 
      if (! for_accounts_report) {
674
 
        handle_value
675
 
          (/* value=         */ diff,
676
 
           /* account=       */ revalued_account,
677
 
           /* xact=          */ &xact,
678
 
           /* temps=         */ temps,
679
 
           /* handler=       */ handler,
680
 
           /* date=          */ *xact._date,
681
 
           /* act_date_p=    */ true,
682
 
           /* total=         */ repriced_total);
683
 
      }
684
 
      else if (show_unrealized) {
685
 
        handle_value
686
 
          (/* value=         */ - diff,
687
 
           /* account=       */ (diff < 0L ?
688
 
                                 losses_equity_account :
689
 
                                 gains_equity_account),
690
 
           /* xact=          */ &xact,
691
 
           /* temps=         */ temps,
692
 
           /* handler=       */ handler,
693
 
           /* date=          */ *xact._date,
694
 
           /* act_date_p=    */ true,
695
 
           /* total=         */ value_t(),
696
 
           /* direct_amount= */ false,
697
 
           /* mark_visited=  */ true);
698
 
      }
699
 
    }
700
 
  }
701
 
}
702
 
 
703
 
namespace {
704
 
  struct insert_prices_in_map {
705
 
    price_map_t& all_prices;
706
 
 
707
 
    insert_prices_in_map(price_map_t& _all_prices)
708
 
      : all_prices(_all_prices) {}
709
 
 
710
 
    void operator()(datetime_t& date, const amount_t& price) {
711
 
      all_prices.insert(price_map_t::value_type(date, price));
712
 
    }
713
 
  };
714
 
}
715
 
 
716
 
void changed_value_posts::output_intermediate_prices(post_t&       post,
717
 
                                                     const date_t& current)
718
 
{
719
 
  // To fix BZ#199, examine the balance of last_post and determine whether the
720
 
  // price of that amount changed after its date and before the new post's
721
 
  // date.  If so, generate an output_revaluation for that price change.
722
 
  // Mostly this is only going to occur if the user has a series of pricing
723
 
  // entries, since a posting-based revaluation would be seen here as a post.
724
 
 
725
 
  value_t display_total(last_total);
726
 
 
727
 
  if (display_total.type() == value_t::SEQUENCE) {
728
 
    xact_t& xact(temps.create_xact());
729
 
 
730
 
    xact.payee = _("Commodities revalued");
731
 
    xact._date = is_valid(current) ? current : post.value_date();
732
 
 
733
 
    post_t& temp(temps.copy_post(post, xact));
734
 
    temp.add_flags(ITEM_GENERATED);
735
 
 
736
 
    post_t::xdata_t& xdata(temp.xdata());
737
 
    if (is_valid(current))
738
 
      xdata.date = current;
739
 
 
740
 
    DEBUG("filters.revalued", "intermediate last_total = " << last_total);
741
 
 
742
 
    switch (last_total.type()) {
743
 
    case value_t::BOOLEAN:
744
 
    case value_t::INTEGER:
745
 
      last_total.in_place_cast(value_t::AMOUNT);
746
 
      // fall through...
747
 
 
748
 
    case value_t::AMOUNT:
749
 
      temp.amount = last_total.as_amount();
750
 
      break;
751
 
 
752
 
    case value_t::BALANCE:
753
 
    case value_t::SEQUENCE:
754
 
      xdata.compound_value = last_total;
755
 
      xdata.add_flags(POST_EXT_COMPOUND);
756
 
      break;
757
 
 
758
 
    case value_t::DATETIME:
759
 
    case value_t::DATE:
760
 
    default:
761
 
      assert(false);
762
 
      break;
763
 
    }
764
 
 
765
 
    bind_scope_t inner_scope(report, temp);
766
 
    display_total = display_total_expr.calc(inner_scope);
767
 
 
768
 
    DEBUG("filters.revalued", "intermediate display_total = " << display_total);
769
 
  }
770
 
 
771
 
  switch (display_total.type()) {
772
 
  case value_t::VOID:
773
 
  case value_t::INTEGER:
774
 
  case value_t::SEQUENCE:
775
 
    break;
776
 
 
777
 
  case value_t::AMOUNT:
778
 
    display_total.in_place_cast(value_t::BALANCE);
779
 
    // fall through...
780
 
 
781
 
  case value_t::BALANCE: {
782
 
    price_map_t all_prices;
783
 
 
784
 
    foreach (const balance_t::amounts_map::value_type& amt_comm,
785
 
             display_total.as_balance().amounts)
786
 
      amt_comm.first->map_prices(insert_prices_in_map(all_prices),
787
 
                                 datetime_t(current),
788
 
                                 datetime_t(post.value_date()), true);
789
 
 
790
 
    // Choose the last price from each day as the price to use
791
 
    typedef std::map<const date_t, bool> date_map;
792
 
    date_map pricing_dates;
793
 
 
794
 
    BOOST_REVERSE_FOREACH(const price_map_t::value_type& price, all_prices) {
795
 
      // This insert will fail if a later price has already been inserted
796
 
      // for that date.
797
 
      DEBUG("filters.revalued",
798
 
            "re-inserting " << price.second << " at " << price.first.date());
799
 
      pricing_dates.insert(date_map::value_type(price.first.date(), true));
800
 
    }
801
 
 
802
 
    // Go through the time-sorted prices list, outputting a revaluation for
803
 
    // each price difference.
804
 
    foreach (const date_map::value_type& price, pricing_dates) {
805
 
      output_revaluation(post, price.first);
806
 
      last_total = repriced_total;
807
 
    }
808
 
    break;
809
 
  }
810
 
  default:
811
 
    assert(false);
812
 
    break;
813
 
  }
814
 
}
815
 
 
816
 
void changed_value_posts::operator()(post_t& post)
817
 
{
818
 
  if (last_post) {
819
 
    if (! for_accounts_report && ! historical_prices_only)
820
 
      output_intermediate_prices(*last_post, post.value_date());
821
 
    output_revaluation(*last_post, post.value_date());
822
 
  }
823
 
 
824
 
  if (changed_values_only)
825
 
    post.xdata().add_flags(POST_EXT_DISPLAYED);
826
 
 
827
 
  item_handler<post_t>::operator()(post);
828
 
 
829
 
  bind_scope_t bound_scope(report, post);
830
 
  last_total = total_expr.calc(bound_scope);
831
 
  last_post  = &post;
832
 
}
833
 
 
834
 
void subtotal_posts::report_subtotal(const char *                     spec_fmt,
835
 
                                     const optional<date_interval_t>& interval)
836
 
{
837
 
  if (component_posts.empty())
838
 
    return;
839
 
 
840
 
  optional<date_t> range_start  = interval ? interval->start : none;
841
 
  optional<date_t> range_finish = interval ? interval->inclusive_end() : none;
842
 
 
843
 
  if (! range_start || ! range_finish) {
844
 
    foreach (post_t * post, component_posts) {
845
 
      date_t date       = post->date();
846
 
      date_t value_date = post->value_date();
847
 
#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
848
 
#pragma GCC diagnostic push
849
 
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
850
 
#endif
851
 
      if (! range_start || date < *range_start)
852
 
        range_start = date;
853
 
      if (! range_finish || value_date > *range_finish)
854
 
        range_finish = value_date;
855
 
#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
856
 
#pragma GCC diagnostic pop
857
 
#endif
858
 
    }
859
 
  }
860
 
  component_posts.clear();
861
 
 
862
 
  std::ostringstream out_date;
863
 
  if (spec_fmt) {
864
 
    out_date << format_date(*range_finish, FMT_CUSTOM, spec_fmt);
865
 
  }
866
 
  else if (date_format) {
867
 
    out_date << "- " << format_date(*range_finish, FMT_CUSTOM,
868
 
                                    date_format->c_str());
869
 
  }
870
 
  else {
871
 
    out_date << "- " << format_date(*range_finish);
872
 
  }
873
 
 
874
 
  xact_t& xact = temps.create_xact();
875
 
  xact.payee = out_date.str();
876
 
  xact._date = *range_start;
877
 
 
878
 
  foreach (values_map::value_type& pair, values)
879
 
    handle_value(/* value=      */ pair.second.value,
880
 
                 /* account=    */ pair.second.account,
881
 
                 /* xact=       */ &xact,
882
 
                 /* temps=      */ temps,
883
 
                 /* handler=    */ handler,
884
 
                 /* date=       */ *range_finish,
885
 
                 /* act_date_p= */ false);
886
 
 
887
 
  values.clear();
888
 
}
889
 
 
890
 
void subtotal_posts::operator()(post_t& post)
891
 
{
892
 
  component_posts.push_back(&post);
893
 
 
894
 
  account_t * acct = post.reported_account();
895
 
  assert(acct);
896
 
 
897
 
#if 0
898
 
  // jww (2012-04-06): The problem with doing this early is that
899
 
  // fn_display_amount will recalculate this again.  For example, if you
900
 
  // use --invert, it will invert both here and in the display amount,
901
 
  // effectively negating it.
902
 
  bind_scope_t bound_scope(*amount_expr.get_context(), post);
903
 
  value_t amount(amount_expr.calc(bound_scope));
904
 
#else
905
 
  value_t amount(post.amount);
906
 
#endif
907
 
 
908
 
  post.xdata().compound_value = amount;
909
 
  post.xdata().add_flags(POST_EXT_COMPOUND);
910
 
 
911
 
  values_map::iterator i = values.find(acct->fullname());
912
 
  if (i == values.end()) {
913
 
#if DEBUG_ON
914
 
    std::pair<values_map::iterator, bool> result =
915
 
#endif
916
 
      values.insert(values_pair
917
 
                    (acct->fullname(),
918
 
                     acct_value_t(acct, amount, post.has_flags(POST_VIRTUAL),
919
 
                                  post.has_flags(POST_MUST_BALANCE))));
920
 
#if DEBUG_ON
921
 
    assert(result.second);
922
 
#endif
923
 
  } else {
924
 
    if (post.has_flags(POST_VIRTUAL) != (*i).second.is_virtual)
925
 
      throw_(std::logic_error,
926
 
             _("'equity' cannot accept virtual and "
927
 
               "non-virtual postings to the same account"));
928
 
 
929
 
    add_or_set_value((*i).second.value, amount);
930
 
  }
931
 
 
932
 
  // If the account for this post is all virtual, mark it as
933
 
  // such, so that `handle_value' can show "(Account)" for accounts
934
 
  // that contain only virtual posts.
935
 
 
936
 
  post.reported_account()->xdata().add_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE);
937
 
 
938
 
  if (! post.has_flags(POST_VIRTUAL))
939
 
    post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS);
940
 
  else if (! post.has_flags(POST_MUST_BALANCE))
941
 
    post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS);
942
 
}
943
 
 
944
 
void interval_posts::report_subtotal(const date_interval_t& ival)
945
 
{
946
 
  if (exact_periods)
947
 
    subtotal_posts::report_subtotal();
948
 
  else
949
 
    subtotal_posts::report_subtotal(NULL, ival);
950
 
}
951
 
 
952
 
namespace {
953
 
  struct sort_posts_by_date {
954
 
    bool operator()(post_t * left, post_t * right) const {
955
 
      return left->date() < right->date();
956
 
    }
957
 
  };
958
 
}
959
 
 
960
 
void interval_posts::operator()(post_t& post)
961
 
{
962
 
  // If there is a duration (such as weekly), we must generate the
963
 
  // report in two passes.  Otherwise, we only have to check whether the
964
 
  // post falls within the reporting period.
965
 
 
966
 
  if (interval.duration) {
967
 
    all_posts.push_back(&post);
968
 
  }
969
 
  else if (interval.find_period(post.date())) {
970
 
    item_handler<post_t>::operator()(post);
971
 
  }
972
 
}
973
 
 
974
 
void interval_posts::flush()
975
 
{
976
 
  if (! interval.duration) {
977
 
    item_handler<post_t>::flush();
978
 
    return;
979
 
  }
980
 
 
981
 
  // Sort all the postings we saw by date ascending
982
 
  std::stable_sort(all_posts.begin(), all_posts.end(),
983
 
                   sort_posts_by_date());
984
 
 
985
 
  // Determine the beginning interval by using the earliest post
986
 
  if (all_posts.front() &&
987
 
      ! interval.find_period(all_posts.front()->date()))
988
 
    throw_(std::logic_error, _("Failed to find period for interval report"));
989
 
 
990
 
  // Walk the interval forward reporting all posts within each one
991
 
  // before moving on, until we reach the end of all_posts
992
 
  bool saw_posts = false;
993
 
  for (std::deque<post_t *>::iterator i = all_posts.begin();
994
 
       i != all_posts.end(); ) {
995
 
    post_t * post(*i);
996
 
 
997
 
    DEBUG("filters.interval",
998
 
          "Considering post " << post->date() << " = " << post->amount);
999
 
#if DEBUG_ON
1000
 
    DEBUG("filters.interval", "interval is:");
1001
 
    debug_interval(interval);
1002
 
#endif
1003
 
    assert(! interval.finish || post->date() < *interval.finish);
1004
 
 
1005
 
    if (interval.within_period(post->date())) {
1006
 
      DEBUG("filters.interval", "Calling subtotal_posts::operator()");
1007
 
      subtotal_posts::operator()(*post);
1008
 
      ++i;
1009
 
      saw_posts = true;
1010
 
    } else {
1011
 
      if (saw_posts) {
1012
 
        DEBUG("filters.interval",
1013
 
              "Calling subtotal_posts::report_subtotal()");
1014
 
        report_subtotal(interval);
1015
 
        saw_posts = false;
1016
 
      }
1017
 
      else if (generate_empty_posts) {
1018
 
        // Generate a null posting, so the intervening periods can be
1019
 
        // seen when -E is used, or if the calculated amount ends up
1020
 
        // being non-zero
1021
 
        xact_t& null_xact = temps.create_xact();
1022
 
        null_xact._date = interval.inclusive_end();
1023
 
 
1024
 
        post_t& null_post = temps.create_post(null_xact, empty_account);
1025
 
        null_post.add_flags(POST_CALCULATED);
1026
 
        null_post.amount = 0L;
1027
 
 
1028
 
        subtotal_posts::operator()(null_post);
1029
 
        report_subtotal(interval);
1030
 
      }
1031
 
 
1032
 
      DEBUG("filters.interval", "Advancing interval");
1033
 
      ++interval;
1034
 
    }
1035
 
  }
1036
 
 
1037
 
  // If the last postings weren't reported, do so now.
1038
 
  if (saw_posts) {
1039
 
    DEBUG("filters.interval",
1040
 
          "Calling subtotal_posts::report_subtotal() at end");
1041
 
    report_subtotal(interval);
1042
 
  }
1043
 
 
1044
 
  // Tell our parent class to flush
1045
 
  subtotal_posts::flush();
1046
 
}
1047
 
 
1048
 
namespace {
1049
 
  struct create_post_from_amount
1050
 
  {
1051
 
    post_handler_ptr handler;
1052
 
    xact_t&          xact;
1053
 
    account_t&       balance_account;
1054
 
    temporaries_t&   temps;
1055
 
 
1056
 
    explicit create_post_from_amount(post_handler_ptr _handler,
1057
 
                                     xact_t&          _xact,
1058
 
                                     account_t&       _balance_account,
1059
 
                                     temporaries_t&   _temps)
1060
 
      : handler(_handler), xact(_xact),
1061
 
        balance_account(_balance_account), temps(_temps) {
1062
 
      TRACE_CTOR(create_post_from_amount,
1063
 
                 "post_handler_ptr, xact_t&, account_t&, temporaries_t&");
1064
 
    }
1065
 
    create_post_from_amount(const create_post_from_amount& other)
1066
 
      : handler(other.handler), xact(other.xact),
1067
 
        balance_account(other.balance_account), temps(other.temps) {
1068
 
      TRACE_CTOR(create_post_from_amount, "copy");
1069
 
    }
1070
 
    ~create_post_from_amount() throw() {
1071
 
      TRACE_DTOR(create_post_from_amount);
1072
 
    }
1073
 
 
1074
 
    void operator()(const amount_t& amount) {
1075
 
      post_t& balance_post = temps.create_post(xact, &balance_account);
1076
 
      balance_post.amount = - amount;
1077
 
      (*handler)(balance_post);
1078
 
    }
1079
 
  };
1080
 
}
1081
 
 
1082
 
void posts_as_equity::report_subtotal()
1083
 
{
1084
 
  date_t finish;
1085
 
  foreach (post_t * post, component_posts) {
1086
 
    date_t date = post->date();
1087
 
    if (! is_valid(finish) || date > finish)
1088
 
      finish = date;
1089
 
  }
1090
 
  component_posts.clear();
1091
 
 
1092
 
  xact_t& xact = temps.create_xact();
1093
 
  xact.payee = _("Opening Balances");
1094
 
  xact._date = finish;
1095
 
 
1096
 
  value_t total = 0L;
1097
 
  foreach (values_map::value_type& pair, values) {
1098
 
    value_t value(pair.second.value.strip_annotations(report.what_to_keep()));
1099
 
    if (! value.is_zero()) {
1100
 
      if (value.is_balance()) {
1101
 
        foreach (const balance_t::amounts_map::value_type& amount_pair,
1102
 
                 value.as_balance_lval().amounts) {
1103
 
          if (! amount_pair.second.is_zero())
1104
 
            handle_value(/* value=      */ amount_pair.second,
1105
 
                         /* account=    */ pair.second.account,
1106
 
                         /* xact=       */ &xact,
1107
 
                         /* temps=      */ temps,
1108
 
                         /* handler=    */ handler,
1109
 
                         /* date=       */ finish,
1110
 
                         /* act_date_p= */ false);
1111
 
        }
1112
 
      } else {
1113
 
        handle_value(/* value=      */ value.to_amount(),
1114
 
                     /* account=    */ pair.second.account,
1115
 
                     /* xact=       */ &xact,
1116
 
                     /* temps=      */ temps,
1117
 
                     /* handler=    */ handler,
1118
 
                     /* date=       */ finish,
1119
 
                     /* act_date_p= */ false);
1120
 
      }
1121
 
    }
1122
 
 
1123
 
    if (! pair.second.is_virtual || pair.second.must_balance)
1124
 
      total += value;
1125
 
  }
1126
 
  values.clear();
1127
 
 
1128
 
  // This last part isn't really needed, since an Equity:Opening
1129
 
  // Balances posting with a null amount will automatically balance with
1130
 
  // all the other postings generated.  But it does make the full
1131
 
  // balancing amount clearer to the user.
1132
 
  if (! total.is_zero()) {
1133
 
    create_post_from_amount post_creator(handler, xact,
1134
 
                                         *balance_account, temps);
1135
 
    if (total.is_balance())
1136
 
      total.as_balance_lval().map_sorted_amounts(post_creator);
1137
 
    else
1138
 
      post_creator(total.to_amount());
1139
 
  }
1140
 
}
1141
 
 
1142
 
void by_payee_posts::flush()
1143
 
{
1144
 
  foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
1145
 
    pair.second->report_subtotal(pair.first.c_str());
1146
 
 
1147
 
  item_handler<post_t>::flush();
1148
 
 
1149
 
  payee_subtotals.clear();
1150
 
}
1151
 
 
1152
 
void by_payee_posts::operator()(post_t& post)
1153
 
{
1154
 
  payee_subtotals_map::iterator i = payee_subtotals.find(post.payee());
1155
 
  if (i == payee_subtotals.end()) {
1156
 
    payee_subtotals_pair
1157
 
      temp(post.payee(),
1158
 
           shared_ptr<subtotal_posts>(new subtotal_posts(handler, amount_expr)));
1159
 
    std::pair<payee_subtotals_map::iterator, bool> result
1160
 
      = payee_subtotals.insert(temp);
1161
 
 
1162
 
    assert(result.second);
1163
 
    if (! result.second)
1164
 
      return;
1165
 
    i = result.first;
1166
 
  }
1167
 
 
1168
 
  (*(*i).second)(post);
1169
 
}
1170
 
 
1171
 
void transfer_details::operator()(post_t& post)
1172
 
{
1173
 
  xact_t& xact = temps.copy_xact(*post.xact);
1174
 
  xact._date = post.date();
1175
 
 
1176
 
  post_t& temp = temps.copy_post(post, xact);
1177
 
  temp.set_state(post.state());
1178
 
 
1179
 
  bind_scope_t bound_scope(scope, temp);
1180
 
  value_t      substitute(expr.calc(bound_scope));
1181
 
 
1182
 
  if (! substitute.is_null()) {
1183
 
    switch (which_element) {
1184
 
    case SET_DATE:
1185
 
      temp._date = substitute.to_date();
1186
 
      break;
1187
 
 
1188
 
    case SET_ACCOUNT: {
1189
 
      string account_name = substitute.to_string();
1190
 
      if (! account_name.empty() &&
1191
 
          account_name[account_name.length() - 1] != ':') {
1192
 
        account_t * prev_account = temp.account;
1193
 
        temp.account->remove_post(&temp);
1194
 
 
1195
 
        account_name += ':';
1196
 
        account_name += prev_account->fullname();
1197
 
 
1198
 
        std::list<string> account_names;
1199
 
        split_string(account_name, ':', account_names);
1200
 
        temp.account = create_temp_account_from_path(account_names, temps,
1201
 
                                                     xact.journal->master);
1202
 
        temp.account->add_post(&temp);
1203
 
 
1204
 
        temp.account->add_flags(prev_account->flags());
1205
 
        if (prev_account->has_xdata())
1206
 
          temp.account->xdata().add_flags(prev_account->xdata().flags());
1207
 
      }
1208
 
      break;
1209
 
    }
1210
 
 
1211
 
    case SET_PAYEE:
1212
 
      xact.payee = substitute.to_string();
1213
 
      break;
1214
 
    }
1215
 
  }
1216
 
 
1217
 
  item_handler<post_t>::operator()(temp);
1218
 
}
1219
 
 
1220
 
void day_of_week_posts::flush()
1221
 
{
1222
 
  for (int i = 0; i < 7; i++) {
1223
 
    foreach (post_t * post, days_of_the_week[i])
1224
 
      subtotal_posts::operator()(*post);
1225
 
    subtotal_posts::report_subtotal("%As");
1226
 
    days_of_the_week[i].clear();
1227
 
  }
1228
 
 
1229
 
  subtotal_posts::flush();
1230
 
}
1231
 
 
1232
 
void generate_posts::add_period_xacts(period_xacts_list& period_xacts)
1233
 
{
1234
 
  foreach (period_xact_t * xact, period_xacts)
1235
 
    foreach (post_t * post, xact->posts)
1236
 
      add_post(xact->period, *post);
1237
 
}
1238
 
 
1239
 
void generate_posts::add_post(const date_interval_t& period, post_t& post)
1240
 
{
1241
 
  pending_posts.push_back(pending_posts_pair(period, &post));
1242
 
}
1243
 
 
1244
 
void budget_posts::report_budget_items(const date_t& date)
1245
 
{
1246
 
  if (pending_posts.size() == 0)
1247
 
    return;
1248
 
 
1249
 
  bool reported;
1250
 
  do {
1251
 
    std::list<pending_posts_list::iterator> posts_to_erase;
1252
 
 
1253
 
    reported = false;
1254
 
    for (pending_posts_list::iterator i = pending_posts.begin();
1255
 
         i != pending_posts.end();
1256
 
         i++) {
1257
 
      pending_posts_list::value_type& pair(*i);
1258
 
 
1259
 
      optional<date_t> begin = pair.first.start;
1260
 
      if (! begin) {
1261
 
        optional<date_t> range_begin;
1262
 
        if (pair.first.range)
1263
 
          range_begin = pair.first.range->begin();
1264
 
 
1265
 
        DEBUG("budget.generate", "Finding period for pending post");
1266
 
        if (! pair.first.find_period(range_begin ? *range_begin : date))
1267
 
          continue;
1268
 
        if (! pair.first.start)
1269
 
          throw_(std::logic_error,
1270
 
                 _("Failed to find period for periodic transaction"));
1271
 
        begin = pair.first.start;
1272
 
      }
1273
 
 
1274
 
#if DEBUG_ON
1275
 
      DEBUG("budget.generate", "begin = " << *begin);
1276
 
      DEBUG("budget.generate", "date  = " << date);
1277
 
      if (pair.first.finish)
1278
 
        DEBUG("budget.generate", "pair.first.finish = " << *pair.first.finish);
1279
 
#endif
1280
 
 
1281
 
      if (*begin <= date &&
1282
 
          (! pair.first.finish || *begin < *pair.first.finish)) {
1283
 
        post_t& post = *pair.second;
1284
 
 
1285
 
        ++pair.first;
1286
 
        if (! pair.first.start)
1287
 
          posts_to_erase.push_back(i);
1288
 
 
1289
 
        DEBUG("budget.generate", "Reporting budget for "
1290
 
              << post.reported_account()->fullname());
1291
 
 
1292
 
        xact_t& xact = temps.create_xact();
1293
 
        xact.payee = _("Budget transaction");
1294
 
        xact._date = begin;
1295
 
 
1296
 
        post_t& temp = temps.copy_post(post, xact);
1297
 
        temp.amount.in_place_negate();
1298
 
 
1299
 
        if (flags & BUDGET_WRAP_VALUES) {
1300
 
          value_t seq;
1301
 
          seq.push_back(0L);
1302
 
          seq.push_back(temp.amount);
1303
 
 
1304
 
          temp.xdata().compound_value = seq;
1305
 
          temp.xdata().add_flags(POST_EXT_COMPOUND);
1306
 
        }
1307
 
 
1308
 
        item_handler<post_t>::operator()(temp);
1309
 
 
1310
 
        reported = true;
1311
 
      }
1312
 
    }
1313
 
 
1314
 
    foreach (pending_posts_list::iterator& i, posts_to_erase)
1315
 
      pending_posts.erase(i);
1316
 
  } while (reported);
1317
 
}
1318
 
 
1319
 
void budget_posts::operator()(post_t& post)
1320
 
{
1321
 
  bool post_in_budget = false;
1322
 
 
1323
 
  foreach (pending_posts_list::value_type& pair, pending_posts) {
1324
 
    for (account_t * acct = post.reported_account();
1325
 
         acct;
1326
 
         acct = acct->parent) {
1327
 
      if (acct == (*pair.second).reported_account()) {
1328
 
        post_in_budget = true;
1329
 
        // Report the post as if it had occurred in the parent account.
1330
 
        if (post.reported_account() != acct)
1331
 
          post.set_reported_account(acct);
1332
 
        goto handle;
1333
 
      }
1334
 
    }
1335
 
  }
1336
 
 
1337
 
 handle:
1338
 
  if (post_in_budget && flags & BUDGET_BUDGETED) {
1339
 
    report_budget_items(post.date());
1340
 
    item_handler<post_t>::operator()(post);
1341
 
  }
1342
 
  else if (! post_in_budget && flags & BUDGET_UNBUDGETED) {
1343
 
    item_handler<post_t>::operator()(post);
1344
 
  }
1345
 
}
1346
 
 
1347
 
void budget_posts::flush()
1348
 
{
1349
 
  if (flags & BUDGET_BUDGETED)
1350
 
    report_budget_items(terminus);
1351
 
 
1352
 
  item_handler<post_t>::flush();
1353
 
}
1354
 
 
1355
 
void forecast_posts::add_post(const date_interval_t& period, post_t& post)
1356
 
{
1357
 
  date_interval_t i(period);
1358
 
  if (! i.start && ! i.find_period(CURRENT_DATE()))
1359
 
    return;
1360
 
 
1361
 
  generate_posts::add_post(i, post);
1362
 
 
1363
 
  // Advance the period's interval until it is at or beyond the current
1364
 
  // date.
1365
 
  while (*i.start < CURRENT_DATE())
1366
 
    ++i;
1367
 
}
1368
 
 
1369
 
void forecast_posts::flush()
1370
 
{
1371
 
  posts_list passed;
1372
 
  date_t     last = CURRENT_DATE();
1373
 
 
1374
 
  // If there are period transactions to apply in a continuing series until
1375
 
  // the forecast condition is met, generate those transactions now.  Note
1376
 
  // that no matter what, we abandon forecasting beyond the next 5 years.
1377
 
  //
1378
 
  // It works like this:
1379
 
  //
1380
 
  // Earlier, in forecast_posts::add_period_xacts, we cut up all the periodic
1381
 
  // transactions into their components postings, so that we have N "periodic
1382
 
  // postings".  For example, if the user had this:
1383
 
  //
1384
 
  // ~ daily
1385
 
  //   Expenses:Food       $10
1386
 
  //   Expenses:Auto:Gas   $20
1387
 
  // ~ monthly
1388
 
  //   Expenses:Food       $100
1389
 
  //   Expenses:Auto:Gas   $200
1390
 
  //
1391
 
  // We now have 4 periodic postings in `pending_posts'.
1392
 
  //
1393
 
  // Each periodic postings gets its own copy of its parent transaction's
1394
 
  // period, which is modified as we go.  This is found in the second member
1395
 
  // of the pending_posts_list for each posting.
1396
 
  //
1397
 
  // The algorithm below works by iterating through the N periodic postings
1398
 
  // over and over, until each of them mets the termination critera for the
1399
 
  // forecast and is removed from the set.
1400
 
 
1401
 
  while (pending_posts.size() > 0) {
1402
 
    // At each step through the loop, we find the first periodic posting whose
1403
 
    // period contains the earliest starting date.
1404
 
    pending_posts_list::iterator least = pending_posts.begin();
1405
 
    for (pending_posts_list::iterator i = ++pending_posts.begin();
1406
 
         i != pending_posts.end();
1407
 
         i++) {
1408
 
      assert((*i).first.start);
1409
 
      assert((*least).first.start);
1410
 
      if (*(*i).first.start < *(*least).first.start)
1411
 
        least = i;
1412
 
    }
1413
 
 
1414
 
#if !NO_ASSERTS
1415
 
    if ((*least).first.finish)
1416
 
      assert(*(*least).first.start < *(*least).first.finish);
1417
 
#endif
1418
 
 
1419
 
    // If the next date in the series for this periodic posting is more than 5
1420
 
    // years beyond the last valid post we generated, drop it from further
1421
 
    // consideration.
1422
 
    date_t& next(*(*least).first.next);
1423
 
    assert(next > *(*least).first.start);
1424
 
 
1425
 
    if (static_cast<std::size_t>((next - last).days()) >
1426
 
        static_cast<std::size_t>(365U) * forecast_years) {
1427
 
      DEBUG("filters.forecast",
1428
 
            "Forecast transaction exceeds " << forecast_years
1429
 
            << " years beyond today");
1430
 
      pending_posts.erase(least);
1431
 
      continue;
1432
 
    }
1433
 
 
1434
 
    // `post' refers to the posting defined in the period transaction.  We
1435
 
    // make a copy of it within a temporary transaction with the payee
1436
 
    // "Forecast transaction".
1437
 
    post_t& post = *(*least).second;
1438
 
    xact_t& xact = temps.create_xact();
1439
 
    xact.payee   = _("Forecast transaction");
1440
 
    xact._date   = next;
1441
 
    post_t& temp = temps.copy_post(post, xact);
1442
 
 
1443
 
    // Submit the generated posting
1444
 
    DEBUG("filters.forecast",
1445
 
          "Forecast transaction: " << temp.date()
1446
 
          << " " << temp.account->fullname()
1447
 
          << " " << temp.amount);
1448
 
    item_handler<post_t>::operator()(temp);
1449
 
 
1450
 
    // If the generated posting matches the user's report query, check whether
1451
 
    // it also fails to match the continuation condition for the forecast.  If
1452
 
    // it does, drop this periodic posting from consideration.
1453
 
    if (temp.has_xdata() && temp.xdata().has_flags(POST_EXT_MATCHES)) {
1454
 
      DEBUG("filters.forecast", "  matches report query");
1455
 
      bind_scope_t bound_scope(context, temp);
1456
 
      if (! pred(bound_scope)) {
1457
 
        DEBUG("filters.forecast", "  fails to match continuation criteria");
1458
 
        pending_posts.erase(least);
1459
 
        continue;
1460
 
      }
1461
 
    }
1462
 
 
1463
 
    // Increment the 'least', but remove it from pending_posts if it
1464
 
    // exceeds its own boundaries.
1465
 
    ++(*least).first;
1466
 
    if (! (*least).first.start) {
1467
 
      pending_posts.erase(least);
1468
 
      continue;
1469
 
    }
1470
 
  }
1471
 
 
1472
 
  item_handler<post_t>::flush();
1473
 
}
1474
 
 
1475
 
inject_posts::inject_posts(post_handler_ptr handler,
1476
 
                           const string&    tag_list,
1477
 
                           account_t *      master)
1478
 
  : item_handler<post_t>(handler)
1479
 
{
1480
 
  scoped_array<char> buf(new char[tag_list.length() + 1]);
1481
 
  std::strcpy(buf.get(), tag_list.c_str());
1482
 
 
1483
 
  for (char * q = std::strtok(buf.get(), ",");
1484
 
       q;
1485
 
       q = std::strtok(NULL, ",")) {
1486
 
    std::list<string> account_names;
1487
 
    split_string(q, ':', account_names);
1488
 
 
1489
 
    account_t * account =
1490
 
      create_temp_account_from_path(account_names, temps, master);
1491
 
    account->add_flags(ACCOUNT_GENERATED);
1492
 
 
1493
 
    tags_list.push_back
1494
 
      (tags_list_pair(q, tag_mapping_pair(account, tag_injected_set())));
1495
 
  }
1496
 
 
1497
 
  TRACE_CTOR(inject_posts, "post_handler_ptr, string, account_t *");
1498
 
}
1499
 
 
1500
 
void inject_posts::operator()(post_t& post)
1501
 
{
1502
 
  foreach (tags_list_pair& pair, tags_list) {
1503
 
    optional<value_t> tag_value = post.get_tag(pair.first, false);
1504
 
    // When checking if the transaction has the tag, only inject once
1505
 
    // per transaction.
1506
 
    if (! tag_value &&
1507
 
        pair.second.second.find(post.xact) == pair.second.second.end() &&
1508
 
        (tag_value = post.xact->get_tag(pair.first)))
1509
 
      pair.second.second.insert(post.xact);
1510
 
 
1511
 
    if (tag_value) {
1512
 
      xact_t& xact = temps.copy_xact(*post.xact);
1513
 
      xact._date = post.date();
1514
 
      xact.add_flags(ITEM_GENERATED);
1515
 
      post_t& temp = temps.copy_post(post, xact);
1516
 
 
1517
 
      temp.account = pair.second.first;
1518
 
      temp.amount  = tag_value->to_amount();
1519
 
      temp.add_flags(ITEM_GENERATED);
1520
 
 
1521
 
      item_handler<post_t>::operator()(temp);
1522
 
    }
1523
 
  }
1524
 
 
1525
 
  item_handler<post_t>::operator()(post);
1526
 
}
1527
 
 
1528
 
} // namespace ledger