using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using com.sun.xml.@internal.fastinfoset.sax; using Comal.Classes; using GenHTTP.Api.Protocol; using GenHTTP.Modules.IO; using InABox.Clients; using InABox.Core; using NPOI.HSSF.Util; using PRS.Shared; using PRSServices; using RequestMethod = GenHTTP.Api.Protocol.RequestMethod; namespace PRSLicensing; public class LicensingHandler : Handler { private LicensingHandlerProperties? _properties; public override void Init(LicensingHandlerProperties properties) { _properties = properties; } private IResponseBuilder RetrieveFees(IRequest request) { if (_properties == null) return request.Respond().Status(ResponseStatus.BadRequest); var lsr = Serialization.Deserialize(request.Content); if (lsr == null) return request.Respond().Status(ResponseStatus.BadRequest); Logger.Send(LogType.Information, "", $"License Enquiry Received ({lsr.RegistrationID})"); var productids = _properties.EngineProperties.Mappings.Select(x => x.Product.ID).ToArray(); if (!productids.Any()) { Logger.Send(LogType.Error,"","Engine Product Mapping List is Empty"); return request.Respond().Status(ResponseStatus.BadRequest); } var query = new MultiQuery(); query.Add( new Filter(x=>x.ID).InList(productids), new Columns(x=>x.ID) .Add(x=>x.NettCost) .Add(x=>x.Charge.Chargeable) .Add(x=>x.Charge.PriceType) .Add(x=>x.Charge.Markup) .Add(x=>x.Charge.Price) ); if (lsr.RegistrationID != Guid.Empty) query.Add( new Filter(x=>x.Customer.ID).IsEqualTo(lsr.RegistrationID).And(x=>x.Product.ID).InList(productids), new Columns(x=>x.Product.ID) .Add(x=>x.Product.NettCost) .Add(x=>x.Charge.Chargeable) .Add(x=>x.Charge.PriceType) .Add(x=>x.Charge.Markup) .Add(x=>x.Charge.Price) ); query.Query(); var products = query.Get().Rows.Select(x => x.ToObject()).ToArray(); var customerproducts = lsr.RegistrationID != Guid.Empty ? query.Get().Rows.Select(x => x.ToObject()).ToArray() : new CustomerProduct[] { }; var result = new LicenseFeeResponse(); foreach (var mapping in _properties.EngineProperties.Mappings) { var customer = customerproducts.FirstOrDefault(x => x.Product.ID == mapping.Product.ID); if (customer != null) { result.LicenseFees[mapping.License] = customer.Charge.PriceType == ProductPriceType.CostPlus ? customer.Product.NettCost * (100F + customer.Charge.Markup) / 100F : customer.Charge.Price; } else { var product = products.FirstOrDefault(x => x.ID == mapping.Product.ID); result.LicenseFees[mapping.License] = product != null ? product.Charge.PriceType == ProductPriceType.CostPlus ? product.NettCost * (100F + product.Charge.Markup) / 100F : product.Charge.Price : 0.0F; } } foreach (var timediscount in _properties.EngineProperties.TimeDiscounts) result.TimeDiscounts[timediscount.Months] = timediscount.Discount; foreach (var userdiscount in _properties.EngineProperties.UserDiscounts) result.UserDiscounts[userdiscount.Users] = userdiscount.Discount; return request.Respond().Status(ResponseStatus.OK).Content(Serialization.Serialize(result)); } private static List>> renewalPeriods = new List>> { new(1, x => x.AddDays(-7)), new(3, x => x.AddDays(-14)), new(6, x => x.AddMonths(-1)) }; private int GetMonthDifference(DateTime date1, DateTime date2){ var months = (date2.Year - date1.Year) * 12 + (date2.Month - date1.Month); if(date2.Day >= date1.Day){ return months; } return months - 1; } private LicenseData GenerateLicense(LicenseRenewalRequest renewalRequest){ var renewalPeriodInMonths = GetMonthDifference(renewalRequest.DateRenewed, renewalRequest.NewExpiry); var renewalAvailable = renewalPeriods .Where(x => renewalPeriodInMonths >= x.Item1) .MaxBy(x => x.Item1) .Item2(renewalRequest.NewExpiry); var newLicense = LicenseUtils.RenewLicense(renewalRequest.OldLicense, renewalRequest.DateRenewed, renewalRequest.NewExpiry, renewalAvailable, renewalRequest.Addresses); return newLicense; } private string NewCustomerCode(LicenseRenewalRequest renewalRequest) { // Try to build a 5 character abbreviation of the company name // is ACME Incorporated should become ACMIN // while P T Barnum should become PTBAR and so on String code = ""; var codecomps = renewalRequest.Company.CompanyName .ToUpper() .Split(' ') .ToList(); while (code.Length < 5 && codecomps.Any()) { var chunk = new string(codecomps.First().Take(Math.Min(3,5-code.Length)).ToArray()); code += chunk; codecomps.RemoveAt(0); } var codes = new Client().Query( new Filter(x => x.Code).BeginsWith(code), new Columns(x => x.Code))?.Rows.Select(x => x.Get(x => x.Code) ).ToList() ?? new List(); var i = 1; while(codes.Contains($"{code}{i:D3}")) i++; return $"{code}{i:D3}"; } private Customer CreateNewCustomer(LicenseRenewalRequest renewalRequest){ Logger.Send(LogType.Information, "", "Creating new customer"); var customer = new Customer { Code = NewCustomerCode(renewalRequest), Name = renewalRequest.Company.CompanyName, ABN = renewalRequest.Company.ABN, Delivery = renewalRequest.Company.DeliveryAddress, Email = renewalRequest.Company.Email, Postal = renewalRequest.Company.PostalAddress }; new Client().Save(customer, "Created by License Renewal"); return customer; } private CustomerDocument CreateNewCustomerDocument(Guid customerID, string fileName, string data){ var document = new Document { FileName = fileName, Private = true, TimeStamp = DateTime.Now, Data = Encoding.UTF8.GetBytes(data) }; document.CRC = CoreUtils.CalculateCRC(document.Data); new Client().Save(document, ""); var documentType = new Client() .Query(new Filter(x => x.Code).IsEqualTo("LICENSE"), new Columns(x => x.ID)) .Rows.FirstOrDefault()?.Get(x => x.ID) ?? Guid.Empty; if(documentType == Guid.Empty){ Logger.Send(LogType.Error, "", "Document Type 'LICENSE' doesn't exist"); } var customerDocument = new CustomerDocument(); customerDocument.Type.ID = documentType; customerDocument.EntityLink.ID = customerID; customerDocument.DocumentLink.ID = document.ID; new Client().Save(customerDocument, "Created by License Renewal"); return customerDocument; } private void CreateInvoice(Guid customerID, LicenseRenewalRequest renewalRequest){ var invoiceLines = new List(); var notes = new List(); foreach(var item in renewalRequest.LicenseTracking){ var invoiceLine = new InvoiceLine { Description = $"{item.Caption} - {item.Users} Users @ ${item.Rate:F2} per user", ExTax = item.ExGST }; invoiceLines.Add(invoiceLine); notes.Add(invoiceLine.Description); } var discountLine = new InvoiceLine { Description = $"${renewalRequest.Discount:F2} discount", ExTax = -renewalRequest.Discount }; invoiceLines.Add(discountLine); notes.Add(discountLine.Description); var invoice = new Invoice { Date = DateTime.Today, Description = $"PRS License Renewal", }; invoice.CustomerLink.ID = customerID; new Client().Save(invoice, "Created by License Renewal"); foreach(var line in invoiceLines){ line.InvoiceLink.ID = invoice.ID; } new Client().Save(invoiceLines, ""); var receipt = new Receipt { Date = DateTime.Today, Notes = $"PRS Renewal Invoice #{invoice.Number} ({renewalRequest.TransactionID})" }; new Client().Save(receipt, ""); var invoiceReceipt = new InvoiceReceipt { Notes = "Receipt for License Renewal", Amount = renewalRequest.Net }; invoiceReceipt.InvoiceLink.ID = invoice.ID; invoiceReceipt.ReceiptLink.ID = receipt.ID; new Client().Save(invoiceReceipt, ""); } private IResponseBuilder RenewLicense(IRequest request) { var renewal = Serialization.Deserialize(request.Content); if(renewal == null){ return request.Respond().Status(ResponseStatus.BadRequest); } Logger.Send(LogType.Information, "", $"Request for license renewal from {renewal.Company.CompanyName}"); var customerID = renewal.OldLicense.CustomerID; if(customerID == Guid.Empty){ customerID = CreateNewCustomer(renewal).ID; renewal.OldLicense.CustomerID = customerID; } Logger.Send(LogType.Information, "", "Generating new license"); var newLicense = GenerateLicense(renewal); var newLicenseData = LicenseUtils.EncryptLicense(newLicense); if(newLicenseData == null){ Logger.Send(LogType.Error, "", "Encryption of new license failed!"); return request.Respond().Status(ResponseStatus.InternalServerError); } Logger.Send(LogType.Information, "", "Generating customer document"); var customerDocument = CreateNewCustomerDocument(customerID, $"{renewal.Company.CompanyName} - PRS License - {DateTime.Now:dd MMM yyyy}.txt", newLicenseData); Logger.Send(LogType.Information, "", "Creating invoice"); CreateInvoice(customerID, renewal); LicenseRenewalResult result = new() { License = newLicenseData }; return request.Respond() .Status(ResponseStatus.OK) .Content(Serialization.Serialize(result)); // Send just the encrypted data. } private IResponseBuilder Ping(IRequest request) { return request.Respond().Status(ResponseStatus.OK); } private IResponseBuilder HandleGET(IRequest request) { var endpoint = request.Target.Current?.Value.ToLower() ?? ""; if (endpoint == nameof(Ping).ToLower()) return Ping(request); return request.Respond().Status(ResponseStatus.NotFound); } private IResponseBuilder HandlePOST(IRequest request) { var endpoint = request.Target.Current?.Value.ToLower() ?? ""; if (endpoint.Equals(nameof(LicenseFeeRequest).ToLower())) return RetrieveFees(request); if (endpoint.Equals(nameof(LicenseRenewalRequest).ToLower())) return RenewLicense(request); return request.Respond().Status(ResponseStatus.NotFound); } public override ValueTask HandleAsync(IRequest request) { try { switch (request.Method.KnownMethod) { case RequestMethod.GET: return new ValueTask(HandleGET(request).Build()); case RequestMethod.POST: return new ValueTask(HandlePOST(request).Build()); default: Logger.Send(LogType.Error, ClientFactory.UserID, $"Request method {request.Method.RawMethod} unknown"); return new ValueTask(request.Respond().Status(ResponseStatus.MethodNotAllowed).Build()); } } catch (Exception eListen) { Logger.Send(LogType.Error, ClientFactory.UserID, eListen.Message); return new ValueTask(request.Respond().Status(ResponseStatus.InternalServerError).Build()); } } }