feat(protese): Fase 4 — PF/PJ + CNPJ + Equipe (técnicos do laboratório)

Provedor de Prótese, Fase 4 (doc/PROVEDOR-PROTESE-DECISAO.md):

- PF/PJ: clinicas.pessoa_tipo (individual | laboratório) + documento (CPF/CNPJ) editáveis.
  Tela "Laboratório & Equipe" (lab-equipe). GET/PATCH /protese/lab.
- Equipe: novo vinculos.role='tecnico_protese'. Dono convida por e-mail (ensureUsuarioPorEmail
  → credencial temporária). GET/POST/DELETE /protese/lab/equipe (só dono).
- Acesso estendido: proteseOsAccesso reconhece o técnico (vínculo no laboratorio_id) e a bancada
  passa a usar labDoUsuario (dono OU técnico). Técnico opera a produção; menu reduzido e sem
  clientes/financeiro/equipe (endpoints retornam vazio/403). Gestão é exclusiva do dono.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-06-24 05:40:10 +02:00
parent c8d679eb70
commit ae48feaa8a
6 changed files with 243 additions and 9 deletions
+24
View File
@@ -1095,6 +1095,30 @@ export const HybridBackend = {
const res = await apiFetch(`${API_URL}/protese/lab/pagamentos`);
return await res.json().catch(() => []);
},
// Fase 4: dados do laboratório (PF/PJ, CPF/CNPJ) + equipe de técnicos
getLabDados: async (): Promise<any> => {
const res = await apiFetch(`${API_URL}/protese/lab`);
return await res.json().catch(() => ({}));
},
updateLabDados: async (body: { nome_fantasia: string; documento?: string; pessoa_tipo?: 'PF' | 'PJ' }) => {
const res = await apiFetch(`${API_URL}/protese/lab`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao salvar');
return await res.json();
},
getLabEquipe: async (): Promise<any[]> => {
const res = await apiFetch(`${API_URL}/protese/lab/equipe`);
return await res.json().catch(() => []);
},
addLabTecnico: async (body: { email: string; nome?: string }) => {
const res = await apiFetch(`${API_URL}/protese/lab/equipe`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao adicionar técnico');
return await res.json();
},
removeLabTecnico: async (userId: string) => {
const res = await apiFetch(`${API_URL}/protese/lab/equipe/${userId}`, { method: 'DELETE' });
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao remover');
return await res.json();
},
// Agenda de uma sala (reservas + bloqueios de manutenção) — usado pelo operador da unidade.
getSalaReservas: async (salaId: string): Promise<any[]> => {