diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 575af416..b23829ca 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -379,37 +379,30 @@ namespace AyaNova.Biz // internal async Task ItemDeleteAsync(long id) { - WorkOrderItem dbObject = await ct.WorkOrderItem - .SingleOrDefaultAsync(m => m.Id == id); + WorkOrderItem dbObject = await ct.WorkOrderItem.SingleOrDefaultAsync(m => m.Id == id); ItemValidateCanDelete(dbObject); if (HasErrors) return false; - //Traverse and delete tree - //maybe need to get tree list of id's then work with that - //a static function to get all id's from object and below? - //https://docs.microsoft.com/en-us/ef/core/saving/transactions + //collect the child id's to delete + var LaborIds = await ct.WorkOrderItemLabor.Where(m => m.WorkOrderItemId == id).Select(m => m.Id).ToListAsync(); + var PartIds = await ct.WorkOrderItemPart.Where(m => m.WorkOrderItemId == id).Select(m => m.Id).ToListAsync(); - WorkOrderItem dbObject = await ct.WorkOrderItem - .Include(wi => wi.Labors) - .Include(wi => wi.Parts) - .SingleOrDefaultAsync(m => m.Id == id); - var LaborIds = dbObject.Labors.Select(m => m.Id).ToList(); - var PartIds = dbObject.Parts.Select(m => m.Id).ToList(); - using (var transaction = ct.Database.BeginTransaction()) + + using (var transaction = await ct.Database.BeginTransactionAsync()) { try { //Delete children - foreach(long l in LaborIds){ - if(!await LaborDeleteAsync(id)) return false; + foreach (long l in LaborIds) + { + if (!await LaborDeleteAsync(id)) return false; } - foreach(long l in PartIds){ - if(!await LaborDeleteAsync(id)) return false; + foreach (long l in PartIds) + { + if (!await LaborDeleteAsync(id)) return false; } - - ct.WorkOrderItem.Remove(dbObject); await ct.SaveChangesAsync(); @@ -419,467 +412,468 @@ namespace AyaNova.Biz await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); //all good do the commit - transaction.Commit(); + await transaction.CommitAsync(); } catch { - //Just re-throw for now, but in future may want to deal with this more here + //Just re-throw for now, let exception handler deal, but in future may want to deal with this more here throw; } return true; } + } - //////////////////////////////////////////////////////////////////////////////////////////////// - //VALIDATION - // - private async Task ItemValidateAsync(WorkOrderItem proposedObj, WorkOrderItem currentObj) + //////////////////////////////////////////////////////////////////////////////////////////////// + //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)) { - //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(x => x.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); - } + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderId"); } - - private void ItemValidateCanDelete(WorkOrderItem obj) + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.WorkOrderItem.ToString()); + if (FormCustomization != null) { - if (obj == null) - { - AddError(ApiErrorCode.NOT_FOUND, "id"); - return; - } + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required - //re-check rights here necessary due to traversal delete from Principle object - if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItem)) - { - AddError(ApiErrorCode.NOT_AUTHORIZED); - return; - } + //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; } - - private async Task ItemSearchIndexAsync(WorkOrderItem obj, bool isNew) + //re-check rights here necessary due to traversal delete from Principle object + if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItem)) { - //SEARCH INDEXING - var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, 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(m => m.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 - if (isNew) - await Search.ProcessNewObjectKeywordsAsync(SearchParams); - else - await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); - } - public async Task ItemGetSearchResultSummary(long id) + + + + + + #region WorkOrderItemLabor level + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task LaborExistsAsync(long id) + { + return await ct.WorkOrderItemLabor.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task LaborCreateAsync(WorkOrderItemLabor newObject) + { + await LaborValidateAsync(newObject, null); + if (HasErrors) + return null; + else { - var obj = await ct.WorkOrderItem.SingleOrDefaultAsync(m => m.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 WorkOrderItemLabor level - //////////////////////////////////////////////////////////////////////////////////////////////// - //EXISTS - internal async Task LaborExistsAsync(long id) - { - return await ct.WorkOrderItemLabor.AnyAsync(e => e.Id == id); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - //CREATE - // - internal async Task LaborCreateAsync(WorkOrderItemLabor newObject) - { - await LaborValidateAsync(newObject, null); - if (HasErrors) - return null; - else - { - newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); - 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(m => m.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(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 - WorkOrderItemLabor SnapshotOfOriginalDBObj = new WorkOrderItemLabor(); - 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 LaborValidateAsync(dbObject, SnapshotOfOriginalDBObj); - if (HasErrors) return null; - try - { - await ct.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) - { - if (!await LaborExistsAsync(dtPutObject.Id)) - AddError(ApiErrorCode.NOT_FOUND); - else - new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); - return null; - } - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); - await LaborSearchIndexAsync(dbObject, false); - await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); - return dbObject; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task LaborDeleteAsync(long id) - { - WorkOrderItemLabor dbObject = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(m => m.Id == id); - LaborValidateCanDelete(dbObject); - if (HasErrors) - return false; - ct.WorkOrderItemLabor.Remove(dbObject); + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + return newObject; + } + } - //Log event - await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); - await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType); - await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); - return true; + //////////////////////////////////////////////////////////////////////////////////////////////// + // 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(m => m.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(m => m.Id == dtPutObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //VALIDATION - // - private async Task LaborValidateAsync(WorkOrderItemLabor proposedObj, WorkOrderItemLabor currentObj) + // 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 = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + + ct.Entry(dbObject).OriginalValues["ConcurrencyToken"] = dtPutObject.ConcurrencyToken; + await LaborValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try { - //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.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(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 - //////////////////////////////////////////////////////////////////////////////////////////////// - //EXISTS - internal async Task PartExistsAsync(long id) - { - return await ct.WorkOrderItemPart.AnyAsync(e => e.Id == id); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - //CREATE - // - internal async Task CreatePartAsync(WorkOrderItemPart newObject) - { - await PartValidateAsync(newObject, null); - if (HasErrors) - return null; - else - { - newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); - return newObject; - } - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - // GET - // - internal async Task GetPartAsync(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(m => m.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(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 - WorkOrderItemPart SnapshotOfOriginalDBObj = new WorkOrderItemPart(); - 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 PartValidateAsync(dbObject, SnapshotOfOriginalDBObj); - if (HasErrors) return null; - try - { - await ct.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) - { - if (!await PartExistsAsync(dtPutObject.Id)) - AddError(ApiErrorCode.NOT_FOUND); - else - new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); - return null; - } - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); - await PartSearchIndexAsync(dbObject, false); - await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); - return dbObject; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task PartDeleteAsync(long id) - { - WorkOrderItemPart dbObject = await ct.WorkOrderItemPart.SingleOrDefaultAsync(m => m.Id == id); - PartValidateCanDelete(dbObject); - if (HasErrors) - return false; - ct.WorkOrderItemPart.Remove(dbObject); await ct.SaveChangesAsync(); - - //Log event - await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); - await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType); - await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); - return true; } - - //////////////////////////////////////////////////////////////////////////////////////////////// - //VALIDATION - // - private async Task PartValidateAsync(WorkOrderItemPart proposedObj, WorkOrderItemPart currentObj) + catch (DbUpdateConcurrencyException) { - //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.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); + if (!await LaborExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); else - await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); + await LaborSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } - public async Task PartGetSearchResultSummary(long id) + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task LaborDeleteAsync(long id) + { + WorkOrderItemLabor dbObject = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(m => m.Id == id); + LaborValidateCanDelete(dbObject); + if (HasErrors) + return false; + ct.WorkOrderItemLabor.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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)) { - var obj = await ct.WorkOrderItemPart.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; + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId"); } - #endregion work order item LABOR level - //////////////////////////////////////////////////////////////////////////////////////////////// - //JOB / OPERATIONS - // + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.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); + } + } - //Other job handlers here... + 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); - }//eoc + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task LaborGetSearchResultSummary(long id) + { + var obj = await ct.WorkOrderItemLabor.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 - }//eons + + + + + + #region WorkOrderItemPart level + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task PartExistsAsync(long id) + { + return await ct.WorkOrderItemPart.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreatePartAsync(WorkOrderItemPart newObject) + { + await PartValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagUtil.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 TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + return newObject; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // GET + // + internal async Task GetPartAsync(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(m => m.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(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 + WorkOrderItemPart SnapshotOfOriginalDBObj = new WorkOrderItemPart(); + 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 PartValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await PartExistsAsync(dtPutObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); + await PartSearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task PartDeleteAsync(long id) + { + WorkOrderItemPart dbObject = await ct.WorkOrderItemPart.SingleOrDefaultAsync(m => m.Id == id); + PartValidateCanDelete(dbObject); + if (HasErrors) + return false; + ct.WorkOrderItemPart.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + 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(x => x.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(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 + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons