private static IEnumerable <DateTimeInterval> GetAccountContractBillingCycles(AccountContract accountContract)
        {
            var start = PracticeController.ConvertToLocalDateTime(accountContract.Practice, accountContract.StartDate);

            if (start == null)
            {
                return(Enumerable.Empty <DateTimeInterval>());
            }

            var startValue  = start.Value;
            var periodType  = accountContract.BillingPeriodType;
            var periodSize  = accountContract.BillingPeriodSize ?? 1;
            var periodCount = accountContract.BillingPeriodCount ?? 7305; // = 365.25 * 20

            var mainIntervals = DateTimeHelper.IntervalRange(
                startValue,
                periodCount,
                d =>
            {
                if (periodType == "M")
                {
                    return(d.AddMonths(periodSize, startValue.Day));
                }
                if (periodType == "d")
                {
                    return(d.AddDays(periodSize));
                }
                if (periodType == "y")
                {
                    return(d.AddYears(periodSize));
                }
                throw new Exception("Unknown period type.");
            });

            return(mainIntervals);
        }
        public ActionResult GenerateInvoice(GenerateInvoiceViewModel viewModel)
        {
            if (viewModel.Step == null)
            {
                if (string.IsNullOrEmpty(viewModel.PracticeIdentifier))
                {
                    this.ModelState.Clear();
                    viewModel.Step = 0;
                    return(this.View());
                }

                viewModel.Step =
                    string.IsNullOrEmpty(viewModel.InvoiceName)
                    ? 1
                    : 2;
            }

            var step = viewModel.Step ?? 0;

            using (var db = this.CreateNewCerebelloEntities())
            {
                // Getting the practice indicated in the view-model.
                Practice practice = null;
                if (!string.IsNullOrWhiteSpace(viewModel.PracticeIdentifier))
                {
                    practice = db.Practices
                               .SingleOrDefault(p => p.UrlIdentifier == viewModel.PracticeIdentifier);
                }

                if (practice == null ||
                    practice.ActiveAccountContractId == null ||
                    practice.AccountContract.IsTrial ||
                    this.ModelState.HasPropertyErrors(() => viewModel.PracticeIdentifier))
                {
                    if (practice == null)
                    {
                        this.ModelState.AddModelError(() => viewModel.PracticeIdentifier, "Consultório inexistente.");
                    }

                    if (practice != null && practice.ActiveAccountContractId == null)
                    {
                        this.ModelState.AddModelError(() => viewModel.PracticeIdentifier, "Consultório não possui uma conta ativa.");
                    }

                    if (practice != null && practice.AccountContract.IsTrial)
                    {
                        this.ModelState.AddModelError(() => viewModel.PracticeIdentifier, "Consultório possui conta trial.");
                    }

                    return(this.View(viewModel));
                }

                var utcNow = this.GetUtcNow();
                viewModel.Invoices = GetAccountInvoices(practice, utcNow);

                if (step == 0)
                {
                    if (this.Request.HttpMethod == "POST")
                    {
                        return(this.RedirectToAction("GenerateInvoice", new { viewModel.PracticeIdentifier }));
                    }

                    // going to the next step
                    this.ModelState.Clear();
                    viewModel.Step = 1;
                    return(this.View(viewModel));
                }

                var localNow = PracticeController.ConvertToLocalDateTime(practice, utcNow);

                if (step == 1)
                {
                    this.ModelState.Clear();
                    viewModel.Step = 1;
                    return(this.View(viewModel));
                }

                var selectedInvoiceInfo = viewModel.Invoices.SingleOrDefault(bi => bi.NameId == viewModel.InvoiceName);

                if (selectedInvoiceInfo == null)
                {
                    this.ModelState.AddModelError(() => viewModel.InvoiceName, "Nome de invoice não encontrado.");
                    viewModel.Step = 1;
                    return(this.View(viewModel));
                }

                Billing billing = null;
                var     idSet   = string.Format(
                    "CEREB.{1}{2}.{0}",
                    localNow.Year,
                    practice.AccountContract.BillingPeriodSize,
                    practice.AccountContract.BillingPeriodType);

                var invoiceStartUtc = PracticeController.ConvertToUtcDateTime(practice, selectedInvoiceInfo.Start);
                billing = db.Billings.SingleOrDefault(b => b.PracticeId == practice.Id &&
                                                      b.MainAccountContractId == practice.ActiveAccountContractId &&
                                                      b.ReferenceDate == invoiceStartUtc);

                if (billing == null)
                {
                    billing = new Billing
                    {
                        PracticeId            = practice.Id,
                        AfterDueMonthlyTax    = 1.00m, // 1%
                        AfterDueTax           = 2.00m, // 2%
                        IssuanceDate          = utcNow,
                        MainAmount            = selectedInvoiceInfo.TotalAmount,
                        MainDiscount          = selectedInvoiceInfo.TotalDiscount,
                        DueDate               = PracticeController.ConvertToUtcDateTime(practice, selectedInvoiceInfo.DueDate),
                        IdentitySetName       = idSet,
                        IdentitySetNumber     = db.Billings.Count(b => b.PracticeId == practice.Id && b.IdentitySetName == idSet) + 1,
                        ReferenceDate         = PracticeController.ConvertToUtcDateTime(practice, selectedInvoiceInfo.Start),
                        ReferenceDateEnd      = PracticeController.ConvertToUtcDateTime(practice, selectedInvoiceInfo.End),
                        MainAccountContractId = practice.ActiveAccountContractId.Value,
                    };

                    db.Billings.AddObject(billing);
                }

                if (practice.AccountContract.BillingPaymentMethod == "PayPal Invoice")
                {
                    this.ViewBag.IsPayPalInvoice = true;
                }

                if (this.ModelState.IsValid)
                {
                    db.SaveChanges();

                    // adding PayPal invoice info
                    viewModel.PayPalInvoice = new GenerateInvoiceViewModel.PayPalInvoiceInfo
                    {
                        UserEmail    = practice.Owner.Person.Email,
                        IssuanceDate = localNow.ToString("dd-MM-yyyy"),
                        Currency     = "BRL - Reais",
                        Number       = string.Format("{0}.{1}", billing.IdentitySetName, billing.IdentitySetNumber),
                        DuaDate      = selectedInvoiceInfo.DueDate.ToString("dd-MM-yyyy"),
                        Terms        = "Vencimento na data especificada",
                        Items        = new List <GenerateInvoiceViewModel.PayPalInvoiceItem>(),
                    };

                    var strStartToEnd = selectedInvoiceInfo.End != null
                        ? string.Format(
                        "{0} até {1}",
                        selectedInvoiceInfo.Start.ToString("yyyy'-'MM'-'dd"),
                        selectedInvoiceInfo.End.Value.ToString("yyyy'-'MM'-'dd"))
                        : string.Format(
                        "{0}",
                        selectedInvoiceInfo.Start.ToString("yyyy'-'MM'-'dd"));

                    viewModel.PayPalInvoice.Items.Add(
                        new GenerateInvoiceViewModel.PayPalInvoiceItem
                    {
                        NameId      = "Assinatura Cerebello",
                        Date        = "",
                        Quantity    = 1,
                        UnitPrice   = selectedInvoiceInfo.TotalAmount.ToString("0.00", CultureInfo.InvariantCulture).Replace('.', ','),
                        Description = string.Format("Assinatura do plano profissional do Cerebello ({0})", strStartToEnd),
                    });

                    var periodType     = practice.AccountContract.BillingPeriodType;
                    var periodSize     = practice.AccountContract.BillingPeriodSize;
                    var discountReason =
                        periodType == "M" && periodSize == 1 ? "mensal" :
                        periodType == "M" && periodSize == 3 ? "trimestral" :
                        periodType == "M" && periodSize == 6 ? "semestral" :
                        periodType == "M" && periodSize == 12 || periodType == "Y" && periodSize == 1 ? "anual" :
                        "";

                    if (selectedInvoiceInfo.TotalDiscount > 0)
                    {
                        viewModel.PayPalInvoice.Items.Add(
                            new GenerateInvoiceViewModel.PayPalInvoiceItem
                        {
                            NameId      = "Desconto da Assinatura Cerebello",
                            Date        = "",
                            Quantity    = 1,
                            UnitPrice   = selectedInvoiceInfo.TotalDiscount.ToString("'-'0.00", CultureInfo.InvariantCulture).Replace('.', ','),
                            Description = string.Format("Desconto na Assinatura (condições especiais para pagamento {0})", discountReason)
                        });
                    }

                    viewModel.Step = 2;
                    return(this.View(viewModel));
                }
            }

            return(this.View(viewModel));
        }
        private static List <InvoiceInfo> GetAccountInvoices(Practice practice, DateTime utcNow)
        {
            // getting info about the services that are being used, that is all active AccountContracts
            var activeAccountContracts = new[] { practice.AccountContract }
            .Concat(
                practice.AccountContracts
                .Where(ac => ac.EndDate == null || ac.EndDate >= utcNow)
                .Where(ac => ac.Id != practice.AccountContract.Id))
            .ToList();

            // The main account contract dictates the billing periods of subcontracts.
            // We must see what products (active account contracts) don't have
            // billing items in any period.
            var mainIntervals = GetAccountContractBillingCycles(practice.AccountContract)
                                .TakeWhile(it => it.Start < utcNow)
                                .ToList();

            var dateSetBillingCycles = new ContinuousSet <DateTime>(mainIntervals.Count * 2);

            foreach (var dateTimeInterval in mainIntervals)
            {
                dateSetBillingCycles.AddInterval(dateTimeInterval.Start, true, dateTimeInterval.End, false);
            }
            dateSetBillingCycles.Flatten(mergeRedundantTrues: false);

            var billingsDic = new Dictionary <DateTime, InvoiceInfo>();

            foreach (var activeAccountContract in activeAccountContracts)
            {
                var accountStart = PracticeController.ConvertToLocalDateTime(practice, activeAccountContract.StartDate);
                var accountEnd   = PracticeController.ConvertToLocalDateTime(practice, activeAccountContract.EndDate);

                var currentContractInterval = new ContinuousSet <DateTime>();
                if (accountStart.HasValue)
                {
                    currentContractInterval.AddPoint(accountStart.Value, false, true, true);
                }
                if (accountEnd.HasValue)
                {
                    currentContractInterval.AddPoint(accountEnd.Value, true, false, false);
                }

                // Getting intervals that already have invoices.
                var dateSetBillings = new ContinuousSet <DateTime>();
                foreach (var eachBilling in activeAccountContract.Billings)
                {
                    if (eachBilling.ReferenceDate == null)
                    {
                        continue;
                    }

                    var billingRefStart = PracticeController.ConvertToLocalDateTime(practice, (DateTime)eachBilling.ReferenceDate);
                    var billingRefEnd   = PracticeController.ConvertToLocalDateTime(practice, eachBilling.ReferenceDateEnd);

                    if (billingRefEnd.HasValue)
                    {
                        dateSetBillings.AddInterval(billingRefStart, true, billingRefEnd.Value, false);
                    }
                    else
                    {
                        dateSetBillings.AddPoint(billingRefStart, false, true, true);
                    }

                    InvoiceInfo billingInfo;
                    if (!billingsDic.TryGetValue(billingRefStart, out billingInfo))
                    {
                        billingsDic[billingRefStart] = billingInfo = new InvoiceInfo
                        {
                            IsSaved = true,
                            Start   = billingRefStart,
                            End     = billingRefEnd,
                            DueDate = PracticeController.ConvertToLocalDateTime(practice, eachBilling.DueDate),
                        }
                    }
                    ;

                    billingInfo.Items.Add(
                        new InvoiceInfo.Item
                    {
                        Amount         = activeAccountContract.BillingAmount ?? 0,
                        DiscountAmount = activeAccountContract.BillingDiscountAmount ?? 0,
                        ContractType   = (ContractTypes)activeAccountContract.ContractTypeId,
                    });
                }

                dateSetBillings.Flatten();

                // Merging date sets, to see where there are holes without any invoice.
                var dateSetResult = !dateSetBillings & dateSetBillingCycles & currentContractInterval;

                // Getting the intervals that represents missing invoices.
                foreach (var eachMissingInterval in dateSetResult.PositiveIntervals)
                {
                    Debug.Assert(practice.AccountContract.BillingDueDay != null, "practice.AccountContract.BillingDueDay != null");
                    var dueDay  = practice.AccountContract.BillingDueDay.Value;
                    var dueDate = DateTimeHelper.FindDayOfMonthOrNearest(eachMissingInterval.Start, dueDay);
                    if (dueDate >= eachMissingInterval.Start.AddDays(10))
                    {
                        dueDate = dueDate.AddMonths(1, dueDay);
                    }

                    InvoiceInfo billingInfo;
                    if (!billingsDic.TryGetValue(eachMissingInterval.Start, out billingInfo))
                    {
                        billingsDic[eachMissingInterval.Start] = billingInfo = new InvoiceInfo
                        {
                            Start   = eachMissingInterval.Start,
                            End     = eachMissingInterval.End,
                            DueDate = dueDate,
                        }
                    }
                    ;

                    billingInfo.Items.Add(
                        new InvoiceInfo.Item
                    {
                        Amount         = activeAccountContract.BillingAmount ?? 0,
                        DiscountAmount = activeAccountContract.BillingDiscountAmount ?? 0,
                        ContractType   = (ContractTypes)activeAccountContract.ContractTypeId,
                    });
                }
            }

            var result = billingsDic.Values.OrderBy(bi => bi.Start).ToList();

            return(result);
        }