220 lines
9.9 KiB
Python
220 lines
9.9 KiB
Python
"""
|
|
app/models/notification_matrix.py
|
|
----------------------------------
|
|
Admin-controlled notification matrix.
|
|
|
|
One row per (event_type, role_key) pair.
|
|
|
|
role_key values
|
|
---------------
|
|
admin — all users with role='admin'
|
|
supervisor — all users with role='supervisor'
|
|
inspector — all users with role='inspector'
|
|
project_manager — all users with role='project_manager'
|
|
customer — all customer-portal users assigned to the relevant facility
|
|
assignee — the specific user the issue/inspection is assigned to
|
|
(implicit; always notified regardless of matrix)
|
|
custom — free-form extra email addresses stored in custom_emails JSON
|
|
|
|
Default matrix (mirrors current hardcoded behaviour)
|
|
-----------------------------------------------------
|
|
inspection_completed : admin ✓ supervisor ✓ inspector ✗ pm ✗ customer ✓
|
|
issue_assigned : admin ✗ supervisor ✗ inspector ✗ pm ✗ customer ✗ (assignee implicit)
|
|
issue_status : admin ✗ supervisor ✗ inspector ✗ pm ✗ customer ✗ (assignee implicit)
|
|
issue_comment : admin ✗ supervisor ✗ inspector ✗ pm ✗ customer ✗ (assignee implicit)
|
|
issue_follow_update : admin ✗ supervisor ✗ inspector ✗ pm ✗ customer ✗ (followers implicit)
|
|
issue_flagged : admin ✗ supervisor ✗ inspector ✗ pm ✗ customer ✓ (assignee implicit)
|
|
issue_created : admin ✗ supervisor ✗ inspector ✗ pm ✗ customer ✓ (assignee implicit)
|
|
issue_updated_customer : admin ✗ supervisor ✗ inspector ✗ pm ✗ customer ✓
|
|
verification_requested : admin ✓ supervisor ✓ inspector ✗ pm ✗ customer ✗
|
|
sla_alert : admin ✓ supervisor ✗ inspector ✗ pm ✗ customer ✗ (assignee + followers implicit)
|
|
"""
|
|
|
|
import json
|
|
from app import db
|
|
|
|
# Role keys available in the matrix UI
|
|
MATRIX_ROLES = [
|
|
('admin', 'Admin'),
|
|
('supervisor', 'Supervisor'),
|
|
('inspector', 'Inspector'),
|
|
('project_manager', 'Project Manager'),
|
|
('customer', 'Customer'),
|
|
('custom', 'Custom Recipients'),
|
|
]
|
|
|
|
# Events shown in the matrix — maps event_key → display label
|
|
# event_key is used as the DB event_type value
|
|
MATRIX_EVENTS = {
|
|
'inspection_completed': 'Inspection completed',
|
|
'issue_assigned': 'Issue assigned (new)',
|
|
'issue_reassigned': 'Issue reassigned',
|
|
'issue_unassigned': 'Issue unassigned',
|
|
'issue_status': 'Issue status changed',
|
|
'issue_comment': 'Issue comment added',
|
|
'issue_follow_update': 'Issue follow update',
|
|
'issue_flagged': 'Issue flagged (from inspection)',
|
|
'issue_created': 'Issue created (standalone)',
|
|
'issue_updated_customer': 'Issue updated (customer)',
|
|
'verification_requested': 'Verification requested',
|
|
'sla_alert': 'SLA at-risk / breached',
|
|
}
|
|
|
|
# Default enabled state: (event_key, role_key) → True/False
|
|
# Mirrors the current hardcoded behaviour exactly.
|
|
MATRIX_DEFAULTS = {
|
|
# inspection_completed
|
|
('inspection_completed', 'admin'): True,
|
|
('inspection_completed', 'supervisor'): True,
|
|
('inspection_completed', 'inspector'): False,
|
|
('inspection_completed', 'project_manager'): False,
|
|
('inspection_completed', 'customer'): True,
|
|
('inspection_completed', 'custom'): False,
|
|
# issue_assigned (assignee is always notified implicitly)
|
|
('issue_assigned', 'admin'): False,
|
|
('issue_assigned', 'supervisor'): False,
|
|
('issue_assigned', 'inspector'): False,
|
|
('issue_assigned', 'project_manager'): False,
|
|
('issue_assigned', 'customer'): False,
|
|
('issue_assigned', 'custom'): False,
|
|
# issue_reassigned
|
|
('issue_reassigned', 'admin'): False,
|
|
('issue_reassigned', 'supervisor'): False,
|
|
('issue_reassigned', 'inspector'): False,
|
|
('issue_reassigned', 'project_manager'): False,
|
|
('issue_reassigned', 'customer'): False,
|
|
('issue_reassigned', 'custom'): False,
|
|
# issue_unassigned
|
|
('issue_unassigned', 'admin'): False,
|
|
('issue_unassigned', 'supervisor'): False,
|
|
('issue_unassigned', 'inspector'): False,
|
|
('issue_unassigned', 'project_manager'): False,
|
|
('issue_unassigned', 'customer'): False,
|
|
('issue_unassigned', 'custom'): False,
|
|
# issue_status
|
|
('issue_status', 'admin'): False,
|
|
('issue_status', 'supervisor'): False,
|
|
('issue_status', 'inspector'): False,
|
|
('issue_status', 'project_manager'): False,
|
|
('issue_status', 'customer'): False,
|
|
('issue_status', 'custom'): False,
|
|
# issue_comment
|
|
('issue_comment', 'admin'): False,
|
|
('issue_comment', 'supervisor'): False,
|
|
('issue_comment', 'inspector'): False,
|
|
('issue_comment', 'project_manager'): False,
|
|
('issue_comment', 'customer'): False,
|
|
('issue_comment', 'custom'): False,
|
|
# issue_follow_update (followers always notified implicitly)
|
|
('issue_follow_update', 'admin'): False,
|
|
('issue_follow_update', 'supervisor'): False,
|
|
('issue_follow_update', 'inspector'): False,
|
|
('issue_follow_update', 'project_manager'): False,
|
|
('issue_follow_update', 'customer'): False,
|
|
('issue_follow_update', 'custom'): False,
|
|
# issue_flagged (from inspection)
|
|
('issue_flagged', 'admin'): False,
|
|
('issue_flagged', 'supervisor'): False,
|
|
('issue_flagged', 'inspector'): False,
|
|
('issue_flagged', 'project_manager'): False,
|
|
('issue_flagged', 'customer'): True,
|
|
('issue_flagged', 'custom'): False,
|
|
# issue_created (standalone)
|
|
('issue_created', 'admin'): False,
|
|
('issue_created', 'supervisor'): False,
|
|
('issue_created', 'inspector'): False,
|
|
('issue_created', 'project_manager'): False,
|
|
('issue_created', 'customer'): True,
|
|
('issue_created', 'custom'): False,
|
|
# issue_updated_customer
|
|
('issue_updated_customer', 'admin'): False,
|
|
('issue_updated_customer', 'supervisor'): False,
|
|
('issue_updated_customer', 'inspector'): False,
|
|
('issue_updated_customer', 'project_manager'): False,
|
|
('issue_updated_customer', 'customer'): True,
|
|
('issue_updated_customer', 'custom'): False,
|
|
# verification_requested
|
|
('verification_requested', 'admin'): True,
|
|
('verification_requested', 'supervisor'): True,
|
|
('verification_requested', 'inspector'): False,
|
|
('verification_requested', 'project_manager'): False,
|
|
('verification_requested', 'customer'): False,
|
|
('verification_requested', 'custom'): False,
|
|
# sla_alert (assignee + followers always notified implicitly)
|
|
('sla_alert', 'admin'): True,
|
|
('sla_alert', 'supervisor'): False,
|
|
('sla_alert', 'inspector'): False,
|
|
('sla_alert', 'project_manager'): False,
|
|
('sla_alert', 'customer'): False,
|
|
('sla_alert', 'custom'): False,
|
|
}
|
|
|
|
|
|
class NotificationMatrix(db.Model):
|
|
"""Admin-controlled per-event notification routing."""
|
|
|
|
__tablename__ = 'notification_matrix'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
event_type = db.Column(db.String(50), nullable=False)
|
|
role_key = db.Column(db.String(30), nullable=False)
|
|
enabled = db.Column(db.Boolean, nullable=False, default=True)
|
|
custom_emails = db.Column(db.Text, nullable=True) # JSON list, only used when role_key='custom'
|
|
|
|
__table_args__ = (
|
|
db.UniqueConstraint('event_type', 'role_key', name='uq_notif_matrix_event_role'),
|
|
)
|
|
|
|
def get_custom_emails(self):
|
|
"""Return custom_emails as a Python list."""
|
|
if not self.custom_emails:
|
|
return []
|
|
try:
|
|
result = json.loads(self.custom_emails)
|
|
return [e.strip() for e in result if isinstance(e, str) and e.strip()]
|
|
except (json.JSONDecodeError, TypeError):
|
|
return []
|
|
|
|
def __repr__(self):
|
|
return f'<NotificationMatrix {self.event_type} / {self.role_key} enabled={self.enabled}>'
|
|
|
|
|
|
def get_matrix_row(event_type: str, role_key: str) -> NotificationMatrix:
|
|
"""
|
|
Return the matrix row for (event_type, role_key), creating it from
|
|
defaults if it doesn't exist yet. Safe to call without seeding.
|
|
"""
|
|
row = NotificationMatrix.query.filter_by(
|
|
event_type=event_type, role_key=role_key
|
|
).first()
|
|
if row is None:
|
|
default = MATRIX_DEFAULTS.get((event_type, role_key), False)
|
|
row = NotificationMatrix(
|
|
event_type=event_type,
|
|
role_key=role_key,
|
|
enabled=default,
|
|
)
|
|
db.session.add(row)
|
|
db.session.flush()
|
|
return row
|
|
|
|
|
|
def is_enabled(event_type: str, role_key: str) -> bool:
|
|
"""Return True if the matrix enables notifications for this event/role pair."""
|
|
row = NotificationMatrix.query.filter_by(
|
|
event_type=event_type, role_key=role_key
|
|
).first()
|
|
if row is None:
|
|
return MATRIX_DEFAULTS.get((event_type, role_key), False)
|
|
return row.enabled
|
|
|
|
|
|
def get_custom_emails_for(event_type: str) -> list:
|
|
"""Return the custom email list for this event type."""
|
|
row = NotificationMatrix.query.filter_by(
|
|
event_type=event_type, role_key='custom'
|
|
).first()
|
|
if row is None:
|
|
return []
|
|
return row.get_custom_emails()
|