get(); } public function findById(int $id): ?Notification { return Notification::find($id); } public function create(array $data): Notification { return Notification::create($data); } public function update(int $id, array $data): ?Notification { $model = $this->findById($id); if (!$model) { return null; } $model->update($data); return $model->fresh(); } public function delete(int $id): bool { $model = $this->findById($id); if (!$model) { return false; } return $model->delete(); } // ------------------------------------------------------------------ // Dispatch: cria a notificação + um recipient por usuário + realtime // ------------------------------------------------------------------ /** * @param array{ * origin_table:string, origin_id:int, type:string, title:string, message:string, * recipient_user_ids:int[], unit_id?:int|null, franchisee_id?:int|null, * url?:string|null, action_text?:string|null, priority?:string, * expires_at?:string|null, created_by_user_id?:int|null * } $params */ public function dispatch(array $params): ?Notification { $recipientUserIds = array_values(array_unique($params['recipient_user_ids'] ?? [])); if (empty($recipientUserIds)) { return null; // ninguém para notificar } return DB::transaction(function () use ($params, $recipientUserIds) { $notification = Notification::create([ 'origin_table' => $params['origin_table'], 'origin_id' => $params['origin_id'], 'unit_id' => $params['unit_id'] ?? null, 'franchisee_id' => $params['franchisee_id'] ?? null, 'notification_type' => $params['type'], 'title' => $params['title'], 'message' => $params['message'], 'url' => $params['url'] ?? null, 'action_text' => $params['action_text'] ?? null, 'priority' => $params['priority'] ?? 'normal', 'expires_at' => $params['expires_at'] ?? null, 'created_by_user_id' => $params['created_by_user_id'] ?? null, ]); $now = now(); $rows = array_map(fn ($userId) => [ 'notification_id' => $notification->id, 'user_id' => $userId, 'unit_id' => $params['unit_id'] ?? null, 'franchisee_id' => $params['franchisee_id'] ?? null, 'is_read' => false, 'channel' => 'in_app', 'is_delivered' => true, 'delivered_at' => $now, 'created_at' => $now, 'updated_at' => $now, ], $recipientUserIds); NotificationRecipient::insert($rows); $this->broadcast($notification, $recipientUserIds); return $notification; }); } /** * Emite um evento realtime por usuário destino (best-effort). */ private function broadcast(Notification $notification, array $userIds): void { $payload = [ 'id' => $notification->id, 'type' => $notification->notification_type, 'title' => $notification->title, ]; foreach ($userIds as $userId) { try { event(new WebsocketEvent(WebsocketEventData::from( room: "user.{$userId}", data: $payload, event: 'notification', ))); } catch (\Throwable $e) { // Realtime é complementar: nunca deve quebrar o fluxo de negócio. report($e); } } } // ------------------------------------------------------------------ // Resolvers de destinatários (fan-out para todos os usuários) // ------------------------------------------------------------------ /** Todos os usuários vinculados a uma unidade. */ public function unitUserIds(?int $unitId): array { if (!$unitId) { return []; } return User::whereHas('units', fn ($q) => $q->where('units.id', $unitId)) ->pluck('id')->all(); } /** Todos os usuários da matriz (franqueadora). */ public function matrizUserIds(): array { return User::where('user_type', UserTypeEnum::ADMIN->value) ->pluck('id')->all(); } // ------------------------------------------------------------------ // Consulta do sino (usuário logado) // ------------------------------------------------------------------ public function forUser(int $userId, int $limit = 30): Collection { return Notification::query() ->select('notifications.*') ->join('notification_recipients as nr', 'nr.notification_id', '=', 'notifications.id') ->where('nr.user_id', $userId) ->where(fn ($q) => $q ->whereNull('notifications.expires_at') ->orWhere('notifications.expires_at', '>', now())) ->with(['recipients' => fn ($q) => $q->where('user_id', $userId)]) ->orderByDesc('notifications.created_at') ->limit($limit) ->get(); } public function unreadCount(int $userId): int { return NotificationRecipient::where('user_id', $userId) ->where('is_read', false) ->count(); } public function markRead(int $notificationId, int $userId): void { NotificationRecipient::where('notification_id', $notificationId) ->where('user_id', $userId) ->where('is_read', false) ->update(['is_read' => true, 'read_at' => now()]); } public function markAllRead(int $userId): void { NotificationRecipient::where('user_id', $userId) ->where('is_read', false) ->update(['is_read' => true, 'read_at' => now()]); } }