Exemplo n.º 1
0
        /// <summary>
        /// Processes a recurring transaction. Meaning that instances get created.
        /// </summary>
        /// <param name="transaction">The recurring transaction.</param>
        /// <param name="addedDailyBalances">The daily balances that were already added. This is needed since the
        /// context does not contain already added entities, resulting in possible double daily balances which results
        /// in an error.</param>
        /// <remarks>Note that the context is not saved.</remarks>
        private void Process(RecurringTransactionEntity transaction, List <DailyBalanceEntity> addedDailyBalances)
        {
            transaction.VerifyEntitiesNotObsolete(this.splitwiseContext);

            var instances = new List <TransactionEntity>();

            while (!transaction.Finished)
            {
                if (!transaction.NeedsProcessing())
                {
                    break;
                }

                var instance = transaction.CreateOccurence();
                var isFuture = instance.Date > DateTime.Today.ToLocalDate();

                // Immediately process if transaction does not need to be confirmed.
                if (!instance.NeedsConfirmation && !isFuture)
                {
                    this.Process(instance, addedDailyBalances);
                }

                instances.Add(instance);
            }

            this.Context.Transactions.AddRange(instances);
        }
 /// <summary>
 /// Gets a value indicating if the recurring transaction needs to be processed.
 /// </summary>
 /// <param name="entity">The transaction.</param>
 /// <returns>A boolean indicating if the transaction needs to be processed.</returns>
 public static bool NeedsProcessing(this RecurringTransactionEntity entity)
 {
     return(!entity.Finished &&
            entity.NextOccurence
            .ToMaybe()
            .ValueOrElse(entity.StartDate)
            // Add instances a week in the future.
            <= LocalDate.FromDateTime(DateTime.Today.AddDays(7)));
 }
        /// <summary>
        /// Creates a recurring transaction with specified, or random values.
        /// </summary>
        /// <param name="context">The database context.</param>
        /// <param name="account">The account.</param>
        /// <param name="type">The type of the transaction.</param>
        /// <param name="description">The description of the transaction.</param>
        /// <param name="startDate">The start date of the transaction.</param>
        /// <param name="endDate">The end date of the transaction.</param>
        /// <param name="amount">The amount.</param>
        /// <param name="category">The category.</param>
        /// <param name="receivingAccount">The receiving account.</param>
        /// <param name="needsConfirmation">A value indicating if the transaction has to be confirmed.</param>
        /// <param name="interval">The interval.</param>
        /// <param name="intervalUnit">The interval unit.</param>
        /// <param name="paymentRequests">The payment requests that are linked to this transaction.</param>
        /// <param name="splitDetails">The split details which are linked to this transaction.</param>
        /// <returns>The created transaction.</returns>
        public static RecurringTransactionEntity GenerateRecurringTransaction(
            this Context context,
            AccountEntity account,
            TransactionType type           = TransactionType.Expense,
            string description             = null,
            LocalDate?startDate            = null,
            LocalDate?endDate              = null,
            decimal?amount                 = null,
            CategoryEntity category        = null,
            AccountEntity receivingAccount = null,
            bool needsConfirmation         = false,
            int interval = 1,
            IntervalUnit intervalUnit = IntervalUnit.Weeks,
            List <PaymentRequestEntity> paymentRequests = null,
            List <SplitDetailEntity> splitDetails       = null)
        {
            if ((type == TransactionType.Expense || type == TransactionType.Income) && category == null)
            {
                throw new Exception("Specify a category for an income or expense transaction.");
            }
            if (type == TransactionType.Transfer && receivingAccount == null)
            {
                throw new Exception("Specify a receiving account for a transfer transaction.");
            }

            var start = startDate ?? DateTime.Now.ToLocalDate();

            var entity = new RecurringTransactionEntity
            {
                Account           = account,
                Amount            = amount ?? (type == TransactionType.Expense ? -50 : 50),
                Description       = description ?? GetRandomString(),
                StartDate         = start,
                NextOccurence     = start,
                EndDate           = endDate,
                Category          = category,
                ReceivingAccount  = receivingAccount,
                NeedsConfirmation = needsConfirmation,
                Type            = type,
                Interval        = interval,
                IntervalUnit    = intervalUnit,
                PaymentRequests = paymentRequests ?? new List <PaymentRequestEntity>(),
                SplitDetails    = splitDetails ?? new List <SplitDetailEntity>(),
            };

            return(context.RecurringTransactions.Add(entity).Entity);
        }
Exemplo n.º 4
0
        public void RecurringTransactions_Future()
        {
            var account      = this.GenerateAccount().Id;
            var description  = "Description";
            var amount       = -30;
            var category     = this.GenerateCategory().Id;
            var startDate    = LocalDate.FromDateTime(DateTime.Today);
            var endDate      = LocalDate.FromDateTime(DateTime.Today.AddDays(14)); // 8 instances should be created
            var interval     = 1;
            var intervalUnit = IntervalUnit.Days;

            var rTransaction = new RecurringTransactionEntity
            {
                Description        = description,
                Amount             = amount,
                StartDate          = startDate,
                EndDate            = endDate,
                AccountId          = account,
                CategoryId         = category,
                ReceivingAccountId = null,
                Interval           = interval,
                IntervalUnit       = intervalUnit,
                NeedsConfirmation  = false,
                NextOccurence      = startDate,
                Type = TransactionType.Expense,
            };

            this.context.RecurringTransactions.Add(rTransaction);
            this.context.SaveChanges();

            this.TransactionProcessor.ProcessAll();

            this.RefreshContext();

            rTransaction = this.context.RecurringTransactions.Single(rt => rt.Id == rTransaction.Id);
            var instances = this.context.Transactions
                            .Where(t => t.RecurringTransactionId == rTransaction.Id &&
                                   !t.NeedsConfirmation) // Verify needs confirmation property
                            .ToList();

            Assert.False(rTransaction.Finished);
            Assert.Equal(8, instances.Count);
            Assert.Equal(1, instances.Count(t => t.Processed));
            Assert.Equal(7, instances.Count(t => !t.Processed));
        }
 /// <summary>
 /// Converts a <see cref="RecurringTransactionEntity"/> to a <see cref="InputRecurringTransaction"/>.
 /// </summary>
 /// <param name="transaction">The entity.</param>
 /// <returns>The converted object.</returns>
 public static InputRecurringTransaction ToInput(this RecurringTransactionEntity transaction)
 {
     return(new InputRecurringTransaction
     {
         Amount = transaction.Amount,
         Description = transaction.Description,
         StartDateString = transaction.StartDate.ToDateString(),
         EndDateString = transaction.EndDate.ToDateString(),
         AccountId = transaction.AccountId,
         CategoryId = transaction.CategoryId.ToMaybe(),
         ReceivingAccountId = transaction.ReceivingAccountId.ToMaybe(),
         PaymentRequests = transaction.PaymentRequests.Select(pr => pr.ToInput()).ToList(),
         SplitwiseSplits = transaction.SplitDetails.Select(pr => pr.ToInput()).ToList(),
         Interval = transaction.Interval,
         IntervalUnit = transaction.IntervalUnit,
         NeedsConfirmation = transaction.NeedsConfirmation,
     });
 }
        /// <summary>
        /// Creates a transaction with specified, or random values.
        /// </summary>
        /// <param name="context">The database context.</param>
        /// <param name="account">The account.</param>
        /// <param name="type">The type of the transaction.</param>
        /// <param name="description">The description of the transaction.</param>
        /// <param name="date">The date of the transaction.</param>
        /// <param name="amount">The amount.</param>
        /// <param name="category">The category.</param>
        /// <param name="receivingAccount">The receiving account.</param>
        /// <param name="recurringTransaction">The recurring transaction from which this is an instance.</param>
        /// <param name="splitwiseTransaction">The Splitwise transaction which is linked to this transaction.</param>
        /// <param name="paymentRequests">The payment requests that are linked to this transaction.</param>
        /// <param name="splitDetails">The split details which are linked to this transaction.</param>
        /// <param name="needsConfirmation">A value indicating if the transaction has to be confirmed.</param>
        /// <returns>The created transaction.</returns>
        public static TransactionEntity GenerateTransaction(
            this Context context,
            AccountEntity account,
            TransactionType type           = TransactionType.Expense,
            string description             = null,
            LocalDate?date                 = null,
            decimal?amount                 = null,
            CategoryEntity category        = null,
            AccountEntity receivingAccount = null,
            RecurringTransactionEntity recurringTransaction = null,
            SplitwiseTransactionEntity splitwiseTransaction = null,
            List <PaymentRequestEntity> paymentRequests     = null,
            List <SplitDetailEntity> splitDetails           = null,
            bool needsConfirmation = false)
        {
            if ((type == TransactionType.Expense || type == TransactionType.Income) && category == null)
            {
                throw new Exception("Specify a category for an income or expense transaction.");
            }
            if (type == TransactionType.Transfer && receivingAccount == null)
            {
                throw new Exception("Specify a receiving account for a transfer transaction.");
            }

            return(context.Transactions.Add(new TransactionEntity
            {
                Account = account,
                Amount = amount ?? (type == TransactionType.Expense ? -50 : 50),
                Description = description ?? GetRandomString(),
                Date = date ?? DateTime.Now.ToLocalDate(),
                Category = category,
                ReceivingAccount = receivingAccount,
                NeedsConfirmation = needsConfirmation,
                PaymentRequests = paymentRequests ?? new List <PaymentRequestEntity>(),
                SplitDetails = splitDetails ?? new List <SplitDetailEntity>(),
                Processed = false,
                IsConfirmed = !needsConfirmation,
                Type = type,
                RecurringTransaction = recurringTransaction,
                SplitwiseTransaction = splitwiseTransaction,
            }).Entity);
        }
        /// <summary>
        /// Calculates and sets the next occurrence for a recurring transaction.
        /// </summary>
        /// <param name="transaction">The recurring transaction.</param>
        public static void SetNextOccurrence(this RecurringTransactionEntity transaction)
        {
            if (!transaction.LastOccurence.HasValue)
            {
                transaction.NextOccurence = transaction.StartDate;
                transaction.Finished      = false;
                return;
            }

            var start = transaction.LastOccurence.Value;
            var next  = LocalDate.MinIsoValue;

            switch (transaction.IntervalUnit)
            {
            case IntervalUnit.Days:
                next = start.PlusDays(transaction.Interval);
                break;

            case IntervalUnit.Weeks:
                next = start.PlusWeeks(transaction.Interval);
                break;

            case IntervalUnit.Months:
                next = start.PlusMonths(transaction.Interval);
                break;

            case IntervalUnit.Years:
                next = start.PlusYears(transaction.Interval);
                break;
            }

            if (!transaction.EndDate.HasValue || next <= transaction.EndDate)
            {
                transaction.NextOccurence = next;
                transaction.Finished      = false;
            }
            else
            {
                transaction.NextOccurence = null;
                transaction.Finished      = true;
            }
        }
        /// <summary>
        /// Converts the entity to a data transfer object.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns>The data transfer object.</returns>
        public static RecurringTransaction AsRecurringTransaction(this RecurringTransactionEntity entity)
        {
            if (entity.CategoryId.HasValue && entity.Category == null)
            {
                throw new ArgumentNullException(nameof(entity.Category));
            }
            if (entity.Account == null)
            {
                throw new ArgumentNullException(nameof(entity.Account));
            }
            if (entity.ReceivingAccountId.HasValue && entity.ReceivingAccount == null)
            {
                throw new ArgumentNullException(nameof(entity.ReceivingAccount));
            }

            return(new RecurringTransaction
            {
                Id = entity.Id,
                Description = entity.Description,
                Amount = entity.Amount,
                StartDate = entity.StartDate.ToDateString(),
                EndDate = entity.EndDate.ToDateString(),
                Type = entity.Type,
                CategoryId = entity.CategoryId.ToMaybe(),
                Category = entity.Category.ToMaybe().Select(c => c.AsCategory()),
                AccountId = entity.AccountId,
                Account = entity.Account.AsAccount(),
                ReceivingAccountId = entity.ReceivingAccountId.ToMaybe(),
                ReceivingAccount = entity.ReceivingAccount.ToMaybe().Select(a => a.AsAccount()),
                IntervalUnit = entity.IntervalUnit,
                Interval = entity.Interval,
                NextOccurence = entity.NextOccurence.ToMaybe().Select(dt => dt.ToDateString()),
                Finished = entity.Finished,
                NeedsConfirmation = entity.NeedsConfirmation,
                SplitDetails = entity.SplitDetails.Select(sd => sd.AsSplitDetail()).ToList(),
                PaymentRequests = entity.PaymentRequests.Select(pr => pr.AsPaymentRequest()).ToList(),
                PersonalAmount = entity.PersonalAmount,
            });
        }
        /// <summary>
        /// Creates a new occurence and calculates the next occurence for a recurring transaction.
        /// </summary>
        /// <param name="transaction">The recurring transaction.</param>
        /// <returns>The created transaction.</returns>
        /// <remarks>Note that the date is not validated in this method.</remarks>
        public static TransactionEntity CreateOccurence(this RecurringTransactionEntity transaction)
        {
            if (!transaction.NextOccurence.HasValue)
            {
                throw new InvalidOperationException("Recurring transaction has no next occurence date set.");
            }

            var instance = new TransactionEntity
            {
                AccountId              = transaction.AccountId,
                Account                = transaction.Account,
                Amount                 = transaction.Amount,
                CategoryId             = transaction.CategoryId,
                Category               = transaction.Category,
                Date                   = transaction.NextOccurence.Value,
                Description            = transaction.Description,
                Processed              = false,
                ReceivingAccountId     = transaction.ReceivingAccountId,
                ReceivingAccount       = transaction.ReceivingAccount,
                RecurringTransactionId = transaction.Id,
                RecurringTransaction   = transaction,
                NeedsConfirmation      = transaction.NeedsConfirmation,
                IsConfirmed            = transaction.NeedsConfirmation ? false : (bool?)null,
                Type                   = transaction.Type,
                PaymentRequests        = new List <PaymentRequestEntity>(),
                SplitDetails           = transaction.SplitDetails.Select(sd =>
                                                                         new SplitDetailEntity
                {
                    Amount          = sd.Amount,
                    SplitwiseUserId = sd.SplitwiseUserId,
                }).ToList(),
            };

            transaction.LastOccurence = transaction.NextOccurence.Value;

            transaction.SetNextOccurrence();

            return(instance);
        }
        /// <summary>
        /// Verifies that the entities linked to a recurring transaction are not obsolete.
        /// </summary>
        /// <param name="transaction">The transaction.</param>
        /// <param name="splitwiseContext">The Splitwise context.</param>
        public static void VerifyEntitiesNotObsolete(
            this RecurringTransactionEntity transaction, ISplitwiseContext splitwiseContext)
        {
            if (transaction.Account == null)
            {
                throw new ArgumentNullException(nameof(transaction.Account));
            }
            if (transaction.ReceivingAccountId.HasValue && transaction.ReceivingAccount == null)
            {
                throw new ArgumentNullException(nameof(transaction.ReceivingAccount));
            }
            if (transaction.CategoryId.HasValue && transaction.Category == null)
            {
                throw new ArgumentNullException(nameof(transaction.Category));
            }

            if (transaction.Account.IsObsolete)
            {
                throw new IsObsoleteException($"Account is obsolete.");
            }
            if (transaction.ReceivingAccountId.HasValue && transaction.ReceivingAccount.IsObsolete)
            {
                throw new IsObsoleteException($"Receiver is obsolete.");
            }
            if (transaction.CategoryId.HasValue && transaction.Category.IsObsolete)
            {
                throw new IsObsoleteException($"Category is obsolete.");
            }

            var currentSplitwiseUserIds = splitwiseContext.GetUsers().Select(u => u.Id).ToList();

            foreach (var splitDetail in transaction.SplitDetails)
            {
                if (!currentSplitwiseUserIds.Contains(splitDetail.SplitwiseUserId))
                {
                    throw new IsObsoleteException("Splitwise user is obsolete.");
                }
            }
        }
Exemplo n.º 11
0
 /// <summary>
 /// Processes a recurring transaction. Meaning that instances get created.
 /// </summary>
 /// <param name="transaction">The recurring transaction.</param>
 /// <remarks>Note that the context is not saved.</remarks>
 public void Process(RecurringTransactionEntity transaction)
 {
     this.Process(transaction, new List <DailyBalanceEntity>());
 }
        /// <inheritdoc />
        public RecurringTransaction CreateRecurringTransaction(InputRecurringTransaction input)
        {
            this.validator.Description(input.Description);
            var startPeriod = this.validator.DateString(input.StartDateString, "startDate");
            var endPeriod   = input.EndDateString.Select(d => this.validator.DateString(d, "endDate"));

            if (endPeriod.IsSome)
            {
                this.validator.Period(startPeriod, endPeriod);
            }
            this.validator.Interval(input.Interval);
            var type = this.GetTransactionType(input.CategoryId, input.ReceivingAccountId, input.Amount);

            this.validator.Splits(this.splitwiseContext, input.PaymentRequests, input.SplitwiseSplits, type, input.Amount);

            return(this.ConcurrentInvoke(() =>
            {
                var processor = new TransactionProcessor(this.Context, this.splitwiseContext);

                var account = this.Context.Accounts.GetEntity(input.AccountId, false);

                this.validator.AccountType(account.Type);

                var category = input.CategoryId.Select(cId => this.Context.Categories.GetEntity(cId, false));

                AccountEntity receivingAccount = null;
                if (input.ReceivingAccountId.IsSome)
                {
                    receivingAccount = this.Context.Accounts.GetEntity(input.ReceivingAccountId.Value, false);

                    if (receivingAccount.Id == account.Id)
                    {
                        throw new ValidationException("Sender account can not be the same as receiver account.");
                    }

                    this.validator.AccountType(receivingAccount.Type);
                }

                // Verify a Splitwise account exists when adding providing splits.
                if (input.SplitwiseSplits.Any())
                {
                    this.Context.Accounts.GetSplitwiseEntity();
                }

                var entity = new RecurringTransactionEntity
                {
                    Description = input.Description,
                    Type = type,
                    Amount = input.Amount,
                    StartDate = startPeriod,
                    EndDate = endPeriod.ToNullable(),
                    AccountId = input.AccountId,
                    Account = account,
                    CategoryId = input.CategoryId.ToNullable(),
                    Category = category.ToNullIfNone(),
                    ReceivingAccountId = input.ReceivingAccountId.ToNullable(),
                    ReceivingAccount = receivingAccount,
                    Interval = input.Interval,
                    IntervalUnit = input.IntervalUnit,
                    NeedsConfirmation = input.NeedsConfirmation,
                    NextOccurence = startPeriod,
                    PaymentRequests = new List <PaymentRequestEntity>(), // TODO: Payment requests
                    SplitDetails = input.SplitwiseSplits.Select(s => s.ToSplitDetailEntity()).ToList(),
                };

                processor.Process(entity);

                this.Context.RecurringTransactions.Add(entity);
                this.Context.SaveChanges();

                return entity.AsRecurringTransaction();
            }));
        }