""" app/models/notification_recipient.py ------------------------------------ Per-contract additional notification recipients. Each row is ONE extra recipient attached to a Contract (Project) who should be notified when selected notification events fire within that contract's facilities — in ADDITION to whoever the global NotificationMatrix already routes to. A recipient is either: - an existing app user (user_id set, email NULL) → in-app notification + email - a free-form email (email set, user_id NULL) → email only `event_types` is a JSON list of event_type keys (matching MATRIX_EVENTS) that this recipient subscribes to for this contract. A recipient is notified only when the firing event_type is present in this list. An empty list means the recipient receives nothing (the add form requires at least one event). Resolution happens centrally in notify_by_matrix() (app/utils/notifications.py), which derives the contract from the event's facility / issue / inspection. """ import json from app import db from app.utils.time_utils import now_eastern class ContractNotificationRecipient(db.Model): __tablename__ = 'contract_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, not the DB). user_id = db.Column( db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), nullable=True, index=True, ) email = db.Column(db.String(200), nullable=True) # JSON list of event_type keys this recipient is subscribed to for this contract. event_types = db.Column(db.Text, nullable=True) # JSON-encoded list[str] created_at = db.Column(db.DateTime, nullable=False, default=now_eastern) 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]) def get_event_types(self) -> list: """Return event_types as a Python list of strings.""" if not self.event_types: return [] try: result = json.loads(self.event_types) return [e for e in result if isinstance(e, str) and e.strip()] except (json.JSONDecodeError, TypeError): return [] def set_event_types(self, values) -> None: """Store a list of event_type keys as a JSON string.""" clean = [v.strip() for v in (values or []) if isinstance(v, str) and v.strip()] self.event_types = json.dumps(clean) @property def display_target(self) -> str: """Human-readable recipient label for the UI.""" if self.user_id and self.user: return f'{self.user.display_name} ({self.user.email})' return self.email or '—' def __repr__(self): who = f'user={self.user_id}' if self.user_id else f'email={self.email}' return f''