Jul 7 - Implement additional recipient per contract
This commit is contained in:
@@ -236,6 +236,150 @@ def remove_assignment(assignment_id):
|
||||
return redirect(url_for('projects.view', project_id=project_id))
|
||||
|
||||
|
||||
# ── Notification Recipients — per-contract (phase37) ─────────────────────────
|
||||
|
||||
@bp.route('/<int:project_id>/recipients')
|
||||
@login_required
|
||||
@supervisor_required
|
||||
def recipients(project_id):
|
||||
"""Manage additional notification recipients for one contract.
|
||||
|
||||
Recipients are either staff users (in-app + email) or external email
|
||||
addresses (email only), each subscribed to a chosen set of matrix events.
|
||||
Dispatched by notify_by_matrix() for events in this contract's facilities.
|
||||
"""
|
||||
from app.models.project_recipient import ProjectNotificationRecipient
|
||||
from app.models.notification_matrix import MATRIX_EVENTS
|
||||
|
||||
project = db.session.get(Project, project_id)
|
||||
if project is None:
|
||||
abort(404)
|
||||
|
||||
recipient_rows = (
|
||||
ProjectNotificationRecipient.query
|
||||
.filter_by(project_id=project_id)
|
||||
.order_by(ProjectNotificationRecipient.created_at)
|
||||
.all()
|
||||
)
|
||||
staff_users = (
|
||||
User.query
|
||||
.filter(User.active == True, User.role != 'customer') # noqa: E712
|
||||
.order_by(User.username)
|
||||
.all()
|
||||
)
|
||||
return render_template(
|
||||
'projects/recipients.html',
|
||||
project=project,
|
||||
recipients=recipient_rows,
|
||||
staff_users=staff_users,
|
||||
matrix_events=MATRIX_EVENTS,
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/<int:project_id>/recipients/add', methods=['POST'])
|
||||
@login_required
|
||||
@supervisor_required
|
||||
def add_recipient(project_id):
|
||||
"""Add (or update, if the recipient already exists) a contract recipient."""
|
||||
import json as _json
|
||||
import re as _re
|
||||
from app.models.project_recipient import ProjectNotificationRecipient
|
||||
from app.models.notification_matrix import MATRIX_EVENTS
|
||||
|
||||
project = db.session.get(Project, project_id)
|
||||
if project is None:
|
||||
abort(404)
|
||||
|
||||
events = [e for e in request.form.getlist('events') if e in MATRIX_EVENTS]
|
||||
if not events:
|
||||
flash('Select at least one event to notify this recipient about.', 'warning')
|
||||
return redirect(url_for('projects.recipients', project_id=project_id))
|
||||
|
||||
recipient_type = request.form.get('recipient_type', 'staff')
|
||||
|
||||
if recipient_type == 'staff':
|
||||
user_id = request.form.get('user_id', type=int)
|
||||
user = db.session.get(User, user_id) if user_id else None
|
||||
if user is None or not user.active or user.role == 'customer':
|
||||
flash('Please choose a valid staff user.', 'warning')
|
||||
return redirect(url_for('projects.recipients', project_id=project_id))
|
||||
|
||||
row = ProjectNotificationRecipient.query.filter_by(
|
||||
project_id=project_id, user_id=user.id
|
||||
).first()
|
||||
if row:
|
||||
row.events = _json.dumps(events)
|
||||
action, verb = 'updated', 'UPDATE'
|
||||
else:
|
||||
row = ProjectNotificationRecipient(
|
||||
project_id=project_id, user_id=user.id,
|
||||
events=_json.dumps(events),
|
||||
)
|
||||
db.session.add(row)
|
||||
action, verb = 'added', 'CREATE'
|
||||
label = user.display_name
|
||||
else:
|
||||
email = (request.form.get('email') or '').strip()
|
||||
if not _re.match(r'^[^@\s]+@[^@\s]+\.[^@\s]+$', email):
|
||||
flash('Please enter a valid email address.', 'warning')
|
||||
return redirect(url_for('projects.recipients', project_id=project_id))
|
||||
|
||||
row = ProjectNotificationRecipient.query.filter(
|
||||
ProjectNotificationRecipient.project_id == project_id,
|
||||
ProjectNotificationRecipient.user_id.is_(None),
|
||||
db.func.lower(ProjectNotificationRecipient.email) == email.lower(),
|
||||
).first()
|
||||
if row:
|
||||
row.events = _json.dumps(events)
|
||||
action, verb = 'updated', 'UPDATE'
|
||||
else:
|
||||
row = ProjectNotificationRecipient(
|
||||
project_id=project_id, email=email,
|
||||
events=_json.dumps(events),
|
||||
)
|
||||
db.session.add(row)
|
||||
action, verb = 'added', 'CREATE'
|
||||
label = email
|
||||
|
||||
db.session.commit()
|
||||
logger.info('PROJECTS | recipient_%s | user=%s project_id=%s recipient=%s events=%s',
|
||||
action, current_user.username, project_id, label, events)
|
||||
log_action(ACTION_CREATE if verb == 'CREATE' else ACTION_UPDATE,
|
||||
'ProjectNotificationRecipient', row.id,
|
||||
f'{label} → {project.name}',
|
||||
f'events={",".join(events)}')
|
||||
flash(f'Recipient "{label}" {action} for contract "{project.name}".', 'success')
|
||||
return redirect(url_for('projects.recipients', project_id=project_id))
|
||||
|
||||
|
||||
@bp.route('/recipients/<int:recipient_id>/remove', methods=['POST'])
|
||||
@login_required
|
||||
@supervisor_required
|
||||
def remove_recipient(recipient_id):
|
||||
"""Remove a contract notification recipient."""
|
||||
from app.models.project_recipient import ProjectNotificationRecipient
|
||||
|
||||
row = db.session.get(ProjectNotificationRecipient, recipient_id)
|
||||
if row is None:
|
||||
abort(404)
|
||||
project = db.session.get(Project, row.project_id)
|
||||
if project is None:
|
||||
abort(404)
|
||||
|
||||
label = row.user.display_name if row.user else (row.email or f'id={row.id}')
|
||||
project_id_snap = row.project_id
|
||||
row_id_snap = row.id
|
||||
|
||||
db.session.delete(row)
|
||||
db.session.commit()
|
||||
logger.info('PROJECTS | recipient_remove | user=%s project_id=%s recipient=%s',
|
||||
current_user.username, project_id_snap, label)
|
||||
log_action(ACTION_DELETE, 'ProjectNotificationRecipient', row_id_snap,
|
||||
f'{label} → {project.name}')
|
||||
flash(f'Recipient "{label}" removed.', 'success')
|
||||
return redirect(url_for('projects.recipients', project_id=project_id_snap))
|
||||
|
||||
|
||||
# ── Bulk Import — Excel template download ─────────────────────────────────────
|
||||
|
||||
@bp.route('/import/template')
|
||||
|
||||
Reference in New Issue
Block a user