LicensingHandler.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using com.sun.xml.@internal.fastinfoset.sax;
  7. using Comal.Classes;
  8. using GenHTTP.Api.Protocol;
  9. using GenHTTP.Modules.IO;
  10. using InABox.Clients;
  11. using InABox.Core;
  12. using NPOI.HSSF.Util;
  13. using PRS.Shared;
  14. using PRSServices;
  15. using RequestMethod = GenHTTP.Api.Protocol.RequestMethod;
  16. namespace PRSLicensing;
  17. public class LicensingHandler : Handler<LicensingHandlerProperties>
  18. {
  19. private LicensingHandlerProperties? _properties;
  20. public override void Init(LicensingHandlerProperties properties)
  21. {
  22. _properties = properties;
  23. }
  24. private IResponseBuilder RetrieveFees(IRequest request)
  25. {
  26. if (_properties == null)
  27. return request.Respond().Status(ResponseStatus.BadRequest);
  28. var lsr = Serialization.Deserialize<LicenseFeeRequest>(request.Content);
  29. if (lsr == null)
  30. return request.Respond().Status(ResponseStatus.BadRequest);
  31. Logger.Send(LogType.Information, "", $"License Enquiry Received ({lsr.RegistrationID})");
  32. var productids = _properties.EngineProperties.Mappings.Select(x => x.Product.ID).ToArray();
  33. if (!productids.Any())
  34. {
  35. Logger.Send(LogType.Error,"","Engine Product Mapping List is Empty");
  36. return request.Respond().Status(ResponseStatus.BadRequest);
  37. }
  38. var query = new MultiQuery();
  39. query.Add(
  40. Filter<Product>.Where(x => x.ID).InList(productids),
  41. Columns.None<Product>()
  42. .Add(x => x.ID)
  43. .Add(x => x.NettCost));
  44. query.Add(
  45. Filter<ProductInstance>.Where(x => x.ID).InList(productids),
  46. Columns.None<ProductInstance>()
  47. .Add(x => x.Product.ID)
  48. .Add(x => x.Charge.Chargeable)
  49. .Add(x => x.Charge.PriceType)
  50. .Add(x => x.Charge.Price)
  51. .Add(x => x.Charge.Markup));
  52. if (lsr.RegistrationID != Guid.Empty)
  53. query.Add(
  54. Filter<CustomerProduct>.Where(x =>x.Customer.ID).IsEqualTo(lsr.RegistrationID).And(x=>x.Product.ID).InList(productids),
  55. Columns.None<CustomerProduct>()
  56. .Add(x => x.Product.ID)
  57. .Add(x => x.Discount)
  58. );
  59. query.Query();
  60. var products = query.Get<Product>()
  61. .ToObjects<Product>()
  62. .ToDictionary(x => x.ID);
  63. var customerproducts = lsr.RegistrationID != Guid.Empty
  64. ? query.Get<CustomerProduct>()
  65. .ToObjects<CustomerProduct>()
  66. .GroupBy(x => x.Product.ID)
  67. .ToDictionary(x => x.Key, x => x.First())
  68. : [];
  69. var productInstances = query.Get<ProductInstance>()
  70. .ToObjects<ProductInstance>()
  71. .GroupBy(x => x.Product.ID)
  72. .ToDictionary(x => x.Key, x => x.First());
  73. var result = new LicenseFeeResponse();
  74. foreach (var mapping in _properties.EngineProperties.Mappings)
  75. {
  76. if(!products.TryGetValue(mapping.Product.ID, out var product))
  77. {
  78. Logger.Send(LogType.Information, "", $"No product found for ID {mapping.Product.ID}");
  79. continue;
  80. }
  81. if(!productInstances.TryGetValue(mapping.Product.ID, out var productInstance))
  82. {
  83. result.LicenseFees[mapping.License] = 0;
  84. Logger.Send(LogType.Information, "", $"No product instance for product {mapping.Product.ID}");
  85. continue;
  86. }
  87. double cost;
  88. if(productInstance.Charge.PriceType == ProductPriceType.CostPlus)
  89. {
  90. cost = product.NettCost * (100 + productInstance.Charge.Markup) / 100;
  91. }
  92. else
  93. {
  94. cost = productInstance.Charge.Price;
  95. }
  96. if(customerproducts.TryGetValue(mapping.Product.ID, out var customerProduct))
  97. {
  98. cost = cost * (100F - customerProduct.Discount) / 100F;
  99. }
  100. result.LicenseFees[mapping.License] = cost;
  101. }
  102. foreach (var timediscount in _properties.EngineProperties.TimeDiscounts)
  103. result.TimeDiscounts[timediscount.Months] = timediscount.Discount;
  104. foreach (var userdiscount in _properties.EngineProperties.UserDiscounts)
  105. result.UserDiscounts[userdiscount.Users] = userdiscount.Discount;
  106. return request.Respond().Status(ResponseStatus.OK).Content(Serialization.Serialize(result));
  107. }
  108. private static List<Tuple<int, Func<DateTime, DateTime>>> renewalPeriods = new List<Tuple<int, Func<DateTime, DateTime>>> {
  109. new(1, x => x.AddDays(-7)),
  110. new(3, x => x.AddDays(-14)),
  111. new(6, x => x.AddMonths(-1))
  112. };
  113. private int GetMonthDifference(DateTime date1, DateTime date2){
  114. var months = (date2.Year - date1.Year) * 12 + (date2.Month - date1.Month);
  115. if(date2.Day >= date1.Day){
  116. return months;
  117. }
  118. return months - 1;
  119. }
  120. private LicenseData GenerateLicense(LicenseRenewalRequest renewalRequest){
  121. var renewalPeriodInMonths = GetMonthDifference(renewalRequest.DateRenewed, renewalRequest.NewExpiry);
  122. var renewalAvailable = renewalPeriods
  123. .Where(x => renewalPeriodInMonths >= x.Item1)
  124. .MaxBy(x => x.Item1)
  125. .Item2(renewalRequest.NewExpiry);
  126. var newLicense = LicenseUtils.RenewLicense(renewalRequest.OldLicense, renewalRequest.DateRenewed, renewalRequest.NewExpiry, renewalAvailable, renewalRequest.Addresses);
  127. return newLicense;
  128. }
  129. private string NewCustomerCode(LicenseRenewalRequest renewalRequest)
  130. {
  131. // Try to build a 5 character abbreviation of the company name
  132. // is ACME Incorporated should become ACMIN
  133. // while P T Barnum should become PTBAR and so on
  134. String code = "";
  135. var codecomps = renewalRequest.Company.CompanyName
  136. .ToUpper()
  137. .Split(' ')
  138. .ToList();
  139. while (code.Length < 5 && codecomps.Any())
  140. {
  141. var chunk = new string(codecomps.First().Take(Math.Min(3,5-code.Length)).ToArray());
  142. code += chunk;
  143. codecomps.RemoveAt(0);
  144. }
  145. var codes = new Client<Customer>().Query(
  146. Filter<Customer>.Where(x => x.Code).BeginsWith(code),
  147. Columns.None<Customer>().Add(x => x.Code))?.Rows.Select(x => x.Get<Customer, string>(x => x.Code)
  148. ).ToList() ?? new List<string>();
  149. var i = 1;
  150. while(codes.Contains($"{code}{i:D3}"))
  151. i++;
  152. return $"{code}{i:D3}";
  153. }
  154. private Customer CreateNewCustomer(LicenseRenewalRequest renewalRequest){
  155. Logger.Send(LogType.Information, "", "Creating new customer");
  156. var customer = new Customer {
  157. Code = NewCustomerCode(renewalRequest),
  158. Name = renewalRequest.Company.CompanyName,
  159. ABN = renewalRequest.Company.ABN,
  160. Delivery = renewalRequest.Company.DeliveryAddress,
  161. Email = renewalRequest.Company.Email,
  162. Postal = renewalRequest.Company.PostalAddress
  163. };
  164. new Client<Customer>().Save(customer, "Created by License Renewal");
  165. return customer;
  166. }
  167. private CustomerDocument CreateNewCustomerDocument(Guid customerID, string fileName, string data){
  168. var document = new Document {
  169. FileName = fileName,
  170. Private = true,
  171. TimeStamp = DateTime.Now,
  172. Data = Encoding.UTF8.GetBytes(data)
  173. };
  174. document.CRC = CoreUtils.CalculateCRC(document.Data);
  175. new Client<Document>().Save(document, "");
  176. var documentType = new Client<DocumentType>()
  177. .Query(
  178. Filter<DocumentType>.Where(x => x.Code).IsEqualTo("LICENSE"),
  179. Columns.None<DocumentType>().Add(x => x.ID))
  180. .Rows.FirstOrDefault()?.Get<DocumentType, Guid>(x => x.ID) ?? Guid.Empty;
  181. if(documentType == Guid.Empty){
  182. Logger.Send(LogType.Error, "", "Document Type 'LICENSE' doesn't exist");
  183. }
  184. var customerDocument = new CustomerDocument();
  185. customerDocument.Type.ID = documentType;
  186. customerDocument.Entity.ID = customerID;
  187. customerDocument.Document.ID = document.ID;
  188. new Client<CustomerDocument>().Save(customerDocument, "Created by License Renewal");
  189. return customerDocument;
  190. }
  191. private void CreateInvoice(Guid customerID, LicenseRenewalRequest renewalRequest){
  192. var invoiceLines = new List<InvoiceLine>();
  193. var notes = new List<string>();
  194. foreach(var item in renewalRequest.LicenseTracking){
  195. var invoiceLine = new InvoiceLine {
  196. Description = $"{item.Caption} - {item.Users} Users @ ${item.Rate:F2} per user",
  197. ExTax = item.ExGST
  198. };
  199. invoiceLines.Add(invoiceLine);
  200. notes.Add(invoiceLine.Description);
  201. }
  202. var discountLine = new InvoiceLine {
  203. Description = $"${renewalRequest.Discount:F2} discount",
  204. ExTax = -renewalRequest.Discount
  205. };
  206. invoiceLines.Add(discountLine);
  207. notes.Add(discountLine.Description);
  208. var invoice = new Invoice {
  209. Date = DateTime.Today,
  210. Description = $"PRS License Renewal",
  211. };
  212. invoice.Customer.ID = customerID;
  213. new Client<Invoice>().Save(invoice, "Created by License Renewal");
  214. foreach(var line in invoiceLines){
  215. line.Invoice.ID = invoice.ID;
  216. }
  217. new Client<InvoiceLine>().Save(invoiceLines, "");
  218. var receipt = new Receipt {
  219. Date = DateTime.Today,
  220. Notes = $"PRS Renewal Invoice #{invoice.Number} ({renewalRequest.TransactionID})"
  221. };
  222. new Client<Receipt>().Save(receipt, "");
  223. var invoiceReceipt = new InvoiceReceipt {
  224. Notes = "Receipt for License Renewal",
  225. Amount = renewalRequest.Net
  226. };
  227. invoiceReceipt.Invoice.ID = invoice.ID;
  228. invoiceReceipt.Receipt.ID = receipt.ID;
  229. new Client<InvoiceReceipt>().Save(invoiceReceipt, "");
  230. }
  231. private IResponseBuilder RenewLicense(IRequest request)
  232. {
  233. var renewal = Serialization.Deserialize<LicenseRenewalRequest>(request.Content);
  234. if(renewal == null){
  235. return request.Respond().Status(ResponseStatus.BadRequest);
  236. }
  237. Logger.Send(LogType.Information, "", $"Request for license renewal from {renewal.Company.CompanyName}");
  238. var customerID = renewal.OldLicense.CustomerID;
  239. if(customerID == Guid.Empty){
  240. customerID = CreateNewCustomer(renewal).ID;
  241. renewal.OldLicense.CustomerID = customerID;
  242. }
  243. Logger.Send(LogType.Information, "", "Generating new license");
  244. var newLicense = GenerateLicense(renewal);
  245. var newLicenseData = LicenseUtils.EncryptLicense(newLicense);
  246. if(newLicenseData == null){
  247. Logger.Send(LogType.Error, "", "Encryption of new license failed!");
  248. return request.Respond().Status(ResponseStatus.InternalServerError);
  249. }
  250. Logger.Send(LogType.Information, "", "Generating customer document");
  251. var customerDocument = CreateNewCustomerDocument(customerID, $"{renewal.Company.CompanyName} - PRS License - {DateTime.Now:dd MMM yyyy}.txt", newLicenseData);
  252. Logger.Send(LogType.Information, "", "Creating invoice");
  253. CreateInvoice(customerID, renewal);
  254. LicenseRenewalResult result = new()
  255. {
  256. License = newLicenseData
  257. };
  258. return request.Respond()
  259. .Status(ResponseStatus.OK)
  260. .Content(Serialization.Serialize(result)); // Send just the encrypted data.
  261. }
  262. private IResponseBuilder Ping(IRequest request)
  263. {
  264. return request.Respond().Status(ResponseStatus.OK);
  265. }
  266. private IResponseBuilder HandleGET(IRequest request)
  267. {
  268. var endpoint = request.Target.Current?.Value.ToLower() ?? "";
  269. if (endpoint == nameof(Ping).ToLower())
  270. return Ping(request);
  271. return request.Respond().Status(ResponseStatus.NotFound);
  272. }
  273. private IResponseBuilder HandlePOST(IRequest request)
  274. {
  275. var endpoint = request.Target.Current?.Value.ToLower() ?? "";
  276. if (endpoint.Equals(nameof(LicenseFeeRequest).ToLower()))
  277. return RetrieveFees(request);
  278. if (endpoint.Equals(nameof(LicenseRenewalRequest).ToLower()))
  279. return RenewLicense(request);
  280. return request.Respond().Status(ResponseStatus.NotFound);
  281. }
  282. public override ValueTask<IResponse?> HandleAsync(IRequest request)
  283. {
  284. try
  285. {
  286. switch (request.Method.KnownMethod)
  287. {
  288. case RequestMethod.GET:
  289. return new ValueTask<IResponse?>(HandleGET(request).Build());
  290. case RequestMethod.POST:
  291. return new ValueTask<IResponse?>(HandlePOST(request).Build());
  292. default:
  293. Logger.Send(LogType.Error, ClientFactory.UserID, $"Request method {request.Method.RawMethod} unknown");
  294. return new ValueTask<IResponse?>(request.Respond().Status(ResponseStatus.MethodNotAllowed).Build());
  295. }
  296. }
  297. catch (Exception eListen)
  298. {
  299. Logger.Send(LogType.Error, ClientFactory.UserID, eListen.Message);
  300. return new ValueTask<IResponse?>(request.Respond().Status(ResponseStatus.InternalServerError).Build());
  301. }
  302. }
  303. }