diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 33450027..3ec0cded 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -32,17 +32,14 @@ namespace AyaNova.Biz return new WorkOrderBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - /* - - ██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗ - ██║ ██║██╔═══██╗██╔══██╗██║ ██╔╝ ██╔═══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗ - ██║ █╗ ██║██║ ██║██████╔╝█████╔╝█████╗██║ ██║██████╔╝██║ ██║█████╗ ██████╔╝ - ██║███╗██║██║ ██║██╔══██╗██╔═██╗╚════╝██║ ██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ - ╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗ ╚██████╔╝██║ ██║██████╔╝███████╗██║ ██║ - ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝ - - - */ +/* + ██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗ + ██║ ██║██╔═══██╗██╔══██╗██║ ██╔╝ ██╔═══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗ + ██║ █╗ ██║██║ ██║██████╔╝█████╔╝█████╗██║ ██║██████╔╝██║ ██║█████╗ ██████╔╝ + ██║███╗██║██║ ██║██╔══██╗██╔═██╗╚════╝██║ ██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ + ╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗ ╚██████╔╝██║ ██║██████╔╝███████╗██║ ██║ + ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝ +*/ #region WorkOrder level @@ -307,17 +304,15 @@ namespace AyaNova.Biz } #endregion workorder level - /* - ██╗████████╗███████╗███╗ ███╗███████╗ - ██║╚══██╔══╝██╔════╝████╗ ████║██╔════╝ - ██║ ██║ █████╗ ██╔████╔██║███████╗ - ██║ ██║ ██╔══╝ ██║╚██╔╝██║╚════██║ - ██║ ██║ ███████╗██║ ╚═╝ ██║███████║ - ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ - - - */ +/* + ██╗████████╗███████╗███╗ ███╗███████╗ + ██║╚══██╔══╝██╔════╝████╗ ████║██╔════╝ + ██║ ██║ █████╗ ██╔████╔██║███████╗ + ██║ ██║ ██╔══╝ ██║╚██╔╝██║╚════██║ + ██║ ██║ ███████╗██║ ╚═╝ ██║███████║ + ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ +*/ #region WorkOrderItem level //////////////////////////////////////////////////////////////////////////////////////////////// @@ -545,16 +540,204 @@ namespace AyaNova.Biz } #endregion work order item level - /* - ██╗ █████╗ ██████╗ ██████╗ ██████╗ - ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ - ██║ ███████║██████╔╝██║ ██║██████╔╝ - ██║ ██╔══██║██╔══██╗██║ ██║██╔══██╗ - ███████╗██║ ██║██████╔╝╚██████╔╝██║ ██║ - ╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ +/* + ███████╗██╗ ██╗██████╗ ███████╗███╗ ██╗███████╗███████╗███████╗ + ██╔════╝╚██╗██╔╝██╔══██╗██╔════╝████╗ ██║██╔════╝██╔════╝██╔════╝ + █████╗ ╚███╔╝ ██████╔╝█████╗ ██╔██╗ ██║███████╗█████╗ ███████╗ + ██╔══╝ ██╔██╗ ██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██╔══╝ ╚════██║ + ███████╗██╔╝ ██╗██║ ███████╗██║ ╚████║███████║███████╗███████║ + ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝╚══════╝╚══════╝ +*/ - */ + #region WorkOrderItemExpense level + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExpenseExistsAsync(long id) + { + return await ct.WorkOrderItemExpense.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task ExpenseCreateAsync(WorkOrderItemExpense newObject) + { + await ExpenseValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + 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(m => m.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(m => m.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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await ExpenseValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExpenseExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemExpense, AyaEvent.Modified), ct); + await ExpenseSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task ExpenseDeleteAsync(long id) + { + WorkOrderItemExpense dbObject = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(m => m.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); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(m => m.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 //////////////////////////////////////////////////////////////////////////////////////////////// @@ -735,16 +918,203 @@ namespace AyaNova.Biz } #endregion work order item LABOR level - /* - ██████╗ █████╗ ██████╗ ████████╗███████╗ - ██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝ - ██████╔╝███████║██████╔╝ ██║ ███████╗ - ██╔═══╝ ██╔══██║██╔══██╗ ██║ ╚════██║ - ██║ ██║ ██║██║ ██║ ██║ ███████║ - ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ +/* + ██╗ ██████╗ █████╗ ███╗ ██╗ + ██║ ██╔═══██╗██╔══██╗████╗ ██║ + ██║ ██║ ██║███████║██╔██╗ ██║ + ██║ ██║ ██║██╔══██║██║╚██╗██║ + ███████╗╚██████╔╝██║ ██║██║ ╚████║ +*/ - */ + #region WorkOrderItemLoan level + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task LoanExistsAsync(long id) + { + return await ct.WorkOrderItemLoan.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task LoanCreateAsync(WorkOrderItemLoan newObject) + { + await LoanValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + 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(m => m.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(m => m.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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await LoanValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await LoanExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemLoan, AyaEvent.Modified), ct); + await LoanSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task LoanDeleteAsync(long id) + { + WorkOrderItemLoan dbObject = await ct.WorkOrderItemLoan.SingleOrDefaultAsync(m => m.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); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(m => m.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 //////////////////////////////////////////////////////////////////////////////////////////////// @@ -923,8 +1293,955 @@ namespace AyaNova.Biz #endregion work order item LABOR level +/* + ██████╗ █████╗ ██████╗ ████████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗███████╗███████╗████████╗███████╗ + ██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝ ██╔══██╗██╔════╝██╔═══██╗██║ ██║██╔════╝██╔════╝╚══██╔══╝██╔════╝ + ██████╔╝███████║██████╔╝ ██║█████╗██████╔╝█████╗ ██║ ██║██║ ██║█████╗ ███████╗ ██║ ███████╗ + ██╔═══╝ ██╔══██║██╔══██╗ ██║╚════╝██╔══██╗██╔══╝ ██║▄▄ ██║██║ ██║██╔══╝ ╚════██║ ██║ ╚════██║ + ██║ ██║ ██║██║ ██║ ██║ ██║ ██║███████╗╚██████╔╝╚██████╔╝███████╗███████║ ██║ ███████║ + ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝ +*/ + + #region WorkOrderItemPartRequest level + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task PartRequestExistsAsync(long id) + { + return await ct.WorkOrderItemPartRequest.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task PartRequestCreateAsync(WorkOrderItemPartRequest newObject) + { + await PartRequestValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + 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(m => m.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(m => m.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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await PartRequestValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await PartRequestExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemPartRequest, AyaEvent.Modified), ct); + await PartRequestSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task PartRequestDeleteAsync(long id) + { + WorkOrderItemPartRequest dbObject = await ct.WorkOrderItemPartRequest.SingleOrDefaultAsync(m => m.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); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(m => m.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(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task ScheduledUserCreateAsync(WorkOrderItemScheduledUser newObject) + { + await ScheduledUserValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + 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(m => m.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(m => m.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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await ScheduledUserValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ScheduledUserExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemScheduledUser, AyaEvent.Modified), ct); + await ScheduledUserSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task ScheduledUserDeleteAsync(long id) + { + WorkOrderItemScheduledUser dbObject = await ct.WorkOrderItemScheduledUser.SingleOrDefaultAsync(m => m.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); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(m => m.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(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task TaskCreateAsync(WorkOrderItemTask newObject) + { + await TaskValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + 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(m => m.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(m => m.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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await TaskValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await TaskExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemTask, AyaEvent.Modified), ct); + await TaskSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task TaskDeleteAsync(long id) + { + WorkOrderItemTask dbObject = await ct.WorkOrderItemTask.SingleOrDefaultAsync(m => m.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); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(m => m.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(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task TravelCreateAsync(WorkOrderItemTravel newObject) + { + await TravelValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + 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(m => m.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(m => m.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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await TravelValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await TravelExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemTravel, AyaEvent.Modified), ct); + await TravelSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task TravelDeleteAsync(long id) + { + WorkOrderItemTravel dbObject = await ct.WorkOrderItemTravel.SingleOrDefaultAsync(m => m.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); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(m => m.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(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task UnitCreateAsync(WorkOrderItemUnit newObject) + { + await UnitValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + 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(m => m.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(m => m.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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await UnitValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await UnitExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemUnit, AyaEvent.Modified), ct); + await UnitSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task UnitDeleteAsync(long id) + { + WorkOrderItemUnit dbObject = await ct.WorkOrderItemUnit.SingleOrDefaultAsync(m => m.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); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(m => m.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 + + + + + - //////////////////////////////////////////////////////////////////////////////////////////////// //JOB / OPERATIONS