Notification Rules
The Notification Rules screen is the write side of the notification system — a library of notification-rule resources that decide:
- when a notification fires (status code and / or rejection reason);
- what it says (subject and body, or the dispatcher defaults);
- to whom (a portal user / role, an email list, or both);
- on which channels (portal inbox, e-mail, external API call), with the rendered PDF attached to the e-mail by default.
Rules are matched by InvoiceStatusCatalog.StatusTransition.apply() after the database write of every status change, by the manual SetStatusModal, and by the CLI flows (-process, -fetch-import, -fetch-status, -fetch-single, -fetch-all). A failure in dispatch never aborts the underlying status update.
The read side of the system — the inbox where users acknowledge notifications and the navbar bell — is documented on the Notifications page.
The page applies regardless of source system — JD Edwards, SAP, NetSuite or a custom ERP. Triggers reference the standard statuses and rejection-reason-codes catalogues, not source-system-specific codes.
Opening the editor
- Sidebar → Management → Notification Rules.
- A fresh installation ships with no rules — the dispatcher emits nothing until the first rule is created. Use Add to create one.
At a glance
The page is split between the rule list on the left and the rule editor on the right. Each rule is a notification-rule resource saved in config-notifications.json; the dispatcher reloads them at startup and after every save.
Toolbar actions
| Action | Effect |
|---|---|
| Add | Opens a modal asking for a name and a description. Creates a new rule in disabled-when-empty state — no trigger, channels defaulted to portal, recipient type defaulted to user. |
| Copy | Duplicates the selected rule under a new name. Convenient for deriving a B2C variant from a B2B rule, or a per-region rule from a generic one. |
| Remove | Deletes the selected rule after a confirmation. |
| Save | Writes the editor state back to config-notifications.json and signals the dispatcher to reload. The next status change picks up the new rule. |
The rule list
Every rule in the catalogue appears with two visual cues:
- Description in italic underneath the rule name — the same field the Add modal asked for, free text.
on/offpill at the right of each row — bound to theenabledproperty. A rule withoffstays in the catalogue but is skipped at dispatch time. Useful while iterating on a rule without losing it.
A search box at the top of the sidebar filters the list by substring on the rule name.
The rule editor
The editor is structured as four section groups plus a synchronous test panel.
Trigger
The trigger decides when the rule fires. Two fields combine, both optional:
| Field | Source | Behaviour |
|---|---|---|
| Status | statuses catalogue | Comma-separated list of status codes (e.g. 9904,9907). The rule fires when the status code of the new transition is in this list. Empty = match every status. |
| Reason | rejection-reason-codes catalogue | Comma-separated list of rejection-reason codes (e.g. REJ_ADR,REJ_FMT). The rule fires only when the reason code of the new transition is in this list. Empty = match every reason. |
Both fields are surfaced as chip multi-selects — picking from a dropdown adds a chip; the × on a chip removes it. The dropdown is populated from the same statuses and rejection-reason-codes resources used by the Set Status modal and the invoice History tab, so a rule cannot reference a code that the application does not recognise.
When both fields are filled, both must match (logical AND) for the rule to fire.
Channels
Three boxes, any combination:
portal— writes a row inF564253for the recipient. The user sees it in the Notifications inbox and the bell.email— sends an SMTP message via the configurede-invoicingmail account.action— fires an outbound HTTP call against an API Connector endpoint.
A rule that emits zero channels is useless and rejected at save time.
Recipient
The recipient model has two independent halves: a portal target and an email list.
| Field | Description |
|---|---|
| Type | Picks the portal target — user (a single F564250 username), role (every user that carries this role on their URROLE value), or empty. When auth is disabled, the empty option reads Broadcast — all portal users and writes a single F564253 row under the * sentinel. |
| Value | The username or role name selected by Type. Free text — auto-completion comes from the connected database when available. |
| CC | Independent list of e-mail addresses, separated by , or ;. Each address goes on the To: header of the dispatched email. The portal target's USEMAIL, when present on its F564250 row, is added automatically. |
When the portal target carries a USEMAIL, the email channel sends to both that address and every entry in CC in a single SMTP transaction. When the F564250 lookup fails, the portal channel still emits (the row is keyed by the literal username), so the inbox stays populated even during a transient database outage.
Email content
| Field | Default | Description |
|---|---|---|
| Subject | Invoice {doc} {dct} {kco} — {statusLabel} | Subject line. Placeholders are resolved at dispatch time. |
| Body | Status: {statusLabel} \n Reason: {reasonLabel} \n Action: {actionLabel} | Plain-text body. Multi-line input. |
| Attach PDF | Y | Render the invoice PDF (via the resolved pdf-template) and attach it to the e-mail. The PDF is rendered once per dispatch and reused across every recipient; a render failure is logged but never fails the e-mail. |
Available placeholders in subject / body: {doc}, {dct}, {kco}, {statusCode}, {statusLabel}, {statusMessage}, {reasonCode}, {reasonLabel}, {actionCode}, {actionLabel}, {ruleName}, {message}.
The portal NTSUBJ uses the same subject; the portal NTMSGE defaults to just {statusLabel} because the inbox UI already shows the doc reference inline — duplicating it in the body would be noise.
Action call
When the action channel is enabled, three additional rows appear:
| Field | Description |
|---|---|
| Connector | Dropdown listing every api-connector template. Same set as on Process API. |
| Endpoint | Dropdown populated by api.connectors.listEndpoints(connector) once a connector is picked. |
| Parameters | Pre-populated from the endpoint's defined parameter list. Each row carries a key (locked) and a value (editable). Values may contain the same {placeholders} as the e-mail subject / body. |
The action call is fired in the same dispatch transaction as the portal write and the e-mail send. Failures are logged and do not abort the underlying status update or the other channels.
Test panel
A synchronous Test runner sits at the bottom of the form. It accepts a (doc, dct, kco) triplet, optionally a status code and a custom message, and actually fires the rule through every enabled channel — the portal write lands in the inbox, the e-mail goes out via SMTP, the action call is made. The resulting banner reports the dispatch counts (✓ Dispatched · 1 portal · 2 email) or the first error.
The test panel does not save the rule — only fires whatever is currently in the form. Use it to validate edits before clicking Save.
How a notification gets dispatched
When a status transition is applied (every database write that touches F564231.UHK74RSCD), the dispatcher walks the catalogue in three steps.
StatusTransition.apply()
↓
NotificationDispatcher.fire(doc, dct, kco, status, reason, action, message)
↓ — for each rule where enabled = Y
↓ match trigger.status (CSV) ∋ status
↓ AND match trigger.reason (CSV) ∋ reason (or trigger.reason = '')
↓ → resolve recipient (portal target + email list)
↓ → render the invoice PDF once if attachPdf = Y
↓ — for each enabled channel:
↓ • portal → INSERT into F564253
↓ • email → SMTP one message with everyone on To:
↓ • action → HTTP call to connector.endpoint with resolved params
↓ all exceptions caught — failures never abort the status update
The dispatcher uses a singleton with a 2-thread asynchronous worker pool, so the calling code returns immediately. A JVM shutdown hook drains the pool with a 2-second grace before the process dies, so CLI flows that exit right after a status update still deliver their notifications.
REST API
The page reads and writes via the standard template endpoints; the dispatcher exposes one extra route for the test panel.
| Method + path | Purpose |
|---|---|
GET /api/templates | Lists all templates; the page filters by type = notification-rule. |
GET /api/templates/{name} | Loads a single rule. |
POST /api/templates | Creates a new rule (Add). |
POST /api/templates/{from}/copy/{to} | Duplicates (Copy). |
PUT /api/templates/{name} | Saves edits. |
DELETE /api/templates/{name} | Removes a rule. |
POST /api/notifications/test | Fires the rule's payload synchronously against every enabled channel — used by the Test panel. |
Tips & best practices
- Start narrow, widen later. A trigger of
9904 + REJ_ADRis easier to validate than a catch-all'', and you keep the noise low while the recipient list and the body are still being tuned. - Use the Test panel before saving. Especially for the action channel — the dispatcher swallows failures, so a misconfigured connector silently no-ops at production time. The test runner surfaces the same error inline.
- Keep one rule per purpose, not per status code. Group several status codes behind a single rule when the body is identical (
9904, 9907 → Rejected); split into separate rules only when the recipient list or the body differs. - PDFs are heavy.
attachPdfrenders the invoice once per dispatch — fine for low-volume rules, expensive for fleet-wide alerts. Disable it on rules that fire on9900(just-created) or9901(validated), where the PDF rarely adds value. - Use
roleoveruserwhenever possible. A role-based recipient survives staff changes; auser-based one needs an edit each time the assignee leaves. The role list onF564251is the source of truth. - Disable, don't delete. While iterating, flip the rule
offinstead of removing it — the catalogue keeps the history, the dispatcher skips it, and the test runner remains available. - Read the inbox after a release. Rules sometimes drift from the codes they reference (a status renamed in the catalogue, a reason retired) — the Notifications page is the fastest cross-check that the production catalogue is still consistent with the rules in this page.