From 5994913153b265dc7e95a49d90322e5237635d2d Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Fri, 1 May 2020 23:03:00 +0000 Subject: [PATCH] STUB remaining v7 corebizobjects --- server/AyaNova/biz/ContractBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/HeadOfficeBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/LoanUnitBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/PMBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/PMItemBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/PMTemplateBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/PMTemplateItemBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/PartBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/ProjectBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/PurchaseOrderBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/QuoteBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/QuoteItemBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/QuoteTemplateBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/QuoteTemplateItemBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/UnitBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/UnitModelBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/VendorBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/WorkOrderBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/WorkOrderItemBiz.cs | 305 ++++++++++++++++++ .../AyaNova/biz/WorkOrderTemplateItemBiz.cs | 305 ++++++++++++++++++ server/AyaNova/biz/WorkorderTemplateBiz.cs | 305 ++++++++++++++++++ 21 files changed, 6405 insertions(+) create mode 100644 server/AyaNova/biz/ContractBiz.cs create mode 100644 server/AyaNova/biz/HeadOfficeBiz.cs create mode 100644 server/AyaNova/biz/LoanUnitBiz.cs create mode 100644 server/AyaNova/biz/PMBiz.cs create mode 100644 server/AyaNova/biz/PMItemBiz.cs create mode 100644 server/AyaNova/biz/PMTemplateBiz.cs create mode 100644 server/AyaNova/biz/PMTemplateItemBiz.cs create mode 100644 server/AyaNova/biz/PartBiz.cs create mode 100644 server/AyaNova/biz/ProjectBiz.cs create mode 100644 server/AyaNova/biz/PurchaseOrderBiz.cs create mode 100644 server/AyaNova/biz/QuoteBiz.cs create mode 100644 server/AyaNova/biz/QuoteItemBiz.cs create mode 100644 server/AyaNova/biz/QuoteTemplateBiz.cs create mode 100644 server/AyaNova/biz/QuoteTemplateItemBiz.cs create mode 100644 server/AyaNova/biz/UnitBiz.cs create mode 100644 server/AyaNova/biz/UnitModelBiz.cs create mode 100644 server/AyaNova/biz/VendorBiz.cs create mode 100644 server/AyaNova/biz/WorkOrderBiz.cs create mode 100644 server/AyaNova/biz/WorkOrderItemBiz.cs create mode 100644 server/AyaNova/biz/WorkOrderTemplateItemBiz.cs create mode 100644 server/AyaNova/biz/WorkorderTemplateBiz.cs diff --git a/server/AyaNova/biz/ContractBiz.cs b/server/AyaNova/biz/ContractBiz.cs new file mode 100644 index 00000000..99f0740d --- /dev/null +++ b/server/AyaNova/biz/ContractBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class ContractBiz : BizObject, ISearchAbleObject + { + + internal ContractBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.Contract; + } + + internal static ContractBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new ContractBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new ContractBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Contract.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.Contract.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(Contract inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with Contract + Contract outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.Contract.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(Contract dbObj) + { + + Contract outObj = new Contract(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.Contract.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.Contract.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(Contract dbObj, Contract inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + Contract SnapshotOfOriginalDBObj = new Contract(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(Contract dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + Contract SnapshotOfOriginalDBObj = new Contract(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(Contract obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.Contract.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(Contract dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.Contract.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(Contract proposedObj, Contract currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Contract.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Contract.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(Contract inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/HeadOfficeBiz.cs b/server/AyaNova/biz/HeadOfficeBiz.cs new file mode 100644 index 00000000..9d1849e4 --- /dev/null +++ b/server/AyaNova/biz/HeadOfficeBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class HeadOfficeBiz : BizObject, ISearchAbleObject + { + + internal HeadOfficeBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.HeadOffice; + } + + internal static HeadOfficeBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new HeadOfficeBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new HeadOfficeBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.HeadOffice.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.HeadOffice.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(HeadOffice inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with HeadOffice + HeadOffice outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.HeadOffice.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(HeadOffice dbObj) + { + + HeadOffice outObj = new HeadOffice(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.HeadOffice.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.HeadOffice.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(HeadOffice dbObj, HeadOffice inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + HeadOffice SnapshotOfOriginalDBObj = new HeadOffice(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(HeadOffice dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + HeadOffice SnapshotOfOriginalDBObj = new HeadOffice(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(HeadOffice obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.HeadOffice.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(HeadOffice dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.HeadOffice.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(HeadOffice proposedObj, HeadOffice currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.HeadOffice.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.HeadOffice.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(HeadOffice inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/LoanUnitBiz.cs b/server/AyaNova/biz/LoanUnitBiz.cs new file mode 100644 index 00000000..6cc4da3e --- /dev/null +++ b/server/AyaNova/biz/LoanUnitBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class LoanUnitBiz : BizObject, ISearchAbleObject + { + + internal LoanUnitBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.LoanUnit; + } + + internal static LoanUnitBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new LoanUnitBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new LoanUnitBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.LoanUnit.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.LoanUnit.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(LoanUnit inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with LoanUnit + LoanUnit outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.LoanUnit.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(LoanUnit dbObj) + { + + LoanUnit outObj = new LoanUnit(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.LoanUnit.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.LoanUnit.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(LoanUnit dbObj, LoanUnit inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + LoanUnit SnapshotOfOriginalDBObj = new LoanUnit(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(LoanUnit dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + LoanUnit SnapshotOfOriginalDBObj = new LoanUnit(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(LoanUnit obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.LoanUnit.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(LoanUnit dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.LoanUnit.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(LoanUnit proposedObj, LoanUnit currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.LoanUnit.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.LoanUnit.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(LoanUnit inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/PMBiz.cs b/server/AyaNova/biz/PMBiz.cs new file mode 100644 index 00000000..39af7dea --- /dev/null +++ b/server/AyaNova/biz/PMBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class PMBiz : BizObject, ISearchAbleObject + { + + internal PMBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.PM; + } + + internal static PMBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new PMBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new PMBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.PM.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.PM.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(PM inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with PM + PM outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.PM.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(PM dbObj) + { + + PM outObj = new PM(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.PM.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.PM.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(PM dbObj, PM inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + PM SnapshotOfOriginalDBObj = new PM(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(PM dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + PM SnapshotOfOriginalDBObj = new PM(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(PM obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.PM.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(PM dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.PM.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(PM proposedObj, PM currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.PM.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.PM.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(PM inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/PMItemBiz.cs b/server/AyaNova/biz/PMItemBiz.cs new file mode 100644 index 00000000..5d669a5f --- /dev/null +++ b/server/AyaNova/biz/PMItemBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class PMItemBiz : BizObject, ISearchAbleObject + { + + internal PMItemBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.PMItem; + } + + internal static PMItemBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new PMItemBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new PMItemBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.PMItem.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.PMItem.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(PMItem inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with PMItem + PMItem outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.PMItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(PMItem dbObj) + { + + PMItem outObj = new PMItem(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.PMItem.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.PMItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(PMItem dbObj, PMItem inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + PMItem SnapshotOfOriginalDBObj = new PMItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(PMItem dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + PMItem SnapshotOfOriginalDBObj = new PMItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(PMItem obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.PMItem.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(PMItem dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.PMItem.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(PMItem proposedObj, PMItem currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.PMItem.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.PMItem.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(PMItem inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/PMTemplateBiz.cs b/server/AyaNova/biz/PMTemplateBiz.cs new file mode 100644 index 00000000..43f44e41 --- /dev/null +++ b/server/AyaNova/biz/PMTemplateBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class PMTemplateBiz : BizObject, ISearchAbleObject + { + + internal PMTemplateBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.PMTemplate; + } + + internal static PMTemplateBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new PMTemplateBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new PMTemplateBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.PMTemplate.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.PMTemplate.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(PMTemplate inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with PMTemplate + PMTemplate outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.PMTemplate.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(PMTemplate dbObj) + { + + PMTemplate outObj = new PMTemplate(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.PMTemplate.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.PMTemplate.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(PMTemplate dbObj, PMTemplate inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + PMTemplate SnapshotOfOriginalDBObj = new PMTemplate(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(PMTemplate dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + PMTemplate SnapshotOfOriginalDBObj = new PMTemplate(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(PMTemplate obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.PMTemplate.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(PMTemplate dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.PMTemplate.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(PMTemplate proposedObj, PMTemplate currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.PMTemplate.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.PMTemplate.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(PMTemplate inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/PMTemplateItemBiz.cs b/server/AyaNova/biz/PMTemplateItemBiz.cs new file mode 100644 index 00000000..3207061b --- /dev/null +++ b/server/AyaNova/biz/PMTemplateItemBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class PMTemplateItemBiz : BizObject, ISearchAbleObject + { + + internal PMTemplateItemBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.PMTemplateItem; + } + + internal static PMTemplateItemBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new PMTemplateItemBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new PMTemplateItemBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.PMTemplateItem.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.PMTemplateItem.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(PMTemplateItem inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with PMTemplateItem + PMTemplateItem outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.PMTemplateItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(PMTemplateItem dbObj) + { + + PMTemplateItem outObj = new PMTemplateItem(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.PMTemplateItem.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.PMTemplateItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(PMTemplateItem dbObj, PMTemplateItem inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + PMTemplateItem SnapshotOfOriginalDBObj = new PMTemplateItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(PMTemplateItem dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + PMTemplateItem SnapshotOfOriginalDBObj = new PMTemplateItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(PMTemplateItem obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.PMTemplateItem.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(PMTemplateItem dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.PMTemplateItem.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(PMTemplateItem proposedObj, PMTemplateItem currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.PMTemplateItem.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.PMTemplateItem.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(PMTemplateItem inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/PartBiz.cs b/server/AyaNova/biz/PartBiz.cs new file mode 100644 index 00000000..3e9189a1 --- /dev/null +++ b/server/AyaNova/biz/PartBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class PartBiz : BizObject, ISearchAbleObject + { + + internal PartBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.Part; + } + + internal static PartBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new PartBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new PartBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Part.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.Part.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(Part inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with Part + Part outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.Part.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(Part dbObj) + { + + Part outObj = new Part(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.Part.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.Part.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(Part dbObj, Part inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + Part SnapshotOfOriginalDBObj = new Part(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(Part dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + Part SnapshotOfOriginalDBObj = new Part(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(Part obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.Part.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(Part dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.Part.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(Part proposedObj, Part currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Part.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Part.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(Part inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/ProjectBiz.cs b/server/AyaNova/biz/ProjectBiz.cs new file mode 100644 index 00000000..c65e6d68 --- /dev/null +++ b/server/AyaNova/biz/ProjectBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class ProjectBiz : BizObject, ISearchAbleObject + { + + internal ProjectBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.Project; + } + + internal static ProjectBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new ProjectBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new ProjectBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Project.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.Project.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(Project inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with Project + Project outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.Project.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(Project dbObj) + { + + Project outObj = new Project(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.Project.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.Project.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(Project dbObj, Project inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + Project SnapshotOfOriginalDBObj = new Project(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(Project dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + Project SnapshotOfOriginalDBObj = new Project(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(Project obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.Project.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(Project dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.Project.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(Project proposedObj, Project currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Project.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Project.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(Project inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/PurchaseOrderBiz.cs b/server/AyaNova/biz/PurchaseOrderBiz.cs new file mode 100644 index 00000000..b458c670 --- /dev/null +++ b/server/AyaNova/biz/PurchaseOrderBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class PurchaseOrderBiz : BizObject, ISearchAbleObject + { + + internal PurchaseOrderBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.PurchaseOrder; + } + + internal static PurchaseOrderBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new PurchaseOrderBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new PurchaseOrderBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.PurchaseOrder.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.PurchaseOrder.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(PurchaseOrder inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with PurchaseOrder + PurchaseOrder outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.PurchaseOrder.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(PurchaseOrder dbObj) + { + + PurchaseOrder outObj = new PurchaseOrder(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.PurchaseOrder.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.PurchaseOrder.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(PurchaseOrder dbObj, PurchaseOrder inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + PurchaseOrder SnapshotOfOriginalDBObj = new PurchaseOrder(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(PurchaseOrder dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + PurchaseOrder SnapshotOfOriginalDBObj = new PurchaseOrder(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(PurchaseOrder obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.PurchaseOrder.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(PurchaseOrder dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.PurchaseOrder.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(PurchaseOrder proposedObj, PurchaseOrder currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.PurchaseOrder.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.PurchaseOrder.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(PurchaseOrder inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/QuoteBiz.cs b/server/AyaNova/biz/QuoteBiz.cs new file mode 100644 index 00000000..812e4140 --- /dev/null +++ b/server/AyaNova/biz/QuoteBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class QuoteBiz : BizObject, ISearchAbleObject + { + + internal QuoteBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.Quote; + } + + internal static QuoteBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new QuoteBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new QuoteBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Quote.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.Quote.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(Quote inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with Quote + Quote outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.Quote.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(Quote dbObj) + { + + Quote outObj = new Quote(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.Quote.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.Quote.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(Quote dbObj, Quote inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + Quote SnapshotOfOriginalDBObj = new Quote(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(Quote dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + Quote SnapshotOfOriginalDBObj = new Quote(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(Quote obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.Quote.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(Quote dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.Quote.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(Quote proposedObj, Quote currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Quote.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Quote.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(Quote inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/QuoteItemBiz.cs b/server/AyaNova/biz/QuoteItemBiz.cs new file mode 100644 index 00000000..2bcf00b3 --- /dev/null +++ b/server/AyaNova/biz/QuoteItemBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class QuoteItemBiz : BizObject, ISearchAbleObject + { + + internal QuoteItemBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.QuoteItem; + } + + internal static QuoteItemBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new QuoteItemBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new QuoteItemBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.QuoteItem.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.QuoteItem.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(QuoteItem inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with QuoteItem + QuoteItem outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.QuoteItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(QuoteItem dbObj) + { + + QuoteItem outObj = new QuoteItem(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.QuoteItem.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.QuoteItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(QuoteItem dbObj, QuoteItem inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + QuoteItem SnapshotOfOriginalDBObj = new QuoteItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(QuoteItem dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + QuoteItem SnapshotOfOriginalDBObj = new QuoteItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(QuoteItem obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.QuoteItem.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(QuoteItem dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.QuoteItem.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(QuoteItem proposedObj, QuoteItem currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.QuoteItem.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.QuoteItem.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(QuoteItem inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/QuoteTemplateBiz.cs b/server/AyaNova/biz/QuoteTemplateBiz.cs new file mode 100644 index 00000000..6ce6be7c --- /dev/null +++ b/server/AyaNova/biz/QuoteTemplateBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class QuoteTemplateBiz : BizObject, ISearchAbleObject + { + + internal QuoteTemplateBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.QuoteTemplate; + } + + internal static QuoteTemplateBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new QuoteTemplateBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new QuoteTemplateBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.QuoteTemplate.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.QuoteTemplate.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(QuoteTemplate inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with QuoteTemplate + QuoteTemplate outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.QuoteTemplate.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(QuoteTemplate dbObj) + { + + QuoteTemplate outObj = new QuoteTemplate(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.QuoteTemplate.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.QuoteTemplate.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(QuoteTemplate dbObj, QuoteTemplate inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + QuoteTemplate SnapshotOfOriginalDBObj = new QuoteTemplate(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(QuoteTemplate dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + QuoteTemplate SnapshotOfOriginalDBObj = new QuoteTemplate(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(QuoteTemplate obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.QuoteTemplate.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(QuoteTemplate dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.QuoteTemplate.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(QuoteTemplate proposedObj, QuoteTemplate currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.QuoteTemplate.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.QuoteTemplate.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(QuoteTemplate inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/QuoteTemplateItemBiz.cs b/server/AyaNova/biz/QuoteTemplateItemBiz.cs new file mode 100644 index 00000000..8049fda8 --- /dev/null +++ b/server/AyaNova/biz/QuoteTemplateItemBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class QuoteTemplateItemBiz : BizObject, ISearchAbleObject + { + + internal QuoteTemplateItemBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.QuoteTemplateItem; + } + + internal static QuoteTemplateItemBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new QuoteTemplateItemBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new QuoteTemplateItemBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.QuoteTemplateItem.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.QuoteTemplateItem.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(QuoteTemplateItem inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with QuoteTemplateItem + QuoteTemplateItem outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.QuoteTemplateItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(QuoteTemplateItem dbObj) + { + + QuoteTemplateItem outObj = new QuoteTemplateItem(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.QuoteTemplateItem.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.QuoteTemplateItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(QuoteTemplateItem dbObj, QuoteTemplateItem inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + QuoteTemplateItem SnapshotOfOriginalDBObj = new QuoteTemplateItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(QuoteTemplateItem dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + QuoteTemplateItem SnapshotOfOriginalDBObj = new QuoteTemplateItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(QuoteTemplateItem obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.QuoteTemplateItem.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(QuoteTemplateItem dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.QuoteTemplateItem.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(QuoteTemplateItem proposedObj, QuoteTemplateItem currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.QuoteTemplateItem.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.QuoteTemplateItem.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(QuoteTemplateItem inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/UnitBiz.cs b/server/AyaNova/biz/UnitBiz.cs new file mode 100644 index 00000000..fa644bc9 --- /dev/null +++ b/server/AyaNova/biz/UnitBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class UnitBiz : BizObject, ISearchAbleObject + { + + internal UnitBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.Unit; + } + + internal static UnitBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new UnitBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new UnitBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Unit.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.Unit.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(Unit inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with Unit + Unit outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.Unit.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(Unit dbObj) + { + + Unit outObj = new Unit(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.Unit.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.Unit.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(Unit dbObj, Unit inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + Unit SnapshotOfOriginalDBObj = new Unit(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(Unit dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + Unit SnapshotOfOriginalDBObj = new Unit(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(Unit obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.Unit.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(Unit dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.Unit.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(Unit proposedObj, Unit currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Unit.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Unit.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(Unit inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/UnitModelBiz.cs b/server/AyaNova/biz/UnitModelBiz.cs new file mode 100644 index 00000000..b2d6f32b --- /dev/null +++ b/server/AyaNova/biz/UnitModelBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class UnitModelBiz : BizObject, ISearchAbleObject + { + + internal UnitModelBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.UnitModel; + } + + internal static UnitModelBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new UnitModelBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new UnitModelBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.UnitModel.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.UnitModel.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(UnitModel inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with UnitModel + UnitModel outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.UnitModel.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(UnitModel dbObj) + { + + UnitModel outObj = new UnitModel(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.UnitModel.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.UnitModel.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(UnitModel dbObj, UnitModel inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + UnitModel SnapshotOfOriginalDBObj = new UnitModel(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(UnitModel dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + UnitModel SnapshotOfOriginalDBObj = new UnitModel(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(UnitModel obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.UnitModel.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(UnitModel dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.UnitModel.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(UnitModel proposedObj, UnitModel currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.UnitModel.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.UnitModel.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(UnitModel inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/VendorBiz.cs b/server/AyaNova/biz/VendorBiz.cs new file mode 100644 index 00000000..42623b47 --- /dev/null +++ b/server/AyaNova/biz/VendorBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class VendorBiz : BizObject, ISearchAbleObject + { + + internal VendorBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.Vendor; + } + + internal static VendorBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new VendorBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new VendorBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Vendor.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.Vendor.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(Vendor inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with Vendor + Vendor outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.Vendor.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(Vendor dbObj) + { + + Vendor outObj = new Vendor(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.Vendor.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.Vendor.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(Vendor dbObj, Vendor inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + Vendor SnapshotOfOriginalDBObj = new Vendor(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(Vendor dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + Vendor SnapshotOfOriginalDBObj = new Vendor(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(Vendor obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.Vendor.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(Vendor dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.Vendor.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(Vendor proposedObj, Vendor currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Vendor.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Vendor.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(Vendor inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs new file mode 100644 index 00000000..b775fe31 --- /dev/null +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class WorkOrderBiz : BizObject, ISearchAbleObject + { + + internal WorkOrderBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.WorkOrder; + } + + internal static WorkOrderBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new WorkOrderBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new WorkOrderBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.WorkOrder.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.WorkOrder.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(WorkOrder inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with WorkOrder + WorkOrder outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.WorkOrder.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(WorkOrder dbObj) + { + + WorkOrder outObj = new WorkOrder(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.WorkOrder.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.WorkOrder.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(WorkOrder dbObj, WorkOrder inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrder SnapshotOfOriginalDBObj = new WorkOrder(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(WorkOrder dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrder SnapshotOfOriginalDBObj = new WorkOrder(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(WorkOrder obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.WorkOrder.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(WorkOrder dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.WorkOrder.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(WorkOrder proposedObj, WorkOrder currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.WorkOrder.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.WorkOrder.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(WorkOrder inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/WorkOrderItemBiz.cs b/server/AyaNova/biz/WorkOrderItemBiz.cs new file mode 100644 index 00000000..87223c28 --- /dev/null +++ b/server/AyaNova/biz/WorkOrderItemBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class WorkOrderItemBiz : BizObject, ISearchAbleObject + { + + internal WorkOrderItemBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.WorkOrderItem; + } + + internal static WorkOrderItemBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new WorkOrderItemBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new WorkOrderItemBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.WorkOrderItem.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.WorkOrderItem.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(WorkOrderItem inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with WorkOrderItem + WorkOrderItem outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.WorkOrderItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(WorkOrderItem dbObj) + { + + WorkOrderItem outObj = new WorkOrderItem(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.WorkOrderItem.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.WorkOrderItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(WorkOrderItem dbObj, WorkOrderItem inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrderItem SnapshotOfOriginalDBObj = new WorkOrderItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(WorkOrderItem dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrderItem SnapshotOfOriginalDBObj = new WorkOrderItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(WorkOrderItem obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.WorkOrderItem.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(WorkOrderItem dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.WorkOrderItem.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(WorkOrderItem proposedObj, WorkOrderItem currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.WorkOrderItem.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.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); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(WorkOrderItem inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/WorkOrderTemplateItemBiz.cs b/server/AyaNova/biz/WorkOrderTemplateItemBiz.cs new file mode 100644 index 00000000..ecf19337 --- /dev/null +++ b/server/AyaNova/biz/WorkOrderTemplateItemBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class WorkOrderTemplateItemBiz : BizObject, ISearchAbleObject + { + + internal WorkOrderTemplateItemBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.WorkOrderTemplateItem; + } + + internal static WorkOrderTemplateItemBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new WorkOrderTemplateItemBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new WorkOrderTemplateItemBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.WorkOrderTemplateItem.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.WorkOrderTemplateItem.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(WorkOrderTemplateItem inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with WorkOrderTemplateItem + WorkOrderTemplateItem outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.WorkOrderTemplateItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(WorkOrderTemplateItem dbObj) + { + + WorkOrderTemplateItem outObj = new WorkOrderTemplateItem(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.WorkOrderTemplateItem.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.WorkOrderTemplateItem.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(WorkOrderTemplateItem dbObj, WorkOrderTemplateItem inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrderTemplateItem SnapshotOfOriginalDBObj = new WorkOrderTemplateItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(WorkOrderTemplateItem dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrderTemplateItem SnapshotOfOriginalDBObj = new WorkOrderTemplateItem(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(WorkOrderTemplateItem obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.WorkOrderTemplateItem.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(WorkOrderTemplateItem dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.WorkOrderTemplateItem.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(WorkOrderTemplateItem proposedObj, WorkOrderTemplateItem currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.WorkOrderTemplateItem.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.WorkOrderTemplateItem.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(WorkOrderTemplateItem inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/biz/WorkorderTemplateBiz.cs b/server/AyaNova/biz/WorkorderTemplateBiz.cs new file mode 100644 index 00000000..3d395be3 --- /dev/null +++ b/server/AyaNova/biz/WorkorderTemplateBiz.cs @@ -0,0 +1,305 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.JsonPatch; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + +namespace AyaNova.Biz +{ + + internal class WorkOrderTemplateBiz : BizObject, ISearchAbleObject + { + + internal WorkOrderTemplateBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.WorkOrderTemplate; + } + + internal static WorkOrderTemplateBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + + if (httpContext != null) + return new WorkOrderTemplateBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new WorkOrderTemplateBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.WorkOrderTemplate.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + /// + /// + + internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.WorkOrderTemplate.SingleOrDefaultAsync(m => m.Id == fetchId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + + //Called from route and also seeder + internal async Task CreateAsync(WorkOrderTemplate inObj) + { + await ValidateAsync(inObj, null); + if (HasErrors) + return null; + else + { + //do stuff with WorkOrderTemplate + WorkOrderTemplate outObj = inObj; + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.WorkOrderTemplate.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + + internal async Task DuplicateAsync(WorkOrderTemplate dbObj) + { + + WorkOrderTemplate outObj = new WorkOrderTemplate(); + CopyObject.Copy(dbObj, outObj, "Wiki"); + // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + //generate unique name + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + NotUnique = await ct.WorkOrderTemplate.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + + outObj.Name = newUniqueName; + + + outObj.Id = 0; + outObj.ConcurrencyToken = 0; + + await ct.WorkOrderTemplate.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + return outObj; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(WorkOrderTemplate dbObj, WorkOrderTemplate inObj) + { + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrderTemplate SnapshotOfOriginalDBObj = new WorkOrderTemplate(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + //patch + internal async Task PatchAsync(WorkOrderTemplate dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) + { + //Validate Patch is allowed + if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; + + //make a snapshot of the original for validation but update the original to preserve workflow + WorkOrderTemplate SnapshotOfOriginalDBObj = new WorkOrderTemplate(); + CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + //Do the patching + objectPatch.ApplyTo(dbObj); + + dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; + await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + if (HasErrors) + return false; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObj, false); + + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + return true; + } + + + private async Task SearchIndexAsync(WorkOrderTemplate obj, bool isNew) + { + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.WorkOrderTemplate.SingleOrDefaultAsync(m => m.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + if (obj != null) + SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); + return SearchParams; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(WorkOrderTemplate dbObj) + { + //Determine if the object can be deleted, do the deletion tentatively + //Probably also in here deal with tags and associated search text etc + + //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + if (HasErrors) + return false; + ct.WorkOrderTemplate.Remove(dbObj); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + return true; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(WorkOrderTemplate proposedObj, WorkOrderTemplate currentObj) + { + + //run validation and biz rules + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Name must be less than 255 characters + if (proposedObj.Name.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.WorkOrderTemplate.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.WorkOrderTemplate.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + //Can delete? + // private void ValidateCanDelete(WorkOrderTemplate inObj) + // { + // //whatever needs to be check to delete this object + // } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons +