Skip to content

Stock Level Monitoring Applet

Overview

Stock Level Monitoring watches the quantity on hand at each location against an optimal level and a maximum level that you set per item category, and e-mails a list of people when anything falls below or rises above them. It is a pure alerting applet: it reads stock balances and writes nothing back to inventory, creates no documents and posts no journals.

Think of it as the warning half of Stock Replenishment. The two applets share the same template → event → run shape and the same Rules Logic / Negation Logic controls (this applet’s own source says the controls were ported from there). Replenishment acts — it raises purchase orders and transfers. Monitoring only tells you.

Where it fits

UpstreamThis appletDownstream
Item categories (inventory labels) and the items carrying them, from Item MaintenanceTemplate — which categories to watch, at which optimal and maximum levels, and the e-mail subject and body to send
Companies and locations from the Organisation AppletTemplate and run scope
Entities with e-mail addresses (customers, suppliers, contacts)The recipient listThe alert e-mail
bl_inv_current_location_stock_balance — the per-location balance the Stock Balance Applet reports onRun — reads qty_ledger per item per location and records breachesAlert e-mail, and the run’s own property_json
Event — a start date and, optionally, a templateOne queued run

Screens and menus

MenuRouteWhat it listsActions
Level Monitoring Runsstock-level-monitoringbl_inv_stock_level_monitoring_run_hdr — Run Name, Description, Created Date, Updated Date, StatusOpen a run; Run Processor (re-run it now); Save; Delete
Level Monitoring Eventsstock-level-monitoring-eventsbl_inv_stock_level_monitoring_event_hdr — Event Name, Event Code, Created Date, Updated Date, StatusCreate, open, delete
Level Monitoring Templatestock-level-monitoring-templatebl_inv_stock_level_monitoring_template_hdr — Template Code, Template Name, Created Date, Updated Date, StatusCreate, open, delete
Settingssettings/field-settings, settings/default-selection, settings/release-notes, plus the shared Webhook, Feature Visibility and permission screensSee Configuration
Personalizationpersonalization/personal-default-selection, personalization/sidebarPer-user defaults, sidebar

The Template and Run screens each open into a view with four sub-sections: Locations, Companies, Categories, and (on a run) Emails. The Event view has Locations, Companies, Categories and Entities. Categories is the one that carries numbers — one row per item category, with an editable Optimal Stock Value and Maximum Stock Value.

Both grids label those two columns "(MYR)". They are not money: the backend compares them against bl_inv_current_location_stock_balance.qty_ledger, a quantity. Read them as units.

Listings load a fixed window and extend it with a More action rather than paging server-side, and the menu file records that Release Notes was routed but unreachable until its entry was added — both are visible in menu-items.ts and the listing components.

Screenshots needed

No screenshots exist for this applet. A capture session should take: the Template listing; a Template view showing the Categories grid with Optimal and Maximum values and the Negation Logic control; the Default Optimal / Maximum Stock Value pair with the inline warning callout; an Event view with Recurring ticked and the recurrence editor open; the Run view with the Run Processor button; and the run’s Emails section. A scratch tenant is required — the Emails section shows real entity e-mail addresses.

Configuration

Before you can use it

PrerequisiteWhereWhy
Item categories with items attachedItem MaintenanceA run resolves each category label to its item set; a category with no items contributes nothing.
Locations on the templateOrganisation AppletdetectBreaches returns immediately when the run has no locations. Nothing is reported and nothing fails.
At least one optimal or maximum valueThe template’s Categories gridA category link with both values null is skipped.
Entities with an e-mail address linked to the runThe run’s Emails sectionRecipients come from the run’s entity links; an entity with a blank email is dropped. With no recipient left the alert is skipped and only a warning is logged.
The job processor enabled for the tenantBigLedger supportQueue code STOCK_LEVEL_MONITORING_RUN_PROCESSOR. Without it, events and manual runs queue and never execute.
Server-side permissionsSettings → Permission Set / Role PermissionSee below.

Applet settings

Settings are applet-local, and there are effectively none.

  • Settings → Field Settings is the unbound eight-toggle stub — Unit Discount, SST/VAT/GST, WHT, Blanket Order, Segment, G/L Dimension, Profit Center, Project — with no form group, no formControlName and a SAVE button with no click handler. The labels belong to a sales-document settings screen and have nothing to do with stock monitoring. No exposed control found (routes and settings components checked at commit 12cab54f).
  • Settings → Default Selection and Personalization → Default Selection offer Default Branch and Default Location, and do persist them (saveMasterSettingsInit / savePersonalSettingsInit, with a RESET that writes nulls). But DEFAULT_BRANCH and DEFAULT_LOCATION are declared in applet-settings.model.ts and read nowhere else in the applet — no screen pre-selects anything from them. They are declared, rendered and persisted, but not consumed, so nothing changes when you set them.
  • Settings → Release Notes shows the applet’s release notes.

Everything that actually changes behaviour lives on the template, the event and the run, not in Settings. That is the part to configure:

WhereSettingWhat it controlsDefault
Template → CategoriesOptimal Stock Value per categoryA location’s qty_ledger below this is an UNDERSTOCK breachempty (no understock check for that category)
Template → CategoriesMaximum Stock Value per categoryA location’s qty_ledger above this is an OVERSTOCK breachempty (no overstock check)
Template → CategoriesDefault Optimal Stock Value, Default Maximum Stock ValueApplied to every category not listed, and only when Negation Logic is ENABLEDempty
Template / Event / RunNegation Logic (category_filter_negation)DISABLED = monitor the categories you picked. ENABLED = monitor every other active category in the same lists, using the two default values aboveDISABLED
Template / Event / RunRules Logic (category_filter_logic, AND / OR)Nothing. The value is carried from template to event to run and logged, but every picked category is always evaluated independently — OR semantics. AND is not implemented.AND (and treated as OR)
TemplateEmail Subject, Email BodyCopied onto the run when the run is created, so editing the template later does not rewrite an alert that already went outempty (a default subject is used)
EventCycle Start DateA start date in the past or now queues the run RUN_NOW; a future date queues it for that momentnow
RunRun ProcessorRe-queues this run RUN_NOW
Turning Negation Logic ENABLED without setting a Default Optimal or Default Maximum value produces a run that reports nothing at all. The applet shows an inline warning under the two fields when both are empty, and the backend logs the same thing — but the run still completes “successfully” with zero breaches.

Feature visibility / permissions

bl_applet_client_side_perm_dfn holds zero rows for stockLevelMonitoringApplet (checked 2026-09-14), and the applet checks no HIDE_* / SHOW_* keys, so the Feature Visibility screen has nothing to gate. Access is entirely server-side, through TntErpPermissions:

ScreenPermission
Level Monitoring TemplateAPI_STOCK_LEVEL_MONITORING_TEMPLATE_HDR_READ / _CREATE / _UPDATE / _DELETE
Level Monitoring EventsAPI_STOCK_LEVEL_MONITORING_EVENT_HDR_READ / _CREATE / _UPDATE / _DELETE
Level Monitoring RunsAPI_STOCK_LEVEL_MONITORING_RUN_READ / _CREATE / _UPDATE / _DELETE
Run Processor buttonAPI_STOCK_LEVEL_MONITORING_RUN_READ — the manual-run endpoint is guarded by the read permission, not by update or create

Fields

Template → Create

FieldMeaningRequiredNotes
Template CodeShort codeYesValidators.required
Template NameDisplay nameYesValidators.required
DescriptionFree textNo
Email SubjectSubject line of the breach alertNoFalls back to a built-in subject
Email BodyHTML body of the alertNoSnapshotted onto each run at creation
StatusACTIVE / …NoDefaults to ACTIVE

The template view adds Creation Date, Created By, Modified Date and Modified By as read-only fields, plus the Locations, Companies, Entities and Categories sections.

Event → Create

FieldMeaningRequiredNotes
Event CodeShort codeYes
Event NameDisplay nameYes
Level Monitoring TemplateThe template the run will useNo validator, but required in practiceA run built from an event with no template is abandoned by the processor — see Lifecycle
Cycle Start DateWhen the run should executeNoEmpty means now
Cycle End DateInformationalNoNot read by the processor
Recurring + recurrence ruleAn RRULE built by the recurrence editorNoStored on the event and never read — see Lifecycle
Description, StatusFree text; ACTIVE by defaultNo

Run → view shows Run Name, Description and Status, the Locations, Companies and Categories snapshots, and the Emails list (Entity Name, Email, Updated Date, Actions).

Lifecycle and effects

This applet writes only its own three header tables and their link tables. No stock movement, no journal, no document. Quantity and amount signums do not apply — there is no generic document and no data-consistency object of the financial kind; the DCOs under StockLevelMonitoringDataConsistencyObjects/ validate the template, event and run rows themselves.

How a run is triggered. There are exactly two paths, and both insert into the STOCK_LEVEL_MONITORING_RUN_PROCESSOR queue:

  1. Creating an event. StockLevelMonitoringEventHdrService.create inserts a queue event carrying the event GUID, with ExecutionStrategy.RUN_NOW if the event’s start date is now or in the past and INSERT_TO_QUEUE if it is in the future. Updating an event does not re-queue anything.
  2. Pressing Run Processor on a run (GET …/stock-level-monitoring-run-hdrs/backoffice-ep/manual-run/{guid}), which queues the run GUID RUN_NOW.

What the processor does. StockLevelMonitoringRunProcessor.processEvent branches on which GUID it was given:

  • event GUID — loads the event; if the event has no template_hdr_guid it logs an error and stops. Otherwise it creates a run header from the template and the event, then processes it.
  • run GUID with a template — processes that run.
  • run GUID with no template — logs “Monitoring run without a template is not supported” and returns an empty result. A run created by hand and never linked to a template can never produce anything.

Processing a run (processRun):

  1. Snapshot the template’s locations, companies and entities onto the run, but only where the run has none yet — so a run that has been edited keeps its own scope.
  2. Snapshot the template’s category links onto the run the same way. The event’s own category links are deliberately not used: they hold category groups, not categories with stock values.
  3. Resolve Negation Logic from the run, falling back to the template.
  4. When negation is ENABLED, replace the picked categories with every other active category in the same label lists, each carrying the template’s default optimal and maximum values.
  5. detectBreaches — for each category link with at least one of the two values set, for each item in that category, for each of the run’s locations, read bl_inv_current_location_stock_balance.qty_ledger (null treated as zero) and compare. Below optimal is UNDERSTOCK; above maximum is OVERSTOCK. Understock wins when both would apply, and the triple (item, location, breach type) is de-duplicated.
  6. Write breach_count, detected_at and the full breaches list into the run header’s property_json.
  7. Send the alert e-mail.

The alert e-mail. Recipients are the entities linked to the run, reduced to those with a non-blank email on bl_fi_mst_entity_hdr. Sender and subject come from the run header, falling back to the platform default sender and a built-in subject. The subject and body were copied from the template when the run was created, so changing the template does not rewrite an alert already sent. Every send is recorded in the e-mail history table. The e-mail service never throws — a failure is returned on the result, logged, and the run still completes.

Nothing displays the result. The breach list exists in the run header’s property_json and in the e-mail. The applet never reads property_json, breach_count or the e-mail history — there is no screen anywhere in it that shows what a run found.

Related applets

  • Stock Replenishment Applet — the same template / event / run shape, and the same Rules Logic and Negation Logic controls, but it acts on the shortfall by creating purchase orders and transfers instead of e-mailing.
  • Stock Balance Applet — reports on the same qty_ledger figures this applet compares against.
  • Stock Availability Applet — the interactive way to answer “how much is at this location right now”.
  • Item Maintenance — where the item categories this applet monitors are defined.
  • Organisation Applet — the companies and locations a template can scope to.

Troubleshooting

SymptomCauseFix
A run completes and reports zero breaches although stock is clearly lowMost often the run has no locations — detectBreaches returns immediately when the location set is empty, without failing.Check the run’s Locations section, and the template’s, and re-run.
Negation Logic is on and every run reports nothingNeither Default Optimal Stock Value nor Default Maximum Stock Value is set, so every synthetic category link is skipped. The applet shows an inline warning and the backend logs one.Set at least one of the two defaults on the template.
A category is listed on the template but never breachesIts Optimal and Maximum values are both empty, so the link is skipped.Enter at least one value on that row.
Breaches are detected but nobody gets an e-mailNo entity linked to the run has an e-mail address; the alert is skipped with a warning in the log.Add entities with e-mail addresses to the run’s Emails section.
Editing the template’s Email Subject or Body does not change the alertSubject and body are snapshotted onto the run when the run is created.Edit them on the run, or create a new run.
Switching Rules Logic from OR to AND changes nothingAND is not implemented; the value is propagated and logged, and every category is evaluated independently.Narrow the category list instead.
An event was set to Recurring and only ran onceis_recurring and rrule are stored on the monitoring event and read by no backend service. The only queue insert happens once, at event creation.Create one event per occurrence, or press Run Processor on the run.
An event was saved with the right dates but never ranOnly creating an event queues a run; updating one does not.Delete and re-create the event, or run the run manually.
Runs sit and never executeThe STOCK_LEVEL_MONITORING_RUN_PROCESSOR job processor is not enabled for the tenant.Ask support to enable it.
There is nowhere to see what the last run foundBy design at this commit — the breach list only exists in the run’s property_json and the alert e-mail.Read the e-mail; for the raw list, ask support to read the run header.
A user can press Run Processor although they only have read accessThe manual-run endpoint is guarded by API_STOCK_LEVEL_MONITORING_RUN_READ.Treat read on runs as “may re-run”; withhold it if that matters.

Related documentation

Last updated on