A Budget model that contains all budgeting information. A budget is effective for a period of time after the EffectiveFrom.
Inheritance: INotifyPropertyChanged
 /// <summary>
 ///     Creates a new empty <see cref="BudgetCollection" /> but does not save it.
 /// </summary>
 public BudgetCollection CreateNew()
 {
     var budget = new BudgetModel();
     this.currentBudgetCollection = new BudgetCollection(budget);
     this.budgetBucketRepository.Initialise(new List<BudgetBucketDto>());
     return this.currentBudgetCollection;
 }
        public void ShouldIndicateFutureBudgetWhenOneIsGiven()
        {
            var budget3 = new BudgetModel { EffectiveFrom = new DateTime(2020, 01, 30) };
            var subject = new BudgetCurrencyContext(
                new BudgetCollection(new[]
                {
                    BudgetModelTestData.CreateTestData1(),
                    BudgetModelTestData.CreateTestData2(),
                    budget3
                }),
                budget3
           );

            Assert.IsFalse(subject.BudgetActive);
            Assert.IsFalse(subject.BudgetArchived);
            Assert.IsTrue(subject.BudgetInFuture);
        }
示例#3
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 Build(
            GlobalFilterCriteria criteria,
            StatementModel statementModel,
            BudgetModel budgetModel,
            LedgerBook ledgerBookModel)
        {
            var beginDate = CalculateBeginDate(criteria);
            var dateRangeDescription = string.Format(CultureInfo.CurrentCulture,
                "For the month starting {0:D} to {1:D} inclusive.", beginDate, beginDate.AddMonths(1).AddDays(-1));

            var listOfCharts = new List<BurnDownChartAnalyserResult>(this.budgetBucketRepository.Buckets.Count());
            foreach (var bucket in this.budgetBucketRepository.Buckets
                .Where(b => b is ExpenseBucket && b.Active)
                .OrderBy(b => b.Code))
            {
                var analysis = AnalyseDataForChart(statementModel, budgetModel, ledgerBookModel, bucket, beginDate);
                analysis.ChartTitle = string.Format(CultureInfo.CurrentCulture, "{0} Spending Chart", bucket.Code);
                listOfCharts.Add(analysis);
            }

            listOfCharts = listOfCharts.ToList();

            // Put surplus at the top.
            var analysisResult = AnalyseDataForChart(statementModel, budgetModel, ledgerBookModel,
                this.budgetBucketRepository.SurplusBucket, beginDate);
            analysisResult.ChartTitle = string.Format(CultureInfo.CurrentCulture, "{0} Spending Chart",
                this.budgetBucketRepository.SurplusBucket);
            listOfCharts.Insert(0, analysisResult);

            // Put any custom charts on top.
            foreach (var customChart in CustomCharts)
            {
                IEnumerable<BudgetBucket> buckets = this.budgetBucketRepository.Buckets
                    .Join(customChart.BucketIds, bucket => bucket.Code, code => code, (bucket, code) => bucket);

                var analysis = AnalyseDataForChart(statementModel, budgetModel, ledgerBookModel, buckets, beginDate);
                analysis.ChartTitle = customChart.Name;
                analysis.IsCustomAggregateChart = true;
                listOfCharts.Insert(0, analysis);
            }

            Results = new BurnDownCharts(beginDate, dateRangeDescription, listOfCharts);
        }
 public BurnDownCharts BuildAllCharts(
     StatementModel statementModel,
     BudgetModel budgetModel,
     LedgerBook ledgerBookModel, 
     GlobalFilterCriteria criteria)
 {
     if (criteria == null) throw new ArgumentNullException(nameof(criteria));
     if (criteria.Cleared) throw new ArgumentException("There is no date range criteria set. This graph is intended for one month of data.");
     if (criteria.EndDate == null || criteria.BeginDate == null)
     {
         throw new ArgumentException("There is no date range set; either the begin or end date is not set. This graph is intended for one month of data.");
     }
     if (criteria.EndDate.Value.Subtract(criteria.EndDate.Value).Days > 31)
     {
         throw new ArgumentException("The date range is too great for this graph. This graph is intended for one month of data.");
     }
     this.chartsBuilder.Build(criteria, statementModel, budgetModel, ledgerBookModel);
     return this.chartsBuilder.Results;
 }
        public BurnDownChartAnalyserResult CreateNewCustomAggregateChart(
            StatementModel statementModel,
            BudgetModel budgetModel,
            IEnumerable<BudgetBucket> buckets,
            LedgerBook ledgerBookModel,
            DateTime beginDate,
            string chartTitle)
        {
            List<BudgetBucket> bucketsList = buckets.ToList();
            var result = this.chartAnalyser.Analyse(statementModel, budgetModel, bucketsList, ledgerBookModel, beginDate);
            result.ChartTitle = chartTitle;
            var persistChart = new CustomAggregateBurnDownGraph
            {
                BucketIds = bucketsList.Select(b => b.Code).ToList(),
                Name = chartTitle
            };

            this.chartsBuilder.CustomCharts = this.chartsBuilder.CustomCharts.Union(new[] { persistChart }).ToList();
            return result;
        }
示例#7
0
        /// <summary>
        ///     Creates a new instance of the <see cref="BudgetCurrencyContext" /> class.
        /// </summary>
        /// <param name="budgets">The collection of available budgets loaded.</param>
        /// <param name="budget">
        ///     The currently selected budget. This isn't necessarily the current one compared with today's date.
        ///     Can be any in the <paramref name="budgets" /> collection.
        /// </param>
        public BudgetCurrencyContext([NotNull] BudgetCollection budgets, [NotNull] BudgetModel budget)
        {
            if (budgets == null)
            {
                throw new ArgumentNullException(nameof(budgets));
            }

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

            BudgetCollection = budgets;
            Model            = budget;

            if (budgets.IndexOf(budget) < 0)
            {
                throw new KeyNotFoundException("The given budget is not found in the given collection.");
            }
        }
        /// <summary>
        ///     A budget model that is effective from 1/1/2013
        /// </summary>
        public static BudgetModel CreateTestData1()
        {
            var budget = new BudgetModel
            {
                EffectiveFrom = new DateTime(2013, 01, 01),
                Name = TestDataConstants.Budget1Name
            };

            var expenses = new List<Expense>(
                new[]
                {
                    new Expense
                    {
                        Amount = 95M,
                        Bucket = new SavedUpForExpenseBucket(TestDataConstants.CarMtcBucketCode, "Car maintenance")
                    },
                    new Expense
                    {
                        Amount = 55M,
                        Bucket = new SpentMonthlyExpenseBucket(TestDataConstants.HairBucketCode, "Hair cuts")
                    },
                    new Expense
                    {
                        Amount = 175M,
                        Bucket = new SpentMonthlyExpenseBucket(TestDataConstants.PowerBucketCode, "Power")
                    }
                });

            var incomes = new List<Income>(
                new[]
                {
                    new Income
                    {
                        Amount = 1500M,
                        Bucket = new IncomeBudgetBucket(TestDataConstants.IncomeBucketCode, "Pay")
                    }
                });

            budget.Update(incomes, expenses);
            return budget;
        }
        public BucketBurnDownController Load(
            StatementModel statementModel,
            BudgetModel budgetModel, 
            [NotNull] BudgetBucket bucket,
            DateTime beginDate,
            Engine.Ledger.LedgerBook ledgerBook)
        {
            if (bucket == null)
            {
                throw new ArgumentNullException("bucket");
            }
            this.Background = ConverterHelper.TileBackgroundBrush;
            this.Bucket = bucket;
            this.ActualSpendingLabel = this.Bucket.Code;
            this.ChartTitle = string.Format(CultureInfo.CurrentCulture, "{0} Spending Chart", bucket.Code);

            this.burnDownGraphAnalyser.Analyse(statementModel, budgetModel, new[] { bucket }, beginDate, ledgerBook);
            CopyOutputFromAnalyser();

            return this;
        }
        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 BudgetModel CloneBudgetModel(BudgetModel sourceBudget, DateTime newBudgetEffectiveFrom)
        {
            if (sourceBudget == null)
            {
                throw new ArgumentNullException(nameof(sourceBudget));
            }

            if (newBudgetEffectiveFrom <= sourceBudget.EffectiveFrom)
            {
                throw new ArgumentException("The effective date of the new budget must be later than the other budget.", nameof(newBudgetEffectiveFrom));
            }

            if (newBudgetEffectiveFrom <= DateTime.Today)
            {
                throw new ArgumentException("The effective date of the new budget must be a future date.", nameof(newBudgetEffectiveFrom));
            }

            var validationMessages = new StringBuilder();
            if (!sourceBudget.Validate(validationMessages))
            {
                throw new ValidationWarningException(string.Format(CultureInfo.CurrentCulture, "The source budget is currently in an invalid state, unable to clone it at this time.\n{0}", validationMessages));
            }

            var newBudget = new BudgetModel
            {
                EffectiveFrom = newBudgetEffectiveFrom,
                Name = string.Format(CultureInfo.CurrentCulture, "Copy of {0}", sourceBudget.Name)
            };
            newBudget.Update(CloneBudgetIncomes(sourceBudget), CloneBudgetExpenses(sourceBudget));

            if (!newBudget.Validate(validationMessages))
            {
                throw new InvalidOperationException("New cloned budget is invalid but the source budget is ok. Code Error.\n" + validationMessages);
            }

            Budgets.Add(newBudget);
            this.budgetRepository.SaveAsync();
            UpdateServiceMonitor();
            return newBudget;
        }
        public void Build(GlobalFilterCriteria criteria,
            StatementModel statementModel,
            BudgetModel budgetModel,
            Engine.Ledger.LedgerBook ledgerBookModel)
        {
            var beginDate = CalculateBeginDate(criteria);
            var dateRangeDescription = String.Format(CultureInfo.CurrentCulture, "For the month starting {0:D} to {1:D} inclusive.", beginDate, beginDate.AddMonths(1).AddDays(-1));

            var listOfCharts = new List<BucketBurnDownController>(this.budgetBucketRepository.Buckets.Count());

            foreach (BudgetBucket bucket in this.budgetBucketRepository.Buckets
                .Where(b => b is ExpenseBucket)
                .OrderBy(b => b.Code))
            {
                BucketBurnDownController chartController = this.bucketSpendingFactory();
                chartController.Load(statementModel, budgetModel, bucket, beginDate, ledgerBookModel);
                listOfCharts.Add(chartController);
            }

            listOfCharts = listOfCharts.OrderBy(x => x.Bucket).ToList();

            // Put surplus at the top.
            listOfCharts.Insert(
                0,
                this.bucketSpendingFactory().Load(statementModel, budgetModel, this.budgetBucketRepository.SurplusBucket, beginDate, ledgerBookModel));

            // Put any custom charts on top.
            foreach (CustomAggregateBurnDownGraph customChart in this.CustomCharts)
            {
                BucketBurnDownController chartController = this.bucketSpendingFactory();
                IEnumerable<BudgetBucket> buckets = this.budgetBucketRepository.Buckets
                    .Join(customChart.BucketIds, bucket => bucket.Code, code => code, (bucket, code) => bucket);
                chartController.LoadCustomChart(statementModel, budgetModel, buckets, beginDate, ledgerBookModel, customChart.Name);
                listOfCharts.Insert(0, chartController);
            }

            this.Results = new BurnDownChartBuilderResults(beginDate, dateRangeDescription, listOfCharts);
        }
        public async Task<BudgetCollection> CreateNewAndSaveAsync(string storageKey)
        {
            if (storageKey.IsNothing())
            {
                throw new ArgumentNullException(nameof(storageKey));
            }

            var newBudget = new BudgetModel
            {
                EffectiveFrom = DateTime.Today,
                Name = Path.GetFileNameWithoutExtension(storageKey).Replace('.', ' ')
            };

            this.currentBudgetCollection = new BudgetCollection(newBudget)
            {
                StorageKey = storageKey
            };

            this.budgetBucketRepository.Initialise(new List<BudgetBucketDto>());

            await SaveAsync(storageKey, false);
            return this.currentBudgetCollection;
        }
        public BudgetCollection CreateNew([NotNull] string fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName))
            {
                throw new ArgumentNullException("fileName");
            }

            var newBudget = new BudgetModel
            {
                EffectiveFrom = DateTime.Today,
                Name = "Default Budget",
            };

            var newCollection = new BudgetCollection(new[] { newBudget })
            {
                FileName = fileName
            };

            BudgetBucketRepository.Initialise(new List<BudgetBucketDto>());

            Save(newCollection);

            return newCollection;
        }
        public async Task <BudgetCollection> CreateNewAndSaveAsync(string storageKey)
        {
            if (storageKey.IsNothing())
            {
                throw new ArgumentNullException(nameof(storageKey));
            }

            var newBudget = new BudgetModel
            {
                EffectiveFrom = DateTime.Today,
                Name          = Path.GetFileNameWithoutExtension(storageKey).Replace('.', ' ')
            };

            this.currentBudgetCollection = new BudgetCollection(newBudget)
            {
                StorageKey = storageKey
            };

            this.budgetBucketRepository.Initialise(new List <BudgetBucketDto>());

            await SaveAsync(storageKey, false);

            return(this.currentBudgetCollection);
        }
        public void Load([NotNull] BudgetModel model)
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            this.surplus = this.budgetPieService.SurplusExpense(model);
            this.budgetModel = model;
            ExpensePieChartValues = this.budgetPieService.PrepareExpenseGraphData(model);
            IncomePieChartValues = this.budgetPieService.PrepareIncomeGraphData(model);

            Shown = true;
            RaisePropertyChanged(() => ExpensePieChartValues);
            RaisePropertyChanged(() => IncomePieChartValues);
        }
示例#17
0
        public void ListsAreInitialised()
        {
            var subject = new BudgetModel();

            Assert.IsNotNull(subject.Incomes);
            Assert.IsNotNull(subject.Expenses);
        }
        public void Load(
            [NotNull] StatementModel statementModel,
            [NotNull] BudgetModel budgetModel,
            [NotNull] GlobalFilterCriteria criteria,
            Engine.Ledger.LedgerBook ledgerBookModel)
        {
            if (statementModel == null)
            {
                throw new ArgumentNullException("statementModel");
            }

            if (budgetModel == null)
            {
                throw new ArgumentNullException("budgetModel");
            }

            if (criteria == null)
            {
                throw new ArgumentNullException("criteria");
            }

            this.statement = statementModel;
            this.budget = budgetModel;
            this.ledgerBook = ledgerBookModel;

            this.chartBuilder.Build(criteria, statementModel, budgetModel, ledgerBookModel);
            this.beginDate = this.chartBuilder.Results.BeginDate;
            DateRangeDescription = this.chartBuilder.Results.DateRangeDescription;
            ChartControllers = new BindingList<BucketBurnDownController>(this.chartBuilder.Results.Charts.ToList());

            RaisePropertyChanged(() => ChartControllers);
        }
 public void Close()
 {
     Shown = false;
     this.budgetModel = null;
     CurrentExpense = null;
     CurrentIncome = null;
     this.surplus = null;
     ExpensePieChartValues = null;
     IncomePieChartValues = null;
 }
示例#20
0
 private void ShowOtherBudget(BudgetModel budgetToShow)
 {
     CurrentBudget = new BudgetCurrencyContext(Budgets, budgetToShow);
     Shown = true;
     Dirty = false; // Need to reset this because events fire needlessly (in this case) as a result of setting the CurrentBudget.
 }
示例#21
0
        /// <summary>
        ///     Called by <see cref="LedgerBook.Reconcile" />. It builds the contents of the new ledger line based on budget and
        ///     statement input.
        /// </summary>
        /// <param name="previousEntries">
        ///     A collection of previous <see cref="LedgerEntry" />s to construct the running balance for
        ///     the entries this line contains.
        /// </param>
        /// <param name="currentBudget">The current applicable budget</param>
        /// <param name="statement">The current period statement.</param>
        /// <param name="startDateIncl">The date for this ledger line.</param>
        internal void AddNew(IEnumerable<KeyValuePair<LedgerColumn, LedgerEntry>> previousEntries, BudgetModel currentBudget, StatementModel statement, DateTime startDateIncl)
        {
            if (!IsNew)
            {
                throw new InvalidOperationException("Cannot add a new entry to an existing Ledger Line, only new Ledger Lines can have new entries added.");
            }

            DateTime finishDateExcl = Date;
            List<Transaction> filteredStatementTransactions = statement == null
                ? new List<Transaction>()
                : statement.AllTransactions.Where(t => t.Date >= startDateIncl && t.Date < finishDateExcl).ToList();
            foreach (var previousEntry in previousEntries)
            {
                LedgerColumn ledger = previousEntry.Key;
                decimal balance = previousEntry.Value == null ? 0 : previousEntry.Value.Balance;
                var newEntry = new LedgerEntry(true) { Balance = balance, LedgerColumn = ledger };
                Expense expenseBudget = currentBudget.Expenses.FirstOrDefault(e => e.Bucket.Code == ledger.BudgetBucket.Code);
                var transactions = new List<LedgerTransaction>();
                if (expenseBudget != null)
                {
                    var budgetedAmount = new BudgetCreditLedgerTransaction { Credit = expenseBudget.Amount, Narrative = "Budgeted Amount" };
                    transactions.Add(budgetedAmount);
                }

                transactions.AddRange(IncludeStatementTransactions(newEntry, filteredStatementTransactions));

                newEntry.SetTransactionsForReconciliation(transactions);
                this.entries.Add(newEntry);
            }
        }
        /// <summary>
        ///     This is effectively stage 2 of the Reconciliation process.
        ///     Called by <see cref="ReconciliationBuilder.CreateNewMonthlyReconciliation" />. It builds the contents of the new
        ///     ledger line based on budget and
        ///     statement input.
        /// </summary>
        /// <param name="budget">The current applicable budget</param>
        /// <param name="statement">The current period statement.</param>
        /// <param name="startDateIncl">
        ///     The date of the previous ledger line. This is used to include transactions from the
        ///     Statement starting from this date and including this date.
        /// </param>
        private void AddNew(
            BudgetModel budget,
            StatementModel statement,
            DateTime startDateIncl)
        {
            if (!this.newReconciliationLine.IsNew)
            {
                throw new InvalidOperationException(
                    "Cannot add a new entry to an existing Ledger Line, only new Ledger Lines can have new entries added.");
            }

            var reconciliationDate = this.newReconciliationLine.Date;
            // Date filter must include the start date, which goes back to and includes the previous ledger date up to the date of this ledger line, but excludes this ledger date.
            // For example if this is a reconciliation for the 20/Feb then the start date is 20/Jan and the finish date is 20/Feb. So transactions pulled from statement are between
            // 20/Jan (inclusive) and 19/Feb (inclusive).
            List<Transaction> filteredStatementTransactions = statement?.AllTransactions.Where(
                t => t.Date >= startDateIncl && t.Date < reconciliationDate).ToList()
                                                              ?? new List<Transaction>();

            IEnumerable<LedgerEntry> previousLedgerBalances = CompileLedgersAndBalances(LedgerBook);

            var entries = new List<LedgerEntry>();
            foreach (var previousLedgerEntry in previousLedgerBalances)
            {
                LedgerBucket ledgerBucket;
                var openingBalance = previousLedgerEntry.Balance;
                var currentLedger = LedgerBook.Ledgers.Single(l => l.BudgetBucket == previousLedgerEntry.LedgerBucket.BudgetBucket);
                if (previousLedgerEntry.LedgerBucket.StoredInAccount != currentLedger.StoredInAccount)
                {
                    // Check to see if a ledger has been moved into a new default account since last reconciliation.
                    ledgerBucket = currentLedger;
                }
                else
                {
                    ledgerBucket = previousLedgerEntry.LedgerBucket;
                }

                var newEntry = new LedgerEntry(true) { Balance = openingBalance, LedgerBucket = ledgerBucket };
                List<LedgerTransaction> transactions = IncludeBudgetedAmount(budget, ledgerBucket, reconciliationDate);
                transactions.AddRange(IncludeStatementTransactions(newEntry, filteredStatementTransactions));
                AutoMatchTransactionsAlreadyInPreviousPeriod(filteredStatementTransactions, previousLedgerEntry, transactions);
                newEntry.SetTransactionsForReconciliation(transactions, reconciliationDate);

                entries.Add(newEntry);
            }

            this.newReconciliationLine.SetNewLedgerEntries(entries);

            CreateBalanceAdjustmentTasksIfRequired();
            if (statement != null)
            {
                AddBalanceAdjustmentsForFutureTransactions(statement, reconciliationDate);
            }

            CreateTasksToTransferFundsIfPaidFromDifferentAccount(filteredStatementTransactions);
        }
        private void ActOnTestData5(StatementModel statementModelTestData = null)
        {
            this.subject = LedgerBookTestData.TestData5();
            this.testDataBudget = BudgetModelTestData.CreateTestData5();
            this.testDataStatement = statementModelTestData ?? StatementModelTestData.TestData5();

            Console.WriteLine("********************** BEFORE RUNNING RECONCILIATION *******************************");
            this.testDataStatement.Output(ReconcileDate.AddMonths(-1));
            this.subject.Output(true);

            var result = Act(bankBalances: new[] { new BankBalance(StatementModelTestData.ChequeAccount, 1850.5M), new BankBalance(StatementModelTestData.SavingsAccount, 1200M) });
            this.testDataToDoList = result.Tasks.ToList();

            Console.WriteLine();
            Console.WriteLine("********************** AFTER RUNNING RECONCILIATION *******************************");
            this.subject.Output(true);
        }
        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);
        }
        public BurnDownChartAnalyserResult Analyse(StatementModel statementModel, BudgetModel budgetModel,
                                                   IEnumerable<BudgetBucket> bucketsSubset, LedgerBook ledgerBook, DateTime beginDate)
        {
            if (statementModel == null)
            {
                throw new ArgumentNullException(nameof(statementModel));
            }

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

            List<BudgetBucket> bucketsCopy = bucketsSubset.ToList();
            this.logger.LogInfo(
                l => "BurnDownChartAnalyser.Analyse: " + string.Join(" ", bucketsCopy.Select(b => b.Code)));

            var result = new BurnDownChartAnalyserResult();

            List<DateTime> datesOfTheMonth = YieldAllDaysInDateRange(beginDate);
            var lastDate = datesOfTheMonth.Last();

            CreateZeroLine(datesOfTheMonth, result);

            var openingBalance = GetBudgetedTotal(budgetModel, ledgerBook, bucketsCopy, beginDate);
            CalculateBudgetLineValues(openingBalance, datesOfTheMonth, result);

            List<ReportTransactionWithRunningBalance> spendingTransactions = CollateStatementTransactions(statementModel, bucketsCopy, beginDate, lastDate,
                openingBalance);

            // Only relevant when calculating surplus burndown - ovespent ledgers are supplemented from surplus so affect its burndown.
            if (ledgerBook != null && bucketsCopy.OfType<SurplusBucket>().Any())
            {
                List<ReportTransaction> overSpentLedgers =
                    this.ledgerCalculator.CalculateOverspentLedgers(statementModel, ledgerBook, beginDate).ToList();
                if (overSpentLedgers.Any())
                {
                    spendingTransactions.AddRange(
                        overSpentLedgers.Select(t => new ReportTransactionWithRunningBalance(t)));
                    spendingTransactions = spendingTransactions.OrderBy(t => t.Date).ToList();
                    UpdateReportTransactionRunningBalances(spendingTransactions);
                }
            }

            // Copy running balance from transaction list into burndown chart data
            var actualSpending = new SeriesData
            {
                SeriesName = BurnDownChartAnalyserResult.BalanceSeriesName,
                Description = "Running balance over time as transactions spend, the balance decreases."
            };
            result.GraphLines.SeriesList.Add(actualSpending);
            foreach (var day in datesOfTheMonth)
            {
                if (day > DateTime.Today)
                {
                    break;
                }

                var dayClosingBalance = GetDayClosingBalance(spendingTransactions, day);
                actualSpending.PlotsList.Add(new DatedGraphPlot { Date = day, Amount = dayClosingBalance });
                var copyOfDay = day;
                this.logger.LogInfo(l => l.Format("    {0} Close Bal:{1:N}", copyOfDay, dayClosingBalance));
            }

            result.ReportTransactions = spendingTransactions;
            return result;
        }
        private static decimal GetBudgetModelTotalForBucket(BudgetModel budgetModel, BudgetBucket bucket)
        {
            if (bucket is PayCreditCardBucket)
            {
                // Ignore
                return 0;
            }

            if (bucket is IncomeBudgetBucket)
            {
                throw new InvalidOperationException(
                    "Code Error: Income bucket detected when Bucket Spending only applies to Expenses.");
            }

            // Use budget values instead
            if (bucket is SurplusBucket)
            {
                return budgetModel.Surplus;
            }

            var budget = budgetModel.Expenses.FirstOrDefault(e => e.Bucket == bucket);
            if (budget != null)
            {
                return budget.Amount;
            }

            return 0;
        }
        private List<LedgerTransaction> IncludeBudgetedAmount(BudgetModel currentBudget, LedgerBucket ledgerBucket, DateTime reconciliationDate)
        {
            var budgetedExpense =
                currentBudget.Expenses.FirstOrDefault(e => e.Bucket.Code == ledgerBucket.BudgetBucket.Code);
            var transactions = new List<LedgerTransaction>();
            if (budgetedExpense != null)
            {
                BudgetCreditLedgerTransaction budgetedAmount;
                if (ledgerBucket.StoredInAccount.IsSalaryAccount)
                {
                    budgetedAmount = new BudgetCreditLedgerTransaction
                    {
                        Amount = budgetedExpense.Bucket.Active ? budgetedExpense.Amount : 0,
                        Narrative =
                            budgetedExpense.Bucket.Active ? "Budgeted Amount" : "Warning! Bucket has been disabled."
                    };
                }
                else
                {
                    budgetedAmount = new BudgetCreditLedgerTransaction
                    {
                        Amount = budgetedExpense.Bucket.Active ? budgetedExpense.Amount : 0,
                        Narrative =
                            budgetedExpense.Bucket.Active
                                ? "Budget amount must be transferred into this account with a bank transfer, use the reference number for the transfer."
                                : "Warning! Bucket has been disabled.",
                        AutoMatchingReference = ReferenceNumberGenerator.IssueTransactionReferenceNumber()
                    };
                    // TODO Maybe the budget should know which account the incomes go into, perhaps mapped against each income?
                    var salaryAccount =
                        this.newReconciliationLine.BankBalances.Single(b => b.Account.IsSalaryAccount).Account;
                    this.toDoList.Add(
                        new TransferTask(
                            string.Format(
                                CultureInfo.CurrentCulture,
                                "Budgeted Amount for {0} transfer {1:C} from Salary Account to {2} with auto-matching reference: {3}",
                                budgetedExpense.Bucket.Code,
                                budgetedAmount.Amount,
                                ledgerBucket.StoredInAccount,
                                budgetedAmount.AutoMatchingReference),
                            true)
                        {
                            Amount = budgetedAmount.Amount,
                            SourceAccount = salaryAccount,
                            DestinationAccount = ledgerBucket.StoredInAccount,
                            BucketCode = budgetedExpense.Bucket.Code,
                            Reference = budgetedAmount.AutoMatchingReference
                        });
                }

                budgetedAmount.Date = reconciliationDate;
                transactions.Add(budgetedAmount);
            }

            return transactions;
        }
 public void TestInitialise()
 {
     this.testDataBudget = BudgetModelTestData.CreateTestData1();
     this.testDataStatement = StatementModelTestData.TestData1();
     this.subject = LedgerBookTestData.TestData1();
 }
示例#29
0
        public void AfterConstructionEffectiveDateIsValidDate()
        {
            var subject = new BudgetModel();

            Assert.AreNotEqual(DateTime.MinValue, subject.EffectiveFrom);
        }
 private BurnDownChartAnalyserResult AnalyseDataForChart(
     StatementModel statementModel,
     BudgetModel budgetModel,
     LedgerBook ledgerBookModel,
     IEnumerable<BudgetBucket> buckets,
     DateTime beginDate)
 {
     var analyser = this.chartAnalyserFactory();
     var result = analyser.Analyse(statementModel, budgetModel, buckets, ledgerBookModel, beginDate);
     return result;
 }
示例#31
0
        public void AfterConstructionLastModifiedDateIsValidDate()
        {
            var subject = new BudgetModel();

            Assert.AreNotEqual(DateTime.MinValue, subject.LastModified);
        }
示例#32
0
 /// <summary>
 ///     Creates a new LedgerEntryLine for this <see cref="LedgerBook" />.
 /// </summary>
 /// <param name="reconciliationDate">
 ///     The startDate for the <see cref="LedgerEntryLine" />. This is usually the previous Month's "Reconciliation-Date",
 ///     as this month's reconciliation starts with this date and includes transactions
 ///     from that date. This date is different to the "Reconciliation-Date" that appears next to the resulting
 ///     reconciliation which is the end date for the period.
 /// </param>
 /// <param name="budget">The current budget.</param>
 /// <param name="statement">The currently loaded statement.</param>
 /// <param name="currentBankBalances">
 ///     The bank balances as at the reconciliation date to include in this new single line of the
 ///     ledger book.
 /// </param>
 internal virtual ReconciliationResult Reconcile(DateTime reconciliationDate, BudgetModel budget,
                                                 StatementModel statement, params BankBalance[] currentBankBalances)
 {
     var newRecon = this.reconciliationBuilder.CreateNewMonthlyReconciliation(reconciliationDate, budget, statement, currentBankBalances);
     this.reconciliations.Insert(0, newRecon.Reconciliation);
     return newRecon;
 }