Compare commits

..

10 Commits

Author SHA1 Message Date
209298cfb2 Prepare for next release 2024-06-03 23:44:55 +00:00
9ac47ab052 changes to support subscription server install script going forward for new ay 8.2.0 and ubuntu 24.04 setups 2024-05-20 23:53:23 +00:00
98c7157b0c case 4576 addition of up to 25 level v7 2024-03-21 14:02:06 +00:00
ab75b98571 2023-04-21 22:58:16 +00:00
9d2a9af98a 2023-04-21 22:22:11 +00:00
4c603cb648 2023-04-21 22:14:30 +00:00
76ffaa6910 2023-04-21 22:07:38 +00:00
a12595b3cf 2023-04-21 21:26:14 +00:00
8f2ab38ba2 2023-04-21 19:34:11 +00:00
a296bc9b4f 2023-04-21 18:01:43 +00:00
10 changed files with 169 additions and 58 deletions

View File

@@ -211,6 +211,9 @@ namespace Sockeye.Biz
case 20:
sb.AppendLine("Up to 20");
break;
case 25://case 4576
sb.AppendLine("Up to 25");
break;
case 50:
sb.AppendLine("Up to 50");
break;

View File

@@ -185,6 +185,7 @@ namespace Sockeye.Biz
case "300740324"://PTI
case "300740325"://OLI
case "300807973"://Up to 15
case "301091845"://Up to 25 //case 4576
case "300740326"://Outlook Schedule Export
case "300740316"://AyaNova LITE
case "999"://Up to 999

View File

@@ -590,23 +590,23 @@ namespace Sockeye.Biz
//Add Handlebars JS for compiling and presenting
//https://handlebarsjs.com/
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-hb.js") });
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-hb.js") });
//add Marked for markdown processing
//https://github.com/markedjs/marked
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-md.js") });
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-md.js") });
//add DOM Purify for markdown template sanitization processing
//https://github.com/cure53/DOMPurify
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-pf.js") });
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-pf.js") });
//add Bar code library if our bar code helper is referenced
//https://github.com/metafloor/bwip-js
if (report.Template.Contains("ayBC ") || report.JsHelpers.Contains("ayBC "))
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-bc.js") });
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-bc.js") });
//add stock helpers
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-report.js") });
await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-report.js") });
//execute to add to handlebars
await page.EvaluateExpressionAsync("ayRegisterHelpers();");

View File

@@ -66,11 +66,12 @@ namespace Sockeye.Biz
////////////////////////////////////////////////////////////////////////////////////////////////
//GET
//
internal async Task<Subscription> GetAsync(long id, bool logTheGetEvent = true, bool populateForReporting = false)
internal async Task<Subscription> GetAsync(long id, bool logTheGetEvent = true)
{
var ret = await ct.Subscription.Include(z => z.Items.OrderByDescending(x=>x.Active).ThenBy(x => x.ExpireDate)).AsNoTracking().SingleOrDefaultAsync(z => z.Id == id);
var ret = await ct.Subscription.Include(z => z.Items.OrderByDescending(x => x.Active).ThenBy(x => x.ExpireDate)).AsNoTracking().SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct);
await PopulateVizFields(ret);
return ret;
}
@@ -237,45 +238,88 @@ namespace Sockeye.Biz
}
////////////////////////////////////////////////////////////////////////////////////////////////
//REPORTING
//
public async Task<JArray> GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId)
{
if (dataListSelectedRequest.SockType == SockType.Subscription)
return await GetSubscriptionsReportData(dataListSelectedRequest, jobId);
else
//subscription items
return await GetSubscriptionItemsReportData(dataListSelectedRequest, jobId);
}
public async Task<JArray> GetSubscriptionsReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId)
{
var idList = dataListSelectedRequest.SelectedRowIds;
JArray ReportData = new JArray();
List<Subscription> batchResults = new List<Subscription>();
while (idList.Any())
{
if (!ReportRenderManager.KeepGoing(jobId)) return null;
var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE);
idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray();
//query for this batch, comes back in db natural order unfortunately
var batchResults = await ct.Subscription.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync();
//order the results back into original
//What is happening here:
//for performance the query is batching a bunch at once by fetching a block of items from the sql server
//however it's returning in db order which is often not the order the id list is in
//so it needs to be sorted back into the same order as the ide list
//This would not be necessary if just fetching each one at a time individually (like in workorder get report data)
var orderedList = from id in batch join z in batchResults on id equals z.Id select z;
batchResults = null;
foreach (Subscription w in orderedList)
batchResults.Clear();
foreach (long batchId in batch)
{
if (!ReportRenderManager.KeepGoing(jobId)) return null;
//var subId = await ct.SubscriptionItem.AsNoTracking().Where(z => z.Id == id).Select(z => z.SubscriptionId).FirstOrDefaultAsync();
batchResults.Add(await GetAsync(batchId, false));
}
//these are individually fetched so there's no need to re-order like most other object types
foreach (Subscription w in batchResults)
{
if (!ReportRenderManager.KeepGoing(jobId)) return null;
await PopulateVizFields(w);
var jo = JObject.FromObject(w);
if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"]))
jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]);
ReportData.Add(jo);
}
orderedList = null;
}
vc.Clear();
return ReportData;
}
public async Task<JArray> GetSubscriptionItemsReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId)
{
var idList = dataListSelectedRequest.SelectedRowIds;
JArray ReportData = new JArray();
List<SubscriptionItem> batchResults = new List<SubscriptionItem>();
while (idList.Any())
{
if (!ReportRenderManager.KeepGoing(jobId)) return null;
var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE);
idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray();
batchResults.Clear();
foreach (long batchId in batch)
{
if (!ReportRenderManager.KeepGoing(jobId)) return null;
//var subId = .Select(z => z.SubscriptionId).FirstOrDefaultAsync();
var subItem = await ct.SubscriptionItem.AsNoTracking().Where(z => z.Id == batchId).FirstOrDefaultAsync();
await PopulateItemVizFields(subItem);
batchResults.Add(subItem);
}
//these are individually fetched so there's no need to re-order like most other object types
foreach (SubscriptionItem w in batchResults)
{
if (!ReportRenderManager.KeepGoing(jobId)) return null;
var jo = JObject.FromObject(w);
ReportData.Add(jo);
}
}
vc.Clear();
return ReportData;
}
private VizCache vc = new VizCache();
@@ -290,14 +334,52 @@ namespace Sockeye.Biz
foreach (var item in o.Items)//some subscriptions have a bunch of the same monthly or yearly raven user in them so this will save in that case
{
if (!vc.Has("productname", item.ProductId))
{
vc.Add(await ct.Product.AsNoTracking().Where(x => x.Id == item.ProductId).Select(x => x.Name).FirstOrDefaultAsync(), "productname", item.ProductId);
}
item.ProductViz = vc.Get("productname", item.ProductId);
await PopulateItemVizFields(item, o);
// if (!vc.Has("productname", item.ProductId))
// {
// var productInfo = await ct.Product.AsNoTracking().Where(x => x.Id == item.ProductId).FirstOrDefaultAsync();
// vc.Add(productInfo.Name, "productname", item.ProductId);
// vc.Add(productInfo.InitialPrice.ToString(), "productinitialprice", item.ProductId);
// vc.Add(productInfo.RenewPrice.ToString(), "productrenewprice", item.ProductId);
// }
// item.ProductViz = vc.Get("productname", item.ProductId);
// item.RenewPriceViz = vc.GetAsDecimal("productrenewprice", item.ProductId) ?? 0;
// item.InitialPriceViz = vc.GetAsDecimal("productinitialprice", item.ProductId) ?? 0;
}
}
//populate viz fields from provided object
private async Task PopulateItemVizFields(SubscriptionItem o, Subscription sub = null)
{
if (sub == null)
sub = await ct.Subscription.AsNoTracking().Where(z => z.Id == o.SubscriptionId).FirstOrDefaultAsync();
o.SubscriptionEmailViz = sub.FetchEmail;
if (!vc.Has("customername", sub.CustomerId))
{
var custInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == sub.CustomerId).FirstOrDefaultAsync();
vc.Add(custInfo.Name, "customername", sub.CustomerId);
vc.Add(custInfo.EmailAddress, "customeremail", sub.CustomerId);
}
o.CustomerViz = vc.Get("customername", sub.CustomerId);
o.CustomerEmailViz = vc.Get("customeremail", sub.CustomerId);
if (!vc.Has("productname", o.ProductId))
{
var productInfo = await ct.Product.AsNoTracking().Where(x => x.Id == o.ProductId).FirstOrDefaultAsync();
vc.Add(productInfo.Name, "productname", o.ProductId);
vc.Add(productInfo.InitialPrice.ToString(), "productinitialprice", o.ProductId);
vc.Add(productInfo.RenewPrice.ToString(), "productrenewprice", o.ProductId);
}
o.ProductViz = vc.Get("productname", o.ProductId);
o.RenewPriceViz = vc.GetAsDecimal("productrenewprice", o.ProductId) ?? 0;
o.InitialPriceViz = vc.GetAsDecimal("productinitialprice", o.ProductId) ?? 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// IMPORT EXPORT
//

View File

@@ -121,7 +121,7 @@ namespace Sockeye.Biz
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;
License lastLicense = null;
newLicense.CustomerId = firstPurchase.CustomerId;
newLicense.Active = false;
@@ -131,7 +131,7 @@ namespace Sockeye.Biz
//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);
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;
@@ -207,6 +207,8 @@ namespace Sockeye.Biz
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)
@@ -280,6 +282,10 @@ namespace Sockeye.Biz
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
@@ -299,19 +305,19 @@ namespace Sockeye.Biz
//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);
//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 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 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 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
//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)
@@ -334,10 +340,10 @@ namespace Sockeye.Biz
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
// - 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);

View File

@@ -15,8 +15,13 @@ namespace Sockeye.Models
[Required]
public string Name { get; set; }
public bool Active { get; set; }
public decimal InitialPrice { get; set; } = 0;
public decimal RenewPrice { get; set; } = 0;
[Required]
public ProductGroup PGroup {get;set;}
public ProductGroup PGroup { get; set; }
public long VendorId { get; set; }
public TimeSpan LicenseInterval { get; set; }
public TimeSpan MaintInterval { get; set; }

View File

@@ -19,8 +19,7 @@ namespace Sockeye.Models
public bool Active { get; set; }
[Required]
public long ProductId { get; set; }
[NotMapped]
public string ProductViz { get; set; }
[Required]
public DateTime ExpireDate { get; set; }
[Required]
@@ -30,6 +29,23 @@ namespace Sockeye.Models
[Required]
public DateTime OriginalOrderDate { get; set; }
public bool Renewal { get; set; }
[NotMapped]
public decimal RenewPriceViz { get; set; } = 0;
[NotMapped]
public decimal InitialPriceViz { get; set; } = 0;
[NotMapped]
public string ProductViz { get; set; }
[NotMapped]
public string CustomerViz { get; set; }
[NotMapped]
public string CustomerEmailViz { get; set; }
[NotMapped]
public string SubscriptionEmailViz { get; set; }
public List<string> Tags { get; set; } = new List<string>();

View File

@@ -4,8 +4,8 @@
</PropertyGroup>
<PropertyGroup>
<GenerateFullPaths>true</GenerateFullPaths>
<Version>8.0.13</Version>
<FileVersion>8.0.13.0</FileVersion>
<Version>8.0.16</Version>
<FileVersion>8.0.16.0</FileVersion>
<ApplicationIcon>sockeye.ico</ApplicationIcon>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<noWarn>1591</noWarn>

View File

@@ -5,7 +5,7 @@ namespace Sockeye.Util
/// </summary>
internal static class SockeyeVersion
{
public const string VersionString = "8.0.13";
public const string VersionString = "8.0.17";
public const string FullNameAndVersion = "Sockeye server " + VersionString;
public const string CurrentApiVersion="v8";
}//eoc

View File

@@ -15,18 +15,16 @@ TODO:
TODO: make a filter showing expired but not set to inactive subscription items
TODO: dashboard items related to subscriptions
- revenue per month for next 12 months graph
- overdue expired subs list
- active count of each product subscription bar graph
todo: build email address list by active subscription product no dupes
maybe in a report?
This is for alerting subscribers to update or non subscribers to update separately
maybe it's a full on feature?
TODO: dashboard items related to subscriptions
- revenue per month for next 12 months graph
- overdue expired subs list
- active count of each product subscription bar graph