424 lines
26 KiB
C#
424 lines
26 KiB
C#
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
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
/// Process purchases into licenses
|
|
/// ############## 2023-02-09 THIS IS ON HOLD FOR NOW - unlikely to work immediately due to all manual fuckery involved instead will find ways to speed up manual processing ################
|
|
/// too much other stuff on the go right now and time required to spend processing purchases is a good problem to have can spend more time on it later
|
|
/// I'll comment out the job so it's not called and keep the vendor notification processing for now, look at client end improvements (duplicate alone will help)
|
|
/// </summary>
|
|
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();
|
|
License lastLicense = null;
|
|
|
|
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)
|
|
{
|
|
lastLicense = await ct.License.AsNoTracking().OrderByDescending(z => z.Id).FirstOrDefaultAsync(z => z.CustomerId == firstPurchase.CustomerId && z.PGroup == firstPurchase.PGroup);
|
|
|
|
newLicense.FetchEmail = purchaseGroupCustomer.EmailAddress;
|
|
newLicense.PGroup = ProductGroup.AyaNova7;
|
|
|
|
//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
|
|
case 4576
|
|
25 Up to 25 AyaNova schedulable resource 1 year subscription license
|
|
*/
|
|
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;
|
|
case "301091845": //case 4576
|
|
newLicense.Users = 25;
|
|
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
|
|
|
|
|
|
//get last license for this dbid, if active then take all it's value and update it with this
|
|
lastLicense = await ct.License.AsNoTracking().OrderByDescending(z => z.Id).FirstOrDefaultAsync(z => z.CustomerId == firstPurchase.CustomerId && z.PGroup == firstPurchase.PGroup);
|
|
|
|
|
|
//If renewal and there is no last license then it's a problem -> THROW EXCEPTION
|
|
|
|
//If no last license and not renewal then it's a new purchase -> GENERATE NEW
|
|
|
|
//If last license and renewal then it's a renewal -> UPDATE LAST LICENSE DATES SAVE NEW
|
|
|
|
//If last license and NEW then it's an additional add on count
|
|
//if dbid matches -> ADD TO COUNT PUT THAT IN NOTES OF LICENSE
|
|
//if no dbid match then -> THROW EXCEPTION
|
|
|
|
//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
|
|
*/
|
|
|
|
// - sockeye note: Need to handle multiple raven license purchases, my policy is the freshest date is honoured so for examle tri-star bought in December then in Feb 2 users each time so the feb is the freshest, the trick is when the december renews not to cut it short but still use the feb date. since eventually sockeye will be automated it needs to handle this up front now, not later
|
|
//algorithm: check if existing license for db id, if yes, is it for more users than current license?
|
|
//if yes then is it unexpired?
|
|
//if yes then honour the
|
|
//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;
|
|
newLicense.PGroup = ProductGroup.RavenPerpetual;
|
|
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;
|
|
newLicense.PGroup = ProductGroup.RavenSubscription;
|
|
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;
|
|
newLicense.PGroup = ProductGroup.RavenSubscription;
|
|
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
|
|
|