From dee89ccd844dd2df700739fd1dcf8aa1e4b97ae3 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 21 May 2026 11:26:21 -0300 Subject: [PATCH] registro profissional: campo livre quando tipo='outro' Quando o profissional seleciona "Outro" no Tipo de registro, agora aparece um campo adicional pra informar o nome do conselho/instituicao livre (ex: APM, ABRAP, conselhos nao-listados). Migration 20260521000009 adiciona profiles.professional_registration_ type_other (text livre). Aplicada e marcada no _db_migrations. ProfilePage e MelissaPerfil: - form.professional_registration_type_other no reactive - SELECT/UPDATE inclui a nova coluna - UI condicional: campo aparece SOMENTE quando type === 'outro' - Preview ao vivo usa type_other no lugar de 'outro' quando aplicavel - Save limpa type_other automaticamente quando troca pra outro tipo DocumentGenerate.service.loadTherapistData puxa type_other da query. Quando profile.type='outro', terapeuta_registro_tipo recebe o valor livre (ex: 'APM 12345/SP' em vez de 'outro 12345/SP'). terapeuta_crp (legacy compat) continua so preenchido quando type RAW = 'CRP'. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...00009_profiles_registration_type_other.sql | 21 ++++++++++++++ src/layout/melissa/MelissaPerfil.vue | 27 +++++++++++++++-- src/services/DocumentGenerate.service.js | 9 ++++-- src/views/pages/account/ProfilePage.vue | 29 +++++++++++++++++-- 4 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 database-novo/migrations/20260521000009_profiles_registration_type_other.sql diff --git a/database-novo/migrations/20260521000009_profiles_registration_type_other.sql b/database-novo/migrations/20260521000009_profiles_registration_type_other.sql new file mode 100644 index 0000000..86073c7 --- /dev/null +++ b/database-novo/migrations/20260521000009_profiles_registration_type_other.sql @@ -0,0 +1,21 @@ +-- ============================================================================ +-- Compliance CFP #5 — campo livre quando tipo de registro = 'outro' +-- ---------------------------------------------------------------------------- +-- Migration 20260521000003 adicionou professional_registration_type com CHECK +-- limitado a 8 valores (CRP/CRM/CRFa/CREFITO/CRESS/CRN/RMS/outro). Quando o +-- profissional escolhe 'outro', precisa informar qual conselho/instituição +-- (ex: associações privadas, conselhos não-listados). +-- +-- Esta migration adiciona professional_registration_type_other (text livre), +-- que só é preenchido quando type = 'outro'. +-- ============================================================================ + +BEGIN; + +ALTER TABLE public.profiles + ADD COLUMN IF NOT EXISTS professional_registration_type_other text; + +COMMENT ON COLUMN public.profiles.professional_registration_type_other IS + 'Nome livre do conselho/instituição quando professional_registration_type = ''outro''. Aparece em recibos/laudos no lugar do tipo padrão.'; + +COMMIT; diff --git a/src/layout/melissa/MelissaPerfil.vue b/src/layout/melissa/MelissaPerfil.vue index 75db3db..a0e62a6 100644 --- a/src/layout/melissa/MelissaPerfil.vue +++ b/src/layout/melissa/MelissaPerfil.vue @@ -160,6 +160,7 @@ const form = reactive({ social_x: '', // Registro profissional (CFP #5 — exigido pra emissão de recibos/laudos) professional_registration_type: '', + professional_registration_type_other: '', professional_registration_number: '', professional_registration_uf: '' }); @@ -372,7 +373,7 @@ async function loadProfile() { const { data: prof, error: pErr } = await supabase .from('profiles') .select( - 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, professional_registration_type, professional_registration_number, professional_registration_uf' + 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf' ) .eq('id', user.id) .maybeSingle(); @@ -392,6 +393,7 @@ async function loadProfile() { form.social_facebook = prof.social_facebook ?? ''; form.social_x = prof.social_x ?? ''; form.professional_registration_type = prof.professional_registration_type ?? ''; + form.professional_registration_type_other = prof.professional_registration_type_other ?? ''; form.professional_registration_number = prof.professional_registration_number ?? ''; form.professional_registration_uf = prof.professional_registration_uf ?? ''; customSocials.value = Array.isArray(prof.social_custom) ? prof.social_custom : []; @@ -463,6 +465,11 @@ async function saveAll() { social_custom: customSocials.value.filter((s) => s.name || s.url), // Registro profissional (CFP) — null se vazio professional_registration_type: String(form.professional_registration_type || '').trim() || null, + // type_other só preenchido quando type === 'outro' (limpa quando muda) + professional_registration_type_other: + form.professional_registration_type === 'outro' + ? (String(form.professional_registration_type_other || '').trim() || null) + : null, professional_registration_number: String(form.professional_registration_number || '').trim() || null, professional_registration_uf: String(form.professional_registration_uf || '').trim() || null }; @@ -894,6 +901,20 @@ onBeforeUnmount(() => { Conselho profissional ao qual você é vinculado. + +
+ + + + + Ex: APM, ABRAP, etc. +
+
{
Aparecerá nos documentos como: - {{ form.professional_registration_type }} + {{ form.professional_registration_type === 'outro' + ? (form.professional_registration_type_other || 'Conselho não informado') + : form.professional_registration_type }} {{ form.professional_registration_number }}{{ form.professional_registration_uf ? '/' + form.professional_registration_uf : '' }}
diff --git a/src/services/DocumentGenerate.service.js b/src/services/DocumentGenerate.service.js index 89128c5..142c2d0 100644 --- a/src/services/DocumentGenerate.service.js +++ b/src/services/DocumentGenerate.service.js @@ -148,7 +148,7 @@ export async function loadTherapistData() { const { data: profile } = await supabase .from('profiles') - .select('full_name, phone, professional_registration_type, professional_registration_number, professional_registration_uf') + .select('full_name, phone, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf') .eq('id', ownerId) .single(); @@ -156,7 +156,10 @@ export async function loadTherapistData() { const { data: userData } = await supabase.auth.getUser(); const email = userData?.user?.email || ''; - const tipo = profile?.professional_registration_type || ''; + const tipoRaw = profile?.professional_registration_type || ''; + const tipoOther = profile?.professional_registration_type_other || ''; + // Quando type='outro', usa o nome livre do conselho/instituição + const tipo = tipoRaw === 'outro' ? tipoOther : tipoRaw; const numero = profile?.professional_registration_number || ''; const uf = profile?.professional_registration_uf || ''; const registro = formatRegistroProfissional({ tipo, numero, uf }); @@ -173,7 +176,7 @@ export async function loadTherapistData() { // o número/UF (sem prefixo) pra não duplicar com o "CRP" já no HTML. // Quando o registro não é CRP, retorna vazio (template visualmente errado // pede pra usar {{terapeuta_registro}}). - terapeuta_crp: tipo === 'CRP' ? (uf ? `${numero}/${uf}` : numero) : '' + terapeuta_crp: tipoRaw === 'CRP' ? (uf ? `${numero}/${uf}` : numero) : '' }; } diff --git a/src/views/pages/account/ProfilePage.vue b/src/views/pages/account/ProfilePage.vue index e23953f..c28734b 100644 --- a/src/views/pages/account/ProfilePage.vue +++ b/src/views/pages/account/ProfilePage.vue @@ -105,6 +105,7 @@ const form = reactive({ // Registro profissional (CFP #5 — exigido pra emissão de recibos/laudos) professional_registration_type: '', + professional_registration_type_other: '', professional_registration_number: '', professional_registration_uf: '', @@ -634,7 +635,7 @@ async function loadProfile() { const { data: prof, error: pErr } = await supabase .from('profiles') .select( - 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_number, professional_registration_uf' + 'role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf' ) .eq('id', user.id) .maybeSingle(); @@ -655,6 +656,7 @@ async function loadProfile() { form.social_x = prof.social_x ?? ''; form.professional_registration_type = prof.professional_registration_type ?? ''; + form.professional_registration_type_other = prof.professional_registration_type_other ?? ''; form.professional_registration_number = prof.professional_registration_number ?? ''; form.professional_registration_uf = prof.professional_registration_uf ?? ''; @@ -738,6 +740,11 @@ async function saveAll() { // Registro profissional (CFP) — null se vazio professional_registration_type: String(form.professional_registration_type || '').trim() || null, + // type_other só preenchido quando type === 'outro' (limpa quando muda) + professional_registration_type_other: + form.professional_registration_type === 'outro' + ? (String(form.professional_registration_type_other || '').trim() || null) + : null, professional_registration_number: String(form.professional_registration_number || '').trim() || null, professional_registration_uf: String(form.professional_registration_uf || '').trim() || null }; @@ -747,7 +754,7 @@ async function saveAll() { .update(profilePayload) .eq('id', userId.value) .select( - 'id, role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_number, professional_registration_uf, updated_at' + 'id, role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf, updated_at' ) .single(); @@ -1173,6 +1180,20 @@ onBeforeUnmount(() => {
Conselho profissional ao qual você é vinculado.
+ +
+ + + + +
Ex: APM (Associação Paulista de Medicina), ABRAP, etc.
+
+
@@ -1212,7 +1233,9 @@ onBeforeUnmount(() => {
Aparecerá nos documentos como: - {{ form.professional_registration_type }} + {{ form.professional_registration_type === 'outro' + ? (form.professional_registration_type_other || 'Conselho não informado') + : form.professional_registration_type }} {{ form.professional_registration_number }}{{ form.professional_registration_uf ? '/' + form.professional_registration_uf : '' }}