From 09609bf1bb41d0e1119ab8465dc132c24336a4fe Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Tue, 15 Jan 2019 22:45:40 +0000 Subject: [PATCH] --- devdocs/specs/core-customize-form-fields.txt | 14 ++++-- devdocs/todo.txt | 7 ++- server/AyaNova/biz/CustomFieldsValidator.cs | 50 ++++++++++++++++++++ server/AyaNova/biz/FormAvailableFields.cs | 36 ++++++++++++-- server/AyaNova/biz/UserBiz.cs | 6 +-- server/AyaNova/biz/WidgetBiz.cs | 2 + server/AyaNova/models/User.cs | 1 + server/AyaNova/models/Widget.cs | 8 ++-- server/AyaNova/util/AySchema.cs | 4 +- 9 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 server/AyaNova/biz/CustomFieldsValidator.cs diff --git a/devdocs/specs/core-customize-form-fields.txt b/devdocs/specs/core-customize-form-fields.txt index b7e23fe6..099c9e19 100644 --- a/devdocs/specs/core-customize-form-fields.txt +++ b/devdocs/specs/core-customize-form-fields.txt @@ -30,6 +30,12 @@ UI FEATURE - The ability to customize forms: if nothing has changed the server returns the unchanged code or instead returns all the data if it is changed to save bandwidth - RAVEN custom fields start with 1 not zero like in v7 - Import v7 custom0 to RAVEN custom1 + - SERVER HANDLING + - Custom fields are stored *with* their associated record in a single TEXT column as a serialized JSON object + - {c1:"blah",c2:"blah",c3:"blah".....c16:"blah"} + - According to the docs https://www.postgresql.org/docs/9.1/datatype-character.html this is pretty efficient + - I don't like the idea of an outside table holding all custom values, it would be hammered pretty hard, this way the data stays with it's record + - The server doesn't validate it or anything, just stores what the client provides and retrieves what the client needs and the client co-erces the value to the correct type ## TODO @@ -50,7 +56,7 @@ UI FEATURE - The ability to customize forms: - Method: IsValidForm(string formKey) bool exists or not - - Need a FormCustom controller that supports routes for: + - #DONE# Need a FormCustom controller that supports routes for: - #DONE# Routes for customization form populating and receiving - 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 @@ -59,14 +65,16 @@ UI FEATURE - The ability to customize forms: - 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 + - #DONE# 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: - If no concurrency token or doesn't match the current db one then: - a list of customized fields (not the non customized ones) so the client can display the UI correctly: - 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!!! + - #DONE# TESTS for the above!!! + + - Put custom fields into widget object or figure out how we will handle that aspect EXISTING v7: diff --git a/devdocs/todo.txt b/devdocs/todo.txt index 4d960ead..29245fd6 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -19,6 +19,8 @@ SERVER - PickListFetcher: WTF? It has a reference to widgetbiz in it, isn't this a generic class?? + - AySchema: License table extra fields, remove any not required (also in model and code) + - 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 @@ -50,8 +52,11 @@ SERVER - PASETO instead of JWT?? - https://paseto.io/ - - UPDATE: Update all modules at server and re-test + - UPDATE: Update all 3rd party libs in use with server and re-test - It's been a while, some of the modules date to last fall + - Test on OPS server + + DOCS / MANUAL - Disable google fonts for manual / docs generator: diff --git a/server/AyaNova/biz/CustomFieldsValidator.cs b/server/AyaNova/biz/CustomFieldsValidator.cs new file mode 100644 index 00000000..314040ed --- /dev/null +++ b/server/AyaNova/biz/CustomFieldsValidator.cs @@ -0,0 +1,50 @@ +using AyaNova.Models; +using System.Linq; +using Newtonsoft.Json.Linq; + + +namespace AyaNova.Biz +{ + internal static class CustomFieldsValidator + { + internal static void Validate(BizObject biz, FormCustom formCustom, string customFields) + { + bool hasCustomData = !string.IsNullOrWhiteSpace(customFields); + + //No form custom = no template to check against so nothing to do + if (formCustom == null) + return; + + + var FormTemplate = JArray.Parse(formCustom.Template); + + var ThisFormCustomFieldsList = FormAvailableFields.FormFields(formCustom.FormKey).Where(x => x.Custom == true).Select(x => x.Key).ToList(); + + //If the customFields string is empty then only validation is if any of the fields are required to be filled in + if (!hasCustomData) + { + //iterate the template + for (int i = 0; i < FormTemplate.Count; i++) + { + //get the field customization + var fldKey = FormTemplate["fld"].Value(); + var fldRequired = FormTemplate["required"].Value(); + //Check if this is an expected custom field and that it was set to required + if (ThisFormCustomFieldsList.Contains(fldKey) && fldRequired == true) + { + //Ok, this field is required but custom fields are all empty so add this error + biz.AddError(ValidationErrorType.RequiredPropertyEmpty, fldKey); + } + } + + return; + } + + //here we have both a bunch of custom fields presumeably and a form customization so let's get cracking... + var CustomFieldData = JObject.Parse(customFields); + + + } + + } +} diff --git a/server/AyaNova/biz/FormAvailableFields.cs b/server/AyaNova/biz/FormAvailableFields.cs index 8bcc17e3..294cafad 100644 --- a/server/AyaNova/biz/FormAvailableFields.cs +++ b/server/AyaNova/biz/FormAvailableFields.cs @@ -5,7 +5,8 @@ namespace AyaNova.Biz public static class FormAvailableFields { - private const string WIDGET = "widget"; + public const string WIDGET_FORM_KEY = "widget"; + public const string USER_FORM_KEY = "user"; public static List AvailableFormKeys @@ -13,7 +14,7 @@ namespace AyaNova.Biz get { List l = new List{ - WIDGET//,USER_CRUD, WIDGET etc + WIDGET_FORM_KEY//,USER_CRUD, WIDGET etc }; return l; } @@ -30,7 +31,7 @@ namespace AyaNova.Biz List l = new List(); switch (key) { - case WIDGET: + case WIDGET_FORM_KEY: l.Add(new FormField("WidgetName", false, false));//is not shared localized text key and not hideable as it is in the validation rules for widget l.Add(new FormField("WidgetSerial"));//not in validation rules...you get the idea l.Add(new FormField("WidgetDollarAmount")); @@ -59,6 +60,35 @@ namespace AyaNova.Biz l.Add(new FormField("WidgetCustom16", false, true, true)); break; + // case USER_FORM_KEY: + // l.Add(new FormField("WidgetName", false, false));//is not shared localized text key and not hideable as it is in the validation rules for widget + // l.Add(new FormField("WidgetSerial"));//not in validation rules...you get the idea + // l.Add(new FormField("WidgetDollarAmount")); + // l.Add(new FormField("WidgetCount")); + // l.Add(new FormField("WidgetRoles")); + // l.Add(new FormField("WidgetStartDate", false, false)); + // l.Add(new FormField("WidgetEndDate", false, false)); + // l.Add(new FormField("WidgetNotes")); + // l.Add(new FormField("CommonActive", true)); + // l.Add(new FormField("Tags", true)); + // l.Add(new FormField("WidgetCustom1", false, true, true)); + // l.Add(new FormField("WidgetCustom2", false, true, true)); + // l.Add(new FormField("WidgetCustom3", false, true, true)); + // l.Add(new FormField("WidgetCustom4", false, true, true)); + // l.Add(new FormField("WidgetCustom5", false, true, true)); + // l.Add(new FormField("WidgetCustom6", false, true, true)); + // l.Add(new FormField("WidgetCustom7", false, true, true)); + // l.Add(new FormField("WidgetCustom8", false, true, true)); + // l.Add(new FormField("WidgetCustom9", false, true, true)); + // l.Add(new FormField("WidgetCustom10", false, true, true)); + // l.Add(new FormField("WidgetCustom11", false, true, true)); + // l.Add(new FormField("WidgetCustom12", false, true, true)); + // l.Add(new FormField("WidgetCustom13", false, true, true)); + // l.Add(new FormField("WidgetCustom14", false, true, true)); + // l.Add(new FormField("WidgetCustom15", false, true, true)); + // l.Add(new FormField("WidgetCustom16", false, true, true)); + // break; + default: diff --git a/server/AyaNova/biz/UserBiz.cs b/server/AyaNova/biz/UserBiz.cs index c9fef09d..d22eeb52 100644 --- a/server/AyaNova/biz/UserBiz.cs +++ b/server/AyaNova/biz/UserBiz.cs @@ -361,9 +361,9 @@ namespace AyaNova.Biz //USEROPTIONS ct.Database.ExecuteSqlCommand($"delete from auseroptions where userid={dbObj.Id}"); - + EventLogProcessor.DeleteObject(UserId, BizType, dbObj.Id, dbObj.Name, ct); - ct.SaveChanges(); + ct.SaveChanges(); Search.ProcessDeletedObjectKeywords(dbObj.Id, BizType); TagUtil.ProcessDeleteTagsInRepository(ct, dbObj.Tags); @@ -521,7 +521,7 @@ namespace AyaNova.Biz if (!string.IsNullOrWhiteSpace(proposedObj.EmployeeNumber) && proposedObj.EmployeeNumber.Length > 255) AddError(ValidationErrorType.LengthExceeded, "EmployeeNumber", "255 max"); - + //CustomFieldsValidator.Validate(this, "user", proposedObj.CustomFields); return; } diff --git a/server/AyaNova/biz/WidgetBiz.cs b/server/AyaNova/biz/WidgetBiz.cs index f093b171..997a95c2 100644 --- a/server/AyaNova/biz/WidgetBiz.cs +++ b/server/AyaNova/biz/WidgetBiz.cs @@ -399,6 +399,8 @@ namespace AyaNova.Biz AddError(ValidationErrorType.InvalidValue, "Roles"); } + CustomFieldsValidator.Validate(this, ct.FormCustom.SingleOrDefault(x => x.FormKey == FormAvailableFields.WIDGET_FORM_KEY), proposedObj.CustomFields); + return; } diff --git a/server/AyaNova/models/User.cs b/server/AyaNova/models/User.cs index 9f818222..c78ff286 100644 --- a/server/AyaNova/models/User.cs +++ b/server/AyaNova/models/User.cs @@ -36,6 +36,7 @@ namespace AyaNova.Models public long? ClientId { get; set; } public long? HeadOfficeId { get; set; } public long? SubVendorId { get; set; } + public string CustomFields { get; set; } public List Tags { get; set; } diff --git a/server/AyaNova/models/Widget.cs b/server/AyaNova/models/Widget.cs index 9b9a3eaf..70476821 100644 --- a/server/AyaNova/models/Widget.cs +++ b/server/AyaNova/models/Widget.cs @@ -23,11 +23,13 @@ namespace AyaNova.Models public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } public string Notes { get; set; } - public int Count {get;set;} + public int Count { get; set; } + public string CustomFields { get; set; } public List Tags { get; set; } - public Widget(){ - Tags=new List(); + public Widget() + { + Tags = new List(); } } diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 8e133edf..d42e93bc 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -167,7 +167,7 @@ namespace AyaNova.Util exec("CREATE TABLE auser (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, active bool not null, name varchar(255) not null, " + "login text not null, password text not null, salt text not null, roles integer not null, localeid bigint not null REFERENCES alocale (id), " + "dlkey text, dlkeyexpire timestamp, usertype integer not null, employeenumber varchar(255), notes text, clientid bigint, " + - "headofficeid bigint, subvendorid bigint, tags varchar(255) ARRAY)"); + "headofficeid bigint, subvendorid bigint, customfields text, tags varchar(255) ARRAY)"); //Index for name fetching exec("CREATE UNIQUE INDEX auser_name_id_idx ON auser (id, name);"); @@ -206,7 +206,7 @@ namespace AyaNova.Util //Add widget table //id, text, longtext, boolean, currency, exec("CREATE TABLE awidget (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, serial bigint not null," + - "startdate timestamp, enddate timestamp, dollaramount decimal(19,5), active bool, roles int4, count integer, notes text, tags varchar(255) ARRAY)"); + "startdate timestamp, enddate timestamp, dollaramount decimal(19,5), active bool, roles int4, count integer, notes text, customfields text, tags varchar(255) ARRAY)"); //TEST TEST TEST ONLY FOR DEVELOPMENT TESTING TO ENSURE UNIQUENESS //exec("CREATE UNIQUE INDEX awidget_serial_idx ON awidget (serial);");