Пример #1
0
        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));
        }
Пример #2
0
        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);
        }
Пример #3
0
        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());
        }
Пример #4
0
        public void Xact_AddPost_PopulatesXactInPost()
        {
            Xact xact = new Xact();
            Post post = new Post();

            xact.AddPost(post);

            Assert.AreEqual(xact, post.Xact);
        }
Пример #5
0
        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();
        }
Пример #6
0
        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());
        }
Пример #7
0
        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
        }
Пример #8
0
        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());
        }
Пример #9
0
        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"]);
        }
Пример #10
0
        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);
        }
Пример #11
0
        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
        }
Пример #12
0
        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);
        }
Пример #13
0
        /// <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);
            }
        }
Пример #14
0
        /// <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);
        }
Пример #15
0
        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);
        }