04/06 implement creating ticket on behalf of another user for IT staff
This commit is contained in:
+43
-1
@@ -145,4 +145,46 @@ def on_join_ticket(data):
|
||||
def on_leave_ticket(data):
|
||||
if current_user.is_authenticated:
|
||||
ticket_id = data.get('ticket_id')
|
||||
leave_room(f'ticket_{ticket_id}')
|
||||
leave_room(f'ticket_{ticket_id}')
|
||||
|
||||
# ─── User Search API (IT Only) ────────────────────────────────────────────────
|
||||
|
||||
@api_bp.route('/users/search')
|
||||
@login_required
|
||||
def search_users():
|
||||
"""Return active employees matching a search query.
|
||||
|
||||
Used by the 'create on behalf' form to populate the employee selector.
|
||||
Restricted to IT staff to prevent employees from enumerating all users.
|
||||
|
||||
Query params
|
||||
------------
|
||||
q : str – search term matched against full_name, email, department
|
||||
limit : int – max results (default 20, max 50)
|
||||
"""
|
||||
if not current_user.is_it_staff:
|
||||
return jsonify({'error': 'Forbidden'}), 403
|
||||
|
||||
q = request.args.get('q', '').strip()
|
||||
limit = min(request.args.get('limit', 20, type=int), 50)
|
||||
|
||||
from app.models import User, UserRole
|
||||
query = User.query.filter(User.is_active == True)
|
||||
if q:
|
||||
query = query.filter(
|
||||
User.full_name.ilike(f'%{q}%') |
|
||||
User.email.ilike(f'%{q}%') |
|
||||
User.department.ilike(f'%{q}%')
|
||||
)
|
||||
users = query.order_by(User.full_name).limit(limit).all()
|
||||
|
||||
return jsonify({'users': [
|
||||
{
|
||||
'id' : u.id,
|
||||
'full_name' : u.full_name,
|
||||
'email' : u.email,
|
||||
'department': u.department or '',
|
||||
'role' : u.role,
|
||||
}
|
||||
for u in users
|
||||
]})
|
||||
|
||||
@@ -188,6 +188,109 @@ def create_ticket():
|
||||
categories=_categories(), priorities=_priorities())
|
||||
|
||||
|
||||
# ─── Create Ticket on Behalf of Employee (IT Staff Only) ─────────────────────
|
||||
|
||||
@tickets_bp.route('/tickets/behalf', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def create_ticket_behalf():
|
||||
"""Allow IT staff to create a ticket on behalf of an employee.
|
||||
|
||||
Use case: an employee calls or emails to report an issue and cannot or
|
||||
does not create a ticket themselves. IT staff completes the form,
|
||||
selecting the employee from a searchable dropdown.
|
||||
|
||||
Data model
|
||||
----------
|
||||
ticket.created_by_id = employee's user ID (ticket shows as theirs)
|
||||
ticket.created_by_staff_id = IT staff's user ID (audit trail)
|
||||
|
||||
The employee sees this ticket in their own dashboard and receives the
|
||||
same new-ticket confirmation notification they would if self-submitted.
|
||||
"""
|
||||
if not current_user.is_it_staff:
|
||||
abort(403)
|
||||
|
||||
employees = User.query.filter(
|
||||
User.is_active == True,
|
||||
).order_by(User.full_name).all()
|
||||
|
||||
if request.method == 'POST':
|
||||
employee_id = request.form.get('employee_id', type=int)
|
||||
title = request.form.get('title', '').strip()
|
||||
description = request.form.get('description', '').strip()
|
||||
category = request.form.get('category', TicketCategory.OTHER)
|
||||
priority = request.form.get('priority', TicketPriority.MEDIUM)
|
||||
location = request.form.get('location', '').strip()
|
||||
asset_tag = request.form.get('asset_tag', '').strip()
|
||||
|
||||
# Validate employee selection
|
||||
employee = db.session.get(User, employee_id) if employee_id else None
|
||||
if not employee or not employee.is_active:
|
||||
flash('Please select a valid active employee.', 'danger')
|
||||
return render_template('tickets/create_behalf.html',
|
||||
employees=employees,
|
||||
categories=_categories(),
|
||||
priorities=_priorities())
|
||||
|
||||
if not title or not description:
|
||||
flash('Title and description are required.', 'danger')
|
||||
return render_template('tickets/create_behalf.html',
|
||||
employees=employees,
|
||||
categories=_categories(),
|
||||
priorities=_priorities(),
|
||||
selected_employee_id=employee_id)
|
||||
|
||||
ticket = Ticket(
|
||||
title = title,
|
||||
description = description,
|
||||
category = category,
|
||||
priority = priority,
|
||||
location = location,
|
||||
asset_tag = asset_tag,
|
||||
created_by_id = employee.id, # ticket belongs to the employee
|
||||
created_by_staff_id = current_user.id, # IT staff who filed it
|
||||
status = TicketStatus.OPEN,
|
||||
)
|
||||
ticket.ticket_number = ticket.generate_ticket_number()
|
||||
db.session.add(ticket)
|
||||
db.session.flush() # get ticket.id before attachments
|
||||
|
||||
# Handle file uploads
|
||||
for f in request.files.getlist('attachments'):
|
||||
if f and f.filename:
|
||||
file_error = validate_file(f, ALLOWED_EXT)
|
||||
if file_error:
|
||||
logger.warning(
|
||||
f'[BEHALF UPLOAD REJECTED] {file_error} '
|
||||
f'filename="{f.filename}" staff_id={current_user.id}'
|
||||
)
|
||||
continue
|
||||
save_attachment(f, ticket_id=ticket.id, uploader_id=current_user.id)
|
||||
|
||||
log_action(
|
||||
current_user.id, 'ticket_create_behalf', 'ticket', ticket.id,
|
||||
f'ticket_number={ticket.ticket_number} on_behalf_of=user_id:{employee.id} '
|
||||
f'({employee.full_name}) priority={priority} category={category}'
|
||||
)
|
||||
db.session.commit()
|
||||
logger.info(
|
||||
f'[TICKET CREATE BEHALF] ticket_id={ticket.id} '
|
||||
f'number={ticket.ticket_number} '
|
||||
f'employee_id={employee.id} staff_id={current_user.id}'
|
||||
)
|
||||
notify_new_ticket(ticket)
|
||||
flash(
|
||||
f'Ticket {ticket.ticket_number} created on behalf of {employee.full_name}.',
|
||||
'success'
|
||||
)
|
||||
return redirect(url_for('tickets.ticket_detail', ticket_id=ticket.id))
|
||||
|
||||
return render_template('tickets/create_behalf.html',
|
||||
employees=employees,
|
||||
categories=_categories(),
|
||||
priorities=_priorities())
|
||||
|
||||
|
||||
# ─── Ticket List ──────────────────────────────────────────────────────────────
|
||||
|
||||
@tickets_bp.route('/tickets')
|
||||
|
||||
Reference in New Issue
Block a user