Jul 7 - Implement additional recipient per contract
This commit is contained in:
@@ -4,6 +4,7 @@ from app.models.inspection import (InspectionTemplate, ChecklistItem,
|
||||
Inspection, InspectionResult)
|
||||
from app.models.issue import Issue
|
||||
from app.models.project import Project, CustomerAssignment
|
||||
from app.models.project_recipient import ProjectNotificationRecipient
|
||||
from app.models.api_token import RefreshToken, DeviceToken
|
||||
from app.models.notification_matrix import NotificationMatrix
|
||||
from app.models.inspection_schedule import InspectionSchedule
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
app/models/project_recipient.py
|
||||
-------------------------------
|
||||
Per-contract (Project) additional notification recipients.
|
||||
|
||||
The global notification matrix controls WHICH ROLES receive each event
|
||||
org-wide. This model adds contract-scoped recipients on top of that:
|
||||
each row subscribes one recipient to a chosen set of matrix event types,
|
||||
but ONLY for events that occur in facilities belonging to that contract.
|
||||
|
||||
Recipient kinds
|
||||
---------------
|
||||
staff — user_id set, email NULL. Gets an in-app Notification + email
|
||||
via notify() (matrix-authority mode, preferences not consulted).
|
||||
external — email set, user_id NULL. Gets a plain email only (no account,
|
||||
no in-app record) via _send_custom_email().
|
||||
|
||||
`events` stores a JSON list of MATRIX_EVENTS keys (see
|
||||
app/models/notification_matrix.py). Dispatch happens inside
|
||||
notify_by_matrix() → _notify_project_recipients() after the matrix roles
|
||||
and global custom emails are processed.
|
||||
"""
|
||||
|
||||
import json
|
||||
from app import db
|
||||
from app.utils.time_utils import now_eastern
|
||||
|
||||
|
||||
class ProjectNotificationRecipient(db.Model):
|
||||
"""A contract-scoped additional notification recipient."""
|
||||
|
||||
__tablename__ = 'project_notification_recipients'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
project_id = db.Column(db.Integer,
|
||||
db.ForeignKey('projects.id', ondelete='CASCADE'),
|
||||
nullable=False, index=True)
|
||||
# Exactly one of (user_id, email) is set — enforced in the route layer.
|
||||
user_id = db.Column(db.Integer,
|
||||
db.ForeignKey('users.id', ondelete='CASCADE'),
|
||||
nullable=True, index=True)
|
||||
email = db.Column(db.String(255), nullable=True)
|
||||
events = db.Column(db.Text, nullable=False, default='[]') # JSON list of event keys
|
||||
created_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
||||
|
||||
# Relationships
|
||||
project = db.relationship('Project', foreign_keys=[project_id],
|
||||
backref=db.backref('notification_recipients',
|
||||
lazy='dynamic',
|
||||
cascade='all, delete-orphan'))
|
||||
user = db.relationship('User', foreign_keys=[user_id],
|
||||
backref=db.backref('project_notification_subscriptions',
|
||||
lazy='dynamic'))
|
||||
|
||||
@property
|
||||
def is_staff(self):
|
||||
"""True when the recipient is an application user (in-app + email)."""
|
||||
return self.user_id is not None
|
||||
|
||||
def get_events(self):
|
||||
"""Return the subscribed event keys as a Python list."""
|
||||
if not self.events:
|
||||
return []
|
||||
try:
|
||||
result = json.loads(self.events)
|
||||
return [e for e in result if isinstance(e, str) and e.strip()]
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return []
|
||||
|
||||
def __repr__(self):
|
||||
who = f'user={self.user_id}' if self.user_id else f'email={self.email}'
|
||||
return f'<ProjectNotificationRecipient project={self.project_id} {who}>'
|
||||
Reference in New Issue
Block a user