Example #1
0
        /// <summary>
        /// Ported from changed_value_posts::output_revaluation
        /// </summary>
        public void OutputRevaluation(Post post, Date date)
        {
            if (date.IsValid())
            {
                post.XData.Date = date;
            }

            try
            {
                BindScope boundScope = new BindScope(Report, post);
                RepricedTotal = TotalExpr.Calc(boundScope);
            }
            finally
            {
                post.XData.Date = default(Date);
            }

            if (!Value.IsNullOrEmpty(LastTotal))
            {
                Value diff = RepricedTotal - LastTotal;
                if (!Value.IsNullOrEmptyOrFalse(diff))
                {
                    Xact xact = Temps.CreateXact();
                    xact.Payee = "Commodities revalued";
                    xact.Date  = date.IsValid() ? date : post.ValueDate;

                    if (!ForAccountsReports)
                    {
                        FiltersCommon.HandleValue(
                            /* value=         */ diff,
                            /* account=       */ RevaluedAccount,
                            /* xact=          */ xact,
                            /* temps=         */ Temps,
                            /* handler=       */ (PostHandler)Handler,
                            /* date=          */ xact.Date.Value,
                            /* act_date_p=    */ true,
                            /* total=         */ RepricedTotal);
                    }
                    else if (ShowUnrealized)
                    {
                        FiltersCommon.HandleValue(
                            /* value=         */ diff.Negated(),
                            /* account=       */
                            (diff.IsLessThan(Value.Zero) ?
                             LossesEquityAccount :
                             GainsEquityAccount),
                            /* xact=          */ xact,
                            /* temps=         */ Temps,
                            /* handler=       */ (PostHandler)Handler,
                            /* date=          */ xact.Date.Value,
                            /* act_date_p=    */ true,
                            /* total=         */ new Value(),
                            /* direct_amount= */ false,
                            /* mark_visited=  */ true);
                    }
                }
            }
        }
Example #2
0
        /// <summary>
        /// Ported from changed_value_posts::output_intermediate_prices
        /// </summary>
        public void OutputIntermediatePrices(Post post, Date current)
        {
            // To fix BZ#199, examine the balance of last_post and determine whether the
            // price of that amount changed after its date and before the new post's
            // date.  If so, generate an output_revaluation for that price change.
            // Mostly this is only going to occur if the user has a series of pricing
            // entries, since a posting-based revaluation would be seen here as a post.

            Value displayTotal = Value.Clone(LastTotal);

            if (displayTotal.Type == ValueTypeEnum.Sequence)
            {
                Xact xact = Temps.CreateXact();
                xact.Payee = "Commodities revalued";
                xact.Date  = current.IsValid() ? current : post.ValueDate;

                Post temp = Temps.CopyPost(post, xact);
                temp.Flags |= SupportsFlagsEnum.ITEM_GENERATED;

                PostXData xdata = temp.XData;
                if (current.IsValid())
                {
                    xdata.Date = current;
                }

                Logger.Current.Debug("filters.revalued", () => String.Format("intermediate last_total = {0}", LastTotal));

                switch (LastTotal.Type)
                {
                case ValueTypeEnum.Boolean:
                case ValueTypeEnum.Integer:
                    LastTotal.InPlaceCast(ValueTypeEnum.Amount);
                    temp.Amount = LastTotal.AsAmount;
                    break;

                case ValueTypeEnum.Amount:
                    temp.Amount = LastTotal.AsAmount;
                    break;

                case ValueTypeEnum.Balance:
                case ValueTypeEnum.Sequence:
                    xdata.CompoundValue = LastTotal;
                    xdata.Compound      = true;
                    break;

                default:
                    throw new InvalidOperationException();
                }

                BindScope innerScope = new BindScope(Report, temp);
                displayTotal = DisplayTotalExpr.Calc(innerScope);

                Logger.Current.Debug("filters.revalued", () => String.Format("intermediate display_total = {0}", displayTotal));
            }

            switch (displayTotal.Type)
            {
            case ValueTypeEnum.Void:
            case ValueTypeEnum.Integer:
            case ValueTypeEnum.Sequence:
                break;

            case ValueTypeEnum.Amount:
            case ValueTypeEnum.Balance:
            {
                if (displayTotal.Type == ValueTypeEnum.Amount)
                {
                    displayTotal.InPlaceCast(ValueTypeEnum.Balance);
                }

                IDictionary <DateTime, Amount> allPrices = new SortedDictionary <DateTime, Amount>();

                foreach (KeyValuePair <Commodity, Amount> amtComm in displayTotal.AsBalance.Amounts)
                {
                    amtComm.Key.MapPrices((d, a) => allPrices[d] = a, current, post.ValueDate, true);
                }

                // Choose the last price from each day as the price to use
                IDictionary <Date, bool> pricingDates = new SortedDictionary <Date, bool>();

                foreach (KeyValuePair <DateTime, Amount> price in allPrices.Reverse())
                {
                    // This insert will fail if a later price has already been inserted
                    // for that date.
                    var priceDate = (Date)price.Key.Date;
                    Logger.Current.Debug("filters.revalued", () => String.Format("re-inserting {0} at {1}", price.Value, priceDate));
                    pricingDates[priceDate] = true;
                }

                // Go through the time-sorted prices list, outputting a revaluation for
                // each price difference.
                foreach (KeyValuePair <Date, bool> price in pricingDates)
                {
                    OutputRevaluation(post, price.Key);
                    LastTotal = RepricedTotal;
                }
                break;
            }

            default:
                throw new InvalidOperationException();
            }
        }
Example #3
0
        /// <summary>
        /// Ported from changed_value_posts::output_revaluation
        /// </summary>
        public void OutputRevaluation(Post post, Date date)
        {
            if (date.IsValid())
            {
                post.XData.Date = date;
            }

            try
            {
                BindScope boundScope = new BindScope(Report, post);
                RepricedTotal = TotalExpr.Calc(boundScope);
            }
            finally
            {
                post.XData.Date = default(Date);
            }

            Logger.Current.Debug("filters.changed_value", () => String.Format("output_revaluation(last_total)     = {0}", LastTotal));
            Logger.Current.Debug("filters.changed_value", () => String.Format("output_revaluation(repriced_total) = {0}", RepricedTotal));

            if (!Value.IsNullOrEmpty(LastTotal))
            {
                Value diff = RepricedTotal - LastTotal;
                if (!Value.IsNullOrEmptyOrFalse(diff))
                {
                    Logger.Current.Debug("filters.changed_value", () => String.Format("output_revaluation(strip(diff)) = {0}", diff.StripAnnotations(Report.WhatToKeep())));

                    Xact xact = Temps.CreateXact();
                    xact.Payee = "Commodities revalued";
                    xact.Date  = date.IsValid() ? date : post.ValueDate;

                    if (!ForAccountsReports)
                    {
                        FiltersCommon.HandleValue(
                            /* value=         */ diff,
                            /* account=       */ RevaluedAccount,
                            /* xact=          */ xact,
                            /* temps=         */ Temps,
                            /* handler=       */ (PostHandler)Handler,
                            /* date=          */ xact.Date.Value,
                            /* act_date_p=    */ true,
                            /* total=         */ RepricedTotal);
                    }
                    else if (ShowUnrealized)
                    {
                        FiltersCommon.HandleValue(
                            /* value=         */ diff.Negated(),
                            /* account=       */
                            (diff.IsLessThan(Value.Zero) ?
                             LossesEquityAccount :
                             GainsEquityAccount),
                            /* xact=          */ xact,
                            /* temps=         */ Temps,
                            /* handler=       */ (PostHandler)Handler,
                            /* date=          */ xact.Date.Value,
                            /* act_date_p=    */ true,
                            /* total=         */ new Value(),
                            /* direct_amount= */ false,
                            /* mark_visited=  */ true);
                    }
                }
            }
        }
Example #4
0
        /// <summary>
        /// Ported from report_budget_items
        /// </summary>
        public void ReportBudgetItems(Date date)
        {
            if (!PendingPosts.Any())
            {
                return;
            }

            bool reported;

            do
            {
                IList <PendingPostsPair> postsToErase = new List <PendingPostsPair>();
                reported = false;

                foreach (PendingPostsPair pair in PendingPosts)
                {
                    Date?begin = pair.DateInterval.Start;
                    if (!begin.HasValue)
                    {
                        Date?rangeBegin = null;
                        if (pair.DateInterval.Range != null)
                        {
                            rangeBegin = pair.DateInterval.Range.Begin;
                        }

                        Logger.Debug(DebugBudgetGenerate, () => "Finding period for pending post");
                        if (!pair.DateInterval.FindPeriod(rangeBegin ?? date))
                        {
                            continue;
                        }
                        if (!pair.DateInterval.Start.HasValue)
                        {
                            throw new LogicError(LogicError.ErrorMessageFailedToFindPeriodForPeriodicTransaction);
                        }
                        begin = pair.DateInterval.Start;
                    }

                    Logger.Debug(DebugBudgetGenerate, () => String.Format("begin = {0}", begin));
                    Logger.Debug(DebugBudgetGenerate, () => String.Format("date  = {0}", date));
                    if (pair.DateInterval.Finish.HasValue)
                    {
                        Logger.Debug(DebugBudgetGenerate, () => String.Format("pair.first.finish = {0}", pair.DateInterval.Finish));
                    }

                    if (begin <= date && (!pair.DateInterval.Finish.HasValue || begin < pair.DateInterval.Finish))
                    {
                        Post post = pair.Post;

                        pair.DateInterval++;
                        if (!pair.DateInterval.Start.HasValue)
                        {
                            postsToErase.Add(pair);
                        }

                        Logger.Debug(DebugBudgetGenerate, () => "Reporting budget for " + post.ReportedAccount.FullName);

                        Xact xact = Temps.CreateXact();
                        xact.Payee = "Budget transaction";
                        xact.Date  = begin.Value;

                        Post temp = Temps.CopyPost(post, xact);
                        temp.Amount.InPlaceNegate();

                        if (Flags.HasFlag(ReportBudgetFlags.BUDGET_WRAP_VALUES))
                        {
                            Value seq = new Value();
                            seq.PushBack(Value.Get(0));
                            seq.PushBack(Value.Get(temp.Amount));

                            temp.XData.CompoundValue = seq;
                            temp.XData.Compound      = true;
                        }

                        base.Handle(temp);

                        reported = true;
                    }
                }

                foreach (PendingPostsPair pair in postsToErase)
                {
                    PendingPosts.Remove(pair);
                }
            }while (reported);
        }
Example #5
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();
        }
Example #6
0
        public override void Flush()
        {
            if (Interval.Duration == null)
            {
                base.Flush();
                return;
            }

            // Sort all the postings we saw by date ascending
            // [DM] Enumerable.OrderBy is a stable sort that preserve original positions for equal items
            AllPosts = AllPosts.OrderBy(p => p, new IntervalPostCompare()).ToList();

            // only if the interval has no start use the earliest post
            if (!(Interval.Begin.HasValue && Interval.FindPeriod(Interval.Begin.Value)))
            {
                // Determine the beginning interval by using the earliest post
                if (AllPosts.Any() && !Interval.FindPeriod(AllPosts.First().GetDate()))
                {
                    throw new LogicError(LogicError.ErrorMessageFailedToFindPeriodForIntervalReport);
                }
            }

            // Walk the interval forward reporting all posts within each one
            // before moving on, until we reach the end of all_posts
            bool sawPosts = false;

            for (int i = 0; i < AllPosts.Count;)
            {
                Post post = AllPosts[i];

                Logger.Current.Debug("filters.interval", () => String.Format("Considering post {0} = {1}", post.GetDate(), post.Amount));
                Logger.Current.Debug("filters.interval", () => String.Format("interval is:{0}", DebugInterval(Interval)));

                if (Interval.Finish.HasValue && post.GetDate() >= Interval.Finish.Value)
                {
                    throw new InvalidOperationException("assert(! interval.finish || post->date() < *interval.finish)");
                }

                if (Interval.WithinPeriod(post.GetDate()))
                {
                    Logger.Current.Debug("filters.interval", () => "Calling subtotal_posts::operator()");
                    base.Handle(post);
                    ++i;
                    sawPosts = true;
                }
                else
                {
                    if (sawPosts)
                    {
                        Logger.Current.Debug("filters.interval", () => "Calling subtotal_posts::report_subtotal()");
                        ReportSubtotal(Interval);
                        sawPosts = false;
                    }
                    else if (GenerateEmptyPosts)
                    {
                        // Generate a null posting, so the intervening periods can be
                        // seen when -E is used, or if the calculated amount ends up
                        // being non-zero
                        Xact nullXact = Temps.CreateXact();
                        nullXact.Date = Interval.InclusiveEnd.Value;

                        Post nullPost = Temps.CreatePost(nullXact, EmptytAccount);
                        nullPost.Flags |= SupportsFlagsEnum.POST_CALCULATED;
                        nullPost.Amount = new Amount(0);

                        base.Handle(nullPost);
                        ReportSubtotal(Interval);
                    }

                    Logger.Current.Debug("filters.interval", () => "Advancing interval");
                    ++Interval;
                }
            }

            // If the last postings weren't reported, do so now.
            if (sawPosts)
            {
                Logger.Current.Debug("filters.interval", () => "Calling subtotal_posts::report_subtotal() at end");
                ReportSubtotal(Interval);
            }

            // Tell our parent class to flush
            base.Flush();
        }