Refactor: Memberships
Status: planejamento — nenhum código escrito ainda
Criado em: 2026-04-17
Bloqueado por: decisões pendentes (§4)
Relação com RBAC Fase 2: este refactor substitui o escopo antigo da Fase 2 (ver memory/project_rbac_refactor.md). Metade do trabalho de RBAC só faz sentido em cima desse novo modelo.
Tamanho estimado: 1-2 sprints bem feito; alto blast radius (cruza backend + frontend + JWT + todo o scoping multi-tenant).
1. Por que estamos fazendo isso
1.1 Como o modelo está hoje
tenant_id + unit_id direto na tabela users. O scoping multi-tenant é feito pelos traits BelongsToTenant e BelongsToTenantAndUnit, que leem esses campos.
Funciona bem para funcionários fixos (recepcionista contratado da Clínica X, Unidade Y).
1.2 Onde quebra — 3 cenários reais do mercado
Cenário A — Radiologista terceirizado multi-clínica Dr. João lauda para Clínica Alfa (3 unidades) E Clínica Beta (2 unidades). → Hoje precisaria de 5 usuários diferentes (5 emails, 5 senhas). Cenário B — Médico em múltiplas unidades da mesma clínica Dra. Maria atende nas unidades Centro e Zona Sul da Clínica Alfa. → Hoje precisa de 2 usuários ou fica presa a uma só. Cenário C — Médico “coringa” da clínica Dr. Pedro pode laudar em qualquer unidade da Clínica Alfa (não só algumas específicas). → Hoje não existe esse “wildcard” — precisa escolher uma unit fixa.1.3 Por que isso é bloqueante para o produto
Médico terceirizado é o padrão no mercado brasileiro de radiologia. Sem suportar esse caso, o produto não fecha vendas reais.2. Solução proposta: memberships
2.1 Conceito em uma frase
Membership = um “crachá” que representa o vínculo de um usuário com uma clínica (opcionalmente, uma unidade específica). Um usuário pode ter N crachás. Quando ele faz login, escolhe qual crachá quer usar (ou o sistema escolhe automaticamente se for só um). Esse crachá define o contexto ativo — o que ele vê e pode fazer naquela sessão.2.2 Comparação antes / depois
Antes:2.3 Schema proposto (sujeito a decisões pendentes)
3. O que NÃO muda (fora de escopo)
Importante delimitar o que este refactor não toca:| Item | Estado | Por quê |
|---|---|---|
modality.unit_id | Continua FK única | Aparelho DICOM fica fisicamente em uma unidade — não faz sentido multi-unit |
study.tenant_id / study.unit_id | Continua fixo | Estudo nasceu em uma unidade específica; dados históricos imutáveis |
patient.tenant_id / patient.unit_id | Continua fixo | Idem |
report_template.tenant_id / .unit_id | Continua fixo | Template pertence à clínica/unidade onde foi criado |
is_super_admin em users | Provavelmente fica (ver §4.3) | Super admin é transversal à plataforma, não por vínculo |
| Fluxo de ingestão DICOM (Orthanc → webhook → worker) | Não muda | Resolve tenant/unit via AETitle da modality, não via user |
| OHIF viewer CORS proxy e token | Lógica de validação muda (ver §6.4), mas arquitetura não |
4. Decisões pendentes — BLOQUEIAM início do código
Cada item abaixo precisa ser decidido antes de escrever qualquer linha.4.1 Wildcard via unit_id NULL?
A favor: resolve o Cenário C elegantemente. Uma única linha na tabela, sem precisar criar N rows (um por unit) que ficam desatualizadas quando nova unit é criada.
Contra: complica o scoping SQL:
membership_units (many-to-many) listando explicitamente cada unit. Mas aí wildcard vira “N rows” que precisam ser re-sincronizadas.
Decisão pendente.
4.2 Role mora no membership ou no user?
Proposta: role mora no membership (não no user). Por quê: João pode serdoctor na Clínica Alfa e tenant_admin na Clínica Gamma (a própria dele). Com flag global no user, não dá pra expressar.
Consequência: remover users.is_tenant_admin. Adicionar memberships.role. O middleware tenant_admin passa a checar active_membership.role === 'tenant_admin'.
Decisão pendente, mas provável: role no membership.
4.3 Onde fica is_super_admin?
Proposta: continua em users. Super admin não precisa de membership — acessa tudo transversalmente.
Decisão pendente, mas provável: continuar em users.
4.4 UNIQUE (user_id, tenant_id, unit_id)?
Se sim: um user não pode ter 2 memberships para a mesma combinação tenant+unit. Simplifica tudo. Se não: permite coisas como “John é doctor nessa unit e tenant_admin nessa unit” (hierarquia de roles). Mais flexível, mais complexo. Proposta: UNIQUE sim. Se precisar de roles múltiplas, juntar em roles separadas via bitmap ou lista. Decisão pendente.4.5 Como guardar o “contexto ativo”?
Três opções:| Opção | Como funciona | Prós | Contras |
|---|---|---|---|
| A — JWT | Gravar active_membership_id (ou tenant+unit) no JWT no login / troca | Stateless, sem round-trip | Precisa reemitir JWT em toda troca de contexto |
| B — Server-side session (Redis) | Guardar contexto em Redis session:{user_id} | Troca de contexto barata | Deixa a API menos stateless |
| C — Cookie separado | Cookie pacs_active_context no navegador, backend lê via middleware | Simples | Vulnerável a manipulação se não assinado |
POST /api/context/switch que reemite token.
Decisão pendente.
4.6 JWT carrega memberships inteiras ou só contexto ativo?
- Só contexto ativo: JWT fica pequeno, mas a API
/meprecisa retornar a lista completa de memberships em todo login - Lista completa: JWT fica maior, mas o frontend tem tudo em memória
/api/me. Troca de contexto: frontend chama POST /api/context/switch {membership_id} → recebe JWT novo.
Decisão pendente.
4.7 “Herança” de permissões — tenant_admin tem acesso a todas as units?
Se João tem membershiptenant_admin na Clínica Alfa (sem especificar unit):
- Ele vê todos os estudos de todas as units da Alfa?
- Ele pode criar laudos em qualquer unit?
tenant_admin implica wildcard para o scoping de leitura.
Mas médico regular: precisa de membership explícito em cada unit (ou wildcard explícito).
Decisão pendente.
4.8 Forçar logout na troca de contexto?
- Sim (seguro): cada troca invalida JWT anterior, força reemissão. Perde estado local do frontend.
- Não (UX): troca transparente, estado preservado.
4.9 Super admin tem pseudo-membership?
Quando super admin acessa uma clínica para operar (ex.: criar user para ela), ele precisa de um contexto ativo? Ou opera num modo “god mode” transversal? Proposta: super admin em modo transversal por padrão (vê tudo). Se precisar impersonar uma clínica específica (ex.: para testar), usa um endpointPOST /admin/impersonate/{tenant_id} que seta contexto.
Decisão pendente.
4.10 Médico solicitante — CRM único global ou profile compartilhado?
Quando o mesmo médico solicitante aparece em várias clínicas, duas arquiteturas possíveis. Discussão completa em §12.6. Proposta: Solução A (CRM único global) no MVP; considerar migração para Solução B (profile compartilhado) se LGPD exigir consentimento explícito do médico antes de outra clínica vinculá-lo. Decisão pendente.5. Mapa de impacto — Backend
5.1 Tabelas (schema)
| Tabela | Ação |
|---|---|
users | Remover (ou manter read-only como “primário”): tenant_id, unit_id, is_tenant_admin. Manter: is_super_admin. Adicionar UNIQUE em crm se adotada Solução A de §12.6. |
memberships | Nova. Ver schema em §2.3. Roles suportadas: doctor, tenant_admin, referring_physician, receptionist. |
studies | Adicionar colunas: requesting_physician_name, requesting_physician_crm, requesting_physician_user_id (FK NULL) — ver §12.10 |
| Todas as demais | Sem mudança — já têm tenant_id/unit_id corretos |
- Cada user atual → cria 1 membership com
tenant_id+unit_idatuais +rolederivada deis_tenant_admin - Depois de validar, remove colunas antigas do
users
5.2 Models Eloquent
| Model | Mudanças |
|---|---|
User | - Remove $fillable de tenant/unit- Adiciona relacionamento hasMany(Membership)- Adiciona método activeMembership(), currentTenantId(), currentUnitId() lendo contexto |
Membership | Novo. Relacionamentos: belongsTo(User), belongsTo(Tenant), belongsTo(Unit) |
| Tenant, Unit, demais | Sem mudança |
5.3 Traits de scoping
| Trait | Mudança |
|---|---|
BelongsToTenant | Hoje lê $user->tenant_id. Passa a ler do contexto ativo (via resolver injetado). |
BelongsToTenantAndUnit | Idem. Trata wildcard (se contexto tem unit_id NULL, não filtra por unit). |
5.4 Controllers afetados
Todos que hoje dependem de scope continuam funcionando (scope lê contexto novo). Mudanças específicas:| Controller | Mudança |
|---|---|
AuthController | - login: emite JWT com contexto automático se user tem 1 membership; senão sem contexto (força switch)- refresh: preserva contexto atual |
UserController (admin) | - show/store/update expõem memberships no response- Criar user pode aceitar initial_memberships |
AdminController (novos endpoints) | - POST /admin/users/{id}/memberships- DELETE /admin/users/{id}/memberships/{mid}- PATCH /admin/users/{id}/memberships/{mid} (ativa/desativa) |
ReportController | Validar: user tem membership ativo no study.tenant_id + study.unit_id (ou wildcard) antes de criar laudo. |
ViewerTokenController | Idem: validar membership antes de emitir token. |
WebhookController / ProcessStudyMetadata | Não muda. Resolve tenant/unit via AETitle, não via user. |
Worker Python (worker/main.py) | Passa a indexar tag DICOM RequestingPhysician; opcionalmente auto-match por CRM — ver §12.5 |
Todos os que retornam me, $user->tenant, $user->unit | Substituir por activeContext() |
5.5 Middlewares
| Middleware | Mudança |
|---|---|
auth:api | Sem mudança |
tenant_admin | Reescrita — checa activeContext->role === 'tenant_admin' (não mais flag no user) |
super_admin | Sem mudança (lê is_super_admin no user) |
5.6 JWT
Claims hoje:sub, tenant_id, unit_id, is_super_admin, is_tenant_admin
Claims propostos:
active_* podem ser null; middleware trata isso.
Se user comum sem contexto escolhido: active_* null → frontend força seleção.
5.7 Endpoints novos
| Endpoint | O que faz |
|---|---|
GET /api/me (modifica) | Retorna user + lista de memberships ativas + contexto atual |
POST /api/context/switch | Body: { membership_id }. Valida que membership pertence ao user e está ativo. Reemite JWT com contexto novo. Invalida JWT anterior. |
GET /api/memberships (opcional) | Lista memberships do próprio usuário (redundante com /me, mas útil para recarregar sem refetch de tudo) |
POST /admin/users/{id}/memberships | Super admin cria membership para user. Body: { tenant_id, unit_id?, role } |
PATCH /admin/users/{id}/memberships/{mid} | Altera role ou ativa/desativa |
DELETE /admin/users/{id}/memberships/{mid} | Remove |
GET /admin/users/{id}/memberships | Lista memberships de um user (super admin) |
GET /admin/users/search?crm=X&name=Y | Busca global por CRM/nome (dados reduzidos) — usado pelo admin ao cadastrar médico solicitante (§12.7) |
PATCH /studies/{id}/requesting-physician | Admin vincula manualmente um user como solicitante de um estudo (§12.11) |
6. Mapa de impacto — Frontend
6.1 Auth store (Zustand)
Hoje:store/auth-store.ts guarda user com tenant_id/unit_id/is_tenant_admin.
Depois:
6.2 Fluxo pós-login
- User faz login com email/senha
- Backend responde com JWT + user + lista de memberships
- Se 0 memberships e não é super admin → erro (“usuário sem vínculos ativos”)
- Se 1 membership → backend já emite JWT com contexto setado; frontend entra direto
- Se >1 membership → JWT sem contexto; frontend mostra tela “Escolha sua clínica” antes de qualquer outra rota
- User escolhe →
POST /api/context/switch→ novo JWT → redirect para dashboard
6.3 Seletor de contexto (componente novo)
Combobox no header (ou modal) com:- Modal com lista de memberships
- Escolher →
POST /api/context/switch→ refresh da página / invalida cache React Query
6.4 Rotas protegidas
| Grupo de rotas | Checagem antiga | Checagem nova |
|---|---|---|
(super-admin)/* | user.is_super_admin | Mesma |
(tenant-admin)/* | user.is_tenant_admin | activeMembership.role === 'tenant_admin' |
(app)/* (médico) | qualquer user autenticado | qualquer user com activeMembership ou super_admin |
6.5 Componentes que leem tenant/unit do user
Rodar subagent para mapear todos os usos deuser.tenant_id, user.unit_id, user.is_tenant_admin. Substituir por activeMembership.*.
6.6 UI condicional por role (inclui referring_physician)
Componentes que precisam checaractiveMembership.role para esconder/mostrar ações:
- Detalhe do estudo: botões “Criar laudo”, “Aplicar preset”, “Assinar” só aparecem para role
doctor(outenant_admincom override) - Página “Meus exames solicitados” (nova): visível só para role
referring_physician - Sidebar: links de “Laudos”, “Templates”, “Presets” ocultos para
referring_physician
6.6 Persistência do contexto
O contexto ativo vive no JWT (no cookie httpOnly). Não precisa duplicar em localStorage — se o cookie for perdido, user refaz login e pode ser forçado a escolher de novo.7. Validações novas (regras de negócio)
Hoje o scope automaticamente impede acesso a recursos de outros tenants. Mas precisamos de validações novas onde o user faz uma ação cross-entity:7.1 Criar/editar laudo
- Antes de salvar:
user.membershipscontém um ativo emstudy.tenant_id+ (study.unit_idou wildcard)? - Se não: 403
7.2 Gerar token OHIF
- Antes de emitir: mesma validação acima.
- Se válido: token carrega user_id e contexto do study (não do user) — o nginx valida se o user pode ver aquele study.
7.3 Trocar de contexto
- Membership existe, pertence ao user, está ativo?
- Se não: 422
7.4 Super admin criar user
- Pode criar user sem membership inicial (super admin só).
- Tenant admin (depois do refactor, se ganhar permissão de criar users da própria clínica) só pode criar com memberships dentro do seu tenant.
8. Plano de fases
Fase 0 — Decisões e mapeamento (AGORA)
- Decidir todos os itens de §4 em discussão com stakeholder
- Rodar subagents Explore (ver §9) para mapa exaustivo de todos os arquivos afetados
- Atualizar este documento com mapa final
Fase 1 — Schema + migração de dados (não-disruptiva)
- Migration: cria tabela
memberships - Seeder/command: para cada user atual, cria 1 membership com dados atuais
- Não remove colunas antigas ainda (compat durante transição)
- Testes de integridade (count de users == count de memberships ativos)
Fase 2 — Backend core
- Model
Membership -
Userexpõememberships()relationship -
TenantContextservice lendo JWT - Reescreve traits
BelongsToTenant/BelongsToTenantAndUnit - JWT carrega
active_tenant_id/unit_id/roleno lugar dos antigos -
/api/meretorna memberships -
POST /api/context/switch - Middleware
tenant_adminreescrito - Validações de §7 em controllers afetados
- Suite de testes atualizada
Fase 3 — Frontend core
- Auth store atualizado
- Tela de escolha de contexto pós-login
- Seletor de contexto no header
- Substituir todos os usos de
user.tenant_id/unit_id/is_tenant_admin - Rotas protegidas atualizadas
Fase 4 — Admin CRUD de memberships
- Endpoints
POST/PATCH/DELETE /admin/users/{id}/memberships - UI no painel super admin para gerenciar memberships de cada user
Fase 5 — Cleanup
- Drop
users.tenant_id,users.unit_id,users.is_tenant_admin - Remover todo código que ainda lê esses campos (deve estar zero após Fase 3)
- Atualizar docs Mintlify (especialmente
admin.mdeauth.md)
Fase 6 — Validação em homolog + PR
- Rodar
HOMOLOG_CHECKLIST.mdinteiro focado em casos multi-membership - Adicionar casos novos ao checklist:
- Login com user de 1 membership (fluxo automático)
- Login com user de múltiplos memberships (tela de escolha)
- Troca de contexto preserva/não preserva estado
- Médico com wildcard vê todas as units do tenant
- tenant_admin num tenant, doctor em outro
- PR develop → main
9. Metodologia de mapeamento (antes de qualquer Edit)
Conformememory/feedback_refactor_methodology.md e docs/STORAGE_ARCHITECTURE.md §6:
Rodar subagents Explore em paralelo, um por área:
9.1 Subagents backend
| Subagent | Missão |
|---|---|
| Explore 1 — Scoping | Listar todos os models que usam BelongsToTenant ou BelongsToTenantAndUnit; todos os where('tenant_id') / where('unit_id') manuais em controllers e services |
| Explore 2 — User access | Listar todos os auth()->user()->tenant_id, $user->unit_id, $user->is_tenant_admin no código backend |
| Explore 3 — Endpoints com tenant/unit | Listar todas as rotas que retornam ou aceitam tenant_id / unit_id no body/response |
| Explore 4 — Middlewares e JWT | Arquivos de config JWT, guards, middlewares — mapear o que precisa mudar |
| Explore 5 — Testes | Todos os testes que setam tenant_id / unit_id direto no user de teste |
9.2 Subagents frontend
| Subagent | Missão |
|---|---|
| Explore 1 — Auth/store | Todos os usos do auth-store, user.tenant_id, user.unit_id, user.is_tenant_admin |
| Explore 2 — Route guards | Rotas com grupos (super-admin), (tenant-admin) e checagens de role |
| Explore 3 — API consumers | Hooks React Query que recebem/enviam tenant/unit; lib/api/client.ts e derivados |
| Explore 4 — Componentes UI | Componentes que mostram nome da clínica/unidade (sidebar, header, avatar etc.) |
9.3 Output esperado
Cada subagent retorna lista exaustiva de arquivos comfile:line e snippet. Consolida tudo num apêndice deste documento (§12) antes de começar Edit.
10. Discussões abertas (design decisions ainda não concluídas)
Coisas que não são decisões de implementação, mas merecem conversa antes de fechar:-
Nome “membership”? Alternativas:
user_access,user_tenant_link,affiliation,vinculo. Membership é padrão em SaaS (Stripe, GitHub, Auth0 usam). -
Tenant admin pode gerenciar memberships da própria clínica? Ou isso fica só com super admin?
- Se sim: precisa endpoint
POST /tenant/memberships(criar user convidado + membership na clínica do admin). - Se não: toda criação de membership vai via super admin (operação nossa).
- Ver
memory/project_rbac_refactor.md— decisão anterior foi super admin cuida de tudo. Mantém?
- Se sim: precisa endpoint
- Convite por email? Quando tenant admin (ou super admin) adicionar um médico que já existe no sistema (de outra clínica), manda email “você foi convidado para Clínica X”? Ou adiciona silenciosamente?
-
CRM e assinatura são do user ou do membership?
- Do user: um médico tem um CRM só (ou lista de CRMs por estado) e uma assinatura. Isso vai junto para qualquer clínica.
- Do membership: médico pode assinar diferente dependendo da clínica (raro, mas possível).
- Proposta: do user.
-
Logo e branding do laudo: hoje o PDF do laudo usa logo do
tenant(viaactiveMembership.tenant). Cross-check. - Notificações: futuras notificações (laudo pendente, etc.) — por user ou por membership? Provavelmente por membership (usuário pode querer desativar notificações de uma clínica específica).
- Histórico de membership: se um médico sai da clínica, soft delete do membership? Hard delete? Auditoria?
- Transição de nomenclatura no frontend: hoje a sidebar mostra “Tenant: Alfa | Unit: Centro”. Precisa mudar para “Trabalhando em: Alfa / Centro” ou similar, mais orgânico.
- Impacto em integrações futuras: se no futuro integrar com sistema externo (ex.: RIS da clínica), a chave é user ou membership?
- Multi-membership ativo simultâneo? Hoje propus “um contexto ativo por vez”. Em algum momento faria sentido “ver todos os estudos de todos os meus memberships consolidados”? (Provavelmente não — multi-tenant puro é por isolamento.)
11. Referências
CLAUDE.md— arquitetura atual (modeloUser, traits, JWT claims)memory/project_rbac_refactor.md— plano antigo da Fase 2 RBAC (este refactor substitui)memory/feedback_refactor_methodology.md— metodologia de blast radiusdocs/STORAGE_ARCHITECTURE.md §6— metodologia canônica aplicada a qualquer refactor grandedocs/api/admin.md— endpoints que precisarão mudar (users + novos para memberships)docs/api/auth.md— login/refresh/me precisarão mudardocs/HOMOLOG_CHECKLIST.md— casos novos a adicionar na Fase 6
12. Role referring_physician — Caso especial do solicitante externo
Adicionado em 2026-04-17 após discussão sobre médicos solicitantes que precisam ter acesso limitado aos estudos que pediram.
12.1 Quem é esse usuário
Médico externo à clínica que encaminhou um paciente para fazer um exame. Ex.: ortopedista que solicitou ressonância. Ele não trabalha na clínica que executa o exame, mas:- Precisa ver o exame dele (imagens + laudo)
- Não precisa laudar nada
- Não pode ver exames de outros médicos solicitantes
12.2 Três camadas distintas (importante não confundir)
Conceitos relacionados mas separados — mantenha cada um na cabeça:| Camada | O que é | Onde mora | Sempre existe? |
|---|---|---|---|
| Tag DICOM | Nome/CRM do solicitante que veio no exame | studies.requesting_physician_name, studies.requesting_physician_crm (denormalizado) | Sim (se a modalidade preencheu) |
| Vínculo ao user | FK opcional do estudo ao user do solicitante | studies.requesting_physician_user_id | Só se houver match por CRM ou vinculação manual |
| Membership com role | Vínculo do user com a clínica (acesso de login) | memberships com role = 'referring_physician' | Só se o solicitante tiver login no sistema |
12.3 O que a role permite
Pode:- Listar estudos onde ele é solicitante (scope adicional filtrando por
requesting_physician_user_id = user.id) - Abrir detalhe do estudo
- Abrir OHIF viewer dos estudos dele
- Ver laudos finalizados (não rascunhos) dos estudos dele
- Criar, editar ou finalizar laudos
- Ver estudos de outros solicitantes
- Acessar templates, presets, configurações da clínica
- Listar pacientes além dos vinculados aos estudos dele
- Nada de admin
12.4 Scope condicional por role
O traitBelongsToTenantAndUnit precisa de uma camada extra quando a role ativa é referring_physician:
12.5 Worker Python — indexação da tag DICOM
O worker hoje indexaStudyInstanceUID, StudyDate, PatientName. Precisa passar a indexar também:
crm normalizado idêntico, preencher studies.requesting_physician_user_id automaticamente.
12.6 Decisão pendente: CRM único ou profile compartilhado?
Duas soluções para o problema de “mesmo médico solicitante em várias clínicas”:Solução A — CRM único global (MVP recomendado)
users.crmUNIQUE (case-insensitive, normalizado)- Admin busca por CRM antes de criar
- Se existe: “esse médico já está no sistema; criar acesso de solicitante para sua clínica?” → cria apenas membership novo (não duplica user)
- Se não existe: cria user + membership
Solução B — Profile compartilhado
Tabelarequesting_physician_profiles separada (CRM, nome, UF). Estudos referenciam o profile. Cada profile pode (opcionalmente) ter user_id vinculado para login.
- Médico pode aparecer em N estudos de N clínicas sem precisar ter login
- Login/membership só é criado quando o próprio médico (ou admin autorizado) pede acesso
- Se o user é criado depois, vincula retroativamente ao profile → acesso automático a todos os estudos onde o profile aparece
12.7 Workflow do admin ao cadastrar solicitante
- Admin clica “Adicionar médico solicitante”
- Digita CRM (obrigatório) + nome
- Backend faz lookup global (
GET /admin/users/search?crm=X):- Match de CRM: mostra dados do user existente (nome, CRM, clínicas onde já tem acesso — omitindo detalhes sensíveis) → admin confirma → cria apenas membership (role:
referring_physician) - Sem match: formulário completo (email opcional, CRM, nome, UF) → cria user + membership
- Match de CRM: mostra dados do user existente (nome, CRM, clínicas onde já tem acesso — omitindo detalhes sensíveis) → admin confirma → cria apenas membership (role:
- (Futuro) Disparar email de boas-vindas se houver email cadastrado
12.8 Caso de borda: médico solicitante também lauda noutra clínica
Dr. João solicita exames na Clínica Alfa e é radiologista na Clínica Gamma. Seus memberships:12.9 Caso de borda: tag DICOM mal preenchida ou vazia
Se a modalidade não mandaRequestingPhysician, ou manda texto mal formatado sem CRM extraível:
studies.requesting_physician_namefica com o texto cru (pode ser vazio)studies.requesting_physician_user_idfica NULL (sem auto-match possível)- Admin pode vincular manualmente via UI
referring_physician. Precisa vínculo explícito (auto ou manual).
12.10 Implicações no schema
Adições à migration do refactor (complementam §2.3):12.11 Endpoints adicionais (além dos de §5.7)
| Endpoint | O que faz |
|---|---|
GET /admin/users/search?crm=X&name=Y | Busca global por CRM/nome. Retorna dados reduzidos (sem sensíveis). Usado pelo admin ao cadastrar solicitante. |
PATCH /studies/{id}/requesting-physician | Admin vincula manualmente um user como solicitante do estudo. Body: { user_id }. |
12.12 Impacto frontend — páginas novas / restritas
- Nova página “Meus exames solicitados” — visão do user com role
referring_physician; lista só os estudos dele - Detalhe do estudo (condicional): esconder botões “Criar laudo”, “Aplicar preset”, “Adicionar assinatura” quando role ativa é
referring_physician; mostrar “Ver laudo” (se finalizado) e “Abrir viewer” - Admin — “Adicionar solicitante”: form com CRM + busca prévia (§12.7)
- Admin — “Vincular solicitante ao estudo”: UI para vínculo manual quando auto-match falhou
- Sidebar: links restritos quando role é
referring_physician(sem “Laudos”, sem “Templates”, etc.)
12.13 Impacto na metodologia de mapeamento
Adicionar aos subagents da §9:| Subagent | Missão adicional |
|---|---|
| Explore backend — Worker | Mapear indexação do worker hoje; identificar onde adicionar RequestingPhysician |
| Explore backend — Studies | Todos os controllers/services que retornam dados de Study; onde adicionar os novos campos |
| Explore frontend — Estudo | Componente de detalhe do estudo; todos os botões/ações que precisam ser condicionais por role |
13. Apêndice — Mapa exaustivo de arquivos afetados
A preencher após rodar os subagents da §9.
