Patterns - quandis/qbo3-Documentation GitHub Wiki
QBO includes patterns across many of our modules, including:
Pattern | Description |
---|---|
Naming | Standard naming patterns and terminology. |
GenericObject | Class that can be bound to any parent. |
Templates | A template describes how an instance should behave. |
User Interface | Common patterns used when implementing the user interface. |
Data Table Inheritance | Normalization of data and functionality across tables. |
History | Audit trail pattern implemented by the database. |
First, some terminology:
Term | Definition |
---|---|
Table | A table persisted in a database. E.g. the Message table stores messages. |
Module | An application tier class, written in C#, that uses a Table as a backing store. E.g. the Message module handles reading and writing data from the Message table. Modules are also referenced as {ClassName} . |
Operation | A method, service, or statement executed by a Module. E.g. the Save method of the Message module saves message data to the Message table. |
Method | An operation implemented in a module (C# code). E.g. Message/Send emails a message. |
Service | An operation implemented in a plugin (.NET code). E.g. Attachment/ToPdf converts a document to PDF. |
Statement | A database query. E.g. the Insert statement of the Message module inserts a row into the Message table. |
Method Signature | The combination of a module and operation. E.g. Message/Save . |
ID | Identifier for a row in a table. ID can be used interchangeably with {Table}ID; that is Contact/Select?ID=1 is the same as Contact/Select?ContactID= . |
QBO's API rigorously follows naming patterns. However, due to popular demand, there are a handful of exceptions within the UI (and documentation):
UI Label | Module | Notes |
---|---|---|
Workflow | Decision | 'Flow charting' workflow is implemented by our Decision module. |
Document | Attachment | Document imaging and generation (mail merge) is implemented by our Attachment module. |
Task | ImportForm | User tasks, including user-defined fields, are implemented by our ImportForm module. |
Accounting | Ledger | Accounting functionality is implemented by our Ledger module. |
Core QBO classes such as Attachment
, Contact
, Decision
, Ledger
, Message
, etc., can be bound to any parent object using 'soft' keys comprising a pair of attributes: Object
and ObjectID
.
For example:
// Bind a message to Contact 1
Message/Save?Subject=Foo&Object=Contact&ObjectID=1
// Bind a message to Ledger 17
Message/Save?Subject=Bar&Object=Ledger&ObjectID=17
// Bind a message to Attachment 22
Message/Save?Subject=Bar&Object=Attachment&ObjectID=22
For the SQL geeks:
-- See all messages associated with ContactID 1
SELECT * FROM Message WHERE Object = 'Contact' AND ObjectID = 1
-- See all messages associated with Attachment 22
SELECT * FROM Message WHERE Object = 'Attachment' AND ObjectID = 22
QBO usage of the Object
/ ObjectID
attributes is pervasive.
Templates are used to drive the behavior of instances, with a template row paired with instance row.
Template | Instance | Description |
---|---|---|
AttachmentTemplate | Attachment | A document template determines how to perform a mail merge, what IFileObject repository to store the document in, when to archive and expire the document. |
DecisionTemplate | Decision | A workflow template determines how to implement a flowchart. |
ImportFileTemplate | ImportFile | An import template determines how to process (import) data uploaded as a file to QBO. |
ImportFormTemplate | ImportForm | A task template determines how to render a task, including user-defined fields. |
ProcessTemplate | Process | A process template determines what Workflow to launch, Ledger to create, and Score to calculate when a process is created. |
The ITemplate
interface provides functionality for templated classes, such as ProcessTemplate
, ImportFormTemplate
, AttachmentTemplate
, and DecisionTemplate
. Each of these classes determines the behavior of an instance class (Process
, ImportForm
, Attachment
, Decision
, etc.).
All template classes implement the ITemplate
interface, defining:
Attribute | Description |
---|---|
AppliesTo | Name of the parent object the template applies to (e.g. a workflow may apply to a Loan). |
Repeatable | Defines how often an instance may occur per parent (repeatability). |
CustomStatusList | Defines whether an instance has a template-driven status list. |
CustomTypeList | defines whether an instance has a template-driven type list. |
ExtranetEnabled | Determines if an instance requires need dedicated access control, or inherits from it's parent record. |
The ITemplate.Repeatable
attribute defines how often an instance may occur per parent:
- Once: only one instance per parent is allowed
- One active: only one active instance per parent is allowed
- Many: may instances per parent are allowed|
When the GenericObject.SetProperties
sees parameters including Object
, ObjectID
and Template
, a SelectByTemplate
statement is called, and these statements are designed to pay attention to the template table's Repeatable
column and the instance table's 'active' column(s). For instance, ImportForm/SelectByTemplate
works as follows:
-
ImportFormTemplate.Repeatable
= 0: if anImportForm
row based on the template exists, it is selected ("reused") -
ImportFormTemplate.Repeatable
= 1: if anImportForm
row based on the template and with noActualCompletion
date exists, it is selected ("reused") -
ImportFormTemplate.Repeatable
= 2: no existingImportForm
row will be selected, effectively creating a new row
The QBO UI leverages API calls using a standard Javascript
library. For any given module, we typically implement the following UI components:
Operation | Description |
---|---|
Home | A home page used to search for or otherwise manage all rows of a table. E.g. Message/Home manages all messages, Attachment/Home manages all documents, etc. |
Summary | A page used to manage a single row of data, it's ancestors and descendants. E.g. Loan/Summary?ID=1 display a Loan , it's parent Property , and children such as Borrowers , Messages , Attachments , Processes and more. The summary page controls what panels are displayed for a given record, including a 'main' panel that calls the Select UI. |
Select | A panel used to display a single instance of a module. E.g. Contact/Select?ID=1 displays the Contact properties for the row where ContactID=1 . |
Edit | A panel used to edit a single instance of a module. E.g. Contact/Edit?ID=1 enables updating of the Contact properties for the row where ContactID=1 . Contact/Edit (without any ID) enabled creating a new Contact row. |
Search | A panel used to display multiple rows of data from a table. |
Dashboard | A panel used to display roll-up statistics of data in a table, such as a chart or a pivot table. |
When looking at a Summary page, the source of the data display is generally obvious. For example, when viewing Contact/Summary?ID=1
:
- The data at the top of the page is coming from the
Contact
table:FirstName
,LastName
, etc. - The data in each of the generic panels at the bottom of the page are straight forward:
- The Messages panel is displaying rows from the
Message
table that apply toContactID=1
- The Documents panel is displaying rows from the
Attachment
table that apply toContactID=1
- The Workflow panel is displaying rows from the
Decision
table that apply toContactID=1
- The Messages panel is displaying rows from the
Other modules may have more complex relationships. For example, Loan/Summary?ID=1
:
- The data at the top of the page is primarily coming from the
Loan
table- The
Address
data is coming from theProperty
table
- The
- If the record has a parent object, QBO will display a tab for that parent
- E.g.
Loan
has a parentProperty
, soLoan/Summary
will include aProperty
tab display data from theProperty
table
- E.g.
- The data in each of the generic panels at the bottom of the page are straight forward (see above).
If you're curious about where a particular data point comes from, leverage QBO's API:
-
Loan/Summary?ID=1
will render the standard UI, but -
Loan/Summary?ID=1&Output=Xml
will render all the data associated withLoanID=1
-
Output=Json
if you prefer JSON
-
In the sample
XML
snippet below, you can see that data is available from theBorrower
,Loan
, andProperty
tables.
<LoanCollection>
<BorrowerItem>
<BorrowerID>43733</BorrowerID>
<Borrower>Bobby Bankrupt</Borrower>
<LoanID>401504</LoanID>
<ContactID>108183</ContactID>
<Contact>Bobby Bankrupt</Contact>
<FirstName>Bobby</FirstName>
<LastName>Bankrupt</LastName>
</BorrowerItem>
<LoanItem>
<LoanID>1</LoanID>
<Loan>123456789</Loan>
<PropertyID>170283</PropertyID>
<Address>30 Woodlawn Avenue</Address>
<City>Waltham</City>
<State>MA</State>
<PostalCode>02451</PostalCode>
<Property>30 Woodlawn Avenue, Waltham, MA 02451</Property>
</LoanItem>
<PropertyItem>
<PropertyID>170283</PropertyID>
<Property>30 Woodlawn Avenue, Waltham, MA 02451</Property>
<Address>30 Woodlawn Avenue</Address>
<City>Waltham</City>
<State>MA</State>
<PostalCode>02451</PostalCode>
<Latitude>42.386653</Latitude>
<Longitude>-71.248708</Longitude>
</PropertyItem>
</LoanCollection>
QBO implements a normalized inheritance pattern in the data tier.
The Contact
table provides for name, address and latitude/longitude information. Rather than replicate these fields across multiple table and modules, the following tables 'inherit' from Contact
by including a ContactID
column:
Table | Description |
---|---|
Organization | Organizations represent corporations or similar entities, frequently driving drop down lists. |
Broker | Brokers perform real estate transactions, with insurance and verification columns. |
Person | Persons are users in QBO, with login and password columns. |
SELECT *
FROM Organization
INNER JOIN Contact ON Contact.ContactID = Organization.ContactID
The Process
table backing the Process module provides for open and closed dates, outsourcing to external vendors, tracking workflows and accounting. Many tables in QBO inherit from Process
.
Table | Description |
---|---|
Auction | Manage the auction of a property. |
Bankruptcy | Manage a bankruptcy filing. |
BidsAtSale | Manage bidding for a property at a foreclosure sale. |
CashForKeys | Manage the process of paying a borrower to move out of a property for cash. |
Closing | Manage a real estate closing process. |
Deficiency | Manage a deficiency judgement process. |
Eviction | Manage the process of evicting people from a property. |
Foreclosure | Manage the process of foreclosing on a property. |
Litigation | Manage a court litigation filing. |
Offers | Manage an offer for the sale of a property. |
REO | Manage the process of selling a real estate owned property. |
Title | Manage the process of ordering and receiving title insurance on a property. |
Valuation | Manage the process of procuring a valuations (appraisal, broker price opinion, automated value) on a property. |
For the data geeks, some sample queries help convey these relationships:
-- See all Bankruptcies that have no ClosedDate
SELECT *
FROM Bankruptcy
INNER JOIN Process ON Process.ProcessID = Bankruptcy.ProcessID
WHERE Process.ClosedDate IS NULL
-- See all Closings with Status = 'Active' in California
SELECT *
FROM Closing
INNER JOIN Process ON Process.ProcessID = Closing.ProcessID
INNER JOIN Property ON Property.PropertyID = Closing.PropertyID
WHERE Process.Status = 'Active'
AND Property.State = 'CA'
-- See all processes outsourced to Acme Lawfirm; this would include Bankruptcy, Foreclosure, Eviction, and other such rows.
SELECT *
FROM Process
INNER JOIN Organization ON Organization.OrganizationID = Process.AssignedOrganizationID
WHERE Organization.Organization = 'Acme Lawfirm'
QBO data tables include a trigger to populate a corresponding {Table}History
with any changes made to the table. For example:
Table | History Table |
---|---|
Attachment |
AttachmentHistory |
Contact |
ContactHistory |
Decision |
DecisionHistory |
etc... |
This pattern includes:
- A column in the history table matching each table column except for
Created*
andUpdated*
- A
RevisionDate
column timestamped by the trigger to indicate the date the change was made - A
RevisionPersonID
column stamped by the trigger indicating the user causing the change- this requires that the
UserID
be available viaSET CONTEXT
; see standardUPDATE
statements
- this requires that the
History tables provide an audit trail of changes, and are not intended for OLTP queries. They are useful for manual review on a case-by-case basis.
History tables should be purged frequently; 3 months of history is generally recommended but may be modified according to a client's data retention requirements.