diff --git a/server/AyaNova/biz/QuoteBiz.cs b/server/AyaNova/biz/QuoteBiz.cs index 446996d0..c3fd52ee 100644 --- a/server/AyaNova/biz/QuoteBiz.cs +++ b/server/AyaNova/biz/QuoteBiz.cs @@ -61,7 +61,7 @@ namespace AyaNova.Biz } - + private VizCache vc = new VizCache(); private ObjectCache oc = new ObjectCache(); @@ -903,27 +903,8 @@ namespace AyaNova.Biz o.CustomerEmailAddressViz = vc.Get("custemail", o.CustomerId); } - // var custInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == o.CustomerId).Select(x => new { x.AlertNotes, x.TechNotes, x.Name, x.Phone1, x.Phone2, x.Phone3, x.Phone4, x.Phone5, x.EmailAddress }).FirstOrDefaultAsync(); - // if (!string.IsNullOrWhiteSpace(custInfo.AlertNotes)) - // { - // o.AlertViz = $"{await Translate("Customer")} - {await Translate("AlertNotes")}\n{custInfo.AlertNotes}\n\n"; - // } - // if (!string.IsNullOrWhiteSpace(custInfo.TechNotes)) - // { - // o.CustomerTechNotesViz = $"{await Translate("CustomerTechNotes")}\n{custInfo.TechNotes}\n\n"; - // } - // o.CustomerViz = custInfo.Name; - // o.CustomerPhone1Viz = custInfo.Phone1; - // o.CustomerPhone2Viz = custInfo.Phone2; - // o.CustomerPhone3Viz = custInfo.Phone3; - // o.CustomerPhone4Viz = custInfo.Phone4; - // o.CustomerPhone5Viz = custInfo.Phone5; - // o.CustomerEmailAddressViz = custInfo.EmailAddress; - - // if (o.ProjectId != null) - // o.ProjectViz = await ct.Project.AsNoTracking().Where(x => x.Id == o.ProjectId).Select(x => x.Name).FirstOrDefaultAsync(); if (o.ProjectId != null) { string value = vc.Get("projname", o.ProjectId); @@ -935,8 +916,7 @@ namespace AyaNova.Biz o.ProjectViz = value; } - // if (o.PreparedById != null) - // o.PreparedByViz = await ct.User.AsNoTracking().Where(x => x.Id == o.PreparedById).Select(x => x.Name).FirstOrDefaultAsync(); + if (o.PreparedById != null) { @@ -947,17 +927,7 @@ namespace AyaNova.Biz o.PreparedByViz = vc.Get("user", o.PreparedById); } - // if (o.ContractId != null) - // { - // var contractVizFields = await ct.Contract.AsNoTracking().Where(x => x.Id == o.ContractId).Select(x => new { Name = x.Name, AlertNotes = x.AlertNotes }).FirstOrDefaultAsync(); - // o.ContractViz = contractVizFields.Name; - // if (!string.IsNullOrWhiteSpace(contractVizFields.AlertNotes)) - // { - // o.AlertViz += $"{await Translate("Contract")}\n{contractVizFields.AlertNotes}\n\n"; - // } - // } - // else - // o.ContractViz = "-"; + if (o.ContractId != null) { if (vc.Get("ctrctname", o.ContractId) == null) @@ -975,6 +945,16 @@ namespace AyaNova.Biz } else o.ContractViz = "-"; + + if (o.LastStatusId != null) + { + var lastState = o.States[o.States.Count - 1]; + o.LastStateColorViz = lastState.ColorViz; + o.LastStateCompletedViz = lastState.CompletedViz; + o.LastStateLockedViz = lastState.LockedViz; + o.LastStateNameViz = lastState.NameViz; + o.LastStateUserViz = lastState.UserViz; + } } @@ -1158,7 +1138,6 @@ namespace AyaNova.Biz // private async Task StatePopulateVizFields(QuoteState o) { - // o.UserViz = await ct.User.AsNoTracking().Where(x => x.Id == o.UserId).Select(x => x.Name).FirstOrDefaultAsync(); if (o.UserId != 0) { @@ -1168,6 +1147,22 @@ namespace AyaNova.Biz } o.UserViz = vc.Get("user", o.UserId); } + + + QuoteStatus QStatus = null; + + if (!oc.Has("quotestatus", o.QuoteStatusId)) + { + QStatus = await ct.QuoteStatus.AsNoTracking().Where(x => x.Id == o.QuoteStatusId).FirstOrDefaultAsync(); + oc.Add(QStatus, "quotestatus", o.QuoteStatusId); + } + else + QStatus = (QuoteStatus)oc.Get("quotestatus", o.QuoteStatusId); + + o.NameViz = QStatus.Name; + o.ColorViz = QStatus.Color; + o.CompletedViz = QStatus.Completed; + o.LockedViz = QStatus.Locked; } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -1216,203 +1211,6 @@ namespace AyaNova.Biz - // //////////////////////////////////////////////////////////////////////////////////////////////// - // // NOTIFICATION PROCESSING - // // - // public async Task StateHandlePotentialNotificationEvent(AyaEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) - // { - - // ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger(); - // if(ServerBootConfig.SEEDING || ServerBootConfig.MIGRATING) return; - // log.LogDebug($"HandlePotentialNotificationEvent processing: [AyaType:{proposedObj.AyaType}, AyaEvent:{ayaEvent}]"); - - // bool isNew = currentObj == null; - - // //currently no quote state notifications but may well be in future so this saves changing a bunch of shit if necessary later - // await Task.CompletedTask; - // // QuoteState oProposed = (QuoteState)proposedObj; - - // // var WorkorderInfo = await ct.Quote.AsNoTracking().Where(x => x.Id == oProposed.QuoteId).Select(x => new { x.Serial, x.Tags, x.CustomerId }).FirstOrDefaultAsync(); - // // QuoteStatus wos = await ct.QuoteStatus.AsNoTracking().FirstOrDefaultAsync(x => x.Id == oProposed.QuoteStatusId); - // // //for notification purposes because has no name / tags field itself - // // oProposed.Name = WorkorderInfo.Serial.ToString(); - // // oProposed.Tags = WorkorderInfo.Tags; - - // // //STANDARD EVENTS FOR ALL OBJECTS - // // //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 NEW status set. Conditions: specific status ID value only (no generic any status allowed), Workorder TAGS - // // //WorkorderCompletedStatusOverdue = 15,//* Workorder object not set to a "Completed" flagged quote status type in selected time span from creation of workorderWorkorderSetToCompletedStatus - // // //WorkorderStatusAge = 24,//* Workorder 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 - - // // //NOTE: ID, state notifications are for the Workorder, not the state itself unlike other objects, so use the WO type and ID here for all notifications - - - - - - // // //## DELETED EVENTS - // // //A state cannot be deleted so nothing to handle that is required - // // //a quote 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 in a blanket way, however specific events below may remove them as appropriate - - - // // // await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, o.Id, NotifyEventType.WorkorderStatusChange); - // // // await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, o.Id, NotifyEventType.WorkorderCompletedStatusOverdue); - // // // await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, o.Id, NotifyEventType.WorkorderStatusAge); - - - // // //## CREATED (this is the only possible notification CREATION ayaEvent type for a quote state as they are create only) - // // if (ayaEvent == AyaEvent.Created) - // // { - // // //# STATUS CHANGE (create new status) - // // { - // // //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.WorkorderStatusChange && z.IdValue == oProposed.QuoteStatusId).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.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags)) - // // { - // // NotifyEvent n = new NotifyEvent() - // // { - // // EventType = NotifyEventType.WorkorderStatusChange, - // // UserId = sub.UserId, - // // AyaType = AyaType.Quote, - // // ObjectId = oProposed.QuoteId, - // // NotifySubscriptionId = sub.Id, - // // Name = $"{WorkorderInfo.Serial.ToString()} - {wos.Name}" - // // }; - // // await ct.NotifyEvent.AddAsync(n); - // // log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); - // // await ct.SaveChangesAsync(); - // // } - // // } - // // }//quote status change event - - // // //# STATUS AGE - // // { - // // //WorkorderStatusAge = 24,//* Workorder 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 - // // //Always clear any old ones for this object as they are all irrelevant the moment the state has changed: - // // await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, proposedObj.Id, NotifyEventType.WorkorderStatusAge); - // // var subs = await ct.NotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderStatusAge && z.IdValue == oProposed.QuoteStatusId).ToListAsync(); - // // foreach (var sub in subs) - // // { - // // //not for inactive users - // // if (!await UserBiz.UserIsActive(sub.UserId)) continue; - - // // //Quote Tag match? (Not State, state has no tags, will be true if no sub tags so always safe to call this) - // // if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags)) - // // { - // // NotifyEvent n = new NotifyEvent() - // // { - // // EventType = NotifyEventType.WorkorderStatusAge, - // // UserId = sub.UserId, - // // AyaType = AyaType.Quote, - // // ObjectId = oProposed.QuoteId, - // // NotifySubscriptionId = sub.Id, - // // Name = $"{WorkorderInfo.Serial.ToString()} - {wos.Name}" - // // }; - // // await ct.NotifyEvent.AddAsync(n); - // // log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); - // // await ct.SaveChangesAsync(); - // // } - // // } - // // }//quote status change event - - - // // //# COMPLETE BY OVERDUE - // // { - // // //NOTE: the initial notification is created by the Workorder Header notification as it's where this time delayed notification is first generated - // // //the only job here in state notification is to remove any prior finish overdue notifications waiting if a new state is selected that is a completed state - - // // //NOTE ABOUT RE-OPEN DECISION ON HOW THIS WORKS: - - // // //what though if it's not a Completed status, then I guess don't remove it, but what if it *was* a Completed status and it's change to a non Completed? - // // //that, in essence re-opens it so it's not Completed at that point. - // // //My decision on this june 2021 is that a work order Completed status notification is satisifed the moment it's saved with a Completed status - // // //and nothing afterwards restarts that process so if a person sets closed status then sets open status again no new Completed overdue notification will be generated - - // // if (wos.Completed) - // // { - // // //Workorder was just set to a completed status so remove any notify events lurking to deliver for overdue - // // await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.AyaType, oProposed.QuoteId, NotifyEventType.WorkorderCompletedStatusOverdue); - // // } - // // }//quote complete by overdue change event - - - - - // // //# WorkorderCompleted - Customer AND User but customer only notifies if it's their quote - // // { - // // if (wos.Completed) - // // { - // // //look for potential subscribers - // // var subs = await ct.NotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderCompleted).ToListAsync(); - // // foreach (var sub in subs) - // // { - // // //not for inactive users - // // if (!await UserBiz.UserIsActive(sub.UserId)) continue; - - // // //Customer User? - // // var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == sub.UserId).Select(x => new { x.CustomerId, x.UserType, x.HeadOfficeId }).FirstOrDefaultAsync(); - // // if (UserInfo.UserType == UserType.Customer || UserInfo.UserType == UserType.HeadOffice) - // // { - // // //CUSTOMER USER - - // // //Quick short circuit: if quote doesn't have a customer id then it's not going to match no matter what - // // if (WorkorderInfo.CustomerId == 0) continue; - - // // var customerUserRights = await UserBiz.CustomerUserEffectiveRightsAsync(sub.UserId); - - // // //Are they allowed right now to use this type of notification? - // // if (!customerUserRights.NotifyWOCompleted) continue; - - // // //is this their related work order? - // // if (UserInfo.CustomerId != WorkorderInfo.CustomerId) - // // { - // // //not the same customer but might be a head office user and this is one of their customers so check for that - // // if (UserInfo.HeadOfficeId == null) continue;//can't match any head office so no need to go further - - // // //see if quote customer's head office is the same id as the user's headofficeid (note that a customer user with the same head office as a *different* customer quote doesn't qualify) - // // var CustomerInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == WorkorderInfo.CustomerId).Select(x => new { x.HeadOfficeId, x.BillHeadOffice }).FirstOrDefaultAsync(); - // // if (!CustomerInfo.BillHeadOffice) continue;//can't possibly match so no need to go further - // // if (UserInfo.HeadOfficeId != CustomerInfo.HeadOfficeId) continue; - // // } - // // } - // // else - // // { - // // //INSIDE USER - // // //Tag match? (will be true if no sub tags so always safe to call this) - // // //check early to avoid cost of fetching and calculating total if unnecessary - // // if (!NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags)) continue; - // // } - - // // //Ok, we're here so it must be ok to notify user - // // NotifyEvent n = new NotifyEvent() - // // { - // // EventType = NotifyEventType.WorkorderCompleted, - // // UserId = sub.UserId, - // // AyaType = AyaType.Quote, - // // ObjectId = oProposed.QuoteId, - // // NotifySubscriptionId = sub.Id, - // // Name = $"{WorkorderInfo.Serial.ToString()}" - // // }; - // // await ct.NotifyEvent.AddAsync(n); - // // log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); - // // await ct.SaveChangesAsync(); - // // } - // // } - // // }//WorkorderCompleted - // // } - - // }//end of process notifications - #endregion work order STATE level diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 000f031b..7729d8b3 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -61,7 +61,7 @@ namespace AyaNova.Biz UserType.Service); } - + private VizCache vc = new VizCache(); private ObjectCache oc = new ObjectCache(); @@ -938,7 +938,7 @@ namespace AyaNova.Biz //REPORTING // public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) - { + { //workorder reports for entire workorder or just sub parts all go through here //if the ayatype is a descendant of the workorder then only the portion of the workorder from that descendant directly up to the header will be populated and returned //however if the report template has includeWoItemDescendants=true then the woitems is fully populated @@ -948,19 +948,19 @@ namespace AyaNova.Biz List batchResults = new List(); while (idList.Any()) { - if (!ReportRenderManager.KeepGoing(jobId)) return null; + 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; + if (!ReportRenderManager.KeepGoing(jobId)) return null; batchResults.Add(await WorkOrderGetPartialAsync(dataListSelectedRequest.AType, batchId, dataListSelectedRequest.IncludeWoItemDescendants, true)); } foreach (WorkOrder w in batchResults) { - if (!ReportRenderManager.KeepGoing(jobId)) return null; + if (!ReportRenderManager.KeepGoing(jobId)) return null; var jo = JObject.FromObject(w); //WorkOrder header custom fields @@ -1102,6 +1102,17 @@ namespace AyaNova.Biz o.FromPMViz = value; } + if (o.LastStatusId != null) + { + var lastState = o.States[o.States.Count - 1]; + o.LastStateColorViz = lastState.ColorViz; + o.LastStateCompletedViz = lastState.CompletedViz; + o.LastStateLockedViz = lastState.LockedViz; + o.LastStateNameViz = lastState.NameViz; + o.LastStateUserViz = lastState.UserViz; + } + + } @@ -3111,14 +3122,14 @@ namespace AyaNova.Biz //////////////////////////////////////////////////////////////////////////////////////////////// // GET // - internal async Task LoanGetAsync(long id, bool logTheGetEvent = true, bool populateViz=false) + internal async Task LoanGetAsync(long id, bool logTheGetEvent = true, bool populateViz = false) { if (UserIsSubContractorRestricted) //no access allowed at all return null; var ret = await ct.WorkOrderItemLoan.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, ret.AyaType, AyaEvent.Retrieved), ct); - if(populateViz) + if (populateViz) await LoanPopulateVizFields(ret); return ret; } diff --git a/server/AyaNova/models/Quote.cs b/server/AyaNova/models/Quote.cs index 2830de5b..93048d21 100644 --- a/server/AyaNova/models/Quote.cs +++ b/server/AyaNova/models/Quote.cs @@ -97,6 +97,17 @@ namespace AyaNova.Models [NotMapped] public string AlertViz { get; set; } = null; + [NotMapped] + public string LastStateUserViz { get; set; } + [NotMapped] + public string LastStateNameViz { get; set; } + [NotMapped] + public string LastStateColorViz { get; set; } + [NotMapped] + public bool LastStateCompletedViz { get; set; } + [NotMapped] + public bool LastStateLockedViz { get; set; } + [NotMapped] public bool IsCompleteRecord { get; set; } = true;//indicates if some items were removed due to user role / type restrictions (i.e. woitems they are not scheduled on) diff --git a/server/AyaNova/models/QuoteState.cs b/server/AyaNova/models/QuoteState.cs index 73ca7727..7625bc93 100644 --- a/server/AyaNova/models/QuoteState.cs +++ b/server/AyaNova/models/QuoteState.cs @@ -23,6 +23,14 @@ namespace AyaNova.Models public long UserId { get; set; } [NotMapped] public string UserViz { get; set; } + [NotMapped] + public string NameViz { get; set; } + [NotMapped] + public string ColorViz { get; set; } + [NotMapped] + public bool CompletedViz { get; set; } + [NotMapped] + public bool LockedViz { get; set; } //workaround for notification [NotMapped, JsonIgnore] diff --git a/server/AyaNova/models/WorkOrder.cs b/server/AyaNova/models/WorkOrder.cs index 82611cae..dbdd0859 100644 --- a/server/AyaNova/models/WorkOrder.cs +++ b/server/AyaNova/models/WorkOrder.cs @@ -99,6 +99,17 @@ namespace AyaNova.Models [NotMapped] public string FromPMViz { get; set; } + [NotMapped] + public string LastStateUserViz { get; set; } + [NotMapped] + public string LastStateNameViz { get; set; } + [NotMapped] + public string LastStateColorViz { get; set; } + [NotMapped] + public bool LastStateCompletedViz { get; set; } + [NotMapped] + public bool LastStateLockedViz { get; set; } + [NotMapped] public bool IsCompleteRecord { get; set; } = true;//indicates if some items were removed due to user role / type restrictions (i.e. woitems they are not scheduled on)