Example #1
0
        public override void Handle(Post post)
        {
            BindScope boundScope = new BindScope(Context, post);

            if (Pred.Calc(boundScope).Bool)
            {
                post.XData.Matches = true;
                base.Handle(post);
            }
        }
Example #2
0
        /// <summary>
        /// Ported from forecast_posts::flush
        /// </summary>
        public override void Flush()
        {
            IList <Post> passed = new List <Post>();
            Date         last   = TimesCommon.Current.CurrentDate;

            // 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 (PendingPosts.Any())
            {
                // At each step through the loop, we find the first periodic posting whose
                // period contains the earliest starting date.
                PendingPostsPair least = PendingPosts.First();
                foreach (PendingPostsPair i in PendingPosts)
                {
                    if (!i.DateInterval.Start.HasValue)
                    {
                        throw new InvalidOperationException("Start is empty");
                    }
                    if (!least.DateInterval.Start.HasValue)
                    {
                        throw new InvalidOperationException("least.Start is empty");
                    }

                    if (i.DateInterval.Start < least.DateInterval.Start)
                    {
                        least = i;
                    }
                }

                // 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 next = least.DateInterval.Next.Value;
                if (next <= least.DateInterval.Start)
                {
                    throw new InvalidOperationException("next <= least.DateInterval.Start");
                }

                if (((next - last).Days) > (365 * ForecastYears))
                {
                    Logger.Current.Debug("filters.forecast", () => String.Format("Forecast transaction exceeds {0} years beyond today", ForecastYears));
                    PendingPosts.Remove(least);
                    continue;
                }

                // `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 post = least.Post;
                Xact xact = Temps.CreateXact();
                xact.Payee = "Forecast transaction";
                xact.Date  = next;
                Post temp = Temps.CopyPost(post, xact);

                // Submit the generated posting
                Logger.Current.Debug("filters.forecast", () => String.Format("Forecast transaction: {0} {1} {2}", temp.GetDate(), temp.Account.FullName, temp.Amount));
                base.Handle(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.HasXData && temp.XData.Matches)
                {
                    Logger.Current.Debug("filters.forecast", () => "  matches report query");
                    BindScope boundScope = new BindScope(Context, temp);
                    if (!Pred.Calc(boundScope).Bool)
                    {
                        Logger.Current.Debug("filters.forecast", () => "  fails to match continuation criteria");
                        PendingPosts.Remove(least);
                        continue;
                    }
                }

                // Increment the 'least', but remove it from pending_posts if it
                // exceeds its own boundaries.
                ++least.DateInterval;
                if (!least.DateInterval.Start.HasValue)
                {
                    PendingPosts.Remove(least);
                    continue;
                }
            }

            base.Flush();
        }