Revised the forecasting logic
There were cases involving sorting when the old logic could go on forever.
This commit is contained in:
parent
0a6f1a6328
commit
da0f3bf14a
1 changed files with 66 additions and 32 deletions
|
|
@ -806,6 +806,7 @@ void forecast_posts::add_post(const date_interval_t& period, post_t& post)
|
|||
{
|
||||
generate_posts::add_post(period, post);
|
||||
|
||||
// Advance the period's interval until it is at or beyond the current date.
|
||||
date_interval_t& i = pending_posts.back().first;
|
||||
if (! i.start) {
|
||||
if (! i.find_period(CURRENT_DATE()))
|
||||
|
|
@ -820,59 +821,92 @@ void forecast_posts::add_post(const date_interval_t& period, post_t& post)
|
|||
void forecast_posts::flush()
|
||||
{
|
||||
posts_list passed;
|
||||
date_t last;
|
||||
date_t last = CURRENT_DATE();
|
||||
|
||||
// If there are period transactions to apply in a continuing series until
|
||||
// the forecast condition is met, generate those transactions now. Note
|
||||
// that no matter what, we abandon forecasting beyond the next 5 years.
|
||||
//
|
||||
// It works like this:
|
||||
//
|
||||
// Earlier, in forecast_posts::add_period_xacts, we cut up all the periodic
|
||||
// transactions into their components postings, so that we have N "periodic
|
||||
// postings". For example, if the user had this:
|
||||
//
|
||||
// ~ daily
|
||||
// Expenses:Food $10
|
||||
// Expenses:Auto:Gas $20
|
||||
// ~ monthly
|
||||
// Expenses:Food $100
|
||||
// Expenses:Auto:Gas $200
|
||||
//
|
||||
// We now have 4 periodic postings in `pending_posts'.
|
||||
//
|
||||
// Each periodic postings gets its own copy of its parent transaction's
|
||||
// period, which is modified as we go. This is found in the second member
|
||||
// of the pending_posts_list for each posting.
|
||||
//
|
||||
// The algorithm below works by iterating through the N periodic postings
|
||||
// over and over, until each of them mets the termination critera for the
|
||||
// forecast and is removed from the set.
|
||||
|
||||
while (pending_posts.size() > 0) {
|
||||
// At each step through the loop, we find the first periodic posting whose
|
||||
// period contains the earliest starting date.
|
||||
pending_posts_list::iterator least = pending_posts.begin();
|
||||
for (pending_posts_list::iterator i = ++pending_posts.begin();
|
||||
i != pending_posts.end();
|
||||
i++)
|
||||
i++) {
|
||||
if (*(*i).first.start < *(*least).first.start)
|
||||
least = i;
|
||||
}
|
||||
|
||||
date_t& begin = *(*least).first.start;
|
||||
if ((*least).first.end)
|
||||
assert(begin < *(*least).first.end);
|
||||
|
||||
if ((*least).first.end && begin >= *(*least).first.end) {
|
||||
// If the next date in the series for this periodic posting is more than 5
|
||||
// years beyond the last valid post we generated, drop it from further
|
||||
// consideration.
|
||||
date_t next = *(*least).first.next;
|
||||
assert(next > begin);
|
||||
|
||||
if ((next - last).days() > 365 * 5) {
|
||||
DEBUG("filters.forecast",
|
||||
"Forecast transaction exceeds 5 years beyond today");
|
||||
pending_posts.erase(least);
|
||||
passed.remove((*least).second);
|
||||
continue;
|
||||
}
|
||||
|
||||
post_t& post = *(*least).second;
|
||||
|
||||
xact_t& xact = temps.create_xact();
|
||||
xact.payee = _("Forecast transaction");
|
||||
xact._date = begin;
|
||||
|
||||
post_t& temp = temps.copy_post(post, xact);
|
||||
|
||||
date_t next = *(*least).first.next;
|
||||
begin = next;
|
||||
++(*least).first;
|
||||
|
||||
if (next < begin || (is_valid(last) && (next - last).days() > 365 * 5))
|
||||
break;
|
||||
begin = next;
|
||||
// `post' refers to the posting defined in the period transaction. We
|
||||
// make a copy of it within a temporary transaction with the payee
|
||||
// "Forecast transaction".
|
||||
post_t& post = *(*least).second;
|
||||
xact_t& xact = temps.create_xact();
|
||||
xact.payee = _("Forecast transaction");
|
||||
xact._date = begin;
|
||||
post_t& temp = temps.copy_post(post, xact);
|
||||
|
||||
// Submit the generated posting
|
||||
DEBUG("filters.forecast",
|
||||
"Forecast transaction: " << temp.date()
|
||||
<< " " << temp.account->fullname()
|
||||
<< " " << temp.amount);
|
||||
item_handler<post_t>::operator()(temp);
|
||||
|
||||
// If the generated posting matches the user's report query, check whether
|
||||
// it also fails to match the continuation condition for the forecast. If
|
||||
// it does, drop this periodic posting from consideration.
|
||||
if (temp.has_xdata() && temp.xdata().has_flags(POST_EXT_MATCHES)) {
|
||||
DEBUG("filters.forecast", " matches report query");
|
||||
bind_scope_t bound_scope(context, temp);
|
||||
if (! pred(bound_scope))
|
||||
break;
|
||||
last = temp.date();
|
||||
passed.clear();
|
||||
} else {
|
||||
bool found = false;
|
||||
foreach (post_t * x, passed)
|
||||
if (x == &post) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (! found) {
|
||||
passed.push_back(&post);
|
||||
if (passed.size() >= pending_posts.size())
|
||||
break;
|
||||
if (! pred(bound_scope)) {
|
||||
DEBUG("filters.forecast", " fails to match continuation criteria");
|
||||
pending_posts.erase(least);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue