using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; using System.Linq; using System; using Newtonsoft.Json.Linq; using System.Collections.Generic; namespace AyaNova.Biz { internal class WorkOrderBiz : BizObject, IJobObject, 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); } /* ██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗ ██║ ██║██╔═══██╗██╔══██╗██║ ██╔╝ ██╔═══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗ ██║ █╗ ██║██║ ██║██████╔╝█████╔╝█████╗██║ ██║██████╔╝██║ ██║█████╗ ██████╔╝ ██║███╗██║██║ ██║██╔══██╗██╔═██╗╚════╝██║ ██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ ╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗ ╚██████╔╝██║ ██║██████╔╝███████╗██║ ██║ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝ */ #region WorkOrder level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task WorkOrderExistsAsync(long id) { return await ct.WorkOrder.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task WorkOrderCreateAsync(dtWorkOrder dtNewObject) { WorkOrder newObject = new WorkOrder(); CopyObject.Copy(dtNewObject, newObject); await WorkOrderValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.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 WorkOrderSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // internal async Task WorkOrderDuplicateAsync(long id) { WorkOrder dbObject = await WorkOrderGetAsync(id, false); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } WorkOrder newObject = new WorkOrder(); CopyObject.Copy(dbObject, newObject, "Wiki,Serial"); newObject.Id = 0; newObject.Concurrency = 0; await ct.WorkOrder.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); await WorkOrderSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task WorkOrderGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrder .Include(w => w.Items) .ThenInclude(wi => wi.Expenses) .Include(w => w.Items) .ThenInclude(wi => wi.Labors) .Include(w => w.Items) .ThenInclude(wi => wi.Loans) .Include(w => w.Items) .ThenInclude(wi => wi.Parts) .Include(w => w.Items) .ThenInclude(wi => wi.PartRequests) .Include(w => w.Items) .ThenInclude(wi => wi.ScheduledUsers) .Include(w => w.Items) .ThenInclude(wi => wi.Tasks) .Include(w => w.Items) .ThenInclude(wi => wi.Travels) .Include(w => w.Items) .ThenInclude(wi => wi.Units) .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task WorkOrderPutAsync(dtWorkOrder dtPutObject) { WorkOrder dbObject = await ct.WorkOrder.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrder SnapshotOfOriginalDBObj = new WorkOrder(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); //Replace the db object with the PUT object CopyObject.Copy(dtPutObject, dbObject, "Id"); //if user has rights then change it, otherwise just ignore it and do the rest if (dtPutObject.Serial != 0 && SnapshotOfOriginalDBObj.Serial != dtPutObject.Serial && Authorized.HasAnyRole(CurrentUserRoles, RolesAllowedToChangeSerial)) { dbObject.Serial = dtPutObject.Serial; } dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await WorkOrderValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await WorkOrderExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); await WorkOrderSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task WorkOrderDeleteAsync(long id) { using (var transaction = await ct.Database.BeginTransactionAsync()) { try { WorkOrder dbObject = await ct.WorkOrder.SingleOrDefaultAsync(z => z.Id == id); WorkOrderValidateCanDelete(dbObject); if (HasErrors) return false; //collect the child id's to delete var ItemIds = await ct.WorkOrderItem.Where(z => z.WorkOrderId == id).Select(z => z.Id).ToListAsync(); //Delete children foreach (long ItemId in ItemIds) if (!await ItemDeleteAsync(ItemId, transaction)) return false; ct.WorkOrder.Remove(dbObject); await ct.SaveChangesAsync(); await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Serial.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); #if (DEBUG) if (dbObject.Wiki == "INTEGRATION_DELETE_TEST_FAIL_BEFORE_COMMIT") { // await transaction.RollbackAsync(); return false;//thus not committing transaction } #endif await transaction.CommitAsync(); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); } catch { //Just re-throw for now, let exception handler deal, but in future may want to deal with this more here throw; } return true; } } //////////////////////////////////////////////////////////////////////////////////////////////// //RESTART SERIAL // internal async Task RestartSerial(long newSerial) { using (var command = ct.Database.GetDbConnection().CreateCommand()) { command.CommandText = $"alter table aworkorder alter column serial restart with {newSerial}"; await ct.Database.OpenConnectionAsync(); await command.ExecuteNonQueryAsync(); await ct.Database.CloseConnectionAsync(); } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 0, BizType, AyaEvent.ResetSerial, newSerial.ToString()), ct); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //GET ANCESTOR TYPE AND ID // internal static async Task GetAncestor(AyaType ayaType, long id, AyContext ct) { long woitemid = 0; switch (ayaType) { case AyaType.WorkOrderItem: woitemid = id; break; case AyaType.WorkOrderItemExpense: woitemid = await ct.WorkOrderItemExpense.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; case AyaType.WorkOrderItemLabor: woitemid = await ct.WorkOrderItemLabor.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; case AyaType.WorkOrderItemLoan: woitemid = await ct.WorkOrderItemLoan.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; case AyaType.WorkOrderItemPart: woitemid = await ct.WorkOrderItemPart.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; case AyaType.WorkOrderItemPartRequest: woitemid = await ct.WorkOrderItemPartRequest.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; case AyaType.WorkOrderItemScheduledUser: woitemid = await ct.WorkOrderItemScheduledUser.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; case AyaType.WorkOrderItemTask: woitemid = await ct.WorkOrderItemTask.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; case AyaType.WorkOrderItemTravel: woitemid = await ct.WorkOrderItemTravel.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync(); break; default: throw new System.NotSupportedException($"WorkOrderBiz::GetAncestor -> AyaType {ayaType.ToString()} is not supported"); } return new AyaTypeId(AyaType.WorkOrder, await ct.WorkOrderItem.Where(z => z.Id == woitemid).Select(z => z.WorkOrderId).SingleOrDefaultAsync()); } //////////////////////////////////////////////////////////////////////////////////////////////// //SEARCH // private async Task WorkOrderSearchIndexAsync(WorkOrder obj, bool isNew) { 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(z => z.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; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // //Can save or update? private async Task WorkOrderValidateAsync(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(z => z.Name == proposedObj.Name && z.Id != proposedObj.Id)) // { // AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); // } // } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.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); } } private void WorkOrderValidateCanDelete(WorkOrder dbObject) { //whatever needs to be check to delete this object } #endregion workorder level /* ██╗████████╗███████╗███╗ ███╗███████╗ ██║╚══██╔══╝██╔════╝████╗ ████║██╔════╝ ██║ ██║ █████╗ ██╔████╔██║███████╗ ██║ ██║ ██╔══╝ ██║╚██╔╝██║╚════██║ ██║ ██║ ███████╗██║ ╚═╝ ██║███████║ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ */ #region WorkOrderItem level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ItemExistsAsync(long id) { return await ct.WorkOrderItem.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task ItemCreateAsync(dtWorkOrderItem dtNewObject) { WorkOrderItem newObject = new WorkOrderItem(); CopyObject.Copy(dtNewObject, newObject); await ItemValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItem.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItem, AyaEvent.Created), ct); await ItemSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task ItemGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItem .Include(wi => wi.Expenses) .Include(wi => wi.Labors) .Include(wi => wi.Loans) .Include(wi => wi.Parts) .Include(wi => wi.PartRequests) .Include(wi => wi.ScheduledUsers) .Include(wi => wi.Tasks) .Include(wi => wi.Travels) .Include(wi => wi.Units) .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItem, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task ItemPutAsync(dtWorkOrderItem dtPutObject) { WorkOrderItem dbObject = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItem SnapshotOfOriginalDBObj = new WorkOrderItem(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); //Replace the db object with the PUT object CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await ItemValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await ItemExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItem, AyaEvent.Modified), ct); await ItemSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task ItemDeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null) { Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null; if (parentTransaction == null) transaction = await ct.Database.BeginTransactionAsync(); try { WorkOrderItem dbObject = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == id); ItemValidateCanDelete(dbObject); if (HasErrors) return false; //collect the child id's to delete var ExpenseIds = await ct.WorkOrderItemExpense.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var LaborIds = await ct.WorkOrderItemLabor.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var LoanIds = await ct.WorkOrderItemLoan.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var PartIds = await ct.WorkOrderItemPart.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var PartRequestIds = await ct.WorkOrderItemPartRequest.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var ScheduledUserIds = await ct.WorkOrderItemScheduledUser.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var TaskIds = await ct.WorkOrderItemTask.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var TravelIds = await ct.WorkOrderItemTravel.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); var UnitIds = await ct.WorkOrderItemUnit.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync(); //Delete children foreach (long ItemId in ExpenseIds) if (!await ExpenseDeleteAsync(ItemId)) return false; foreach (long ItemId in LaborIds) if (!await LaborDeleteAsync(ItemId)) return false; foreach (long ItemId in LoanIds) if (!await LoanDeleteAsync(ItemId)) return false; foreach (long ItemId in PartIds) if (!await PartDeleteAsync(ItemId)) return false; foreach (long ItemId in PartRequestIds) if (!await PartRequestDeleteAsync(ItemId)) return false; foreach (long ItemId in ScheduledUserIds) if (!await ScheduledUserDeleteAsync(ItemId)) return false; foreach (long ItemId in TaskIds) if (!await TaskDeleteAsync(ItemId)) return false; foreach (long ItemId in TravelIds) if (!await TravelDeleteAsync(ItemId)) return false; foreach (long ItemId in UnitIds) if (!await UnitDeleteAsync(ItemId)) return false; ct.WorkOrderItem.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItem, dbObject.Id, "wo:" + dbObject.WorkOrderId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItem, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); //all good do the commit if it's ours if (parentTransaction == null) await transaction.CommitAsync(); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); } catch { //Just re-throw for now, let exception handler deal, but in future may want to deal with this more here throw; } finally { if (parentTransaction == null) await transaction.DisposeAsync(); } return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task ItemValidateAsync(WorkOrderItem proposedObj, WorkOrderItem currentObj) { //run validation and biz rules bool isNew = currentObj == null; //does it have a valid workorder id if (proposedObj.WorkOrderId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderId"); else if (!await WorkOrderExistsAsync(proposedObj.WorkOrderId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItem.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void ItemValidateCanDelete(WorkOrderItem obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItem)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } private async Task ItemSearchIndexAsync(WorkOrderItem obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItem); SearchParams.AddText(obj.Notes).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task ItemGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item level /* ███████╗██╗ ██╗██████╗ ███████╗███╗ ██╗███████╗███████╗███████╗ ██╔════╝╚██╗██╔╝██╔══██╗██╔════╝████╗ ██║██╔════╝██╔════╝██╔════╝ █████╗ ╚███╔╝ ██████╔╝█████╗ ██╔██╗ ██║███████╗█████╗ ███████╗ ██╔══╝ ██╔██╗ ██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██╔══╝ ╚════██║ ███████╗██╔╝ ██╗██║ ███████╗██║ ╚████║███████║███████╗███████║ ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝╚══════╝╚══════╝ */ #region WorkOrderItemExpense level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExpenseExistsAsync(long id) { return await ct.WorkOrderItemExpense.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task ExpenseCreateAsync(WorkOrderItemExpense newObject) { await ExpenseValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemExpense.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemExpense, AyaEvent.Created), ct); await ExpenseSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task ExpenseGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemExpense .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemExpense, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task ExpensePutAsync(WorkOrderItemExpense dtPutObject) { WorkOrderItemExpense dbObject = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemExpense SnapshotOfOriginalDBObj = new WorkOrderItemExpense(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await ExpenseValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await ExpenseExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemExpense, AyaEvent.Modified), ct); await ExpenseSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task ExpenseDeleteAsync(long id) { WorkOrderItemExpense dbObject = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == id); ExpenseValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemExpense.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemExpense, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemExpense, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task ExpenseValidateAsync(WorkOrderItemExpense proposedObj, WorkOrderItemExpense currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemExpense.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void ExpenseValidateCanDelete(WorkOrderItemExpense obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemExpense)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task ExpenseSearchIndexAsync(WorkOrderItemExpense obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemExpense); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task ExpenseGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ██╗ █████╗ ██████╗ ██████╗ ██████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ ██║ ███████║██████╔╝██║ ██║██████╔╝ ██║ ██╔══██║██╔══██╗██║ ██║██╔══██╗ ███████╗██║ ██║██████╔╝╚██████╔╝██║ ██║ ╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ */ #region WorkOrderItemLabor level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task LaborExistsAsync(long id) { return await ct.WorkOrderItemLabor.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task LaborCreateAsync(WorkOrderItemLabor newObject) { await LaborValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemLabor.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemLabor, AyaEvent.Created), ct); await LaborSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task LaborGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemLabor .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemLabor, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task LaborPutAsync(WorkOrderItemLabor dtPutObject) { WorkOrderItemLabor dbObject = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemLabor SnapshotOfOriginalDBObj = new WorkOrderItemLabor(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await LaborValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await LaborExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemLabor, AyaEvent.Modified), ct); await LaborSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task LaborDeleteAsync(long id) { WorkOrderItemLabor dbObject = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(z => z.Id == id); LaborValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemLabor.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemLabor, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemLabor, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task LaborValidateAsync(WorkOrderItemLabor proposedObj, WorkOrderItemLabor currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemLabor.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void LaborValidateCanDelete(WorkOrderItemLabor obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemLabor)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task LaborSearchIndexAsync(WorkOrderItemLabor obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemLabor); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task LaborGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ██╗ ██████╗ █████╗ ███╗ ██╗ ██║ ██╔═══██╗██╔══██╗████╗ ██║ ██║ ██║ ██║███████║██╔██╗ ██║ ██║ ██║ ██║██╔══██║██║╚██╗██║ ███████╗╚██████╔╝██║ ██║██║ ╚████║ */ #region WorkOrderItemLoan level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task LoanExistsAsync(long id) { return await ct.WorkOrderItemLoan.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task LoanCreateAsync(WorkOrderItemLoan newObject) { await LoanValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemLoan.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemLoan, AyaEvent.Created), ct); await LoanSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task LoanGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemLoan .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemLoan, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task LoanPutAsync(WorkOrderItemLoan dtPutObject) { WorkOrderItemLoan dbObject = await ct.WorkOrderItemLoan.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemLoan SnapshotOfOriginalDBObj = new WorkOrderItemLoan(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await LoanValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await LoanExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemLoan, AyaEvent.Modified), ct); await LoanSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task LoanDeleteAsync(long id) { WorkOrderItemLoan dbObject = await ct.WorkOrderItemLoan.SingleOrDefaultAsync(z => z.Id == id); LoanValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemLoan.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemLoan, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemLoan, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task LoanValidateAsync(WorkOrderItemLoan proposedObj, WorkOrderItemLoan currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemLoan.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void LoanValidateCanDelete(WorkOrderItemLoan obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemLoan)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task LoanSearchIndexAsync(WorkOrderItemLoan obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemLoan); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task LoanGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemLoan.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ██████╗ █████╗ ██████╗ ████████╗███████╗ ██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝ ██████╔╝███████║██████╔╝ ██║ ███████╗ ██╔═══╝ ██╔══██║██╔══██╗ ██║ ╚════██║ ██║ ██║ ██║██║ ██║ ██║ ███████║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ */ #region WorkOrderItemPart level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task PartExistsAsync(long id) { return await ct.WorkOrderItemPart.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task CreatePartAsync(WorkOrderItemPart newObject) { await PartValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemPart.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemPart, AyaEvent.Created), ct); await PartSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task PartGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemPart .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemPart, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task PartPutAsync(WorkOrderItemPart dtPutObject) { WorkOrderItemPart dbObject = await ct.WorkOrderItemPart.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemPart SnapshotOfOriginalDBObj = new WorkOrderItemPart(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await PartValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await PartExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemPart, AyaEvent.Modified), ct); await PartSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task PartDeleteAsync(long id) { WorkOrderItemPart dbObject = await ct.WorkOrderItemPart.SingleOrDefaultAsync(z => z.Id == id); PartValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemPart.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemPart, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemPart, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task PartValidateAsync(WorkOrderItemPart proposedObj, WorkOrderItemPart currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemPart.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void PartValidateCanDelete(WorkOrderItemPart obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemPart)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } private async Task PartSearchIndexAsync(WorkOrderItemPart obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemPart); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task PartGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemPart.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ██████╗ █████╗ ██████╗ ████████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗███████╗███████╗████████╗███████╗ ██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝ ██╔══██╗██╔════╝██╔═══██╗██║ ██║██╔════╝██╔════╝╚══██╔══╝██╔════╝ ██████╔╝███████║██████╔╝ ██║█████╗██████╔╝█████╗ ██║ ██║██║ ██║█████╗ ███████╗ ██║ ███████╗ ██╔═══╝ ██╔══██║██╔══██╗ ██║╚════╝██╔══██╗██╔══╝ ██║▄▄ ██║██║ ██║██╔══╝ ╚════██║ ██║ ╚════██║ ██║ ██║ ██║██║ ██║ ██║ ██║ ██║███████╗╚██████╔╝╚██████╔╝███████╗███████║ ██║ ███████║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝ */ #region WorkOrderItemPartRequest level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task PartRequestExistsAsync(long id) { return await ct.WorkOrderItemPartRequest.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task PartRequestCreateAsync(WorkOrderItemPartRequest newObject) { await PartRequestValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemPartRequest.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemPartRequest, AyaEvent.Created), ct); await PartRequestSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task PartRequestGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemPartRequest .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemPartRequest, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task PartRequestPutAsync(WorkOrderItemPartRequest dtPutObject) { WorkOrderItemPartRequest dbObject = await ct.WorkOrderItemPartRequest.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemPartRequest SnapshotOfOriginalDBObj = new WorkOrderItemPartRequest(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await PartRequestValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await PartRequestExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemPartRequest, AyaEvent.Modified), ct); await PartRequestSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task PartRequestDeleteAsync(long id) { WorkOrderItemPartRequest dbObject = await ct.WorkOrderItemPartRequest.SingleOrDefaultAsync(z => z.Id == id); PartRequestValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemPartRequest.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemPartRequest, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemPartRequest, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task PartRequestValidateAsync(WorkOrderItemPartRequest proposedObj, WorkOrderItemPartRequest currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemPartRequest.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void PartRequestValidateCanDelete(WorkOrderItemPartRequest obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemPartRequest)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task PartRequestSearchIndexAsync(WorkOrderItemPartRequest obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemPartRequest); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task PartRequestGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemPartRequest.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ███████╗ ██████╗██╗ ██╗███████╗██████╗ ██╗ ██╗██╗ ███████╗██████╗ ██╗ ██╗███████╗███████╗██████╗ ███████╗ ██╔════╝██╔════╝██║ ██║██╔════╝██╔══██╗██║ ██║██║ ██╔════╝██╔══██╗ ██║ ██║██╔════╝██╔════╝██╔══██╗██╔════╝ ███████╗██║ ███████║█████╗ ██║ ██║██║ ██║██║ █████╗ ██║ ██║█████╗██║ ██║███████╗█████╗ ██████╔╝███████╗ ╚════██║██║ ██╔══██║██╔══╝ ██║ ██║██║ ██║██║ ██╔══╝ ██║ ██║╚════╝██║ ██║╚════██║██╔══╝ ██╔══██╗╚════██║ ███████║╚██████╗██║ ██║███████╗██████╔╝╚██████╔╝███████╗███████╗██████╔╝ ╚██████╔╝███████║███████╗██║ ██║███████║ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝ */ #region WorkOrderItemScheduledUser level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ScheduledUserExistsAsync(long id) { return await ct.WorkOrderItemScheduledUser.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task ScheduledUserCreateAsync(WorkOrderItemScheduledUser newObject) { await ScheduledUserValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemScheduledUser.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemScheduledUser, AyaEvent.Created), ct); await ScheduledUserSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task ScheduledUserGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemScheduledUser .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemScheduledUser, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task ScheduledUserPutAsync(WorkOrderItemScheduledUser dtPutObject) { WorkOrderItemScheduledUser dbObject = await ct.WorkOrderItemScheduledUser.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemScheduledUser SnapshotOfOriginalDBObj = new WorkOrderItemScheduledUser(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await ScheduledUserValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await ScheduledUserExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemScheduledUser, AyaEvent.Modified), ct); await ScheduledUserSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task ScheduledUserDeleteAsync(long id) { WorkOrderItemScheduledUser dbObject = await ct.WorkOrderItemScheduledUser.SingleOrDefaultAsync(z => z.Id == id); ScheduledUserValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemScheduledUser.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemScheduledUser, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemScheduledUser, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task ScheduledUserValidateAsync(WorkOrderItemScheduledUser proposedObj, WorkOrderItemScheduledUser currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemScheduledUser.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void ScheduledUserValidateCanDelete(WorkOrderItemScheduledUser obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemScheduledUser)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task ScheduledUserSearchIndexAsync(WorkOrderItemScheduledUser obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemScheduledUser); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task ScheduledUserGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemScheduledUser.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ████████╗ █████╗ ███████╗██╗ ██╗ ╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ ██║ ███████║███████╗█████╔╝ ██║ ██╔══██║╚════██║██╔═██╗ ██║ ██║ ██║███████║██║ ██╗ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ */ #region WorkOrderItemTask level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task TaskExistsAsync(long id) { return await ct.WorkOrderItemTask.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task TaskCreateAsync(WorkOrderItemTask newObject) { await TaskValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemTask.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemTask, AyaEvent.Created), ct); await TaskSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task TaskGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemTask .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemTask, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task TaskPutAsync(WorkOrderItemTask dtPutObject) { WorkOrderItemTask dbObject = await ct.WorkOrderItemTask.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemTask SnapshotOfOriginalDBObj = new WorkOrderItemTask(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await TaskValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await TaskExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemTask, AyaEvent.Modified), ct); await TaskSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task TaskDeleteAsync(long id) { WorkOrderItemTask dbObject = await ct.WorkOrderItemTask.SingleOrDefaultAsync(z => z.Id == id); TaskValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemTask.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemTask, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemTask, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task TaskValidateAsync(WorkOrderItemTask proposedObj, WorkOrderItemTask currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemTask.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void TaskValidateCanDelete(WorkOrderItemTask obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemTask)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task TaskSearchIndexAsync(WorkOrderItemTask obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemTask); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task TaskGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemTask.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ████████╗██████╗ █████╗ ██╗ ██╗███████╗██╗ ╚══██╔══╝██╔══██╗██╔══██╗██║ ██║██╔════╝██║ ██║ ██████╔╝███████║██║ ██║█████╗ ██║ ██║ ██╔══██╗██╔══██║╚██╗ ██╔╝██╔══╝ ██║ ██║ ██║ ██║██║ ██║ ╚████╔╝ ███████╗███████╗ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚══════╝ */ #region WorkOrderItemTravel level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task TravelExistsAsync(long id) { return await ct.WorkOrderItemTravel.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task TravelCreateAsync(WorkOrderItemTravel newObject) { await TravelValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemTravel.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemTravel, AyaEvent.Created), ct); await TravelSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task TravelGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemTravel .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemTravel, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task TravelPutAsync(WorkOrderItemTravel dtPutObject) { WorkOrderItemTravel dbObject = await ct.WorkOrderItemTravel.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemTravel SnapshotOfOriginalDBObj = new WorkOrderItemTravel(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await TravelValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await TravelExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemTravel, AyaEvent.Modified), ct); await TravelSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task TravelDeleteAsync(long id) { WorkOrderItemTravel dbObject = await ct.WorkOrderItemTravel.SingleOrDefaultAsync(z => z.Id == id); TravelValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemTravel.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemTravel, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemTravel, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task TravelValidateAsync(WorkOrderItemTravel proposedObj, WorkOrderItemTravel currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemTravel.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void TravelValidateCanDelete(WorkOrderItemTravel obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemTravel)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task TravelSearchIndexAsync(WorkOrderItemTravel obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemTravel); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task TravelGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemTravel.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level /* ██╗ ██╗███╗ ██╗██╗████████╗ ██║ ██║████╗ ██║██║╚══██╔══╝ ██║ ██║██╔██╗ ██║██║ ██║ ██║ ██║██║╚██╗██║██║ ██║ ╚██████╔╝██║ ╚████║██║ ██║ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ */ #region WorkOrderItemUnit level //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task UnitExistsAsync(long id) { return await ct.WorkOrderItemUnit.AnyAsync(z => z.Id == id); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE // internal async Task UnitCreateAsync(WorkOrderItemUnit newObject) { await UnitValidateAsync(newObject, null); if (HasErrors) return null; else { newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemUnit.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemUnit, AyaEvent.Created), ct); await UnitSearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task UnitGetAsync(long id, bool logTheGetEvent = true) { //Note: there could be rules checking here in future, i.e. can only get own workorder or something //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now //https://docs.microsoft.com/en-us/ef/core/querying/related-data //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections var ret = await ct.WorkOrderItemUnit .SingleOrDefaultAsync(z => z.Id == id); if (logTheGetEvent && ret != null) await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemUnit, AyaEvent.Retrieved), ct); return ret; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // internal async Task UnitPutAsync(WorkOrderItemUnit dtPutObject) { WorkOrderItemUnit dbObject = await ct.WorkOrderItemUnit.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return null; } // make a snapshot of the original for validation but update the original to preserve workflow WorkOrderItemUnit SnapshotOfOriginalDBObj = new WorkOrderItemUnit(); CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); CopyObject.Copy(dtPutObject, dbObject, "Id"); dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags); dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency; await UnitValidateAsync(dbObject, SnapshotOfOriginalDBObj); if (HasErrors) return null; try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await UnitExistsAsync(dtPutObject.Id)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemUnit, AyaEvent.Modified), ct); await UnitSearchIndexAsync(dbObject, false); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj); return dbObject; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal async Task UnitDeleteAsync(long id) { WorkOrderItemUnit dbObject = await ct.WorkOrderItemUnit.SingleOrDefaultAsync(z => z.Id == id); UnitValidateCanDelete(dbObject); if (HasErrors) return false; ct.WorkOrderItemUnit.Remove(dbObject); await ct.SaveChangesAsync(); //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemUnit, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemUnit, ct); await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // private async Task UnitValidateAsync(WorkOrderItemUnit proposedObj, WorkOrderItemUnit currentObj) { //run validation and biz rules bool isNew = currentObj == null; if (proposedObj.WorkOrderItemId == 0) AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId"); else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemUnit.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);//note: this is passed only to add errors //validate custom fields CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); } } private void UnitValidateCanDelete(WorkOrderItemUnit obj) { if (obj == null) { AddError(ApiErrorCode.NOT_FOUND, "id"); return; } //re-check rights here necessary due to traversal delete from Principle object if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemUnit)) { AddError(ApiErrorCode.NOT_AUTHORIZED); return; } } ////////////////////////////////////////////// //INDEXING // private async Task UnitSearchIndexAsync(WorkOrderItemUnit obj, bool isNew) { //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemUnit); SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); } public async Task UnitGetSearchResultSummary(long id) { var obj = await ct.WorkOrderItemUnit.SingleOrDefaultAsync(z => z.Id == id); var SearchParams = new Search.SearchIndexProcessObjectParameters(); if (obj != null) SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields); return SearchParams; } #endregion work order item LABOR level #region Utility public async Task GetWorkOrderGraphItem(AyaType ayaType, long id) { switch (ayaType) { case AyaType.WorkOrder: return await WorkOrderGetAsync(id, false) as ICoreBizObjectModel; case AyaType.WorkOrderItem: return await ItemGetAsync(id, false); case AyaType.WorkOrderItemExpense: return await ExpenseGetAsync(id, false); case AyaType.WorkOrderItemLabor: return await LaborGetAsync(id, false); case AyaType.WorkOrderItemLoan: return await LoanGetAsync(id, false); case AyaType.WorkOrderItemPart: return await PartGetAsync(id, false); case AyaType.WorkOrderItemPartRequest: return await PartRequestGetAsync(id, false); case AyaType.WorkOrderItemScheduledUser: return await ScheduledUserGetAsync(id, false); case AyaType.WorkOrderItemTask: return await TaskGetAsync(id, false); case AyaType.WorkOrderItemTravel: return await TravelGetAsync(id, false); case AyaType.WorkOrderItemUnit: return await UnitGetAsync(id, false); default: throw new System.ArgumentOutOfRangeException($"WorkOrder::GetWorkOrderGraphItem -> Invalid ayaType{ayaType}"); } } public async Task PutWorkOrderGraphItem(AyaType ayaType, ICoreBizObjectModel o) { ClearErrors(); switch (ayaType) { case AyaType.WorkOrder: if (o is WorkOrder) { dtWorkOrder dto = new dtWorkOrder(); CopyObject.Copy(o, dto); return await WorkOrderPutAsync((dtWorkOrder)dto); } return await WorkOrderPutAsync((dtWorkOrder)o) as ICoreBizObjectModel; case AyaType.WorkOrderItem: if (o is WorkOrderItem) { dtWorkOrderItem dto = new dtWorkOrderItem(); CopyObject.Copy(o, dto); return await ItemPutAsync((dtWorkOrderItem)dto); } return await ItemPutAsync((dtWorkOrderItem)o); case AyaType.WorkOrderItemExpense: return await ExpensePutAsync((WorkOrderItemExpense)o); case AyaType.WorkOrderItemLabor: return await LaborPutAsync((WorkOrderItemLabor)o); case AyaType.WorkOrderItemLoan: return await LoanPutAsync((WorkOrderItemLoan)o); case AyaType.WorkOrderItemPart: return await PartPutAsync((WorkOrderItemPart)o); case AyaType.WorkOrderItemPartRequest: return await PartRequestPutAsync((WorkOrderItemPartRequest)o); case AyaType.WorkOrderItemScheduledUser: return await ScheduledUserPutAsync((WorkOrderItemScheduledUser)o); case AyaType.WorkOrderItemTask: return await TaskPutAsync((WorkOrderItemTask)o); case AyaType.WorkOrderItemTravel: return await TravelPutAsync((WorkOrderItemTravel)o); case AyaType.WorkOrderItemUnit: return await UnitPutAsync((WorkOrderItemUnit)o); default: throw new System.ArgumentOutOfRangeException($"WorkOrder::PutWorkOrderGraphItem -> Invalid ayaType{ayaType}"); } } #endregion utility //////////////////////////////////////////////////////////////////////////////////////////////// //JOB / OPERATIONS // public async Task HandleJobAsync(OpsJob job) { switch (job.JobType) { case JobType.BulkCoreBizObjectOperation: await ProcessBulkJobAsync(job); break; default: throw new System.ArgumentOutOfRangeException($"WorkOrder.HandleJob-> Invalid job type{job.JobType.ToString()}"); } } private async Task ProcessBulkJobAsync(OpsJob job) { await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); await JobsBiz.LogJobAsync(job.GId, $"Bulk job {job.SubType} started..."); List idList = new List(); long ProcessedObjectCount = 0; JObject jobData = JObject.Parse(job.JobInfo); if (jobData.ContainsKey("idList")) idList = ((JArray)jobData["idList"]).ToObject>(); else idList = await ct.Widget.Select(z => z.Id).ToListAsync(); bool SaveIt = false; foreach (long id in idList) { try { SaveIt = false; ClearErrors(); ICoreBizObjectModel o = await GetWorkOrderGraphItem(job.ObjectType, id); switch (job.SubType) { case JobSubType.TagAddAny: case JobSubType.TagAdd: case JobSubType.TagRemoveAny: case JobSubType.TagRemove: case JobSubType.TagReplaceAny: case JobSubType.TagReplace: SaveIt = TagBiz.ProcessBulkTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); break; default: throw new System.ArgumentOutOfRangeException($"ProcessBulkJob -> Invalid job Subtype{job.SubType}"); } if (SaveIt) { o = await PutWorkOrderGraphItem(job.ObjectType, o); if (o == null) await JobsBiz.LogJobAsync(job.GId, $"Error processing item {id}: {GetErrorsAsString()}"); else ProcessedObjectCount++; } else ProcessedObjectCount++; } catch (Exception ex) { await JobsBiz.LogJobAsync(job.GId, $"Error processing item {id}"); await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); } } await JobsBiz.LogJobAsync(job.GId, $"Bulk job {job.SubType} processed {ProcessedObjectCount} of {idList.Count}"); await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); } ///////////////////////////////////////////////////////////////////// }//eoc }//eons