Jul 2nd - Optimized code 6

This commit is contained in:
2026-07-02 17:25:19 -04:00
parent d2ea7174ba
commit 3c31c7a491
5 changed files with 107 additions and 44 deletions
+6
View File
@@ -113,6 +113,12 @@ class Ticket(db.Model):
closed_at = db.Column(db.DateTime)
due_date = db.Column(db.DateTime)
# Set once an SLA breach notification has been sent for this ticket, so
# the 30-minute scheduler job doesn't re-notify every run. Cleared when
# the ticket resolves/closes/re-opens (see sla_service.clear_sla_notification)
# so a re-opened ticket gets a fresh alert if it breaches again.
sla_breach_notified = db.Column(db.Boolean, nullable=False, default=False, server_default='0')
ai_generated = db.Column(db.Boolean, default=False)
internal_notes = db.Column(db.Text)
resolution_notes = db.Column(db.Text)
+17 -6
View File
@@ -617,15 +617,26 @@ def kb_delete_attachment(article_id, att_id):
att = KBAttachment.query.filter_by(id=att_id, article_id=article_id).first_or_404()
upload_dir = current_app.config['UPLOAD_FOLDER']
filepath = os.path.join(upload_dir, att.stored_name)
if os.path.exists(filepath):
os.remove(filepath)
log_action(current_user.id, 'kb_attachment_delete', 'kb_attachment', att.id,
f'article_id={article_id} filename={att.filename}')
logger.info(f'[KB ATTACHMENT DELETE] att_id={att.id} article_id={article_id} by user_id={current_user.id}')
att_id_val, filename = att.id, att.filename
# DB row is the source of truth — delete and commit it first, then remove
# the physical file. If the process dies mid-operation this leaves at
# worst an orphan file on disk (harmless, cleanable later) rather than a
# DB row pointing at a file that's already gone (a broken download link).
log_action(current_user.id, 'kb_attachment_delete', 'kb_attachment', att_id_val,
f'article_id={article_id} filename={filename}')
db.session.delete(att)
db.session.commit()
logger.info(f'[KB ATTACHMENT DELETE] att_id={att_id_val} article_id={article_id} by user_id={current_user.id}')
if os.path.exists(filepath):
try:
os.remove(filepath)
except OSError as exc:
logger.warning(f'[KB ATTACHMENT DELETE] Could not remove file {filepath}: {exc}')
# Return JSON so the edit page can remove the row without a full reload
return jsonify({'ok': True, 'att_id': att.id})
return jsonify({'ok': True, 'att_id': att_id_val})
+19 -38
View File
@@ -16,11 +16,11 @@ Responsibilities
Notification suppression
------------------------
A dedicated SystemSetting key sla_notified_tickets stores a
comma-separated list of ticket IDs that have already received a breach
notification. When a ticket is resolved or closed the ID is removed
from the list so the suppression does not persist across re-opens
(edge case: ticket re-opened after resolution unlikely but handled).
Ticket.sla_breach_notified is a per-ticket boolean flag, set once a breach
notification has been sent so the 30-minute scheduler run doesn't re-notify
every time. When a ticket is resolved or closed the flag is cleared so
the suppression does not persist across re-opens (edge case: ticket
re-opened after resolution unlikely but handled).
Design notes
------------
@@ -137,10 +137,10 @@ def check_sla_breaches(app):
"""Scheduled job: find overdue tickets and notify responsible parties.
Safe to call repeatedly already-notified tickets are suppressed via
the sla_notified_tickets SystemSetting key. The suppression list is
cleared for a ticket when it transitions to resolved/closed (handled by
the update_ticket route clearing it on status change) or when the ticket
is re-opened, ensuring fresh notifications if the issue resurfaces.
Ticket.sla_breach_notified. The flag is cleared for a ticket when it
transitions to resolved/closed (handled by the update_ticket route) or
when the ticket is re-opened, ensuring fresh notifications if the issue
resurfaces.
"""
with app.app_context():
from app.services.license_service import feature_enabled
@@ -155,24 +155,18 @@ def check_sla_breaches(app):
def _run_sla_check(app):
from app import db
from app.models import (
Ticket, TicketStatus, User, UserRole,
NotificationType, SystemSetting,
)
from app.models import Ticket, TicketStatus, User, UserRole, NotificationType
from app.services.notification_service import create_notification, send_email
from flask import render_template_string
now = datetime.utcnow()
# ── Load suppression list ──────────────────────────────────────────────────
raw = SystemSetting.get('sla_notified_tickets', '')
already_notified = set(int(x) for x in raw.split(',') if x.strip().isdigit())
# ── Query overdue open tickets ─────────────────────────────────────────────
# ── Query overdue, not-yet-notified open tickets ───────────────────────────
overdue = Ticket.query.filter(
Ticket.status.in_([TicketStatus.OPEN, TicketStatus.IN_PROGRESS]),
Ticket.due_date.isnot(None),
Ticket.due_date < now,
Ticket.sla_breach_notified.is_(False),
).all()
if not overdue:
@@ -185,9 +179,6 @@ def _run_sla_check(app):
newly_notified = []
for ticket in overdue:
if ticket.id in already_notified:
continue # already sent — skip
ticket_url = f'{base_url}/tickets/{ticket.id}'
overdue_mins = int((now - ticket.due_date).total_seconds() / 60)
overdue_label = (
@@ -263,18 +254,11 @@ def _run_sla_check(app):
if it_dept_email:
send_email(subject, [it_dept_email], html)
ticket.sla_breach_notified = True
newly_notified.append(ticket.id)
db.session.commit()
# ── Update suppression list ────────────────────────────────────────────────
if newly_notified:
updated = already_notified | set(newly_notified)
SystemSetting.set(
'sla_notified_tickets',
','.join(str(i) for i in sorted(updated)),
'Comma-separated ticket IDs that have received SLA breach notifications',
)
db.session.commit()
logger.info(
f'[SLA] Notified {len(newly_notified)} breach(es): '
f'{[str(i) for i in newly_notified]}'
@@ -282,17 +266,14 @@ def _run_sla_check(app):
def clear_sla_notification(ticket_id: int):
"""Remove a ticket from the SLA suppression list.
"""Clear the SLA breach-notified flag for a ticket.
Call this when a ticket is resolved, closed, or re-opened so that
subsequent breaches (if the ticket re-opens) trigger fresh alerts.
Callers are responsible for committing after calling this function.
"""
from app.models import SystemSetting
raw = SystemSetting.get('sla_notified_tickets', '')
current = set(int(x) for x in raw.split(',') if x.strip().isdigit())
current.discard(ticket_id)
SystemSetting.set(
'sla_notified_tickets',
','.join(str(i) for i in sorted(current)),
)
from app import db
from app.models import Ticket
ticket = db.session.get(Ticket, ticket_id)
if ticket:
ticket.sla_breach_notified = False