feat(protese): módulo de OS de prótese (clínica ↔ laboratório/protético)
- backend: tabelas protese_os/_evento/_foto/_produtos + endpoints /api/protese/* (laboratorios, os, bancada, status, foto/raw assinado, produtos); catálogo do protético; custo de lab → DESPESA na entrega (idempotente, estorno no delete) + garantia com foto. - Acesso: helper proteticoAcessivelPelaClinica (interno por vínculo OU diretório) valida o POST e o catálogo; catálogos passam por tenantGuard; correção do label da notificação (tipo resolvido). - Frontend: ProteseView (Bancada do protético / Minhas OS da clínica) + rota /proteses + item de menu PRÓTESES no Sidebar (dentista/protético/funcionário). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -742,6 +742,79 @@ export const HybridBackend = {
|
||||
await apiFetch(`${API_URL}/glosas/${id}?clinicaId=${encodeURIComponent(clinicaId)}`, { method: 'DELETE' });
|
||||
},
|
||||
|
||||
// ── Próteses · Ordem de Serviço (clínica ↔ laboratório/protético) ───────────
|
||||
getProteseLaboratorios: async () => {
|
||||
const clinicaId = HybridBackend.getActiveWorkspace()?.id || '';
|
||||
const res = await apiFetch(`${API_URL}/protese/laboratorios?clinicaId=${encodeURIComponent(clinicaId)}`);
|
||||
return await res.json();
|
||||
},
|
||||
getProteseOS: async (opts: { status?: string; paciente_id?: string } = {}) => {
|
||||
const clinicaId = HybridBackend.getActiveWorkspace()?.id || '';
|
||||
const qs = new URLSearchParams({ clinicaId });
|
||||
if (opts.status) qs.set('status', opts.status);
|
||||
if (opts.paciente_id) qs.set('paciente_id', opts.paciente_id);
|
||||
const res = await apiFetch(`${API_URL}/protese/os?${qs.toString()}`);
|
||||
return await res.json();
|
||||
},
|
||||
getProteseBancada: async (status?: string) => {
|
||||
const qs = status ? `?status=${encodeURIComponent(status)}` : '';
|
||||
const res = await apiFetch(`${API_URL}/protese/bancada${qs}`);
|
||||
return await res.json();
|
||||
},
|
||||
getProteseOSDetalhe: async (id: string) => {
|
||||
const res = await apiFetch(`${API_URL}/protese/os/${id}`);
|
||||
return await res.json();
|
||||
},
|
||||
createProteseOS: async (payload: any) => {
|
||||
const clinicaId = HybridBackend.getActiveWorkspace()?.id || '';
|
||||
const res = await apiFetch(`${API_URL}/protese/os?clinicaId=${encodeURIComponent(clinicaId)}`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao criar OS');
|
||||
return await res.json();
|
||||
},
|
||||
setProteseOSStatus: async (id: string, status: string, observacao?: string) => {
|
||||
const res = await apiFetch(`${API_URL}/protese/os/${id}/status`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status, observacao }) });
|
||||
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao mudar status');
|
||||
return await res.json();
|
||||
},
|
||||
uploadProteseFoto: async (id: string, file: File) => {
|
||||
const fd = new FormData();
|
||||
fd.append('foto', file);
|
||||
const res = await apiFetch(`${API_URL}/protese/os/${id}/foto`, { method: 'POST', body: fd });
|
||||
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao anexar foto');
|
||||
return await res.json();
|
||||
},
|
||||
deleteProteseOS: async (id: string) => {
|
||||
const clinicaId = HybridBackend.getActiveWorkspace()?.id || '';
|
||||
await apiFetch(`${API_URL}/protese/os/${id}?clinicaId=${encodeURIComponent(clinicaId)}`, { method: 'DELETE' });
|
||||
},
|
||||
// Catálogo do protético (preço fixo + garantia)
|
||||
getProteseProdutos: async () => {
|
||||
const res = await apiFetch(`${API_URL}/protese/produtos`);
|
||||
return await res.json();
|
||||
},
|
||||
getLaboratorioProdutos: async (proteticoId: string) => {
|
||||
const clinicaId = HybridBackend.getActiveWorkspace()?.id || '';
|
||||
const res = await apiFetch(`${API_URL}/protese/laboratorios/${proteticoId}/produtos?clinicaId=${encodeURIComponent(clinicaId)}`);
|
||||
return await res.json();
|
||||
},
|
||||
createProteseProduto: async (payload: { nome: string; preco?: number; garantia_meses?: number }) => {
|
||||
const res = await apiFetch(`${API_URL}/protese/produtos`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao criar produto');
|
||||
return await res.json();
|
||||
},
|
||||
updateProteseProduto: async (id: string, payload: any) => {
|
||||
const res = await apiFetch(`${API_URL}/protese/produtos/${id}`, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || 'Erro ao salvar produto');
|
||||
return await res.json();
|
||||
},
|
||||
deleteProteseProduto: async (id: string) => {
|
||||
await apiFetch(`${API_URL}/protese/produtos/${id}`, { method: 'DELETE' });
|
||||
},
|
||||
|
||||
// ── GTO (builder → finaliza → lança no financeiro) ──────────────────────────
|
||||
createGtoBuilder: async (payload: { pacienteId: string; dentistaId: string; credenciadaId: string }) => {
|
||||
const res = await apiFetch(`${API_URL}/gto-builder`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||
|
||||
Reference in New Issue
Block a user