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) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
@@ -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(() => {
|
||||
<small class="mpr-hint">Conselho profissional ao qual você é vinculado.</small>
|
||||
</div>
|
||||
|
||||
<!-- Campo livre quando tipo='outro' -->
|
||||
<div v-if="form.professional_registration_type === 'outro'" class="mpr-field mpr-field--half">
|
||||
<FloatLabel variant="on">
|
||||
<InputText
|
||||
id="mpr_reg_type_other"
|
||||
v-model="form.professional_registration_type_other"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
<label for="mpr_reg_type_other">Nome do conselho/instituição *</label>
|
||||
</FloatLabel>
|
||||
<small class="mpr-hint">Ex: APM, ABRAP, etc.</small>
|
||||
</div>
|
||||
|
||||
<div class="mpr-field mpr-field--half">
|
||||
<FloatLabel variant="on">
|
||||
<InputText
|
||||
@@ -930,7 +951,9 @@ onBeforeUnmount(() => {
|
||||
<div class="mpr-preview-box">
|
||||
<span class="mpr-preview-label">Aparecerá nos documentos como:</span>
|
||||
<strong class="mpr-preview-value">
|
||||
{{ 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 : '' }}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
@@ -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) : ''
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
<div class="mt-1.5 text-[1rem] text-[var(--text-color-secondary)]">Conselho profissional ao qual você é vinculado.</div>
|
||||
</div>
|
||||
|
||||
<!-- Campo livre quando tipo='outro' -->
|
||||
<div v-if="form.professional_registration_type === 'outro'" class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<InputText
|
||||
id="prof_registration_type_other"
|
||||
v-model="form.professional_registration_type_other"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
<label for="prof_registration_type_other">Nome do conselho/instituição <span class="text-red-400">*</span></label>
|
||||
</FloatLabel>
|
||||
<div class="mt-1.5 text-[1rem] text-[var(--text-color-secondary)]">Ex: APM (Associação Paulista de Medicina), ABRAP, etc.</div>
|
||||
</div>
|
||||
|
||||
<!-- Número -->
|
||||
<div class="col-span-7 md:col-span-3">
|
||||
<FloatLabel variant="on">
|
||||
@@ -1212,7 +1233,9 @@ onBeforeUnmount(() => {
|
||||
<div class="rounded-md border border-[var(--c-border)] bg-[var(--c-dim)] p-3 text-[0.9rem]">
|
||||
<span class="text-[var(--text-color-secondary)] mr-2">Aparecerá nos documentos como:</span>
|
||||
<strong class="text-[var(--text-color)]">
|
||||
{{ 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 : '' }}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user