Commit 424c929
feat(atendimento): fila de leads por atendente ganha porta, e o rodízio passa a distribuir lead (#144)
Pedido por @WerikoEntusiasta: distribuir leads em rodízio entre atendentes, com
cada um vendo só os seus. Medido antes de escrever uma linha: as duas coisas já
existiam INTEIRAS no backend — o worker de rodízio (`lib/routing/`, modo
`round_robin`, elegibilidade por disponibilidade e horário) e a RLS de
visibilidade (`fn_can_view_lead`, `fn_can_view_conversation`, modo `own`).
O que não existia era a PORTA. Nenhum arquivo de `app/` ou `components/`
consumia `/api/v1/settings/routing`, e `visibility_mode` só aparecia sendo LIDO
em `app/app/layout.tsx` — escrito por ninguém. O único jeito de ligar a feature
que o contribuidor pediu era `UPDATE` à mão no Postgres. Num produto self-host
isso é a feature não existir.
TRÊS BURACOS, e os dois últimos só apareceram porque a prova foi pela tela:
1. **Sem tela.** Entra `/app/settings/atendimento` (manager+, que é o que a
matriz da spec 13 §4 dá para "atendimento/routing"), com as duas decisões
juntas porque uma sem a outra quebra: distribuir sem restringir deixa todos
vendo a carteira do colega; restringir sem distribuir deixa o funil vazio. A
tela avisa justamente essa combinação morta. Registrada em
`lib/navigation/registry.ts` — tela sem porta é o mesmo defeito de novo.
2. **O rodízio distribuía CONVERSA e não LEAD.** `fn_conversation_assign` grava
`conversations.assigned_to_user_id` e não toca em `crm_leads.owner_user_id`.
Com `visibility_mode='own'` — exatamente o que a issue pede — lead sem dono
não aparece para NINGUÉM: o atendente receberia a conversa e abriria um funil
vazio. A restrição funcionaria e a fila não existiria. O worker agora adota os
leads abertos do contato, e só os SEM DONO (nem humano nem agente de IA):
roubar lead com dono transformaria o rodízio em reatribuição silenciosa a
cada mensagem nova.
3. **`PATCH /api/v1/settings/routing` era um no-op silencioso.** A única policy
de escrita de `organizations` é `orgs_write_platform_admin`, com
`USING (fn_is_platform_admin())`. Pelo client de sessão o UPDATE de um manager
casa ZERO linhas — e o PostgREST devolve sucesso, porque "nada casou o filtro"
não é erro. A tela dizia "salvo" e o reload trazia o estado antigo. Medido no
banco: manager → 0 linhas, postgres → 1 (controle positivo). Ninguém tinha
notado porque o dono do repo e o owner criado pelo `bootstrap-owner.ts` são
platform_admin; quem tropeça é o segundo admin convidado e todo manager.
Consertei a CLASSE, não a instância: `updateTenant` (perfil da org, admin-only)
tinha o mesmo defeito e está no mesmo commit.
PROVAS
- `distribuicao-atendimento.spec.ts` dirige o frontend: acha a tela pela
navegação (não pela URL), liga rodízio + restrição, salva, RECARREGA e confere
que o estado voltou do banco, e confirma que a API concorda com a tela. Mais o
RBAC pelo servidor (agent → 403), porque o redirect da página é conforto, não
defesa. **3 testes verdes** contra Supabase local com o `baseline.sql` aplicado.
- Sabotagem: voltar o PATCH para o client de sessão deixa a spec vermelha — foi
assim que o defeito nº 3 apareceu.
- Unit: `routing-adota-lead.test.ts` guarda as condições da adoção (sabotado nos
dois eixos) e `atendimento-config-visibilidade.test.ts` cobra que um corpo sem
`visibility_mode` PRESERVE a restrição em vez de voltar ao default — o jeito de
uma org perder a restrição sem ninguém pedir.
- Sem mudança de schema: `settings` é jsonb e as colunas de dono já existem.
- gov:verify verde: 258 arquivos, 2381 testes.
Closes #144
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HstH7nNmrTvCtZsasppeHj1 parent f261cef commit 424c929
12 files changed
Lines changed: 896 additions & 15 deletions
File tree
- .github/workflows
- app
- actions/settings
- api/v1/settings/routing
- app/settings/atendimento
- docs/doctrine
- lib
- routing
- schemas
- tests
- e2e
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
| 128 | + | |
128 | 129 | | |
129 | 130 | | |
130 | 131 | | |
| |||
143 | 144 | | |
144 | 145 | | |
145 | 146 | | |
146 | | - | |
| 147 | + | |
147 | 148 | | |
148 | 149 | | |
149 | | - | |
| 150 | + | |
| 151 | + | |
150 | 152 | | |
151 | 153 | | |
152 | 154 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
30 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
31 | 51 | | |
32 | 52 | | |
33 | 53 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
3 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
12 | 29 | | |
13 | 30 | | |
14 | 31 | | |
| |||
17 | 34 | | |
18 | 35 | | |
19 | 36 | | |
20 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
21 | 40 | | |
22 | 41 | | |
23 | 42 | | |
| |||
41 | 60 | | |
42 | 61 | | |
43 | 62 | | |
44 | | - | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
45 | 68 | | |
46 | 69 | | |
47 | 70 | | |
| |||
52 | 75 | | |
53 | 76 | | |
54 | 77 | | |
55 | | - | |
| 78 | + | |
56 | 79 | | |
57 | 80 | | |
58 | 81 | | |
| |||
63 | 86 | | |
64 | 87 | | |
65 | 88 | | |
66 | | - | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
67 | 109 | | |
68 | 110 | | |
69 | 111 | | |
| |||
72 | 114 | | |
73 | 115 | | |
74 | 116 | | |
75 | | - | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
76 | 124 | | |
77 | 125 | | |
78 | 126 | | |
| |||
87 | 135 | | |
88 | 136 | | |
89 | 137 | | |
90 | | - | |
| 138 | + | |
91 | 139 | | |
92 | 140 | | |
93 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
94 | 150 | | |
0 commit comments