124 lines
5.0 KiB
Python
124 lines
5.0 KiB
Python
from app import db
|
|
from app.utils.time_utils import now_eastern
|
|
|
|
|
|
class SupportChatSession(db.Model):
|
|
__tablename__ = 'support_chat_sessions'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
customer_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
|
|
title = db.Column(db.String(200), nullable=True)
|
|
created_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
|
last_msg_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
|
|
|
customer = db.relationship('User', foreign_keys=[customer_id], backref='chat_sessions')
|
|
messages = db.relationship(
|
|
'SupportChatMessage', back_populates='session',
|
|
cascade='all, delete-orphan',
|
|
order_by='SupportChatMessage.created_at',
|
|
)
|
|
|
|
@property
|
|
def message_count(self):
|
|
"""Number of turns in this session.
|
|
|
|
`messages` is a plain list relationship here (ST's is lazy='dynamic'),
|
|
so this is len() rather than .count(). It is already loaded whenever the
|
|
session is, so this costs no extra query.
|
|
"""
|
|
return len(self.messages)
|
|
|
|
@property
|
|
def preview(self):
|
|
"""First user message, for list views.
|
|
|
|
Scans the loaded list instead of ST's .filter_by(role='user').first(),
|
|
for the same reason. `messages` is ordered by created_at, so the first
|
|
match is the opening question.
|
|
"""
|
|
for m in self.messages:
|
|
if m.role == 'user':
|
|
return m.content
|
|
return '(no messages)'
|
|
|
|
def __repr__(self):
|
|
return f'<SupportChatSession {self.id}>'
|
|
|
|
|
|
class SupportChatMessage(db.Model):
|
|
__tablename__ = 'support_chat_messages'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
session_id = db.Column(db.Integer,
|
|
db.ForeignKey('support_chat_sessions.id', ondelete='CASCADE'),
|
|
nullable=False)
|
|
role = db.Column(db.String(20), nullable=False) # 'user' | 'assistant'
|
|
content = db.Column(db.Text, nullable=False)
|
|
created_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
|
|
|
session = db.relationship('SupportChatSession', back_populates='messages')
|
|
|
|
def __repr__(self):
|
|
return f'<SupportChatMessage {self.id} [{self.role}]>'
|
|
|
|
|
|
class SupportKnowledge(db.Model):
|
|
__tablename__ = 'support_knowledge'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
title = db.Column(db.String(200), nullable=False)
|
|
body = db.Column(db.Text, nullable=False)
|
|
active = db.Column(db.Boolean, default=True, nullable=False)
|
|
# MT-19 — admin-controlled ordering. Entries are injected into the support
|
|
# chat's system prompt in this order, so a low sort_order is how an admin
|
|
# promotes the guidance the assistant should reach for first. Ties break on
|
|
# id, keeping the order stable.
|
|
sort_order = db.Column(db.Integer, nullable=False, default=0)
|
|
created_by = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
|
|
created_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
|
updated_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
|
|
|
creator = db.relationship('User', foreign_keys=[created_by])
|
|
|
|
def __repr__(self):
|
|
return f'<SupportKnowledge {self.id} "{self.title[:30]}">'
|
|
|
|
|
|
class SupportTicket(db.Model):
|
|
__tablename__ = 'support_tickets'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
customer_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
|
|
facility_id = db.Column(db.Integer, db.ForeignKey('facilities.id', ondelete='SET NULL'), nullable=True)
|
|
subject = db.Column(db.String(200), nullable=False)
|
|
body = db.Column(db.Text, nullable=False)
|
|
status = db.Column(db.String(20), nullable=False, default='open') # open / answered / closed
|
|
created_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
|
|
|
customer = db.relationship('User', foreign_keys=[customer_id], backref='support_tickets')
|
|
facility = db.relationship('Facility', foreign_keys=[facility_id], backref='support_tickets')
|
|
replies = db.relationship(
|
|
'SupportTicketReply', backref='ticket',
|
|
cascade='all, delete-orphan',
|
|
order_by='SupportTicketReply.created_at',
|
|
lazy='dynamic',
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f'<SupportTicket {self.id} [{self.status}]>'
|
|
|
|
|
|
class SupportTicketReply(db.Model):
|
|
__tablename__ = 'support_ticket_replies'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
ticket_id = db.Column(db.Integer, db.ForeignKey('support_tickets.id', ondelete='CASCADE'), nullable=False)
|
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
|
|
body = db.Column(db.Text, nullable=False)
|
|
created_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
|
|
|
author = db.relationship('User', foreign_keys=[user_id])
|
|
|
|
def __repr__(self):
|
|
return f'<SupportTicketReply {self.id} ticket={self.ticket_id}>'
|