public override void Handle(Post post) { foreach (Tuple <string, Account, ISet <Xact> > pair in TagsList) { Value tagValue = post.GetTag(pair.Item1, false); // When checking if the transaction has the tag, only inject once // per transaction. if (Value.IsNullOrEmptyOrFalse(tagValue) && !pair.Item3.Contains(post.Xact)) { tagValue = post.Xact.GetTag(pair.Item1); if (!Value.IsNullOrEmptyOrFalse(tagValue)) { pair.Item3.Add(post.Xact); } } if (!Value.IsNullOrEmptyOrFalse(tagValue)) { Xact xact = Temps.CopyXact(post.Xact); xact.Date = post.GetDate(); xact.Flags |= SupportsFlagsEnum.ITEM_GENERATED; Post temp = Temps.CopyPost(post, xact); temp.Account = pair.Item2; temp.Amount = tagValue.AsAmount; temp.Flags |= SupportsFlagsEnum.ITEM_GENERATED; base.Handle(temp); } } base.Handle(post); }
/// <summary> /// Ported from void transfer_details::operator()(post_t& post) /// </summary> public override void Handle(Post post) { Xact xact = Temps.CopyXact(post.Xact); xact.Date = post.GetDate(); Post temp = Temps.CopyPost(post, xact); temp.State = post.State; BindScope boundScope = new BindScope(Scope, temp); Value substitute = Expr.Calc(boundScope); if (!Value.IsNullOrEmpty(substitute)) { switch (WhichElement) { case TransferDetailsElementEnum.SET_DATE: temp.Date = substitute.AsDate; break; case TransferDetailsElementEnum.SET_ACCOUNT: { string accountName = substitute.AsString; if (!String.IsNullOrEmpty(accountName) && !accountName.EndsWith(":")) { Account prevAccount = temp.Account; temp.Account.RemovePost(temp); accountName += ":" + prevAccount.FullName; string[] accountNames = accountName.Split(':'); temp.Account = FiltersCommon.CreateTempAccountFromPath(accountNames, Temps, xact.Journal.Master); temp.Account.AddPost(temp); temp.Account.SetFlags(prevAccount); if (prevAccount.HasXData) { temp.Account.XData.SetFlags(prevAccount.XData); } } break; } case TransferDetailsElementEnum.SET_PAYEE: xact.Payee = substitute.AsString; break; } } base.Handle(temp); }
public override void Handle(Post post) { bool copyXactDetails = false; if (LastXact != post.Xact) { Temps.CopyXact(post.Xact); LastXact = post.Xact; copyXactDetails = true; } Xact xact = Temps.LastXact; xact.Code = null; if (copyXactDetails) { xact.CopyDetails(post.Xact); string buf = String.Format("{0}{1}{0}", post.Xact.Payee, IntegerGen.Value()); xact.Payee = SHA1.GetHash(buf); xact.Note = null; } else { xact.Journal = post.Xact.Journal; } IList <string> accountNames = new List <string>(); for (Account acct = post.Account; acct != null; acct = acct.Parent) { string buf = String.Format("{0}{1}{2}", IntegerGen.Value(), acct, acct.FullName); accountNames.Add(SHA1.GetHash(buf)); } Account newAccount = FiltersCommon.CreateTempAccountFromPath(accountNames, Temps, xact.Journal.Master); Post temp = Temps.CopyPost(post, xact, newAccount); temp.Note = null; temp.Flags |= SupportsFlagsEnum.POST_ANONYMIZED; RenderCommodity(temp.Amount); if (temp.Amount.HasAnnotation) { temp.Amount.Annotation.Tag = null; if (temp.Amount.Annotation.Price != null) { RenderCommodity(temp.Amount.Annotation.Price); } } if (temp.Cost != null) { RenderCommodity(temp.Cost); } if (temp.AssignedAmount != null) { RenderCommodity(temp.AssignedAmount); } base.Handle(temp); }
/// <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(); } }
/// <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); }
/// <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(); }