This commit is contained in:
@@ -68,11 +68,9 @@ Only Customers who are set to Active and have an email address set will be notif
|
||||
|
||||
### Duplicate or overlapping notifications
|
||||
|
||||
You can set up multiple subscriptions for the same event to allow for alternative languages and other settings. Because of this, it is possible to set a combination of settings that results in the same customer being notified more than once of the same event.
|
||||
You can set up multiple subscriptions for the same event to allow for alternative languages and other settings. Because of this, it is possible to set a combination of settings that could result in the same customer being notified more than once of the same event.
|
||||
|
||||
For example if you have two notifications set up for the same event but with different language or time zone settings but choose tags in each subscription that end up matching the same Customer, that customer will receive two notifications.
|
||||
|
||||
Use distinct and specific Customer notification tags to ensure that the appropriate notification event goes to the correct Customers without overlap. For example "workorder-completed-spanish", "workorder-completed-english" or whatever makes sense for your business but clearly separates the Customers into the appropriate groups for notification.
|
||||
To prevent redundant excess notifications, AyaNova will only send *one* notification per event to the same Customer. If two notification subscriptions for the same event would apply to the same Customer, the subscription created first (lowest id number) will "win" and be the one used to make the delivery.
|
||||
|
||||
### Email address
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace AyaNova.Api.Controllers
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
string custTagsWhere = DataList.DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("acustomer.tags", customerTags, false);
|
||||
string custTagsWhere = DataList.DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("acustomer.tags", customerTags, false);
|
||||
|
||||
|
||||
List<CustomerNotifySubscriptionWhoRecord> ret = new List<CustomerNotifySubscriptionWhoRecord>();
|
||||
|
||||
@@ -1146,11 +1146,11 @@ namespace AyaNova.DataList
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
/// <summary>
|
||||
/// Translate KPI tag filter to PostgreSQL friendly SQL criteria
|
||||
/// Translate tag filter to PostgreSQL friendly SQL criteria
|
||||
/// </summary>
|
||||
public static string KPITagFilterToSqlCriteria(string SqlColumnNameToFilter, List<string> sValue, bool bAny)
|
||||
///
|
||||
public static string TagFilterToSqlCriteriaHelper(string SqlColumnNameToFilter, List<string> sValue, bool bAny)
|
||||
{
|
||||
if(sValue.Count==0) return string.Empty;
|
||||
//if it's an OR (any) query then need to build individual terms, if it's not then it's just an all or AND query and can pass through as is
|
||||
|
||||
@@ -1249,6 +1249,7 @@ namespace AyaNova.Biz
|
||||
{
|
||||
//# STATUS CHANGE (create new status)
|
||||
{
|
||||
//PERSONAL SUBSCCRIPTION
|
||||
//Conditions: must match specific status id value and also tags below
|
||||
//delivery is immediate so no need to remove old ones of this kind
|
||||
var subs = await ct.NotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.QuoteStatusChange && z.IdValue == oProposed.QuoteStatusId).ToListAsync();
|
||||
@@ -1276,6 +1277,44 @@ namespace AyaNova.Biz
|
||||
}
|
||||
}//quote status change event
|
||||
|
||||
{
|
||||
//PROXY CUSTOMER NOTIFICATION SUBSCRIPTION HANDLING
|
||||
//can this customer even be delivered to?
|
||||
var custInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == QuoteInfo.CustomerId).Select(x => new { x.Active, x.Tags, x.EmailAddress }).FirstOrDefaultAsync();
|
||||
if (custInfo != null && custInfo.Active && !string.IsNullOrWhiteSpace(custInfo.EmailAddress))
|
||||
{
|
||||
//Conditions: must match specific status id value and also tags below
|
||||
//delivery is immediate so no need to remove old ones of this kind
|
||||
//note order by id ascending so that only the oldest notification "wins" as per docs in case of overlap to same customer
|
||||
var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.QuoteStatusChange && z.IdValue == oProposed.QuoteStatusId).OrderBy(z=>z.Id).ToListAsync();
|
||||
|
||||
foreach (var sub in subs)
|
||||
{
|
||||
//Object tags must match and Customer tags must match
|
||||
if (NotifyEventHelper.ObjectHasAllSubscriptionTags(QuoteInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
NotifyEvent n = new NotifyEvent()
|
||||
{
|
||||
EventType = NotifyEventType.QuoteStatusChange,
|
||||
UserId = sub.UserId,
|
||||
AyaType = AyaType.Quote,
|
||||
ObjectId = oProposed.QuoteId,
|
||||
NotifySubscriptionId = sub.Id,
|
||||
Name = $"{QuoteInfo.Serial.ToString()} - {qos.Name}"
|
||||
};
|
||||
await ct.NotifyEvent.AddAsync(n);
|
||||
log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]");
|
||||
await ct.SaveChangesAsync();
|
||||
break;//we have a match no need to process any further subs for this event
|
||||
}
|
||||
}
|
||||
}
|
||||
}//quote status change event
|
||||
|
||||
//# STATUS AGE
|
||||
{
|
||||
//QuoteStatusAge = 29,//* Quote STATUS unchanged for set time (stuck in state), conditional on: Duration (how long stuck), exact status selected IdValue, Tags. Advance notice can NOT be set
|
||||
@@ -4172,7 +4211,7 @@ namespace AyaNova.Biz
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isNew && UserIsRestrictedType)
|
||||
if (!isNew && UserIsRestrictedType)
|
||||
{
|
||||
//Existing record so just make sure they haven't changed the not changeable fields from the db version
|
||||
//* Tasks: view and edit existing tasks, set completion type and date only, no add or remove or changing other fields
|
||||
|
||||
@@ -29,7 +29,7 @@ AuthorizationRoles.TechRestricted;
|
||||
{
|
||||
var custtags = options.Criteria["custtags"].ToObject<List<string>>();
|
||||
bool custtagsany = options.Criteria["custtagsany"].ToObject<bool>();
|
||||
string custTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("acustomer.tags", custtags, custtagsany); ;
|
||||
string custTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("acustomer.tags", custtags, custtagsany); ;
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
SELECT ACUSTOMERSERVICEREQUEST.DATEREQUESTED,
|
||||
|
||||
@@ -62,8 +62,8 @@ namespace AyaNova.KPI
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
var woitemtags = options.Criteria["woitemtags"].ToObject<List<string>>();
|
||||
bool woitemtagsany = options.Criteria["woitemtagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
SELECT distinct(AWORKORDER.ID), AWORKORDER.SERIAL,
|
||||
|
||||
@@ -45,7 +45,7 @@ AuthorizationRoles.Accounting;
|
||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ AuthorizationRoles.Accounting;
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
var woitemtags = options.Criteria["woitemtags"].ToObject<List<string>>();
|
||||
bool woitemtagsany = options.Criteria["woitemtagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
SELECT COUNT(AWORKORDER.ID) Y,
|
||||
|
||||
@@ -50,9 +50,9 @@ AuthorizationRoles.Accounting;
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
var woitemtags = options.Criteria["woitemtags"].ToObject<List<string>>();
|
||||
bool woitemtagsany = options.Criteria["woitemtagsany"].ToObject<bool>();
|
||||
string techTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("auser.tags", techtags, techtagsany);
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
string techTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("auser.tags", techtags, techtagsany);
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace AyaNova.KPI
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
var woitemtags = options.Criteria["woitemtags"].ToObject<List<string>>();
|
||||
bool woitemtagsany = options.Criteria["woitemtagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
select SUM(AWORKORDERITEMLABOR.serviceratequantity) y, date_trunc('{interval}',AWORKORDERITEMLABOR.servicestopdate) x
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace AyaNova.KPI
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
var woitemtags = options.Criteria["woitemtags"].ToObject<List<string>>();
|
||||
bool woitemtagsany = options.Criteria["woitemtagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
SELECT distinct(AWORKORDER.ID),
|
||||
|
||||
@@ -25,8 +25,8 @@ namespace AyaNova.KPI
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
var woitemtags = options.Criteria["woitemtags"].ToObject<List<string>>();
|
||||
bool woitemtagsany = options.Criteria["woitemtagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
SELECT distinct(AWORKORDER.ID),
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace AyaNova.KPI
|
||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
SELECT COUNT(AWORKORDER.ID) Y,DATE_TRUNC('{interval}', AWORKORDER.createddate) X, aworkorder.laststatusid Z
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace AyaNova.KPI
|
||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
|
||||
_dataQuery = @$"SELECT row_to_json(t) as res from (
|
||||
WITH SUBQ AS
|
||||
|
||||
@@ -45,8 +45,8 @@ AuthorizationRoles.TechRestricted;
|
||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||
var woitemtags = options.Criteria["woitemtags"].ToObject<List<string>>();
|
||||
bool woitemtagsany = options.Criteria["woitemtagsany"].ToObject<bool>();
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.KPITagFilterToSqlCriteria("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
string woTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorder.tags", wotags, wotagsany);
|
||||
string woItemTagsWhere = DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("aworkorderitem.tags", woitemtags, woitemtagsany); ;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ namespace AyaNova.Models
|
||||
public virtual DbSet<ViewScheduleWorkOrder> ViewScheduleWorkOrder { get; set; }
|
||||
|
||||
public virtual DbSet<CustomerNotifySubscription> CustomerNotifySubscription { get; set; }
|
||||
public virtual DbSet<CustomerNotifyEvent> CustomerNotifyEvent { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
56
server/AyaNova/models/CustomerNotifyEvent.cs
Normal file
56
server/AyaNova/models/CustomerNotifyEvent.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AyaNova.Biz;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AyaNova.Models
|
||||
{
|
||||
//Customer notification event
|
||||
public class CustomerNotifyEvent
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public uint Concurrency { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime Created { get; set; }
|
||||
public AyaType AyaType { get; set; }
|
||||
public long ObjectId { get; set; }
|
||||
[Required]
|
||||
public string Name { get; set; }//object name or closest equivalent for display
|
||||
[Required]
|
||||
public NotifyEventType EventType { get; set; }
|
||||
[Required]
|
||||
public long CustomerId { get; set; }
|
||||
[Required]
|
||||
public long CustomerNotifySubscriptionId { get; set; }//source subscription that triggered this event to be created
|
||||
|
||||
public decimal DecValue { get; set; }
|
||||
|
||||
//date of the event actually occuring, e.g. WarrantyExpiry date. Compared with subscription to determine if deliverable or not
|
||||
public DateTime EventDate { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
|
||||
public CustomerNotifyEvent()
|
||||
{
|
||||
Created = EventDate = DateTime.UtcNow;
|
||||
// IdValue = 0;
|
||||
DecValue = 0;
|
||||
AyaType = AyaType.NoType;
|
||||
ObjectId = 0;
|
||||
Name = string.Empty;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.None);
|
||||
}
|
||||
|
||||
//linked entity
|
||||
// public NotifySubscription NotifySubscription { get; set; }
|
||||
// public User User { get; set; }
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
@@ -1213,6 +1213,9 @@ $BODY$ LANGUAGE PLPGSQL STABLE");
|
||||
+ "idvalue BIGINT NOT NULL, decvalue DECIMAL(38,18) NOT NULL, agevalue INTERVAL NOT NULL, "
|
||||
+ "linkreportid BIGINT, template TEXT NOT NULL, tags VARCHAR(255) ARRAY)");
|
||||
|
||||
await ExecQueryAsync("CREATE TABLE acustomernotifyevent (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, created TIMESTAMPTZ NOT NULL, "
|
||||
+ "ayatype INTEGER NOT NULL, objectid BIGINT NOT NULL, name TEXT NOT NULL, eventtype INTEGER NOT NULL, customernotifysubscriptionid BIGINT NOT NULL REFERENCES acustomernotifysubscription(id) ON DELETE CASCADE, "
|
||||
+ "customerid BIGINT NOT NULL REFERENCES acustomer (id) ON DELETE CASCADE, eventdate TIMESTAMPTZ NOT NULL, decvalue DECIMAL(38,18) NULL, message TEXT)");
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user