diff --git a/spp_alerts/README.rst b/spp_alerts/README.rst
index 54f87c0d..3c726bc6 100644
--- a/spp_alerts/README.rst
+++ b/spp_alerts/README.rst
@@ -23,22 +23,26 @@ OpenSPP Alerts
|badge1| |badge2| |badge3|
Generic alert engine for threshold monitoring, expiry tracking, and
-deadline management. Provides base models and state machine for alert
-lifecycle tracking. Consumer modules (like ``spp_drims``) extend these
-models and implement evaluation logic to generate alerts based on
-domain-specific conditions.
+deadline management. Evaluates configurable rules on a daily schedule or
+on-demand and generates alerts when conditions are met. Consumer modules
+(like ``spp_drims``) extend these models to add domain-specific fields.
Key Capabilities
~~~~~~~~~~~~~~~~
-- Track alert lifecycle through state machine: active → acknowledged →
- resolved
-- Record resolution details including user, timestamp, and notes
-- Classify alerts by type using ``spp.vocabulary`` codes (threshold,
- expiry, deadline, manual, system)
-- Prioritize alerts as low, medium, high, or critical
-- Send mail notifications via ``mail.thread`` integration
-- Auto-generate alert references in ALR-YYYY-NNNNN format
+- Define alert rules with threshold or date conditions against any model
+- Evaluate rules via daily cron or "Run Now" button
+- Compare numeric fields using 5 operators: <, <=, >, >=, =
+- Check date/datetime fields against a days-before window
+- Prevent duplicates: skip records with existing active/acknowledged
+ alerts
+- Filter monitored records using a visual domain builder
+- Track alert lifecycle: active → acknowledged → resolved
+- Record resolution details: user, timestamp, and notes
+- Navigate from alert to source record via stat button
+- Classify alerts by type using ``spp.vocabulary`` codes
+- Prioritize as low, medium, high, or critical
+- Auto-generate references in ``ALR-YYYY-NNNNN`` format
Key Models
~~~~~~~~~~
@@ -47,10 +51,10 @@ Key Models
| Model | Description |
+====================+=================================================+
| ``spp.alert`` | Alert instance with state tracking and |
-| | resolution workflow |
+| | resolution audit |
+--------------------+-------------------------------------------------+
-| ``spp.alert.rule`` | Rule configuration for monitoring criteria and |
-| | thresholds |
+| ``spp.alert.rule`` | Rule configuration with evaluation engine and |
+| | scheduling |
+--------------------+-------------------------------------------------+
Configuration
@@ -59,40 +63,44 @@ Configuration
After installing:
1. Navigate to **Settings > Technical > Alerts > Alert Rules**
-2. Create rules specifying alert type, priority, threshold values, and
- days before deadline
-3. Consumer modules implement checking logic (e.g., cron jobs or event
- handlers) to evaluate rules and create alerts
+2. Create rules: select model, rule type (threshold/date), and
+ conditions
+3. The daily cron "Alerts: Evaluate Alert Rules" is active by default
UI Location
~~~~~~~~~~~
- **Menu**: Settings > Technical > Alerts > Alerts
- **Configuration**: Settings > Technical > Alerts > Alert Rules
-- **Form Tabs**: Details, Resolution (alerts); Thresholds (rules)
+- **Views**: List, kanban (grouped by state), and form
+- **Alert form tabs**: Details, Resolution
+- **Rule form**: Description above tabs; Evaluation tab with settings +
+ domain builder
Security
~~~~~~~~
-=================================== ====================================
-Group Access
-=================================== ====================================
-``spp_alerts.group_alerts_viewer`` Read alerts
-``spp_alerts.group_alerts_officer`` Read/Write/Create (no delete) alerts
-``spp_alerts.group_alerts_manager`` Full CRUD on alerts and rules
-=================================== ====================================
++-------------------------------------+----------------------------+-----------+
+| Group | Alerts | Rules |
++=====================================+============================+===========+
+| ``spp_alerts.group_alerts_viewer`` | Read | Read |
++-------------------------------------+----------------------------+-----------+
+| ``spp_alerts.group_alerts_officer`` | Read/Write/Create (no | Read |
+| | delete) | |
++-------------------------------------+----------------------------+-----------+
+| ``spp_alerts.group_alerts_manager`` | Full CRUD | Full CRUD |
++-------------------------------------+----------------------------+-----------+
Extension Points
~~~~~~~~~~~~~~~~
-- Inherit ``spp.alert`` to add domain-specific fields (e.g., stock
- levels, document references)
-- Inherit ``spp.alert.rule`` to add custom threshold or evaluation
- criteria
-- Override ``action_acknowledge()`` or ``action_resolve()`` to add
- custom workflow steps
-- Consumer modules implement alert checking via cron jobs or event
- handlers that evaluate rules and call ``create()`` on ``spp.alert``
+- Inherit ``spp.alert`` to add domain-specific fields
+- Inherit ``spp.alert.rule`` to add custom evaluation criteria
+- Override ``_evaluate_threshold()`` or ``_evaluate_date()`` for custom
+ logic
+- Override ``action_acknowledge()`` or ``action_resolve()`` for custom
+ workflows
+- Rules can be configured via UI without code
Dependencies
~~~~~~~~~~~~
@@ -104,6 +112,662 @@ Dependencies
.. contents::
:local:
+Usage
+=====
+
+UI Testing Guide
+----------------
+
+This guide covers all user-facing functionality in the ``spp_alerts``
+module. Follow each section in order. Each test case includes the steps,
+what to verify, and the expected result.
+
+**Prerequisites:**
+
+- Install the ``spp_alerts`` module
+- Log in as **admin** (has all permissions by default)
+- Ensure at least one model is available (e.g., ``res.partner`` — always
+ present)
+
+--------------
+
+1. Navigation and Menu Access
+-----------------------------
+
+**TC-1.1: Menu visibility**
+
+1. Navigate to **Settings > Technical**
+2. Scroll down to find the **Alerts** section
+
+**Verify:**
+
+- ☐ An **Alerts** menu group is visible under Settings > Technical
+- ☐ It contains two submenus: **Alerts** and **Alert Rules**
+
+**TC-1.2: URL paths**
+
+1. Navigate to **Settings > Technical > Alerts > Alerts**
+2. Check the browser URL
+
+**Verify:**
+
+- ☐ URL ends with ``/odoo/alerts``
+
+3. Navigate to **Settings > Technical > Alerts > Alert Rules**
+
+**Verify:**
+
+- ☐ URL ends with ``/odoo/alert-rules``
+
+--------------
+
+2. Alert Rules — List View
+--------------------------
+
+**TC-2.1: Empty state**
+
+1. Navigate to **Settings > Technical > Alerts > Alert Rules**
+2. Remove any active search filters
+
+**Verify (if no rules exist):**
+
+- ☐ Empty state shows smiley face icon
+- ☐ Message reads: "No alert rules configured"
+- ☐ Description mentions configuring rules for thresholds, expiry dates,
+ and deadlines
+
+**TC-2.2: List view columns**
+
+1. Create at least one alert rule (see TC-3.1)
+2. Return to the list view
+
+**Verify:**
+
+- ☐ Columns visible: drag handle (sequence), Name, Alert Type, Model to
+ Monitor, Rule Type, Priority, Threshold Value, Days Before, Active
+- ☐ Rule Type, Threshold Value, and Days Before have "optional column"
+ toggles
+- ☐ Rows can be reordered by dragging the handle icon
+- ☐ Sample data appears in the background when the list is empty
+
+**TC-2.3: Search and filters**
+
+1. Click the search bar
+2. Try searching by Name, Alert Type, and Model
+
+**Verify:**
+
+- ☐ Filters available: Active, Inactive, Critical Priority, High
+ Priority
+- ☐ Group By options: Alert Type, Model, Priority, Rule Type
+
+--------------
+
+3. Alert Rules — Form View
+--------------------------
+
+**TC-3.1: Create a threshold rule**
+
+1. Click **New** on the Alert Rules list
+2. Fill in:
+
+ - **Rule Name**: "Test Low Color Warning"
+ - **Alert Type**: select "Threshold Alert" from the dropdown (cannot
+ type to create new)
+ - **Default Priority**: "High"
+ - **Model to Monitor**: select "Contact" (res.partner)
+ - **Rule Type**: select "Threshold"
+
+3. Optionally add a **Description** in the text area below the main
+ fields
+4. Observe the **Evaluation** tab appears after selecting Rule Type
+
+**Verify:**
+
+- ☐ Alert Type dropdown does NOT show a "Create" option
+- ☐ After selecting Model and Rule Type, the Evaluation tab appears
+- ☐ Evaluation tab shows **Threshold Settings** section with: Monitored
+ Field, Comparison, Threshold Value
+
+5. In the Evaluation tab:
+
+ - **Monitored Field**: select "Color Index" (or any numeric field)
+ - **Comparison**: "Less Than (<)"
+ - **Threshold Value**: 5
+
+6. Under **Record Filter**, observe the visual domain builder
+
+**Verify:**
+
+- ☐ Domain builder shows fields from the selected model
+ (Contact/res.partner)
+- ☐ You can add filter conditions visually (e.g., "Active is set")
+
+7. Save the rule
+
+**Verify:**
+
+- ☐ Rule saves without errors
+- ☐ Chatter (message log) appears at the bottom of the form
+- ☐ An **Alerts** stat button appears in the top-right showing "0
+ Alerts"
+
+**TC-3.2: Create a date rule**
+
+1. Create a new rule:
+
+ - **Rule Name**: "Test Deadline Warning"
+ - **Alert Type**: "Deadline Alert"
+ - **Model to Monitor**: "Contact"
+ - **Rule Type**: "Date / Deadline"
+
+**Verify:**
+
+- ☐ Evaluation tab shows **Date Settings** section (not Threshold
+ Settings)
+- ☐ Date Settings has: Date Field and Days Before
+
+2. Fill in:
+
+ - **Date Field**: select "Last Updated on" (write_date) or any
+ date/datetime field
+ - **Days Before**: 30
+
+3. Save
+
+**Verify:**
+
+- ☐ Rule saves without errors
+
+**TC-3.3: Run Now button**
+
+1. Open the threshold rule created in TC-3.1
+2. Click the **Run Now** button in the header
+
+**Verify:**
+
+- ☐ A notification appears: "X alert(s) created by rule 'Test Low Color
+ Warning'"
+- ☐ The Alerts stat button count updates to reflect created alerts
+- ☐ Click the stat button — it opens a filtered list of alerts from this
+ rule
+
+**TC-3.4: Archive and unarchive**
+
+1. Open any rule
+2. Click **Action > Archive**
+
+**Verify:**
+
+- ☐ A red **Archived** ribbon appears on the form
+- ☐ The rule disappears from the default list view (which filters active
+ rules)
+
+3. In the list view, add the "Inactive" filter
+
+**Verify:**
+
+- ☐ The archived rule appears
+- ☐ Open it and click **Action > Unarchive** — ribbon disappears
+
+**TC-3.5: Validation errors**
+
+1. Create a new rule with Rule Type = "Threshold" but leave Monitored
+ Field empty
+2. Try to save
+
+**Verify:**
+
+- ☐ Error: "A monitored field is required for threshold rules."
+
+3. Create a new rule with Rule Type = "Date / Deadline" but leave Date
+ Field empty
+4. Try to save
+
+**Verify:**
+
+- ☐ Error: "A date field is required for date rules."
+
+5. Create a rule and set Domain Filter to an invalid expression (e.g.,
+ type ``INVALID`` in the domain builder's code editor if available)
+6. Try to save
+
+**Verify:**
+
+- ☐ Error: "Invalid domain filter: ..."
+
+**TC-3.6: Run Now without configuration**
+
+1. Create a rule with no Rule Type and no Model
+2. Observe the header
+
+**Verify:**
+
+- ☐ The **Run Now** button is NOT visible (it requires both Rule Type
+ and Model)
+
+--------------
+
+4. Alerts — Creating Manually
+-----------------------------
+
+**TC-4.1: Create a manual alert**
+
+1. Navigate to **Settings > Technical > Alerts > Alerts**
+2. Click **New**
+3. Fill in:
+
+ - **Alert Type**: "Manual Alert"
+ - **Priority**: "Critical"
+ - **Title**: "Test Manual Alert"
+
+4. Go to the **Details** tab and add a description
+5. Save
+
+**Verify:**
+
+- ☐ Reference auto-generated in format ``ALR-YYYY-NNNNN`` (e.g.,
+ ALR-2026-00001)
+- ☐ State shows "Active" in the statusbar
+- ☐ **Acknowledge** button (blue) is visible in the header
+- ☐ **Resolve** button (green) is visible in the header
+- ☐ Priority and Alert Type fields are editable
+- ☐ Chatter (message log) appears at the bottom
+- ☐ No "View Source" or "Related Alerts" stat buttons (manual alert has
+ no source)
+
+**TC-4.2: Unique references**
+
+1. Create three alerts quickly
+
+**Verify:**
+
+- ☐ Each alert gets a unique, sequential reference number
+
+--------------
+
+5. Alerts — List View
+---------------------
+
+**TC-5.1: List view appearance**
+
+1. Navigate to **Settings > Technical > Alerts > Alerts**
+2. Ensure some alerts exist (use Run Now on a rule, or create manually)
+
+**Verify:**
+
+- ☐ Columns visible: Reference, Title, Alert Type, Priority (badge),
+ State (badge), Created On
+- ☐ Critical-priority rows have a red tint
+- ☐ High-priority rows have an orange/warning tint
+- ☐ Resolved rows are muted/grayed out
+- ☐ Priority badges: Critical = red, High = orange, Medium = blue
+- ☐ State badges: Active = red, Acknowledged = orange, Resolved = green
+- ☐ Source Rule and Company columns are hidden by default (use optional
+ column toggle)
+- ☐ Default filter shows only Active alerts (check search bar for
+ "Active" filter chip)
+
+**TC-5.2: Search panel**
+
+1. Look at the left side of the list view
+
+**Verify:**
+
+- ☐ Search panel shows three filter groups: State, Priority, Alert Type
+- ☐ Each option shows a count of matching alerts
+- ☐ Clicking a filter value narrows the list immediately
+
+**TC-5.3: Search filters and Group By**
+
+1. Click the search bar
+
+**Verify:**
+
+- ☐ Can search by Reference, Title, Alert Type
+- ☐ Filters: Active, Acknowledged, Resolved, Critical, High Priority
+- ☐ Group By: State, Priority, Type
+
+--------------
+
+6. Alerts — Kanban View
+-----------------------
+
+**TC-6.1: Switch to kanban**
+
+1. On the Alerts list, click the kanban view icon (grid icon in the view
+ switcher)
+
+**Verify:**
+
+- ☐ Alerts are displayed as cards grouped into columns by State: Active,
+ Acknowledged, Resolved
+- ☐ Each column header shows the state name and alert count
+- ☐ A colored progress bar appears at the top of each column showing
+ priority distribution (gray = low, blue = medium, orange = high, red =
+ critical)
+- ☐ Quick create is disabled (no "+" button at top of columns)
+
+**TC-6.2: Kanban card content**
+
+1. Examine an alert card
+
+**Verify:**
+
+- ☐ Card shows: priority stars, reference (bold), title, alert type,
+ creation date
+- ☐ A three-dot dropdown menu appears on hover (top-right of card)
+
+**TC-6.3: Kanban dropdown actions**
+
+1. Hover over an **Active** alert card and click the three-dot menu
+
+**Verify:**
+
+- ☐ Dropdown shows: **Acknowledge** and **Resolve**
+
+2. Click **Acknowledge**
+
+**Verify:**
+
+- ☐ Card moves to the Acknowledged column
+
+3. Hover over the now-acknowledged card, open dropdown
+
+**Verify:**
+
+- ☐ Dropdown shows only **Resolve** (Acknowledge is gone)
+
+4. Click **Resolve**
+
+**Verify:**
+
+- ☐ A dialog or form opens requesting resolution notes (since the action
+ requires notes)
+
+--------------
+
+7. Alert State Transitions
+--------------------------
+
+**TC-7.1: Active to Acknowledged**
+
+1. Open an Active alert
+2. Click the **Acknowledge** button
+
+**Verify:**
+
+- ☐ State changes to "Acknowledged"
+- ☐ Statusbar updates to highlight "Acknowledged"
+- ☐ Acknowledge button disappears
+- ☐ Resolve button remains visible
+- ☐ Fields (Alert Type, Priority, Title) are still editable
+- ☐ Chatter logs a state change message
+
+**TC-7.2: Acknowledged to Resolved**
+
+1. On the acknowledged alert, go to the **Resolution** tab
+
+**Verify:**
+
+- ☐ An info banner reads: "Please add resolution notes describing how
+ this alert was addressed, then click Resolve."
+- ☐ Resolved By and Resolved At fields are empty
+
+2. Enter resolution notes in the text area
+3. Click the **Resolve** button
+
+**Verify:**
+
+- ☐ State changes to "Resolved"
+- ☐ Resolved By shows your user name
+- ☐ Resolved At shows the current timestamp
+- ☐ Resolution Notes are preserved and now read-only
+- ☐ Info banner disappears
+- ☐ Both Acknowledge and Resolve buttons disappear
+- ☐ All main fields (Alert Type, Priority, Title, Description) become
+ read-only
+
+**TC-7.3: Active to Resolved directly (skip Acknowledge)**
+
+1. Create a new alert and leave it in Active state
+2. Go to the **Resolution** tab
+3. Enter resolution notes
+4. Click **Resolve**
+
+**Verify:**
+
+- ☐ Alert goes directly from Active to Resolved (skipping Acknowledged)
+- ☐ All resolution fields are populated correctly
+
+**TC-7.4: Resolve without notes**
+
+1. Create a new Active alert
+2. Click **Resolve** without entering resolution notes
+
+**Verify:**
+
+- ☐ Error message: "Please provide resolution notes before resolving the
+ alert."
+- ☐ Alert remains in its current state
+
+**TC-7.5: Double-acknowledge prevention**
+
+1. Acknowledge an alert
+2. Try to acknowledge it again (via API or another browser tab)
+
+**Verify:**
+
+- ☐ Error: "Only active alerts can be acknowledged."
+- ☐ The Acknowledge button is not visible on the form (it only shows for
+ Active alerts)
+
+**TC-7.6: Double-resolve prevention**
+
+1. Resolve an alert
+2. Try to resolve it again
+
+**Verify:**
+
+- ☐ Error: "Alert 'ALR-YYYY-NNNNN' is already resolved."
+- ☐ The Resolve button is not visible on the form
+
+--------------
+
+8. Alert Form — Rule-Generated Alerts
+-------------------------------------
+
+**TC-8.1: Source tracking fields**
+
+1. Run a rule (via **Run Now** on an alert rule)
+2. Open one of the generated alerts
+
+**Verify:**
+
+- ☐ Right side of the form shows a **Source** section with: Source Rule,
+ Source Model, Source Record, Source Record Name
+- ☐ If the rule is a threshold rule, a **Metrics** section shows:
+ Current Value, Threshold, Days Until
+- ☐ A **View Source** stat button appears in the top-right
+- ☐ A **Related Alerts** stat button appears (if rule created multiple
+ alerts)
+
+**TC-8.2: View Source button**
+
+1. Click the **View Source** stat button
+
+**Verify:**
+
+- ☐ Opens the source record's form view (e.g., a Contact form)
+- ☐ The correct record is displayed
+
+**TC-8.3: Related Alerts button**
+
+1. Go back to the alert and click the **Related Alerts** stat button
+
+**Verify:**
+
+- ☐ Opens a list of other alerts from the same rule (excluding the
+ current alert)
+- ☐ Default filter shows Active alerts
+
+--------------
+
+9. Keyboard Shortcuts
+---------------------
+
+**TC-9.1: Hotkeys**
+
+1. Open an Active alert form
+2. Press **Alt+A** (or the platform equivalent for ``data-hotkey="a"``)
+
+**Verify:**
+
+- ☐ Alert is acknowledged
+
+3. Press **Alt+R**
+
+**Verify:**
+
+- ☐ Resolve action is triggered (will ask for notes if none provided)
+
+--------------
+
+10. Cron Job
+------------
+
+**TC-10.1: Scheduled action exists**
+
+1. Navigate to **Settings > Technical > Scheduled Actions**
+2. Search for "Alerts"
+
+**Verify:**
+
+- ☐ A scheduled action named **"Alerts: Evaluate Alert Rules"** exists
+- ☐ It is active
+- ☐ Interval is set to 1 day
+
+**TC-10.2: Cron execution**
+
+1. Ensure at least one active alert rule with matching records exists
+2. Click **Run Manually** on the scheduled action
+
+**Verify:**
+
+- ☐ New alerts are created for matching records
+- ☐ No duplicate alerts for records that already have
+ active/acknowledged alerts
+
+--------------
+
+11. Security and Access Control
+-------------------------------
+
+Test with three different users. Create them via **Settings > Users &
+Companies > Users** and assign the appropriate group under the SPP Admin
+section.
+
+**TC-11.1: Viewer role**
+
+1. Log in as a user with **Alerts Viewer** group only
+2. Navigate to **Settings > Technical > Alerts > Alerts**
+
+**Verify:**
+
+- ☐ Can see the Alerts menu and list
+- ☐ Can open and read alert details
+- ☐ Cannot create new alerts (New button absent or errors on save)
+- ☐ Cannot edit existing alerts
+- ☐ Cannot acknowledge or resolve alerts (buttons error on click)
+- ☐ **Alert Rules** submenu is NOT visible
+
+**TC-11.2: Officer role**
+
+1. Log in as a user with **Alerts Officer** group
+2. Navigate to **Settings > Technical > Alerts > Alerts**
+
+**Verify:**
+
+- ☐ Can create new alerts
+- ☐ Can edit alerts (change priority, title, etc.)
+- ☐ Can acknowledge and resolve alerts
+- ☐ Cannot delete alerts
+- ☐ Can see **Alert Rules** submenu but rules are read-only
+- ☐ Cannot create or edit alert rules
+
+**TC-11.3: Manager role**
+
+1. Log in as a user with **Alerts Manager** group
+
+**Verify:**
+
+- ☐ Full access to alerts: create, read, update, delete
+- ☐ Full access to alert rules: create, read, update, delete
+- ☐ Can click **Run Now** on alert rules
+- ☐ Can archive/unarchive rules
+
+--------------
+
+12. Multi-Company (if applicable)
+---------------------------------
+
+Only test this section if multi-company is enabled.
+
+**TC-12.1: Company isolation**
+
+1. Create an alert in Company A
+2. Switch to Company B
+
+**Verify:**
+
+- ☐ The alert from Company A is not visible in Company B
+- ☐ New alerts default to the current company
+
+--------------
+
+13. Alert Types (Vocabulary)
+----------------------------
+
+**TC-13.1: Pre-installed types**
+
+1. Open any alert or rule form
+2. Click the **Alert Type** dropdown
+
+**Verify the following types are available:**
+
+- ☐ Threshold Alert
+- ☐ Expiry Alert
+- ☐ Deadline Alert
+- ☐ Manual Alert
+- ☐ System Alert
+- ☐ Cannot create new types from the dropdown (no "Create" option)
+
+--------------
+
+14. Edge Cases
+--------------
+
+**TC-14.1: Empty state**
+
+1. Delete or resolve all alerts
+2. Remove the "Active" default filter
+
+**Verify:**
+
+- ☐ Empty state shows: "No active alerts" with smiley face
+
+**TC-14.2: Sorting**
+
+1. Create alerts with different priorities (low, medium, high, critical)
+2. View the list (default sort)
+
+**Verify:**
+
+- ☐ Alerts are sorted by priority (critical first) then by creation date
+ (newest first)
+- ☐ This is semantic ordering: critical > high > medium > low (not
+ alphabetical)
+
Bug Tracker
===========
diff --git a/spp_alerts/__manifest__.py b/spp_alerts/__manifest__.py
index 53128cd7..65b48aa4 100644
--- a/spp_alerts/__manifest__.py
+++ b/spp_alerts/__manifest__.py
@@ -32,7 +32,6 @@
"views/alert_rule_views.xml",
"views/menus.xml",
],
- "assets": {},
"application": False,
"installable": True,
"auto_install": False,
diff --git a/spp_alerts/models/alert.py b/spp_alerts/models/alert.py
index 8976c420..b4738661 100644
--- a/spp_alerts/models/alert.py
+++ b/spp_alerts/models/alert.py
@@ -6,6 +6,13 @@
_logger = logging.getLogger(__name__)
+PRIORITY_SEQUENCE = {
+ "low": 1,
+ "medium": 2,
+ "high": 3,
+ "critical": 4,
+}
+
class Alert(models.Model):
"""Generic alert model for monitoring thresholds, expiries, and deadlines.
@@ -25,8 +32,9 @@ class Alert(models.Model):
_name = "spp.alert"
_description = "Alert"
_inherit = ["mail.thread"]
- _order = "priority desc, create_date desc"
+ _order = "priority_sequence desc, create_date desc"
_rec_name = "reference"
+ _check_company_auto = True
reference = fields.Char(
string="Reference",
@@ -47,6 +55,8 @@ class Alert(models.Model):
help="Type of alert from alert types vocabulary",
)
+ # Named `alert_type` (not `alert_type_code`) for concise domain filtering.
+ # Use `alert_type_id` for the vocabulary record, `alert_type` for the code string.
alert_type = fields.Char(
related="alert_type_id.code",
store=True,
@@ -69,6 +79,16 @@ class Alert(models.Model):
help="Priority level of the alert",
)
+ priority_sequence = fields.Integer(
+ string="Priority Sequence",
+ compute="_compute_priority_sequence",
+ store=True,
+ help="Numeric ordering of priority for consistent sorting (low=1, medium=2, high=3, critical=4)",
+ )
+
+ # Alert states differ from the standard approval workflow states (draft/pending/approved)
+ # because the alert lifecycle is fundamentally different: alerts are raised, acknowledged,
+ # and resolved — there is no approval decision involved.
state = fields.Selection(
[
("active", "Active"),
@@ -102,6 +122,7 @@ class Alert(models.Model):
index=True,
readonly=True,
ondelete="set null",
+ check_company=True,
help="Alert rule that generated this alert (empty for manually created alerts)",
)
@@ -120,6 +141,12 @@ class Alert(models.Model):
help="Record that triggered this alert",
)
+ res_name = fields.Char(
+ string="Source Record Name",
+ compute="_compute_res_name",
+ help="Display name of the record that triggered this alert",
+ )
+
# Metrics for threshold and deadline tracking
current_value = fields.Float(
string="Current Value",
@@ -164,6 +191,28 @@ class Alert(models.Model):
help="Company this alert belongs to",
)
+ @api.depends("priority")
+ def _compute_priority_sequence(self):
+ """Compute numeric priority sequence for consistent ordering."""
+ for record in self:
+ record.priority_sequence = PRIORITY_SEQUENCE.get(record.priority, 0)
+
+ def _compute_res_name(self):
+ """Compute the display name of the source record.
+
+ Handles missing models or deleted records gracefully by returning an
+ empty string rather than raising an error.
+ """
+ for record in self:
+ if not record.res_model or not record.res_id:
+ record.res_name = ""
+ continue
+ try:
+ source = self.env[record.res_model].browse(record.res_id)
+ record.res_name = source.display_name if source.exists() else ""
+ except (KeyError, AttributeError):
+ record.res_name = ""
+
@api.model_create_multi
def create(self, vals_list):
"""Override create to auto-generate reference from sequence."""
@@ -180,7 +229,13 @@ def action_acknowledge(self):
This is typically the first step in addressing an alert - acknowledging
that it has been seen and is being investigated.
+
+ Raises:
+ UserError: If the alert is not in the active state.
"""
+ for record in self:
+ if record.state != "active":
+ raise UserError(_("Only active alerts can be acknowledged."))
self.write({"state": "acknowledged"})
return True
@@ -194,9 +249,13 @@ def action_resolve(self, notes=None):
bool: True on success
Raises:
- UserError: If resolution notes are not provided.
+ UserError: If the alert is already resolved or resolution notes are missing.
"""
+ # Fail-fast: if any record in the batch lacks resolution notes, none are resolved.
+ # This prevents partial resolution of related alert batches.
for record in self:
+ if record.state == "resolved":
+ raise UserError(_("Alert '%s' is already resolved.", record.reference))
resolution = notes or record.resolution_notes
if not resolution:
raise UserError(_("Please provide resolution notes before resolving the alert."))
@@ -210,3 +269,38 @@ def action_resolve(self, notes=None):
vals["resolution_notes"] = notes
self.write(vals)
return True
+
+ def action_view_related_alerts(self):
+ """Return an action to view other alerts from the same rule.
+
+ Returns:
+ dict: An ir.actions.act_window action dict, or False if no rule is set.
+ """
+ self.ensure_one()
+ if not self.rule_id:
+ return False
+ return {
+ "type": "ir.actions.act_window",
+ "name": _("Related Alerts"),
+ "res_model": "spp.alert",
+ "view_mode": "list,form",
+ "domain": [("rule_id", "=", self.rule_id.id), ("id", "!=", self.id)],
+ "context": {"search_default_filter_active": 1},
+ }
+
+ def action_view_source(self):
+ """Return an action to open the source record in a form view.
+
+ Returns:
+ dict: An ir.actions.act_window action dict, or False if no source is set.
+ """
+ self.ensure_one()
+ if not self.res_model or not self.res_id:
+ return False
+ return {
+ "type": "ir.actions.act_window",
+ "res_model": self.res_model,
+ "res_id": self.res_id,
+ "view_mode": "form",
+ "target": "current",
+ }
diff --git a/spp_alerts/models/alert_rule.py b/spp_alerts/models/alert_rule.py
index a31722f9..158a4408 100644
--- a/spp_alerts/models/alert_rule.py
+++ b/spp_alerts/models/alert_rule.py
@@ -35,6 +35,7 @@ class AlertRule(models.Model):
_name = "spp.alert.rule"
_description = "Alert Rule"
+ _inherit = ["mail.thread"]
_order = "sequence, name"
name = fields.Char(
@@ -55,6 +56,14 @@ class AlertRule(models.Model):
"ir.model",
string="Model to Monitor",
help="Odoo model this rule monitors",
+ tracking=True,
+ )
+
+ model_name = fields.Char(
+ related="model_id.model",
+ string="Model Name",
+ readonly=True,
+ help="Technical model name, used by the domain filter widget",
)
priority = fields.Selection(
@@ -68,12 +77,14 @@ class AlertRule(models.Model):
default="medium",
required=True,
help="Default priority for alerts created by this rule",
+ tracking=True,
)
active = fields.Boolean(
string="Active",
default=True,
help="Inactive rules will not create alerts",
+ tracking=True,
)
sequence = fields.Integer(
@@ -92,6 +103,7 @@ class AlertRule(models.Model):
help="Determines evaluation logic:\n"
"- Threshold: Compare a numeric field against threshold_value\n"
"- Date: Check if a date field is within days_before of today",
+ tracking=True,
)
domain_filter = fields.Text(
@@ -132,12 +144,14 @@ class AlertRule(models.Model):
threshold_value = fields.Float(
string="Threshold Value",
help="Threshold value for comparison (e.g., minimum stock level, maximum days)",
+ tracking=True,
)
days_before = fields.Integer(
string="Days Before",
default=0,
help="Days before expiry/deadline to trigger alert (0 = at deadline)",
+ tracking=True,
)
description = fields.Text(
@@ -153,6 +167,35 @@ class AlertRule(models.Model):
help="Company this rule applies to (empty = all companies)",
)
+ alert_count = fields.Integer(
+ string="Alert Count",
+ compute="_compute_alert_count",
+ help="Number of alerts created by this rule",
+ )
+
+ def _compute_alert_count(self):
+ """Compute the number of alerts associated with each rule."""
+ alert_data = self.env["spp.alert"].read_group(
+ [("rule_id", "in", self.ids)],
+ ["rule_id"],
+ ["rule_id"],
+ )
+ count_map = {d["rule_id"][0]: d["rule_id_count"] for d in alert_data}
+ for rule in self:
+ rule.alert_count = count_map.get(rule.id, 0)
+
+ def action_view_alerts(self):
+ """Open list view of alerts created by this rule."""
+ self.ensure_one()
+ return {
+ "type": "ir.actions.act_window",
+ "name": _("Alerts"),
+ "res_model": "spp.alert",
+ "view_mode": "list,form",
+ "domain": [("rule_id", "=", self.id)],
+ "context": {"default_rule_id": self.id},
+ }
+
# -------------------------------------------------------------------------
# Constraints
# -------------------------------------------------------------------------
@@ -170,6 +213,32 @@ def _check_rule_configuration(self):
if rule.rule_type == "date" and not rule.date_field_id:
raise ValidationError(_("A date field is required for date rules."))
+ def _domain_eval_context(self):
+ """Return the safe_eval context used when parsing domain filter expressions."""
+ return {
+ "datetime": safe_eval.datetime,
+ "dateutil": safe_eval.dateutil,
+ "time": safe_eval.time,
+ "uid": self.env.uid,
+ }
+
+ @api.constrains("domain_filter")
+ def _check_domain_filter(self):
+ """Validate that domain_filter is a parseable Odoo domain expression."""
+ for rule in self:
+ if not rule.domain_filter or rule.domain_filter.strip() == "[]":
+ continue
+ try:
+ result = safe_eval.safe_eval( # nosemgrep: odoo-unsafe-safe-eval
+ rule.domain_filter, self._domain_eval_context()
+ )
+ if not isinstance(result, list):
+ raise ValidationError(_("Domain filter must be a list, got %s.", type(result).__name__))
+ except ValidationError:
+ raise
+ except Exception as e:
+ raise ValidationError(_("Invalid domain filter: %s", e)) from e
+
# -------------------------------------------------------------------------
# Actions
# -------------------------------------------------------------------------
@@ -182,14 +251,21 @@ def action_evaluate(self):
if not self.model_id:
raise UserError(_("Cannot evaluate rule '%s': no model to monitor is configured.", self.name))
- count = self._evaluate_rule()
+ try:
+ count = self._evaluate_rule()
+ except Exception as e:
+ raise UserError(_("Error evaluating rule '%(rule)s': %(error)s", rule=self.name, error=e)) from e
return {
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"title": _("Rule Evaluated"),
- "message": _("%d alert(s) created by rule '%s'.", count, self.name),
+ "message": _(
+ "%(count)d alert(s) created by rule '%(rule)s'.",
+ count=count,
+ rule=self.name,
+ ),
"type": "success" if count > 0 else "info",
"sticky": False,
},
@@ -215,21 +291,16 @@ def _evaluate_rule(self):
try:
Model = self.env[model_name]
except KeyError:
- _logger.warning("Alert rule '%s': model '%s' not found, skipping.", self.id, model_name)
+ _logger.warning("Alert rule '%s' (ID: %d): model '%s' not found, skipping.", self.name, self.id, model_name)
return 0
# Parse domain filter
try:
- eval_context = {
- "datetime": safe_eval.datetime,
- "dateutil": safe_eval.dateutil,
- "time": safe_eval.time,
- "uid": self.env.uid,
- "user": self.env.user,
- }
- domain = safe_eval.safe_eval(self.domain_filter or "[]", eval_context) # nosemgrep: odoo-unsafe-safe-eval
+ domain = safe_eval.safe_eval( # nosemgrep: odoo-unsafe-safe-eval
+ self.domain_filter or "[]", self._domain_eval_context()
+ )
except Exception as e:
- _logger.error("Alert rule '%s': invalid domain filter: %s", self.id, e)
+ _logger.error("Alert rule '%s' (ID: %d): invalid domain filter: %s", self.name, self.id, e)
return 0
records = Model.search(domain)
@@ -238,7 +309,7 @@ def _evaluate_rule(self):
if self.rule_type == "threshold":
return self._evaluate_threshold(records, model_name)
- elif self.rule_type == "date":
+ if self.rule_type == "date":
return self._evaluate_date(records, model_name)
return 0
@@ -260,6 +331,8 @@ def _evaluate_threshold(self, records, model_name):
existing = self._get_existing_alert_keys(model_name, records.ids)
alerts_to_create = []
+ # Performance note: iterates records in Python. For very large recordsets (10k+),
+ # consider batch-reading field values via mapped() or read().
for record in records:
if record.id in existing:
continue
@@ -291,6 +364,8 @@ def _evaluate_date(self, records, model_name):
existing = self._get_existing_alert_keys(model_name, records.ids)
alerts_to_create = []
+ # Performance note: iterates records in Python. For very large recordsets (10k+),
+ # consider batch-reading field values via mapped() or read().
for record in records:
if record.id in existing:
continue
@@ -360,7 +435,6 @@ def _prepare_alert_vals(self, record, model_name, current_value=None, days_until
"description": self.description or "",
"res_model": model_name,
"res_id": record.id,
- "threshold_value": self.threshold_value,
}
if self.company_id:
@@ -368,6 +442,7 @@ def _prepare_alert_vals(self, record, model_name, current_value=None, days_until
if current_value is not None:
vals["current_value"] = current_value
+ vals["threshold_value"] = self.threshold_value
if days_until is not None:
vals["days_until"] = days_until
@@ -378,6 +453,9 @@ def _prepare_alert_vals(self, record, model_name, current_value=None, days_until
# Cron
# -------------------------------------------------------------------------
+ # Cron runs as superuser (OdooBot). Rule evaluation searches monitored models with
+ # full access, bypassing record rules. This is intentional — only managers can create
+ # rules, so the monitored scope is admin-controlled.
@api.model
def _cron_evaluate_rules(self):
"""Scheduled action to evaluate all active, configured rules."""
diff --git a/spp_alerts/readme/DESCRIPTION.md b/spp_alerts/readme/DESCRIPTION.md
index 66f7d653..69fee072 100644
--- a/spp_alerts/readme/DESCRIPTION.md
+++ b/spp_alerts/readme/DESCRIPTION.md
@@ -1,49 +1,61 @@
-Generic alert engine for threshold monitoring, expiry tracking, and deadline management. Provides base models and state machine for alert lifecycle tracking. Consumer modules (like `spp_drims`) extend these models and implement evaluation logic to generate alerts based on domain-specific conditions.
+Generic alert engine for threshold monitoring, expiry tracking, and deadline
+management. Evaluates configurable rules on a daily schedule or on-demand and
+generates alerts when conditions are met. Consumer modules (like `spp_drims`)
+extend these models to add domain-specific fields.
### Key Capabilities
-- Track alert lifecycle through state machine: active → acknowledged → resolved
-- Record resolution details including user, timestamp, and notes
-- Classify alerts by type using `spp.vocabulary` codes (threshold, expiry, deadline, manual, system)
-- Prioritize alerts as low, medium, high, or critical
-- Send mail notifications via `mail.thread` integration
-- Auto-generate alert references in ALR-YYYY-NNNNN format
+- Define alert rules with threshold or date conditions against any model
+- Evaluate rules via daily cron or "Run Now" button
+- Compare numeric fields using 5 operators: <, <=, >, >=, =
+- Check date/datetime fields against a days-before window
+- Prevent duplicates: skip records with existing active/acknowledged alerts
+- Filter monitored records using a visual domain builder
+- Track alert lifecycle: active → acknowledged → resolved
+- Record resolution details: user, timestamp, and notes
+- Navigate from alert to source record via stat button
+- Classify alerts by type using `spp.vocabulary` codes
+- Prioritize as low, medium, high, or critical
+- Auto-generate references in `ALR-YYYY-NNNNN` format
### Key Models
-| Model | Description |
-| ------------------ | ------------------------------------------------------- |
-| `spp.alert` | Alert instance with state tracking and resolution workflow |
-| `spp.alert.rule` | Rule configuration for monitoring criteria and thresholds |
+| Model | Description |
+| ---------------- | -------------------------------------------------------- |
+| `spp.alert` | Alert instance with state tracking and resolution audit |
+| `spp.alert.rule` | Rule configuration with evaluation engine and scheduling |
### Configuration
After installing:
1. Navigate to **Settings > Technical > Alerts > Alert Rules**
-2. Create rules specifying alert type, priority, threshold values, and days before deadline
-3. Consumer modules implement checking logic (e.g., cron jobs or event handlers) to evaluate rules and create alerts
+2. Create rules: select model, rule type (threshold/date), and conditions
+3. The daily cron "Alerts: Evaluate Alert Rules" is active by default
### UI Location
- **Menu**: Settings > Technical > Alerts > Alerts
- **Configuration**: Settings > Technical > Alerts > Alert Rules
-- **Form Tabs**: Details, Resolution (alerts); Thresholds (rules)
+- **Views**: List, kanban (grouped by state), and form
+- **Alert form tabs**: Details, Resolution
+- **Rule form**: Description above tabs; Evaluation tab with settings + domain builder
### Security
-| Group | Access |
-| -------------------------------- | ---------------------------------- |
-| `spp_alerts.group_alerts_viewer` | Read alerts |
-| `spp_alerts.group_alerts_officer` | Read/Write/Create (no delete) alerts |
-| `spp_alerts.group_alerts_manager` | Full CRUD on alerts and rules |
+| Group | Alerts | Rules |
+| --------------------------------- | ----------------------------- | --------- |
+| `spp_alerts.group_alerts_viewer` | Read | Read |
+| `spp_alerts.group_alerts_officer` | Read/Write/Create (no delete) | Read |
+| `spp_alerts.group_alerts_manager` | Full CRUD | Full CRUD |
### Extension Points
-- Inherit `spp.alert` to add domain-specific fields (e.g., stock levels, document references)
-- Inherit `spp.alert.rule` to add custom threshold or evaluation criteria
-- Override `action_acknowledge()` or `action_resolve()` to add custom workflow steps
-- Consumer modules implement alert checking via cron jobs or event handlers that evaluate rules and call `create()` on `spp.alert`
+- Inherit `spp.alert` to add domain-specific fields
+- Inherit `spp.alert.rule` to add custom evaluation criteria
+- Override `_evaluate_threshold()` or `_evaluate_date()` for custom logic
+- Override `action_acknowledge()` or `action_resolve()` for custom workflows
+- Rules can be configured via UI without code
### Dependencies
diff --git a/spp_alerts/readme/USAGE.md b/spp_alerts/readme/USAGE.md
new file mode 100644
index 00000000..3622dde5
--- /dev/null
+++ b/spp_alerts/readme/USAGE.md
@@ -0,0 +1,588 @@
+## UI Testing Guide
+
+This guide covers all user-facing functionality in the `spp_alerts` module.
+Follow each section in order. Each test case includes the steps, what to verify,
+and the expected result.
+
+**Prerequisites:**
+
+- Install the `spp_alerts` module
+- Log in as **admin** (has all permissions by default)
+- Ensure at least one model is available (e.g., `res.partner` — always present)
+
+---
+
+## 1. Navigation and Menu Access
+
+**TC-1.1: Menu visibility**
+
+1. Navigate to **Settings > Technical**
+2. Scroll down to find the **Alerts** section
+
+**Verify:**
+
+- [ ] An **Alerts** menu group is visible under Settings > Technical
+- [ ] It contains two submenus: **Alerts** and **Alert Rules**
+
+**TC-1.2: URL paths**
+
+1. Navigate to **Settings > Technical > Alerts > Alerts**
+2. Check the browser URL
+
+**Verify:**
+
+- [ ] URL ends with `/odoo/alerts`
+
+3. Navigate to **Settings > Technical > Alerts > Alert Rules**
+
+**Verify:**
+
+- [ ] URL ends with `/odoo/alert-rules`
+
+---
+
+## 2. Alert Rules — List View
+
+**TC-2.1: Empty state**
+
+1. Navigate to **Settings > Technical > Alerts > Alert Rules**
+2. Remove any active search filters
+
+**Verify (if no rules exist):**
+
+- [ ] Empty state shows smiley face icon
+- [ ] Message reads: "No alert rules configured"
+- [ ] Description mentions configuring rules for thresholds, expiry dates, and deadlines
+
+**TC-2.2: List view columns**
+
+1. Create at least one alert rule (see TC-3.1)
+2. Return to the list view
+
+**Verify:**
+
+- [ ] Columns visible: drag handle (sequence), Name, Alert Type, Model to Monitor, Rule Type, Priority, Threshold Value, Days Before, Active
+- [ ] Rule Type, Threshold Value, and Days Before have "optional column" toggles
+- [ ] Rows can be reordered by dragging the handle icon
+- [ ] Sample data appears in the background when the list is empty
+
+**TC-2.3: Search and filters**
+
+1. Click the search bar
+2. Try searching by Name, Alert Type, and Model
+
+**Verify:**
+
+- [ ] Filters available: Active, Inactive, Critical Priority, High Priority
+- [ ] Group By options: Alert Type, Model, Priority, Rule Type
+
+---
+
+## 3. Alert Rules — Form View
+
+**TC-3.1: Create a threshold rule**
+
+1. Click **New** on the Alert Rules list
+2. Fill in:
+ - **Rule Name**: "Test Low Color Warning"
+ - **Alert Type**: select "Threshold Alert" from the dropdown (cannot type to create new)
+ - **Default Priority**: "High"
+ - **Model to Monitor**: select "Contact" (res.partner)
+ - **Rule Type**: select "Threshold"
+3. Optionally add a **Description** in the text area below the main fields
+4. Observe the **Evaluation** tab appears after selecting Rule Type
+
+**Verify:**
+
+- [ ] Alert Type dropdown does NOT show a "Create" option
+- [ ] After selecting Model and Rule Type, the Evaluation tab appears
+- [ ] Evaluation tab shows **Threshold Settings** section with: Monitored Field, Comparison, Threshold Value
+
+5. In the Evaluation tab:
+ - **Monitored Field**: select "Color Index" (or any numeric field)
+ - **Comparison**: "Less Than (<)"
+ - **Threshold Value**: 5
+6. Under **Record Filter**, observe the visual domain builder
+
+**Verify:**
+
+- [ ] Domain builder shows fields from the selected model (Contact/res.partner)
+- [ ] You can add filter conditions visually (e.g., "Active is set")
+
+7. Save the rule
+
+**Verify:**
+
+- [ ] Rule saves without errors
+- [ ] Chatter (message log) appears at the bottom of the form
+- [ ] An **Alerts** stat button appears in the top-right showing "0 Alerts"
+
+**TC-3.2: Create a date rule**
+
+1. Create a new rule:
+ - **Rule Name**: "Test Deadline Warning"
+ - **Alert Type**: "Deadline Alert"
+ - **Model to Monitor**: "Contact"
+ - **Rule Type**: "Date / Deadline"
+
+**Verify:**
+
+- [ ] Evaluation tab shows **Date Settings** section (not Threshold Settings)
+- [ ] Date Settings has: Date Field and Days Before
+
+2. Fill in:
+ - **Date Field**: select "Last Updated on" (write_date) or any date/datetime field
+ - **Days Before**: 30
+3. Save
+
+**Verify:**
+
+- [ ] Rule saves without errors
+
+**TC-3.3: Run Now button**
+
+1. Open the threshold rule created in TC-3.1
+2. Click the **Run Now** button in the header
+
+**Verify:**
+
+- [ ] A notification appears: "X alert(s) created by rule 'Test Low Color Warning'"
+- [ ] The Alerts stat button count updates to reflect created alerts
+- [ ] Click the stat button — it opens a filtered list of alerts from this rule
+
+**TC-3.4: Archive and unarchive**
+
+1. Open any rule
+2. Click **Action > Archive**
+
+**Verify:**
+
+- [ ] A red **Archived** ribbon appears on the form
+- [ ] The rule disappears from the default list view (which filters active rules)
+
+3. In the list view, add the "Inactive" filter
+
+**Verify:**
+
+- [ ] The archived rule appears
+- [ ] Open it and click **Action > Unarchive** — ribbon disappears
+
+**TC-3.5: Validation errors**
+
+1. Create a new rule with Rule Type = "Threshold" but leave Monitored Field empty
+2. Try to save
+
+**Verify:**
+
+- [ ] Error: "A monitored field is required for threshold rules."
+
+3. Create a new rule with Rule Type = "Date / Deadline" but leave Date Field empty
+4. Try to save
+
+**Verify:**
+
+- [ ] Error: "A date field is required for date rules."
+
+5. Create a rule and set Domain Filter to an invalid expression (e.g., type `INVALID` in
+ the domain builder's code editor if available)
+6. Try to save
+
+**Verify:**
+
+- [ ] Error: "Invalid domain filter: ..."
+
+**TC-3.6: Run Now without configuration**
+
+1. Create a rule with no Rule Type and no Model
+2. Observe the header
+
+**Verify:**
+
+- [ ] The **Run Now** button is NOT visible (it requires both Rule Type and Model)
+
+---
+
+## 4. Alerts — Creating Manually
+
+**TC-4.1: Create a manual alert**
+
+1. Navigate to **Settings > Technical > Alerts > Alerts**
+2. Click **New**
+3. Fill in:
+ - **Alert Type**: "Manual Alert"
+ - **Priority**: "Critical"
+ - **Title**: "Test Manual Alert"
+4. Go to the **Details** tab and add a description
+5. Save
+
+**Verify:**
+
+- [ ] Reference auto-generated in format `ALR-YYYY-NNNNN` (e.g., ALR-2026-00001)
+- [ ] State shows "Active" in the statusbar
+- [ ] **Acknowledge** button (blue) is visible in the header
+- [ ] **Resolve** button (green) is visible in the header
+- [ ] Priority and Alert Type fields are editable
+- [ ] Chatter (message log) appears at the bottom
+- [ ] No "View Source" or "Related Alerts" stat buttons (manual alert has no source)
+
+**TC-4.2: Unique references**
+
+1. Create three alerts quickly
+
+**Verify:**
+
+- [ ] Each alert gets a unique, sequential reference number
+
+---
+
+## 5. Alerts — List View
+
+**TC-5.1: List view appearance**
+
+1. Navigate to **Settings > Technical > Alerts > Alerts**
+2. Ensure some alerts exist (use Run Now on a rule, or create manually)
+
+**Verify:**
+
+- [ ] Columns visible: Reference, Title, Alert Type, Priority (badge), State (badge), Created On
+- [ ] Critical-priority rows have a red tint
+- [ ] High-priority rows have an orange/warning tint
+- [ ] Resolved rows are muted/grayed out
+- [ ] Priority badges: Critical = red, High = orange, Medium = blue
+- [ ] State badges: Active = red, Acknowledged = orange, Resolved = green
+- [ ] Source Rule and Company columns are hidden by default (use optional column toggle)
+- [ ] Default filter shows only Active alerts (check search bar for "Active" filter chip)
+
+**TC-5.2: Search panel**
+
+1. Look at the left side of the list view
+
+**Verify:**
+
+- [ ] Search panel shows three filter groups: State, Priority, Alert Type
+- [ ] Each option shows a count of matching alerts
+- [ ] Clicking a filter value narrows the list immediately
+
+**TC-5.3: Search filters and Group By**
+
+1. Click the search bar
+
+**Verify:**
+
+- [ ] Can search by Reference, Title, Alert Type
+- [ ] Filters: Active, Acknowledged, Resolved, Critical, High Priority
+- [ ] Group By: State, Priority, Type
+
+---
+
+## 6. Alerts — Kanban View
+
+**TC-6.1: Switch to kanban**
+
+1. On the Alerts list, click the kanban view icon (grid icon in the view switcher)
+
+**Verify:**
+
+- [ ] Alerts are displayed as cards grouped into columns by State: Active, Acknowledged, Resolved
+- [ ] Each column header shows the state name and alert count
+- [ ] A colored progress bar appears at the top of each column showing priority distribution
+ (gray = low, blue = medium, orange = high, red = critical)
+- [ ] Quick create is disabled (no "+" button at top of columns)
+
+**TC-6.2: Kanban card content**
+
+1. Examine an alert card
+
+**Verify:**
+
+- [ ] Card shows: priority stars, reference (bold), title, alert type, creation date
+- [ ] A three-dot dropdown menu appears on hover (top-right of card)
+
+**TC-6.3: Kanban dropdown actions**
+
+1. Hover over an **Active** alert card and click the three-dot menu
+
+**Verify:**
+
+- [ ] Dropdown shows: **Acknowledge** and **Resolve**
+
+2. Click **Acknowledge**
+
+**Verify:**
+
+- [ ] Card moves to the Acknowledged column
+
+3. Hover over the now-acknowledged card, open dropdown
+
+**Verify:**
+
+- [ ] Dropdown shows only **Resolve** (Acknowledge is gone)
+
+4. Click **Resolve**
+
+**Verify:**
+
+- [ ] A dialog or form opens requesting resolution notes (since the action requires notes)
+
+---
+
+## 7. Alert State Transitions
+
+**TC-7.1: Active to Acknowledged**
+
+1. Open an Active alert
+2. Click the **Acknowledge** button
+
+**Verify:**
+
+- [ ] State changes to "Acknowledged"
+- [ ] Statusbar updates to highlight "Acknowledged"
+- [ ] Acknowledge button disappears
+- [ ] Resolve button remains visible
+- [ ] Fields (Alert Type, Priority, Title) are still editable
+- [ ] Chatter logs a state change message
+
+**TC-7.2: Acknowledged to Resolved**
+
+1. On the acknowledged alert, go to the **Resolution** tab
+
+**Verify:**
+
+- [ ] An info banner reads: "Please add resolution notes describing how this alert was addressed, then click Resolve."
+- [ ] Resolved By and Resolved At fields are empty
+
+2. Enter resolution notes in the text area
+3. Click the **Resolve** button
+
+**Verify:**
+
+- [ ] State changes to "Resolved"
+- [ ] Resolved By shows your user name
+- [ ] Resolved At shows the current timestamp
+- [ ] Resolution Notes are preserved and now read-only
+- [ ] Info banner disappears
+- [ ] Both Acknowledge and Resolve buttons disappear
+- [ ] All main fields (Alert Type, Priority, Title, Description) become read-only
+
+**TC-7.3: Active to Resolved directly (skip Acknowledge)**
+
+1. Create a new alert and leave it in Active state
+2. Go to the **Resolution** tab
+3. Enter resolution notes
+4. Click **Resolve**
+
+**Verify:**
+
+- [ ] Alert goes directly from Active to Resolved (skipping Acknowledged)
+- [ ] All resolution fields are populated correctly
+
+**TC-7.4: Resolve without notes**
+
+1. Create a new Active alert
+2. Click **Resolve** without entering resolution notes
+
+**Verify:**
+
+- [ ] Error message: "Please provide resolution notes before resolving the alert."
+- [ ] Alert remains in its current state
+
+**TC-7.5: Double-acknowledge prevention**
+
+1. Acknowledge an alert
+2. Try to acknowledge it again (via API or another browser tab)
+
+**Verify:**
+
+- [ ] Error: "Only active alerts can be acknowledged."
+- [ ] The Acknowledge button is not visible on the form (it only shows for Active alerts)
+
+**TC-7.6: Double-resolve prevention**
+
+1. Resolve an alert
+2. Try to resolve it again
+
+**Verify:**
+
+- [ ] Error: "Alert 'ALR-YYYY-NNNNN' is already resolved."
+- [ ] The Resolve button is not visible on the form
+
+---
+
+## 8. Alert Form — Rule-Generated Alerts
+
+**TC-8.1: Source tracking fields**
+
+1. Run a rule (via **Run Now** on an alert rule)
+2. Open one of the generated alerts
+
+**Verify:**
+
+- [ ] Right side of the form shows a **Source** section with: Source Rule, Source Model, Source Record, Source Record Name
+- [ ] If the rule is a threshold rule, a **Metrics** section shows: Current Value, Threshold, Days Until
+- [ ] A **View Source** stat button appears in the top-right
+- [ ] A **Related Alerts** stat button appears (if rule created multiple alerts)
+
+**TC-8.2: View Source button**
+
+1. Click the **View Source** stat button
+
+**Verify:**
+
+- [ ] Opens the source record's form view (e.g., a Contact form)
+- [ ] The correct record is displayed
+
+**TC-8.3: Related Alerts button**
+
+1. Go back to the alert and click the **Related Alerts** stat button
+
+**Verify:**
+
+- [ ] Opens a list of other alerts from the same rule (excluding the current alert)
+- [ ] Default filter shows Active alerts
+
+---
+
+## 9. Keyboard Shortcuts
+
+**TC-9.1: Hotkeys**
+
+1. Open an Active alert form
+2. Press **Alt+A** (or the platform equivalent for `data-hotkey="a"`)
+
+**Verify:**
+
+- [ ] Alert is acknowledged
+
+3. Press **Alt+R**
+
+**Verify:**
+
+- [ ] Resolve action is triggered (will ask for notes if none provided)
+
+---
+
+## 10. Cron Job
+
+**TC-10.1: Scheduled action exists**
+
+1. Navigate to **Settings > Technical > Scheduled Actions**
+2. Search for "Alerts"
+
+**Verify:**
+
+- [ ] A scheduled action named **"Alerts: Evaluate Alert Rules"** exists
+- [ ] It is active
+- [ ] Interval is set to 1 day
+
+**TC-10.2: Cron execution**
+
+1. Ensure at least one active alert rule with matching records exists
+2. Click **Run Manually** on the scheduled action
+
+**Verify:**
+
+- [ ] New alerts are created for matching records
+- [ ] No duplicate alerts for records that already have active/acknowledged alerts
+
+---
+
+## 11. Security and Access Control
+
+Test with three different users. Create them via **Settings > Users & Companies > Users**
+and assign the appropriate group under the SPP Admin section.
+
+**TC-11.1: Viewer role**
+
+1. Log in as a user with **Alerts Viewer** group only
+2. Navigate to **Settings > Technical > Alerts > Alerts**
+
+**Verify:**
+
+- [ ] Can see the Alerts menu and list
+- [ ] Can open and read alert details
+- [ ] Cannot create new alerts (New button absent or errors on save)
+- [ ] Cannot edit existing alerts
+- [ ] Cannot acknowledge or resolve alerts (buttons error on click)
+- [ ] **Alert Rules** submenu is NOT visible
+
+**TC-11.2: Officer role**
+
+1. Log in as a user with **Alerts Officer** group
+2. Navigate to **Settings > Technical > Alerts > Alerts**
+
+**Verify:**
+
+- [ ] Can create new alerts
+- [ ] Can edit alerts (change priority, title, etc.)
+- [ ] Can acknowledge and resolve alerts
+- [ ] Cannot delete alerts
+- [ ] Can see **Alert Rules** submenu but rules are read-only
+- [ ] Cannot create or edit alert rules
+
+**TC-11.3: Manager role**
+
+1. Log in as a user with **Alerts Manager** group
+
+**Verify:**
+
+- [ ] Full access to alerts: create, read, update, delete
+- [ ] Full access to alert rules: create, read, update, delete
+- [ ] Can click **Run Now** on alert rules
+- [ ] Can archive/unarchive rules
+
+---
+
+## 12. Multi-Company (if applicable)
+
+Only test this section if multi-company is enabled.
+
+**TC-12.1: Company isolation**
+
+1. Create an alert in Company A
+2. Switch to Company B
+
+**Verify:**
+
+- [ ] The alert from Company A is not visible in Company B
+- [ ] New alerts default to the current company
+
+---
+
+## 13. Alert Types (Vocabulary)
+
+**TC-13.1: Pre-installed types**
+
+1. Open any alert or rule form
+2. Click the **Alert Type** dropdown
+
+**Verify the following types are available:**
+
+- [ ] Threshold Alert
+- [ ] Expiry Alert
+- [ ] Deadline Alert
+- [ ] Manual Alert
+- [ ] System Alert
+- [ ] Cannot create new types from the dropdown (no "Create" option)
+
+---
+
+## 14. Edge Cases
+
+**TC-14.1: Empty state**
+
+1. Delete or resolve all alerts
+2. Remove the "Active" default filter
+
+**Verify:**
+
+- [ ] Empty state shows: "No active alerts" with smiley face
+
+**TC-14.2: Sorting**
+
+1. Create alerts with different priorities (low, medium, high, critical)
+2. View the list (default sort)
+
+**Verify:**
+
+- [ ] Alerts are sorted by priority (critical first) then by creation date (newest first)
+- [ ] This is semantic ordering: critical > high > medium > low (not alphabetical)
diff --git a/spp_alerts/security/ir.model.access.csv b/spp_alerts/security/ir.model.access.csv
index 8674d63a..f17a88ee 100644
--- a/spp_alerts/security/ir.model.access.csv
+++ b/spp_alerts/security/ir.model.access.csv
@@ -1,13 +1,13 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_spp_alert_sysadmin,Alert System Admin,model_spp_alert,base.group_system,1,1,1,1
-access_spp_alert_rule_sysadmin,Alert Rule System Admin,model_spp_alert_rule,base.group_system,1,1,1,1
-access_spp_alert_admin,Alert Admin,model_spp_alert,spp_security.group_spp_admin,1,1,1,1
-access_spp_alert_rule_admin,Alert Rule Admin,model_spp_alert_rule,spp_security.group_spp_admin,1,1,1,1
-access_spp_alert_read,Alert Read,model_spp_alert,group_alerts_read,1,0,0,0
-access_spp_alert_rule_read,Alert Rule Read,model_spp_alert_rule,group_alerts_read,1,0,0,0
-access_spp_alert_write,Alert Write,model_spp_alert,group_alerts_write,1,1,0,0
-access_spp_alert_create,Alert Create,model_spp_alert,group_alerts_create,1,1,1,0
-access_spp_alert_manager,Alert Manager,model_spp_alert,group_alerts_manager,1,1,1,1
-access_spp_alert_rule_manager,Alert Rule Manager,model_spp_alert_rule,group_alerts_manager,1,1,1,1
-access_spp_alert_officer,Alert Officer,model_spp_alert,group_alerts_officer,1,1,1,0
-access_spp_alert_viewer,Alert Viewer,model_spp_alert,group_alerts_viewer,1,0,0,0
+access_spp_alert_system,Alert System Admin,model_spp_alert,base.group_system,1,1,1,1
+access_spp_alert_rule_system,Alert Rule System Admin,model_spp_alert_rule,base.group_system,1,1,1,1
+access_spp_alert_spp_admin,Alert SPP Admin,model_spp_alert,spp_security.group_spp_admin,1,1,1,1
+access_spp_alert_rule_spp_admin,Alert Rule SPP Admin,model_spp_alert_rule,spp_security.group_spp_admin,1,1,1,1
+access_spp_alert_alerts_read,Alert Read,model_spp_alert,group_alerts_read,1,0,0,0
+access_spp_alert_rule_alerts_read,Alert Rule Read,model_spp_alert_rule,group_alerts_read,1,0,0,0
+access_spp_alert_alerts_write,Alert Write,model_spp_alert,group_alerts_write,1,1,0,0
+access_spp_alert_alerts_create,Alert Create,model_spp_alert,group_alerts_create,1,1,1,0
+access_spp_alert_alerts_manager,Alert Manager,model_spp_alert,group_alerts_manager,1,1,1,1
+access_spp_alert_rule_alerts_manager,Alert Rule Manager,model_spp_alert_rule,group_alerts_manager,1,1,1,1
+access_spp_alert_alerts_officer,Alert Officer,model_spp_alert,group_alerts_officer,1,1,1,0
+access_spp_alert_alerts_viewer,Alert Viewer,model_spp_alert,group_alerts_viewer,1,0,0,0
diff --git a/spp_alerts/static/description/index.html b/spp_alerts/static/description/index.html
index 78cf219d..9fe102a8 100644
--- a/spp_alerts/static/description/index.html
+++ b/spp_alerts/static/description/index.html
@@ -371,21 +371,25 @@
Generic alert engine for threshold monitoring, expiry tracking, and
-deadline management. Provides base models and state machine for alert
-lifecycle tracking. Consumer modules (like spp_drims) extend these
-models and implement evaluation logic to generate alerts based on
-domain-specific conditions.
+deadline management. Evaluates configurable rules on a daily schedule or
+on-demand and generates alerts when conditions are met. Consumer modules
+(like spp_drims) extend these models to add domain-specific fields.
Key Capabilities
-
Track alert lifecycle through state machine: active → acknowledged →
-resolved
-
Record resolution details including user, timestamp, and notes
-
Classify alerts by type using spp.vocabulary codes (threshold,
-expiry, deadline, manual, system)
-
Prioritize alerts as low, medium, high, or critical
-
Send mail notifications via mail.thread integration
-
Auto-generate alert references in ALR-YYYY-NNNNN format
+
Define alert rules with threshold or date conditions against any model
This guide covers all user-facing functionality in the spp_alerts
+module. Follow each section in order. Each test case includes the steps,
+what to verify, and the expected result.
+
Prerequisites:
+
+
Install the spp_alerts module
+
Log in as admin (has all permissions by default)
+
Ensure at least one model is available (e.g., res.partner — always
+present)
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
@@ -492,15 +1173,15 @@