diff --git a/devdocs/specs/core-license-key-system.txt b/devdocs/specs/core-license-key-system.txt index 3c3c4542..bf99f789 100644 --- a/devdocs/specs/core-license-key-system.txt +++ b/devdocs/specs/core-license-key-system.txt @@ -24,7 +24,7 @@ LICENSE BOOTSTRAPPING - Also this is the new fetch code - RAVEN Is unlicensed it should check for a new key on a shorter frequency loop rather than once a day? or, providing a button to trigger that should be enough really - or when server starts it starts the GENERATE loop and checks for a key right away, so they can just reboot teh server if they want an immediate license check? + or when server starts it starts the GENERATE loop and checks for a key right away, so they can just reboot the server if they want an immediate license check? - RAVEN DB is empty (of biz data) it's license locked and a User MUST DONE ONE OF THE FOLLOWING: diff --git a/devdocs/specs/core-notification.txt b/devdocs/specs/core-notification.txt index 2d81efc0..c5567948 100644 --- a/devdocs/specs/core-notification.txt +++ b/devdocs/specs/core-notification.txt @@ -24,7 +24,7 @@ BACKEND TODO / SYSTEM FOR RAVEN IMPLEMENTATION PATH / PLAN - code as minimal a route as possible to get to working notifications end to end with minimal subset first is test notification which is done as an implementation of the GeneralNotification Event type and is delivered to the current use running the test - This will go through all potential problems or maybe trigger a troubleshoot route at teh server that checks all and reports back with any issues found + This will go through all potential problems or maybe trigger a troubleshoot route at the server that checks all and reports back with any issues found Also needs to be localized (do the test route thing, that just makes the most sense. The test route should accept the type of notification i.e. inapp or smtp, act accordingly if it's a customer user vs regular if there is any difference) Should report to user exactly any issues found at any level preventing it from happening Should submit as a job and await running in place like other ones so that its actually working end to end with the generator diff --git a/devdocs/specs/core-reporting.txt b/devdocs/specs/core-reporting.txt index c0916f07..757aed86 100644 --- a/devdocs/specs/core-reporting.txt +++ b/devdocs/specs/core-reporting.txt @@ -119,7 +119,7 @@ todo: MEMORY / PERFORMANCE / CRASHING todo: memory usage and timeout is directly related to the amount of space taken up physically on the page - it's NOT related to the helpers as just putting static text on the page causes teh same issue + it's NOT related to the helpers as just putting static text on the page causes the same issue it's memory taken to render to pdf probably and a byte is a byte even if it's blank white page =-=-=-=-=- diff --git a/devdocs/specs/core-workorder.txt b/devdocs/specs/core-workorder.txt index f9a1c77e..cde0c042 100644 --- a/devdocs/specs/core-workorder.txt +++ b/devdocs/specs/core-workorder.txt @@ -7,7 +7,7 @@ If each part has it's own concurrency then can submit whole and bits that aren't CONCURRENCY decision / research - updating, in bits seperately, entirely at once? [## DECIDED, BITS MAKES MOST SENSE AND DIRTY TRACKING AT CLIENT ##] - Considering we are looking at a system where some users won't see or even get the data for some parts of teh workorder then it's important that they be able to just save their piece of it separately + Considering we are looking at a system where some users won't see or even get the data for some parts of the workorder then it's important that they be able to just save their piece of it separately So this strongly leans towards seperately saved and not entirely at all. concurrency issue reduction Things that might require an entire workorder graph be updated at once: @@ -38,7 +38,7 @@ CONCURRENCY decision / research - updating, in bits seperately, entirely at once Actually isn't this just handled at the server as a biz rule? (no because if header saved first then it's locked potentially and sub items can't be save subsequently in a graph walking save) New items are always dirty so a new labor record added would be dirty and saved individually At the server upon item being saved there would always need to be a quick check of the entire workorder to see if it's LOCKED status or not - if locked then bumps back with error that wo is now locked (of course they could just change teh status in the header and resave I guess) + if locked then bumps back with error that wo is now locked (of course they could just change the status in the header and resave I guess) UI stuff @@ -47,7 +47,7 @@ UI stuff so a single workorderitem workorder would have all fields exposed and if any grandchildren those are entered in a single form too until the user has more than one in v7 it's three clicks to go to a grandchild items specific field from the top header, woitem click -> left nav button row selection of area click -> grid row column click - in v8 it would be one more click then if it was a multi record grandchild but if it was a single record all teh way down then all would be exposed and visible at hand could go directly to the field in question + in v8 it would be one more click then if it was a multi record grandchild but if it was a single record all the way down then all would be exposed and visible at hand could go directly to the field in question so in this concept every level exposes an edit form below a grid of one or more items, but grid doesn't show if there is one item so it's very clean. if there is no record at that collection then just a click to add button as unobtrusive but clearly marked as possible (if user has selected to hide that section then doesn't even show and some users don't see at all) if multiple at that level then a grid to select the active row to edit below it showing as much data as possible in each row to help user see at a glance while editing a sibling item diff --git a/server/AyaNova/Controllers/WorkOrderController.cs b/server/AyaNova/Controllers/WorkOrderController.cs index e5091e7b..66b9d040 100644 --- a/server/AyaNova/Controllers/WorkOrderController.cs +++ b/server/AyaNova/Controllers/WorkOrderController.cs @@ -67,6 +67,9 @@ namespace AyaNova.Api.Controllers WorkOrderBiz biz = WorkOrderBiz.GetBiz(ct, HttpContext); if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); + if (newObject.Items.Count > 0) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "Work order POST route accepts header only; POST Work order descendants separately")); + if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); WorkOrder o = await biz.WorkOrderCreateAsync(newObject); @@ -127,7 +130,7 @@ namespace AyaNova.Api.Controllers /// Update WorkOrder /// /// - /// WorkOrder - top level only, no descendants + /// WorkOrder - top level only, no Items or other descendants /// Updated work order header [HttpPut] public async Task PutWorkOrder([FromBody] WorkOrder updatedObject) diff --git a/server/AyaNova/biz/NotifyEventType.cs b/server/AyaNova/biz/NotifyEventType.cs index 2926c872..fa3e209d 100644 --- a/server/AyaNova/biz/NotifyEventType.cs +++ b/server/AyaNova/biz/NotifyEventType.cs @@ -10,10 +10,11 @@ namespace AyaNova.Biz { //see core-notifications.txt spec doc for a bit more info on each type (they are named a little bit differently) //#### NOTE: once event is NOTED IN COMMENT (not necessarily coded yet as some can't be yet) in NotifyEventProcessor I'll mark it with a * in the comment so I know if I miss any + //#### NOTE: once event is fully coded I'll mark it with CODED at the start of the comment line ObjectDeleted = 1,//* Deletion of any object of conditional specific AyaType and optionally conditional tags ObjectCreated = 2,//* creation of any object of conditional specific AyaType and optionally conditional tags ObjectModified = 3,//* Modification / update of any kind of any object of conditional specific AyaType and optionally conditional tags - WorkorderStatusChange = 4,//* Workorder object, any *change* of status including from no status (new) to a specific conditional status ID value + WorkorderStatusChange = 4,//* Workorder object, any NEW status set. Conditions: specific status ID value, Workorder TAGS ContractExpiring = 5,//* Contract object, aged notification with optional advance notice for expiration date of contract. Customer version and User version deliveries possible. CSRAccepted = 6,//*CustomerServiceRequest object, saved with ACCEPTED status, delivered to Customer only CSRRejected = 7,//*CustomerServiceRequest object, saved with REJECTED status, delivered to Customer only diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 9e5c8fb7..081dabf0 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -77,9 +77,9 @@ namespace AyaNova.Biz await WorkOrderSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); - //Was this a full workorder posted all at once? - //(seeder or api user, not something AyaNova front end would do) - if (newObject.Items.Count > 0)//our front end will post the header alone on new so this indicates a fully populated wo was saved + //# NOTE: only internal code can post an entire workorder graph, no external user can as controller will reject right up front + //however, internally seeder will post entire workorders + if (newObject.Items.Count > 0) { await GetCurrentContractFromContractIdAsync(newObject.ContractId); @@ -111,6 +111,8 @@ namespace AyaNova.Biz } } + + //NOTE: not running individual notification here for children, seeder won't require it and that's all that posts an entire wo currently } await transaction.CommitAsync(); if (populateViz) @@ -985,6 +987,14 @@ namespace AyaNova.Biz //SPECIFIC EVENTS FOR THIS OBJECT + if (ayaEvent == AyaEvent.Deleted) + { + //remove any potential time delayed STATUS notifications for OVERDUE NOT CLOSED STATUS + //NOTE: if the workorder is deleted the above process standard etc will automatically + //remove *ALL* events created for this workorder so no need to remove overdue or any wo related time delayed status here + + } + //todo: contract response time notification }//end of process notifications @@ -1029,6 +1039,7 @@ namespace AyaNova.Biz await ct.WorkOrderState.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderStatus, AyaEvent.Created), ct); + await StateHandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } @@ -1074,9 +1085,12 @@ namespace AyaNova.Biz foreach (var wostate in stateList) { - await StateHandlePotentialNotificationEvent(AyaEvent.Deleted, wostate); ct.WorkOrderState.Remove(wostate); await ct.SaveChangesAsync(); + //no need to call this because it's only going to run this method if the workorder is deleted and + //via process standard notifciation events for workorder deletion will remove any state delayed notifications anyway so + //nothing to call or do here related to notification + // await StateHandlePotentialNotificationEvent(AyaEvent.Deleted, wostate); } } catch @@ -1139,24 +1153,89 @@ namespace AyaNova.Biz //STANDARD EVENTS FOR ALL OBJECTS - await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + //NONE: state notifications are specific and not the same as for general objects so don't process standard events //SPECIFIC EVENTS FOR THIS OBJECT + //WorkorderStatusChange = 4,//* Workorder object, any *change* of status including from no status (new) to a specific conditional status ID value + //WorkorderFinishStatusOverdue = 15,//* Workorder object not set to a "Finished" flagged workorder status type in selected time span from creation of workorderWorkorderSetToFinishedStatus + //WorkorderStatusAge = 24,//* Workorder object Created / Updated, conditional on exact status selected IdValue, Tags conditional, advance notice can be set + WorkOrderState o = (WorkOrderState)proposedObj; + WorkOrder wo = await ct.WorkOrder.AsNoTracking().FirstOrDefaultAsync(x => x.Id == o.WorkOrderId); //## DELETED EVENTS - //any event added below needs to be removed, so - //just blanket remove any event for this object of eventtype that would be added below here - //do it regardless any time there's an update and then - //let this code below handle the refreshing addition that could have changes - // await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, o.Id, NotifyEventType.ContractExpiring); + //A state cannot be deleted so nothing to handle that is required + //a workorder CAN be deleted and it will automatically remove all events for it so also no need to remove time delayed status events either if wo is deleted. + //so in essence there is nothing to be done regarding deleted events with states + + //NO, the above is not correct, time delayed ones should NOT be removed for example finished until appropriate or if wo deleted + //so there should be a deleted event here handled with delete of all notifications sitting and the blanket remove below should be more surgical + //and only apply to stuff that it is approriate to remove + + //#NOTE: state can be deleted so this block will handle that + await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, o.Id, NotifyEventType.WorkorderStatusChange); + await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, o.Id, NotifyEventType.WorkorderFinishStatusOverdue); + await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, o.Id, NotifyEventType.WorkorderStatusAge); - //## CREATED / MODIFIED EVENTS - if (ayaEvent == AyaEvent.Created || ayaEvent == AyaEvent.Modified) + //## CREATED (this is the only possible notification CREATION ayaEvent type for a workorder state as they are create only) + if (ayaEvent == AyaEvent.Created) { - //todo: fix etc, tons of shit here incoming + //# STATUS CHANGE (create new status) + { + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.WorkorderStatusChange).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + + //Tag match? (will be true if no sub tags so always safe to call this) + if (NotifyEventHelper.TagsMatch(wo.Tags, sub.Tags)) + { + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.WorkorderStatusChange, + UserId = sub.UserId, + AyaType = o.AyaType, + ObjectId = o.Id, + NotifySubscriptionId = sub.Id, + Name = wo.Serial.ToString() + }; + await ct.NotifyEvent.AddAsync(n); + log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); + await ct.SaveChangesAsync(); + } + } + }//workorder status change event + + + //# WorkorderFinishStatusOverdue + {//* Workorder object not set to a "Finished" flagged workorder status type in selected time span from creation of workorderWorkorderSetToFinishedStatus + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.WorkorderStatusChange).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + + //Tag match? (will be true if no sub tags so always safe to call this) + if (NotifyEventHelper.TagsMatch(wo.Tags, sub.Tags)) + { + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.WorkorderStatusChange, + UserId = sub.UserId, + AyaType = o.AyaType, + ObjectId = o.Id, + NotifySubscriptionId = sub.Id, + Name = wo.Serial.ToString() + }; + await ct.NotifyEvent.AddAsync(n); + log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); + await ct.SaveChangesAsync(); + } + } + }//workorder finish change event } diff --git a/server/AyaNova/generator/CoreJobNotify.cs b/server/AyaNova/generator/CoreJobNotify.cs index 92178038..7f217c80 100644 --- a/server/AyaNova/generator/CoreJobNotify.cs +++ b/server/AyaNova/generator/CoreJobNotify.cs @@ -23,16 +23,12 @@ namespace AyaNova.Biz private static DateTime lastRun = DateTime.MinValue; - // private static TimeSpan DELETE_AFTER_AGE = new TimeSpan(90, 0, 0, 0); - // private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 2, 0);//once every 2 minutes minimum private static DateTime lastNotifyHealthCheckSentLocal = DateTime.MinValue; private static TimeSpan TS_24_HOURS = new TimeSpan(24, 0, 0);//used to ensure daily ops happen no more than that -#if (DEBUG) - private static TimeSpan DELETE_AFTER_AGE = new TimeSpan(0, 12, 0, 0); +#if (DEBUG) private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 0, 20);//no more frequently than once every 20 seconds -#else - private static TimeSpan DELETE_AFTER_AGE = new TimeSpan(90, 0, 0, 0); +#else private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 2, 0);//no more frequently than once every 2 minutes #endif diff --git a/server/AyaNova/generator/Generate.cs b/server/AyaNova/generator/Generate.cs index 78c6cc0c..cbfeb6b6 100644 --- a/server/AyaNova/generator/Generate.cs +++ b/server/AyaNova/generator/Generate.cs @@ -37,7 +37,7 @@ namespace AyaNova.Generator /* todo: improve this it should timeout: https://stackoverflow.com/questions/23476576/cancellationtoken-timeout-vs-task-delay-and-timeout - it should never stop running no matter what unless teh server shuts down + it should never stop running no matter what unless the server shuts down */ protected override async Task ExecuteAsync(CancellationToken stoppingToken) diff --git a/server/AyaNova/models/PartInventory.cs b/server/AyaNova/models/PartInventory.cs index 21a95a7a..bfd6fbf4 100644 --- a/server/AyaNova/models/PartInventory.cs +++ b/server/AyaNova/models/PartInventory.cs @@ -107,7 +107,7 @@ CREATE TABLE [dbo].[APARTBYWAREHOUSEINVENTORY]( [APARTID] [uniqueidentifier] NOT NULL, [APARTWAREHOUSEID] [uniqueidentifier] NOT NULL, [AQUANTITYONHAND] [decimal](19, 5) NOT NULL, - [AQUANTITYONORDER] [decimal](19, 5) NOT NULL,//calculated on teh fly from active PO's + [AQUANTITYONORDER] [decimal](19, 5) NOT NULL,//calculated on the fly from active PO's [AMINSTOCKLEVEL] [decimal](19, 5) NOT NULL,//New table / feature PartRestock (partid/warhouseid/stocklevel) [AQTYONORDERCOMMITTED] [decimal](19, 5) NOT NULL, //Calculated on the fly from po and partrequests */ \ No newline at end of file diff --git a/server/AyaNova/util/License.cs b/server/AyaNova/util/License.cs index f8c6d106..465d7988 100644 --- a/server/AyaNova/util/License.cs +++ b/server/AyaNova/util/License.cs @@ -66,7 +66,7 @@ namespace AyaNova.Core //Current license key, can be empty private static AyaNovaLicenseKey _ActiveLicense = new AyaNovaLicenseKey(); - //The license dbid, separate from teh server dbid + //The license dbid, separate from the server dbid private static string LicenseDbId { get; set; } #region license classes