Some example BusinessObjects and explanations of parts of their scripts
Note that all Developers can open the "DB Utility" shown below and from this screen access ALL the structural details of every BusinessObject in their App. This allows Developers to "learn by doing" as they can review the contents of the BusinessObject and then open its records/grids/reports in the App and see how it works.
Orixa Creation Script for a Simplified "Contracts" BusinessObject
Note that this script was generated automatically, by opening the "BusinessObject" record for the "Contracts" BusinessObject and selecting "Reverse Engineer BusinessObject" from the Actions menu.
The SQL script to Create the "Contracts" BusinessObject
CREATE TABLE "Contracts"
(
"ID" INTEGER DEFAULT UID() NOT NULL,
"OID" INTEGER DEFAULT OID('Contracts'),
"DateStart" DATE,
"DateEnd" DATE,
"CustomersID" INTEGER,
"Name" VARCHAR(100) COLLATE "ANSI",
"StatusID" INTEGER DEFAULT StatusID('In negotiation'),
"ContractsTypeID" INTEGER,
"Internal" BOOLEAN DEFAULT FALSE NOT NULL,
"Description" CLOB COLLATE "ANSI",
"AuthorID" INTEGER,
"LeadStaffID" INTEGER,
"DateCreated" TIMESTAMP DEFAULT Current_Timestamp,
"Current" BOOLEAN DEFAULT true NOT NULL,
"Complete" BOOLEAN DEFAULT false NOT NULL,
"FullName" VARCHAR(120) COLLATE "ANSI" COMPUTED ALWAYS AS IF(COMPLETE = true then 'zzz/' ELSE '') +
IF(Internal = true then 'I' ELSE '') +
'C-' + CAST(OID AS VARCHAR) + ': ' + Name,
"ShortName" VARCHAR(20) COLLATE "ANSI" GENERATED ALWAYS AS IF(Internal = true then 'I' ELSE '')
+ 'C-' + CAST(OID AS VARCHAR(12)) + '-' + OrgCode(CustomersID),
"VATExempt" BOOLEAN GENERATED ALWAYS AS CustomerVATExempt(CustomersID),
CONSTRAINT "PK_Contracts" PRIMARY KEY ("ID"),
CONSTRAINT "LeadStaffID" FOREIGN KEY ("LeadStaffID") REFERENCES "Staff" ("ID")
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "ContractsTypeID" FOREIGN KEY ("ContractsTypeID") REFERENCES "Types" ("ID")
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "StatusID" FOREIGN KEY ("StatusID") REFERENCES "Status" ("ID")
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "CustomersID" FOREIGN KEY ("CustomersID") REFERENCES "Customers" ("ID")
ON UPDATE NO ACTION ON DELETE NO ACTION
DESCRIPTION 'Default'
)
DESCRIPTION '[Properties]
AddDuplicateRecord=1'
Note the field-names used and how some fields are generated by functions, or link to other tables via contraints.
Functions Used by the BusinessObject
CREATE FUNCTION "StatusID" (IN "Value" VARCHAR COLLATE "ANSI")
RETURNS INTEGER
BEGIN
DECLARE Crsr Cursor FOR Stmt;
DECLARE Result INTEGER;
PREPARE Stmt FROM
' SELECT ID
FROM Status
WHERE UPPER(Name) = UPPER(?) ';
OPEN Crsr USING Value;
FETCH FIRST FROM Crsr('ID') INTO Result;
CLOSE Crsr;
RETURN Result;
END
VERSION 1.00!
The "StatusID" function is built into Framework, you can use it in your table definition to create a default value for the StatusID field.
CREATE FUNCTION "OID" (IN "aLinkTable" VARCHAR COLLATE "ANSI")
RETURNS INTEGER
BEGIN
DECLARE Crsr CURSOR FOR Stmt;
DECLARE Result INTEGER;
DECLARE ID VARCHAR;
PREPARE Stmt FROM
' SELECT
CAST(ID as VARCHAR) as ID,
OID
FROM "SystemDB"."OIDGenerator"
WHERE LinkTable = ? ';
OPEN Crsr USING aLinkTable;
FETCH FIRST FROM Crsr ('ID') INTO ID;
FETCH FIRST FROM Crsr ('OID') INTO Result;
EXECUTE IMMEDIATE
' UPDATE "SystemDB"."OIDGenerator"
SET OID = OID + 1
WHERE ID = ' + ID ;
RETURN Result;
END
VERSION 1.00!
The "OID" Function is built into the Framework, you can use it in your table definition to create a unique counting-field for each table, so that one column has a value where each new record in the table is one higher than the previous record. Note that "OID" fields are dependent on a database NOT using distributed data-structure.
CREATE FUNCTION "OrgCode" (IN "aID" INTEGER)
RETURNS VARCHAR(12) COLLATE "ANSI"
BEGIN
DECLARE Crsr CURSOR FOR Stmt;
DECLARE Result VARCHAR(12);
IF aID IS NULL THEN
SET Result = '';
ELSE
PREPARE Stmt FROM
' SELECT
OrgCode
FROM Organisations
WHERE ID = ? ';
OPEN Crsr USING aID;
FETCH FIRST FROM Crsr('OrgCode') INTO RESULT;
IF RESULT IS NULL THEN
SET RESULT = '';
END IF;
END IF;
RETURN Result;
END
VERSION 1.00!
CREATE FUNCTION "CustomerVATExempt" (IN "aID" INTEGER)
RETURNS BOOLEAN
BEGIN
DECLARE Crsr CURSOR FOR Stmt;
DECLARE Result BOOLEAN;
IF aID IS NULL THEN
SET Result = false;
ELSE
PREPARE Stmt FROM
' SELECT
VATExempt
FROM Customers
WHERE ID = ? ';
OPEN Crsr USING aID;
FETCH FIRST FROM Crsr('VATExempt') INTO RESULT;
IF RESULT IS NULL THEN
SET RESULT = false;
END IF;
END IF;
RETURN Result;
END
VERSION 1.00!
CREATE TRIGGER "CascadeLinkTableDeletes" BEFORE DELETE ON "Contracts"
BEGIN
EXECUTE IMMEDIATE
' DELETE FROM Comments WHERE LinkID = ' + CAST(OLDROW.ID AS VARCHAR(20)) +
' AND LinkTable = ''Contracts'' ';
END
The BusinessObjects record for this BusinessObject
INSERT INTO "BusinessObjects"
(Name, Color, BusObjType,
AddNewButton, LinkToImages, LinkToComments, LinkToAddresses,
LinkToPhones, SecurityLevel, Icon,
DisplayScript, ListScript, ViewScript, DiaryScript,
SummaryScript, Settings)
VALUES
('Contracts', 15519677, 'N', True, False, True, False, False, 0, 7,
' SELECT
IF(Complete = true THEN ''zzz\'' else '''') +
IF(Internal = true THEN ''I'' ELSE '''')
+ ''C-'' + CAST(OID AS VARCHAR) + '': ''
+ O.Name + '' '' +
+ C.Name AS Name,
ID
FROM "Contracts" C
LEFT JOIN Organisations O ON O.ID = C.CustomersID
WHERE ID = %d ', ' SELECT
IF(Complete = true THEN ''zzz\'' else '''') +
IF(Internal = true THEN ''I'' ELSE '''') +
''C-'' + CAST(OID AS VARCHAR) + '': ''
+ O.Name + '' '' +
+ C.Name as FullName, ID
FROM "Contracts" C
LEFT JOIN Organisations O ON O.ID = C.CustomersID
ORDER BY FullName ', 'SELECT
ID,
Internal,
ShortName,
Name,
DateStart,
DateEnd,
S.FullName as "Status",
T.Name as ContractsType,
O.Name as Customer,
SUM(CI1.WorkedHours) as HoursWorked,
SUM(CI1.BudgetQuantity) as BudgetQty,
SUM(CI1.BudgetValue) as BudgetValue,
SUM(CI1.BilledQuantity) as BilledQty,
SUM(CI1.BilledValue) as BilledValue,
IF(SUM(CI1.BudgetValue) = 0 THEN IF((SUM(CI1.WorkedHours) / SUM(CI1.BudgetQuantity) > 1) THEN ''Hours Exceeded'' ELSE ''Within Hours'')
ELSE IF(SUM(CI1.WorkedValue) / SUM(CI1.BudgetValue) > 1 THEN ''Over Budget'' ELSE ''Within Budget'')) as Budget,
IF(SUM(CI1.BudgetValue) = 0 THEN SUM(CI1.WorkedHours) / SUM(CI1.BudgetQuantity) * 100
ELSE(SUM(CI1.WorkedValue) / SUM(CI1.BudgetValue)) * 100) as PercentUsed,
MIN(TotalBilled) as TotalBilled, --multiple lines so just pick one, not the sum
MIN(TotalPaid) as TotalPaid, --multiple lines so just pick one, not the sum
P1.FullName as Staff,
DateCreated,
Current,
Complete
FROM "Contracts" C
LEFT JOIN Status S ON S.ID = C.StatusID
LEFT JOIN Types T ON T.ID = C.ContractsTypeID
LEFT JOIN People P1 ON P1.ID = C.LeadStaffID
LEFT JOIN Organisations O ON O.ID = C.CustomersID
LEFT JOIN
( SELECT
CI.ContractsID,
WI.ContractItemsID,
CI.BudgetQuantity,
CI.BudgetValue,
CI.BilledQuantity,
CI.BilledValue,
CI.PaidValue,
SUM(WI.HoursWorked) as WorkedHours,
SUM(WI.Value) as WorkedValue
FROM ContractItems CI
LEFT JOIN WorkItems WI ON CI.ID = WI.ContractItemsID
LEFT JOIN People P ON P.ID = WI.StaffID
GROUP BY ContractItemsID) as CI1 ON CI1.ContractsID = C.ID
LEFT JOIN
( SELECT
ContractsID,
SUM(ValueBilled) as TotalBilled,
SUM(ValuePaid) as TotalPaid
FROM ContractPayments
GROUP BY ContractsID ) as CP ON CP.ContractsID = C.ID
%s
GROUP BY C.ID',
'', 'SELECT
ContractsID as ID,
SUM(WorkedHours) as WorkedHours,
SUM(WorkedValue) as WorkedValue,
SUM(BudgetQuantity) as BudgetQty,
SUM(BudgetValue) as BudgetValue,
SUM(BilledQuantity) as BilledQty,
SUM(BilledValue) as BilledValue,
IF(SUM(BudgetValue) = 0 THEN IF((SUM(WorkedHours) / SUM(BudgetQuantity) > 1) THEN ''Hours Exceeded'' ELSE ''Within Hours'')
ELSE IF(SUM(WorkedValue) / SUM(BudgetValue) > 1 THEN ''Over Budget'' ELSE ''Within Budget'')) as Budget,
IF(SUM(BudgetValue) = 0 THEN ROUND(SUM(COALESCE(WorkedHours, 0)) / SUM(COALESCE(BudgetQuantity, 0)) * 100 TO 2)
ELSE ROUND(SUM(COALESCE(WorkedValue, 0)) / SUM(COALESCE(BudgetValue, 0)) * 100 TO 2)) as PercentUsed,
COALESCE(MIN(TotalBilled), 0) as TotalBilled, --multiple lines so just pick one, not the sum
COALESCE(MIN(TotalPaid), 0) as TotalPaid --multiple lines so just pick one, not the sum
FROM
(SELECT
CI.ContractsID,
CI.ID as ContractItemsID,
CI.BudgetQuantity,
CI.BudgetValue,
CI.BilledQuantity,
CI.BilledValue,
CI.PaidValue,
SUM(COALESCE(WI.HoursWorked, 0)) as WorkedHours,
SUM(COALESCE(WI."Value", 0)) as WorkedValue
FROM ContractItems CI
LEFT OUTER JOIN WorkItems WI ON CI.ID = WI.ContractItemsID
WHERE CI.ContractsID = %d
GROUP BY ContractItemsID) as CI1
LEFT JOIN
( SELECT
ContractsID,
ROUND(SUM(COALESCE(ValueBilled, 0)) TO 2) as TotalBilled,
ROUND(SUM(COALESCE(ValuePaid, 0)) TO 2) as TotalPaid
FROM ContractPayments
GROUP BY ContractsID ) as CP ON CP.ContractsID = CI1.ContractsID
GROUP BY ID', '' )
!
Note the use of the "%d" wildcard in various parts of the above scripts. This indicates where the Orixa App will insert an ID Field from the BusinessObject to generate a list.
Some examples of "Searches" "Types" and "Status" records for this BusinessObject
INSERT INTO Searches
(LinkTable, Name, SQLStr)
VALUES
('Contracts', 'Not Complete', 'Where Complete = false')!
-- INSERT SEARCHES DATA RECORDS --
INSERT INTO Searches
(LinkTable, Name, SQLStr)
VALUES
('Contracts', 'All Records', '')!
INSERT INTO Types
(Name, LinkField, LinkTable)
VALUES
('One-off', 'ContractsTypeID', 'Contracts') !
INSERT INTO Types
(Name, LinkField, LinkTable)
VALUES
('Annual', 'ContractsTypeID', 'Contracts') !
INSERT INTO Types
(Name, LinkField, LinkTable)
VALUES
('Quarterly', 'ContractsTypeID', 'Contracts') !
INSERT INTO Status
(Name, LogicalOrder, LinkTable)
VALUES
('In negotiation', 1, 'Contracts') !
INSERT INTO Status
(Name, LogicalOrder, LinkTable)
VALUES
('Active', 2, 'Contracts') !
INSERT INTO Status
(Name, LogicalOrder, LinkTable)
VALUES
('Complete', 3, 'Contracts') !
Some examples of "Resources" for this BusinessObject
INSERT INTO Resources
( ID, "Name", LocationID, ComponentID,
SQLStr, "Description", ObjectProperties,
SecurityLevel, LinkTable, TargetTable )
VALUES
(11375, 'ContractItemsDashboardGrid', TypeID('Program Resource'), TypeID('Grid'),
'SELECT
W.ID,
W.DateDone,
P2.FullName as "Staff",
W.WorkPlanned + '' '' + W.WorkDone,
T.Name as ItemType,
W.HoursWorked,
IF(C.Chargeable = true THEN CAST(C.UnitValue * W.HoursWorked AS DECIMAL(19, 4))
ELSE CAST(0.00 AS DECIMAL(19,4))) as "BillableValue",
C.Chargeable,
S.FullName as "Status",
C1.DatePaid,
P.Name as Product,
P1.FullName as LeadStaff
FROM ContractItems C
LEFT JOIN Products P ON (C.ProductsID = P.ID)
LEFT JOIN Types T ON (T.ID = C.ContractItemsTypeID)
LEFT JOIN People P1 ON (C.StaffResponsibleID = P1.ID)
LEFT JOIN Status S ON (C.StatusID = S.ID)
LEFT JOIN ContractPayments C1 ON (C.ContractPaymentsID = C1.ID)
LEFT JOIN WorkItems W ON (C.ID = W.ContractItemsID)
LEFT JOIN People P2 ON (W.StaffID = P2.ID)
WHERE C.ID = %d
',
'',
'',
0, 'Contracts', 'ContractItems' )!
INSERT INTO Resources
( ID, "Name", LocationID, ComponentID,
SQLStr, "Description", ObjectProperties,
SecurityLevel, LinkTable, TargetTable )
VALUES
(38091, 'Contracts Completion Dashboard', TypeID('Record'), TypeID('Chart'),
'SELECT
ContractItemsID as ID,
SUM(CI.BudgetQuantity) as SumBudget,
SUM(CI.BilledQuantity) as SumBilled,
SUM(WI1.Hours) as SumWorked,
SUM(WI2.Hours) as SumPlanned
FROM ContractItems CI
LEFT JOIN Types T ON T.ID = CI.ContractItemsTypeID
LEFT JOIN
( SELECT
ContractItemsID,
SUM(HoursWorked) as Hours
FROM WorkItems
WHERE DateDone < Current_Date
GROUP BY ContractItemsID ) AS WI1 ON CI.ID = WI1.ContractItemsID
LEFT JOIN
( SELECT
ContractItemsID,
SUM(HoursWorked) as Hours
FROM WorkItems
WHERE DateDone < Current_Date
GROUP BY ContractItemsID ) AS WI2 ON CI.ID = WI2.ContractItemsID
WHERE ContractsID = [BO Contracts]
GROUP BY ContractItems.ContractsID',
'',
'[General]
DateSaved=24/07/2020
SeriesCount=3
Title=Sum of Totals for Budget, Billing and Worked Hours
FooterText=
View3D=0
LegendVisible=1
AxisVisible=1
[Series0]
ArrowAxisFieldName=
XAxisFieldName=ID
YAxisFieldName=SumBudget
CalcOption=0
SeriesType=3
Title=SumBudget
Color=32768
ShowDataPoints=0
[Series1]
ArrowAxisFieldName=
XAxisFieldName=ID
YAxisFieldName=SumBilled
CalcOption=0
SeriesType=3
Title=SumBilled
Color=128
ShowDataPoints=0
[Series2]
ArrowAxisFieldName=
XAxisFieldName=ID
YAxisFieldName=SumWorked
CalcOption=0
SeriesType=3
Title=SumWorked
Color=8421376
ShowDataPoints=0
',
0, 'Contracts', 'ContractItems' )!
Note that the Resources data-table data includes both SQL to return data from the database and Decoration data (created by the user in the App) to describe how the Resource dashboard will display.
Accessing BusinessObject Details from the DB Utility
DB Utility Reviewing BusinessObjects
- DB Utility Open in App, this can be accessed from the "System", "Show DB Management Utility" menu.
- Select "BusinessObjects" from the Database Treeview.
- All the BusinessObjects in your App will be listed.
- The schema elements of the data-table are shown in detail.
- The SQL Definition is shown in full. This includes the "Description" field, which may contain Orixa Decorations and generate in-App behaviour.
Accessing BusinessObject Details from the BusinessObjects Edit Form
Once a BusinessObject has been created as a Data-table, with the correct data-structure needed by your App, it is then made visible in-App by adding a record to the BusinessObjects data-table.
To understand how the different items in this table works it can be easiest to open an example App and review the contents of different BusinessObjects Records.
Business Objects data-grid
You can see from the above image that the BusinessObjects framework-table includes fields for the Icon, Name, BusinessObjectType & Color to be used in the App, plus SQL scripts to control how / whether details of the BusinessObject appears in different parts of the App.
Structure of the BusinessObjects Framework-table
CREATE TABLE "BusinessObjects"
(
"ID" INTEGER DEFAULT UID() NOT NULL,
"Name" VARCHAR(60) COLLATE "ANSI" NOT NULL,
"Top" INTEGER DEFAULT 1 NOT NULL,
"Left" INTEGER DEFAULT 1 NOT NULL,
"Color" INTEGER
DESCRIPTION '[Properties]
DisplayControl=ColorCombo',
"DisplayScript" CLOB COLLATE "ANSI",
"ListScript" CLOB COLLATE "ANSI",
"ViewScript" CLOB COLLATE "ANSI",
"DiaryScript" CLOB COLLATE "ANSI",
"SummaryScript" CLOB COLLATE "ANSI",
"Settings" CLOB COLLATE "ANSI",
"BusObjType" VARCHAR(2) COLLATE "ANSI" DEFAULT 'N' NOT NULL
DESCRIPTION 'Distinct',
"AddNewButton" BOOLEAN DEFAULT true NOT NULL,
"LinkToImages" BOOLEAN DEFAULT false NOT NULL,
"LinkToComments" BOOLEAN DEFAULT false NOT NULL,
"LinkToAddresses" BOOLEAN DEFAULT false NOT NULL,
"LinkToEmailAddresses" BOOLEAN DEFAULT false NOT NULL,
"LinkToPhones" BOOLEAN DEFAULT false NOT NULL,
"SecurityLevel" INTEGER DEFAULT 0 NOT NULL,
"Icon" INTEGER,
"DateCreated" TIMESTAMP DEFAULT Current_Timestamp,
"DateEdited" TIMESTAMP DEFAULT Current_Timestamp,
"Current" BOOLEAN DEFAULT true NOT NULL,
"AcceptsLinks" BOOLEAN DEFAULT false NOT NULL,
"CreatesLinks" BOOLEAN DEFAULT false NOT NULL,
CONSTRAINT "PK_ConfigurationSettings" PRIMARY KEY ("ID"),
CONSTRAINT "NameUnique" UNIQUE ("Name")
)