示例#1
0
        public void TestIntialise()
        {
            this.mockRuleService = new Mock <ITransactionRuleService>(MockBehavior.Strict);
            this.mockReconciliationConsistency = new Mock <IReconciliationConsistency>();
            this.bucketRepo            = new BucketBucketRepoAlwaysFind();
            this.testDataBudgets       = BudgetModelTestData.CreateCollectionWith1And2();
            this.testDataBudgetContext = new BudgetCurrencyContext(this.testDataBudgets, this.testDataBudgets.CurrentActiveBudget);
            this.testDataStatement     = new StatementModelBuilder()
                                         .TestData5()
                                         .AppendTransaction(new Transaction
            {
                Account         = StatementModelTestData.ChequeAccount,
                Amount          = -23.56M,
                BudgetBucket    = StatementModelTestData.RegoBucket,
                Date            = ReconcileDate.Date.AddDays(-1),
                TransactionType = new NamedTransaction("Foo"),
                Description     = "Last transaction"
            })
                                         .Build();
            this.testDataToDoList = new List <ToDoTask>();
            this.subject          = new ReconciliationCreationManager(this.mockRuleService.Object, this.mockReconciliationConsistency.Object, new FakeLogger());

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

            this.mockReconciliationConsistency.Setup(m => m.EnsureConsistency(It.IsAny <LedgerBook>())).Returns(new Mock <IDisposable>().Object);
        }
        public void Load(StatementModel statementModel, BudgetCollection budgets, GlobalFilterCriteria criteria)
        {
            Analysis = new OverallPerformanceBudgetAnalyser(statementModel, budgets, this.bucketRepository);
            Analysis.Analyse(criteria);
            OverallPerformance = (double)Analysis.OverallPerformance;
            ExpenseFilter = true;
            IncomeFilter = false;

            RaisePropertyChanged(() => Analysis);
            ICollectionView view = CollectionViewSource.GetDefaultView(Analysis.Analyses);
            view.Filter = x =>
            {
                var bucketAnalysis = x as BucketPerformanceAnalyser;
                if (bucketAnalysis == null)
                {
                    return true;
                }

                if (IncomeFilter)
                {
                    return bucketAnalysis.Bucket is IncomeBudgetBucket;
                }

                bool result = !(bucketAnalysis.Bucket is IncomeBudgetBucket);
                return result;
            };
        }
        public void TestIntialise()
        {
            this.mockRuleService = new Mock<ITransactionRuleService>(MockBehavior.Strict);
            this.mockReconciliationConsistency = new Mock<IReconciliationConsistency>();
            this.bucketRepo = new BucketBucketRepoAlwaysFind();
            this.testDataBudgets = BudgetModelTestData.CreateCollectionWith1And2();
            this.testDataBudgetContext = new BudgetCurrencyContext(this.testDataBudgets, this.testDataBudgets.CurrentActiveBudget);
            this.testDataStatement = new StatementModelBuilder()
                .TestData5()
                .AppendTransaction(new Transaction
                {
                    Account = StatementModelTestData.ChequeAccount,
                    Amount = -23.56M,
                    BudgetBucket = StatementModelTestData.RegoBucket,
                    Date = ReconcileDate.Date.AddDays(-1),
                    TransactionType = new NamedTransaction("Foo"),
                    Description = "Last transaction"
                })
                .Build();
            this.testDataToDoList = new List<ToDoTask>();
            this.subject = new ReconciliationManager(this.mockRuleService.Object, this.mockReconciliationConsistency.Object, new FakeLogger());

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

            this.mockReconciliationConsistency.Setup(m => m.EnsureConsistency(It.IsAny<LedgerBook>())).Returns(new Mock<IDisposable>().Object);
        }
        public void ForDates_1_1_2013_to_20_1_2014_ShouldReturnBudget1And2()
        {
            BudgetCollection          subject = Arrange();
            IEnumerable <BudgetModel> result  = subject.ForDates(new DateTime(2013, 1, 1), new DateTime(2014, 1, 20));

            Assert.AreEqual(2, result.Count());
        }
        private static void AnalysisPreconditions(GlobalFilterCriteria criteria, StatementModel statement,
                                                  BudgetCollection budgets, out DateTime beginDate, out DateTime endDate)
        {
            if (criteria == null)
            {
                throw new ArgumentNullException(nameof(criteria));
            }

            if (!criteria.Cleared && (criteria.BeginDate == null || criteria.EndDate == null))
            {
                throw new ArgumentException("The given criteria does not contain any filtering dates.");
            }

            if (statement == null)
            {
                throw new ArgumentNullException(nameof(statement),
                                                "The statement supplied is null, analysis cannot proceed with no statement.");
            }

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

            if (criteria.Cleared)
            {
                beginDate = statement.AllTransactions.First().Date;
                endDate   = statement.AllTransactions.Last().Date;
            }
            else
            {
                beginDate = criteria.BeginDate ?? DateTime.MinValue;
                endDate   = criteria.EndDate ?? DateTime.MinValue;
            }
        }
        partial void ToModelPostprocessing(BudgetCollectionDto dto, ref BudgetCollection model)
        {
            var budgetCollection = model;

            dto.Budgets.ForEach(x => budgetCollection.Add(this.budgetMapper.ToModel(x)));
            dto.Buckets.ForEach(x => this.bucketRepo.GetOrCreateNew(x.Code, () => this.bucketMapper.ToModel(x)));
        }
 /// <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;
 }
示例#8
0
        public void Load(StatementModel statementModel, BudgetCollection budgets, GlobalFilterCriteria criteria)
        {
            Analysis           = this.chartService.BuildChart(statementModel, budgets, criteria);
            OverallPerformance = (double)Analysis.OverallPerformance;
            ExpenseFilter      = true;
            IncomeFilter       = false;

            RaisePropertyChanged(() => Analysis);
            ICollectionView view = CollectionViewSource.GetDefaultView(Analysis.Analyses);

            view.Filter = x =>
            {
                var bucketAnalysis = x as BucketPerformanceResult;
                if (bucketAnalysis == null)
                {
                    return(true);
                }

                if (IncomeFilter)
                {
                    return(bucketAnalysis.Bucket is IncomeBudgetBucket);
                }

                bool result = !(bucketAnalysis.Bucket is IncomeBudgetBucket);
                return(result);
            };
        }
        public void ValidateShouldReturnTrueGivenGoodBudgets()
        {
            BudgetCollection subject = Arrange();
            bool             result  = subject.Validate(new StringBuilder());

            Assert.IsTrue(result);
        }
        public void ForDates_WithDatesOutsideBudgetCollection_ShouldThrow()
        {
            BudgetCollection subject = Arrange();

            subject.ForDates(DateTime.MinValue, DateTime.MaxValue);

            Assert.Fail();
        }
        public void ForDate_WithEarlierDateThanFirstBudget_ShouldReturnNull()
        {
            BudgetCollection subject = Arrange();

            BudgetModel result = subject.ForDate(DateTime.MinValue);

            Assert.IsNull(result);
        }
        public void ForDate_25_1_2014_ShouldReturnBudget2()
        {
            BudgetCollection subject = Arrange();

            BudgetModel result = subject.ForDate(new DateTime(2014, 1, 25));

            Assert.AreSame(subject.First(b => b.Name == TestDataConstants.Budget2Name), result);
        }
示例#13
0
 private async Task CheckBudgetContainsAllUsedBucketsFromStatement(BudgetCollection budgets = null)
 {
     if (!await this.transactionService.ValidateWithCurrentBudgetsAsync(budgets))
     {
         this.uiContext.UserPrompts.MessageBox.Show(
             "WARNING! By loading a different budget with a Statement loaded, data loss may occur. There may be budget buckets used in the Statement that do not exist in the new loaded Budget. This will result in those Statement Transactions being declassified. \nCheck for unclassified transactions.",
             "Data Loss Wanring!");
     }
 }
        public void OutputBudgetCollection()
        {
            BudgetCollection subject = Arrange();

            foreach (BudgetModel budget in subject)
            {
                Console.WriteLine("Budget: '{0}' EffectiveFrom: {1:d}", budget.Name, budget.EffectiveFrom);
            }
        }
 public static BudgetCollection CreateCollectionWith1And2()
 {
     var collection = new BudgetCollection(new[]
     {
         CreateTestData1(),
         CreateTestData2()
     });
     collection.FileName = @"C:\Temp\Foo.xaml";
     return collection;
 }
        /// <summary>
        ///     Closes the currently loaded file.  No warnings will be raised if there is unsaved data.
        /// </summary>
        public void Close()
        {
            this.transactions = new ObservableCollection <Transaction>();
            StatementModel?.Dispose();
            StatementModel        = null;
            this.budgetCollection = null;
            this.budgetHash       = 0;
            var handler = Closed;

            handler?.Invoke(this, EventArgs.Empty);
        }
        public async Task LoadAsync(ApplicationDatabase applicationDatabase)
        {
            if (applicationDatabase == null)
            {
                throw new ArgumentNullException(nameof(applicationDatabase));
            }

            Budgets = await this.budgetRepository.LoadAsync(applicationDatabase.FullPath(applicationDatabase.BudgetCollectionStorageKey), applicationDatabase.IsEncrypted);

            UpdateServiceMonitor();
            NewDataSourceAvailable?.Invoke(this, EventArgs.Empty);
        }
示例#18
0
        public static BudgetCollection CreateCollectionWith1And2()
        {
            var collection = new BudgetCollection(
                new[]
            {
                CreateTestData1(),
                CreateTestData2()
            });

            collection.StorageKey = @"C:\Temp\Foo.xaml";
            return(collection);
        }
        public void ValidateShouldReturnTrueGivenBudgetsWithDuplicateEffectiveDates()
        {
            BudgetCollection subject = Arrange();

            subject.Add(
                new BudgetModelFake
            {
                EffectiveFrom = subject.First().EffectiveFrom,
                Name          = Guid.NewGuid().ToString()
            });

            Assert.IsTrue(subject.Validate(new StringBuilder()));
        }
        public void ValidateShouldFixGivenBudgetsWithDuplicateEffectiveDates()
        {
            BudgetCollection subject = Arrange();

            subject.Add(
                new BudgetModelFake
            {
                EffectiveFrom = subject.First().EffectiveFrom,
                Name          = Guid.NewGuid().ToString()
            });
            subject.Validate(new StringBuilder());

            Assert.IsTrue(subject.GroupBy(b => b.EffectiveFrom).Sum(group => group.Count()) == 3);
        }
        public void ValidateShouldReturnFalseGivenOneBadBudget()
        {
            BudgetCollection subject = Arrange();

            subject.Add(
                new BudgetModelFake
            {
                EffectiveFrom      = DateTime.Now,
                Name               = "Foo123",
                InitialiseOverride = () => { },
                ValidateOverride   = msg => false
            });

            bool result = subject.Validate(new StringBuilder());

            Assert.IsFalse(result);
        }
        /// <summary>
        ///     Validates the currently loaded <see cref="StatementModel" /> against the provided budgets and ensures all buckets
        ///     used by the transactions
        ///     exist in the budgets.  This is performed asynchronously.
        ///     This method can be called when a budget is loaded or changed or when a new Budget Analyser Statement is loaded.
        /// </summary>
        /// <param name="budgets">
        ///     The current budgets. This must be provided at least once. It can be omitted when
        ///     calling this method after the statement model has changed if the budget was previously provided.
        /// </param>
        /// <returns>
        ///     A task that will result in true if all buckets used, are present in the budgets, otherwise false.
        ///     If false, this indicates that some transactions may have their bucket allocation removed possibly resulting in
        ///     unintended data loss.
        /// </returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        public async Task <bool> ValidateWithCurrentBudgetsAsync(BudgetCollection budgets = null)
        {
            // This method must be called at least once with a budget collection.  Second and subsequent times do not require the budget.
            if (this.budgetCollection == null && budgets == null)
            {
                throw new ArgumentNullException(nameof(budgets));
            }

            this.budgetCollection = budgets ?? this.budgetCollection;

            if (StatementModel == null)
            {
                // Can't check yet, statement hasn't been loaded yet. Everything is ok for now.
                return(true);
            }

            if (this.budgetCollection.GetHashCode() == this.budgetHash)
            {
                // This budget has already been checked against this statement. No need to repeatedly check the validity below, this is an expensive operation.
                // Everything is ok.
                return(true);
            }

            var allBuckets = new List <BudgetBucket>(this.bucketRepository.Buckets.OrderBy(b => b.Code));
            var allTransactionHaveABucket = await Task.Run(
                () =>
            {
                return(StatementModel.AllTransactions
                       .Where(t => t.BudgetBucket != null)
                       .AsParallel()
                       .All(
                           t =>
                {
                    var bucketExists = allBuckets.Contains(t.BudgetBucket);
                    if (!bucketExists)
                    {
                        t.BudgetBucket = null;
                        this.logger.LogWarning(l => l.Format("Transaction {0} has a bucket ({1}) that doesn't exist!", t.Date, t.BudgetBucket));
                    }
                    return bucketExists;
                }));
            });

            this.budgetHash = this.budgetCollection.GetHashCode();
            return(allTransactionHaveABucket);
        }
示例#23
0
        public static BudgetCollection GetObservableCollection()
        {
            var collection = new BudgetCollection();
            var sqlBuilder = new StringBuilder();

            sqlBuilder.AppendLine("SELECT * FROM");
            sqlBuilder.AppendLine(TABLE_NAME);
            var dataTable = DatabaseController.ExecuteSelectQuery(sqlBuilder);

            foreach (DataRow dataRow in dataTable.Rows)
            {
                var budget = new Budget();
                budget.SetPropertiesFromDataRow(dataRow);
                collection.Add(budget);
            }
            return(collection);
        }
示例#24
0
        private void ActOnTestData5(StatementModel statementModelTestData = null, bool ignoreWarnings = false)
        {
            this.testDataLedgerBook    = LedgerBookTestData.TestData5(() => new LedgerBookTestHarness(new Mock <IReconciliationBuilder>().Object));
            this.testDataBudgets       = new BudgetCollection(new[] { BudgetModelTestData.CreateTestData5() });
            this.testDataBudgetContext = new BudgetCurrencyContext(this.testDataBudgets, this.testDataBudgets.CurrentActiveBudget);
            this.testDataStatement     = statementModelTestData ?? StatementModelTestData.TestData5();

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

            Act(bankBalances: new[] { new BankBalance(StatementModelTestData.ChequeAccount, 1850.5M), new BankBalance(StatementModelTestData.SavingsAccount, 1200M) },
                ignoreWarnings: ignoreWarnings);

            Console.WriteLine();
            Console.WriteLine("********************** AFTER RUNNING RECONCILIATION *******************************");
            this.testDataLedgerBook.Output(true);
        }
        public OverallPerformanceBudgetResult BuildChart(StatementModel statementModel, BudgetCollection budgets,
                                                         GlobalFilterCriteria criteria)
        {
            if (statementModel == null)
            {
                throw new ArgumentNullException(nameof(statementModel));
            }

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

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

            return this.analyser.Analyse(statementModel, budgets, criteria);
        }
        public BudgetCurrencyContext([NotNull] BudgetCollection budgets, [NotNull] BudgetModel budget)
        {
            if (budgets == null)
            {
                throw new ArgumentNullException("budgets");
            }

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

            this.budgets = budgets;
            Model = budget;

            if (budgets.All(b => b != budget))
            {
                throw new KeyNotFoundException("The given budget is not found in the given collection.");
            }
        }
        public OverallPerformanceBudgetAnalyser([NotNull] StatementModel statement, [NotNull] BudgetCollection budgets, [NotNull] IBudgetBucketRepository bucketRepository)
        {
            if (statement == null)
            {
                throw new ArgumentNullException("statement");
            }

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

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

            this.statement = statement;
            this.budgets = budgets;
            this.bucketRepository = bucketRepository;
        }
        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;
        }
        private static void CalculateTotalsAndAverage(DateTime beginDate, StatementModel statement,
                                                      BudgetCollection budgets, OverallPerformanceBudgetResult result)
        {
            // First total the expenses without the saved up for expenses.
            var totalExpensesSpend = statement.Transactions
                                     .Where(t => t.BudgetBucket is ExpenseBucket)
                                     .Sum(t => t.Amount);

            var totalSurplusSpend = statement.Transactions
                                    .Where(t => t.BudgetBucket is SurplusBucket)
                                    .Sum(t => t.Amount);

            result.AverageSpend   = totalExpensesSpend / result.DurationInMonths; // Expected to be negative
            result.AverageSurplus = totalSurplusSpend / result.DurationInMonths;  // Expected to be negative

            for (var month = 0; month < result.DurationInMonths; month++)
            {
                var budget = budgets.ForDate(beginDate.AddMonths(month));
                result.TotalBudgetExpenses += budget.Expenses.Sum(e => e.Amount);
            }

            result.OverallPerformance = result.AverageSpend + result.TotalBudgetExpenses;
        }
        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;
        }
        /// <summary>
        ///     Analyses the supplied statement using the supplied budget within the criteria given to this method.
        /// </summary>
        /// <param name="budgets">The current budgets collection.</param>
        /// <param name="criteria">The criteria to limit the analysis.</param>
        /// <param name="statementModel">The current statement model.</param>
        /// <exception cref="BudgetException">
        ///     Will be thrown if no budget is supplied or if no budget can be found for the dates
        ///     given in the criteria.
        /// </exception>
        /// <exception cref="ArgumentException">If statement or budget is null.</exception>
        public virtual OverallPerformanceBudgetResult Analyse(StatementModel statementModel, BudgetCollection budgets,
                                                              [NotNull] GlobalFilterCriteria criteria)
        {
            DateTime endDate, beginDate;

            AnalysisPreconditions(criteria, statementModel, budgets, out beginDate, out endDate);

            var result = new OverallPerformanceBudgetResult();

            List <BudgetModel> budgetsInvolved = budgets.ForDates(beginDate, endDate).ToList();

            result.UsesMultipleBudgets = budgetsInvolved.Count() > 1;
            var currentBudget = budgetsInvolved.Last(); // Use most recent budget as the current

            result.DurationInMonths = StatementModel.CalculateDuration(criteria, statementModel.Transactions);

            CalculateTotalsAndAverage(beginDate, statementModel, budgets, result);

            result.AnalysesList = new List <BucketPerformanceResult>();
            var list = new List <BucketPerformanceResult>();

            foreach (var bucket in this.bucketRepository.Buckets)
            {
                var bucketCopy           = bucket;
                List <Transaction> query = statementModel.Transactions.Where(t => t.BudgetBucket == bucketCopy).ToList();
                var totalSpent           = query.Sum(t => t.Amount);
                var averageSpend         = totalSpent / result.DurationInMonths;

                if (bucket == this.bucketRepository.SurplusBucket)
                {
                    var budgetedTotal  = CalculateBudgetedTotalAmount(beginDate, b => b.Surplus, budgets, result);
                    var perMonthBudget = budgetedTotal / result.DurationInMonths;
                    // Calc an average in case multiple budgets are used and the budgeted amounts are different.
                    var surplusAnalysis = new BucketPerformanceResult
                    {
                        Bucket                  = bucket,
                        TotalSpent              = -totalSpent,
                        Balance                 = budgetedTotal - totalSpent,
                        BudgetTotal             = budgetedTotal,
                        Budget                  = perMonthBudget,
                        AverageSpend            = -averageSpend,
                        BudgetComparedToAverage =
                            string.Format(CultureInfo.CurrentCulture, "Budget per Month: {0:C}, Actual per Month: {1:C}",
                                          perMonthBudget, -averageSpend)
                    };
                    list.Add(surplusAnalysis);
                    continue;
                }

                // If the most recent budget does not contain this bucket, then skip it.
                if (currentBudget.Expenses.Any(e => e.Bucket == bucket))
                {
                    var totalBudget = CalculateBudgetedTotalAmount(beginDate, BuildExpenseFinder(bucket), budgets,
                                                                   result);
                    var perMonthBudget = totalBudget / result.DurationInMonths;
                    var analysis       = new BucketPerformanceResult
                    {
                        Bucket                  = bucket,
                        TotalSpent              = -totalSpent,
                        Balance                 = totalBudget - totalSpent,
                        BudgetTotal             = totalBudget,
                        Budget                  = perMonthBudget,
                        AverageSpend            = -averageSpend,
                        BudgetComparedToAverage =
                            string.Format(CultureInfo.CurrentCulture, "Budget per Month: {0:C}, Actual per Month: {1:C}",
                                          perMonthBudget, -averageSpend)
                    };
                    list.Add(analysis);
                    continue;
                }

                // If the most recent budget does not contain this bucket, then skip it.
                if (currentBudget.Incomes.Any(i => i.Bucket == bucket))
                {
                    var totalBudget    = CalculateBudgetedTotalAmount(beginDate, BuildIncomeFinder(bucket), budgets, result);
                    var perMonthBudget = totalBudget / result.DurationInMonths;
                    var analysis       = new BucketPerformanceResult
                    {
                        Bucket                  = bucket,
                        TotalSpent              = totalSpent,
                        Balance                 = totalBudget - totalSpent,
                        BudgetTotal             = totalBudget,
                        Budget                  = perMonthBudget,
                        AverageSpend            = averageSpend,
                        BudgetComparedToAverage =
                            string.Format(CultureInfo.CurrentCulture, "Budget per Month: {0:C}, Actual per month: {1:C}",
                                          perMonthBudget, -averageSpend)
                    };
                    list.Add(analysis);
                }
            }

            result.AnalysesList = list.OrderByDescending(a => a.Percent).ToList();
            return(result);
        }
        private static decimal CalculateBudgetedTotalAmount(DateTime beginDate,
                                                            Func <BudgetModel, decimal> whichBudgetBucket, BudgetCollection budgets,
                                                            OverallPerformanceBudgetResult result)
        {
            if (!result.UsesMultipleBudgets)
            {
                return(whichBudgetBucket(budgets.ForDate(beginDate)) * result.DurationInMonths);
            }

            decimal budgetedAmount = 0;

            for (var month = 0; month < result.DurationInMonths; month++)
            {
                var budget = budgets.ForDate(beginDate.AddMonths(month));
                budgetedAmount += whichBudgetBucket(budget);
            }

            return(budgetedAmount);
        }
 public void TestInitialise()
 {
     TestData = BudgetModelTestData.CreateCollectionWith1And2();
 }
        public void Save(BudgetCollection budget)
        {
            BudgetCollectionDto dataFormat = this.toDtoMapper.Map(budget);

            string serialised = Serialise(dataFormat);
            WriteToDisk(dataFormat.FileName, serialised);

            EventHandler<ApplicationHookEventArgs> handler = ApplicationEvent;
            if (handler != null)
            {
                handler(this, new ApplicationHookEventArgs(ApplicationHookEventType.Repository, "BudgetRepository", ApplicationHookEventArgs.Save));
            }
        }
        public async Task LoadAsync(ApplicationDatabase applicationDatabase)
        {
            if (applicationDatabase == null)
            {
                throw new ArgumentNullException(nameof(applicationDatabase));
            }

            Budgets = await this.budgetRepository.LoadAsync(applicationDatabase.FullPath(applicationDatabase.BudgetCollectionStorageKey), applicationDatabase.IsEncrypted);
            UpdateServiceMonitor();
            NewDataSourceAvailable?.Invoke(this, EventArgs.Empty);
        }
 partial void ToModelPostprocessing(BudgetCollectionDto dto, ref BudgetCollection model)
 {
     var budgetCollection = model;
     dto.Budgets.ForEach(x => budgetCollection.Add(this.budgetMapper.ToModel(x)));
     dto.Buckets.ForEach(x => this.bucketRepo.GetOrCreateNew(x.Code, () => this.bucketMapper.ToModel(x)));
 }
        /// <summary>
        ///     Analyses the supplied statement using the supplied budget within the criteria given to this method.
        /// </summary>
        /// <param name="budgets">The current budgets collection.</param>
        /// <param name="criteria">The criteria to limit the analysis.</param>
        /// <param name="statementModel">The current statement model.</param>
        /// <exception cref="BudgetException">
        ///     Will be thrown if no budget is supplied or if no budget can be found for the dates
        ///     given in the criteria.
        /// </exception>
        /// <exception cref="ArgumentException">If statement or budget is null.</exception>
        public virtual OverallPerformanceBudgetResult Analyse(StatementModel statementModel, BudgetCollection budgets,
                                                              [NotNull] GlobalFilterCriteria criteria)
        {
            DateTime endDate, beginDate;
            AnalysisPreconditions(criteria, statementModel, budgets, out beginDate, out endDate);

            var result = new OverallPerformanceBudgetResult();

            List<BudgetModel> budgetsInvolved = budgets.ForDates(beginDate, endDate).ToList();
            result.UsesMultipleBudgets = budgetsInvolved.Count() > 1;
            var currentBudget = budgetsInvolved.Last(); // Use most recent budget as the current

            result.DurationInMonths = StatementModel.CalculateDuration(criteria, statementModel.Transactions);

            CalculateTotalsAndAverage(beginDate, statementModel, budgets, result);

            result.AnalysesList = new List<BucketPerformanceResult>();
            var list = new List<BucketPerformanceResult>();
            foreach (var bucket in this.bucketRepository.Buckets)
            {
                var bucketCopy = bucket;
                List<Transaction> query = statementModel.Transactions.Where(t => t.BudgetBucket == bucketCopy).ToList();
                var totalSpent = query.Sum(t => t.Amount);
                var averageSpend = totalSpent / result.DurationInMonths;

                if (bucket == this.bucketRepository.SurplusBucket)
                {
                    var budgetedTotal = CalculateBudgetedTotalAmount(beginDate, b => b.Surplus, budgets, result);
                    var perMonthBudget = budgetedTotal / result.DurationInMonths;
                    // Calc an average in case multiple budgets are used and the budgeted amounts are different.
                    var surplusAnalysis = new BucketPerformanceResult
                    {
                        Bucket = bucket,
                        TotalSpent = -totalSpent,
                        Balance = budgetedTotal - totalSpent,
                        BudgetTotal = budgetedTotal,
                        Budget = perMonthBudget,
                        AverageSpend = -averageSpend,
                        BudgetComparedToAverage =
                            string.Format(CultureInfo.CurrentCulture, "Budget per Month: {0:C}, Actual per Month: {1:C}",
                                perMonthBudget, -averageSpend)
                    };
                    list.Add(surplusAnalysis);
                    continue;
                }

                // If the most recent budget does not contain this bucket, then skip it.
                if (currentBudget.Expenses.Any(e => e.Bucket == bucket))
                {
                    var totalBudget = CalculateBudgetedTotalAmount(beginDate, BuildExpenseFinder(bucket), budgets,
                        result);
                    var perMonthBudget = totalBudget / result.DurationInMonths;
                    var analysis = new BucketPerformanceResult
                    {
                        Bucket = bucket,
                        TotalSpent = -totalSpent,
                        Balance = totalBudget - totalSpent,
                        BudgetTotal = totalBudget,
                        Budget = perMonthBudget,
                        AverageSpend = -averageSpend,
                        BudgetComparedToAverage =
                            string.Format(CultureInfo.CurrentCulture, "Budget per Month: {0:C}, Actual per Month: {1:C}",
                                perMonthBudget, -averageSpend)
                    };
                    list.Add(analysis);
                    continue;
                }

                // If the most recent budget does not contain this bucket, then skip it.
                if (currentBudget.Incomes.Any(i => i.Bucket == bucket))
                {
                    var totalBudget = CalculateBudgetedTotalAmount(beginDate, BuildIncomeFinder(bucket), budgets, result);
                    var perMonthBudget = totalBudget / result.DurationInMonths;
                    var analysis = new BucketPerformanceResult
                    {
                        Bucket = bucket,
                        TotalSpent = totalSpent,
                        Balance = totalBudget - totalSpent,
                        BudgetTotal = totalBudget,
                        Budget = perMonthBudget,
                        AverageSpend = averageSpend,
                        BudgetComparedToAverage =
                            string.Format(CultureInfo.CurrentCulture, "Budget per Month: {0:C}, Actual per month: {1:C}",
                                perMonthBudget, -averageSpend)
                    };
                    list.Add(analysis);
                }
            }

            result.AnalysesList = list.OrderByDescending(a => a.Percent).ToList();
            return result;
        }
        private static void CalculateTotalsAndAverage(DateTime beginDate, StatementModel statement,
                                                      BudgetCollection budgets, OverallPerformanceBudgetResult result)
        {
            // First total the expenses without the saved up for expenses.
            var totalExpensesSpend = statement.Transactions
                .Where(t => t.BudgetBucket is ExpenseBucket)
                .Sum(t => t.Amount);

            var totalSurplusSpend = statement.Transactions
                .Where(t => t.BudgetBucket is SurplusBucket)
                .Sum(t => t.Amount);

            result.AverageSpend = totalExpensesSpend / result.DurationInMonths; // Expected to be negative
            result.AverageSurplus = totalSurplusSpend / result.DurationInMonths; // Expected to be negative

            for (var month = 0; month < result.DurationInMonths; month++)
            {
                var budget = budgets.ForDate(beginDate.AddMonths(month));
                result.TotalBudgetExpenses += budget.Expenses.Sum(e => e.Amount);
            }

            result.OverallPerformance = result.AverageSpend + result.TotalBudgetExpenses;
        }
        private static decimal CalculateBudgetedTotalAmount(DateTime beginDate,
                                                            Func<BudgetModel, decimal> whichBudgetBucket, BudgetCollection budgets,
                                                            OverallPerformanceBudgetResult result)
        {
            if (!result.UsesMultipleBudgets)
            {
                return whichBudgetBucket(budgets.ForDate(beginDate)) * result.DurationInMonths;
            }

            decimal budgetedAmount = 0;
            for (var month = 0; month < result.DurationInMonths; month++)
            {
                var budget = budgets.ForDate(beginDate.AddMonths(month));
                budgetedAmount += whichBudgetBucket(budget);
            }

            return budgetedAmount;
        }
        private static void AnalysisPreconditions(GlobalFilterCriteria criteria, StatementModel statement,
                                                  BudgetCollection budgets, out DateTime beginDate, out DateTime endDate)
        {
            if (criteria == null)
            {
                throw new ArgumentNullException(nameof(criteria));
            }

            if (!criteria.Cleared && (criteria.BeginDate == null || criteria.EndDate == null))
            {
                throw new ArgumentException("The given criteria does not contain any filtering dates.");
            }

            if (statement == null)
            {
                throw new ArgumentNullException(nameof(statement),
                    "The statement supplied is null, analysis cannot proceed with no statement.");
            }

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

            if (criteria.Cleared)
            {
                beginDate = statement.AllTransactions.First().Date;
                endDate = statement.AllTransactions.Last().Date;
            }
            else
            {
                beginDate = criteria.BeginDate ?? DateTime.MinValue;
                endDate = criteria.EndDate ?? DateTime.MinValue;
            }
        }
 private void CreateNewBudgetCollection()
 {
     Budgets = this.budgetRepository.CreateNew();
 }
        /// <summary>
        ///     Validates the currently loaded <see cref="StatementModel" /> against the provided budgets and ensures all buckets
        ///     used by the transactions
        ///     exist in the budgets.  This is performed asynchronously.
        ///     This method can be called when a budget is loaded or changed or when a new Budget Analyser Statement is loaded.
        /// </summary>
        /// <param name="budgets">
        ///     The current budgets. This must be provided at least once. It can be omitted when
        ///     calling this method after the statement model has changed if the budget was previously provided.
        /// </param>
        /// <returns>
        ///     A task that will result in true if all buckets used, are present in the budgets, otherwise false.
        ///     If false, this indicates that some transactions may have their bucket allocation removed possibly resulting in
        ///     unintended data loss.
        /// </returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        public async Task<bool> ValidateWithCurrentBudgetsAsync(BudgetCollection budgets = null)
        {
            // This method must be called at least once with a budget collection.  Second and subsequent times do not require the budget.
            if (this.budgetCollection == null && budgets == null)
            {
                throw new ArgumentNullException(nameof(budgets));
            }

            this.budgetCollection = budgets ?? this.budgetCollection;

            if (StatementModel == null)
            {
                // Can't check yet, statement hasn't been loaded yet. Everything is ok for now.
                return true;
            }

            if (this.budgetCollection.GetHashCode() == this.budgetHash)
            {
                // This budget has already been checked against this statement. No need to repeatedly check the validity below, this is an expensive operation.
                // Everything is ok.
                return true;
            }

            var allBuckets = new List<BudgetBucket>(this.bucketRepository.Buckets.OrderBy(b => b.Code));
            var allTransactionHaveABucket = await Task.Run(
                () =>
                {
                    return StatementModel.AllTransactions
                        .Where(t => t.BudgetBucket != null)
                        .AsParallel()
                        .All(
                            t =>
                            {
                                var bucketExists = allBuckets.Contains(t.BudgetBucket);
                                if (!bucketExists)
                                {
                                    t.BudgetBucket = null;
                                    this.logger.LogWarning(l => l.Format("Transaction {0} has a bucket ({1}) that doesn't exist!", t.Date, t.BudgetBucket));
                                }
                                return bucketExists;
                            });
                });

            this.budgetHash = this.budgetCollection.GetHashCode();
            return allTransactionHaveABucket;
        }
 public BudgetSelectionViewModel(BudgetCollection budgets)
 {
     Budgets = budgets;
 }
        public async Task<BudgetCollection> LoadAsync(string storageKey, bool isEncrypted)
        {
            if (storageKey.IsNothing())
            {
                throw new ArgumentNullException(nameof(storageKey));
            }

            this.isEncryptedAtLastAccess = isEncrypted;
            var reader = this.readerWriterSelector.SelectReaderWriter(isEncrypted);

            if (!reader.FileExists(storageKey))
            {
                throw new KeyNotFoundException("File not found. " + storageKey);
            }

            object serialised;
            try
            {
                var xaml = await reader.LoadFromDiskAsync(storageKey); // May return null for some errors.
                serialised = Deserialise(xaml);
            }
            catch (XamlObjectWriterException ex)
            {
                throw new DataFormatException($"The budget file '{storageKey}' is an invalid format. This is probably due to changes in the code, most likely namespace changes.", ex);
            }
            catch (EncryptionKeyIncorrectException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new DataFormatException("Deserialisation the Budget file failed, an exception was thrown by the Xaml deserialiser, the file format is invalid.", ex);
            }

            var correctDataFormat = serialised as BudgetCollectionDto;
            if (correctDataFormat == null)
            {
                throw new DataFormatException(
                    string.Format(CultureInfo.InvariantCulture,
                        "The file used to store application state ({0}) is not in the correct format. It may have been tampered with.",
                        storageKey));
            }

            // Bucket Repository must be initialised first, the budget model incomes/expenses are dependent on the bucket repository.
            this.budgetBucketRepository.Initialise(correctDataFormat.Buckets);

            var budgetCollection = this.mapper.ToModel(correctDataFormat);
            budgetCollection.StorageKey = storageKey;
            this.currentBudgetCollection = budgetCollection;
            return budgetCollection;
        }
 partial void ToDtoPostprocessing(ref BudgetCollectionDto dto, BudgetCollection model)
 {
     dto.Buckets = this.bucketRepo.Buckets.Select(b => this.bucketMapper.ToDto(b)).ToList();
     dto.Budgets = model.ToList().Select(x => this.budgetMapper.ToDto(x)).ToList();
 }
示例#46
0
 private void OnBudgetReadyMessageReceived(BudgetReadyMessage message)
 {
     this.budgets = message.Budgets;
 }
示例#47
0
 public void TestInitialise()
 {
     TestData = BudgetModelTestData.CreateCollectionWith1And2();
 }
 public BudgetSelectionViewModel(BudgetCollection budgets)
 {
     Budgets = budgets;
 }
        private void ActOnTestData5(StatementModel statementModelTestData = null, bool ignoreWarnings = false)
        {
            this.testDataLedgerBook = LedgerBookTestData.TestData5(() => new LedgerBookTestHarness(new Mock<IReconciliationBuilder>().Object));
            this.testDataBudgets = new BudgetCollection(new[] { BudgetModelTestData.CreateTestData5() });
            this.testDataBudgetContext = new BudgetCurrencyContext(this.testDataBudgets, this.testDataBudgets.CurrentActiveBudget);
            this.testDataStatement = statementModelTestData ?? StatementModelTestData.TestData5();

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

            Act(bankBalances: new[] { new BankBalance(StatementModelTestData.ChequeAccount, 1850.5M), new BankBalance(StatementModelTestData.SavingsAccount, 1200M) },
                ignoreWarnings: ignoreWarnings);

            Console.WriteLine();
            Console.WriteLine("********************** AFTER RUNNING RECONCILIATION *******************************");
            this.testDataLedgerBook.Output(true);
        }
 /// <summary>
 ///     Closes the currently loaded file.  No warnings will be raised if there is unsaved data.
 /// </summary>
 public void Close()
 {
     this.transactions = new ObservableCollection<Transaction>();
     StatementModel?.Dispose();
     StatementModel = null;
     this.budgetCollection = null;
     this.budgetHash = 0;
     var handler = Closed;
     handler?.Invoke(this, EventArgs.Empty);
 }
 public BudgetReadyMessage(BudgetCurrencyContext activeBudget, BudgetCollection budgets = null)
 {
     ActiveBudget = activeBudget;
     Budgets = budgets;
 }
        public OverallPerformanceBudgetResult BuildChart(StatementModel statementModel, BudgetCollection budgets,
                                                         GlobalFilterCriteria criteria)
        {
            if (statementModel == null)
            {
                throw new ArgumentNullException(nameof(statementModel));
            }

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

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

            return(this.analyser.Analyse(statementModel, budgets, criteria));
        }
示例#53
0
 public BudgetReadyMessage(IBudgetCurrencyContext activeBudget, BudgetCollection budgets = null)
 {
     ActiveBudget = activeBudget;
     Budgets      = budgets;
 }
 private void CreateNewBudgetCollection()
 {
     Budgets = this.budgetRepository.CreateNew();
 }
 private void OnBudgetReadyMessageReceived(BudgetReadyMessage message)
 {
     this.budgets = message.Budgets;
 }
 partial void ToDtoPostprocessing(ref BudgetCollectionDto dto, BudgetCollection model)
 {
     dto.Buckets = this.bucketRepo.Buckets.Select(b => this.bucketMapper.ToDto(b)).ToList();
     dto.Budgets = model.ToList().Select(x => this.budgetMapper.ToDto(x)).ToList();
 }