Jul 7 - Implement additional recipient per contract
This commit is contained in:
@@ -603,12 +603,140 @@ def notify_by_matrix(
|
||||
for email in custom_emails:
|
||||
_send_custom_email(email, title, body, link)
|
||||
|
||||
# ── Per-contract additional recipients (phase37) ──────────────────────
|
||||
_notify_project_recipients(
|
||||
event_type = event_type,
|
||||
title = title,
|
||||
body = body,
|
||||
link = link,
|
||||
issue_id = issue_id,
|
||||
inspection_id = inspection_id,
|
||||
facility_id = facility_id,
|
||||
exclude_user_ids = exclude,
|
||||
already_notified = notified,
|
||||
already_emailed = {e.strip().lower() for e in custom_emails},
|
||||
)
|
||||
|
||||
logger.info(
|
||||
'MATRIX NOTIFY | event=%s | notified=%s | custom_emails=%s',
|
||||
event_type, len(notified), len(custom_emails),
|
||||
)
|
||||
|
||||
|
||||
def _notify_project_recipients(
|
||||
event_type: str,
|
||||
title: str,
|
||||
body: str,
|
||||
link: str = None,
|
||||
issue_id: int = None,
|
||||
inspection_id: int = None,
|
||||
facility_id: int = None,
|
||||
exclude_user_ids: set = None,
|
||||
already_notified: set = None,
|
||||
already_emailed: set = None,
|
||||
):
|
||||
"""
|
||||
Dispatch to per-contract additional recipients (ProjectNotificationRecipient).
|
||||
|
||||
Called from notify_by_matrix() AFTER the global matrix roles and custom
|
||||
emails. Resolves the contract via the event's facility:
|
||||
facility_id arg → else issue.resolved_facility → else inspection.facility_id
|
||||
then notifies every recipient of that facility's contract whose subscribed
|
||||
event list contains event_type.
|
||||
|
||||
Staff recipients (user_id set) get an in-app Notification + email via
|
||||
notify() with respect_preferences=False (matrix-authority mode, same as
|
||||
role broadcasts). External recipients (email set) get a plain email only.
|
||||
|
||||
Deduplication: `already_notified` (user IDs notified by the matrix roles)
|
||||
and `already_emailed` (lowercased global custom emails) are honoured and
|
||||
mutated in place so a recipient is never contacted twice per event.
|
||||
|
||||
Best-effort: any failure is logged and never propagates to the caller.
|
||||
"""
|
||||
exclude = set(exclude_user_ids or [])
|
||||
notified = already_notified if already_notified is not None else set()
|
||||
emailed = already_emailed if already_emailed is not None else set()
|
||||
|
||||
try:
|
||||
from app.models.project_recipient import ProjectNotificationRecipient
|
||||
from app.models.facility import Facility
|
||||
|
||||
# Resolve the facility this event occurred at
|
||||
fid = facility_id
|
||||
if fid is None and issue_id:
|
||||
from app.models.issue import Issue
|
||||
issue = db.session.get(Issue, issue_id)
|
||||
if issue:
|
||||
fac = issue.resolved_facility
|
||||
fid = fac.id if fac else None
|
||||
if fid is None and inspection_id:
|
||||
from app.models.inspection import Inspection
|
||||
insp = db.session.get(Inspection, inspection_id)
|
||||
fid = insp.facility_id if insp else None
|
||||
if fid is None:
|
||||
return # no facility context — contract cannot be determined
|
||||
|
||||
facility = db.session.get(Facility, fid)
|
||||
if not facility or not facility.project_id:
|
||||
return # facility unknown or not linked to a contract
|
||||
|
||||
recipients = ProjectNotificationRecipient.query.filter_by(
|
||||
project_id=facility.project_id
|
||||
).all()
|
||||
|
||||
sent = 0
|
||||
for r in recipients:
|
||||
if event_type not in r.get_events():
|
||||
continue
|
||||
|
||||
if r.user_id:
|
||||
if r.user_id in exclude or r.user_id in notified:
|
||||
continue
|
||||
user = r.user
|
||||
if not user or not user.active:
|
||||
continue
|
||||
notify(
|
||||
recipient = user,
|
||||
title = title,
|
||||
body = body,
|
||||
link = link,
|
||||
issue_id = issue_id,
|
||||
inspection_id = inspection_id,
|
||||
event_type = event_type,
|
||||
send_email = True,
|
||||
respect_preferences = False, # contract config is the authority
|
||||
)
|
||||
notified.add(user.id)
|
||||
sent += 1
|
||||
logger.info(
|
||||
'CONTRACT NOTIFY | project_id=%s | event=%s | user=%s',
|
||||
facility.project_id, event_type, user.username,
|
||||
)
|
||||
elif r.email:
|
||||
key = r.email.strip().lower()
|
||||
if not key or key in emailed:
|
||||
continue
|
||||
_send_custom_email(r.email.strip(), title, body, link)
|
||||
emailed.add(key)
|
||||
sent += 1
|
||||
logger.info(
|
||||
'CONTRACT NOTIFY | project_id=%s | event=%s | email=%s',
|
||||
facility.project_id, event_type, key,
|
||||
)
|
||||
|
||||
if sent:
|
||||
logger.info(
|
||||
'CONTRACT NOTIFY DONE | project_id=%s | event=%s | sent=%s',
|
||||
facility.project_id, event_type, sent,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
'CONTRACT NOTIFY FAILED | event=%s | facility_id=%s | error=%s',
|
||||
event_type, facility_id, exc,
|
||||
)
|
||||
|
||||
|
||||
def _send_custom_email(to_email: str, title: str, body: str, link: str = None):
|
||||
"""Send a plain email to a custom (non-user) address. Best-effort."""
|
||||
try:
|
||||
|
||||
+5
-4
@@ -319,10 +319,11 @@ def send_score_alerts(threshold=SCORE_DROP_THRESHOLD):
|
||||
link = f'/reports/facility/{fid}/scorecard'
|
||||
|
||||
notify_by_matrix(
|
||||
event_type = 'score_alert',
|
||||
title = title,
|
||||
body = body,
|
||||
link = link,
|
||||
event_type = 'score_alert',
|
||||
title = title,
|
||||
body = body,
|
||||
link = link,
|
||||
facility_id = fid,
|
||||
)
|
||||
total_sent += 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user