05/22 Enhance codes and fix bugs 3

This commit is contained in:
2026-05-22 15:19:34 -04:00
parent 12b5fd9ce0
commit 5c9a124a71
11 changed files with 665 additions and 36 deletions
+77 -2
View File
@@ -10,11 +10,12 @@ from app import db
from app.models import (Ticket, Comment, Attachment, Notification,
TicketStatus, TicketPriority, TicketCategory,
User, UserRole, KnowledgeBase, TicketLink, CannedResponse,
KBFeedback, TicketTemplate, TicketSatisfaction)
KBFeedback, TicketTemplate, TicketSatisfaction,
TicketWatcher, TimeEntry)
from app.services.notification_service import (
notify_new_ticket, notify_status_change,
notify_comment_added, notify_assignment,
send_satisfaction_survey,
send_satisfaction_survey, notify_watchers,
)
from app.services.log_service import log_action, log_ticket_history
from app.services.sla_service import clear_sla_notification
@@ -446,12 +447,21 @@ def ticket_detail(ticket_id):
CannedResponse.category, CannedResponse.title
).all()
is_watching = TicketWatcher.query.filter_by(
ticket_id=ticket_id, user_id=current_user.id
).first() is not None
total_minutes = sum(e.minutes for e in ticket.time_entries.all())
return render_template('tickets/detail.html',
ticket=ticket, comments=comments,
it_staff=it_staff, history=history,
statuses=_statuses(), priorities=_priorities(),
linked_tickets=linked_tickets,
canned_responses=canned_responses,
now=datetime.utcnow(),
is_watching=is_watching,
total_minutes=total_minutes,
time_entries=ticket.time_entries.order_by(TimeEntry.logged_at.desc()).limit(10).all(),
)
@@ -538,6 +548,14 @@ def update_ticket(ticket_id):
if new_status == TicketStatus.RESOLVED:
send_satisfaction_survey(ticket)
if changes:
notify_watchers(
ticket,
event_title = f'Ticket {ticket.ticket_number} updated',
event_message = '; '.join(changes),
exclude_user_id = current_user.id,
)
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
from flask import jsonify
return jsonify({'ok': True, 'changes': changes,
@@ -976,6 +994,63 @@ def ticket_survey(token):
quick_rating=quick_rating)
# ─── Ticket Watchers ─────────────────────────────────────────────────────────
@tickets_bp.route('/tickets/<int:ticket_id>/watch', methods=['POST'])
@login_required
def watch_ticket(ticket_id):
ticket = db.session.get(Ticket, ticket_id) or abort(404)
if not current_user.is_it_staff and ticket.created_by_id != current_user.id:
abort(403)
existing = TicketWatcher.query.filter_by(ticket_id=ticket_id, user_id=current_user.id).first()
if not existing:
db.session.add(TicketWatcher(ticket_id=ticket_id, user_id=current_user.id))
db.session.commit()
from flask import jsonify
return jsonify({'watching': True})
@tickets_bp.route('/tickets/<int:ticket_id>/unwatch', methods=['POST'])
@login_required
def unwatch_ticket(ticket_id):
ticket = db.session.get(Ticket, ticket_id) or abort(404)
if not current_user.is_it_staff and ticket.created_by_id != current_user.id:
abort(403)
TicketWatcher.query.filter_by(ticket_id=ticket_id, user_id=current_user.id).delete()
db.session.commit()
from flask import jsonify
return jsonify({'watching': False})
# ─── Time Tracking ────────────────────────────────────────────────────────────
@tickets_bp.route('/tickets/<int:ticket_id>/log-time', methods=['POST'])
@login_required
def log_time(ticket_id):
if not current_user.is_it_staff:
abort(403)
ticket = db.session.get(Ticket, ticket_id) or abort(404)
try:
minutes = int(request.form.get('minutes', 0))
except (ValueError, TypeError):
minutes = 0
if minutes <= 0:
flash('Please enter a valid number of minutes.', 'warning')
return redirect(url_for('tickets.ticket_detail', ticket_id=ticket_id))
note = request.form.get('note', '').strip()[:200]
entry = TimeEntry(ticket_id=ticket_id, user_id=current_user.id, minutes=minutes, note=note)
db.session.add(entry)
log_action(current_user.id, 'time_log', 'ticket', ticket_id,
f'minutes={minutes} note={note!r}')
db.session.commit()
from flask import jsonify
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
total = sum(e.minutes for e in ticket.time_entries.all())
return jsonify({'ok': True, 'minutes': minutes, 'total_minutes': total})
flash(f'{minutes} minutes logged.', 'success')
return redirect(url_for('tickets.ticket_detail', ticket_id=ticket_id))
# ─── Helpers ─────────────────────────────────────────────────────────────────
def _sla_due_date(priority: str) -> 'datetime':