using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.JsonPatch; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { internal class WorkOrderBiz : BizObject, ISearchAbleObject { //Feature specific roles internal static AuthorizationRoles RolesAllowedToChangeSerial = AuthorizationRoles.BizAdminFull | AuthorizationRoles.DispatchFull | AuthorizationRoles.AccountingFull; internal WorkOrderBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; UserId = currentUserId; UserTranslationId = userTranslationId; CurrentUserRoles = UserRoles; BizType = AyaType.WorkOrder; } internal static WorkOrderBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { if (httpContext != null) return new WorkOrderBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); else//when called internally for internal ops there will be no context so need to set default values for that return new WorkOrderBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) { return await ct.WorkOrder.AnyAsync(e => e.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task CreateAsync(WorkOrder newObject) { await ValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Serial = ServerBootConfig.WORKORDER_SERIAL.GetNext(); newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrder.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); await SearchIndexAsync(newObject, true); await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // internal async Task DuplicateAsync(long id) { WorkOrder dbObject = await ct.WorkOrder.FirstOrDefaultAsync(m => m.Id == id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } WorkOrder newObject = new WorkOrder(); CopyObject.Copy(dbObject, newObject, "Wiki"); newObject.Serial = ServerBootConfig.WORKORDER_SERIAL.GetNext(); newObject.Id = 0; newObject.ConcurrencyToken = 0; await ct.WorkOrder.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); await SearchIndexAsync(newObject, true); await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); return newObject; } //////////////////////////////////////////////////////////////////////////////////////////////// /// GET /// /// internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) { /* https://docs.microsoft.com/en-us/ef/core/querying/related-data using (var context = new BloggingContext()) { var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ThenInclude(author => author.Photo) .ToList(); } WARNING: Since version 3.0.0, each Include will cause an additional JOIN to be added to SQL queries produced by relational providers, whereas previous versions generated additional SQL queries. This can significantly change the performance of your queries, for better or worse. In particular, LINQ queries with an exceedingly high number of Include operators may need to be broken down into multiple separate LINQ queries in order to avoid the cartesian explosion problem. */ //This is simple so nothing more here, but often will be copying to a different output object or some other ops //var ret = await ct.WorkOrder.SingleOrDefaultAsync(m => m.Id == fetchId); var ret = await ct.WorkOrder.Include(w => w.WorkorderItems).SingleOrDefaultAsync(m => m.Id == fetchId); if (logTheGetEvent && ret != null) { //Log await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); } return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // //put internal async Task PutAsync(WorkOrder dbObj, WorkOrder putObj) { // make a snapshot of the original for validation but update the original to preserve workflow WorkOrder SnapshotOfOriginalDBObj = new WorkOrder(); CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); //Replace the db object with the PUT object CopyObject.Copy(putObj, dbObj, "Id,Serial"); //if user has rights then change it, otherwise just ignore it and do the rest if (SnapshotOfOriginalDBObj.Serial != putObj.Serial && Authorized.HasAnyRole(CurrentUserRoles, RolesAllowedToChangeSerial)) { dbObj.Serial = putObj.Serial; } dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); //Set "original" value of concurrency token to input token //this will allow EF to check it out ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = putObj.ConcurrencyToken; await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); if (HasErrors) return false; //Log event and save context await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); await SearchIndexAsync(dbObj, false); await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); return true; } private async Task SearchIndexAsync(WorkOrder obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Serial).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task GetSearchResultSummary(long id) { var obj = await ct.WorkOrder.SingleOrDefaultAsync(m => m.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Serial).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task DeleteAsync(WorkOrder dbObj) { // NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); if (HasErrors) return false; ct.WorkOrder.Remove(dbObj); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Serial.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // //Can save or update? private async Task ValidateAsync(WorkOrder proposedObj, WorkOrder currentObj) { //run validation and biz rules bool isNew = currentObj == null; // //Name required // if (string.IsNullOrWhiteSpace(proposedObj.Name)) // AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); // //Name must be less than 255 characters // if (proposedObj.Name.Length > 255) // AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); // //If name is otherwise OK, check that name is unique // if (!PropertyHasErrors("Name")) // { // //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false // if (await ct.WorkOrder.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) // { // AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); // } // } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.WorkOrder.ToString()); if (FormCustomization != null) { //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required //validate users choices for required non custom fields RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } //Can delete? // private void ValidateCanDelete(WorkOrder inObj) // { // //whatever needs to be check to delete this object // } //////////////////////////////////////////////////////////////////////////////////////////////// //JOB / OPERATIONS // //Other job handlers here... ///////////////////////////////////////////////////////////////////// }//eoc }//eons