LicensingEngine.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. using Comal.Classes;
  2. using Comal.Stores;
  3. using GenHTTP.Api.Protocol;
  4. using GenHTTP.Modules.IO;
  5. using InABox.Clients;
  6. using InABox.Configuration;
  7. using InABox.Core;
  8. using InABox.Rpc;
  9. using InABox.Wpf.Reports;
  10. using PRS.Shared;
  11. using PRSServer;
  12. using PRSServices;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Linq;
  16. using System.Text;
  17. using System.Threading;
  18. using System.Threading.Tasks;
  19. using RequestMethod = GenHTTP.Api.Protocol.RequestMethod;
  20. namespace PRSLicensing;
  21. public class LicensingHandler : Handler<LicensingHandlerProperties>
  22. {
  23. public override void Init(LicensingHandlerProperties properties)
  24. {
  25. }
  26. private IResponseBuilder LicenseSummary(IRequest request)
  27. {
  28. var summary = new LicenseSummary {
  29. LicenseFees = new() {
  30. { typeof(CoreLicense).EntityName(), 7.99 },
  31. { typeof(DigitalFormsLicense).EntityName(), 3.99 },
  32. { typeof(SchedulingControlLicense).EntityName(), 1.99 },
  33. { typeof(TimeManagementLicense).EntityName(), 2.99 },
  34. { typeof(AccountsPayableLicense).EntityName(), 1.99 },
  35. { typeof(GPSTrackerLicense).EntityName(), 2.99 },
  36. { typeof(LogisticsLicense).EntityName(), 4.99 },
  37. { typeof(ScheduleEngineLicense).EntityName(), 2.99 },
  38. { typeof(QuotesManagementLicense).EntityName(), 4.99 },
  39. { typeof(LeaveManagementLicense).EntityName(), 2.99 },
  40. { typeof(TaskManagementLicense).EntityName(), 1.99 },
  41. { typeof(WarehouseLicense).EntityName(), 5.99 },
  42. { typeof(ProjectManagementLicense).EntityName(), 4.99 },
  43. { typeof(ManufacturingLicense).EntityName(), 4.99 },
  44. { typeof(ProductManagementLicense).EntityName(), 2.99 },
  45. { typeof(EquipmentLicense).EntityName(), 2.99 },
  46. { typeof(HumanResourcesLicense).EntityName(), 2.99 },
  47. { typeof(AccountsReceivableLicense).EntityName(), 1.99 }
  48. },
  49. TimeDiscounts = new() {
  50. { 1, 0.00 },
  51. { 3, 5.00 },
  52. { 6, 5.00 },
  53. { 12, 10.00 }
  54. },
  55. UserDiscounts = new() {
  56. { 1, 00.31 },
  57. { 6, 08.63 },
  58. { 11, 16.94 },
  59. { 21, 25.25 },
  60. { 51, 33.57 }
  61. }
  62. };
  63. return request.Respond().Status(ResponseStatus.OK).Content(Serialization.Serialize(summary));
  64. }
  65. private static List<Tuple<int, Func<DateTime, DateTime>>> renewalPeriods = new List<Tuple<int, Func<DateTime, DateTime>>> {
  66. new(1, x => x.AddDays(-7)),
  67. new(3, x => x.AddDays(-14)),
  68. new(6, x => x.AddMonths(-1))
  69. };
  70. private int GetMonthDifference(DateTime date1, DateTime date2){
  71. var months = (date2.Year - date1.Year) * 12 + (date2.Month - date1.Month);
  72. if(date2.Day >= date1.Day){
  73. return months;
  74. }
  75. return months - 1;
  76. }
  77. private LicenseData GenerateLicense(LicenseRenewal renewal){
  78. var renewalPeriodInMonths = GetMonthDifference(renewal.DateRenewed, renewal.NewExpiry);
  79. var renewalAvailable = renewalPeriods
  80. .Where(x => renewalPeriodInMonths >= x.Item1)
  81. .MaxBy(x => x.Item1)
  82. .Item2(renewal.NewExpiry);
  83. var newLicense = LicenseUtils.RenewLicense(renewal.OldLicense, renewal.DateRenewed, renewal.NewExpiry, renewalAvailable);
  84. return newLicense;
  85. }
  86. private string NewCustomerCode(LicenseRenewal renewal){
  87. var code = renewal.Company.CompanyName.ToUpper();
  88. var codes = new Client<Customer>().Query(
  89. new Filter<Customer>(x => x.Code).BeginsWith(code),
  90. new Columns<Customer>(x => x.Code))?.Rows.Select(x => x.Get<Customer, string>(x => x.Code)).ToList();
  91. var tryCode = code;
  92. var i = 0;
  93. while(codes.Contains(tryCode)){
  94. tryCode = $"{code}{i}";
  95. ++i;
  96. }
  97. return tryCode;
  98. }
  99. private Customer CreateNewCustomer(LicenseRenewal renewal){
  100. Logger.Send(LogType.Information, "", "Creating new customer");
  101. var customer = new Customer {
  102. Code = NewCustomerCode(renewal),
  103. Name = $"{renewal.Company.CompanyName} - {renewal.Company.ABN}",
  104. Delivery = renewal.Company.DeliveryAddress,
  105. Email = renewal.Company.Email,
  106. Postal = renewal.Company.PostalAddress
  107. };
  108. new Client<Customer>().Save(customer, "Created by License Renewal");
  109. return customer;
  110. }
  111. private CustomerDocument CreateNewCustomerDocument(Guid customerID, string fileName, string data){
  112. var document = new Document {
  113. FileName = fileName,
  114. Private = true,
  115. TimeStamp = DateTime.Now,
  116. Data = Encoding.UTF8.GetBytes(data)
  117. };
  118. document.CRC = CoreUtils.CalculateCRC(document.Data);
  119. new Client<Document>().Save(document, "");
  120. var documentType = new Client<DocumentType>()
  121. .Query(new Filter<DocumentType>(x => x.Code).IsEqualTo("LICENSE"), new Columns<DocumentType>(x => x.ID))
  122. .Rows.FirstOrDefault()?.Get<DocumentType, Guid>(x => x.ID) ?? Guid.Empty;
  123. if(documentType == Guid.Empty){
  124. Logger.Send(LogType.Error, "", "Document Type 'LICENSE' doesn't exist");
  125. }
  126. var customerDocument = new CustomerDocument();
  127. customerDocument.Type.ID = documentType;
  128. customerDocument.EntityLink.ID = customerID;
  129. customerDocument.DocumentLink.ID = document.ID;
  130. new Client<CustomerDocument>().Save(customerDocument, "Created by License Renewal");
  131. return customerDocument;
  132. }
  133. private void CreateInvoice(Guid customerID, LicenseRenewal renewal){
  134. var invoiceLines = new List<InvoiceLine>();
  135. var notes = new List<string>();
  136. foreach(var item in renewal.LicenseTracking){
  137. var invoiceLine = new InvoiceLine {
  138. Description = $"{item.License} - {item.Users} Users @ ${item.Rate:F2} per user",
  139. ExTax = item.ExGST
  140. };
  141. invoiceLines.Add(invoiceLine);
  142. notes.Add(invoiceLine.Description);
  143. }
  144. var discountLine = new InvoiceLine {
  145. Description = $"${renewal.Discount:F2} discount",
  146. ExTax = -renewal.Discount
  147. };
  148. invoiceLines.Add(discountLine);
  149. notes.Add(discountLine.Description);
  150. var invoice = new Invoice {
  151. Date = DateTime.Today,
  152. Description = $"License Renewal - Stripe Transaction No. '{renewal.TransactionID}'"
  153. };
  154. invoice.CustomerLink.ID = customerID;
  155. new Client<Invoice>().Save(invoice, "Created by License Renewal");
  156. foreach(var line in invoiceLines){
  157. line.InvoiceLink.ID = invoice.ID;
  158. }
  159. new Client<InvoiceLine>().Save(invoiceLines, "");
  160. var receipt = new Receipt {
  161. Date = DateTime.Today,
  162. Notes = string.Join('\n', notes)
  163. };
  164. new Client<Receipt>().Save(receipt, "");
  165. var invoiceReceipt = new InvoiceReceipt {
  166. Notes = "Receipt for License Renewal",
  167. Amount = renewal.Net
  168. };
  169. invoiceReceipt.InvoiceLink.ID = invoice.ID;
  170. invoiceReceipt.ReceiptLink.ID = receipt.ID;
  171. new Client<InvoiceReceipt>().Save(invoiceReceipt, "");
  172. }
  173. private IResponseBuilder RenewLicense(IRequest request)
  174. {
  175. var renewal = Serialization.Deserialize<LicenseRenewal>(request.Content);
  176. if(renewal == null){
  177. return request.Respond().Status(ResponseStatus.BadRequest);
  178. }
  179. Logger.Send(LogType.Information, "", $"Request for license renewal from {renewal.Company.CompanyName}");
  180. var customerID = renewal.OldLicense.CustomerID;
  181. if(customerID == Guid.Empty){
  182. customerID = CreateNewCustomer(renewal).ID;
  183. renewal.OldLicense.CustomerID = customerID;
  184. }
  185. Logger.Send(LogType.Information, "", "Generating new license");
  186. var newLicense = GenerateLicense(renewal);
  187. var newLicenseData = LicenseUtils.EncryptLicense(newLicense);
  188. if(newLicenseData == null){
  189. Logger.Send(LogType.Error, "", "Encryption of new license failed!");
  190. return request.Respond().Status(ResponseStatus.InternalServerError);
  191. }
  192. Logger.Send(LogType.Information, "", "Generating customer document");
  193. var customerDocument = CreateNewCustomerDocument(customerID, $"{renewal.Company.CompanyName} - PRS License - {DateTime.Now:dd MMM yyyy}.txt", newLicenseData);
  194. Logger.Send(LogType.Information, "", "Creating invoice");
  195. CreateInvoice(customerID, renewal);
  196. return request.Respond()
  197. .Status(ResponseStatus.OK)
  198. .Content(Serialization.Serialize(new License { Data = newLicenseData } )); // Send just the encrypted data.
  199. }
  200. private IResponseBuilder Ping(IRequest request)
  201. {
  202. return request.Respond().Status(ResponseStatus.OK);
  203. }
  204. private IResponseBuilder HandleGET(IRequest request)
  205. {
  206. var endpoint = request.Target.Current?.Value.ToLower() ?? "";
  207. if(endpoint == nameof(LicenseSummary).ToLower())
  208. {
  209. return LicenseSummary(request);
  210. }
  211. else if(endpoint == nameof(Ping).ToLower())
  212. {
  213. return Ping(request);
  214. }
  215. return request.Respond().Status(ResponseStatus.NotFound);
  216. }
  217. private IResponseBuilder HandlePOST(IRequest request)
  218. {
  219. var endpoint = string.Join('/', request.Target.GetRemaining());
  220. if(endpoint == nameof(RenewLicense).ToLower())
  221. {
  222. return RenewLicense(request);
  223. }
  224. return request.Respond().Status(ResponseStatus.NotFound);
  225. }
  226. public override ValueTask<IResponse?> HandleAsync(IRequest request)
  227. {
  228. try
  229. {
  230. switch (request.Method.KnownMethod)
  231. {
  232. case RequestMethod.GET:
  233. return new ValueTask<IResponse?>(HandleGET(request).Build());
  234. case RequestMethod.POST:
  235. return new ValueTask<IResponse?>(HandlePOST(request).Build());
  236. default:
  237. Logger.Send(LogType.Error, ClientFactory.UserID, $"Request method {request.Method.RawMethod} unknown");
  238. return new ValueTask<IResponse?>(request.Respond().Status(ResponseStatus.MethodNotAllowed).Build());
  239. }
  240. }
  241. catch (Exception eListen)
  242. {
  243. Logger.Send(LogType.Error, ClientFactory.UserID, eListen.Message);
  244. return new ValueTask<IResponse?>(request.Respond().Status(ResponseStatus.InternalServerError).Build());
  245. }
  246. }
  247. }
  248. public class LicensingHandlerProperties
  249. {
  250. }
  251. public class LicensingEngine : Engine<LicensingEngineProperties>
  252. {
  253. private Listener<LicensingHandler, LicensingHandlerProperties> listener;
  254. public override void Run()
  255. {
  256. Logger.Send(LogType.Information, "", "Starting..");
  257. if (string.IsNullOrWhiteSpace(Properties.Server))
  258. {
  259. Logger.Send(LogType.Error, "", "Server is blank!");
  260. return;
  261. }
  262. var transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(Properties.Server, true));
  263. ClientFactory.SetClientType(typeof(RpcClient<>), Platform.WebEngine, Version, transport);
  264. CheckConnection();
  265. Logger.Send(LogType.Information, "", "Registering Classes");
  266. StoreUtils.RegisterClasses();
  267. CoreUtils.RegisterClasses();
  268. ComalUtils.RegisterClasses();
  269. PRSSharedUtils.RegisterClasses();
  270. ReportUtils.RegisterClasses();
  271. ConfigurationUtils.RegisterClasses();
  272. Logger.Send(LogType.Information, "", "Starting Listener on port " + Properties.ListenPort);
  273. try
  274. {
  275. listener = new Listener<LicensingHandler, LicensingHandlerProperties>(new LicensingHandlerProperties());
  276. listener.InitHTTPS((ushort)Properties.ListenPort, CertificateFileName());
  277. Logger.Send(LogType.Information, "", "Starting Web Listener on port " + Properties.ListenPort);
  278. listener.Start();
  279. }
  280. catch (Exception eListen)
  281. {
  282. Logger.Send(LogType.Error, ClientFactory.UserID, eListen.Message);
  283. }
  284. }
  285. private string CertificateFileName() => Properties.CertificateFile;
  286. private void CheckConnection()
  287. {
  288. // Wait for server connection
  289. while (!Client.Ping())
  290. {
  291. Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
  292. Task.Delay(30_000).Wait();
  293. Logger.Send(LogType.Information, "", "Retrying connection...");
  294. }
  295. ClientFactory.SetBypass();
  296. }
  297. public override void Stop()
  298. {
  299. Logger.Send(LogType.Information, "", "Stopping");
  300. listener?.Stop();
  301. }
  302. }
  303. public class LicensingEngineProperties : ServerProperties
  304. {
  305. [ComboLookupEditor(typeof(LicensingDatabaseServerLookupGenerator))]
  306. [EditorSequence(1)]
  307. public string Server { get; set; }
  308. [IntegerEditor]
  309. [EditorSequence(2)]
  310. public int ListenPort { get; set; }
  311. [EditorSequence(3)]
  312. [FileNameEditor("Certificate Files (*.pfx)|*.pfx")]
  313. public string CertificateFile { get; set; }
  314. public override ServerType Type()
  315. {
  316. return ServerType.Other;
  317. }
  318. }
  319. public class LicensingDatabaseServerLookupGenerator : LookupGenerator<LicensingEngineProperties>
  320. {
  321. public LicensingDatabaseServerLookupGenerator(LicensingEngineProperties[] items) : base(items)
  322. {
  323. }
  324. protected override void DoGenerateLookups()
  325. {
  326. var config = new LocalConfiguration<ServerSettings>(CoreUtils.GetCommonAppData("PRSServer"), "");
  327. var servers = config.LoadAll();
  328. foreach (var server in servers.Select(x => x.Value.CreateServer(x.Key)))
  329. {
  330. if (server.Type == ServerType.Database)
  331. {
  332. AddValue(server.Key, server.Name);
  333. }
  334. }
  335. }
  336. }