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); }
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); }
/// <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; }