Files
raven/server/AyaNova/biz/ObjectFields.cs
2020-01-17 19:56:35 +00:00

465 lines
26 KiB
C#

using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using System.Text;
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 object keys in AvailableObjectKeys
//SQL code uses this:
// - a source of database column names and ID column names
// - Essentially this replaces all the attribute decoration in v7 and the individual classes with objects
public static class ObjectFields
{
//DEFINE VALID KEYS HERE
//For objects that are both list and edit form it's just the name of the object
//For objects that are compound list objects or reporting objects it's whatever uniquely but clearly identifies that and ends in _LIST
public const string WIDGET_KEY = "widget";
public const string USER_KEY = "user";
public const string TEST_WIDGET_USER_EMAIL_ADDRESS_LIST_KEY = "TEST_WIDGET_USER_EMAIL_ADDRESS_LIST";//for development testing, not a real thing going forward
public static List<string> AvailableObjectKeys
{
get
{
List<string> l = new List<string>{
WIDGET_KEY, USER_KEY
};
return l;
}
}
public static bool IsValidObjectKey(string key)
{
return AvailableObjectKeys.Contains(key);
}
public static List<ObjectField> ObjectFieldsList(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
Defaults of paramterless constructor:
SharedLTKey = false;
Hideable = true;
Custom = false;
Filterable = true;
Sortable = true;
MiniAvailable = true;
*/
List<ObjectField> l = new List<ObjectField>();
switch (key)
{
/*
TODO: going to need some SQL hints added here
- SqlIDColumn indicating what the ID column is to fetch the underlying value from
- SqlColumn indicating an alternative column name as known to the sql server if it varies from the propertyname (in lowercase)
Also: going to need a definition of the table name and the default identity column
Maybe decorate with an INTERNAL ONLY bool so that we see it at the server but not at the client or when fetched as possible fields
hypothetical to help develop this:
Client and head office list:
{key=ClientName, propertyname=Name, idcolumn=id,}
In v7 this is what the attribute decorations looked like:
[SqlColumnNameAttribute("aClient.aName", "aClient.aID")]
public GridNameValueCellItem LT_O_Client
{
get
{
return mClient;
}
}
[SqlColumnNameAttribute("aHeadOffice.aName","aHeadOffice.aID")]
public GridNameValueCellItem LT_O_HeadOffice
{
get
{
return mHeadOffice;
}
}
And the query (from clientlistdetailed)
string q =
"SELECT ~MAXRECS~ ACLIENT.aID, ACLIENT.aName, ACLIENT.aModified, " +
" ACLIENT.AACTIVE, ACLIENT.aUsesBanking, " +
" ACLIENT.aBillHeadOffice, ACLIENT.aWebAddress, " +
" ACLIENT.aDispatchZoneID, ACLIENT.aClientGroupID, " +
" ACLIENT.aLastWorkorderID, " +
" ACLIENT.aLastServiceDate, (SELECT TOP 1 aSBANK.aHoursBalance " +
"FROM aServiceBank aSBANK WHERE aSBANK.AAPPLIESTOROOTOBJECTID " +
"= ACLIENT.aID ORDER " +
"BY aSBANK.aCreated DESC) AS HOURSBAL, (SELECT TOP " +
"1 aSBANK.aIncidentsBalance FROM aServiceBank aSBANK " +
"WHERE aSBANK.AAPPLIESTOROOTOBJECTID = ACLIENT.aID " +
"ORDER BY aSBANK.aCreated DESC) AS INCIDENTSBAL, " +
" (SELECT TOP 1 aSBANK.aCurrencyBalance FROM " +
"aServiceBank aSBANK WHERE aSBANK.AAPPLIESTOROOTOBJECTID " +
"= ACLIENT.aID ORDER BY aSBANK.aCreated DESC) " +
"AS CURRENCYBAL, " +
" ACLIENT.AACCOUNTNUMBER, aHeadOffice.aName " +
"AS aHeadOfficeName, aHeadOffice.aID " +
"AS aHeadOfficeID, aDispatchZone.aName AS aDispatchZoneName, " +
" ACLIENT.aRegionID, aRegion.aName " +
"AS aRegionName, aClientGroup.aName AS " +
"aClientGroupName, aPHYSICAL.aDeliveryAddress, " +
"aPHYSICAL.aCity, aPHYSICAL.aStateProv, aPHYSICAL.aCountry, " +
" aPHYSICAL.aPostal, aPHYSICAL.AADDRESSTYPE, " +
" aPHYSICAL.aLongitude, aPHYSICAL.aLatitude, " +
" aWorkorderService.aServiceNumber, " +
" ACLIENT.aContractID, aContract.aName AS " +
"aContractName, ACLIENT.aContractExpires, aPHYSICAL.aCountryCode, " +
" aPOSTAL.AADDRESSTYPE AS aPAddressType, " +
" aPOSTAL.aDeliveryAddress AS aPDeliveryAddress, " +
" aPOSTAL.aCity AS aPCity, aPOSTAL.aStateProv " +
"AS aPStateProv, aPOSTAL.aCountryCode AS aPCountryCode, " +
" aPOSTAL.aCountry AS aPCountry, aPOSTAL.aPostal " +
"AS aPPostal, ACLIENT.aCustom1, ACLIENT.aCustom2, " +
" ACLIENT.aCustom3, ACLIENT.aCustom4, " +
" ACLIENT.aCustom5, ACLIENT.aCustom6, " +
" ACLIENT.aCustom7, ACLIENT.aCustom8, ACLIENT.aCustom9, " +
" ACLIENT.aCustom0, ACLIENT.aTechNotes, " +
" ACLIENT.aNotes, ACLIENT.aPopUpNotes, " +
" ACLIENT.ACONTACT, ACLIENT.AEMAIL, ACLIENT.APHONE1, ACLIENT.APHONE2, ACLIENT.APHONE3, ACLIENT.APHONE4, ACLIENT.APHONE5, ACLIENT.ACONTACTNOTES " +
"FROM " +
" ACLIENT ACLIENT " +
" LEFT OUTER JOIN ADISPATCHZONE ON (ACLIENT.ADISPATCHZONEID=ADISPATCHZONE.AID) " +
" LEFT OUTER JOIN ACLIENTGROUP ON (ACLIENT.ACLIENTGROUPID=ACLIENTGROUP.AID) " +
" LEFT OUTER JOIN AADDRESS aPHYSICAL ON (ACLIENT.AID=aPHYSICAL.AROOTOBJECTID) " +
" LEFT OUTER JOIN AADDRESS aPOSTAL ON (ACLIENT.AID=aPOSTAL.AROOTOBJECTID) " +
" LEFT OUTER JOIN ACONTRACT ON (ACLIENT.ACONTRACTID=ACONTRACT.AID) " +
" LEFT OUTER JOIN AHEADOFFICE ON (ACLIENT.AHEADOFFICEID=AHEADOFFICE.AID) " +
" LEFT OUTER JOIN AWORKORDERSERVICE ON (ACLIENT.ALASTWORKORDERID=AWORKORDERSERVICE.AWORKORDERID) " +
" LEFT OUTER JOIN AREGION ON (ACLIENT.AREGIONID=AREGION.AID) " +
"WHERE (aPHYSICAL.AADDRESSTYPE " +
"IS NULL OR aPHYSICAL.AADDRESSTYPE " +
"= 2) AND (aPOSTAL.AADDRESSTYPE IS NULL OR aPOSTAL.AADDRESSTYPE " +
"= 1) \r\n ";
*/
case WIDGET_KEY:
#region WIDGET_KEY
//first column is the non visible Default Id column so that the client knows what to open when there is no field with ID selected that matches underlying record type
//in this case the default of id is sufficient for this list
l.Add(new ObjectField { LtKey = "df", AyaObjectType = (int)AyaType.Widget });
l.Add(new ObjectField { LtKey = "WidgetName", FieldName = "Name", UiFieldDataType = (int)AyaUiFieldDataType.Text, Hideable = false });
l.Add(new ObjectField { LtKey = "WidgetSerial", FieldName = "Serial", UiFieldDataType = (int)AyaUiFieldDataType.Integer });
l.Add(new ObjectField { LtKey = "WidgetDollarAmount", FieldName = "DollarAmount", UiFieldDataType = (int)AyaUiFieldDataType.Currency });
l.Add(new ObjectField { LtKey = "WidgetCount", FieldName = "Count", UiFieldDataType = (int)AyaUiFieldDataType.Integer });
l.Add(new ObjectField { LtKey = "WidgetRoles", FieldName = "Roles", UiFieldDataType = (int)AyaUiFieldDataType.Enum, EnumType = typeof(AuthorizationRoles).ToString() });
l.Add(new ObjectField { LtKey = "WidgetStartDate", FieldName = "StartDate", UiFieldDataType = (int)AyaUiFieldDataType.DateTime });
l.Add(new ObjectField { LtKey = "WidgetEndDate", FieldName = "EndDate", UiFieldDataType = (int)AyaUiFieldDataType.DateTime });
l.Add(new ObjectField { LtKey = "WidgetNotes", FieldName = "Notes", UiFieldDataType = (int)AyaUiFieldDataType.Text });
//More to do on this, maybe the datatype should be a LINK or something for UI purposes
//circle back on this when there is enough infrastructure to test
l.Add(new ObjectField { LtKey = "User", FieldName = "userid", UiFieldDataType = (int)AyaUiFieldDataType.Text, AyaObjectType = (int)AyaType.User });
l.Add(new ObjectField { LtKey = "Active", FieldName = "Active", UiFieldDataType = (int)AyaUiFieldDataType.Bool, Hideable = false });
l.Add(new ObjectField { LtKey = "Tags", FieldName = "Tags", UiFieldDataType = (int)AyaUiFieldDataType.Tags });
l.Add(new ObjectField { LtKey = "WidgetCustom1", FieldName = "WidgetCustom1", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom2", FieldName = "WidgetCustom2", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom3", FieldName = "WidgetCustom3", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom4", FieldName = "WidgetCustom4", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom5", FieldName = "WidgetCustom5", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom6", FieldName = "WidgetCustom6", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom7", FieldName = "WidgetCustom7", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom8", FieldName = "WidgetCustom8", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom9", FieldName = "WidgetCustom9", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom10", FieldName = "WidgetCustom10", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom11", FieldName = "WidgetCustom11", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom12", FieldName = "WidgetCustom12", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom13", FieldName = "WidgetCustom13", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom14", FieldName = "WidgetCustom14", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom15", FieldName = "WidgetCustom15", IsCustomField = true });
l.Add(new ObjectField { LtKey = "WidgetCustom16", FieldName = "WidgetCustom16", IsCustomField = true });
break;
#endregion
case USER_KEY:
#region USER_KEY
l.Add(new ObjectField { LtKey = "df", AyaObjectType = (int)AyaType.User });
l.Add(new ObjectField { LtKey = "Name", FieldName = "Name", UiFieldDataType = (int)AyaUiFieldDataType.Text, Hideable = false });
l.Add(new ObjectField { LtKey = "UserEmployeeNumber", FieldName = "EmployeeNumber", UiFieldDataType = (int)AyaUiFieldDataType.Text });
l.Add(new ObjectField { LtKey = "AuthorizationRoles", FieldName = "Roles", Hideable = false, UiFieldDataType = (int)AyaUiFieldDataType.Enum, EnumType = typeof(AuthorizationRoles).ToString() });
l.Add(new ObjectField { LtKey = "UserNotes", FieldName = "Notes", UiFieldDataType = (int)AyaUiFieldDataType.Text });
l.Add(new ObjectField { LtKey = "UserUserType", FieldName = "UserType", Hideable = false, UiFieldDataType = (int)AyaUiFieldDataType.Enum, EnumType = typeof(UserType).ToString() });
l.Add(new ObjectField { LtKey = "Active", FieldName = "Active", UiFieldDataType = (int)AyaUiFieldDataType.Bool, Hideable = false });
l.Add(new ObjectField { LtKey = "Tags", FieldName = "Tags", UiFieldDataType = (int)AyaUiFieldDataType.Tags });
l.Add(new ObjectField { LtKey = "UserCustom1", FieldName = "UserCustom1", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom2", FieldName = "UserCustom2", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom3", FieldName = "UserCustom3", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom4", FieldName = "UserCustom4", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom5", FieldName = "UserCustom5", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom6", FieldName = "UserCustom6", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom7", FieldName = "UserCustom7", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom8", FieldName = "UserCustom8", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom9", FieldName = "UserCustom9", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom10", FieldName = "UserCustom10", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom11", FieldName = "UserCustom11", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom12", FieldName = "UserCustom12", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom13", FieldName = "UserCustom13", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom14", FieldName = "UserCustom14", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom15", FieldName = "UserCustom15", IsCustomField = true });
l.Add(new ObjectField { LtKey = "UserCustom16", FieldName = "UserCustom16", IsCustomField = true });
break;
#endregion
case TEST_WIDGET_USER_EMAIL_ADDRESS_LIST_KEY:
#region WIDGET_USER_EMAIL_ADDRESS_LIST_KEY
/*
Select awidget.id as df, awidget.id as widgetid, awidget.name as widgetname, auser.name as username, auser.id as userid, auseroptions.emailaddress
from awidget
left outer join auser on (awidget.userid=auser.id)
left outer join auseroptions on (auser.id=auseroptions.userid)
public string Key { get; set; }
public string PropertyName { get; set; }
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 int UiFieldDataType { get; set; }
public string EnumType { get; set; }
public int AyObjectType { get; set; }
//For query building
//This is the equivalent of the v7 SqlColumnNameAttribute
public string SqlIdColumn { get; set; }
public string SqlDisplayColumn { get; set; }
*/
//THIS is strictly a list object, not for forms so it doesn't need hideable set to any as that's a customization thing
//Property name is what the client looks for in the object to display
// and also what the return list generator uses to read in the column to deal with
//so property name is really displayasname and is always unique so actually it's really the unique key identifier, not KEY which is the ltkey
//sqlidcolumn and sqldisplaycolumn are actually sqlselectid and sqlselectdisplay
// - they could be a fragment with an alias i.e. sqlselectid="awidget.id as widgetid"
//need a separate property for the id column name to fetch
// for example:
/* WORKING OUT WHAT OBJECTFIELD REALLY SHOULD BE
Select awidget.id, awidget.name, auser.name, auser.id, auseroptions.emailaddress
from awidget
left outer join auser on (awidget.userid=auser.id)
left outer join auseroptions on (auser.id=auseroptions.userid)
order by auseroptions.emailaddress desc
There's actually no need for aliases. I'm fetching the values positionally and already know the columns that will be involved and the user will never see the aliases anyway so it's just fluff
Havev confirmed even without a join can still use the full tablename.columnname method for select statement so stick with that always
It's alright to have all the info in one place here because whatever the client needs can be parsed out and send separately without the server needed stuff
Nothing at the client yet is using any of this other than the custom fields formatter
Here at the server some validators maybe are using it and the sql builders
So rename things and see what breaks I guess
Named AYFIELD?
CLIENT ONLY USED
LtKey="WidgetName" (this is not necessarily unique per list, for example it could be "active" or "notes" in the UI (in a report for example) more than once in cases )
AyaObjectType=ayatype.widget
UiFieldDataType=AyaUiFieldDataType.Text
Hideable=false (this is required for the edit form customizations, not the list template because it will warn if no fields are visible and doesn't require any particular field to be visible, just one of them)
enumtype (required for client display)
SERVER ONLY USED
sqlidcolumnName="awidget.id"
sqldisplaycolumnName="awidget.name"
BOTH
PropertyName (RENAME TO FieldName)="widgetname" (this is the unique key for this object and what the template and builders and client should be using)
Custom (RENAME it IsCustomField TODO: WHO NEEDS THIS? this is used by the form customization at the client end and for validation I think NEED TO CHECK)
filterable (RENAME to IsFilterable required for CLIENT UI datafilter builder and validation)
sortable (RENAME to IsSortable required for CLIENT UI datafilter builder and validation)
SharedLTKEY= (DEPRECATE, NO IDEA, NOT REALLY required, probably thought I needed it for localized text editing maybe?)
*/
l.Add(new ObjectField { LtKey = "df", AyaObjectType = (int)AyaType.Widget, SqlIdColumnName = "awidget.id as df" });
l.Add(new ObjectField
{
LtKey = "WidgetName",
FieldName = "widgetname",
UiFieldDataType = (int)AyaUiFieldDataType.Text,
AyaObjectType = (int)AyaType.Widget,
SqlIdColumnName = "awidget.id as widgetid",
SqlDisplayColumnName = "awidget.name as widgetname"
});
l.Add(new ObjectField
{
LtKey = "User",
FieldName = "username",
UiFieldDataType = (int)AyaUiFieldDataType.Text,
AyaObjectType = (int)AyaType.User,
SqlIdColumnName = "userid",
SqlDisplayColumnName = "auser.name as username"
});
l.Add(new ObjectField { LtKey = "UserEmailAddress", FieldName = "emailaddress", UiFieldDataType = (int)AyaUiFieldDataType.EmailAddress });
break;
#endregion
default:
throw new System.ArgumentOutOfRangeException($"ObjectFields: {key} is not a valid object 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}";
}
//Standard mini COLUMN definition
public static string GenerateMINIListColumnsJSON(AyaType defaultLinkType)
{
return $"[ {{\"cm\":\"df\",\"dt\":0,\"ay\":{(int)defaultLinkType}}},{{\"cm\":\"Widget\",\"dt\":{(int)AyaUiFieldDataType.Text},\"ay\":{(int)defaultLinkType}}}]";
}
//Accept a json template
//return a column list suitable for api list return
public static string GenerateListColumnsJSONFromTemplate(AyaType defaultLinkType, string ObjectKey, string template)
{
//parse the template
var jtemplate = JObject.Parse(template);
//get the fields list
var fields = ObjectFieldsList(ObjectKey);
//convert to strings (https://stackoverflow.com/a/33836599/8939)
var fullFields = ((JArray)jtemplate["full"]).ToObject<string[]>();
//Generate JSON fragment to return with column definitions
StringBuilder sb = new StringBuilder();
sb.Append("[");
//df First column is always the df column
sb.Append($"{{\"cm\":\"df\",\"dt\":0,\"ay\":{(int)defaultLinkType}}}");
foreach (string s in fullFields)
{
ObjectField o = fields.FirstOrDefault(x => x.LtKey == s);
#if (DEBUG)
//Developers little helper
if (o == null)
{
throw new System.ArgumentNullException($"DEV ERROR in objectFields::GenerateListColumnsJSONFromTemplate - field {s} specified in template was NOT found in ObjectFields list for key \"{ObjectKey}\"");
}
#endif
if (o != null)
{//Here is where we can vet the field name, if it doesn't exist. For production we'll just ignore those ones
sb.Append(",");
sb.Append("{");
//Build required part of column definition
sb.Append($"\"cm\":\"{o.LtKey}\",\"dt\":{(int)o.UiFieldDataType}");
//Has a AyObjectType? (linkable / openable)
if (o.AyaObjectType != 0)
sb.Append($",\"ay\":{(int)o.AyaObjectType}");
sb.Append("}");
}
}
sb.Append("]");
return sb.ToString();
}
}//eoc ObjectFields
public class ObjectField
{
public string LtKey { get; set; }
public string FieldName { get; set; }
public bool Hideable { get; set; }
// public bool SharedLTKey { get; set; }
public bool IsCustomField { get; set; }
public bool IsFilterable { get; set; }
public bool IsSortable { get; set; }
//had this but don't know why
//if a user wants to shove a bunch of notes into a single column with other shit that's their lookout surely
//public bool MiniAvailable { get; set; }
public int UiFieldDataType { get; set; }
//If it's an enum DataType then this is the specific enum type which sb the name of the class that holds the enum in the server project
public string EnumType { get; set; }
//if field is a reference to another object (i.e. a client in a workorders list)
//then the type to open is set here
public int AyaObjectType { get; set; }
//For query building
//This is the equivalent of the v7 SqlColumnNameAttribute
public string SqlIdColumnName { get; set; }
public string SqlDisplayColumnName { get; set; }
public ObjectField()
{
//most common defaults
Hideable = true;
IsCustomField = false;
IsFilterable = true;
IsSortable = true;
//Set openable object type to no type which is the default and means it's not a link to another object
AyaObjectType = (int)AyaType.NoType;
}
//Get column to query for display name or use FieldName if there is no difference
public string GetSqlDisplayColumnName()
{
if (string.IsNullOrEmpty(SqlDisplayColumnName))
{
return FieldName.ToLowerInvariant();
}
else
{
return SqlDisplayColumnName;
}
}
}
}//ens