diff --git a/devdocs/todo.txt b/devdocs/todo.txt index 5b470f7f..a101bab7 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -1,6 +1,13 @@ # now +DOING: adding integration objects and routes for qbi and others + todo: document this in the developer section + todo: needs a front end to show the integration log, the integrations as a collection list and openable item where it can be disabled, deleted etc + needs to be available to bizadmin I guess and possibly accounting?? or should each integration control which roles see it?? + or should the UI for each integration be *in* the integration itself and also in one place for biz admin to super control all integrations and troubleshoot log sb available to ops + todo: doc front end additions for integrations + JUST DONE: posted rc.1 updated forum (and turned off "views" column in custom css in forum as well it was looking pathetic) @@ -17,7 +24,8 @@ todo: BIG PICTURE QBI NEEDS TO BE DONE BEFORE RAVEN RELEASE AS RAVEN MAY REQUIRE MORE OBJECTS FOR DATA MAPPING AND OPTIONS SAVING IN CENTRAL LOCATION ETC This saves users needing to back up their local QBI only settings and mappings separate from RAVEN backup - + Integration objects and integration log plus front end to see that shit and remove / control it + QBI order of release decision (does raven qbi need to be released along with v8 simultaneously??) if yes then need to whip that shit up pronto with a beta to go with RC for raven figure out how many active qbi users, like will this prevent most people from upgrading? diff --git a/server/AyaNova/biz/AyaType.cs b/server/AyaNova/biz/AyaType.cs index c5c6aab8..4f8f0b4d 100644 --- a/server/AyaNova/biz/AyaType.cs +++ b/server/AyaNova/biz/AyaType.cs @@ -174,7 +174,8 @@ namespace AyaNova.Biz [ReportableBizObject] PartInventoryDataList = 90,//for list/reporting only, synthetic object [ReportableBizObject] - PartInventoryRequestDataList = 91//for list/reporting only, synthetic object + PartInventoryRequestDataList = 91,//for list/reporting only, synthetic object + Integration = 92 //3rd party or add-on integration data store diff --git a/server/AyaNova/biz/BizObjectExistsInDatabase.cs b/server/AyaNova/biz/BizObjectExistsInDatabase.cs index 14d33686..0a8ae51c 100644 --- a/server/AyaNova/biz/BizObjectExistsInDatabase.cs +++ b/server/AyaNova/biz/BizObjectExistsInDatabase.cs @@ -157,6 +157,8 @@ namespace AyaNova.Biz return await ct.CustomerServiceRequest.AnyAsync(z => z.Id == id); case AyaType.CustomerNotifySubscription: return await ct.CustomerNotifySubscription.AnyAsync(z => z.Id == id); + case AyaType.Integration: + return await ct.Integration.AnyAsync(z => z.Id == id); default: throw new System.NotSupportedException($"AyaNova.Biz.BizObjectExistsInDatabase::ExistsAsync type {aType.ToString()} is not supported"); } diff --git a/server/AyaNova/biz/BizRoles.cs b/server/AyaNova/biz/BizRoles.cs index 8fc42732..06fd3e25 100644 --- a/server/AyaNova/biz/BizRoles.cs +++ b/server/AyaNova/biz/BizRoles.cs @@ -1042,6 +1042,17 @@ namespace AyaNova.Biz | AuthorizationRoles.OpsAdmin, }); + //////////////////////////////////////////////////////////// + //INTEGRATION - note this is for the management UI + //for *all* integrations. Separately, each integration app + //will have it's own authorization system + // + roles.Add(AyaType.Integration, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + }); + //////////////////////////////////////////////////////////////////// #endregion all roles init @@ -1056,9 +1067,9 @@ namespace AyaNova.Biz //GENERATE CLIENT COMPATIBLE JSON FROM ROLES OUTPUT TO DEBUG LOG //And seperately, set the JSON variable so can copy from debug variable "value" property for lastRoles here to compare - // string json = Newtonsoft.Json.JsonConvert.SerializeObject(roles, Newtonsoft.Json.Formatting.None); - // System.Diagnostics.Debugger.Log(1, "JSONFRAGMENTFORCLIENT", "BizRoles.cs -> biz-role-rights.js Client roles JSON fragment:\n\n"); - // System.Diagnostics.Debugger.Log(1, "JSONFRAGMENTFORCLIENT", json + "\n\n"); + string json = Newtonsoft.Json.JsonConvert.SerializeObject(roles, Newtonsoft.Json.Formatting.None); + System.Diagnostics.Debugger.Log(1, "JSONFRAGMENTFORCLIENT", "BizRoles.cs -> biz-role-rights.js Client roles JSON fragment:\n\n"); + System.Diagnostics.Debugger.Log(1, "JSONFRAGMENTFORCLIENT", json + "\n\n"); //ONGOING VALIDATION TO CATCH MISMATCH WHEN NEW ROLES ADDED (wont' catch changes to existing unfortunately) diff --git a/server/AyaNova/models/AyContext.cs b/server/AyaNova/models/AyContext.cs index 8d1d9c43..8b3c14bf 100644 --- a/server/AyaNova/models/AyContext.cs +++ b/server/AyaNova/models/AyContext.cs @@ -133,6 +133,10 @@ namespace AyaNova.Models + public virtual DbSet Integration { get; set; } + public virtual DbSet IntegrationItem { get; set; } + public virtual DbSet IntegrationLog { get; set; } + //Note: had to add this constructor to work with the code in startup.cs that gets the connection string from the appsettings.json file //and commented out the above on configuring public AyContext(DbContextOptions options) : base(options) diff --git a/server/AyaNova/models/Integration.cs b/server/AyaNova/models/Integration.cs new file mode 100644 index 00000000..bf914700 --- /dev/null +++ b/server/AyaNova/models/Integration.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using AyaNova.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace AyaNova.Models +{ + //https://stackoverflow.com/questions/46517584/how-to-add-a-parent-record-with-its-children-records-in-ef-core#46615455 + + public class Integration + { + public long Id { get; set; } + public uint Concurrency { get; set; } + [Required] + public Guid IntegrationAppId { get; set; }//Guid of integrating application. All data for it is stored under this guid key. This guid should be fixed and unchanged for all users of the integration app, i.e. it shouldn't be unique to each install but unique to itself once, a publish app id + [Required] + public string Name { get; set; } + public bool Active { get; set; }//integration apps should always check if this is true before working or not and give appropriate error if turned off + public string IntegrationData { get; set; }//foreign object data store for entire integration, can be used for custom data, unused by any AyaNova add-on's, expect json or xml or some other text value here + + + public List Items { get; set; } = new List(); + + + + }//eoc + +}//eons + + +// CREATE TABLE [dbo].[AINTEGRATION]( +// [AID] [uniqueidentifier] NOT NULL, +// [ACREATED] [datetime] NULL, +// [AMODIFIED] [datetime] NULL, +// [ACREATOR] [uniqueidentifier] NULL, +// [AMODIFIER] [uniqueidentifier] NULL, +// [AACTIVE] [bit] NOT NULL, <--was set to true at creation of integration in plugins but never used beyond that, was intended to be able to temporarily pause an integration, maybe should enable it to work that way as intended +// [ANAME] [nvarchar](255) NOT NULL, +// [ALASTCONNECT] [datetime] NULL, <=---unused in v7 I'm dropping this as it's always going to be ambiguous and we don't use it anyway, let people put this in the integrationdata as json data in if they want to +// [AIOBJECT] [image] NULL, <--was not used don't bring forward as binary but do bring forward as a IntegrationData string where they can put json or whatever +// [AIOBJECTSIZE] [int] NULL, <- this one either +// [AAPPID] [uniqueidentifier] NOT NULL, +// [AAPPVERSION] [nvarchar](25) NOT NULL, <--unused, not keeping, if people need this they can use the intgrationdata and store as JSON with anything else they need +// [ASYNCCHECKPOINT] [nvarchar](255) NULL, <--unused, not keeping, if people need this they can use the intgrationdata and store as JSON with anything else they need \ No newline at end of file diff --git a/server/AyaNova/models/IntegrationItem.cs b/server/AyaNova/models/IntegrationItem.cs new file mode 100644 index 00000000..d4981288 --- /dev/null +++ b/server/AyaNova/models/IntegrationItem.cs @@ -0,0 +1,51 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using AyaNova.Biz; +using Newtonsoft.Json; + +namespace AyaNova.Models +{ + //https://stackoverflow.com/questions/46517584/how-to-add-a-parent-record-with-its-children-records-in-ef-core#46615455 + public class IntegrationItem + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long IntegrationId { get; set; } + + [Required] + public long ObjectId { get; set; } + [Required] + public AyaType AType { get; set; } + [Required] + public string IntegrationItemId { get; set; }//foreign id (e.g. QB Id) + public string IntegrationItemName { get; set; }//foreign object's name for UI, not required if not displayed anywhere + public DateTime? LastSync { get; set; }//optional, used by qbi + public string IntegrationItemData { get; set; }//foreign object data store for this item, unused by any AyaNova add-on's in the past but might be useful for others, expect json or xml or some other text value here + + + [JsonIgnore] + public Integration Integration { get; set; } + + }//eoc + +}//eons + +// CREATE TABLE [dbo].[AINTEGRATIONMAP]( +// [AID] [uniqueidentifier] NOT NULL, +// [ACREATED] [datetime] NOT NULL, +// [AMODIFIED] [datetime] NOT NULL, +// [ACREATOR] [uniqueidentifier] NOT NULL, +// [AMODIFIER] [uniqueidentifier] NOT NULL, +// [AINTEGRATIONID] [uniqueidentifier] NOT NULL, +// [AROOTOBJECTID] [uniqueidentifier] NOT NULL, <--Now "ObjectId" +// [AROOTOBJECTTYPE] [smallint] NOT NULL, <-- now "AType" +// [AFOREIGNID] [nvarchar](255) NOT NULL, <-- renamed to "IntegrationItemId" still 255 char string +// [ANAME] [nvarchar](255) NULL, <--keep rename to "IntegrationItemName" +// [AFOREIGNCHECKSUM] [nvarchar](255) NULL, drop was not used and thsi is hwat the lastsynch is for anyway +// [AAYANOVACHECKSUM] [nvarchar](255) NULL, drop was not used and thsi is hwat the lastsynch is for anyway +// [AMAPOBJECT] [image] NULL, Now "IntegrationItemData" +// [AMAPOBJECTSIZE] [int] NULL, dropped, expects to be json data now, size is irrelevant as it's not a binary object anymore +// [ALASTSYNC] [datetime] NOT NULL, \ No newline at end of file diff --git a/server/AyaNova/models/IntegrationLog.cs b/server/AyaNova/models/IntegrationLog.cs new file mode 100644 index 00000000..e8ef67c2 --- /dev/null +++ b/server/AyaNova/models/IntegrationLog.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace AyaNova.Models +{ + + /// + /// Integration log + /// + public class IntegrationLog + { + public long Id { get; set; } + [Required] + public Guid IntegrationAppId { get; set; } + [Required] + public DateTime Created { get; set; } + [Required] + public string StatusText { get; set; } + + public IntegrationLog() + { + Created = DateTime.UtcNow; + StatusText = "default / not set"; + } + + } + +} diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index a93d9724..f33c8481 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -20,7 +20,7 @@ namespace AyaNova.Util /////////// CHANGE THIS ON NEW SCHEMA UPDATE //////////////////// //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImportingAsync WHEN NEW TABLES ADDED!!!! - private const int DESIRED_SCHEMA_LEVEL = 3; + private const int DESIRED_SCHEMA_LEVEL = 4; internal const long EXPECTED_COLUMN_COUNT = 1359; internal const long EXPECTED_INDEX_COUNT = 156; @@ -361,8 +361,7 @@ $BODY$; "); - //Name fetcher function - //CoreBizObject ADD here + //Original Name fetcher function, superseded by later updates await ExecQueryAsync(@" CREATE OR REPLACE FUNCTION PUBLIC.AYGETNAME(IN AYOBJECTID BIGINT, IN AYATYPE INTEGER,TRANSLATIONID integer) RETURNS TEXT AS $BODY$ DECLARE @@ -1328,6 +1327,137 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); } + if (currentSchema < 4) + { + LogUpdateMessage(log); + + //INTEGRATION + await ExecQueryAsync("CREATE TABLE aintegration (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, integrationappid uuid NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE, active BOOL NOT NULL, " + + "integrationdata TEXT )"); + + //INTEGRATIONITEM + await ExecQueryAsync("CREATE TABLE aintegrationitem (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, integrationid BIGINT NOT NULL REFERENCES aintegration ON DELETE CASCADE, " + + "atype INTEGER NOT NULL, objectid BIGINT NOT NULL, integrationitemid TEXT NOT NULL, lastsync TIMESTAMPTZ, integrationitemdata TEXT " + + ")"); + + + await ExecQueryAsync(@" +CREATE OR REPLACE FUNCTION PUBLIC.AYGETNAME(IN AYOBJECTID BIGINT, IN AYATYPE INTEGER,TRANSLATIONID integer) RETURNS TEXT AS $BODY$ +DECLARE + aytable TEXT DEFAULT ''; + aynamecolumn TEXT DEFAULT 'name'; + aytkey TEXT DEFAULT 'no'; + returnstr TEXT DEFAULT ''; +BEGIN + case ayatype + when 0 then aytkey= 'NoType'; + when 1 then aytkey= 'Global'; + when 2 then return 'FormUserOptions'; + when 3 then aytable = 'auser'; + when 4 then aytkey= 'ServerState'; + when 5 then aytkey= 'License'; + when 6 then aytkey= 'LogFile'; + when 7 then aytkey= 'PickListTemplate'; + when 8 then aytable = 'acustomer'; + when 9 then aytkey= 'ServerJob'; + when 10 then aytable = 'acontract'; + when 11 then aytkey= 'TrialSeeder'; + when 12 then aytkey= 'ServerMetrics'; + when 13 then aytable = 'atranslation'; + when 14 then aytkey= 'UserOptions'; + when 15 then aytable = 'aheadoffice'; + when 16 then aytable = 'aloanunit'; + when 17 then aytable = 'afileattachment'; aynamecolumn ='displayfilename'; + when 18 then aytable = 'adatalistsavedfilter'; + when 19 then aytable = 'aformcustom'; aynamecolumn = 'formkey'; + when 20 then aytable = 'apart'; + when 21 then aytable = 'apm'; aynamecolumn ='serial'; + when 22 then aytkey= 'PMItem'; + when 23 then aytkey= 'WorkOrderItemExpense'; + when 24 then aytkey= 'WorkOrderItemLabor'; + when 25 then aytable = 'aproject'; + when 26 then aytable = 'apurchaseorder'; aynamecolumn = 'serial'; + when 27 then aytable = 'aquote'; aynamecolumn = 'serial'; + when 28 then aytkey= 'QuoteItem'; + when 29 then aytkey= 'WorkOrderItemLoan'; + when 30 then aytkey= 'WorkOrderItemPart'; + when 31 then aytable = 'aunit'; aynamecolumn = 'serial'; + when 32 then aytable = 'aunitmodel'; aynamecolumn = 'name'; + when 33 then aytable = 'avendor'; + when 34 then aytable = 'aworkorder'; aynamecolumn = 'serial'; + when 35 then aytkey= 'WorkOrderItem'; + when 36 then aytkey= 'WorkOrderItemExpense'; + when 37 then aytkey= 'WorkOrderItemLabor'; + when 38 then aytkey= 'WorkOrderItemLoan'; + when 39 then aytkey= 'WorkOrderItemPart'; + when 40 then aytkey= 'WorkOrderItemPartRequest'; + when 41 then aytkey= 'WorkOrderItemScheduledUser'; + when 42 then aytkey= 'WorkOrderItemTask'; + when 43 then aytkey= 'WorkOrderItemTravel'; + when 44 then aytkey= 'WorkOrderItemUnit'; + when 45 then aytkey= 'WorkOrderItemScheduledUser'; + when 46 then aytkey= 'WorkOrderItemTask'; + when 47 then aytkey= 'GlobalOps'; + when 48 then aytkey= 'BizMetrics'; + when 49 then aytkey= 'Backup'; + when 50 then aytable = 'ainappnotification'; + when 51 then aytkey= 'NotifySubscription'; + when 52 then aytable = 'areminder'; + when 53 then aytkey= 'UnitMeterReading'; + when 54 then aytable = 'acustomerservicerequest'; + when 56 then aytkey= 'OpsNotificationSettings'; + when 57 then aytable = 'areport'; + when 58 then aytkey= 'DashBoardView'; + when 59 then aytable = 'acustomernote'; aynamecolumn = 'notedate'; + when 60 then aytable = 'amemo'; + when 61 then aytable = 'areview'; + when 62 then aytable = 'aservicerate'; + when 63 then aytable = 'atravelrate'; + when 64 then aytable = 'ataxcode'; + when 65 then aytable = 'apartassembly'; + when 66 then aytable = 'apartwarehouse'; + when 67 then aytable = 'apartinventory'; aynamecolumn='description'; + when 68 then return format('DataListColumnView %L', ayobjectid); + when 71 then aytable = 'aworkorderstatus'; + when 72 then aytable = 'ataskgroup'; + when 73 then aytkey= 'WorkOrderItemOutsideService'; + when 74 then aytable = 'aworkorderitempriority'; + when 75 then aytable = 'aworkorderitemstatus'; + when 76 then aytkey= 'WorkOrderItemTravel'; + when 77 then aytkey= 'QuoteItemUnit'; + when 78 then aytable = 'aquotestatus'; + when 79 then aytkey= 'WorkOrderItemOutsideService'; + when 80 then aytkey= 'WorkOrderItemExpense'; + when 81 then aytkey= 'WorkOrderItemLabor'; + when 82 then aytkey= 'WorkOrderItemLoan'; + when 83 then aytkey= 'WorkOrderItemPart'; + when 84 then aytkey= 'CustomerNotifySubscription'; + when 85 then aytkey= 'WorkOrderItemScheduledUser'; + when 86 then aytkey= 'WorkOrderItemTask'; + when 87 then aytkey= 'WorkOrderItemTravel'; + when 88 then aytkey= 'WorkOrderItemUnit'; + when 89 then aytkey= 'WorkOrderItemOutsideService'; + when 90 then aytkey= 'PartInventoryDataList'; + when 91 then return 'PartInventoryRequestDataList synthetic unnamed object'; + when 92 then aytable = 'aintegration'; + + else + RETURN returnstr; + end case; + + IF aytkey='no' then + EXECUTE format('SELECT %I FROM %I WHERE id = %L', aynamecolumn, aytable, ayobjectid) INTO returnstr; + else + EXECUTE format('select display from atranslationitem where translationid=%L and key=%L', TRANSLATIONID, aytkey) INTO returnstr; + END if; + RETURN returnstr; +END; +$BODY$ LANGUAGE PLPGSQL STABLE"); + + await SetSchemaLevelAsync(++currentSchema); + } + + //######################################### //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!!