Compare commits

..

12 Commits

Author SHA1 Message Date
275eb1ad42 testing commit and etc 2026-02-13 08:34:33 -08:00
45d2b0fd29 Add .gitignore 2026-02-12 18:18:58 -08:00
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
12 changed files with 188 additions and 59 deletions

18
.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
# .NET
bin/
obj/
*.user
*.suo
.vs/
# Build outputs
dist/installers/
# IDE
.vscode/settings.json
.idea/
# OS
Thumbs.db
Desktop.ini
.DS_Store

View File

@@ -1,6 +1,6 @@
set m=Unknown error set m=Unknown error
@echo ****************** CLEAN ALL OUTPUT FOLDERS ********** @echo ****************** CLEAN ALL OUTPUT FOLDERS ***********
rmdir c:\data\code\sockeye\server\wwwroot /s/q rmdir c:\data\code\sockeye\server\wwwroot /s/q
mkdir c:\data\code\sockeye\server\wwwroot mkdir c:\data\code\sockeye\server\wwwroot
rmdir C:\data\code\sockeye\dist\installers /s/q rmdir C:\data\code\sockeye\dist\installers /s/q

View File

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

View File

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

View File

@@ -590,23 +590,23 @@ namespace Sockeye.Biz
//Add Handlebars JS for compiling and presenting //Add Handlebars JS for compiling and presenting
//https://handlebarsjs.com/ //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 //add Marked for markdown processing
//https://github.com/markedjs/marked //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 //add DOM Purify for markdown template sanitization processing
//https://github.com/cure53/DOMPurify //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 //add Bar code library if our bar code helper is referenced
//https://github.com/metafloor/bwip-js //https://github.com/metafloor/bwip-js
if (report.Template.Contains("ayBC ") || report.JsHelpers.Contains("ayBC ")) 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 //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 //execute to add to handlebars
await page.EvaluateExpressionAsync("ayRegisterHelpers();"); await page.EvaluateExpressionAsync("ayRegisterHelpers();");

View File

@@ -66,11 +66,12 @@ namespace Sockeye.Biz
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
//GET //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) if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct);
await PopulateVizFields(ret); await PopulateVizFields(ret);
return ret; return ret;
} }
@@ -237,45 +238,88 @@ namespace Sockeye.Biz
} }
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
//REPORTING //REPORTING
// //
public async Task<JArray> GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) 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; var idList = dataListSelectedRequest.SelectedRowIds;
JArray ReportData = new JArray(); JArray ReportData = new JArray();
List<Subscription> batchResults = new List<Subscription>();
while (idList.Any()) while (idList.Any())
{ {
if (!ReportRenderManager.KeepGoing(jobId)) return null;
var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE);
idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray();
batchResults.Clear();
//query for this batch, comes back in db natural order unfortunately foreach (long batchId in batch)
var batchResults = await ct.Subscription.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); {
if (!ReportRenderManager.KeepGoing(jobId)) return null;
//order the results back into original //var subId = await ct.SubscriptionItem.AsNoTracking().Where(z => z.Id == id).Select(z => z.SubscriptionId).FirstOrDefaultAsync();
//What is happening here: batchResults.Add(await GetAsync(batchId, false));
//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 //these are individually fetched so there's no need to re-order like most other object types
//This would not be necessary if just fetching each one at a time individually (like in workorder get report data)
foreach (Subscription w in batchResults)
var orderedList = from id in batch join z in batchResults on id equals z.Id select z;
batchResults = null;
foreach (Subscription w in orderedList)
{ {
if (!ReportRenderManager.KeepGoing(jobId)) return null; if (!ReportRenderManager.KeepGoing(jobId)) return null;
await PopulateVizFields(w);
var jo = JObject.FromObject(w); var jo = JObject.FromObject(w);
if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"]))
jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]);
ReportData.Add(jo); ReportData.Add(jo);
} }
orderedList = null;
} }
vc.Clear(); vc.Clear();
return ReportData; 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(); private VizCache vc = new VizCache();
@@ -290,12 +334,50 @@ 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 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)) 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)
{ {
vc.Add(await ct.Product.AsNoTracking().Where(x => x.Id == item.ProductId).Select(x => x.Name).FirstOrDefaultAsync(), "productname", item.ProductId);
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);
} }
item.ProductViz = vc.Get("productname", item.ProductId); 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;
} }
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -207,6 +207,8 @@ namespace Sockeye.Biz
21 Single AyaNova service techncian perpetual license 301028314 135 100 21 Single AyaNova service techncian perpetual license 301028314 135 100
22 Single AyaNova service techncian 1 year maintenance plan - new 301028317 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 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); var dtOneYear = DateTime.UtcNow.AddYears(1);
switch (product.VendorCode) switch (product.VendorCode)
@@ -280,6 +282,10 @@ namespace Sockeye.Biz
newLicense.Users = 15; newLicense.Users = 15;
newLicense.MaintenanceExpire = dtOneYear; newLicense.MaintenanceExpire = dtOneYear;
break; break;
case "301091845": //case 4576
newLicense.Users = 25;
newLicense.MaintenanceExpire = dtOneYear;
break;
default: default:
var err = $"SockBotProcessPurchasesIntoLicenses purchase: {purchase.Id} has product not part of v7 group expected: {product.Name}-{product.VendorCode}"; var err = $"SockBotProcessPurchasesIntoLicenses purchase: {purchase.Id} has product not part of v7 group expected: {product.Name}-{product.VendorCode}";
//serious issue requires immediate notification //serious issue requires immediate notification

View File

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

View File

@@ -19,8 +19,7 @@ namespace Sockeye.Models
public bool Active { get; set; } public bool Active { get; set; }
[Required] [Required]
public long ProductId { get; set; } public long ProductId { get; set; }
[NotMapped]
public string ProductViz { get; set; }
[Required] [Required]
public DateTime ExpireDate { get; set; } public DateTime ExpireDate { get; set; }
[Required] [Required]
@@ -30,6 +29,23 @@ namespace Sockeye.Models
[Required] [Required]
public DateTime OriginalOrderDate { get; set; } 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>(); public List<string> Tags { get; set; } = new List<string>();

View File

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

View File

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

View File

@@ -15,18 +15,16 @@ TODO:
TODO: make a filter showing expired but not set to inactive subscription items 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 todo: build email address list by active subscription product no dupes
maybe in a report? maybe in a report?
This is for alerting subscribers to update or non subscribers to update separately This is for alerting subscribers to update or non subscribers to update separately
maybe it's a full on feature? 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