| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- <?php
- namespace App\Services;
- use App\Broadcasting\Entity\WebsocketEventData;
- use App\Broadcasting\Events\WebsocketEvent;
- use App\Enums\UserTypeEnum;
- use App\Models\Notification;
- use App\Models\NotificationRecipient;
- use App\Models\User;
- use Illuminate\Database\Eloquent\Collection;
- use Illuminate\Support\Facades\DB;
- class NotificationService
- {
- // ------------------------------------------------------------------
- // CRUD genérico (mantido para o controller base / rotas existentes)
- // ------------------------------------------------------------------
- public function getAll(): Collection
- {
- return Notification::orderBy('created_at', 'desc')->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()]);
- }
- }
|