public void Journal_AddXact_DoesNotAllowToAddTwoXactsWithTheSameUUIDButDifferentPostAmounts() { Account account1 = new Account(); Account account2 = new Account(); Journal journal = new Journal(); Xact xact1 = new Xact(); xact1.SetTag("UUID", Value.StringValue("val1")); xact1.AddPost(new Post() { Account = account1, Amount = new Amount(10) }); xact1.AddPost(new Post() { Account = account2, Amount = new Amount(-10) }); Xact xact2 = new Xact(); xact2.SetTag("UUID", Value.StringValue("val1")); xact2.AddPost(new Post() { Account = account1, Amount = new Amount(5) }); xact2.AddPost(new Post() { Account = account2, Amount = new Amount(-5) }); journal.AddXact(xact1); Assert.Throws <RuntimeError>(() => journal.AddXact(xact2)); }
public Post CreatePost(Xact xact, Account account, bool bidirLink = true) { if (PostTemps == null) { PostTemps = new List <Post>(); } Post temp = new Post(account); temp.Flags |= SupportsFlagsEnum.ITEM_TEMP; temp.Account = account; temp.Account.AddPost(temp); PostTemps.Add(temp); if (bidirLink) { xact.AddPost(temp); } else { temp.Xact = xact; } return(temp); }
public void Journal_Valid_ReturnsFalseIfXactNotValid() { Journal journal = new Journal(); journal.Master = new Account(); Assert.True(journal.Valid()); Xact xact = new Xact(); xact.AddPost(new Post(journal.Master, new Amount(10))); xact.AddPost(new Post(journal.Master, new Amount(-10))); journal.AddXact(xact); Assert.False(xact.Valid()); // [DM] - Xact is not valid (but finalizable to add to the journal) because of no date. Assert.False(journal.Valid()); }
public void Xact_AddPost_PopulatesXactInPost() { Xact xact = new Xact(); Post post = new Post(); xact.AddPost(post); Assert.AreEqual(xact, post.Xact); }
public void Xact_Detach_FailsIfThereIsTempPost() { Account account = new Account(); Xact xact = new Xact(); Post post = new Post(account); post.Flags = SupportsFlagsEnum.ITEM_TEMP; xact.AddPost(post); xact.Detach(); }
public void Xact_Detach_FailsIfThereIsTempPost() { Account account = new Account(); Xact xact = new Xact(); Post post = new Post(account); post.Flags = SupportsFlagsEnum.ITEM_TEMP; xact.AddPost(post); Assert.Throws <InvalidOperationException>(() => xact.Detach()); }
public void Xact_Detach_RemovesPostsFromAccounts() { Account account = new Account(); Xact xact = new Xact(); Post post = new Post(account); xact.AddPost(post); account.Posts.Add(post); xact.Detach(); Assert.AreEqual(0, account.Posts.Count); // Post has been removed }
public void CanStartNLedger() { var engine = new ServiceEngine( configureContext: context => { context.IsAtty = true; }, createCustomProvider: mem => { mem.Attach(w => new MemoryAnsiTextWriter(w)); return null; }); var session = engine.CreateSession("-f ledger.dat"); var journal = session.GlobalScope.Session.Journal; Assert.True(session.IsActive); var response = session.ExecuteCommand("bal checking --account=code"); session.ExecuteJournalAction((j) => { Xact xact = new Xact(); xact.AddPost(new Post(journal.Master, new Amount(10, new NLedger.Commodities.Commodity(session.MainApplicationContext.CommodityPool, new NLedger.Commodities.CommodityBase("$"))))); xact.AddPost(new Post(journal.Master, new Amount(-10, new NLedger.Commodities.Commodity(session.MainApplicationContext.CommodityPool, new NLedger.Commodities.CommodityBase("$"))))); j.AddXact(xact); //j.Master.AddAccount(new Account() { f}) }); // response = session.ExecuteCommand("bal checking --account=code"); //session.ExecuteCommand //j response = session.ExecuteCommand("bal"); //session.Dispose(); Assert.False(response.HasErrors); Assert.Equal(2, session.GlobalScope.Session.Journal.Xacts.Count); //session.ServiceEngine. //Assert.Equal(BalCheckingOutputText.Replace("\r", "").Trim(), response.OutputText.Trim()); }
public void Journal_AddXact_AllowsToAddDifferentUUID() { Account account1 = new Account(); Account account2 = new Account(); Journal journal = new Journal(); Xact xact1 = new Xact(); xact1.SetTag("UUID", Value.StringValue("val1")); xact1.AddPost(new Post() { Account = account1, Amount = new Amount(10) }); xact1.AddPost(new Post() { Account = account2, Amount = new Amount(-10) }); Xact xact2 = new Xact(); xact2.SetTag("UUID", Value.StringValue("val2")); xact2.AddPost(new Post() { Account = account1, Amount = new Amount(10) }); xact2.AddPost(new Post() { Account = account2, Amount = new Amount(-10) }); journal.AddXact(xact1); journal.AddXact(xact2); Assert.Equal(xact1, journal.ChecksumMapping["val1"]); Assert.Equal(xact2, journal.ChecksumMapping["val2"]); }
public void Journal_AddXact_DoesNotAllowToAddTwoXactsWithTheSameUUIDButDifferentNumberOfPosts() { Account account = new Account(); Journal journal = new Journal(); Xact xact1 = new Xact(); xact1.SetTag("UUID", Value.StringValue("val1")); xact1.AddPost(new Post() { Account = account, Amount = new Amount(10) }); xact1.AddPost(new Post() { Account = account, Amount = new Amount(-10) }); Xact xact2 = new Xact(); xact2.SetTag("UUID", Value.StringValue("val1")); xact2.AddPost(new Post() { Account = account, Amount = new Amount(10) }); xact2.AddPost(new Post() { Account = account, Amount = new Amount(-5) }); xact2.AddPost(new Post() { Account = account, Amount = new Amount(-5) }); journal.AddXact(xact1); journal.AddXact(xact2); }
public void Xact_Detach_DoesNothingIf_ITEM_TEMP() { Account account = new Account(); Xact xact = new Xact(); xact.Flags = SupportsFlagsEnum.ITEM_TEMP; Post post = new Post(account); post.Flags = SupportsFlagsEnum.ITEM_TEMP; xact.AddPost(post); account.Posts.Add(post); xact.Detach(); Assert.AreEqual(1, account.Posts.Count); // Post has not been removed }
public Post CopyPost(Post origin, Xact xact, Account account = null) { if (PostTemps == null) { PostTemps = new List <Post>(); } Post temp = new Post(origin); PostTemps.Add(temp); temp.Flags |= SupportsFlagsEnum.ITEM_TEMP; if (account != null) { temp.Account = account; } temp.Account.AddPost(temp); xact.AddPost(temp); return(temp); }
/// <remarks>ported from create_timelog_xact</remarks> public static void CreateTimelogXact(TimeXact inEvent, TimeXact outEvent, ParseContext context) { Xact curr = new Xact() { Date = (Date)inEvent.Checkin.Date, Code = outEvent.Desc, // if it wasn't used above Payee = inEvent.Desc, Pos = inEvent.Position }; if (!String.IsNullOrEmpty(inEvent.Note)) { curr.AppendNote(inEvent.Note, context.Scope); } string buf = String.Format("{0}s", (outEvent.Checkin - inEvent.Checkin).TotalSeconds); Amount amt = new Amount(); amt.Parse(ref buf); Validator.Verify(() => amt.Valid()); Post post = new Post(inEvent.Account, amt); post.Flags |= SupportsFlagsEnum.POST_VIRTUAL; post.State = outEvent.Completed ? ItemStateEnum.Cleared : ItemStateEnum.Uncleared; post.Pos = inEvent.Position; post.Checkin = inEvent.Checkin; post.Checkout = outEvent.Checkin; curr.AddPost(post); inEvent.Account.AddPost(post); if (!context.Journal.AddXact(curr)) { throw new ParseError(ParseError.ParseError_FailedToRecordOutTimelogTransaction); } }
/// <summary> /// Ported from draft_t::insert /// </summary> public Xact Insert(Journal journal) { if (Tmpl == null) { return(null); } if (Tmpl.PayeeMask == null) { throw new RuntimeError(RuntimeError.ErrorMessageXactCommandRequiresAtLeastAPayee); } Xact matching = null; Xact added = new Xact(); Xact xact = Lookup.LookupProbableAccount(Tmpl.PayeeMask.ToString(), journal.Xacts.Reverse()).Item1; if (xact != null) { Logger.Current.Debug("draft.xact", () => String.Format("Found payee by lookup: transaction on line {0}", xact.Pos.BegLine)); matching = xact; } else { matching = journal.Xacts.LastOrDefault(x => Tmpl.PayeeMask.Match(x.Payee)); if (matching != null) { Logger.Current.Debug("draft.xact", () => String.Format("Found payee match: transaction on line {0}", matching.Pos.BegLine)); } } if (!Tmpl.Date.HasValue) { added.Date = TimesCommon.Current.CurrentDate; Logger.Current.Debug("draft.xact", () => "Setting date to current date"); } else { added.Date = Tmpl.Date; Logger.Current.Debug("draft.xact", () => String.Format("Setting date to template date: {0}", Tmpl.Date)); } added.State = ItemStateEnum.Uncleared; if (matching != null) { added.Payee = matching.Payee; //added->code = matching->code; //added->note = matching->note; Logger.Current.Debug("draft.xact", () => String.Format("Setting payee from match: {0}", added.Payee)); } else { added.Payee = Tmpl.PayeeMask.ToString(); Logger.Current.Debug("draft.xact", () => String.Format("Setting payee from template: {0}", added.Payee)); } if (!String.IsNullOrEmpty(Tmpl.Code)) { added.Code = Tmpl.Code; Logger.Current.Debug("draft.xact", () => String.Format("Now setting code from template: {0}", added.Code)); } if (!String.IsNullOrEmpty(Tmpl.Note)) { added.Note = Tmpl.Note; Logger.Current.Debug("draft.xact", () => String.Format("Now setting note from template: {0}", added.Note)); } if (!Tmpl.Posts.Any()) { if (matching != null) { Logger.Current.Debug("draft.xact", () => "Template had no postings, copying from match"); foreach (Post post in matching.Posts) { added.AddPost(new Post(post) { State = ItemStateEnum.Uncleared }); } } else { throw new RuntimeError(String.Format(RuntimeError.ErrorMessageNoAccountsAndNoPastTransactionMatchingSmth, Tmpl.PayeeMask.ToString())); } } else { Logger.Current.Debug("draft.xact", () => "Template had postings"); bool anyPostHasAmount = Tmpl.Posts.Any(p => (bool)p.Amount); if (anyPostHasAmount) { Logger.Current.Debug("draft.xact", () => " and at least one has an amount specified"); } foreach (DraftXactPostTemplate post in Tmpl.Posts) { Post newPost = null; Commodity foundCommodity = null; if (matching != null) { if (post.AccountMask != null) { Logger.Current.Debug("draft.xact", () => "Looking for matching posting based on account mask"); Post x = matching.Posts.FirstOrDefault(p => post.AccountMask.Match(p.Account.FullName)); if (x != null) { newPost = new Post(x); Logger.Current.Debug("draft.xact", () => String.Format("Founding posting from line {0}", x.Pos.BegLine)); } } else { if (post.From) { Post x = matching.Posts.LastOrDefault(p => p.MustBalance); if (x != null) { newPost = new Post(x); Logger.Current.Debug("draft.xact", () => "Copied last real posting from matching"); } } else { Post x = matching.Posts.FirstOrDefault(p => p.MustBalance); if (x != null) { newPost = new Post(x); Logger.Current.Debug("draft.xact", () => "Copied first real posting from matching"); } } } } if (newPost == null) { newPost = new Post(); Logger.Current.Debug("draft.xact", () => "New posting was NULL, creating a blank one"); } if (newPost.Account == null) { Logger.Current.Debug("draft.xact", () => "New posting still needs an account"); if (post.AccountMask != null) { Logger.Current.Debug("draft.xact", () => "The template has an account mask"); Account acct = journal.FindAccountRe(post.AccountMask.ToString()); if (acct != null) { Logger.Current.Debug("draft.xact", () => "Found account as a regular expression"); } else { acct = journal.FindAccount(post.AccountMask.ToString()); if (acct != null) { Logger.Current.Debug("draft.xact", () => "Found (or created) account by name"); } } // Find out the default commodity to use by looking at the last // commodity used in that account foreach (Xact j in journal.Xacts.Reverse()) { Post x = j.Posts.FirstOrDefault(p => p.Account == acct && !(p.Amount == null || p.Amount.IsEmpty)); if (x != null) { newPost = new Post(x); Logger.Current.Debug("draft.xact", () => "Found account in journal postings, setting new posting"); break; } } newPost.Account = acct; Logger.Current.Debug("draft.xact", () => String.Format("Set new posting's account to: {0}", acct.FullName)); } else { if (post.From) { newPost.Account = journal.FindAccount("Liabilities:Unknown"); Logger.Current.Debug("draft.xact", () => "Set new posting's account to: Liabilities:Unknown"); } else { newPost.Account = journal.FindAccount("Expenses:Unknown"); Logger.Current.Debug("draft.xact", () => "Set new posting's account to: Expenses:Unknown"); } } } if (newPost.Account == null) { throw new InvalidOperationException("assert(new_post->account)"); } if (newPost != null && !(newPost.Amount == null || newPost.Amount.IsEmpty)) { foundCommodity = newPost.Amount.Commodity; if (anyPostHasAmount) { newPost.Amount = new Amount(); Logger.Current.Debug("draft.xact", () => "New posting has an amount, but we cleared it"); } else { anyPostHasAmount = true; Logger.Current.Debug("draft.xact", () => "New posting has an amount, and we're using it"); } } if ((bool)post.Amount) { newPost.Amount = post.Amount; Logger.Current.Debug("draft.xact", () => "Copied over posting amount"); if (post.From) { newPost.Amount.InPlaceNegate(); Logger.Current.Debug("draft.xact", () => "Negated new posting amount"); } } if ((bool)post.Cost) { if (post.Cost.Sign < 0) { throw new ParseError(ParseError.ParseError_PostingCostMayNotBeNegative); } post.Cost.InPlaceUnround(); if (post.CostOperator == "@") { // For the sole case where the cost might be uncommoditized, // guarantee that the commodity of the cost after multiplication // is the same as it was before. Commodity costCommodity = post.Cost.Commodity; post.Cost = post.Cost.Multiply(newPost.Amount); post.Cost.SetCommodity(costCommodity); } else if (newPost.Amount.Sign < 0) { newPost.Cost.InPlaceNegate(); } newPost.Cost = post.Cost; Logger.Current.Debug("draft.xact", () => "Copied over posting cost"); } if (foundCommodity != null && !(newPost.Amount == null) && !(newPost.Amount.IsEmpty) && !(newPost.Amount.HasCommodity)) { newPost.Amount.SetCommodity(foundCommodity); Logger.Current.Debug("draft.xact", () => String.Format("Set posting amount commodity to: {0}", newPost.Amount.Commodity)); newPost.Amount = newPost.Amount.Rounded(); Logger.Current.Debug("draft.xact", () => String.Format("Rounded posting amount to: {0}", newPost.Amount)); } added.AddPost(newPost); newPost.Account.AddPost(newPost); newPost.State = ItemStateEnum.Uncleared; Logger.Current.Debug("draft.xact", () => "Added new posting to derived entry"); } } if (!journal.AddXact(added)) { throw new RuntimeError(RuntimeError.ErrorMessageFailedToFinalizeDerivedTransactionCheckCommodities); } return(added); }
public Xact ReadXact(bool richData) { string line = NextLine(Context.Reader); string origLine = line; if (String.IsNullOrEmpty(line) || !Index.Any()) { return(null); } Context.LineNum++; Xact xact = new Xact(); Post post = new Post(); xact.State = ItemStateEnum.Cleared; xact.Pos = new ItemPosition() { PathName = Context.PathName, BegPos = (int)Context.Reader.Position, BegLine = Context.LineNum, Sequence = Context.Sequence++ }; post.Xact = xact; post.Pos = new ItemPosition() { PathName = Context.PathName, BegPos = (int)Context.Reader.Position, BegLine = Context.LineNum, Sequence = Context.Sequence++ }; post.State = ItemStateEnum.Cleared; post.Account = null; int n = 0; Amount amt = null; string total = null; string field; while (!String.IsNullOrEmpty(line) && n < Index.Count) { field = ReadField(ref line); switch (Index[n]) { case CsvHeadersEnum.FIELD_DATE: xact.Date = TimesCommon.Current.ParseDate(field); break; case CsvHeadersEnum.FIELD_DATE_AUX: if (!String.IsNullOrEmpty(field)) { xact.DateAux = TimesCommon.Current.ParseDate(field); } break; case CsvHeadersEnum.FIELD_CODE: if (!String.IsNullOrEmpty(field)) { xact.Code = field; } break; case CsvHeadersEnum.FIELD_PAYEE: { bool found = false; foreach (Tuple <Mask, string> value in Context.Journal.PayeeAliasMappings) { Logger.Current.Debug("csv.mappings", () => String.Format("Looking for payee mapping: {0}", value.Item1)); if (value.Item1.Match(field)) { xact.Payee = value.Item2; found = true; break; } } if (!found) { xact.Payee = field; } break; } case CsvHeadersEnum.FIELD_AMOUNT: { amt = new Amount(); amt.Parse(ref field, AmountParseFlagsEnum.PARSE_NO_REDUCE); if (!amt.HasCommodity && CommodityPool.Current.DefaultCommodity != null) { amt.SetCommodity(CommodityPool.Current.DefaultCommodity); } post.Amount = amt; break; } case CsvHeadersEnum.FIELD_COST: { amt = new Amount(); amt.Parse(ref field, AmountParseFlagsEnum.PARSE_NO_REDUCE); if (!amt.HasCommodity && CommodityPool.Current.DefaultCommodity != null) { amt.SetCommodity(CommodityPool.Current.DefaultCommodity); } post.Cost = amt; break; } case CsvHeadersEnum.FIELD_TOTAL: total = field; break; case CsvHeadersEnum.FIELD_NOTE: if (!String.IsNullOrEmpty(field)) { xact.Note = field; } break; case CsvHeadersEnum.FIELD_UNKNOWN: if (!String.IsNullOrEmpty(Names[n]) && !String.IsNullOrEmpty(field)) { xact.SetTag(Names[n], Value.StringValue(field)); } break; default: throw new InvalidOperationException(); } n++; } if (richData) { xact.SetTag("Imported", Value.StringValue(TimesCommon.Current.FormatDate(TimesCommon.Current.CurrentDate, FormatTypeEnum.FMT_WRITTEN).ToString())); xact.SetTag("CSV", Value.StringValue(origLine)); } // Translate the account name, if we have enough information to do so foreach (Tuple <Mask, Account> value in Context.Journal.PayeesForUnknownAccounts) { if (value.Item1.Match(xact.Payee)) { post.Account = value.Item2; break; } } xact.AddPost(post); // Create the "balancing post", which refers to the account for this data post = new Post(); post.Xact = xact; post.Pos = new ItemPosition() { PathName = Context.PathName, BegPos = (int)Context.Reader.Position, BegLine = Context.LineNum, Sequence = Context.Sequence++ }; post.State = ItemStateEnum.Cleared; post.Account = Context.Master; if (amt != null && !amt.IsEmpty) { post.Amount = amt.Negated(); } if (!String.IsNullOrEmpty(total)) { amt = new Amount(); amt.Parse(ref total, AmountParseFlagsEnum.PARSE_NO_REDUCE); if (!amt.HasCommodity && CommodityPool.Current.DefaultCommodity != null) { amt.SetCommodity(CommodityPool.Current.DefaultCommodity); } post.AssignedAmount = amt; } xact.AddPost(post); return(xact); }