This represents the horizontal row on the LedgerBook that crosses all LedgerColumns for a date. Each LedgerEntry must have a reference to an instance of this.
Inheritance: IModelValidate
Exemple #1
0
        internal void RecalculateClosingBalance(LedgerBook ledgerBook)
        {
            // Recalc balance based on opening balance and transactions.
            var previousLine   = ledgerBook.Reconciliations.Skip(1).FirstOrDefault();
            var openingBalance = LedgerEntryLine.FindPreviousEntryClosingBalance(previousLine, LedgerBucket);

            RecalculateClosingBalance(openingBalance);
        }
        public BankBalanceViewModel([CanBeNull] LedgerEntryLine line, [NotNull] BankBalance balance) : base(balance.Account, balance.Balance)
        {
            if (balance == null)
            {
                throw new ArgumentNullException(nameof(balance));
            }

            this.line = line;
        }
        public void CancelBalanceAdjustment(LedgerEntryLine entryLine, Guid transactionId)
        {
            if (entryLine == null)
            {
                throw new ArgumentNullException(nameof(entryLine));
            }

            entryLine.CancelBalanceAdjustment(transactionId);
        }
Exemple #4
0
        private decimal CalculateTransactionTotal(
            DateTime beginDate,
            [NotNull] StatementModel statement,
            [CanBeNull] LedgerEntryLine entryLine,
            string bucketCode)
        {
            var autoMatchLedgerTransactions = (List <LedgerTransaction>)GetOrAddFromCache(
                BuildCacheKey(statement, entryLine, beginDate),
                () => ReconciliationBuilder.FindAutoMatchingTransactions(entryLine, true).ToList());

            this.logger.LogInfo(
                l =>
            {
                var builder = new StringBuilder();
                builder.AppendLine("Ledger Transactions found that are 'Auto-Matching-Transactions':");
                foreach (var txn in autoMatchLedgerTransactions)
                {
                    builder.AppendLine(
                        $"{txn.Date:d}   {txn.Amount:F2}  {txn.Narrative}  {txn.AutoMatchingReference}");
                }
                return(builder.ToString());
            });

            IEnumerable <Transaction> query = statement.Transactions
                                              .Where(t => t.Date < beginDate.AddMonths(1))
                                              .Where(txn => !ReconciliationBuilder.IsAutoMatchingTransaction(txn, autoMatchLedgerTransactions));

            if (bucketCode == SurplusBucket.SurplusCode)
            {
                // This is to allow inclusion of special Surplus bucket subclasses. (IE: Special Project Surplus buckets)
                query = query.Where(t => t.BudgetBucket is SurplusBucket);
            }
            else
            {
                query = query.Where(t => t.BudgetBucket != null && t.BudgetBucket.Code == bucketCode);
            }

            this.logger.LogInfo(
                l =>
            {
                var builder = new StringBuilder();
                builder.AppendLine(
                    $"Statement Transactions found that are '{bucketCode}' and not 'Auto-Matching-Transactions':");
                foreach (var txn in query)
                {
                    builder.AppendLine($"{txn.Date:d}   {txn.Amount:F2}  {txn.Description}  {txn.Account}");
                }
                return(builder.ToString());
            });
            var transactionTotal = query.Sum(txn => txn.Amount);

            this.logger.LogInfo(l => l.Format("Total Transactions {0:F2}", transactionTotal));
            return(transactionTotal);
        }
 public void Show(LedgerEntryLine line, bool isNew)
 {
     LedgerEntryLine = line;
     Remarks = LedgerEntryLine.Remarks;
     IsReadOnly = !isNew;
     this.dialogCorrelationId = Guid.NewGuid();
     var dialogRequest = new ShellDialogRequestMessage(BudgetAnalyserFeature.LedgerBook, this, ShellDialogType.Ok)
     {
         Title = "Ledger Entry Remarks",
         CorrelationId = this.dialogCorrelationId
     };
     MessengerInstance.Send(dialogRequest);
 }
        public void TestIntialise()
        {
            this.mockBucketRepo = new Mock<IBudgetBucketRepository>();
            this.mockRuleService = new Mock<ITransactionRuleService>();
            this.mockReconciliationConsistency = new Mock<IReconciliationConsistency>();
            this.subject = new ReconciliationManager(this.mockRuleService.Object, this.mockReconciliationConsistency.Object, new FakeLogger());

            this.testDataLedgerBook = LedgerBookTestData.TestData5(() => new LedgerBookTestHarness(new Mock<IReconciliationBuilder>().Object));
            this.testDataEntryLine = this.testDataLedgerBook.Reconciliations.First();
            this.testDataEntryLine.Unlock();

            this.surplusChqLedger = new SurplusLedger { StoredInAccount = StatementModelTestData.ChequeAccount };
            this.insHomeSavLedger = this.testDataLedgerBook.Ledgers.Single(l => l.BudgetBucket == StatementModelTestData.InsHomeBucket);
            this.phNetChqLedger = this.testDataLedgerBook.Ledgers.Single(l => l.BudgetBucket == StatementModelTestData.PhoneBucket);
        }
        public void ShowDialog([NotNull] LedgerEntryLine ledgerLine)
        {
            if (ledgerLine == null)
            {
                throw new ArgumentNullException(nameof(ledgerLine));
            }

            SurplusBalances = new ObservableCollection<BankBalance>(ledgerLine.SurplusBalances);
            this.ledgerEntryLine = ledgerLine;

            var dialogRequest = new ShellDialogRequestMessage(BudgetAnalyserFeature.LedgerBook, this, ShellDialogType.Ok)
            {
                CorrelationId = Guid.NewGuid(),
                Title = "Surplus Balances in all Accounts"
            };

            MessengerInstance.Send(dialogRequest);
        }
        public ReconciliationResult CreateNewMonthlyReconciliation(
            DateTime reconciliationDateExclusive,
            BudgetModel budget,
            StatementModel statement,
            params BankBalance[] bankBalances)
        {
            if (bankBalances == null)
            {
                throw new ArgumentNullException(nameof(bankBalances));
            }

            if (budget == null)
            {
                throw new ArgumentNullException(nameof(budget));
            }

            if (statement == null)
            {
                throw new ArgumentNullException(nameof(statement));
            }

            if (LedgerBook == null)
            {
                throw new ArgumentException("The Ledger Book property cannot be null. You must set this prior to calling this method.");
            }

            try
            {
                this.newReconciliationLine = new LedgerEntryLine(reconciliationDateExclusive, bankBalances);
                AddNew(budget, statement, CalculateDateForReconcile(LedgerBook, reconciliationDateExclusive));

                CreateToDoForAnyOverdrawnSurplusBalance();

                return new ReconciliationResult { Reconciliation = this.newReconciliationLine, Tasks = this.toDoList };
            }
            finally
            {
                this.newReconciliationLine = null;
            }
        }
        public LedgerTransaction CreateBalanceAdjustment(LedgerEntryLine entryLine, decimal amount, string narrative,
                                                         Account account)
        {
            if (entryLine == null)
            {
                throw new ArgumentNullException(nameof(entryLine));
            }

            if (narrative == null)
            {
                throw new ArgumentNullException(nameof(narrative));
            }

            if (account == null)
            {
                throw new ArgumentNullException(nameof(account));
            }

            var adjustmentTransaction = entryLine.BalanceAdjustment(amount, narrative, account);
            adjustmentTransaction.Date = entryLine.Date;
            return adjustmentTransaction;
        }
        /// <summary>
        ///     Performs a funds transfer for the given ledger entry line.
        /// </summary>
        public void TransferFunds(TransferFundsCommand transferDetails, LedgerEntryLine ledgerEntryLine)
        {
            if (transferDetails == null)
            {
                throw new ArgumentNullException(nameof(transferDetails));
            }

            if (ledgerEntryLine == null)
            {
                throw new ArgumentNullException(nameof(ledgerEntryLine));
            }

            if (!transferDetails.IsValid())
            {
                throw new InvalidOperationException(
                    "Code Error: The transfer command is in an invalid state, this should be resolved in the UI.");
            }

            PerformBankTransfer(transferDetails, ledgerEntryLine);
        }
        private void PerformBankTransfer(TransferFundsCommand transferDetails, LedgerEntryLine ledgerEntryLine)
        {
            var sourceTransaction = new CreditLedgerTransaction
            {
                Amount = -transferDetails.TransferAmount,
                AutoMatchingReference = transferDetails.AutoMatchingReference,
                Date = ledgerEntryLine.Date,
                Narrative = transferDetails.Narrative
            };

            var destinationTransaction = new CreditLedgerTransaction
            {
                Amount = transferDetails.TransferAmount,
                AutoMatchingReference = transferDetails.AutoMatchingReference,
                Date = ledgerEntryLine.Date,
                Narrative = transferDetails.Narrative
            };

            if (transferDetails.BankTransferRequired)
            {
                ledgerEntryLine.BalanceAdjustment(-transferDetails.TransferAmount, transferDetails.Narrative,
                    transferDetails.FromLedger.StoredInAccount);
                ledgerEntryLine.BalanceAdjustment(transferDetails.TransferAmount, transferDetails.Narrative,
                    transferDetails.ToLedger.StoredInAccount);
                this.transactionRuleService.CreateNewSingleUseRule(
                    transferDetails.FromLedger.BudgetBucket.Code,
                    null,
                    new[] { transferDetails.AutoMatchingReference },
                    null,
                    -transferDetails.TransferAmount,
                    true);
                this.transactionRuleService.CreateNewSingleUseRule(
                    transferDetails.ToLedger.BudgetBucket.Code,
                    null,
                    new[] { transferDetails.AutoMatchingReference },
                    null,
                    transferDetails.TransferAmount,
                    true);
            }

            // No need for a source transaction for surplus ledger.
            if (!(transferDetails.FromLedger.BudgetBucket is SurplusBucket))
            {
                var ledgerEntry = ledgerEntryLine.Entries.Single(e => e.LedgerBucket == transferDetails.FromLedger);
                ledgerEntry.AddTransaction(sourceTransaction);
            }

            // No need for a destination transaction for surplus ledger.
            if (!(transferDetails.ToLedger.BudgetBucket is SurplusBucket))
            {
                var ledgerEntry = ledgerEntryLine.Entries.Single(e => e.LedgerBucket == transferDetails.ToLedger);
                ledgerEntry.AddTransaction(destinationTransaction);
            }
        }
        private static decimal GetLedgerBalanceForBucket(BudgetModel budgetModel, LedgerEntryLine applicableLine,
                                                         BudgetBucket bucket)
        {
            if (bucket is SurplusBucket)
            {
                return applicableLine.CalculatedSurplus;
            }

            var ledger = applicableLine.Entries.FirstOrDefault(e => e.LedgerBucket.BudgetBucket == bucket);
            if (ledger != null)
            {
                return ledger.Balance;
            }

            // The Ledger line might not actually have a ledger for the given bucket.
            return GetBudgetModelTotalForBucket(budgetModel, bucket);
        }
        /// <summary>
        ///     Show the Ledger Transactions view, for viewing and editing Balance Adjustments
        /// </summary>
        public void ShowDialog(LedgerEntryLine ledgerEntryLine, bool isNew)
        {
            if (ledgerEntryLine == null)
            {
                return;
            }

            LedgerEntry = null;
            ShownTransactions = new ObservableCollection<LedgerTransaction>(ledgerEntryLine.BankBalanceAdjustments);
            Title = "Balance Adjustment Transactions";
            this.entryLine = ledgerEntryLine;
            ShowDialogCommon(isNew);
        }
 private void Reset()
 {
     this.ShowAddingNewTransactionPanel = false;
     this.isAddDirty = false;
     ShownTransactions = null;
     LedgerEntry = null;
     this.entryLine = null;
     NewTransactionAmount = 0;
     NewTransactionIsCredit = false;
     NewTransactionIsDebit = true;
     NewTransactionIsReversal = false;
     NewTransactionNarrative = null;
     NewTransactionAccountType = null;
 }
 public BankBalanceViewModel([CanBeNull] LedgerEntryLine line, [NotNull] Account account, decimal balance) : base(account, balance)
 {
     this.line = line;
 }
        private void SetReconciliation(IReadOnlyDictionary<LedgerBucket, SpecificLedgerEntryTestDataBuilder> ledgers, string remarks)
        {
            var recon = new LedgerEntryLine(this.tempReconDate, this.tempBankBalances) { Remarks = remarks };
            LedgerEntryLine previousRecon = Reconciliations.OrderByDescending(r => r.Date).FirstOrDefault();
            var entries = new List<LedgerEntry>();

            foreach (LedgerBucket ledgerBucket in this.ledgerBuckets)
            {
                decimal openingBalance;
                if (previousRecon == null)
                {
                    openingBalance = this.openingBalances[ledgerBucket];
                }
                else
                {
                    LedgerEntry previousEntry = previousRecon.Entries.Single(e => e.LedgerBucket == ledgerBucket);
                    openingBalance = previousEntry.Balance;
                }
                var entry = new LedgerEntry
                {
                    LedgerBucket = ledgerBucket,
                    Balance = openingBalance
                };
                entry.SetTransactionsForTesting(ledgers[ledgerBucket].Transactions.ToList());
                entries.Add(entry);
            }

            recon.SetEntriesForTesting(entries);
            this.reconciliations.Add(recon);
        }
Exemple #17
0
        /// <summary>
        ///     Creates a new LedgerEntryLine for this <see cref="LedgerBook" />.
        /// </summary>
        /// <param name="date">
        ///     The date for the <see cref="LedgerEntryLine" />. Also used to search for transactions in the
        ///     <see cref="statement" />.
        /// </param>
        /// <param name="bankBalances">
        ///     The bank balances as at the <see cref="date" /> to include in this new single line of the
        ///     ledger book.
        /// </param>
        /// <param name="budget">The current budget.</param>
        /// <param name="statement">The currently loaded statement.</param>
        /// <param name="ignoreWarnings">Ignores validation warnings if true, otherwise <see cref="ValidationWarningException" />.</param>
        /// <exception cref="InvalidOperationException">Thrown when this <see cref="LedgerBook" /> is in an invalid state.</exception>
        public LedgerEntryLine Reconcile(
            DateTime date,
            IEnumerable<BankBalance> bankBalances,
            BudgetModel budget,
            StatementModel statement = null,
            bool ignoreWarnings = false)
        {
            try
            {
                PreReconciliationValidation(date, statement);
            }
            catch (ValidationWarningException)
            {
                if (!ignoreWarnings)
                {
                    throw;
                }
            }

            decimal consistencyCheck1 = DatedEntries.Sum(e => e.CalculatedSurplus);
            var newLine = new LedgerEntryLine(date, bankBalances);
            var previousEntries = new Dictionary<LedgerColumn, LedgerEntry>();
            LedgerEntryLine previousLine = this.datedEntries.FirstOrDefault();
            foreach (LedgerColumn ledger in Ledgers)
            {
                LedgerEntry previousEntry = null;
                if (previousLine != null)
                {
                    previousEntry = previousLine.Entries.FirstOrDefault(e => e.LedgerColumn.Equals(ledger));
                }

                previousEntries.Add(ledger, previousEntry);
            }

            newLine.AddNew(previousEntries, budget, statement, CalculateStartDateForReconcile(date));
            decimal consistencyCheck2 = DatedEntries.Sum(e => e.CalculatedSurplus);
            if (consistencyCheck1 != consistencyCheck2)
            {
                throw new CorruptedLedgerBookException("Code Error: The previous dated entries have changed, this is not allowed. Data is corrupt.");
            }

            this.datedEntries.Insert(0, newLine);
            this.newlyAddedLedgers.Clear();
            return newLine;
        }
 public void TestInitialise()
 {
     Subject = CreateSubject();
 }
        /// <summary>
        ///     Show the Ledger Transactions view for viewing and editing Ledger Transactions.
        /// </summary>
        public void ShowLedgerTransactionsDialog(LedgerEntryLine ledgerEntryLine, LedgerEntry ledgerEntry, bool isNew)
        {
            if (ledgerEntry == null)
            {
                return;
            }

            InBalanceAdjustmentMode = false;
            InLedgerEntryMode = true;
            LedgerEntry = ledgerEntry;
            this.entryLine = ledgerEntryLine; // Will be null when editing an existing LedgerEntry as opposed to creating a new reconciliation.
            ShownTransactions = new ObservableCollection<LedgerTransaction>(LedgerEntry.Transactions);
            Title = string.Format(CultureInfo.CurrentCulture, "{0} Transactions", ledgerEntry.LedgerBucket.BudgetBucket.Code);
            OpeningBalance = RetrieveOpeningBalance();
            ShowDialogCommon(isNew);
        }
        public LedgerTransaction CreateLedgerTransaction(LedgerEntryLine reconciliation, LedgerEntry ledgerEntry,
                                                         decimal amount, string narrative)
        {
            if (reconciliation == null)
            {
                throw new ArgumentNullException(nameof(reconciliation));
            }

            if (ledgerEntry == null)
            {
                throw new ArgumentNullException(nameof(ledgerEntry));
            }

            if (narrative == null)
            {
                throw new ArgumentNullException(nameof(narrative));
            }

            LedgerTransaction newTransaction = new CreditLedgerTransaction();
            newTransaction.WithAmount(amount).WithNarrative(narrative);
            newTransaction.Date = reconciliation.Date;
            ledgerEntry.AddTransaction(newTransaction);
            return newTransaction;
        }
        private int AddSurplusCell(Grid grid, int gridRow, int gridColumn, LedgerEntryLine line)
        {
            AddBorderToGridCell(grid, SurplusBackground, false, gridRow, gridColumn);

            var stackPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right };
            grid.Children.Add(stackPanel);
            Grid.SetRow(stackPanel, gridRow);
            Grid.SetColumn(stackPanel, gridColumn);

            if (line.SurplusBalances.Any(b => b.Balance < 0))
            {
                var warningImage = new ContentControl
                {
                    Template = (ControlTemplate)FindResource("WarningImage"),
                    Width = 20,
                    Height = 20,
                    Margin = new Thickness(5),
                    ToolTip = "There are one or more negative surplus balances!"
                };
                stackPanel.Children.Add(warningImage);
            }

            var hyperlink = new Hyperlink(new Run(line.CalculatedSurplus.ToString("N", CultureInfo.CurrentCulture)))
            {
                Command = this.showSurplusBalancesCommand,
                CommandParameter = line
            };
            var textBlock = new TextBlock(hyperlink)
            {
                Style = (Style)FindResource(ImportantNumberStyle),
                ToolTip = string.Format(CultureInfo.CurrentCulture, "Total Surplus: {0:N}. Click for more detail...", line.CalculatedSurplus),
                Foreground = (Brush)FindResource(SurplusTextBrush)
            };
            stackPanel.Children.Add(textBlock);
            return ++gridRow;
        }
        public void UpdateRemarks(LedgerEntryLine entryLine, string remarks)
        {
            if (entryLine == null)
            {
                throw new ArgumentNullException(nameof(entryLine));
            }

            if (remarks == null)
            {
                throw new ArgumentNullException(nameof(remarks));
            }

            entryLine.UpdateRemarks(remarks);
        }
        /// <summary>
        ///     Transfer funds from one ledger bucket to another. This is only possible if the current ledger reconciliation is
        ///     unlocked.
        ///     This is usually used during reconciliation.
        /// </summary>
        /// <param name="reconciliation">
        ///     The reconciliation line that this transfer will be created in.  A transfer can only occur
        ///     between two ledgers in the same reconciliation.
        /// </param>
        /// <param name="transferDetails">The details of the requested transfer.</param>
        public void TransferFunds(LedgerEntryLine reconciliation, TransferFundsCommand transferDetails)
        {
            if (reconciliation == null)
            {
                throw new ArgumentNullException(nameof(reconciliation),
                    "There are no reconciliations. Transfer funds can only be used on the most recent reconciliation.");
            }

            this.reconciliationManager.TransferFunds(transferDetails, reconciliation);
        }
        private void OnShellDialogResponseReceived(ShellDialogResponseMessage message)
        {
            if (!message.IsItForMe(this.dialogCorrelationId))
            {
                return;
            }

            if (message.Response == ShellDialogButton.Ok)
            {
                if (ShowAddingNewTransactionPanel)
                {
                    OnAddNewTransactionCommandExecuted();
                }

                Save();
                this.entryLine = null;
                LedgerEntry = null;
            }

            if (message.Response == ShellDialogButton.Cancel)
            {
                this.isAddDirty = false;
                this.entryLine = null;
                LedgerEntry = null;
            }

            EventHandler<LedgerTransactionEventArgs> handler = Complete;
            handler?.Invoke(this, new LedgerTransactionEventArgs(this.wasChanged));

            Reset();
            this.wasChanged = false;
        }
 private static LedgerEntryLine CreateLine(DateTime date, IEnumerable<BankBalance> bankBalances, string remarks)
 {
     var line = new LedgerEntryLine(date, bankBalances) { Remarks = remarks };
     return line;
 }
 private int AddDateCellToLedgerEntryLine(Grid grid, int gridRow, ref int gridColumn, LedgerEntryLine line)
 {
     Border dateBorder = AddBorderToGridCell(grid, false, true, gridRow, gridColumn);
     AddContentToGrid(dateBorder, line.Date.ToString(DateFormat, CultureInfo.CurrentCulture), ref gridRow, gridColumn, DateColumnStyle);
     return gridRow;
 }
        private int AddDateCellToLedgerEntryLine(Grid grid, int gridRow, ref int gridColumn, LedgerEntryLine line)
        {
            Border dateBorder = AddBorderToGridCell(grid, false, true, gridRow, gridColumn);
            AddContentToGrid(dateBorder, line.Date.ToString(DateFormat, CultureInfo.CurrentCulture), ref gridRow, gridColumn, DateColumnStyle);
            gridRow--; // Not finished adding content to this cell yet.
            var button = new Button
            {
                Style = Application.Current.Resources["Button.Round.SmallCross"] as Style,
                HorizontalAlignment = HorizontalAlignment.Right,
                Command = this.removeLedgerEntryLineCommand,
                CommandParameter = line,
            };
            var visibilityBinding = new Binding("IsEnabled")
            {
                Converter = (IValueConverter)Application.Current.Resources["Converter.BoolToVis"],
                RelativeSource = new RelativeSource(RelativeSourceMode.Self),
            };
            button.SetBinding(UIElement.VisibilityProperty, visibilityBinding);

            grid.Children.Add(button);
            Grid.SetColumn(button, gridColumn);
            Grid.SetRow(button, gridRow++);

            return gridRow;
        }
        private static string BuildToolTipForBankBalance(LedgerEntryLine line)
        {
            var individualLedgerBalances = new StringBuilder();
            foreach (BankBalance bankBalance in line.BankBalances)
            {
                individualLedgerBalances.AppendFormat(
                    CultureInfo.CurrentCulture,
                    "{0}: {1:N}; ",
                    bankBalance.Account,
                    bankBalance.Balance + line.BankBalanceAdjustments.Where(a => a.BankAccount == bankBalance.Account).Sum(a => a.Amount));
            }

            return string.Format(
                CultureInfo.CurrentCulture,
                "Total Ledger Balance: {0:N}; Adjusted Bank Balance {1:N}; {2}",
                line.LedgerBalance,
                line.TotalBankBalance + line.TotalBalanceAdjustments,
                individualLedgerBalances);
        }
        private static decimal GetLedgerBalance(LedgerEntryLine applicableLine, BudgetBucket bucket)
        {
            if (applicableLine == null)
            {
                return -1;
            }

            if (bucket is SurplusBucket)
            {
                return applicableLine.CalculatedSurplus;
            }

            LedgerEntry ledger = applicableLine.Entries.FirstOrDefault(e => e.LedgerColumn.BudgetBucket == bucket);
            if (ledger != null)
            {
                return ledger.Balance;
            }

            return -1;
        }