Created: 2nd September 2022
Amended: 19th May 2023
As well as the standard fields in the core database tables, the GM-X application also has the ability for the user to define additional fields for certain tables, then to assign values for entities in those tables. In order to provide this ability the current convention is to have the following additional tables in the core database:
The value XXX denotes the core table. The following tables are currently supported:
Note that in the REQUEST subsystem that QUOTES are derived from REQUESTS, therefore they share the same set of user-defined names.
Figure 1 - Entity-Relationship Diagram
<TABLE> is the core table in the GM-X database.
<TABLE>_EXTRA_NAMES identifies the extra field names.
<TABLE>_EXTRA_NAME_OPTIONS identifies the options when the extra_type is 'DROPDOWN'.
<TABLE>_EXTRA_VALUES identifies the values entered for each row in that table.
This identifies the extra column names that may be added to a database record.
Column Name | Type | Description |
---|---|---|
extra_id | string | Identifies the field name for a user-defined value. This should NOT be the same as a field name in the associated core table. |
extra_name | string | A user-friendly name for this field. |
extra_desc | string | A longer description for this field. |
extra_type | string | Identifies the data type for all values for this field. Options are:
|
is_required | boolean | A Yes/No flag. If YES then a value must be supplied. |
sort_seq | numeric | Identifies the order in which the fields should appear on the screen.. |
Note that additional fields mat be added if required, such as the following:
Column Name | Type | Description |
---|---|---|
applies_to | string | This limits the availability of this extra column. Examples are:
|
is_exportable | boolean | Indicates if this field can be included in a CSV export file. |
This identifies the options when the extra_type is 'DROPDOWN'.
Column Name | Type | Description |
---|---|---|
extra_id | string | Links to an entry on the <TABLE>_EXTRA_NAMES table. |
option_id | number | Option value assigned by the system. |
option_name | string | Description of the option value displayed in dropdown lists. |
sort_seq | number | Sort Sequence. A numeric value denoting the sequence in which this option appears in dropdown lists. |
This identifies the options when the extra_type is 'DROPDOWN'.
Column Name | Type | Description |
---|---|---|
<primary_key> | Same as the primary key of the core table to which it is attached. | |
extra_id | string | Links to an entry on the XXX_EXTRA_NAMES table. |
extra_value | string | This holds the value for this Extra Name with this entry on the core table. |
As these tables are in addition to the core tables each has its own set of maintenance tasks. The EXTRA_NAMES table has a set of tasks which start with a LIST1 screen while the EXTRA_VALUES table starts with a LIST2 screen where the outer entity is the core table for which it holds extra values. In some cases there may be an UPDATE3 screen which shows, for a selected object, all the EXTRA_NAMES entries plus the current value for each of those entries. This allows all the entries to be viewed and input or amended on a single screen. Any name without a value will be automatically deleted from the EXTRA_VALUES table.
It is also possible to provide a custom version of a standard screen which includes a selection of user-defined fields as well as the core fields. As well as a custom screen this also requires custom processing to both retrieve values from the EXTRA_VALUES table and to update them afterwards. A current example can be found in the PRODUCT subsystem where extra fields for LENGTH, WIDTH, HEIGHT and WEIGHT are shown in the detail screens.
A comment has been received from some clients who have made use of the existing ability for user-defined fields that it is not very user-friendly as it requires too many keystrokes to view and update these extra fields. They would like to see any user-defined fields to automatically appear in the relevant detail screens so that they can be viewed and maintained along with the core fields. They would also like to be able to search on any of these user-defined fields.
In order to make the handling of user-defined fields as easy as possible it has been decided that for any entity which has entries on its EXTRA_NAMES table these should automatically be displayed on that entity's DETAIL screens, those which show a single row. These are provided by the following patterns:
In order to minimise the amount of extra coding in those table classes which are affected by this change the majority of code has been inserted into framework components as follows:
This approach therefore requires the minimum of effort to make this new functionality available in those table classes which are affected. There are only two steps:
require_once 'std.table.class.inc'; require_once 'include.user_defined_fields.trait.inc'; class XXXX extends Default_Table { use user_defined_fields; // include methods from this trait ...Without these extra lines of code the relevant _udf_XXX methods cannot be called.
The following methods are available in each table class which has included the trait for user-defined fields mentioned previously.
In order to be processed as normal fields the relevant entries must be added to the standard $fieldspec array which is populated from the relevant <table>.dict.inc file which is produced from the Data Dictionary. This requires a procedure to extract all the field names from the EXTRA_NAMES table and then build the relevant entries which can be added to the $fieldspec array. Rather than reading from the database, each time the table class is loaded the processing inside the EXTRA_NAMES class has been amended to create a separate file called classes/custom-processing/<project_code>/<table>_extra_names.colspecs.<project_code>.inc to contain this information. This file will automatically be rewritten each time a record is added, updated or deleted from the EXTRA_NAMES table.
Each time the core table is instantiated into an object this procedure will automatically be called before the _cm_changeConfig() method in order to load the contents of the colspecs file. Note that this file may not exist or may even be empty. This data will be loaded into $this->udf_fieldspec as well as being appended to the standard $this->fieldspec array.
The standard <table>.<pattern>.screen.inc files produced by the Data Dictionary, which may also be customised later, do not by default contain any of the fields identified in the EXTRA_NAMES table. As this file is loaded into memory at the start of each task before it is processed by the $view->buildXML() method at the end of the task there is ample time to amend the version in memory before it is processed.
This method will automatically add references to any user-defined fields into the current screen structure. These will be inserted before any refence to created_date. The value in the extra_name column will be used as the field label.
Note that if any of these fields has already been added to a customised screen structure file then it will not be duplicated.
Instead of reading the contents of the EXTRA_VALUES table in a separate operation after the call to the standard getData() method this procedure will automatically adjust the SQL query so that it will read all the data in a single combined operation. It will do this by updating the SELECT string of the database query to include the following for each user-defined field:
SELECT extra_value FROM $udf_tablename AS UDF WHERE $link AND UDF.extra_id='$extra_id') AS $extra_id
$udf_tablename is the name of the EXTRA_VALUES table.
$link is where the primary key of the core table to the primary key of the EXTRA_VALUES table.
$extra_id is the name of the user-defined field. Note that this name must be a valid SQL name.
In some circumstances it may not be possible to adjust the SQL query as outlined above, so this method will access the EXTRA_VALUES table in a separate operation and add any values found to the current result set.
The search screen will now include any user-defined fields if they exist for that table. However, upon returning to the LIST screen the search criteria will have to be adjusted otherwise the resulting query will be invalid and will cause a fatal error. This method will then examine the search string and where it finds
extra_id='extra_value'with
EXISTS(SELECT 1 FROM $udf_tablename WHERE $link AND extra_id='$extra_id' AND extra_value='$extra_value')
$udf_tablename is the name of the EXTRA_VALUES table.
$link is where the primary key of the core table to the primary key of the EXTRA_VALUES table.
$extra_id is the name of the user-defined field. Note that this name must be a valid SQL name.
$extra_value is the value entered in the search screen.
During any amendment to the core table, either through an insert, update or delete operation, the data array may contain entries on the associated EXTRA_VALUES table. This method will identify and extract any references to the user-defined fields, then compare any new value will the current value and perform the appropriate insert/update/delete operation. Note that if a value is changed to NULL the corresponding database record will be deleted.
This method has been added to script includes/std.extra_names.class.inc in order to recreate the colspecs file (refer to _udf_getColumnSpecs() above) each time an insert, update or delete operation is performed on an EXTRA_NAMES or EXTRA_NAME_OPTIONS table. This file will contain additions to the standard $fieldspec array as well as the $lookup_data array.
To create the classes for the additional tables you will need to do the following in order to inherit the pre-written code:
Change the initial lines to the following:
//require_once 'std.table.class.inc'; //class <table>_extra_names extends Default_Table require_once 'std.extra_names.class.inc'; class <table>_extra_names extends Extra_Names
Change the initial lines to the following:
//require_once 'std.table.class.inc'; //class <table>_extra_name_options extends Default_Table require_once 'std.extra_name_options.class.inc'; class <table>_extra_name_options extends Extra_Name_Options
Change the initial lines to the following:
//require_once 'std.table.class.inc'; //class <table>_extra_values extends Default_Table require_once 'std.extra_values.class.inc'; class <table>_extra_values extends Extra_Values
There may be circumstances where the standard processing for user-defined fields needs to be adjusted. I have encountered several such areas in my development and testing of this new functionality:
function __construct () { parent::__construct(); // switch directory name for the retrieval of message text $this->dirname = dirname(__file__); // switch table names for extra_names and extra_values $this->nameof_extra_names_db = 'finance_ap'; $this->nameof_extra_names_tbl = 'supplier_invoice_extra_names'; $this->nameof_extra_values_db = 'finance_ap'; $this->nameof_extra_values_tbl = 'supplier_invoice_extra_values'; } // __construct
if (!empty($this->udf_fieldspec)) { // hide entries which do not apply to this party_type foreach ($this->udf_fieldspec as $field => $spec) { if (!empty($spec['party_type']) AND !empty($fieldarray['party_type'])) { if ($fieldarray['party_type'] == 'ORG' AND preg_match('/^(ORG|ALL)$/i', $spec['party_type'])) { unset($this->fieldspec[$field]['nodisplay']); // unhide this entry } elseif ($fieldarray['party_type'] == 'PER' AND preg_match('/^(PER|ALL)$/i', $spec['party_type'])) { unset($this->fieldspec[$field]['nodisplay']); // unhide this entry } else { $this->fieldspec[$field]['nodisplay'] = 'y'; // hide this entry } // if } // if } // foreach } // if
if (!empty($this->udf_fieldspec)) { // remove entries which do not apply to this table foreach ($this->udf_fieldspec as $field => $spec) { if (!empty($spec['applies_to'])) { if (!preg_match('/^(R|B)$/i', $spec['applies_to'])) { unset($this->udf_fieldspec[$field]); unset($this->fieldspec[$field]); } // if } // if } // foreach } // ifThis requires code similar to the following in the _cm_changeConfig() method of the QUOTE_HEADER class:
if (!empty($this->udf_fieldspec)) { // remove entries which do not apply to this table foreach ($this->udf_fieldspec as $field => $spec) { if (!empty($spec['applies_to'])) { if (!preg_match('/^(Q|B)$/i', $spec['applies_to'])) { unset($this->udf_fieldspec[$field]); unset($this->fieldspec[$field]); } // if } // if } // foreach } // if
Date created: 2nd September 2022
19th May 2023 | Added the <TABLE>_EXTRA_NAME_OPTIONS table. |
Copyright © 1999-2025 by Geoprise Technologies Licensing, All Rights Reserved.