id) ->orderByDesc('id') ->get(); } /** * ========================= * VALIDATION RULE ENGINE * ========================= */ private function type(Ticket $ticket) { return $ticket->ticketType; } private function ensureRequiredAssets(Ticket $ticket, array $payload = []) { $type = $this->type($ticket); // PHOTO VALIDATION if ($type?->require_photo) { $photoCount = $payload['photo_count'] ?? 0; if ($photoCount <= 0) { throw ValidationException::withMessages([ 'photo' => 'Foto wajib diupload untuk ticket ini' ]); } } // MATERIAL VALIDATION if ($type?->require_material) { $materialCount = $payload['material_count'] ?? 0; if ($materialCount <= 0) { throw ValidationException::withMessages([ 'material' => 'Material wajib diinput untuk ticket ini' ]); } } } private function ensureCustomer(Ticket $ticket) { $type = $this->type($ticket); if ($type?->need_customer && empty($ticket->customer_id)) { throw ValidationException::withMessages([ 'customer_id' => 'Customer wajib ada untuk ticket ini' ]); } } /** * ========================= * ASSIGN / REASSIGN * ========================= */ public function assign( Ticket $ticket, array $technicians, int $leaderId, int $userId ): Ticket { TicketWorkflowValidator::allow($ticket, ['approved']); DB::transaction(function () use ( $ticket, $technicians, $leaderId, $userId ) { foreach ($technicians as $technicianId) { TicketAssignment::create([ 'ticket_id' => $ticket->id, 'user_id' => $technicianId, 'is_leader' => ($technicianId == $leaderId), 'assigned_by' => $userId, 'assigned_at' => now(), 'status' => 'assigned' ]); } $ticket->update([ 'status' => 'assigned', 'assigned_at' => now() ]); $this->log($ticket->id, $userId, 'Assign', 'Teknisi ditugaskan'); }); return $ticket->fresh(); } public function reassign( Ticket $ticket, array $technicians, int $leaderId, int $userId ): Ticket { DB::transaction(function () use ( $ticket, $technicians, $leaderId, $userId ) { TicketAssignment::where('ticket_id', $ticket->id)->delete(); foreach ($technicians as $technicianId) { TicketAssignment::create([ 'ticket_id' => $ticket->id, 'user_id' => $technicianId, 'is_leader' => ($technicianId == $leaderId), 'assigned_by' => $userId, 'assigned_at' => now(), 'status' => 'assigned' ]); } $this->log($ticket->id, $userId, 'Reassign', 'Penugasan diperbarui'); }); return $ticket->fresh(); } /** * ========================= * APPROVAL FLOW * ========================= */ public function approve( Ticket $ticket, ?string $notes, int $userId ): Ticket { TicketWorkflowValidator::allow($ticket, ['open']); DB::transaction(function () use ($ticket, $notes, $userId) { TicketApproval::create([ 'ticket_id' => $ticket->id, 'approver_id' => $userId, 'status' => 'approved', 'notes' => $notes, 'approved_at' => now(), 'created_at' => now() ]); $ticket->update([ 'status' => 'approved', 'approved_by' => $userId, 'approved_at' => now() ]); $this->log($ticket->id, $userId, 'Approved', $notes); }); return $ticket->fresh(); } public function reject(Ticket $ticket, string $reason, int $userId): Ticket { DB::transaction(function () use ($ticket, $reason, $userId) { TicketApproval::create([ 'ticket_id' => $ticket->id, 'approver_id' => $userId, 'status' => 'rejected', 'notes' => $reason, 'created_at' => now() ]); $ticket->update([ 'status' => 'rejected', 'rejected_by' => $userId, 'rejected_at' => now(), 'rejection_reason' => $reason ]); $this->log($ticket->id, $userId, 'Rejected', $reason); }); return $ticket->fresh(); } /** * ========================= * WORKFLOW START * ========================= */ public function start(Ticket $ticket, array $location, int $userId): Ticket { TicketWorkflowValidator::allow($ticket, ['assigned']); DB::transaction(function () use ($ticket, $location, $userId) { $ticket->update([ 'status' => 'progress' ]); $this->log( $ticket->id, $userId, 'Start Work', 'Pekerjaan dimulai' ); }); return $ticket->fresh(); } public function pending(Ticket $ticket, string $reason, int $userId): Ticket { $ticket->update(['status' => 'pending']); $this->log($ticket->id, $userId, 'Pending', $reason); return $ticket->fresh(); } public function resume(Ticket $ticket, int $userId): Ticket { $ticket->update(['status' => 'progress']); $this->log($ticket->id, $userId, 'Resume', 'Dilanjutkan'); return $ticket->fresh(); } /** * ========================= * RESOLVE (VALIDATION POINT) * ========================= */ public function resolve( Ticket $ticket, string $notes, int $userId, array $payload = [] ): Ticket { TicketWorkflowValidator::allow($ticket, ['progress']); // 🔥 VALIDASI MATERIAL + PHOTO + CUSTOMER $this->ensureRequiredAssets($ticket, $payload); $this->ensureCustomer($ticket); DB::transaction(function () use ($ticket, $notes, $userId) { $ticket->update([ 'status' => 'resolved', 'resolved_at' => now() ]); $this->log($ticket->id, $userId, 'Resolved', $notes); }); return $ticket->fresh(); } public function close(Ticket $ticket, ?string $notes, int $userId): Ticket { TicketWorkflowValidator::allow($ticket, ['resolved']); $ticket->update([ 'status' => 'closed', 'closed_at' => now() ]); $this->log($ticket->id, $userId, 'Closed', $notes); return $ticket->fresh(); } public function cancel(Ticket $ticket, string $reason, int $userId): Ticket { $ticket->update(['status' => 'cancelled']); $this->log($ticket->id, $userId, 'Cancelled', $reason); return $ticket->fresh(); } public function escalate(Ticket $ticket, string $reason, int $userId): Ticket { $this->log($ticket->id, $userId, 'Escalated', $reason); return $ticket->fresh(); } /** * ========================= * LOG SYSTEM * ========================= */ private function log( int $ticketId, int $userId, string $activity, ?string $notes = null ): void { TicketLog::create([ 'ticket_id' => $ticketId, 'user_id' => $userId, 'activity' => $activity, 'notes' => $notes, 'created_at'=> now() ]); } }