309 lines
11 KiB
PHP
309 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Modules\ManagementTools\Http\Controllers;
|
|
|
|
use App\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Routing\Controller;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Modules\ManagementTools\Entities\MtUserTicket;
|
|
use Modules\ManagementTools\Entities\MtUserTicketReply;
|
|
use Modules\ManagementTools\Utils\ManagementToolsUtil;
|
|
use Yajra\DataTables\Facades\DataTables;
|
|
|
|
class TicketController extends Controller
|
|
{
|
|
protected $mtUtil;
|
|
|
|
public function __construct(ManagementToolsUtil $mtUtil)
|
|
{
|
|
$this->mtUtil = $mtUtil;
|
|
}
|
|
protected function canAccess(): void
|
|
{
|
|
if (! auth()->user()->can('managementtools.ticket_own') && ! auth()->user()->can('managementtools.ticket_manage')) {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
}
|
|
|
|
protected function canManage(): bool
|
|
{
|
|
return auth()->user()->can('managementtools.ticket_manage');
|
|
}
|
|
|
|
protected function ticketQuery(int $businessId)
|
|
{
|
|
$query = MtUserTicket::forBusiness($businessId)
|
|
->with(['user', 'assignee'])
|
|
->select('mt_user_tickets.*');
|
|
|
|
if (! $this->canManage()) {
|
|
$query->where(function ($q) {
|
|
$q->where('user_id', auth()->id())
|
|
->orWhere('assigned_to', auth()->id());
|
|
});
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
protected function findTicket(int $businessId, int $id): MtUserTicket
|
|
{
|
|
$ticket = MtUserTicket::forBusiness($businessId)->findOrFail($id);
|
|
|
|
if (! $this->canManage() && (int) $ticket->user_id !== (int) auth()->id() && (int) $ticket->assigned_to !== (int) auth()->id()) {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
|
|
return $ticket;
|
|
}
|
|
|
|
public function index(Request $request)
|
|
{
|
|
$this->canAccess();
|
|
$businessId = (int) $request->session()->get('user.business_id');
|
|
|
|
if ($request->ajax()) {
|
|
$tickets = $this->ticketQuery($businessId)->orderByDesc('updated_at');
|
|
|
|
if ($request->filled('status') && $request->status !== 'all') {
|
|
$tickets->where('status', (int) $request->status);
|
|
}
|
|
if ($request->filled('priority') && $request->priority !== 'all') {
|
|
$tickets->where('priority', $request->priority);
|
|
}
|
|
if ($this->canManage() && $request->filled('user_id')) {
|
|
$tickets->where('user_id', (int) $request->user_id);
|
|
}
|
|
|
|
return DataTables::of($tickets)
|
|
->addColumn('reference', fn ($row) => $row->reference)
|
|
->addColumn('requester', fn ($row) => $row->user ? trim($row->user->first_name.' '.($row->user->last_name ?? '')) : '—')
|
|
->addColumn('assignee_name', fn ($row) => $row->assignee ? trim($row->assignee->first_name.' '.($row->assignee->last_name ?? '')) : '—')
|
|
->addColumn('status_label', function ($row) {
|
|
return '<span class="label '.$row->statusBadgeClass().'">'.$row->statusLabel().'</span>';
|
|
})
|
|
->addColumn('priority_label', function ($row) {
|
|
return '<span class="label '.$row->priorityBadgeClass().'">'.$row->priorityLabel().'</span>';
|
|
})
|
|
->addColumn('action', function ($row) {
|
|
$show = action([self::class, 'show'], [$row->id]);
|
|
|
|
return '<a href="'.$show.'" class="btn btn-xs btn-primary"><i class="fa fa-eye"></i> '.__('managementtools::lang.view').'</a>';
|
|
})
|
|
->editColumn('subject', fn ($row) => e($row->subject))
|
|
->editColumn('updated_at', fn ($row) => $this->mtUtil->formatDateTime($row->updated_at))
|
|
->rawColumns(['status_label', 'priority_label', 'action'])
|
|
->make(true);
|
|
}
|
|
|
|
$stats = $this->buildStats($businessId);
|
|
$users = $this->canManage()
|
|
? User::where('business_id', $businessId)->user()->pluck('first_name', 'id')
|
|
: collect();
|
|
|
|
return view('managementtools::tickets.index', [
|
|
'stats' => $stats,
|
|
'statuses' => MtUserTicket::statuses(),
|
|
'priorities' => MtUserTicket::priorities(),
|
|
'users' => $users,
|
|
'can_manage' => $this->canManage(),
|
|
]);
|
|
}
|
|
|
|
public function create()
|
|
{
|
|
$this->canAccess();
|
|
if (! auth()->user()->can('managementtools.ticket_own')) {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
|
|
$businessId = (int) request()->session()->get('user.business_id');
|
|
$recipients = User::where('business_id', $businessId)
|
|
->user()
|
|
->where('allow_login', 1)
|
|
->where('id', '!=', auth()->id())
|
|
->whereNull('deleted_at')
|
|
->orderBy('first_name')
|
|
->get()
|
|
->mapWithKeys(function ($u) {
|
|
return [$u->id => trim($u->first_name.' '.($u->last_name ?? ''))];
|
|
});
|
|
|
|
return view('managementtools::tickets.create', [
|
|
'priorities' => MtUserTicket::priorities(),
|
|
'recipients' => $recipients,
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request)
|
|
{
|
|
$this->canAccess();
|
|
if (! auth()->user()->can('managementtools.ticket_own')) {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
|
|
$businessId = (int) $request->session()->get('user.business_id');
|
|
$request->validate([
|
|
'subject' => 'required|string|max:255',
|
|
'body' => 'required|string|max:5000',
|
|
'priority' => 'required|in:low,normal,high,urgent',
|
|
'assigned_to' => 'nullable|integer|exists:users,id',
|
|
]);
|
|
|
|
$ticket = MtUserTicket::create([
|
|
'business_id' => $businessId,
|
|
'user_id' => auth()->id(),
|
|
'subject' => $request->subject,
|
|
'body' => $request->body,
|
|
'status' => MtUserTicket::STATUS_OPEN,
|
|
'priority' => $request->priority,
|
|
'assigned_to' => $request->input('assigned_to') ?: null,
|
|
'is_read_by_admin' => false,
|
|
'is_read_by_user' => true,
|
|
]);
|
|
|
|
if ($request->ajax() || $request->wantsJson()) {
|
|
return response()->json([
|
|
'success' => true,
|
|
'msg' => __('managementtools::lang.ticket_created'),
|
|
'redirect' => action([self::class, 'show'], [$ticket->id]),
|
|
]);
|
|
}
|
|
|
|
return redirect()->action([self::class, 'show'], [$ticket->id])
|
|
->with('status', ['success' => 1, 'msg' => __('managementtools::lang.ticket_created')]);
|
|
}
|
|
|
|
public function show(Request $request, $id)
|
|
{
|
|
$this->canAccess();
|
|
$businessId = (int) $request->session()->get('user.business_id');
|
|
$ticket = $this->findTicket($businessId, $id);
|
|
$ticket->load(['user', 'assignee', 'replies.user']);
|
|
|
|
if ($this->canManage()) {
|
|
$ticket->update(['is_read_by_admin' => true]);
|
|
} elseif ((int) $ticket->user_id === (int) auth()->id() || (int) $ticket->assigned_to === (int) auth()->id()) {
|
|
$ticket->update(['is_read_by_user' => true]);
|
|
}
|
|
|
|
$agents = $this->canManage()
|
|
? User::where('business_id', $businessId)->user()->get()
|
|
: collect();
|
|
|
|
return view('managementtools::tickets.show', [
|
|
'ticket' => $ticket,
|
|
'statuses' => MtUserTicket::statuses(),
|
|
'priorities' => MtUserTicket::priorities(),
|
|
'agents' => $agents,
|
|
'can_manage' => $this->canManage(),
|
|
]);
|
|
}
|
|
|
|
public function reply(Request $request, $id)
|
|
{
|
|
$this->canAccess();
|
|
$businessId = (int) $request->session()->get('user.business_id');
|
|
$ticket = $this->findTicket($businessId, $id);
|
|
|
|
if (in_array((int) $ticket->status, [MtUserTicket::STATUS_CLOSED], true)) {
|
|
return response()->json(['success' => false, 'msg' => __('managementtools::lang.ticket_closed_no_reply')]);
|
|
}
|
|
|
|
$request->validate([
|
|
'body' => 'required_without:attachment|nullable|string|max:5000',
|
|
'attachment' => 'nullable|file|max:5120|mimes:jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,zip',
|
|
]);
|
|
|
|
$attachmentPath = null;
|
|
$attachmentType = null;
|
|
if ($request->hasFile('attachment')) {
|
|
$file = $request->file('attachment');
|
|
$attachmentType = $file->getClientMimeType();
|
|
$attachmentPath = $file->store('tickets/'.$businessId, 'public');
|
|
}
|
|
|
|
MtUserTicketReply::create([
|
|
'business_id' => $businessId,
|
|
'ticket_id' => $ticket->id,
|
|
'user_id' => auth()->id(),
|
|
'body' => $request->input('body'),
|
|
'attachment' => $attachmentPath,
|
|
'attachment_type' => $attachmentType,
|
|
]);
|
|
|
|
$updates = ['updated_at' => now()];
|
|
if ($this->canManage()) {
|
|
$updates['is_read_by_user'] = false;
|
|
if ((int) $ticket->status === MtUserTicket::STATUS_OPEN) {
|
|
$updates['status'] = MtUserTicket::STATUS_IN_PROGRESS;
|
|
}
|
|
} else {
|
|
$updates['is_read_by_admin'] = false;
|
|
}
|
|
$ticket->update($updates);
|
|
|
|
return response()->json(['success' => true, 'msg' => __('managementtools::lang.ticket_reply_sent')]);
|
|
}
|
|
|
|
public function update(Request $request, $id)
|
|
{
|
|
$this->canAccess();
|
|
if (! $this->canManage()) {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
|
|
$businessId = (int) $request->session()->get('user.business_id');
|
|
$ticket = $this->findTicket($businessId, $id);
|
|
|
|
$request->validate([
|
|
'status' => 'nullable|integer|in:0,1,2,3',
|
|
'priority' => 'nullable|in:low,normal,high,urgent',
|
|
'assigned_to' => 'nullable|integer|exists:users,id',
|
|
]);
|
|
|
|
$ticket->update(array_filter([
|
|
'status' => $request->has('status') ? (int) $request->status : $ticket->status,
|
|
'priority' => $request->input('priority', $ticket->priority),
|
|
'assigned_to' => $request->input('assigned_to') ?: null,
|
|
'is_read_by_user' => false,
|
|
], fn ($v) => $v !== null));
|
|
|
|
return response()->json(['success' => true, 'msg' => __('managementtools::lang.ticket_updated')]);
|
|
}
|
|
|
|
public function destroy($id)
|
|
{
|
|
$this->canAccess();
|
|
if (! $this->canManage()) {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
|
|
$businessId = (int) request()->session()->get('user.business_id');
|
|
$ticket = $this->findTicket($businessId, $id);
|
|
|
|
foreach ($ticket->replies as $reply) {
|
|
if ($reply->attachment) {
|
|
Storage::disk('public')->delete($reply->attachment);
|
|
}
|
|
}
|
|
$ticket->delete();
|
|
|
|
return response()->json(['success' => true, 'msg' => __('managementtools::lang.ticket_deleted')]);
|
|
}
|
|
|
|
protected function buildStats(int $businessId): array
|
|
{
|
|
$base = $this->ticketQuery($businessId);
|
|
|
|
return [
|
|
'open' => (clone $base)->where('status', MtUserTicket::STATUS_OPEN)->count(),
|
|
'in_progress' => (clone $base)->where('status', MtUserTicket::STATUS_IN_PROGRESS)->count(),
|
|
'resolved' => (clone $base)->where('status', MtUserTicket::STATUS_RESOLVED)->count(),
|
|
'closed' => (clone $base)->where('status', MtUserTicket::STATUS_CLOSED)->count(),
|
|
'total' => (clone $base)->count(),
|
|
];
|
|
}
|
|
}
|