282 lines
9.5 KiB
Python
282 lines
9.5 KiB
Python
from flask import request, jsonify, current_app
|
|
from datetime import datetime
|
|
from app import db
|
|
from app.models import Ticket, Product, TicketReply
|
|
from . import api_bp
|
|
from .decorators import require_login, require_admin
|
|
from app.utils.logger import log_operation
|
|
|
|
@api_bp.route('/tickets', methods=['GET'])
|
|
@require_login
|
|
def get_tickets():
|
|
"""获取工单列表"""
|
|
try:
|
|
page = request.args.get('page', 1, type=int)
|
|
per_page = min(request.args.get('per_page', 20, type=int), 100)
|
|
status = request.args.get('status', type=int)
|
|
priority = request.args.get('priority', type=int)
|
|
product_id = request.args.get('product_id') # 修复:确保参数名与前端一致
|
|
keyword = request.args.get('keyword', '').strip()
|
|
|
|
query = Ticket.query
|
|
|
|
if status is not None:
|
|
query = query.filter_by(status=status)
|
|
if priority is not None:
|
|
query = query.filter_by(priority=priority)
|
|
if product_id:
|
|
query = query.filter_by(product_id=product_id)
|
|
if keyword:
|
|
query = query.filter(Ticket.title.like(f'%{keyword}%'))
|
|
|
|
query = query.order_by(Ticket.create_time.desc())
|
|
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
|
|
tickets = [ticket.to_dict() for ticket in pagination.items]
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'data': {
|
|
'tickets': tickets,
|
|
'pagination': {
|
|
'page': page,
|
|
'per_page': per_page,
|
|
'total': pagination.total,
|
|
'pages': pagination.pages
|
|
}
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
current_app.logger.error(f"获取工单列表失败: {str(e)}")
|
|
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|
|
|
|
@api_bp.route('/tickets', methods=['POST'])
|
|
def create_ticket():
|
|
"""创建工单(用户接口)"""
|
|
try:
|
|
data = request.get_json()
|
|
if not data:
|
|
return jsonify({'success': False, 'message': '请求数据为空'}), 400
|
|
|
|
title = data.get('title', '').strip()
|
|
product_id = data.get('product_id')
|
|
description = data.get('description', '').strip()
|
|
|
|
if not all([title, product_id, description]):
|
|
return jsonify({'success': False, 'message': '缺少必要参数'}), 400
|
|
|
|
# 验证产品存在
|
|
product = Product.query.filter_by(product_id=product_id).first()
|
|
if not product:
|
|
return jsonify({'success': False, 'message': '产品不存在'}), 404
|
|
|
|
ticket = Ticket(
|
|
title=title,
|
|
product_id=product_id,
|
|
software_version=data.get('software_version'),
|
|
machine_code=data.get('machine_code'),
|
|
license_key=data.get('license_key'),
|
|
description=description,
|
|
priority=data.get('priority', 1)
|
|
)
|
|
|
|
db.session.add(ticket)
|
|
db.session.commit()
|
|
|
|
# 记录操作日志
|
|
log_operation('CREATE_TICKET', 'TICKET', ticket.ticket_id, {
|
|
'title': ticket.title,
|
|
'product_id': ticket.product_id
|
|
})
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': '工单创建成功',
|
|
'data': ticket.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
current_app.logger.error(f"创建工单失败: {str(e)}")
|
|
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|
|
|
|
|
|
@api_bp.route('/tickets/batch/status', methods=['PUT'])
|
|
@require_login
|
|
def batch_update_ticket_status():
|
|
"""批量更新工单状态"""
|
|
try:
|
|
data = request.get_json()
|
|
if not data or 'ticket_ids' not in data or 'status' not in data:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '请求数据为空或缺少ticket_ids/status字段'
|
|
}), 400
|
|
|
|
ticket_ids = data['ticket_ids']
|
|
status = data['status']
|
|
remark = data.get('remark', '')
|
|
|
|
if not isinstance(ticket_ids, list) or len(ticket_ids) == 0:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'ticket_ids必须是非空列表'
|
|
}), 400
|
|
|
|
if status not in [0, 1, 2, 3]:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'status必须是0(待处理)、1(处理中)、2(已解决)或3(已关闭)'
|
|
}), 400
|
|
|
|
# 查找所有要更新的工单
|
|
tickets = Ticket.query.filter(Ticket.ticket_id.in_(ticket_ids)).all()
|
|
if len(tickets) != len(ticket_ids):
|
|
found_ids = [t.ticket_id for t in tickets]
|
|
missing_ids = [tid for tid in ticket_ids if tid not in found_ids]
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'以下工单不存在: {", ".join(map(str, missing_ids))}'
|
|
}), 404
|
|
|
|
# 批量更新工单状态
|
|
for ticket in tickets:
|
|
ticket.update_status(status, remark)
|
|
|
|
# 记录操作日志
|
|
ticket_ids = [ticket.ticket_id for ticket in tickets]
|
|
log_operation('BATCH_UPDATE_TICKET_STATUS', 'TICKET', None, {
|
|
'ticket_ids': ticket_ids,
|
|
'status': status,
|
|
'count': len(tickets)
|
|
})
|
|
|
|
status_names = {0: '待处理', 1: '处理中', 2: '已解决', 3: '已关闭'}
|
|
status_name = status_names.get(status, '未知')
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'成功将 {len(tickets)} 个工单状态更新为{status_name}'
|
|
})
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
current_app.logger.error(f"批量更新工单状态失败: {str(e)}")
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '服务器内部错误'
|
|
}), 500
|
|
|
|
|
|
@api_bp.route('/tickets/<int:ticket_id>', methods=['GET'])
|
|
@require_login
|
|
def get_ticket_detail(ticket_id):
|
|
"""获取工单详情"""
|
|
try:
|
|
ticket = Ticket.query.get_or_404(ticket_id)
|
|
|
|
# 检查权限(管理员或工单创建者)
|
|
from flask import session
|
|
admin = session.get('admin')
|
|
if not admin:
|
|
# 检查是否是工单创建者
|
|
if ticket.creator != admin.get('username') if admin else True:
|
|
return jsonify({'success': False, 'message': '没有权限访问该工单'}), 403
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'data': ticket.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
current_app.logger.error(f"获取工单详情失败: {str(e)}")
|
|
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|
|
|
|
|
|
@api_bp.route('/tickets/<int:ticket_id>/status', methods=['PUT'])
|
|
@require_login
|
|
def update_ticket_status(ticket_id):
|
|
"""更新工单状态"""
|
|
try:
|
|
ticket = Ticket.query.get_or_404(ticket_id)
|
|
data = request.get_json()
|
|
|
|
if not data or 'status' not in data:
|
|
return jsonify({'success': False, 'message': '缺少status参数'}), 400
|
|
|
|
status = data['status']
|
|
remark = data.get('remark', '')
|
|
|
|
if status not in [0, 1, 2, 3]:
|
|
return jsonify({'success': False, 'message': '无效的状态值'}), 400
|
|
|
|
old_status = ticket.status
|
|
ticket.update_status(status, remark)
|
|
|
|
# 记录操作日志
|
|
status_names = {0: '待处理', 1: '处理中', 2: '已解决', 3: '已关闭'}
|
|
log_operation('UPDATE_TICKET_STATUS', 'TICKET', ticket.ticket_id, {
|
|
'old_status': old_status,
|
|
'new_status': status,
|
|
'status_name': status_names.get(status, '未知')
|
|
})
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'工单状态已更新为{status_names.get(status, "未知")}',
|
|
'data': ticket.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
current_app.logger.error(f"更新工单状态失败: {str(e)}")
|
|
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|
|
|
|
|
|
@api_bp.route('/tickets/<int:ticket_id>/replies', methods=['POST'])
|
|
@require_login
|
|
def add_ticket_reply(ticket_id):
|
|
"""添加工单回复"""
|
|
try:
|
|
ticket = Ticket.query.get_or_404(ticket_id)
|
|
data = request.get_json()
|
|
|
|
if not data or 'content' not in data:
|
|
return jsonify({'success': False, 'message': '缺少content参数'}), 400
|
|
|
|
content = data['content'].strip()
|
|
if not content:
|
|
return jsonify({'success': False, 'message': '回复内容不能为空'}), 400
|
|
|
|
# 获取当前管理员信息
|
|
from flask import session
|
|
admin = session.get('admin')
|
|
creator = admin.get('username') if admin else '支持团队'
|
|
|
|
# 创建回复
|
|
reply = TicketReply(
|
|
ticket_id=ticket_id,
|
|
content=content,
|
|
creator=creator
|
|
)
|
|
|
|
db.session.add(reply)
|
|
db.session.commit()
|
|
|
|
# 记录操作日志
|
|
log_operation('ADD_TICKET_REPLY', 'TICKET', ticket_id, {
|
|
'reply_id': reply.reply_id,
|
|
'creator': creator
|
|
})
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': '回复添加成功',
|
|
'data': reply.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
current_app.logger.error(f"添加工单回复失败: {str(e)}")
|
|
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|