From 55266fd87fc25b2e2cccf99435a477a051143d52 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Thu, 10 Jan 2019 00:15:01 +0000 Subject: [PATCH] --- devdocs/specs/core-customize-form-fields.txt | 8 +- devdocs/todo.txt | 5 +- .../Controllers/DataFilterController.cs | 7 +- .../Controllers/FormCustomController.cs | 273 +++++++++++++++++ server/AyaNova/biz/AyaType.cs | 5 +- .../AyaNova/biz/BizObjectExistsInDatabase.cs | 2 + server/AyaNova/biz/BizObjectFactory.cs | 4 +- server/AyaNova/biz/BizObjectNameFetcher.cs | 67 ----- .../AyaNova/biz/BizObjectNameFetcherDirect.cs | 4 + server/AyaNova/biz/BizRoles.cs | 11 + server/AyaNova/biz/FormCustomBiz.cs | 284 ++++++++++++++++++ 11 files changed, 591 insertions(+), 79 deletions(-) create mode 100644 server/AyaNova/Controllers/FormCustomController.cs delete mode 100644 server/AyaNova/biz/BizObjectNameFetcher.cs create mode 100644 server/AyaNova/biz/FormCustomBiz.cs diff --git a/devdocs/specs/core-customize-form-fields.txt b/devdocs/specs/core-customize-form-fields.txt index 14a8a7a8..a65a7371 100644 --- a/devdocs/specs/core-customize-form-fields.txt +++ b/devdocs/specs/core-customize-form-fields.txt @@ -51,10 +51,12 @@ UI FEATURE - The ability to customize forms: - Need a FormCustom controller that supports routes for: - Routes for customization form populating and receiving - - GET (formkey), gets a FormOptions object that combines the data from FormAvailableFields static class with the current custom values if any from the FormCustom table + - GET (formkey), gets a FormOptions object that provides the basis for building the form customization view + - Also required at the client is the current form customizations if any which can be gotten from the other route for day to day ops below - POST (formkey), accepts a FormCustom object that contains the users customization choices only - Validation ensures "CORE" fields cannot be customized - - Localization changes are handled here even though they aren't part of the actual FormCustom table, so split off and separately the localized text is updated + - Localization changes are NOT handled here even though it's the same client end form, that is handled at the CLIENT end after it has updated this part separately through regular localized text routes + - To ensure proper separation of concerns - Routes for the day to day form display purposes that efficiently fetch the form customization to use on demand and caches at the client - (GET formkey, token): Given a formkey and an *optional* concurrency token, returns one of the following: @@ -63,7 +65,7 @@ UI FEATURE - The ability to customize forms: - Basically whatever is in the FormCustom table record for that formkey - If concurrency token provided and is unchanged then simply returns a code 304 (NOT MODIFIED) - + - TESTS for the above!!! EXISTING v7: diff --git a/devdocs/todo.txt b/devdocs/todo.txt index a3452466..6bb59a1a 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -18,6 +18,8 @@ SERVER - Resource localization edit all Custom field locale keys for all langauges and change to be 1 to 16 (remove 0 and add 6 more) - So, for example ClientCustom0 becomes ClientCustom1 and make sure the display values are identical as right now there are "My Custom0" and "Custom Field 8" in the same bunch - Copy from WidgetCustom1 to 16 if necessary or for reference + - Resource localization edit wherever possible to change "Common*" such as "CommonActive" to remove the "common" part wherever possible. + - Also check if used anywhere in client or at server and rename there too - CUSTOM FIELDS?! - CUSTOM FIELDS (case 3426) @@ -77,7 +79,8 @@ DEVOPS - Doesn't require docs support as is now changed to a standard file attachment - +- SERVER landing page + - I know TTM and all that but the server landing page should look nicer. Maybe a logo and better layout or something. =-=-=- diff --git a/server/AyaNova/Controllers/DataFilterController.cs b/server/AyaNova/Controllers/DataFilterController.cs index 7d79dc7a..52046372 100644 --- a/server/AyaNova/Controllers/DataFilterController.cs +++ b/server/AyaNova/Controllers/DataFilterController.cs @@ -15,12 +15,9 @@ using AyaNova.Biz; namespace AyaNova.Api.Controllers { - //DOCUMENTATING THE API - //https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/recommended-tags-for-documentation-comments - //https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments - + /// - /// Sample controller class used during development for testing purposes + /// /// [ApiVersion("8.0")] [Route("api/v{version:apiVersion}/[controller]")] diff --git a/server/AyaNova/Controllers/FormCustomController.cs b/server/AyaNova/Controllers/FormCustomController.cs new file mode 100644 index 00000000..873715c3 --- /dev/null +++ b/server/AyaNova/Controllers/FormCustomController.cs @@ -0,0 +1,273 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using AyaNova.Models; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Biz; + + +namespace AyaNova.Api.Controllers +{ + + /// + /// + /// + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/[controller]")] + [Produces("application/json")] + [Authorize] + public class FormCustomController : Controller + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public FormCustomController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + /// + /// Get form customizations for Client form display + /// + /// Required roles: + /// Any + /// + /// + /// + /// + /// A single FormCustom + [HttpGet("{formkey}")] + public async Task GetFormCustom([FromRoute] string formkey, [FromQuery] uint? concurrencyToken) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + + //Instantiate the business object handler + FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext); + + //Just have to be authenticated for this one + if (!Authorized.IsAuthorizedToReadFullRecord(HttpContext.Items, biz.BizType)) + return StatusCode(401, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + + var o = await biz.GetAsync(formkey); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + //If concurrency token specified then check if ours is newer + if (concurrencyToken != null) + { + if (o.ConcurrencyToken != concurrencyToken) + { + //returns a code 304 (NOT MODIFIED) + return StatusCode(304); + } + } + + return Ok(new ApiOkResponse(o)); + } + + + + + /// + /// Get available fields for form specified + /// Used to build UI for customizing a form + /// + /// Required roles: + /// BizAdminFull only has rights to customize forms + /// + /// + /// + /// A single FormCustom + [HttpGet("AvailableFields/{formkey}")] + public ActionResult GetAvailableFields([FromRoute] string formkey) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + + if (!Authorized.IsAuthorizedToReadFullRecord(HttpContext.Items, AyaType.FormCustom)) + return StatusCode(401, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (FormAvailableFields.IsValidFormKey(formkey)) + { + return Ok(new ApiOkResponse(FormAvailableFields.FormFields(formkey))); + } + else + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + } + + + + // /// + // /// Get FormCustom pick list + // /// + // /// Required roles: Any + // /// + // /// + // /// List of public or owned data filters for listKey provided + // [HttpGet("PickList", Name = nameof(FormCustomPickList))] + // public async Task FormCustomPickList([FromQuery] string ListKey) + // { + // if (serverState.IsClosed) + // return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // //Instantiate the business object handler + // FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext); + + // var l = await biz.GetPickListAsync(ListKey); + // return Ok(new ApiOkResponse(l)); + + // } + + + // /// + // /// Put (update) FormCustom + // /// + // /// Required roles: + // /// Any (public filter) or owned only (private filter) + // /// + // /// + // /// + // /// + // /// + // [HttpPut("{id}")] + // public async Task PutFormCustom([FromRoute] long id, [FromBody] FormCustom inObj) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // //Instantiate the business object handler + // FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext); + + // var o = await biz.GetNoLogAsync(id); + // if (o == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + // if (!Authorized.IsAuthorizedToModify(HttpContext.Items, biz.BizType, o.OwnerId)) + // return StatusCode(401, new ApiNotAuthorizedResponse()); + + // try + // { + // if (!biz.Put(o, inObj)) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // } + // catch (DbUpdateConcurrencyException) + // { + // if (!await biz.ExistsAsync(id)) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + // else + // return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + // } + // return Ok(new ApiOkResponse(new { ConcurrencyToken = o.ConcurrencyToken })); + // } + + + // /// + // /// Post FormCustom + // /// + // /// Required roles: + // /// BizAdminFull, InventoryFull, TechFull + // /// + // /// + // /// + // [HttpPost] + // public async Task PostFormCustom([FromBody] FormCustom inObj) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + + // //Instantiate the business object handler + // FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext); + + // //If a user has change roles, or editOwnRoles then they can create, true is passed for isOwner since they are creating so by definition the owner + // if (!Authorized.IsAuthorizedToCreate(HttpContext.Items, biz.BizType)) + // return StatusCode(401, new ApiNotAuthorizedResponse()); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // //Create and validate + // FormCustom o = await biz.CreateAsync(inObj); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction("GetFormCustom", new { id = o.Id }, new ApiCreatedResponse(o)); + + // } + + + + // /// + // /// Delete FormCustom + // /// + // /// Required roles: + // /// Any if public otherwise creator only + // /// + // /// + // /// + // /// Ok + // [HttpDelete("{id}")] + // public async Task DeleteFormCustom([FromRoute] long id) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // //Instantiate the business object handler + // FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext); + + // var o = await biz.GetNoLogAsync(id); + // if (o == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + // if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, biz.BizType, o.OwnerId)) + // return StatusCode(401, new ApiNotAuthorizedResponse()); + + // if (!biz.Delete(o)) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + + // return NoContent(); + // } + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/AyaNova/biz/AyaType.cs b/server/AyaNova/biz/AyaType.cs index 005b855a..1f037f6a 100644 --- a/server/AyaNova/biz/AyaType.cs +++ b/server/AyaNova/biz/AyaType.cs @@ -32,14 +32,15 @@ namespace AyaNova.Biz DEPRECATED_REUSELATER_15 = 15, DEPRECATED_REUSELATER_16 = 16, FileAttachment = 17, - DataFilter = 18 + DataFilter = 18, + FormCustom = 19 //NOTE: New objects added here need to also be added to the following classes: //AyaNova.Biz.BizObjectExistsInDatabase //AyaNova.Biz.BizObjectFactory //AyaNova.Biz.BizRoles - //AyaNova.Biz.BizObjectNameFetcher && BizObjectNameFetcherDIRECT + //AyaNova.Biz.BizObjectNameFetcherDIRECT } diff --git a/server/AyaNova/biz/BizObjectExistsInDatabase.cs b/server/AyaNova/biz/BizObjectExistsInDatabase.cs index 5cacbe1d..95fdfff5 100644 --- a/server/AyaNova/biz/BizObjectExistsInDatabase.cs +++ b/server/AyaNova/biz/BizObjectExistsInDatabase.cs @@ -43,6 +43,8 @@ namespace AyaNova.Biz return ct.FileAttachment.Any(m => m.Id == id); case AyaType.DataFilter: return ct.DataFilter.Any(m => m.Id == id); + case AyaType.FormCustom: + return ct.FormCustom.Any(m => m.Id == id); diff --git a/server/AyaNova/biz/BizObjectFactory.cs b/server/AyaNova/biz/BizObjectFactory.cs index c4317eb4..9db846ed 100644 --- a/server/AyaNova/biz/BizObjectFactory.cs +++ b/server/AyaNova/biz/BizObjectFactory.cs @@ -26,7 +26,7 @@ namespace AyaNova.Biz case AyaType.User: return new UserBiz(dbcontext, userId, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, roles); case AyaType.Widget: - return new WidgetBiz(dbcontext, userId, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, roles); + return new WidgetBiz(dbcontext, userId, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, roles); case AyaType.JobOperations: return new JobOperationsBiz(dbcontext, userId, roles); case AyaType.AyaNova7Import: @@ -37,6 +37,8 @@ namespace AyaNova.Biz return new LocaleBiz(dbcontext, userId, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, roles); case AyaType.DataFilter: return new DataFilterBiz(dbcontext, userId, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, roles); + case AyaType.FormCustom: + return new FormCustomBiz(dbcontext, userId, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, roles); default: diff --git a/server/AyaNova/biz/BizObjectNameFetcher.cs b/server/AyaNova/biz/BizObjectNameFetcher.cs deleted file mode 100644 index ac639bf2..00000000 --- a/server/AyaNova/biz/BizObjectNameFetcher.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.JsonPatch; -using EnumsNET; -using AyaNova.Util; -using AyaNova.Api.ControllerHelpers; -using AyaNova.Biz; -using AyaNova.Models; - - -namespace AyaNova.Biz -{ - - //Turn a type and ID into a displayable name - internal static class BizObjectNameFetcher - { - - /* - NOT SURE IF I WILL WANT TO USE THIS OR NOT GOING FORWARD SO KEEPING IT IN PLACE BUT NOT USABLE FOR NOW - - */ - - internal static string Name(AyaTypeId tid, AyContext ct = null) - { - throw new System.NotSupportedException("BizObjectNameFetcher:: Slow version, did you mean to call this one?"); - //return Name(tid.ObjectType, tid.ObjectId, ct); - } - - - //Returns existance status of object type and id specified in database - internal static string Name(AyaType aytype, long id, AyContext ct = null) - { - throw new System.NotSupportedException("BizObjectNameFetcher:: Slow version, did you mean to call this one?"); - // //new up a context?? - // if (ct == null) - // { - // ct = ServiceProviderProvider.DBContext; - // } - // switch (aytype) - // { - // case AyaType.User: - // return ct.User.AsNoTracking().Where(m => m.Id == id).Select(m => m.Name).FirstOrDefault(); - // case AyaType.Widget: - // return ct.Widget.AsNoTracking().Where(m => m.Id == id).Select(m => m.Name).FirstOrDefault(); - - // case AyaType.FileAttachment: - // return ct.FileAttachment.AsNoTracking().Where(m => m.Id == id).Select(m => m.DisplayFileName).FirstOrDefault(); - - // default: - // throw new System.NotSupportedException($"AyaNova.BLL.BizObjectNameFetcher::Name type {aytype.ToString()} is not supported"); - // } - - } - - - - - - ///////////////////////////////////////////////////////////////////// - - }//eoc - - -}//eons - diff --git a/server/AyaNova/biz/BizObjectNameFetcherDirect.cs b/server/AyaNova/biz/BizObjectNameFetcherDirect.cs index 039679e0..d3e8f10a 100644 --- a/server/AyaNova/biz/BizObjectNameFetcherDirect.cs +++ b/server/AyaNova/biz/BizObjectNameFetcherDirect.cs @@ -45,6 +45,10 @@ namespace AyaNova.Biz case AyaType.DataFilter: TABLE = "adatafilter"; break; + case AyaType.FormCustom: + TABLE = "aformcustom"; + COLUMN = "formkey"; + break; default: throw new System.NotSupportedException($"AyaNova.BLL.BizObjectNameFetcher::Name type {aytype.ToString()} is not supported"); } diff --git a/server/AyaNova/biz/BizRoles.cs b/server/AyaNova/biz/BizRoles.cs index db6db221..5d35bafb 100644 --- a/server/AyaNova/biz/BizRoles.cs +++ b/server/AyaNova/biz/BizRoles.cs @@ -149,6 +149,17 @@ namespace AyaNova.Biz ReadFullRecord = AuthorizationRoles.AnyRole }); + //////////////////////////////////////////////////////////// + //FORMCUSTOM + // + roles.Add(AyaType.FormCustom, new BizRoleSet() + { + //Only BizAdminFull can modify forms + Change = AuthorizationRoles.BizAdminFull, + EditOwn = AuthorizationRoles.NoRole, + ReadFullRecord = AuthorizationRoles.AnyRole + }); + //////////////////////////////////////////////////////////////////// #endregion all roles init diff --git a/server/AyaNova/biz/FormCustomBiz.cs b/server/AyaNova/biz/FormCustomBiz.cs new file mode 100644 index 00000000..2ae9f0df --- /dev/null +++ b/server/AyaNova/biz/FormCustomBiz.cs @@ -0,0 +1,284 @@ +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Models; + + +namespace AyaNova.Biz +{ + + + internal class FormCustomBiz : BizObject + { + + internal FormCustomBiz(AyContext dbcontext, long currentUserId, long userLocaleId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserLocaleId = userLocaleId; + CurrentUserRoles = UserRoles; + BizType = AyaType.FormCustom; + } + + internal static FormCustomBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext) + { + return new FormCustomBiz(ct, UserIdFromContext.Id(httpContext.Items), UserLocaleIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + } + + //Version for internal use + internal static FormCustomBiz GetBizInternal(AyContext ct) + { + return new FormCustomBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, AuthorizationRoles.BizAdminFull); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.FormCustom.AnyAsync(e => e.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + internal async Task GetNoLogAsync(long fetchId) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + return await ct.FormCustom.SingleOrDefaultAsync(m => m.Id == fetchId); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + internal async Task CreateAsync(FormCustom inObj) + { + Validate(inObj, true); + if (HasErrors) + return null; + else + { + //do stuff with datafilter + FormCustom outObj = inObj; + outObj.OwnerId = UserId; + + + await ct.FormCustom.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + + //EVENT LOG + EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + + //SEARCH INDEXING + // Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name); + + return outObj; + + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + internal FormCustom Create(AyContext TempContext, FormCustom inObj) + { + Validate(inObj, true); + if (HasErrors) + return null; + else + { + //do stuff with datafilter + FormCustom outObj = inObj; + outObj.OwnerId = UserId; + + + TempContext.FormCustom.Add(outObj); + TempContext.SaveChanges(); + + //Handle child and associated items: + + //EVENT LOG + EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext); + + //SEARCH INDEXING + // Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name); + + return outObj; + + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(string formKey) + { + var ret = await ct.FormCustom.SingleOrDefaultAsync(m => m.FormKey == formKey); + //Do not log this, it's going to be called a zillion times anyway + return ret; + } + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal bool Put(FormCustom dbObj, FormCustom inObj) + { + //preserve the owner ID if none was specified + if (inObj.OwnerId == 0) + inObj.OwnerId = dbObj.OwnerId; + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObj, "Id"); + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; + + Validate(dbObj, false); + if (HasErrors) + return false; + + //Log modification + EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + + //Update keywords + // Search.ProcessUpdatedObjectKeywords(UserLocaleId, dbObj.Id, BizType, dbObj.Name, dbObj.Name); + + return true; + } + + + //NO DELETE, ONLY EDIT + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void Validate(FormCustom inObj, bool isNew) + { + + //OwnerId required + if (!isNew) + { + if (inObj.OwnerId == 0) + AddError(ValidationErrorType.RequiredPropertyEmpty, "OwnerId"); + } + + // //Owner must be current user, there are no exceptions + // if (inObj.OwnerId != UserId) + // AddError(ValidationErrorType.InvalidValue, "OwnerId", "OwnerId must be current user Id"); + + //FormKey required + if (string.IsNullOrWhiteSpace(inObj.FormKey)) + AddError(ValidationErrorType.RequiredPropertyEmpty, "FormKey"); + + //FormKey must be less than 255 characters + if (inObj.FormKey.Length > 255) + AddError(ValidationErrorType.LengthExceeded, "FormKey", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("FormKey")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (ct.FormCustom.Any(m => m.FormKey == inObj.FormKey && m.Id != inObj.Id)) + { + AddError(ValidationErrorType.NotUnique, "FormKey"); + } + } + + + + //Template json must parse + if (!string.IsNullOrWhiteSpace(inObj.Template)) + { + try + { + var v = JArray.Parse(inObj.Template); + //TODO: validate the json + // for (int i = 0; i < v.Count; i++) + // { + // var filterItem = v[i]; + // if (filterItem["fld"] == null) + // AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing required \"fld\" property "); + // else + // { + // var fld = filterItem["fld"].Value(); + // if (string.IsNullOrWhiteSpace(fld)) + // AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, \"fld\" property is empty and required"); + + // //validate the field name if we can + // if (ListValidFilterOptions != null) + // { + + // if (!ListValidFilterOptions.Flds.Exists(x => x.Fld == fld)) + // { + // AddError(ValidationErrorType.InvalidValue, "Filter", $"Filter array item {i}, fld property value \"{fld}\" is not a valid value for ListKey specified"); + // } + + // } + // } + // if (filterItem["op"] == null) + // AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing required \"op\" property "); + // else + // { + // var opType = filterItem["op"].Value(); + // if (!FilterComparisonOperator.Operators.Contains(opType)) + // AddError(ValidationErrorType.InvalidValue, "Filter", $"Filter array item {i}, \"op\" property value of \"{opType}\" is not a valid FilterComparisonOperator type"); + // } + + // if (filterItem["value"] == null) + // AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property "); + // else + // { + // if (filterItem["value"].Type == JTokenType.String && string.IsNullOrWhiteSpace(filterItem["value"].Value())) + // AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property "); + + // if (filterItem["value"].Type == JTokenType.Array && filterItem["value"].Count() == 0) + // AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ARRAY "); + // } + + + // //NOTE: value of nothing, null or empty is a valid value so no checking for it here + // } + } + catch (Newtonsoft.Json.JsonReaderException ex) + { + AddError(ValidationErrorType.InvalidValue, "Template", "Template is not valid JSON string: " + ex.Message); + + } + } + + + + + + return; + } + + + // //Can delete? + // private void ValidateCanDelete(FormCustom inObj) + // { + // //Leaving this off for now + // } + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons +