using System; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Sockeye.Models; using Sockeye.Util; namespace Sockeye.Biz { /// /// Process purchases into licenses /// /// internal static class SockBotProcessPurchasesIntoLicenses { private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("SockBotProcessPurchasesIntoLicenses"); private static DateTime lastSweep = DateTime.MinValue; private static TimeSpan PROCESS_V7_AGE = new TimeSpan(0, 5, 0);//Don't process a v7 order until at least 5 minutes old as it doesn't always come in at once #if (DEBUG) private static TimeSpan PROCESS_EVERY_INTERVAL = new TimeSpan(0, 0, 30);//every 30 seconds during development #else private static TimeSpan PROCESS_EVERY_INTERVAL = new TimeSpan(0, 5, 10);//every 5 minutes #endif //////////////////////////////////////////////////////////////////////////////////////////////// // DoSweep // public static async Task DoWorkAsync() { //This will get triggered roughly every minute, but we don't want to check that frequently if (DateTime.UtcNow - lastSweep < PROCESS_EVERY_INTERVAL) return; if (ServerBootConfig.MIGRATING) return;//don't do this during migration (migration is one time only so can remove after up and running) log.LogDebug("Process purchases into licenses starting"); await ProcessPurchasesIntoLicenses(); lastSweep = DateTime.UtcNow; } private static async Task ProcessPurchasesIntoLicenses() { using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext) { //get a list of all actionable purchases grouped by customer id var purchaseList = (await ct.Purchase.AsNoTracking() .Where(z => z.Processed == false && z.LicenseId == null && z.CustomerId != null && (z.PGroup == ProductGroup.AyaNova7 || z.PGroup == ProductGroup.RavenPerpetual || z.PGroup == ProductGroup.RavenSubscription)) .OrderByDescending(z => z.PurchaseDate) .ToListAsync()).GroupBy(z => (long)z.CustomerId); try { foreach (var purchaseGroup in purchaseList) { //Iterate the group and qualify it bool isRavenPerpetual = false; bool isRavenSubscription = false; bool isV7 = false; bool isLessThanV7Age = false; var purchaseGroupCustomer = await ct.Customer.AsNoTracking().FirstAsync(z => z.Id == purchaseGroup.First().CustomerId); foreach (var purchase in purchaseGroup) { if (purchase.PGroup == ProductGroup.AyaNova7) isV7 = true; if (purchase.PGroup == ProductGroup.RavenPerpetual) isRavenPerpetual = true; if (purchase.PGroup == ProductGroup.RavenSubscription) isRavenSubscription = true; if (DateTime.UtcNow - purchase.PurchaseDate < PROCESS_V7_AGE) isLessThanV7Age = true; ; } //sanity checks should never be mixed if ((isRavenPerpetual && isV7)) { var err = $"SockBotProcessPurchasesIntoLicenses both raven perpetual and v7 in same customer's purchase group First record order number: {purchaseGroup.First().SalesOrderNumber}"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err); log.LogError(err); continue; } if ((isRavenSubscription && isV7)) { var err = $"SockBotProcessPurchasesIntoLicenses both raven subscription and v7 in same customer's purchase group First record order number: {purchaseGroup.First().SalesOrderNumber}"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err); log.LogError(err); continue; } if ((isRavenPerpetual && isRavenSubscription)) { var err = $"SockBotProcessPurchasesIntoLicenses both raven perpetual and raven subscription in same customer's purchase group First record order number: {purchaseGroup.First().SalesOrderNumber}"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err); log.LogError(err); continue; } //if v7 skip for this iteration to ensure multiple add-on's have all arrived from mycommerce if (isV7 && isLessThanV7Age) continue; License newLicense = new License(); //Get last license if any, set up some basic stuff common to all license types var firstPurchase = purchaseGroup.First(); var lastLicense = await ct.License.AsNoTracking().OrderByDescending(z => z.Id).FirstOrDefaultAsync(z => z.CustomerId == firstPurchase.CustomerId && z.PGroup == firstPurchase.PGroup); newLicense.CustomerId = firstPurchase.CustomerId; newLicense.Active = false; newLicense.RegTo = firstPurchase.RegTo; //if v7 license then lookup last license for same pgroup for same customer, if none then consider it a new license fresh //if there is one and it's not entirely expired then duplicate and fixup from purchases in this group if (isV7) { newLicense.FetchEmail = purchaseGroupCustomer.EmailAddress; //is there a prior license? if (lastLicense != null) { //copy all the values to the new license newLicense.ExportToXLS = lastLicense.ExportToXLS; newLicense.ExportToXLSExpires = lastLicense.ExportToXLSExpires; newLicense.ImportExportCSVDuplicate = lastLicense.ImportExportCSVDuplicate; newLicense.ImportExportCSVDuplicateExpires = lastLicense.ImportExportCSVDuplicateExpires; newLicense.MaintenanceExpire = lastLicense.MaintenanceExpire; newLicense.MBI = lastLicense.MBI; newLicense.MBIExpires = lastLicense.MBIExpires; newLicense.OLI = lastLicense.OLI; newLicense.OLIExpires = lastLicense.OLIExpires; newLicense.OutlookSchedule = lastLicense.OutlookSchedule; newLicense.OutlookScheduleExpires = lastLicense.OutlookScheduleExpires; newLicense.PTI = lastLicense.PTI; newLicense.PTIExpires = lastLicense.PTIExpires; newLicense.QBI = lastLicense.QBI; newLicense.QBIExpires = lastLicense.QBIExpires; newLicense.QBOI = lastLicense.QBOI; newLicense.QBOIExpires = lastLicense.QBOIExpires; newLicense.QuickNotification = lastLicense.QuickNotification; newLicense.QuickNotificationExpires = lastLicense.QuickNotificationExpires; newLicense.Renewal = true; newLicense.RI = lastLicense.RI; newLicense.RIExpires = lastLicense.RIExpires; newLicense.Tags = lastLicense.Tags; newLicense.Users = lastLicense.Users; newLicense.WBI = lastLicense.WBI; newLicense.WBIExpires = lastLicense.WBIExpires; newLicense.Wiki = lastLicense.Wiki; } //iterate the purchases and update / set the license foreach (var purchase in purchaseGroup) { var product = await ct.Product.AsNoTracking().FirstOrDefaultAsync(z => z.Id == purchase.ProductId);//should *always* exist if (product == null) { var err = $"SockBotProcessPurchasesIntoLicenses purchase: {purchase.Id} has un-matchable product id: {purchase.ProductId}"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err); log.LogError(err); continue; } /* id name productCode price renewPrice 1 Key administration 300093112 3500 3500 2 Custom work 300151145 0 0 3 AyaNova RI 1 year subscription license 300740314 19900 6965 4 Single AyaNova schedulable resource 1 year subscription license 300740315 15900 5565 5 Single AyaNova Lite 1 year subscription license 300740316 6900 2415 6 Up to 5 AyaNova schedulable resource 1 year subscription license 300740317 69500 24325 7 Up to 10 AyaNova schedulable resource 1 year subscription license 300740318 119000 41650 8 Up to 20 AyaNova schedulable resource 1 year subscription license 300740319 198000 69300 9 AyaNova WBI (web browser interface) 1 year subscription license 300740321 9900 3465 10 AyaNova MBI (minimal browser interface) 1 year subscription license 300740322 9900 3465 11 AyaNova QBI(QuickBooks interface) 1 year subscription license 300740323 9900 3465 12 AyaNova PTI(US Peachtree/Sage 50 interface) 1 year subscription license 300740324 9900 3465 13 AyaNova OLI(Outlook interface) 1 year subscription license 300740325 9900 3465 14 Plug-in Outlook Schedule Export 1 year subscription license 300740326 1900 665 15 Plug-in Export to XLS 1 year subscription license 300740327 1900 665 16 Plug-in Quick Notification 1 year subscription license 300740328 1900 665 17 Plug-in importexport.csv duplicate 1 year subscription license 300740329 1900 665 18 Up to 999 AyaNova schedulable resource 1 year subscription license 300741264 15000 5250 19 Up to 15 AyaNova schedulable resource 1 year subscription license 300807973 165000 57750 20 AyaNova QBOI(QuickBooks Online interface) 1 year subscription license 300784766 12900 4515 21 Single AyaNova service techncian perpetual license 301028314 135 100 22 Single AyaNova service techncian 1 year maintenance plan - new 301028317 135 100 23 Single AyaNova service techncian 1 year maintenance plan - active 301028315 100 100 */ var dtOneYear = DateTime.UtcNow.AddYears(1); switch (product.VendorCode) { case "300740314": newLicense.RI = true; newLicense.RIExpires = dtOneYear; break; case "300740321": newLicense.WBI = true; newLicense.WBIExpires = dtOneYear; break; case "300740322": newLicense.MBI = true; newLicense.MBIExpires = dtOneYear; break; case "300740323": newLicense.QBI = true; newLicense.QBIExpires = dtOneYear; break; case "300740324": newLicense.PTI = true; newLicense.PTIExpires = dtOneYear; break; case "300740325": newLicense.OLI = true; newLicense.OLIExpires = dtOneYear; break; case "300740326": newLicense.OutlookSchedule = true; newLicense.OutlookScheduleExpires = dtOneYear; break; case "300740327": newLicense.ExportToXLS = true; newLicense.ExportToXLSExpires = dtOneYear; break; case "300740328": newLicense.QuickNotification = true; newLicense.QuickNotificationExpires = dtOneYear; break; case "300740329": newLicense.ImportExportCSVDuplicate = true; newLicense.ImportExportCSVDuplicateExpires = dtOneYear; break; case "300784766": newLicense.QBOI = true; newLicense.QBOIExpires = dtOneYear; break; //USERS case "300740315": newLicense.Users = 1; newLicense.MaintenanceExpire = dtOneYear; break; case "300740317": newLicense.Users = 5; newLicense.MaintenanceExpire = dtOneYear; break; case "300740318": newLicense.Users = 10; newLicense.MaintenanceExpire = dtOneYear; break; case "300740319": newLicense.Users = 20; newLicense.MaintenanceExpire = dtOneYear; break; case "300741264": newLicense.Users = 999; newLicense.MaintenanceExpire = dtOneYear; break; case "300807973": newLicense.Users = 15; newLicense.MaintenanceExpire = dtOneYear; break; default: var err = $"SockBotProcessPurchasesIntoLicenses purchase: {purchase.Id} has product not part of v7 group expected: {product.Name}-{product.VendorCode}"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err); log.LogError(err); continue; }//switch product code }//each purchase in purchasegroup //license done ready to save not active so still requires manual intervention but should be substantially ready to send out by here fingers crossed! }//if v7 block else { //it's a RAVEN license //iterate the purchases and update / set the license foreach (var purchase in purchaseGroup) { var product = await ct.Product.AsNoTracking().FirstOrDefaultAsync(z => z.Id == purchase.ProductId);//should *always* exist if (product == null) { var err = $"SockBotProcessPurchasesIntoLicenses purchase: {purchase.Id} has un-matchable product id: {purchase.ProductId}"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err); log.LogError(err); continue; } /* 301028317 AyaNova perpetual single user license includes one year maintenance plan perpetual Product 12 months HD1 Aug 15, 2022, 9:07 PM 301028467 AyaNova subscription one user monthly subscriptionmonthly Product monthly HD1 Aug 18, 2022, 1:21 AM 301028468 AyaNova subscription one user yearly subscriptionyearly Product 12 months HD1 Aug 18, 2022, 1:25 AM 301033167 AyaNova subscription additional 250 customer users monthly 250customerusersmonthly Product monthly HD1 Oct 6, 2022, 12:56 AM 301033168 AyaNova subscription additional 250 customer users yearly 250customerusersyearly Product 12 months HD1 Oct 6, 2022, 12:59 AM */ //RAVEN licenses have one week padding to be on the safe side var dtOneYear = DateTime.UtcNow.AddYears(1).AddDays(7); var dtOneMonth = DateTime.UtcNow.AddMonths(1).AddDays(7); switch (product.VendorCode) { case "301028317"://perpetual newLicense.Users = purchase.Quantity; newLicense.MaintenanceExpire = dtOneYear; break; case "301028467"://subscription monthly newLicense.Users = purchase.Quantity; newLicense.MaintenanceExpire = dtOneMonth; //FUTURE: for now subscriptions all 20gb if ever add ability to go higher max data with product code etc then remove this newLicense.MaxDataGB = 20; break; case "301028468"://subscription yearly newLicense.Users = purchase.Quantity; newLicense.MaintenanceExpire = dtOneYear; //FUTURE: for now subscriptions all 20gb if ever add ability to go higher max data with product code etc then remove this newLicense.MaxDataGB = 20; break; case "301033167"://Customer users monthly price case "301033168"://Customer users yearly price newLicense.CustomerUsers = purchase.Quantity; break; default: var err = $"SockBotProcessPurchasesIntoLicenses purchase: {purchase.Id} has product not part of RAVEN products expected: {product.Name}-{product.VendorCode}"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err); log.LogError(err); continue; }//switch product code }//each purchase in purchasegroup } //we have a new savable license at this point if (newLicense != null) { LicenseBiz lbiz = LicenseBiz.GetBiz(ct); newLicense = await lbiz.CreateAsync(newLicense); if (newLicense == null) { //did not save, throw an error throw new System.ApplicationException($"Error creating license from purchases: {lbiz.GetErrorsAsString()}"); } } } } catch (Exception ex) { var err = "SockBotProcessPurchasesIntoLicenses exepected exception error running job"; //serious issue requires immediate notification await NotifyEventHelper.AddOpsProblemEvent(err, ex); log.LogError(ex, err); } } } ///////////////////////////////////////////////////////////////////// }//eoc }//eons