|
|
@@ -2,15 +2,23 @@
|
|
|
|
|
|
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();
|
|
|
+ return Notification::orderBy('created_at', 'desc')->get();
|
|
|
}
|
|
|
|
|
|
public function findById(int $id): ?Notification
|
|
|
@@ -46,5 +54,146 @@ public function delete(int $id): bool
|
|
|
return $model->delete();
|
|
|
}
|
|
|
|
|
|
- // Add custom business logic methods here
|
|
|
+ // ------------------------------------------------------------------
|
|
|
+ // 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()]);
|
|
|
+ }
|
|
|
}
|