-- Views
-- Gerado automaticamente em 2026-05-11T16:53:50.932Z
-- Total: 29

CREATE VIEW public.audit_log_unified WITH (security_invoker='true') AS
 SELECT ('audit:'::text || (al.id)::text) AS uid,
    al.tenant_id,
    al.user_id,
    al.entity_type,
    al.entity_id,
    al.action,
        CASE al.action
            WHEN 'insert'::text THEN ('Criou '::text || al.entity_type)
            WHEN 'update'::text THEN (('Alterou '::text || al.entity_type) || COALESCE(((' ('::text || array_to_string(al.changed_fields, ', '::text)) || ')'::text), ''::text))
            WHEN 'delete'::text THEN ('Excluiu '::text || al.entity_type)
            ELSE NULL::text
        END AS description,
    al.created_at AS occurred_at,
    'audit_logs'::text AS source,
    jsonb_build_object('old_values', al.old_values, 'new_values', al.new_values, 'changed_fields', al.changed_fields) AS details
   FROM public.audit_logs al
UNION ALL
 SELECT ('doc_access:'::text || (dal.id)::text) AS uid,
    dal.tenant_id,
    dal.user_id,
    'document'::text AS entity_type,
    (dal.documento_id)::text AS entity_id,
    dal.acao AS action,
        CASE dal.acao
            WHEN 'visualizou'::text THEN 'Visualizou documento'::text
            WHEN 'baixou'::text THEN 'Baixou documento'::text
            WHEN 'imprimiu'::text THEN 'Imprimiu documento'::text
            WHEN 'compartilhou'::text THEN 'Compartilhou documento'::text
            WHEN 'assinou'::text THEN 'Assinou documento'::text
            ELSE dal.acao
        END AS description,
    dal.acessado_em AS occurred_at,
    'document_access_logs'::text AS source,
    jsonb_build_object('ip', (dal.ip)::text, 'user_agent', dal.user_agent) AS details
   FROM public.document_access_logs dal
UNION ALL
 SELECT ('psh:'::text || (psh.id)::text) AS uid,
    psh.tenant_id,
    psh.alterado_por AS user_id,
    'patient_status'::text AS entity_type,
    (psh.patient_id)::text AS entity_id,
    'status_change'::text AS action,
    (((('Status do paciente: '::text || COALESCE(psh.status_anterior, '—'::text)) || ' → '::text) || psh.status_novo) || COALESCE(((' ('::text || psh.motivo) || ')'::text), ''::text)) AS description,
    psh.alterado_em AS occurred_at,
    'patient_status_history'::text AS source,
    jsonb_build_object('status_anterior', psh.status_anterior, 'status_novo', psh.status_novo, 'motivo', psh.motivo, 'encaminhado_para', psh.encaminhado_para, 'data_saida', psh.data_saida) AS details
   FROM public.patient_status_history psh
UNION ALL
 SELECT ('notif:'::text || (nl.id)::text) AS uid,
    nl.tenant_id,
    nl.owner_id AS user_id,
    'notification'::text AS entity_type,
    (nl.patient_id)::text AS entity_id,
    nl.status AS action,
    (((('Notificação '::text || nl.channel) || ' '::text) || nl.status) || COALESCE((' para '::text || nl.recipient_address), ''::text)) AS description,
    nl.created_at AS occurred_at,
    'notification_logs'::text AS source,
    jsonb_build_object('channel', nl.channel, 'template_key', nl.template_key, 'status', nl.status, 'provider', nl.provider, 'failure_reason', nl.failure_reason) AS details
   FROM public.notification_logs nl
UNION ALL
 SELECT ('addon:'::text || (at.id)::text) AS uid,
    at.tenant_id,
    at.admin_user_id AS user_id,
    'addon_transaction'::text AS entity_type,
    (at.id)::text AS entity_id,
    at.type AS action,
        CASE at.type
            WHEN 'purchase'::text THEN ((('Compra de '::text || at.amount) || ' créditos de '::text) || at.addon_type)
            WHEN 'consumption'::text THEN ((('Consumo de '::text || abs(at.amount)) || ' crédito(s) '::text) || at.addon_type)
            WHEN 'adjustment'::text THEN ('Ajuste de créditos '::text || at.addon_type)
            WHEN 'refund'::text THEN ((('Reembolso de '::text || abs(at.amount)) || ' créditos '::text) || at.addon_type)
            ELSE ((at.type || ' '::text) || at.addon_type)
        END AS description,
    at.created_at AS occurred_at,
    'addon_transactions'::text AS source,
    jsonb_build_object('addon_type', at.addon_type, 'amount', at.amount, 'balance_after', at.balance_after, 'price_cents', at.price_cents, 'payment_reference', at.payment_reference) AS details
   FROM public.addon_transactions at;

CREATE VIEW public.conversation_threads WITH (security_invoker='true') AS
 WITH base AS (
         SELECT cm.id,
            cm.tenant_id,
            cm.patient_id,
            cm.channel,
            cm.body,
            cm.direction,
            cm.kanban_status,
            cm.read_at,
            cm.created_at,
                CASE
                    WHEN (cm.direction = 'inbound'::text) THEN cm.from_number
                    ELSE cm.to_number
                END AS contact_number,
            COALESCE((cm.patient_id)::text, ('anon:'::text || COALESCE(
                CASE
                    WHEN (cm.direction = 'inbound'::text) THEN cm.from_number
                    ELSE cm.to_number
                END, 'unknown'::text))) AS thread_key
           FROM public.conversation_messages cm
        ), latest AS (
         SELECT DISTINCT ON (base.tenant_id, base.thread_key) base.tenant_id,
            base.thread_key,
            base.patient_id,
            base.channel,
            base.contact_number,
            base.body AS last_message_body,
            base.direction AS last_message_direction,
            base.kanban_status,
            base.created_at AS last_message_at
           FROM base
          ORDER BY base.tenant_id, base.thread_key, base.created_at DESC
        ), counts AS (
         SELECT base.tenant_id,
            base.thread_key,
            count(*) AS message_count,
            count(*) FILTER (WHERE ((base.direction = 'inbound'::text) AND (base.read_at IS NULL))) AS unread_count
           FROM base
          GROUP BY base.tenant_id, base.thread_key
        )
 SELECT l.tenant_id,
    l.thread_key,
    l.patient_id,
    p.nome_completo AS patient_name,
    l.contact_number,
    l.channel,
    c.message_count,
    c.unread_count,
    l.last_message_at,
    l.last_message_body,
    l.last_message_direction,
    l.kanban_status,
    ca.assigned_to,
    ca.assigned_at
   FROM (((latest l
     JOIN counts c ON (((c.tenant_id = l.tenant_id) AND (c.thread_key = l.thread_key))))
     LEFT JOIN public.patients p ON ((p.id = l.patient_id)))
     LEFT JOIN public.conversation_assignments ca ON (((ca.tenant_id = l.tenant_id) AND (ca.thread_key = l.thread_key))));

CREATE VIEW public.current_tenant_id AS
 SELECT current_setting('request.jwt.claim.tenant_id'::text, true) AS current_setting;

CREATE VIEW public.owner_feature_entitlements AS
 WITH base AS (
         SELECT s.user_id AS owner_id,
            f.key AS feature_key,
            pf.limits,
            'plan'::text AS source
           FROM ((public.subscriptions s
             JOIN public.plan_features pf ON (((pf.plan_id = s.plan_id) AND (pf.enabled = true))))
             JOIN public.features f ON ((f.id = pf.feature_id)))
          WHERE ((s.status = 'active'::text) AND (s.user_id IS NOT NULL))
        UNION ALL
         SELECT tm.owner_id,
            f.key AS feature_key,
            mf.limits,
            'module'::text AS source
           FROM (((public.tenant_modules tm
             JOIN public.modules m ON (((m.id = tm.module_id) AND (m.is_active = true))))
             JOIN public.module_features mf ON (((mf.module_id = m.id) AND (mf.enabled = true))))
             JOIN public.features f ON ((f.id = mf.feature_id)))
          WHERE ((tm.status = 'active'::text) AND (tm.owner_id IS NOT NULL))
        )
 SELECT owner_id,
    feature_key,
    array_agg(DISTINCT source) AS sources,
    jsonb_agg(limits) FILTER (WHERE (limits IS NOT NULL)) AS limits_list
   FROM base
  GROUP BY owner_id, feature_key;

CREATE VIEW public.subscription_intents AS
 SELECT t.id,
    t.user_id,
    t.created_by_user_id,
    t.email,
    t.plan_id,
    t.plan_key,
    t."interval",
    t.amount_cents,
    t.currency,
    t.status,
    t.source,
    t.notes,
    t.created_at,
    t.paid_at,
    t.tenant_id,
    t.subscription_id,
    'clinic'::text AS plan_target
   FROM public.subscription_intents_tenant t
UNION ALL
 SELECT p.id,
    p.user_id,
    p.created_by_user_id,
    p.email,
    p.plan_id,
    p.plan_key,
    p."interval",
    p.amount_cents,
    p.currency,
    p.status,
    p.source,
    p.notes,
    p.created_at,
    p.paid_at,
    NULL::uuid AS tenant_id,
    p.subscription_id,
    'therapist'::text AS plan_target
   FROM public.subscription_intents_personal p;

CREATE VIEW public.v_auth_users_public AS
 SELECT id AS user_id,
    email,
    created_at,
    last_sign_in_at
   FROM auth.users u;

CREATE VIEW public.v_cashflow_projection WITH (security_invoker='on') AS
 SELECT gs.mes,
    to_char(gs.mes, 'YYYY-MM'::text) AS mes_label,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric) AS receitas_projetadas,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric) AS despesas_projetadas,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = 'pending'::text))), (0)::numeric) AS receitas_pendentes,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = 'overdue'::text))), (0)::numeric) AS receitas_vencidas,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = 'pending'::text))), (0)::numeric) AS despesas_pendentes,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = 'overdue'::text))), (0)::numeric) AS despesas_vencidas,
    (COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric) - COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric)) AS saldo_projetado,
    count(fr.id) FILTER (WHERE (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text]))) AS count_registros
   FROM (generate_series(((date_trunc('month'::text, (CURRENT_DATE)::timestamp with time zone))::date)::timestamp with time zone, (((date_trunc('month'::text, (CURRENT_DATE)::timestamp with time zone) + '5 mons'::interval))::date)::timestamp with time zone, '1 mon'::interval) gs(mes)
     LEFT JOIN public.financial_records fr ON (((fr.deleted_at IS NULL) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])) AND ((date_trunc('month'::text, (fr.due_date)::timestamp with time zone))::date = gs.mes))))
  GROUP BY gs.mes
  ORDER BY gs.mes;

CREATE VIEW public.v_commitment_totals AS
 SELECT c.tenant_id,
    c.id AS commitment_id,
    (COALESCE(sum(l.minutes), (0)::bigint))::integer AS total_minutes
   FROM (public.determined_commitments c
     LEFT JOIN public.commitment_time_logs l ON ((l.commitment_id = c.id)))
  GROUP BY c.tenant_id, c.id;

CREATE VIEW public.v_patient_engajamento WITH (security_invoker='on') AS
 WITH sessoes AS (
         SELECT ae.patient_id,
            ae.tenant_id,
            count(*) FILTER (WHERE (ae.status = 'realizado'::public.status_evento_agenda)) AS total_realizadas,
            count(*) FILTER (WHERE (ae.status = ANY (ARRAY['realizado'::public.status_evento_agenda, 'cancelado'::public.status_evento_agenda, 'faltou'::public.status_evento_agenda]))) AS total_marcadas,
            count(*) FILTER (WHERE (ae.status = 'faltou'::public.status_evento_agenda)) AS total_faltas,
            max(ae.inicio_em) FILTER (WHERE (ae.status = 'realizado'::public.status_evento_agenda)) AS ultima_sessao_em,
            min(ae.inicio_em) FILTER (WHERE (ae.status = 'realizado'::public.status_evento_agenda)) AS primeira_sessao_em,
            count(*) FILTER (WHERE ((ae.status = 'realizado'::public.status_evento_agenda) AND (ae.inicio_em >= (now() - '30 days'::interval)))) AS sessoes_ultimo_mes
           FROM public.agenda_eventos ae
          WHERE (ae.patient_id IS NOT NULL)
          GROUP BY ae.patient_id, ae.tenant_id
        ), financeiro AS (
         SELECT fr.patient_id,
            fr.tenant_id,
            COALESCE(sum(fr.final_amount) FILTER (WHERE (fr.status = 'paid'::text)), (0)::numeric) AS total_pago,
            COALESCE(avg(fr.final_amount) FILTER (WHERE (fr.status = 'paid'::text)), (0)::numeric) AS ticket_medio,
            count(*) FILTER (WHERE ((fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])) AND (fr.due_date < now()))) AS cobr_vencidas,
            count(*) FILTER (WHERE (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text]))) AS cobr_pendentes,
            count(*) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = 'paid'::text))) AS cobr_pagas
           FROM public.financial_records fr
          WHERE ((fr.patient_id IS NOT NULL) AND (fr.deleted_at IS NULL))
          GROUP BY fr.patient_id, fr.tenant_id
        )
 SELECT p.id AS patient_id,
    p.tenant_id,
    p.nome_completo,
    p.status,
    p.risco_elevado,
    COALESCE(s.total_realizadas, (0)::bigint) AS total_sessoes,
    COALESCE(s.sessoes_ultimo_mes, (0)::bigint) AS sessoes_ultimo_mes,
    s.primeira_sessao_em,
    s.ultima_sessao_em,
    (EXTRACT(day FROM (now() - s.ultima_sessao_em)))::integer AS dias_sem_sessao,
        CASE
            WHEN (COALESCE(s.total_marcadas, (0)::bigint) = 0) THEN NULL::numeric
            ELSE round((((s.total_realizadas)::numeric / (s.total_marcadas)::numeric) * (100)::numeric), 1)
        END AS taxa_comparecimento,
    COALESCE(f.total_pago, (0)::numeric) AS ltv_total,
    round(COALESCE(f.ticket_medio, (0)::numeric), 2) AS ticket_medio,
    COALESCE(f.cobr_vencidas, (0)::bigint) AS cobr_vencidas,
    COALESCE(f.cobr_pagas, (0)::bigint) AS cobr_pagas,
        CASE
            WHEN (COALESCE((f.cobr_pagas + f.cobr_vencidas), (0)::bigint) = 0) THEN NULL::numeric
            ELSE round((((f.cobr_pagas)::numeric / ((f.cobr_pagas + f.cobr_vencidas))::numeric) * (100)::numeric), 1)
        END AS taxa_pagamentos_dia,
    round(LEAST((100)::numeric, COALESCE(((
        CASE
            WHEN (COALESCE(s.total_marcadas, (0)::bigint) = 0) THEN (50)::numeric
            ELSE LEAST((50)::numeric, (((s.total_realizadas)::numeric / (s.total_marcadas)::numeric) * (50)::numeric))
        END +
        CASE
            WHEN (COALESCE((f.cobr_pagas + f.cobr_vencidas), (0)::bigint) = 0) THEN (30)::numeric
            ELSE LEAST((30)::numeric, (((f.cobr_pagas)::numeric / ((f.cobr_pagas + f.cobr_vencidas))::numeric) * (30)::numeric))
        END) + (
        CASE
            WHEN (s.ultima_sessao_em IS NULL) THEN 0
            WHEN (EXTRACT(day FROM (now() - s.ultima_sessao_em)) <= (14)::numeric) THEN 20
            WHEN (EXTRACT(day FROM (now() - s.ultima_sessao_em)) <= (30)::numeric) THEN 15
            WHEN (EXTRACT(day FROM (now() - s.ultima_sessao_em)) <= (60)::numeric) THEN 8
            ELSE 0
        END)::numeric), (0)::numeric)), 0) AS engajamento_score,
        CASE
            WHEN (s.primeira_sessao_em IS NULL) THEN NULL::integer
            ELSE (EXTRACT(day FROM (now() - s.primeira_sessao_em)))::integer
        END AS duracao_tratamento_dias
   FROM ((public.patients p
     LEFT JOIN sessoes s ON (((s.patient_id = p.id) AND (s.tenant_id = p.tenant_id))))
     LEFT JOIN financeiro f ON (((f.patient_id = p.id) AND (f.tenant_id = p.tenant_id))));

CREATE VIEW public.v_patient_groups_with_counts AS
 SELECT pg.id,
    pg.nome,
    pg.cor,
    pg.owner_id,
    pg.is_system,
    pg.is_active,
    pg.created_at,
    pg.updated_at,
    (COALESCE(count(pgp.patient_id), (0)::bigint))::integer AS patients_count
   FROM (public.patient_groups pg
     LEFT JOIN public.patient_group_patient pgp ON ((pgp.patient_group_id = pg.id)))
  GROUP BY pg.id, pg.nome, pg.cor, pg.owner_id, pg.is_system, pg.is_active, pg.created_at, pg.updated_at;

CREATE VIEW public.v_patients_risco WITH (security_invoker='on') AS
 SELECT p.id,
    p.tenant_id,
    p.nome_completo,
    p.status,
    p.risco_elevado,
    p.risco_nota,
    p.risco_sinalizado_em,
    e.dias_sem_sessao,
    e.engajamento_score,
    e.taxa_comparecimento,
        CASE
            WHEN p.risco_elevado THEN 'risco_sinalizado'::text
            WHEN ((COALESCE(e.dias_sem_sessao, 999) > 30) AND (p.status = 'Ativo'::text)) THEN 'sem_sessao_30d'::text
            WHEN (COALESCE(e.taxa_comparecimento, (100)::numeric) < (60)::numeric) THEN 'baixo_comparecimento'::text
            WHEN (COALESCE(e.cobr_vencidas, (0)::bigint) > 0) THEN 'cobranca_vencida'::text
            ELSE 'ok'::text
        END AS alerta_tipo
   FROM (public.patients p
     JOIN public.v_patient_engajamento e ON ((e.patient_id = p.id)))
  WHERE ((p.status = 'Ativo'::text) AND ((p.risco_elevado = true) OR (COALESCE(e.dias_sem_sessao, 999) > 30) OR (COALESCE(e.taxa_comparecimento, (100)::numeric) < (60)::numeric) OR (COALESCE(e.cobr_vencidas, (0)::bigint) > 0)));

CREATE VIEW public.v_plan_active_prices AS
 SELECT plan_id,
    max(
        CASE
            WHEN (("interval" = 'month'::text) AND is_active) THEN amount_cents
            ELSE NULL::integer
        END) AS monthly_cents,
    max(
        CASE
            WHEN (("interval" = 'year'::text) AND is_active) THEN amount_cents
            ELSE NULL::integer
        END) AS yearly_cents,
    max(
        CASE
            WHEN (("interval" = 'month'::text) AND is_active) THEN currency
            ELSE NULL::text
        END) AS monthly_currency,
    max(
        CASE
            WHEN (("interval" = 'year'::text) AND is_active) THEN currency
            ELSE NULL::text
        END) AS yearly_currency
   FROM public.plan_prices
  GROUP BY plan_id;

CREATE VIEW public.v_public_pricing AS
 SELECT p.id AS plan_id,
    p.key AS plan_key,
    p.name AS plan_name,
    COALESCE(pp.public_name, ''::text) AS public_name,
    COALESCE(pp.public_description, ''::text) AS public_description,
    pp.badge,
    COALESCE(pp.is_featured, false) AS is_featured,
    COALESCE(pp.is_visible, true) AS is_visible,
    COALESCE(pp.sort_order, 0) AS sort_order,
    ap.monthly_cents,
    ap.yearly_cents,
    ap.monthly_currency,
    ap.yearly_currency,
    COALESCE(( SELECT jsonb_agg(jsonb_build_object('id', b.id, 'text', b.text, 'highlight', b.highlight, 'sort_order', b.sort_order) ORDER BY b.sort_order, b.created_at) AS jsonb_agg
           FROM public.plan_public_bullets b
          WHERE (b.plan_id = p.id)), '[]'::jsonb) AS bullets,
    p.target AS plan_target
   FROM ((public.plans p
     LEFT JOIN public.plan_public pp ON ((pp.plan_id = p.id)))
     LEFT JOIN public.v_plan_active_prices ap ON ((ap.plan_id = p.id)))
  ORDER BY COALESCE(pp.sort_order, 0), p.key;

CREATE VIEW public.v_subscription_feature_mismatch AS
 WITH expected AS (
         SELECT s.user_id AS owner_id,
            f.key AS feature_key
           FROM ((public.subscriptions s
             JOIN public.plan_features pf ON (((pf.plan_id = s.plan_id) AND (pf.enabled = true))))
             JOIN public.features f ON ((f.id = pf.feature_id)))
          WHERE ((s.status = 'active'::text) AND (s.tenant_id IS NULL) AND (s.user_id IS NOT NULL))
        ), actual AS (
         SELECT e.owner_id,
            e.feature_key
           FROM public.owner_feature_entitlements e
        )
 SELECT COALESCE(expected.owner_id, actual.owner_id) AS owner_id,
    COALESCE(expected.feature_key, actual.feature_key) AS feature_key,
        CASE
            WHEN ((expected.feature_key IS NOT NULL) AND (actual.feature_key IS NULL)) THEN 'missing_entitlement'::text
            WHEN ((expected.feature_key IS NULL) AND (actual.feature_key IS NOT NULL)) THEN 'unexpected_entitlement'::text
            ELSE NULL::text
        END AS mismatch_type
   FROM (expected
     FULL JOIN actual ON (((expected.owner_id = actual.owner_id) AND (expected.feature_key = actual.feature_key))))
  WHERE ((expected.feature_key IS NULL) OR (actual.feature_key IS NULL));

CREATE VIEW public.v_subscription_health AS
 SELECT s.id AS subscription_id,
    s.user_id AS owner_id,
    s.status,
    s.plan_id,
    p.key AS plan_key,
    s.current_period_start,
    s.current_period_end,
    s.updated_at,
        CASE
            WHEN (s.plan_id IS NULL) THEN 'missing_plan'::text
            WHEN (p.id IS NULL) THEN 'invalid_plan'::text
            WHEN ((s.status = 'active'::text) AND (s.current_period_end IS NOT NULL) AND (s.current_period_end < now())) THEN 'expired_but_active'::text
            WHEN ((s.status = 'canceled'::text) AND (s.current_period_end > now())) THEN 'canceled_but_still_in_period'::text
            ELSE 'ok'::text
        END AS health_status,
        CASE
            WHEN (s.tenant_id IS NOT NULL) THEN 'clinic'::text
            ELSE 'therapist'::text
        END AS owner_type,
    COALESCE(s.tenant_id, s.user_id) AS owner_ref
   FROM (public.subscriptions s
     LEFT JOIN public.plans p ON ((p.id = s.plan_id)));

CREATE VIEW public.v_subscription_health_v2 AS
 SELECT s.id AS subscription_id,
    s.user_id AS owner_id,
        CASE
            WHEN (s.tenant_id IS NOT NULL) THEN 'clinic'::text
            ELSE 'therapist'::text
        END AS owner_type,
    COALESCE(s.tenant_id, s.user_id) AS owner_ref,
    s.status,
    s.plan_id,
    p.key AS plan_key,
    s.current_period_start,
    s.current_period_end,
    s.updated_at,
        CASE
            WHEN (s.plan_id IS NULL) THEN 'missing_plan'::text
            WHEN (p.id IS NULL) THEN 'invalid_plan'::text
            WHEN ((s.status = 'active'::text) AND (s.current_period_end IS NOT NULL) AND (s.current_period_end < now())) THEN 'expired_but_active'::text
            WHEN ((s.status = 'canceled'::text) AND (s.current_period_end > now())) THEN 'canceled_but_still_in_period'::text
            ELSE 'ok'::text
        END AS health_status
   FROM (public.subscriptions s
     LEFT JOIN public.plans p ON ((p.id = s.plan_id)));

CREATE VIEW public.v_tag_patient_counts AS
 SELECT t.id,
    t.owner_id,
    t.nome,
    t.cor,
    t.is_padrao,
    t.created_at,
    t.updated_at,
    (COALESCE(count(ppt.patient_id), (0)::bigint))::integer AS pacientes_count,
    (COALESCE(count(ppt.patient_id), (0)::bigint))::integer AS patient_count
   FROM (public.patient_tags t
     LEFT JOIN public.patient_patient_tag ppt ON (((ppt.tag_id = t.id) AND (ppt.owner_id = t.owner_id))))
  GROUP BY t.id, t.owner_id, t.nome, t.cor, t.is_padrao, t.created_at, t.updated_at;

CREATE VIEW public.v_tenant_active_subscription AS
 SELECT DISTINCT ON (tenant_id) tenant_id,
    plan_id,
    plan_key,
    "interval",
    status,
    current_period_start,
    current_period_end,
    created_at
   FROM public.subscriptions s
  WHERE ((tenant_id IS NOT NULL) AND (status = 'active'::text) AND ((current_period_end IS NULL) OR (current_period_end > now())))
  ORDER BY tenant_id, created_at DESC;

CREATE VIEW public.v_tenant_entitlements AS
 SELECT a.tenant_id,
    f.key AS feature_key,
    true AS allowed
   FROM ((public.v_tenant_active_subscription a
     JOIN public.plan_features pf ON (((pf.plan_id = a.plan_id) AND (pf.enabled = true))))
     JOIN public.features f ON ((f.id = pf.feature_id)));

CREATE VIEW public.v_tenant_entitlements_full AS
 SELECT a.tenant_id,
    f.key AS feature_key,
    (pf.enabled = true) AS allowed,
    pf.limits,
    a.plan_id,
    p.key AS plan_key
   FROM (((public.v_tenant_active_subscription a
     JOIN public.plan_features pf ON ((pf.plan_id = a.plan_id)))
     JOIN public.features f ON ((f.id = pf.feature_id)))
     JOIN public.plans p ON ((p.id = a.plan_id)));

CREATE VIEW public.v_tenant_entitlements_json AS
 SELECT tenant_id,
    max(plan_key) AS plan_key,
    jsonb_object_agg(feature_key, jsonb_build_object('allowed', allowed, 'limits', COALESCE(limits, '{}'::jsonb)) ORDER BY feature_key) AS entitlements
   FROM public.v_tenant_entitlements_full
  GROUP BY tenant_id;

CREATE VIEW public.v_tenant_feature_exceptions AS
 SELECT tf.tenant_id,
    a.plan_key,
    tf.feature_key,
    'commercial_exception'::text AS exception_type
   FROM ((public.tenant_features tf
     JOIN public.v_tenant_active_subscription a ON ((a.tenant_id = tf.tenant_id)))
     LEFT JOIN public.v_tenant_entitlements_full v ON (((v.tenant_id = tf.tenant_id) AND (v.feature_key = tf.feature_key))))
  WHERE ((tf.enabled = true) AND (COALESCE(v.allowed, false) = false));

CREATE VIEW public.v_tenant_feature_mismatch AS
 WITH plan_allowed AS (
         SELECT v.tenant_id,
            v.feature_key,
            v.allowed
           FROM public.v_tenant_entitlements_full v
        ), overrides AS (
         SELECT tf.tenant_id,
            tf.feature_key,
            tf.enabled
           FROM public.tenant_features tf
        )
 SELECT o.tenant_id,
    o.feature_key,
        CASE
            WHEN ((o.enabled = true) AND (COALESCE(p.allowed, false) = false)) THEN 'unexpected_override'::text
            ELSE NULL::text
        END AS mismatch_type
   FROM (overrides o
     LEFT JOIN plan_allowed p ON (((p.tenant_id = o.tenant_id) AND (p.feature_key = o.feature_key))))
  WHERE ((o.enabled = true) AND (COALESCE(p.allowed, false) = false));

CREATE VIEW public.v_tenant_members_with_profiles AS
 SELECT tm.id AS tenant_member_id,
    tm.tenant_id,
    tm.user_id,
    tm.role,
    tm.status,
    tm.created_at,
    p.full_name,
    au.email
   FROM ((public.tenant_members tm
     LEFT JOIN public.profiles p ON ((p.id = tm.user_id)))
     LEFT JOIN auth.users au ON ((au.id = tm.user_id)));

CREATE VIEW public.v_tenant_people AS
 SELECT 'member'::text AS type,
    m.tenant_id,
    m.user_id,
    u.email,
    m.role,
    m.status,
    NULL::uuid AS invite_token,
    NULL::timestamp with time zone AS expires_at
   FROM (public.tenant_members m
     JOIN auth.users u ON ((u.id = m.user_id)))
UNION ALL
 SELECT 'invite'::text AS type,
    i.tenant_id,
    NULL::uuid AS user_id,
    i.email,
    i.role,
    'invited'::text AS status,
    i.token AS invite_token,
    i.expires_at
   FROM public.tenant_invites i
  WHERE ((i.accepted_at IS NULL) AND (i.revoked_at IS NULL));

CREATE VIEW public.v_tenant_staff AS
 SELECT ('m_'::text || (tm.id)::text) AS row_id,
    tm.tenant_id,
    tm.user_id,
    tm.role,
    tm.status,
    tm.created_at,
    p.full_name,
    au.email,
    NULL::uuid AS invite_token
   FROM ((public.tenant_members tm
     LEFT JOIN public.profiles p ON ((p.id = tm.user_id)))
     LEFT JOIN auth.users au ON ((au.id = tm.user_id)))
UNION ALL
 SELECT ('i_'::text || (ti.id)::text) AS row_id,
    ti.tenant_id,
    NULL::uuid AS user_id,
    ti.role,
    'invited'::text AS status,
    ti.created_at,
    NULL::text AS full_name,
    ti.email,
    ti.token AS invite_token
   FROM public.tenant_invites ti
  WHERE ((ti.accepted_at IS NULL) AND (ti.revoked_at IS NULL) AND (ti.expires_at > now()));

CREATE VIEW public.v_twilio_whatsapp_overview AS
 SELECT nc.id AS channel_id,
    nc.tenant_id,
    nc.owner_id,
    nc.is_active,
    nc.connection_status,
    nc.display_name,
    nc.twilio_subaccount_sid,
    nc.twilio_phone_number,
    nc.twilio_phone_sid,
    nc.cost_per_message_usd,
    nc.price_per_message_brl,
    nc.provisioned_at,
    nc.created_at,
    nc.updated_at,
    COALESCE(u.messages_sent, 0) AS current_month_sent,
    COALESCE(u.messages_delivered, 0) AS current_month_delivered,
    COALESCE(u.messages_failed, 0) AS current_month_failed,
    COALESCE(u.cost_usd, (0)::numeric) AS current_month_cost_usd,
    COALESCE(u.cost_brl, (0)::numeric) AS current_month_cost_brl,
    COALESCE(u.revenue_brl, (0)::numeric) AS current_month_revenue_brl,
    COALESCE(u.margin_brl, (0)::numeric) AS current_month_margin_brl
   FROM (public.notification_channels nc
     LEFT JOIN public.twilio_subaccount_usage u ON (((u.channel_id = nc.id) AND (u.period_start = (date_trunc('month'::text, (CURRENT_DATE)::timestamp with time zone))::date))))
  WHERE ((nc.channel = 'whatsapp'::text) AND (nc.provider = 'twilio'::text) AND (nc.deleted_at IS NULL));

CREATE VIEW public.v_user_active_subscription AS
 SELECT DISTINCT ON (user_id) user_id,
    plan_id,
    plan_key,
    "interval",
    status,
    current_period_start,
    current_period_end,
    created_at
   FROM public.subscriptions s
  WHERE ((tenant_id IS NULL) AND (user_id IS NOT NULL) AND (status = 'active'::text) AND ((current_period_end IS NULL) OR (current_period_end > now())))
  ORDER BY user_id, created_at DESC;

CREATE VIEW public.v_user_entitlements AS
 SELECT a.user_id,
    f.key AS feature_key,
    true AS allowed
   FROM ((public.v_user_active_subscription a
     JOIN public.plan_features pf ON (((pf.plan_id = a.plan_id) AND (pf.enabled = true))))
     JOIN public.features f ON ((f.id = pf.feature_id)));
