using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Linq; using System.Threading; using HeuristicLab.Services.Optimization.Billing.Business; using HeuristicLab.Services.Optimization.Billing.Interfaces; using HeuristicLab.Services.Optimization.Billing.Model; namespace HeuristicLab.Services.Optimization.Billing.BillingEngine { public class BillingEngine { private bool stop; private AutoResetEvent waitHandle; private TimeSpan interval; private DateTime currentDate; private IOptimizationBilling billingService; public IOptimizationBilling BillingService { get { if (billingService == null) { billingService = new BillingService(); } return billingService; } set { billingService = value; } } private IInvoiceFormattingEngine invoiceFormattingEngine; public IInvoiceFormattingEngine InvoiceFormattingEngine { get { if (invoiceFormattingEngine == null) { invoiceFormattingEngine = new PlainTextInvoiceFormattingEngine(); } return invoiceFormattingEngine; } set { invoiceFormattingEngine = value; } } public BillingEngine() { stop = false; interval = TimeSpan.Parse(ConfigurationManager.AppSettings["BillingEngineInterval"]); waitHandle = new AutoResetEvent(true); } public void StopBillingEngine() { stop = true; waitHandle.Set(); } public void Run() { while (!stop) { try { Trace.WriteLine("[BillingEngine] Start billing cycle"); currentDate = DateTime.Now.Date; Trace.WriteLine(string.Format("[BillingEngine] Current date: {0}", currentDate)); IList activeOrders = BillingService.GetOrdersByState(OrderState.Active); Trace.WriteLine(string.Format("[BillingEngine] Processing {0} active orders.", activeOrders.Count)); foreach (Order order in activeOrders) { if (!order.NextBillableDay.HasValue) { // An invoice has never been generated from this order order.NextBillableDay = currentDate; order.EntityState = State.Modified; } if (order.NextBillableDay >= currentDate) { // TODO: Embed everything in a transaction // Collect Usage Data //IList usageRecrods = BillingService.GetUsageRecords(order.User.Name, order.LastBillableDay.Value, currentDate); // Collect Invoice Data Invoice invoice = new Invoice(); invoice.Order = order; invoice.User = order.User; invoice.Due = currentDate.AddMonths(1); invoice.InvoiceDate = currentDate; invoice.Status = InvoiceStatus.Open; invoice.InvoiceLines = new List(); foreach (OrderLine orderLine in order.OrderLines) { InvoiceLine invoiceLine = new InvoiceLine(); invoiceLine.Invoice = invoice; invoiceLine.Product = orderLine.Product; invoiceLine.ProductPrice = orderLine.ProductPrice; invoiceLine.Quantity = orderLine.Quantity; invoice.InvoiceLines.Add(invoiceLine); } InvoiceLine usageCharges = new InvoiceLine(); usageCharges.Invoice = invoice; usageCharges.Product = billingService.GetProductByName("Oaas Usage Charges").First(); usageCharges.ProductPrice = 999.99; usageCharges.Quantity = 1; invoice.InvoiceLines.Add(usageCharges); // Bill Post Processing: Apply Discounts or Promotions // add discounts, sum up items to sub-total, calc. tax -> total current charges // maybe use windows workflow foundation for this task // Invoice Formatting Engine: Generate Invoice invoice.InvoiceDocument = InvoiceFormattingEngine.Generate(invoice); UpdateBillingState(order); billingService.UpdateOrder(order); billingService.SaveInvoice(invoice); } } Trace.WriteLine("[BillingEngine] Finished billing cycle"); } catch (Exception e) { Trace.WriteLine(string.Format("[BillingEngine] The following exception occured: {0}", e.ToString())); } waitHandle.WaitOne(interval); } waitHandle.Close(); } private void UpdateBillingState(Order order) { if (order.BillingType == BillingType.Pre) { throw new Exception("This billing type is currently not supported: " + order.BillingType); } order.LastBillableDay = currentDate; DateTime nextBillingDay = CalculateNextBillingDate(order); if (order.ActiveUntil.HasValue && order.ActiveUntil == currentDate) { order.State = OrderState.Finished; order.NextBillableDay = null; } else if (order.ActiveUntil.HasValue && order.ActiveUntil < nextBillingDay) { order.NextBillableDay = order.ActiveUntil; } else { order.NextBillableDay = nextBillingDay; } order.EntityState = State.Modified; } private DateTime CalculateNextBillingDate(Order order) { DateTime nextBillingDay = currentDate; if (order.BillingPeriod == BillingPeriod.Weekly) { nextBillingDay = Next(currentDate, DayOfWeek.Sunday); } else if (order.BillingPeriod == BillingPeriod.Monthly) { DateTime endOfNextMonth = new DateTime( currentDate.AddMonths(1).Year, currentDate.AddMonths(1).Month, DateTime.DaysInMonth(currentDate.AddMonths(1).Year, currentDate.AddMonths(1).Month)); nextBillingDay = endOfNextMonth; } else if (order.BillingPeriod == BillingPeriod.Yearly) { nextBillingDay = new DateTime(currentDate.AddYears(1).Year, 12, 31); } return nextBillingDay; } private DateTime Next(DateTime from, DayOfWeek dayOfWeek) { int start = (int)from.DayOfWeek; int target = (int)dayOfWeek; if (target <= start) target += 7; return from.AddDays(target - start); } } }