04/07 updated password reset, ticket templates, and satisfaction survey
This commit is contained in:
+123
-1
@@ -11,7 +11,8 @@ from werkzeug.utils import secure_filename
|
||||
import bleach
|
||||
from app import db, limiter
|
||||
from app.models import (User, Ticket, Comment, ActivityLog, KnowledgeBase,
|
||||
KBAttachment, UserRole, TicketStatus, CannedResponse)
|
||||
KBAttachment, UserRole, TicketStatus, CannedResponse,
|
||||
TicketTemplate, TicketSatisfaction)
|
||||
from app.services.log_service import log_action, log_ticket_history
|
||||
from app.services.validation_service import validate_password, validate_file
|
||||
|
||||
@@ -1033,6 +1034,127 @@ def bulk_ticket_action():
|
||||
))
|
||||
|
||||
|
||||
# ─── Ticket Templates ────────────────────────────────────────────────────────
|
||||
|
||||
@admin_bp.route('/ticket-templates')
|
||||
@login_required
|
||||
@it_required
|
||||
def ticket_templates():
|
||||
templates = TicketTemplate.query.order_by(
|
||||
TicketTemplate.sort_order, TicketTemplate.name
|
||||
).all()
|
||||
return render_template('admin/ticket_templates.html', templates=templates)
|
||||
|
||||
|
||||
@admin_bp.route('/ticket-templates/new', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@it_required
|
||||
def ticket_template_new():
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name', '').strip()
|
||||
category = request.form.get('category', 'other')
|
||||
priority = request.form.get('priority', 'medium')
|
||||
title_hint = request.form.get('title_hint', '').strip()
|
||||
description= request.form.get('description', '').strip()
|
||||
icon = request.form.get('icon', 'bi-file-text').strip()
|
||||
sort_order = request.form.get('sort_order', 0, type=int)
|
||||
is_active = bool(request.form.get('is_active'))
|
||||
if not name:
|
||||
flash('Template name is required.', 'danger')
|
||||
return render_template('admin/ticket_template_edit.html',
|
||||
t=None, categories=_categories_list(),
|
||||
priorities=_priorities_list())
|
||||
t = TicketTemplate(
|
||||
name=name, category=category, priority=priority,
|
||||
title_hint=title_hint, description=description,
|
||||
icon=icon, sort_order=sort_order, is_active=is_active,
|
||||
created_by=current_user.id,
|
||||
)
|
||||
db.session.add(t)
|
||||
db.session.flush()
|
||||
log_action(current_user.id, 'ticket_template_create', 'ticket_template', t.id,
|
||||
f'name={name}')
|
||||
db.session.commit()
|
||||
logger.info(f'[TEMPLATE CREATE] id={t.id} name={name} by user_id={current_user.id}')
|
||||
flash(f'Template "{name}" created.', 'success')
|
||||
return redirect(url_for('admin.ticket_templates'))
|
||||
return render_template('admin/ticket_template_edit.html',
|
||||
t=None, categories=_categories_list(),
|
||||
priorities=_priorities_list())
|
||||
|
||||
|
||||
@admin_bp.route('/ticket-templates/<int:tmpl_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@it_required
|
||||
def ticket_template_edit(tmpl_id):
|
||||
t = db.session.get(TicketTemplate, tmpl_id) or abort(404)
|
||||
if request.method == 'POST':
|
||||
t.name = request.form.get('name', t.name).strip()
|
||||
t.category = request.form.get('category', t.category)
|
||||
t.priority = request.form.get('priority', t.priority)
|
||||
t.title_hint = request.form.get('title_hint', '').strip()
|
||||
t.description = request.form.get('description', '').strip()
|
||||
t.icon = request.form.get('icon', 'bi-file-text').strip()
|
||||
t.sort_order = request.form.get('sort_order', 0, type=int)
|
||||
t.is_active = bool(request.form.get('is_active'))
|
||||
log_action(current_user.id, 'ticket_template_edit', 'ticket_template', t.id,
|
||||
f'name={t.name}')
|
||||
db.session.commit()
|
||||
logger.info(f'[TEMPLATE EDIT] id={t.id} by user_id={current_user.id}')
|
||||
flash(f'Template "{t.name}" updated.', 'success')
|
||||
return redirect(url_for('admin.ticket_templates'))
|
||||
return render_template('admin/ticket_template_edit.html',
|
||||
t=t, categories=_categories_list(),
|
||||
priorities=_priorities_list())
|
||||
|
||||
|
||||
@admin_bp.route('/ticket-templates/<int:tmpl_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
@it_required
|
||||
def ticket_template_delete(tmpl_id):
|
||||
t = db.session.get(TicketTemplate, tmpl_id) or abort(404)
|
||||
name = t.name
|
||||
log_action(current_user.id, 'ticket_template_delete', 'ticket_template', t.id,
|
||||
f'name={name}')
|
||||
logger.info(f'[TEMPLATE DELETE] id={t.id} name={name} by user_id={current_user.id}')
|
||||
db.session.delete(t)
|
||||
db.session.commit()
|
||||
flash(f'Template "{name}" deleted.', 'success')
|
||||
return redirect(url_for('admin.ticket_templates'))
|
||||
|
||||
|
||||
# ─── Satisfaction Survey Report ───────────────────────────────────────────────
|
||||
|
||||
@admin_bp.route('/satisfaction')
|
||||
@login_required
|
||||
@it_required
|
||||
def satisfaction_report():
|
||||
surveys = TicketSatisfaction.query.filter(
|
||||
TicketSatisfaction.rating.isnot(None)
|
||||
).order_by(TicketSatisfaction.submitted_at.desc()).all()
|
||||
total = len(surveys)
|
||||
avg_rating = round(sum(s.rating for s in surveys) / total, 2) if total else None
|
||||
dist = {i: sum(1 for s in surveys if s.rating == i) for i in range(1, 6)}
|
||||
pending = TicketSatisfaction.query.filter_by(rating=None).count()
|
||||
return render_template('admin/satisfaction_report.html',
|
||||
surveys=surveys, total=total,
|
||||
avg_rating=avg_rating, dist=dist, pending=pending)
|
||||
|
||||
|
||||
def _categories_list():
|
||||
from app.models import TicketCategory
|
||||
return [TicketCategory.HARDWARE, TicketCategory.SOFTWARE,
|
||||
TicketCategory.NETWORK, TicketCategory.ACCESS,
|
||||
TicketCategory.EMAIL, TicketCategory.PRINTER,
|
||||
TicketCategory.PHONE, TicketCategory.SECURITY, TicketCategory.OTHER]
|
||||
|
||||
|
||||
def _priorities_list():
|
||||
from app.models import TicketPriority
|
||||
return [TicketPriority.LOW, TicketPriority.MEDIUM,
|
||||
TicketPriority.HIGH, TicketPriority.CRITICAL]
|
||||
|
||||
|
||||
def _roles():
|
||||
return [UserRole.EMPLOYEE, UserRole.IT_STAFF, UserRole.ADMIN]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user