feat: add GeneratesUniqueSlug class for unique slug generation
feat: create PostCategoryFactory for generating post category data feat: create PostFactory for generating post data feat: add retry_requested_at column to services table feat: add vcenter provisioning fields to products table feat: add module_host_id to os table feat: create email_notification_templates table for email templates feat: add scenario and format fields to template table feat: add admin fields to transactions table fix: update ticket table columns and types feat: add uuid to ticket table and backfill existing records fix: make ticket_reply user_id nullable feat: create notifications table for user notifications fix: make ticket uuid nullable feat: create post_categories table for blog categories feat: create posts table for blog posts feat: add invoice email template for PDF generation
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Client\Ticket;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\Ticket\TicketResource;
|
||||
use App\Http\Resources\Api\Ticket\TicketReplyResource;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\Notification;
|
||||
use App\Models\TicketReply;
|
||||
use App\Events\TicketReplyCreated;
|
||||
use App\Repositories\Ticket\TicketRepositoryInterface;
|
||||
use App\Repositories\TicketReply\TicketReplyRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected TicketRepositoryInterface $ticketRepository,
|
||||
protected TicketReplyRepositoryInterface $ticketReplyRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* List tickets for the authenticated customer (paginated, filterable by status).
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$customer = auth('client')->user();
|
||||
|
||||
$query = Ticket::query()
|
||||
->where('customer_id', $customer->id)
|
||||
->latest('id');
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->input('status'));
|
||||
}
|
||||
|
||||
$paginated = $query->paginate(15);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'items' => TicketResource::collection($paginated->items()),
|
||||
'total' => $paginated->total(),
|
||||
'per_page' => $paginated->perPage(),
|
||||
'current_page' => $paginated->currentPage(),
|
||||
'last_page' => $paginated->lastPage(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ticket for the authenticated customer.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'subject' => ['required', 'string', 'min:5', 'max:200'],
|
||||
'description' => ['required', 'string', 'min:20'],
|
||||
'room' => ['nullable', 'string'],
|
||||
'data' => ['nullable', 'array'],
|
||||
]);
|
||||
|
||||
$customer = auth('client')->user();
|
||||
|
||||
$ticketNum = 'TKT-' . now()->format('Ymd') . '-' . strtoupper(Str::random(5));
|
||||
|
||||
$ticket = $this->ticketRepository->create([
|
||||
'ticket_num' => $ticketNum,
|
||||
'subject' => $request->input('subject'),
|
||||
'description' => $request->input('description'),
|
||||
'status' => 'open',
|
||||
'room' => $request->input('room'),
|
||||
'data' => $request->input('data') ? json_encode($request->input('data')) : null,
|
||||
'customer_id' => $customer->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Ticket đã được tạo thành công.',
|
||||
'data' => new TicketResource($ticket),
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a single ticket with its replies (must belong to customer).
|
||||
*/
|
||||
public function show(string $ticket_num)
|
||||
{
|
||||
$customer = auth('client')->user();
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->with(['reply.user', 'customer'])
|
||||
->where('ticket_num', $ticket_num)
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
if (!$ticket) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Ticket không tồn tại hoặc bạn không có quyền truy cập.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => new TicketResource($ticket),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a reply to a ticket (customer side).
|
||||
*/
|
||||
public function reply(Request $request, string $ticket_num)
|
||||
{
|
||||
$request->validate([
|
||||
'message' => ['required', 'string', 'max:2000'],
|
||||
]);
|
||||
|
||||
$customer = auth('client')->user();
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('ticket_num', $ticket_num)
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
if (!$ticket) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Ticket không tồn tại hoặc bạn không có quyền truy cập.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if (in_array($ticket->status, ['closed', 'resolved'])) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Ticket đã đóng, không thể thêm phản hồi.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$reply = $this->ticketReplyRepository->create([
|
||||
'ticket_id' => $ticket->id,
|
||||
'customer_id' => $customer->id,
|
||||
'message' => $request->input('message'),
|
||||
'user_id' => null,
|
||||
]);
|
||||
|
||||
Notification::notifyAdmins([
|
||||
'type' => 'ticket_reply',
|
||||
'title' => 'Có phản hồi mới từ khách hàng',
|
||||
'body' => $ticket->ticket_num . ' - ' . $ticket->subject,
|
||||
'link' => '/tickets/' . $ticket->ticket_num,
|
||||
'data' => [
|
||||
'ticket_num' => $ticket->ticket_num,
|
||||
'reply_id' => $reply->id,
|
||||
],
|
||||
]);
|
||||
|
||||
broadcast(new TicketReplyCreated(
|
||||
ticketNum: $ticket->ticket_num,
|
||||
customerId: $customer->id,
|
||||
replyId: $reply->id,
|
||||
));
|
||||
|
||||
// Update ticket status back to open (customer replied)
|
||||
$ticket->update(['status' => 'open']);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Phản hồi đã được gửi.',
|
||||
'data' => new TicketReplyResource($reply),
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer closes their ticket.
|
||||
*/
|
||||
public function close(string $ticket_num)
|
||||
{
|
||||
$customer = auth('client')->user();
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('ticket_num', $ticket_num)
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
if (!$ticket) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Ticket không tồn tại hoặc bạn không có quyền truy cập.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if ($ticket->status === 'closed') {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Ticket đã được đóng trước đó.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$ticket->update(['status' => 'closed']);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Ticket đã được đóng.',
|
||||
'data' => new TicketResource($ticket->fresh()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user