Docs›Infraestrutura & Ferramentas›Notificações
Infraestrutura & Ferramentas

Notificações

Notificações in-app e por email.

Notificações — MadNotify

Sistema de notificações in-app nativo do MAD: sino no header, central "ver todas", contagem de não lidas e push em tempo real via Laravel Reverb (WebSocket), com degradação automática para polling quando o Reverb está desligado ou indisponível.

Como funciona

MadNotify::to($userId)->subject(...)->action(...)->send()
    └── grava 1 linha em mad_comm_notification (conexão `comm`)
    └── dispara MadNotificationReceived (Reverb) se config('mad.notifications.reverb')
            ├── Reverb ligado e saudável → sino atualiza na hora (badge + toast)
            └── Reverb desligado/indisponível (try/catch) → só o polling do header
                                                              atualiza o sino depois

A persistência nunca depende do Reverb estar no ar — o push é só uma camada de velocidade por cima de uma notificação que já foi salva.


Enviando uma notificação

Builder fluente (one-off)

Para notificações pontuais, sem precisar criar uma classe:

use App\Notifications\MadNotify;

MadNotify::to($userId)
    ->subject('Pedido aprovado')
    ->message('Seu pedido #123 foi aprovado e já está em produção.')
    ->icon('shopping-cart')
    ->level('success') // info | success | warning | danger
    ->action('PedidoForm', 'onEdit', ['id' => 123], 'Ver pedido')
    ->send();

Métodos do builder: subject() (ou title()), message(), icon(), level(), type(), from(?int $userId), with(array $extra) (dados livres preservados em data), e a ação — escolha uma:

Método Comportamento no clique
->action($class, $method, $params, $label) Abre uma tela MAD via Mad.go(class, method, params)
->navigate($url, $label) Navega por URL amigável via Mad.navigate(url)
->url($url, $label) Abre link externo em nova aba

to() aceita 1 destinatário, um array ou uma instância de User — send() grava 1 linha por destinatário e retorna os models criados.

Múltiplos destinatários

MadNotify::to([$gerente->id, $supervisor->id])
    ->subject('Estoque baixo')
    ->message("O produto {$produto->nome} está com {$produto->qtd} unidades.")
    ->level('warning')
    ->send();

Notificações tipadas (reutilizáveis)

Quando o mesmo tipo de notificação é disparado em vários pontos do código, estenda MadNotificationType em vez de repetir o builder:

<?php

namespace App\Notifications;

use App\Models\Iam\User;

class PedidoAguardandoAprovacao extends MadNotificationType
{
    public function __construct(private Pedido $pedido) {}

    public function toMad(?User $user): array
    {
        return [
            'title'   => _t('Aprovação de pedido'),
            'message' => _t('O pedido #^1 aguarda sua aprovação.', $this->pedido->id),
            'icon'    => 'shopping-cart',
            'level'   => 'warning',
            'action'  => $this->goAction('PedidoForm', 'onEdit', ['id' => $this->pedido->id], _t('Ver pedido')),
        ];
    }
}

Disparando:

use App\Notifications\MadNotify;

MadNotify::send($userId, new PedidoAguardandoAprovacao($pedido));
// ou, com vários destinatários:
MadNotify::send([$id1, $id2], new PedidoAguardandoAprovacao($pedido));

Dentro de toMad(), use os helpers protegidos goAction(), navigateAction() e urlAction() para montar a ação — mesma semântica das três opções do builder fluente.


Trait MadNotifiable — sintaxe $user->notify()

O model App\Models\Iam\User já usa a trait App\Models\Concerns\MadNotifiable, que dá a ergonomia familiar do Laravel sem depender do Auth::user() nativo (o MAD identifica o usuário via session('userid')):

$user->notify(new PedidoAguardandoAprovacao($pedido));

$user->notifications();              // Builder — todas, mais recentes primeiro
$user->unreadNotifications();        // Builder — só não lidas
$user->unreadNotificationsCount();   // int
$user->markAllNotificationsRead();   // int — quantas foram marcadas

Consultando notificações

App\Models\Comm\Notification é um Eloquent model normal (conexão comm, tabela mad_comm_notification):

use App\Models\Comm\Notification;

// Não lidas de um usuário
$notificacoes = Notification::forUser($userId)->unread()->orderByDesc('id')->limit(20)->get();

foreach ($notificacoes as $n) {
    echo $n->title() . ' — ' . $n->body();
}

// Marcar como lida / não lida
$n->markRead();
$n->markUnread();

// Contar não lidas
$count = Notification::forUser($userId)->unread()->count();

Estrutura da tabela mad_comm_notification

Coluna Tipo Descrição
id BIGINT PK ID
user_to_id BIGINT Destinatário
user_from_id BIGINT, nullable Remetente (default: session('userid') no momento do envio)
type VARCHAR FQCN da notificação tipada, ou 'generic' para o builder
data JSON title, message, icon, level + extras do dev (->with())
action JSON, nullable {kind: go|navigate|url, ...}
read_at TIMESTAMP, nullable null = não lida
created_at TIMESTAMP Criação

title(), body(), icon(), level() são acessores de conveniência que leem do JSON data — não acesse $n->data['title'] direto em código novo.


Tempo real — Laravel Reverb

O sino do header e a central de notificações funcionam com ou sem Reverb — é uma camada de velocidade opcional, não um requisito:

Estado Comportamento
MAD_NOTIFICATIONS_REVERB=true + Reverb no ar Push instantâneo: canal privado notifications.{userId}, badge + toast no cliente, sem F5
Reverb desligado (default) ou indisponível O polling nativo do header (MadTemplate.updateNotificationsMenu) atualiza o sino periodicamente

Ative no .env:

MAD_NOTIFICATIONS_REVERB=true

Isso registra a rota de autorização do canal privado (POST /broadcasting/auth-notifications → NotificationBroadcastAuthController, autenticado por session('userid')) e injeta a config do cliente Echo no layout. O broadcast em si (MadNotificationReceived) é disparado dentro de um try/catch em MadNotify::deliver() — se o Reverb cair, a notificação já persistida não se perde, só o push instantâneo não acontece.

No fio, o canal é private-notifications.{userId} (o PrivateChannel('notifications.'.$id) do MadNotificationReceived ganha o prefixo private- do Echo/Pusher), e o evento é publicado com o nome curto MadNotificationReceived (broadcastAs()) — não com o FQCN. O authEndpoint do Echo é apontado para /broadcasting/auth-notifications pelo ShellViewModel, separado do /broadcasting/auth usado por chat e Correio.

O evento entrega um payload pronto pra UI (já inclui unread_count recalculado):

['id', 'title', 'message', 'icon', 'level', 'action', 'created_at', 'unread_count']

Telas prontas

Componente Papel
HeaderMessageList Dropdown do sino no header (lista curta + contagem)
NotificationCenter Tela "ver todas" — filtro lido/não-lido, marcar (todas) como lida, excluir
NotificationView Drawer de leitura de 1 notificação — abrir marca como lida e mostra o botão de ação, se houver

Rotas auxiliares usadas pelo sino sem precisar abrir tela:

POST /app/notifications/mark-all-read
POST /app/notifications/{id}/read

Veja também

  • Filas (Queue) — para notificar em lote sem travar a request.
  • E-mail (MailService) — quando a notificação também precisa sair por e-mail, dispare os dois caminhos a partir do mesmo evento de domínio.