This commit is contained in:
2020-07-16 23:21:09 +00:00
parent 68c5b30e5b
commit fd23ce1fcb
7 changed files with 128 additions and 42 deletions

View File

@@ -262,7 +262,7 @@ UnitMeterReadingMultipleExceeded case 1254 [GENERAL]
DefaultNotification case 3792 used for all system and old Quick notifications [EVERYONE]
Always present for inapp, user can add more for email if they want and filters or whatever but there is always a built in non-visible subscription to inapp for this
deprecated, use objectCUD events for this: TagNotification case 3799 [CONDITION: tag, , ayatype global/specific]
deprecated, use objectCRUD events for this: TagNotification case 3799 [CONDITION: tag, , ayatype global/specific]
(Note: this is actually just general notification if an object is created or updated and the tagging is optional)
CRUD Notifications:

View File

@@ -34,36 +34,67 @@ namespace AyaNova.Biz
else
log.LogWarning($"Ops problem notification: \"{message}\"");
await UpsertEvent(new NotifyEvent() { EventType = NotifyEventType.ServerOperationsProblem, Message = message });
await AddGeneralNotifyEvent(NotifyEventType.ServerOperationsProblem, message);
}
//Add event if there are subscribers
//several optional conditional parameters
public static async Task UpsertEvent(NotifyEvent ev, List<string> inTags = null, long? IdValue = null, TimeSpan? AgeValue = null, decimal? DecValue = null)
//This handles general notification events not requiring a decision or tied to an object that are basically just a immediate message to the user
//e.g. ops problems, DefaultNotification, NotifyHealthCheck etc
//optional user id to send directly to them
public static async Task AddGeneralNotifyEvent(NotifyEventType eventType, string message, long userId=0)
{
#if (DEBUG)
switch (eventType)
{
case NotifyEventType.BackupStatus:
case NotifyEventType.DefaultNotification:
case NotifyEventType.NotifyHealthCheck:
case NotifyEventType.ServerOperationsProblem:
break;
default://this will likely be a development error, not a production error so no need to log etc
throw (new System.NotSupportedException($"NotifyEventProcessor:AddGeneralNotifyEvent - Type of event {eventType} is unexpected and not supported"));
}
#endif
//Default notification goes to a specific user only
if(eventType==NotifyEventType.DefaultNotification){
if(userId==0){
//this will likely be a development error, not a production error so no need to log etc
throw new System.ArgumentException("NotifyEventProcessor:AddGeneralNotifyEvent: DefaultNotification requires a user id but none was specified");
}
NotifyEvent n=new NotifyEvent(){EventType=eventType,UserId=userId, Message=message}
return;
}
}
//This is told about an event and then determines if there are any subscriptions related to that event and proceses them accordingly
//todo: this should take some kind of general event type like the AyaEvent types (i.e. which CRUD operation is in effect if relevant)
//and also a biz object before and after or just before if not a change and also a AyaType
//then *this* code will go through and look for subscriptions related to that event
//this way the biz object code can be "dumb" about notifications in general and just let this code handle it as needed
//will iterate the subscriptions and see if any apply here
public static async Task HandlePotentialNotificationEvent(AyaType ayaType, AyaEvent ayaEvent, BizObject newObject, BizObject currentObject, )
{
log.LogTrace($"UpsertEvent processing: [{ev.ToString()}]");
try
{
// List<NotifySubscription> subs = new List<NotifySubscription>();
//Widget.updated.tags=yellow
//find subs where widget.updated and either no tags or tags=yellow if tags
//do I really need to remove old events? They should be zipping out the door pretty quickly.
//only I guess if it's delayed like a time delayed one or send later kind of situation?
var HasTags = inTags != null && inTags.Count > 0;
using (AyContext ct = AyaNova.Util.ServiceProviderProvider.DBContext)
{
//find all the subs that are relevant
//find all the Notification subscription subs that are relevant
//this first query ensures that the equality matching conditions of AyaType and EventType and idValue are matched leaving only more complex matches to rectify below
var subs = await ct.NotifySubscription.AsNoTracking().Where(z => z.EventType == ev.EventType && z.AyaType == ev.AyaType && z.IdValue == ev.IdValue).ToListAsync();
foreach (var sub in subs)
{
//Tags condition
//CONDITION CHECK Tags condition
if (sub.Tags.Count > 0)
{
if (!HasTags)
@@ -75,24 +106,66 @@ namespace AyaNova.Biz
continue;
}
//Decimal value, so far only for "The Andy" so the only condition is that the event value be greater than or equal to subscription setting
//CONDITION CHECK Decimal value, so far only for "The Andy" so the only condition is that the event value be greater than or equal to subscription setting
if (ev.EventType == NotifyEventType.WorkorderTotalExceedsThreshold && ev.DecValue < sub.DecValue)
continue;
//AgeValue
//Here ready for delivery
//HANDLE RELATED INDIRECT EVENTS AND CLEANUP
//some event types create related events here automatically (e.g. WorkorderStatusChange in turn will trigger workorderstatusage if subbed here)
//Deleting an object or changing status etc will affect other events in turn so clean up for them here
switch (ev.EventType)
{
case NotifyEventType.ObjectAge:
case NotifyEventType.WorkorderStatusAge:
case NotifyEventType.ObjectDeleted:
{
//object is deleted so remove any other events pending for it
//current working concept is there is no reason to keep any event related to a freshly deleted object
var deleteEventList = await ct.NotifyEvent.Where(z => z.ObjectId == ev.ObjectId && z.AyaType == ev.AyaType).ToListAsync();
ct.NotifyEvent.RemoveRange(deleteEventList);
await ct.SaveChangesAsync();
}
break;
case NotifyEventType.WorkorderStatusChange:
{
//Workorder status changed so remove any other prior workorderstatuschanged events and
//remove any prior workorderstatusAge events for this object id
var deleteEventList = await ct.NotifyEvent.Where(z => z.ObjectId == ev.ObjectId && z.AyaType == ev.AyaType && ev.EventType == NotifyEventType.WorkorderStatusChange).ToListAsync();
ct.NotifyEvent.RemoveRange(deleteEventList);
await ct.SaveChangesAsync();
}
break;
case NotifyEventType.WorkorderStatusAge://delete any workorder status age ones because there is potentially a new status invalidating the old status
case NotifyEventType.QuoteStatusAge:
if (AgeValue != null && AgeValue < sub.AgeValue)//current object age value is less than subscription required age value? (not old enough)
continue;//then skip it
//Delete any existing ones
var StaleAgedEvents = await ct.NotifyEvent.Where(z => z.EventType == ev.EventType && z.ObjectId == ev.ObjectId && z.AyaType == ev.AyaType).ToListAsync();
break;
}
//Here we know the sub matches so create the NotifyEvent here
NotifyEvent ne = new NotifyEvent() { };
//AgeValue refresh?
//Age based ones need to replace any prior existing age based ones made for the same object as they are the only ones that can sit in the queue for any length of time
//and become invalidated (i.e. deadman type deliveries that sb removed if no longer applicable e.g. WorkorderStatusAge)
switch (ev.EventType)
{
case NotifyEventType.ObjectAge://this is set on creation so it will never change unless the object is deleted
case NotifyEventType.WorkorderStatusAge://delete any workorder status age ones because there is potentially a new status invalidating the old status
case NotifyEventType.QuoteStatusAge:
//Delete any existing ones
var StaleAgedEvents = await ct.NotifyEvent.Where(z => z.EventType == ev.EventType && z.ObjectId == ev.ObjectId && z.AyaType == ev.AyaType).ToListAsync();
break;
}
//Here we know the sub matches the event and passes all the conditions so set the deliver date if necessary and save the NotifyEvent here
//todo: create message here (delivery thing?)
//todo: Attach report here or is that a delivery thing (delivery thing)

View File

@@ -36,7 +36,7 @@ namespace AyaNova.Biz
WorkorderStatusAge = 24,//sitting too long in same status
UnitWarrantyExpiry = 25,
UnitMeterReadingMultipleExceeded = 26,
DefaultNotification = 27,//old quick notification, refers now to any direct text notification internal or user to user used for system notifications
DefaultNotification = 27,//old quick notification, refers now to any direct text notification internal or user to user used for system notifications (default delivers in app but user can opt to also get email)
ServerOperationsProblem = 28,//and serious issue with server operations requiring intervention,
QuoteStatusAge = 29//quote stuck in same status too long

View File

@@ -45,14 +45,16 @@ namespace AyaNova.Biz
using (AyContext ct = AyaNova.Util.ServiceProviderProvider.DBContext)
{
//select all jobs with no deliver date or deliver date no longer in future
//### PLAN if it's an smtp delivery that fails and it's to someone who can be delivered in app then it should send an inapp notification of
//delivery failure and still delete the smtp delivery
//If it's not possible to notify the person via in app of the failed smtp then perhaps it notifies OPS personnel and biz admin personnel
//NEW NOTIFICATION SUBSCRIPTION EVENT TYPE:
//OPERATIONS_PROBLEMS - backup, notifications, out of memory, what have you, anyone can subscribe to it regardless of rights
//this is just to let people know there is a problem
//### PLAN if it's an smtp delivery that fails and it's to someone who can be delivered in app then it should send an inapp notification of
//delivery failure and still delete the smtp delivery
//If it's not possible to notify the person via in app of the failed smtp then perhaps it notifies OPS personnel and biz admin personnel
//NEW NOTIFICATION SUBSCRIPTION EVENT TYPE:
//OPERATIONS_PROBLEMS - backup, notifications, out of memory, what have you, anyone can subscribe to it regardless of rights
//this is just to let people know there is a problem
//todo: create message here if not already set?
//todo: generate and attach report here?
}
}

View File

@@ -21,10 +21,15 @@ namespace AyaNova.Models
[Required]
public NotifyEventType EventType { get; set; }
[Required]
public long UserId { get; set; }
[Required]
public long SubscriptionId { get; set; }//source subscription that triggered this event to be created
[Required]
public long IdValue { get; set; }
[Required]
public decimal DecValue { get; set; }
[Required]
public TimeSpan AgeValue { get; set; }
public DateTime? EventDate { get; set; }//date of the event actually occuring, e.g. WarrantyExpiry date. Duped with delivery date as source of truth in case edit to advance setting in subscription changes delivery date
public DateTime? DeliverDate { get; set; }//date user wants the event notification delivered, usually same as event date but could be set earlier if Advance setting in effect. This is the date consulted for delivery only.
public string Message { get; set; }
@@ -35,8 +40,9 @@ namespace AyaNova.Models
Created = DateTime.UtcNow;
IdValue = 0;
DecValue = 0;
AyaType=AyaType.NoType;
ObjectId=0;
AgeValue = new TimeSpan(0, 0, 0);
AyaType = AyaType.NoType;
ObjectId = 0;
}
public override string ToString()

View File

@@ -24,16 +24,18 @@ namespace AyaNova.Models
public long AttachReportId { get; set; }
//CONDITIONS - Following fields are all conditions set on whether to notify or not
//CREATE NOTIFY EVENT CONDITIONS - Following fields are all conditions set on whether to create a notify event or not
public AyaType AyaType { get; set; }//Note: must be specific object, not global for any object related stuff to avoid many role issues and also potential overload
[Required]
public NotifyEventType EventType { get; set; }
[Required]
public long IdValue { get; set; }//if required, this must match, default is zero to match to not set
public decimal DecValue { get; set; }//if required this must match or be greater or something
public TimeSpan AgeValue { get; set; }//for events that depend on an age of something (e.g. WorkorderStatusAge)
public decimal DecValue { get; set; }//if required this must match or be greater or something
public List<string> Tags { get; set; }//Tags to filter an event, object *must* have these tags to generate event related to it (AT TIME OF UPDATE)
//DELIVERY CONDITIONS - following are all conditions on *whether* to deliver the existing notify event or not
public TimeSpan AgeValue { get; set; }//for events that depend on an age of something (e.g. WorkorderStatusAge)
public NotifySubscription()
{
Tags = new List<string>();

View File

@@ -685,17 +685,20 @@ $BODY$;
await ExecQueryAsync("CREATE TABLE anotifysubscription (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " +
"userid bigint not null, ayatype integer not null, eventtype integer not null, advancenotice interval not null, " +
"idvalue bigint not null, decvalue decimal(19,4) not null, agevalue interval not null, deliverymethod integer not null, deliveryaddress text, attachreportid bigint not null, tags varchar(255) ARRAY)");
"idvalue bigint not null, decvalue decimal(19,4) not null, agevalue interval not null, deliverymethod integer not null, " +
"deliveryaddress text, attachreportid bigint not null, tags varchar(255) ARRAY)");
await ExecQueryAsync("CREATE TABLE anotifyevent (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, created timestamp not null, " +
"ayatype integer not null, objectid bigint not null, eventtype integer not null, subscriptionid bigint not null, idvalue bigint not null, decvalue decimal(19,4) not null, eventdate timestamp, deliverdate timestamp, message text)");
"ayatype integer not null, objectid bigint not null, eventtype integer not null, subscriptionid bigint not null, userid bigint not null, " +
"idvalue bigint not null, decvalue decimal(19,4) not null, agevalue interval not null, eventdate timestamp, deliverdate timestamp, message text)");
await ExecQueryAsync("CREATE TABLE anotification (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, userid bigint not null, created timestamp not null, ayatype integer not null, objectid bigint not null, eventtype integer not null, " +
await ExecQueryAsync("CREATE TABLE anotification (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, userid bigint not null, " +
"created timestamp not null, ayatype integer not null, objectid bigint not null, eventtype integer not null, " +
"subscriptionid bigint not null, message text, fetched bool not null)");
await ExecQueryAsync("CREATE TABLE anotifydeliverylog (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, processed timestamp not null, ayatype integer not null, objectid bigint not null, " +
"eventtype integer not null, subscriptionid bigint not null, idvalue bigint not null, decvalue decimal(19,4) not null, userid bigint not null, " +
"deliverymethod integer not null, fail bool not null, error text)");
await ExecQueryAsync("CREATE TABLE anotifydeliverylog (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, processed timestamp not null, " +
"ayatype integer not null, objectid bigint not null, eventtype integer not null, subscriptionid bigint not null, idvalue bigint not null, "+
"decvalue decimal(19,4) not null, userid bigint not null, deliverymethod integer not null, fail bool not null, error text)");
await SetSchemaLevelAsync(++currentSchema);
}