Mar 04 2026: Implement some operational features: scheduled reports, issue resolution verification, etc - Phase 6
This commit is contained in:
@@ -64,8 +64,17 @@ class Inspection(db.Model):
|
||||
form_data = db.Column(db.JSON) # filled form field responses {field_id: value}
|
||||
completed_at = db.Column(db.DateTime)
|
||||
|
||||
results = db.relationship('InspectionResult', backref='inspection', lazy='dynamic', cascade='all, delete-orphan')
|
||||
issues = db.relationship('Issue', backref='inspection', lazy='dynamic', cascade='all, delete-orphan')
|
||||
# ── Re-inspection / follow-up workflow ────────────────────────────────
|
||||
parent_inspection_id = db.Column(
|
||||
db.Integer, db.ForeignKey('inspections.id', ondelete='SET NULL'), nullable=True
|
||||
)
|
||||
follow_up_required = db.Column(db.Boolean, nullable=False, default=False)
|
||||
follow_up_note = db.Column(db.Text, nullable=True)
|
||||
|
||||
results = db.relationship('InspectionResult', backref='inspection', lazy='dynamic', cascade='all, delete-orphan')
|
||||
issues = db.relationship('Issue', backref='inspection', lazy='dynamic', cascade='all, delete-orphan')
|
||||
follow_ups = db.relationship('Inspection', backref=db.backref('parent', remote_side='Inspection.id'),
|
||||
lazy='dynamic', foreign_keys='Inspection.parent_inspection_id')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Inspection {self.id} - {self.inspection_date}>'
|
||||
|
||||
+8
-2
@@ -51,19 +51,25 @@ class Issue(db.Model):
|
||||
severity = db.Column(db.Enum('low', 'medium', 'high', 'critical'), nullable=False)
|
||||
description = db.Column(db.Text, nullable=False)
|
||||
photo_path = db.Column(db.String(255))
|
||||
status = db.Column(db.Enum('open', 'in_progress', 'resolved'), default='open')
|
||||
status = db.Column(db.Enum('open', 'in_progress', 'resolved', 'pending_verification'), default='open')
|
||||
assigned_to = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
reported_at = db.Column(db.DateTime, default=now_eastern)
|
||||
resolved_at = db.Column(db.DateTime)
|
||||
result_notes = db.Column(db.Text)
|
||||
result_photos = db.Column(db.JSON) # list of relative paths e.g. ["uploads/issue_photos/abc.jpg"]
|
||||
|
||||
# ── Resolution verification ──────────────────────────────────────────
|
||||
verified_by = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
|
||||
verified_at = db.Column(db.DateTime, nullable=True)
|
||||
verification_note = db.Column(db.Text, nullable=True)
|
||||
|
||||
# Tracks which SLA alert level has already been notified so cron runs
|
||||
# don't fire duplicate notifications. Values: None / 'at_risk' / 'breached'
|
||||
sla_notified = db.Column(db.String(10), nullable=True, default=None)
|
||||
|
||||
# Relationships
|
||||
assigned_user = db.relationship('User', foreign_keys=[assigned_to], backref='assigned_issues')
|
||||
assigned_user = db.relationship('User', foreign_keys=[assigned_to], backref='assigned_issues')
|
||||
verifier = db.relationship('User', foreign_keys=[verified_by], backref='verified_issues')
|
||||
comments = db.relationship('IssueComment', backref='issue', lazy='dynamic',
|
||||
order_by='IssueComment.created_at',
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
app/models/scheduled_report.py
|
||||
-------------------------------
|
||||
Stores the configuration for automated scheduled report emails.
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app.utils.time_utils import now_eastern
|
||||
|
||||
|
||||
class ScheduledReport(db.Model):
|
||||
"""Configuration record for a recurring emailed report.
|
||||
|
||||
report_type options:
|
||||
summary — overall KPI digest (inspections + issues)
|
||||
facility — single-facility scorecard
|
||||
issues — open/in-progress issues list
|
||||
|
||||
frequency options: daily | weekly | monthly
|
||||
|
||||
recipients: JSON list of email address strings, e.g.
|
||||
["manager@acme.com", "client@acme.com"]
|
||||
|
||||
include_pdf / include_csv: attach respective exports to the email.
|
||||
"""
|
||||
__tablename__ = 'scheduled_reports'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255), nullable=False)
|
||||
report_type = db.Column(
|
||||
db.Enum('summary', 'facility', 'issues'),
|
||||
nullable=False, default='summary'
|
||||
)
|
||||
frequency = db.Column(
|
||||
db.Enum('daily', 'weekly', 'monthly'),
|
||||
nullable=False
|
||||
)
|
||||
facility_id = db.Column(
|
||||
db.Integer, db.ForeignKey('facilities.id', ondelete='SET NULL'),
|
||||
nullable=True
|
||||
)
|
||||
recipients = db.Column(db.JSON, nullable=False, default=list)
|
||||
include_pdf = db.Column(db.Boolean, nullable=False, default=False)
|
||||
include_csv = db.Column(db.Boolean, nullable=False, default=False)
|
||||
active = db.Column(db.Boolean, nullable=False, default=True)
|
||||
created_by = db.Column(
|
||||
db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'),
|
||||
nullable=True
|
||||
)
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=now_eastern)
|
||||
last_sent_at = db.Column(db.DateTime, nullable=True)
|
||||
next_send_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
facility = db.relationship('Facility', foreign_keys=[facility_id])
|
||||
creator = db.relationship('User', foreign_keys=[created_by])
|
||||
|
||||
def recipient_list(self):
|
||||
"""Return recipients as a Python list (safe even if stored as string)."""
|
||||
if isinstance(self.recipients, list):
|
||||
return self.recipients
|
||||
import json
|
||||
try:
|
||||
return json.loads(self.recipients)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ScheduledReport {self.id} {self.name!r} {self.frequency}>'
|
||||
Reference in New Issue
Block a user