This commit is contained in:
@@ -60,4 +60,14 @@ Back AND front end
|
||||
- REPORTS (FUTURE) Should send the customized templated display name field to the reports as well as all the regular including name fields
|
||||
- This is because users will likely want that for many reports
|
||||
- Kind of a calculated field
|
||||
- Default templates come with Raven, user can customize further
|
||||
- Default templates come with Raven, user can customize further
|
||||
|
||||
MOCK TEMPLATE
|
||||
{
|
||||
columns:[
|
||||
|
||||
],
|
||||
mini:[
|
||||
|
||||
]
|
||||
}
|
||||
@@ -20,6 +20,15 @@ REALLY MAKING MORE PROGRESS WHEN CLIENT DEV DRIVES BACKEND DEV, STICK TO THAT!!
|
||||
-----------------------
|
||||
|
||||
GRID LISTS TODO NOW:
|
||||
|
||||
- FormAvailableFields rename to ObjectFields
|
||||
- It was previously used for only custom forms, but it also needs to exist for creating list templates
|
||||
- Some list templates don't have exact corresponding biz object as they are compound or made up so need for all lists as well
|
||||
- Need filterable property added so can control what can be filtered
|
||||
- Need sortable property so can control what can be sorted
|
||||
- This will now drive both form customization and list filters and possibly other shit in future
|
||||
- Remove FilterOptions in list objects and use this instead
|
||||
|
||||
- Return JSON and internally work with JSON so the list can return dynamic object
|
||||
- https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/
|
||||
- https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#fast-built-in-json-support
|
||||
|
||||
@@ -117,9 +117,9 @@ namespace AyaNova.Api.Controllers
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (FormAvailableFields.IsValidFormKey(formkey))
|
||||
if (ObjectFields.IsValidObjectKey(formkey))
|
||||
{
|
||||
return Ok(ApiOkResponse.Response(FormAvailableFields.FormFields(formkey), true));
|
||||
return Ok(ApiOkResponse.Response(ObjectFields.FormFields(formkey), true));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -173,7 +173,7 @@ namespace AyaNova.Api.Controllers
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
return Ok(ApiOkResponse.Response(FormAvailableFields.AvailableFormKeys, true));
|
||||
return Ok(ApiOkResponse.Response(ObjectFields.AvailableObjectKeys, true));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace AyaNova.Biz
|
||||
return;
|
||||
|
||||
var FormTemplate = JArray.Parse(formCustom.Template);
|
||||
var ThisFormCustomFieldsList = FormAvailableFields.FormFields(formCustom.FormKey).Where(x => x.Custom == true).Select(x => x.Key).ToList();
|
||||
var ThisFormCustomFieldsList = ObjectFields.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)
|
||||
@@ -52,7 +52,7 @@ namespace AyaNova.Biz
|
||||
{
|
||||
|
||||
//Translate the LT field key to the actual customFieldData field key
|
||||
var InternalCustomFieldName = FormAvailableFields.TranslateLTCustomFieldToInternalCustomFieldName(iFldKey);
|
||||
var InternalCustomFieldName = ObjectFields.TranslateLTCustomFieldToInternalCustomFieldName(iFldKey);
|
||||
//Check if it's set to required
|
||||
var isRequired = CustomFieldIsSetToRequired(FormTemplate, iFldKey);
|
||||
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AyaNova.Biz
|
||||
{
|
||||
//************************************************
|
||||
// This contains all the fields that are on customizable forms
|
||||
//in addition it serves as a source for valid form keys in AvailableFormKeys
|
||||
//
|
||||
public static class FormAvailableFields
|
||||
{
|
||||
|
||||
public const string WIDGET_FORM_KEY = "widget";
|
||||
public const string USER_FORM_KEY = "user";
|
||||
|
||||
|
||||
public static List<string> AvailableFormKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> l = new List<string>{
|
||||
WIDGET_FORM_KEY, USER_FORM_KEY
|
||||
};
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidFormKey(string key)
|
||||
{
|
||||
return AvailableFormKeys.Contains(key);
|
||||
}
|
||||
|
||||
public static List<FormField> FormFields(string key)
|
||||
{
|
||||
/*
|
||||
***************************** WARNING: Be careful here, if a standard field is hideable and also it's DB SCHEMA is set to NON NULLABLE then the CLIENT end needs to set a default
|
||||
***************************** Otherwise the hidden field can't be set and the object can't be saved EVER
|
||||
*/
|
||||
List<FormField> l = new List<FormField>();
|
||||
switch (key)
|
||||
{
|
||||
case WIDGET_FORM_KEY:
|
||||
l.Add(new FormField("WidgetName", "Name", 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", "Serial"));//not in validation rules BUT, is HIDEABLE and is set to NOT NULLABLE in the schema so this field MUST MUST have a default value in client
|
||||
l.Add(new FormField("WidgetDollarAmount", "DollarAmount"));
|
||||
l.Add(new FormField("WidgetCount", "Count"));
|
||||
l.Add(new FormField("WidgetRoles", "Roles"));//not required but must be a valid default
|
||||
l.Add(new FormField("WidgetStartDate", "StartDate"));//have rule for date precedence but can both be empty so allowing to hide is ok
|
||||
l.Add(new FormField("WidgetEndDate", "EndDate"));
|
||||
l.Add(new FormField("WidgetNotes", "Notes"));
|
||||
l.Add(new FormField("Active", "Active", true, false));//active is not hideable / required
|
||||
l.Add(new FormField("Tags", "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;
|
||||
|
||||
case USER_FORM_KEY:
|
||||
l.Add(new FormField("Name", true, false));//is not shared localized text key and not hideable as it is in the validation rules for widget
|
||||
l.Add(new FormField("EmployeeNumber"));
|
||||
l.Add(new FormField("Roles", false, false));
|
||||
l.Add(new FormField("UserNotes", "Notes"));
|
||||
l.Add(new FormField("UserType", false, false));
|
||||
l.Add(new FormField("Active", true, false));
|
||||
l.Add(new FormField("Tags", true, false));
|
||||
l.Add(new FormField("UserCustom1", false, true, true));
|
||||
l.Add(new FormField("UserCustom2", false, true, true));
|
||||
l.Add(new FormField("UserCustom3", false, true, true));
|
||||
l.Add(new FormField("UserCustom4", false, true, true));
|
||||
l.Add(new FormField("UserCustom5", false, true, true));
|
||||
l.Add(new FormField("UserCustom6", false, true, true));
|
||||
l.Add(new FormField("UserCustom7", false, true, true));
|
||||
l.Add(new FormField("UserCustom8", false, true, true));
|
||||
l.Add(new FormField("UserCustom9", false, true, true));
|
||||
l.Add(new FormField("UserCustom10", false, true, true));
|
||||
l.Add(new FormField("UserCustom11", false, true, true));
|
||||
l.Add(new FormField("UserCustom12", false, true, true));
|
||||
l.Add(new FormField("UserCustom13", false, true, true));
|
||||
l.Add(new FormField("UserCustom14", false, true, true));
|
||||
l.Add(new FormField("UserCustom15", false, true, true));
|
||||
l.Add(new FormField("UserCustom16", false, true, true));
|
||||
break;
|
||||
|
||||
|
||||
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException($"FormAvailableFields: {key} is not a valid form key");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
public static string TranslateLTCustomFieldToInternalCustomFieldName(string lTCustomFieldName)
|
||||
{
|
||||
var i = System.Convert.ToInt32(System.Text.RegularExpressions.Regex.Replace(
|
||||
lTCustomFieldName, // Our input
|
||||
"[^0-9]", // Select everything that is not in the range of 0-9
|
||||
"" // Replace that with an empty string.
|
||||
));
|
||||
|
||||
return $"c{i}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//eoc FormAvailableFields
|
||||
|
||||
public class FormField
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string PropertyName { get; set; }
|
||||
//NOTE: Not hideable fields don't require a PropertyName value because they are already required and will be validated
|
||||
public bool Hideable { get; set; }
|
||||
public bool SharedLTKey { get; set; }
|
||||
public bool Custom { get; set; }
|
||||
|
||||
|
||||
public FormField(string key, string propertyName, bool sharedLTKey = false, bool hideable = true, bool custom = false)
|
||||
{
|
||||
Key = key;
|
||||
Hideable = hideable;
|
||||
Custom = custom;
|
||||
SharedLTKey = sharedLTKey;
|
||||
PropertyName = propertyName;//Only if hideable do they require this as non-hideable ones are automatically validated anyway and this is only required for validation
|
||||
}
|
||||
|
||||
public FormField(string key, bool sharedLTKey = false, bool hideable = true, bool custom = false)
|
||||
{
|
||||
Key = key;
|
||||
Hideable = hideable;
|
||||
Custom = custom;
|
||||
SharedLTKey = sharedLTKey;
|
||||
PropertyName = null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//ens
|
||||
@@ -96,7 +96,7 @@ namespace AyaNova.Biz
|
||||
}
|
||||
|
||||
//If it doesn't exist, vet the form key name is ok by checking with this list
|
||||
if (!FormAvailableFields.AvailableFormKeys.Contains(formKey))
|
||||
if (!ObjectFields.AvailableObjectKeys.Contains(formKey))
|
||||
{
|
||||
//Nope, whatever it is, it's not valid
|
||||
return null;
|
||||
@@ -165,7 +165,7 @@ namespace AyaNova.Biz
|
||||
AddError(ApiErrorCode.VALIDATION_REQUIRED, "FormKey");
|
||||
else
|
||||
{
|
||||
if (!FormAvailableFields.IsValidFormKey(inObj.FormKey))
|
||||
if (!ObjectFields.IsValidObjectKey(inObj.FormKey))
|
||||
{
|
||||
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "FormKey");
|
||||
}
|
||||
@@ -192,7 +192,7 @@ namespace AyaNova.Biz
|
||||
if ((!PropertyHasErrors("FormKey") && !string.IsNullOrWhiteSpace(inObj.Template)))
|
||||
{
|
||||
var ValidCustomFieldTypes = CustomFieldType.ValidCustomFieldTypes;
|
||||
var ValidFormFields = FormAvailableFields.FormFields(inObj.FormKey);
|
||||
var ValidFormFields = ObjectFields.FormFields(inObj.FormKey);
|
||||
try
|
||||
{
|
||||
//Parse the json, expecting something like this:
|
||||
@@ -206,7 +206,7 @@ namespace AyaNova.Biz
|
||||
|
||||
for (int i = 0; i < v.Count; i++)
|
||||
{
|
||||
FormField MasterFormField = null;
|
||||
ObjectField MasterFormField = null;
|
||||
|
||||
var formFieldItem = v[i];
|
||||
if (formFieldItem["fld"] == null)
|
||||
|
||||
159
server/AyaNova/biz/ObjectFields.cs
Normal file
159
server/AyaNova/biz/ObjectFields.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AyaNova.Biz
|
||||
{
|
||||
//************************************************
|
||||
// This contains all the fields that are:
|
||||
// Customizable on forms
|
||||
// In grid list templates
|
||||
//In addition it serves as a source for valid form keys in AvailableFormKeys
|
||||
//
|
||||
public static class ObjectFields
|
||||
{
|
||||
|
||||
public const string WIDGET_FORM_KEY = "widget";
|
||||
public const string USER_FORM_KEY = "user";
|
||||
|
||||
|
||||
public static List<string> AvailableObjectKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> l = new List<string>{
|
||||
WIDGET_FORM_KEY, USER_FORM_KEY
|
||||
};
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidObjectKey(string key)
|
||||
{
|
||||
return AvailableObjectKeys.Contains(key);
|
||||
}
|
||||
|
||||
public static List<ObjectField> FormFields(string key)
|
||||
{
|
||||
/*
|
||||
***************************** WARNING: Be careful here, if a standard field is hideable and also it's DB SCHEMA is set to NON NULLABLE then the CLIENT end needs to set a default
|
||||
***************************** Otherwise the hidden field can't be set and the object can't be saved EVER
|
||||
*/
|
||||
List<ObjectField> l = new List<ObjectField>();
|
||||
switch (key)
|
||||
{
|
||||
case WIDGET_FORM_KEY:
|
||||
l.Add(new ObjectField("WidgetName", "Name", false, false));//is not shared localized text key and not hideable as it is in the validation rules for widget
|
||||
l.Add(new ObjectField("WidgetSerial", "Serial"));//not in validation rules BUT, is HIDEABLE and is set to NOT NULLABLE in the schema so this field MUST MUST have a default value in client
|
||||
l.Add(new ObjectField("WidgetDollarAmount", "DollarAmount"));
|
||||
l.Add(new ObjectField("WidgetCount", "Count"));
|
||||
l.Add(new ObjectField("WidgetRoles", "Roles"));//not required but must be a valid default
|
||||
l.Add(new ObjectField("WidgetStartDate", "StartDate"));//have rule for date precedence but can both be empty so allowing to hide is ok
|
||||
l.Add(new ObjectField("WidgetEndDate", "EndDate"));
|
||||
l.Add(new ObjectField("WidgetNotes", "Notes"));
|
||||
l.Add(new ObjectField("Active", "Active", true, false));//active is not hideable / required
|
||||
l.Add(new ObjectField("Tags", "Tags", true));
|
||||
l.Add(new ObjectField("WidgetCustom1", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom2", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom3", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom4", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom5", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom6", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom7", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom8", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom9", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom10", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom11", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom12", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom13", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom14", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom15", false, true, true));
|
||||
l.Add(new ObjectField("WidgetCustom16", false, true, true));
|
||||
break;
|
||||
|
||||
case USER_FORM_KEY:
|
||||
l.Add(new ObjectField("Name", true, false));//is not shared localized text key and not hideable as it is in the validation rules for widget
|
||||
l.Add(new ObjectField("EmployeeNumber"));
|
||||
l.Add(new ObjectField("Roles", false, false));
|
||||
l.Add(new ObjectField("UserNotes", "Notes"));
|
||||
l.Add(new ObjectField("UserType", false, false));
|
||||
l.Add(new ObjectField("Active", true, false));
|
||||
l.Add(new ObjectField("Tags", true, false));
|
||||
l.Add(new ObjectField("UserCustom1", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom2", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom3", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom4", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom5", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom6", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom7", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom8", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom9", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom10", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom11", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom12", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom13", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom14", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom15", false, true, true));
|
||||
l.Add(new ObjectField("UserCustom16", false, true, true));
|
||||
break;
|
||||
|
||||
|
||||
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException($"FormAvailableFields: {key} is not a valid form key");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
public static string TranslateLTCustomFieldToInternalCustomFieldName(string lTCustomFieldName)
|
||||
{
|
||||
var i = System.Convert.ToInt32(System.Text.RegularExpressions.Regex.Replace(
|
||||
lTCustomFieldName, // Our input
|
||||
"[^0-9]", // Select everything that is not in the range of 0-9
|
||||
"" // Replace that with an empty string.
|
||||
));
|
||||
|
||||
return $"c{i}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//eoc FormAvailableFields
|
||||
|
||||
public class ObjectField
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string PropertyName { get; set; }
|
||||
//NOTE: Not hideable fields don't require a PropertyName value because they are already required and will be validated
|
||||
public bool Hideable { get; set; }
|
||||
public bool SharedLTKey { get; set; }
|
||||
public bool Custom { get; set; }
|
||||
public bool Filterable { get; set; }
|
||||
public bool Sortable { get; set; }
|
||||
public bool MiniAvailable { get; set; }
|
||||
public string DataType { get; set; }
|
||||
|
||||
|
||||
public ObjectField() { }
|
||||
|
||||
public ObjectField(string key, string propertyName, bool sharedLTKey = false, bool hideable = true, bool custom = false)
|
||||
{
|
||||
Key = key;
|
||||
Hideable = hideable;
|
||||
Custom = custom;
|
||||
SharedLTKey = sharedLTKey;
|
||||
PropertyName = propertyName;//Only if hideable do they require this as non-hideable ones are automatically validated anyway and this is only required for validation
|
||||
}
|
||||
|
||||
public ObjectField(string key, bool sharedLTKey = false, bool hideable = true, bool custom = false)
|
||||
{
|
||||
Key = key;
|
||||
Hideable = hideable;
|
||||
Custom = custom;
|
||||
SharedLTKey = sharedLTKey;
|
||||
PropertyName = null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//ens
|
||||
@@ -18,7 +18,7 @@ namespace AyaNova.Biz
|
||||
//var OuterJson=JObject.Parse(formCustom.Template);
|
||||
var FormTemplate = JArray.Parse(formCustom.Template);
|
||||
// var FormTemplate=(JArray)OuterJson["template"];
|
||||
var FormFields = FormAvailableFields.FormFields(formCustom.FormKey);
|
||||
var FormFields = ObjectFields.FormFields(formCustom.FormKey);
|
||||
// var ThisFormNormalFieldsList = FormFields.Where(x => x.Custom == false).Select(x => x.Key).ToList();
|
||||
|
||||
foreach (JObject jo in FormTemplate)
|
||||
@@ -30,7 +30,7 @@ namespace AyaNova.Biz
|
||||
// - e.g.: {template:[{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"bool"},{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"text"]}
|
||||
|
||||
//get the FormField object
|
||||
FormField FF = FormFields.Where(x => x.Key == FldLtKey).Single();
|
||||
ObjectField FF = FormFields.Where(x => x.Key == FldLtKey).Single();
|
||||
if (!string.IsNullOrWhiteSpace(FF.PropertyName))
|
||||
{
|
||||
//Now get the actual property name from the available fields using the lt key
|
||||
|
||||
@@ -531,7 +531,7 @@ namespace AyaNova.Biz
|
||||
AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "EmployeeNumber", "255 max");
|
||||
|
||||
//Any form customizations to validate?
|
||||
var FormCustomization = ct.FormCustom.SingleOrDefault(x => x.FormKey == FormAvailableFields.USER_FORM_KEY);
|
||||
var FormCustomization = ct.FormCustom.SingleOrDefault(x => x.FormKey == ObjectFields.USER_FORM_KEY);
|
||||
if (FormCustomization != null)
|
||||
{
|
||||
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
|
||||
|
||||
@@ -338,7 +338,7 @@ namespace AyaNova.Biz
|
||||
|
||||
|
||||
//BUILD THE RETURN BASED ON TEMPLATE and MINI CONDITIONAL FORMAT
|
||||
//TODO: Get template
|
||||
//TODO: Get template (MOCKED FOR NOW UNTIL PROOF OF CONCEPT)
|
||||
|
||||
//TODO: BUILD THE RETURN LIST OF FIELDS / TYPES AND ORDER FROM TEMPLATE AND MINI CONDITIONAL
|
||||
|
||||
@@ -495,7 +495,7 @@ namespace AyaNova.Biz
|
||||
}
|
||||
|
||||
//Any form customizations to validate?
|
||||
var FormCustomization = ct.FormCustom.SingleOrDefault(x => x.FormKey == FormAvailableFields.WIDGET_FORM_KEY);
|
||||
var FormCustomization = ct.FormCustom.SingleOrDefault(x => x.FormKey == ObjectFields.WIDGET_FORM_KEY);
|
||||
if (FormCustomization != null)
|
||||
{
|
||||
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
|
||||
|
||||
Reference in New Issue
Block a user