PaymentConstructors
The base plugin of the Payment Constructor cluster: it manages the payment constructors themselves and owns the extension points that the fields and reports plugins — and the host application — build on.
Overview
A payment constructor is a named, workspace-scoped configuration of a payment run. It carries a caption, an owner, a status, an SQL filter fragment and — through the sibling plugins — a set of fields and generated reports.
This plugin provides the managed list of constructors and, more importantly, the cluster-wide contracts the rest of the cluster and the host plug into. It is the piece that must exist first; fields and reports layer onto a constructor.
Requirements
The three plugins of the cluster work only together.
Each plugin class names the other two plugins in @pluginDependency.
| Requirement | Why the plugin needs it |
|---|---|
PaymentConstructorFields plugin |
@pluginDependency, and the module dependency in getDepends() |
PaymentConstructorReports plugin |
@pluginDependency |
ModelFilters plugin |
@pluginDependency; the Filters page and the Duplicate action use it |
Modules plugin |
The plugin is a module. The install SQL writes to modules and module_options. |
Jimbo system plugin |
The settings db_datetime_format and users_table |
Theme package (Festi\Theme) |
DisplayPlugin and StageBuilder |
Tables users, users_types, festi_* |
See the list below |
The plugin uses the tables of the project as follows:
payment_constructors.id_userhas a foreign key tousers.id.- The list shows the owner from the column
users.full_name. - The install SQL gives the sections to the user type with the ident
admin.
The repository has no init.php file.
Installation
This is the base plugin. Install it first.
The other two plugins have foreign keys to the payment_constructors table.
Cluster install order:
PaymentConstructors(this plugin)PaymentConstructorFieldsPaymentConstructorReports
Install the plugin with the Festi CLI:
./vendor/bin/festi-plugin --mode install --path ./src/dashboard/ --dump ./dumps/ \
--plugin PaymentConstructors \
--repository [email protected]:FestiPlugins/php_festi_plugin_payment_constructors.git
This CLI example was not run for this page.
Set --path to the directory that contains the project config.php.
Set --dump to the dumps directory of the project.
The command does these steps:
- It adds the repository to the plugins directory as a git submodule.
- It copies
install/install.<database type>.sqlinto the dumps directory as a new migration. - It applies the migrations that the database does not have.
Commit the submodule and the new migration together.
On each other database of the project, apply the migration with ./vendor/bin/festi-migrate.
The install/ directory contains install.mysql.sql, install.pgsql.sql and install.mssql.sql.
The install SQL makes these objects:
- the
payment_constructorstable; - the plugin row in
festi_plugins; - the module
payment_constructorsinmodules; - the module option
workspace_url_segmentinmodule_options; - six URL rules in the area
backend; - four sections, with the permission for the user type
admin.
| Section | Use |
|---|---|
payment_constructors_list |
The list of constructors |
payment_constructors_filters |
The Filters page and the filters field |
payment_constructors_duplicate |
The Duplicate action |
payment_constructors_report_regenerate |
The PaymentConstructorReports plugin uses it for the Regenerate and Logs actions of the reports grid |
Configuration
Workspace URLs
Every cluster page answers on two routes: the bare one (/payment/constructors/...)
and a workspace-scoped one that carries the workspace id
(/workspace/{id}/payment/constructors/...). Which one a link points at is decided
at render time by IPaymentConstructorFacade::getWorkSpaceUrl(): with no active
workspace it builds the bare URL, with one it prepends the prefix.
Nothing else in the cluster hardcodes a URL. Plugins call the facade directly; stores
build their tblDef links through it and hand them to the definition as parameters
(filtersUrl, duplicateUrl, constructorsUrl, previewReportsUrl, reportLogsUrl,
fieldsUrl), so a tblDef never resolves a URL itself.
Renaming the segment. workspace is only the default. A host that mounts its
workspaces elsewhere sets the workspace_url_segment module option on the
payment_constructors module and the whole cluster follows; Prosvita, for example,
sets it to company so cluster pages sit under /company/{id}/... alongside the
rest of that application.
The option is deliberately is_workspace = FALSE, is_user = FALSE: the segment has
to match the route patterns in festi_url_rules, so it is a deployment-level
setting and never appears in a workspace administrator's module options.
A change of the segment has two parts. Both parts are necessary:
- Set the default value of the option to the new segment.
- Change the pattern of the nine workspace routes of the cluster.
The nine routes are three routes of this plugin, two of PaymentConstructorFields and four of
PaymentConstructorReports.
Do not change the rows by hand in a database client. Make a migration file in the dumps directory of the project:
./vendor/bin/festi-make-migration --dump ./dumps/
Put the two statements in the new file.
This example changes the segment to company:
UPDATE module_options
SET default_value = 'company'
WHERE name = 'workspace_url_segment'
AND id_module = (SELECT id FROM modules WHERE ident = 'payment_constructors');
UPDATE festi_url_rules
SET pattern = REPLACE(pattern, '~^/workspace/(?:', '~^/company/(?:')
WHERE plugin IN ('PaymentConstructors', 'PaymentConstructorFields', 'PaymentConstructorReports')
AND pattern LIKE '~^/workspace/(?:%';
Apply the migration:
./vendor/bin/festi-migrate --path ./src/dashboard/ --dump ./dumps/
This migration example was not run for this page.
The workspace id in those patterns sits in a non-capturing group —
~^/workspace/(?:[0-9]+)/payment/constructors/([0-9]+)/filters/$~ — so it never
enters the router's match list and the handlers keep receiving the constructor id
as their only argument. Copying the pattern with a capturing group instead would
hand every handler the workspace id where it expects the constructor id.
Permissions on a multi-workspace host
The seed grants each section to the
user type with ident admin, which is the right default for a host with one
global role set. A host that keeps a separate role set per workspace
(users_types.id_workspace) has to repeat those grants against its own
per-workspace roles — otherwise the pages install correctly but no workspace user
can open them. On such a host ident is not unique either, so the follow-up
migration must qualify the role lookup with id_workspace.
Write those grants as a migration file of the project.
Apply the migration with ./vendor/bin/festi-migrate.
Usage
The list lives at /payment/constructors/, or under the workspace prefix on a host
that runs workspaces — see Workspace URLs (a "Payment Constructors"
item under the Finance sidebar). Each constructor has:
- a caption and a status (
active/disabled). The list highlights a disabled constructor with the CSS classm-row-redand removes its Preview Reports action; - an owner, set to the current user on create;
- a filter: an SQL
WHEREfragment in thefiltersfield of the form. The field needs thepayment_constructors_filterssection; - a Filters action that opens the ModelFilters editor for the constructor. The editor offers the filterable columns of the payment-user source table;
- a Duplicate action that opens the Add form with the caption, status and filter of the source constructor. After the save, the plugin copies the ModelFilters filters, and the listeners copy the related data of the fields and reports plugins.
The plugins of the cluster do not apply the filter to the users.
The host selects the users in its IPaymentUserProvider (see PaymentConstructorReports).
The sibling plugins add more actions to each row. The row actions have this sequence: Calculations, Preview Reports, Fields, Filters, Logs, Edit, Duplicate.
Workspace scope. With an active workspace, the list shows only the constructors of
that workspace, and a new constructor gets the workspace id. Without an active
workspace, the list has no workspace filter, and a new constructor gets no workspace id.
The Filters and Duplicate pages are strict: getByIDInWorkSpace() refuses a
constructor whose workspace is not the current one.
Filter safety. Before a filter is saved it is checked two ways: a blacklist
rejects mutating keywords (DELETE, ALTER, INSERT, DROP, UPDATE, CREATE,
BACKUP), and the fragment is test-run as a WHERE clause to confirm it is valid SQL.
The test-run reads the table that the system plugin setting users_table names.
The blacklist check is case-sensitive: it finds a keyword only when the keyword is in upper case.
The store does not save a fragment that fails one of the two checks.
Host integration
Like the rest of the cluster, this plugin stays host-agnostic and lets the host fill in what it alone knows, through framework events and observer hooks:
- Who the users are —
CreatePaymentUserSourceProviderEventlets a host return anIPaymentUserSourceProviderdescribing the source table, the user key, an optional workspace key, an allowed-tables whitelist and an optional list of printable columns. With no host, the default targets the coreuserstable keyed onid. The Filters page, the field typesgeneralandsql, and the calculation read this configuration. - Page chrome —
CreatePaymentNavigationProviderEventlets a host apply its own active-menu and breadcrumbs to cluster pages. With no host it is a no-op. - Extra store actions and fields —
IPaymentConstructorsStoreInitListenerlets satellite plugins add their own actions and fields to the constructors grid (this is how the reports plugin injects Preview/Logs, and how a host adds a constructor column of its own). A contributed action arrives after the model has loaded, so it never passes through the attribute defaults the model fills in for its own tblDef actions, while the grid reads every attribute unconditionally. Hand it toContributedStoreAction::getPrepared($model, $action)— it runs the array through the model's own action-attribute spec — instead of restating the attribute list at each call site. - Duplicate defaults —
ICollectPaymentConstructorDuplicateFieldsListenerlets a plugin that contributed its own field pre-fill it on the duplicate form, the way the engine pre-fills caption, status and filter. - Duplicate follow-up —
IPaymentConstructorDuplicateListenerfires after a constructor is duplicated, so dependent plugins can copy their own related data onto the new constructor.
The host-facing facade IPaymentConstructorFacade has these methods:
| Method | Result |
|---|---|
getPaymentUserSourceConfig() |
The configuration of the payment-user source table |
getPaymentUserSourceColumns() |
The columns that a general field can print, as table.column => label |
isColumnExistInTable(), isCorrectSQL() |
Checks that the sibling plugins use for validation |
getByID(), getByIDInWorkSpace() |
One constructor; the second method refuses a constructor of a different workspace |
create() |
A new constructor; the method sets cdate and mdate |
getByWorkSpaceID(), getActiveInWorkSpaces() |
Lists of constructors for a host that runs a schedule |
getCurrentWorkSpaceID(), getWorkSpaceUrl() |
The current workspace id and a cluster URL with the workspace prefix |
Notable behavior
- Active in both scopes, and dependency-aware — implements
IWorkSpaceModuleandISystemSpaceModule, so the module resolves as active on a host that runs workspaces and on one that does not. The two dependency lists are not the same list:@pluginDependencynames all three siblings (PaymentConstructorFields,PaymentConstructorReports,ModelFilters) so they are reachable as$this->plugin->…, while the module-levelgetDepends()names onlyPaymentConstructorFields— the reports module declares the dependency in the other direction, and ModelFilters is a library with no module at all. - A constructor is a ModelFilters model — its value object implements
IModelFilter(model typepayment_constructor), which is what lets the Filters action reuse the generic ModelFilters editor instead of a bespoke one. - Engine-clean schema — the install owns only the
payment_constructorstable. The table has one foreign key, fromid_usertousers.id, and no host-specific columns. The cross-plugin foreign keys are added by the fields and reports plugins, which is exactly why this plugin installs first.