using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Linq; using System.Threading; using System.Transactions; 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) { TransactionOptions options = new TransactionOptions(); options.IsolationLevel = IsolationLevel.ReadUncommitted; TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required, options); 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) { Trace.WriteLine(string.Format("[BillingEngine] Processing order with id: {0}", order.OrderId)); if (!order.NextBillableDay.HasValue) { // An invoice has never been generated from this order order.NextBillableDay = currentDate; order.EntityState = State.Modified; } if (order.NextBillableDay <= currentDate) { // Collect Usage Data Trace.WriteLine("[BillingEngine] Collecting usage data"); //IList usageRecrods = BillingService.GetUsageRecords(order.User.Name, order.LastBillableDay.Value, currentDate); // Collect Invoice Data Trace.WriteLine("[BillingEngine] Collecting 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 rule execution feature from the windows workflow foundation for this task Trace.WriteLine("[BillingEngine] Bill post preccessing"); double subTotal = 0.0; foreach (InvoiceLine invoiceLine in invoice.InvoiceLines) { subTotal += invoiceLine.ProductPrice; } invoice.SubTotal = subTotal; invoice.Tax = invoice.SubTotal * 0.2; invoice.Total = invoice.SubTotal + invoice.Tax; // Invoice Formatting Engine: Generate Invoice Trace.WriteLine("[BillingEngine] Generating invoice document"); invoice.InvoiceDocument = InvoiceFormattingEngine.Generate(invoice); UpdateBillingState(order); billingService.UpdateOrder(order); billingService.SaveInvoice(invoice); transaction.Complete(); } } Trace.WriteLine("[BillingEngine] Finished billing cycle"); } catch (Exception e) { Trace.WriteLine(string.Format("[BillingEngine] The following exception occured: {0}", e.ToString())); } finally { transaction.Dispose(); } 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); } } }