66 lines
2.6 KiB
Python
66 lines
2.6 KiB
Python
"""
|
|
app/models/tenant_settings.py
|
|
------------------------------
|
|
Per-tenant branding / self-service settings (MT-7).
|
|
|
|
One row per tenant DB. Created on first save; reads fall back to defaults
|
|
when no row exists so tenant-zero needs zero data migration.
|
|
"""
|
|
|
|
import sqlalchemy.exc
|
|
|
|
from app import db
|
|
from app.utils.time_utils import now_eastern
|
|
|
|
|
|
class TenantSettings(db.Model):
|
|
__tablename__ = 'tenant_settings'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
company_name = db.Column(db.String(150), nullable=True)
|
|
logo_url = db.Column(db.String(500), nullable=True)
|
|
primary_color = db.Column(db.String(7), nullable=False, default='#1a56db')
|
|
accent_color = db.Column(db.String(7), nullable=False, default='#16a34a')
|
|
support_email = db.Column(db.String(255), nullable=True)
|
|
updated_at = db.Column(db.DateTime, nullable=False, default=now_eastern)
|
|
updated_by = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'),
|
|
nullable=True)
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────────
|
|
DEFAULTS = {
|
|
'company_name': 'Janitorial QC',
|
|
'logo_url': None,
|
|
'primary_color': '#1a56db',
|
|
'accent_color': '#16a34a',
|
|
'support_email': None,
|
|
}
|
|
|
|
@classmethod
|
|
def get_or_default(cls):
|
|
"""Return the settings row, a default-filled transient instance, or None.
|
|
|
|
Returns None when the tenant_settings table does not yet exist (i.e.
|
|
phase33 migration has not been run for this tenant DB). The caller
|
|
must handle None — routes redirect to the dashboard with a migration
|
|
prompt; the context processor silently returns no branding.
|
|
"""
|
|
try:
|
|
row = cls.query.first()
|
|
except (sqlalchemy.exc.ProgrammingError, sqlalchemy.exc.OperationalError):
|
|
# Table 'tenant_settings' doesn't exist — phase33 migration not yet applied.
|
|
# Any other DB error (connection failure, permission error) is re-raised.
|
|
return None
|
|
if row is not None:
|
|
return row
|
|
# Return a transient default so templates always get a real object.
|
|
obj = cls()
|
|
for k, v in cls.DEFAULTS.items():
|
|
setattr(obj, k, v)
|
|
return obj
|
|
|
|
@property
|
|
def display_name(self):
|
|
return self.company_name or self.DEFAULTS['company_name']
|
|
|
|
def __repr__(self):
|
|
return f'<TenantSettings company={self.company_name}>' |