This commit is contained in:
2021-03-31 20:25:50 +00:00
parent ef5899d4bb
commit f1c3a6700f
2 changed files with 160 additions and 157 deletions

View File

@@ -138,7 +138,7 @@ namespace AyaNova.Biz
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct);
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, putObject.Id, BizType, AyaEvent.Modified), ct);
await SearchIndexAsync(putObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject);

View File

@@ -23,6 +23,8 @@ namespace AyaNova.Biz
I guess just implementing a very simple portion like from wo down to item down to a single grandchild makes the most sense
todo: Don't all *child items require a transaction to be passed for *any* crud op, i.e. including put and etc?
As they might be called from a parent transaction?
*/
@@ -627,7 +629,7 @@ namespace AyaNova.Biz
*/
use this as the basis for the children below, it's using latest methodology
#region WorkOrderState level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
@@ -841,10 +843,8 @@ use this as the basis for the children below, it's using latest methodology
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItem> ItemCreateAsync(WorkOrderItem dtNewObject)
internal async Task<WorkOrderItem> ItemCreateAsync(WorkOrderItem newObject)
{
WorkOrderItem newObject = new WorkOrderItem();
CopyObject.Copy(dtNewObject, newObject);
await ItemValidateAsync(newObject, null);
if (HasErrors)
return null;
@@ -874,17 +874,17 @@ use this as the basis for the children below, it's using latest methodology
//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);
await ct.WorkOrderItem.AsNoTracking()
.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;
@@ -894,48 +894,42 @@ use this as the basis for the children below, it's using latest methodology
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItem> ItemPutAsync(WorkOrderItem dtPutObject)
internal async Task<WorkOrderItem> ItemPutAsync(WorkOrderItem putObject)
{
WorkOrderItem dbObject = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
var dbObject = await ItemGetAsync(putObject.Id, false);
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)
if (dbObject.Concurrency != putObject.Concurrency)
{
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
putObject.Tags = TagBiz.NormalizeTags(putObject.Tags);
putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields);
await ItemValidateAsync(putObject, dbObject);
if (HasErrors) return null;
ct.Replace(dbObject, putObject);
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await ItemExistsAsync(dtPutObject.Id))
if (!await ItemExistsAsync(putObject.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 ItemHandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, putObject.Id, AyaType.WorkOrderItem, AyaEvent.Modified), ct);
await ItemSearchIndexAsync(putObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags);
await ItemHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject);
return putObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -948,7 +942,12 @@ use this as the basis for the children below, it's using latest methodology
transaction = await ct.Database.BeginTransactionAsync();
try
{
WorkOrderItem dbObject = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == id);
var dbObject = await ItemGetAsync(id, false);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
ItemValidateCanDelete(dbObject);
if (HasErrors)
return false;
@@ -966,38 +965,38 @@ use this as the basis for the children below, it's using latest methodology
//Delete children
foreach (long ItemId in ExpenseIds)
if (!await ExpenseDeleteAsync(ItemId))
if (!await ExpenseDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in LaborIds)
if (!await LaborDeleteAsync(ItemId))
if (!await LaborDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in LoanIds)
if (!await LoanDeleteAsync(ItemId))
if (!await LoanDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in PartIds)
if (!await PartDeleteAsync(ItemId))
if (!await PartDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in PartRequestIds)
if (!await PartRequestDeleteAsync(ItemId))
if (!await PartRequestDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in ScheduledUserIds)
if (!await ScheduledUserDeleteAsync(ItemId))
if (!await ScheduledUserDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in TaskIds)
if (!await TaskDeleteAsync(ItemId))
if (!await TaskDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in TravelIds)
if (!await TravelDeleteAsync(ItemId))
if (!await TravelDeleteAsync(ItemId, transaction))
return false;
foreach (long ItemId in UnitIds)
if (!await UnitDeleteAsync(ItemId))
if (!await UnitDeleteAsync(ItemId, transaction))
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 EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItem, dbObject.Id, "wo:" + dbObject.WorkOrderId.ToString(), ct);//FIX wo?? Not sure what is best here; revisit
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItem, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
@@ -1012,14 +1011,29 @@ use this as the basis for the children below, it's using latest methodology
//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;
}
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<Search.SearchIndexProcessObjectParameters> 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;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
@@ -1069,27 +1083,6 @@ use this as the basis for the children below, it's using latest methodology
}
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<Search.SearchIndexProcessObjectParameters> 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;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// NOTIFICATION PROCESSING
//
@@ -1180,36 +1173,31 @@ use this as the basis for the children below, it's using latest methodology
//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);
var ret = await ct.WorkOrderItemExpense.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemExpense, AyaEvent.Retrieved), ct);
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, ret.AyaType, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemExpense> ExpensePutAsync(WorkOrderItemExpense dtPutObject)
internal async Task<WorkOrderItemExpense> ExpensePutAsync(WorkOrderItemExpense putObject)
{
WorkOrderItemExpense dbObject = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
var dbObject = await ExpenseGetAsync(putObject.Id, false);
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");
if (dbObject.Concurrency != putObject.Concurrency)
{
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
// dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
// dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await ExpenseValidateAsync(dbObject, SnapshotOfOriginalDBObj);
await ExpenseValidateAsync(putObject, dbObject);
if (HasErrors) return null;
try
{
@@ -1217,86 +1205,53 @@ use this as the basis for the children below, it's using latest methodology
}
catch (DbUpdateConcurrencyException)
{
if (!await ExpenseExistsAsync(dtPutObject.Id))
if (!await ExpenseExistsAsync(putObject.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 ExpenseHandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, putObject.AyaType, AyaEvent.Modified), ct);
await ExpenseSearchIndexAsync(putObject, false);
//await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags);
await ExpenseHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject);
return putObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> ExpenseDeleteAsync(long id)
internal async Task<bool> ExpenseDeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null)
{
WorkOrderItemExpense dbObject = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == id);
ExpenseValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemExpense.Remove(dbObject);
await ct.SaveChangesAsync();
Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null;
if (parentTransaction == null)
transaction = await ct.Database.BeginTransactionAsync();
try
{
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 ExpenseHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
//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);
if (parentTransaction == null)
await transaction.CommitAsync();
await ExpenseHandlePotentialNotificationEvent(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;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//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
//
@@ -1314,13 +1269,61 @@ use this as the basis for the children below, it's using latest methodology
public async Task<Search.SearchIndexProcessObjectParameters> ExpenseGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == id);
var obj = await ExpenseGetAsync(id, false);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Description).AddText(obj.Name);
return SearchParams;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task ExpenseValidateAsync(WorkOrderItemExpense proposedObj, WorkOrderItemExpense currentObj)
{
//skip validation if seeding
// if (ServerBootConfig.SEEDING) return;
//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;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// NOTIFICATION PROCESSING
//