Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
206
docs/billing/Agencia_PSI_Billing_Subscriptions_v1_2.html
Normal file
206
docs/billing/Agencia_PSI_Billing_Subscriptions_v1_2.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Agência PSI — Billing & Subscriptions v1.2</title>
|
||||
<style>
|
||||
body{
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
margin:0;
|
||||
padding:40px;
|
||||
background:#f6f8fc;
|
||||
color:#0f172a;
|
||||
}
|
||||
h1{font-size:28px;margin-bottom:8px;}
|
||||
h2{margin-top:40px;font-size:20px;}
|
||||
h3{margin-top:24px;font-size:16px;}
|
||||
p{line-height:1.6;color:#334155;}
|
||||
pre{
|
||||
background:#0f172a;
|
||||
color:#e2e8f0;
|
||||
padding:16px;
|
||||
border-radius:12px;
|
||||
overflow:auto;
|
||||
}
|
||||
code{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;}
|
||||
.section{margin-bottom:40px;}
|
||||
.badge{
|
||||
display:inline-block;
|
||||
padding:4px 10px;
|
||||
border-radius:999px;
|
||||
background:#e2e8f0;
|
||||
font-size:12px;
|
||||
margin-right:6px;
|
||||
}
|
||||
.rule{
|
||||
background:#e0f2fe;
|
||||
padding:14px;
|
||||
border-left:4px solid #0284c7;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
.warn{
|
||||
background:#fef3c7;
|
||||
padding:14px;
|
||||
border-left:4px solid #d97706;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
.danger{
|
||||
background:#fee2e2;
|
||||
padding:14px;
|
||||
border-left:4px solid #dc2626;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
.ok{
|
||||
background:#dcfce7;
|
||||
padding:14px;
|
||||
border-left:4px solid #16a34a;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
footer{
|
||||
margin-top:60px;
|
||||
font-size:12px;
|
||||
color:#64748b;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Billing & Subscriptions — v1.2</h1>
|
||||
<p><strong>Agência PSI</strong> — Documento consolidado da sessão técnica sobre Subscriptions, Guardrails e Seeder.</p>
|
||||
|
||||
<div class="section">
|
||||
<h2>1. Escopo desta versão</h2>
|
||||
<p>Este documento consolida tudo o que foi tratado nesta sessão:</p>
|
||||
<ul>
|
||||
<li>Modelagem real da tabela <code>subscriptions</code></li>
|
||||
<li>Histórico via <code>subscription_events</code></li>
|
||||
<li>Triggers (guardrails) de proteção</li>
|
||||
<li>Views oficiais</li>
|
||||
<li>Seeder completo (planos + preços + metadata pública)</li>
|
||||
<li>Erros reais encontrados e solução aplicada</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>2. Estrutura confirmada — subscriptions</h2>
|
||||
<pre><code>id uuid PK
|
||||
tenant_id uuid NULL
|
||||
user_id uuid NULL
|
||||
plan_id uuid NOT NULL
|
||||
plan_key text NULL
|
||||
interval text CHECK ('month','year')
|
||||
status text DEFAULT 'active'
|
||||
current_period_start timestamptz
|
||||
current_period_end timestamptz
|
||||
provider text DEFAULT 'manual'
|
||||
cancel_at_period_end boolean DEFAULT false
|
||||
created_at timestamptz DEFAULT now()
|
||||
updated_at timestamptz DEFAULT now()</code></pre>
|
||||
|
||||
<div class="rule">
|
||||
Assinatura de clínica exige <strong>tenant_id</strong>.
|
||||
Assinatura de terapeuta pode usar <strong>user_id</strong>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>3. Guardrails (Proteções de Integridade)</h2>
|
||||
|
||||
<h3>3.1 Impedir deletar planos core</h3>
|
||||
<pre><code>create or replace function guard_no_delete_core_plans()
|
||||
returns trigger language plpgsql as $$
|
||||
begin
|
||||
if old.key in ('clinic_free','clinic_pro','therapist_free','therapist_pro') then
|
||||
raise exception 'Plano padrão (%) não pode ser removido.', old.key;
|
||||
end if;
|
||||
return old;
|
||||
end $$;</code></pre>
|
||||
|
||||
<h3>3.2 Impedir alterar target</h3>
|
||||
<pre><code>create or replace function guard_no_change_plan_target()
|
||||
returns trigger language plpgsql as $$
|
||||
begin
|
||||
if new.target is distinct from old.target then
|
||||
raise exception 'Não é permitido alterar target do plano.';
|
||||
end if;
|
||||
return new;
|
||||
end $$;</code></pre>
|
||||
|
||||
<h3>3.3 Impedir alterar key core</h3>
|
||||
<pre><code>create or replace function guard_no_change_core_plan_key()
|
||||
returns trigger language plpgsql as $$
|
||||
begin
|
||||
if old.key in ('clinic_free','clinic_pro','therapist_free','therapist_pro')
|
||||
and new.key is distinct from old.key then
|
||||
raise exception 'Não é permitido alterar a key do plano padrão.';
|
||||
end if;
|
||||
return new;
|
||||
end $$;</code></pre>
|
||||
|
||||
<div class="warn">
|
||||
Esses guardrails impediram alterações indevidas quando tentamos renomear planos core.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>4. Views Oficiais</h2>
|
||||
<p><strong>v_public_pricing</strong> — Tela pública de preços.</p>
|
||||
<p><strong>v_tenant_active_subscription</strong> — Plano vigente do tenant.</p>
|
||||
<p><strong>v_subscription_health</strong> — Diagnóstico de inconsistências.</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>5. Seeder Oficial (MVP)</h2>
|
||||
|
||||
<pre><code>insert into plans (key,name,target,is_active)
|
||||
values
|
||||
('clinic_free','Clinic Free','clinic',true),
|
||||
('clinic_pro','Clinic Pro','clinic',true),
|
||||
('therapist_free','Therapist Free','therapist',true),
|
||||
('therapist_pro','Therapist Pro','therapist',true)
|
||||
on conflict (key) do update set
|
||||
name=excluded.name,
|
||||
target=excluded.target,
|
||||
is_active=excluded.is_active;</code></pre>
|
||||
|
||||
<div class="ok">
|
||||
Seeder é idempotente. Pode rodar múltiplas vezes sem duplicar.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>6. Incidentes reais resolvidos</h2>
|
||||
|
||||
<h3>6.1 Pricing retornando null</h3>
|
||||
<p>Causa: não havia preço vigente (is_active=true e active_to is null).</p>
|
||||
|
||||
<h3>6.2 Erro ao alterar plano padrão</h3>
|
||||
<p>Causa: trigger guard_no_change_core_plan_key bloqueando alteração.</p>
|
||||
|
||||
<h3>6.3 Assinatura sem tenant_id</h3>
|
||||
<p>Causa: regra de negócio no banco impedindo clinic sem tenant.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>7. Diretrizes finais</h2>
|
||||
<ul>
|
||||
<li>Plano nunca deve ser inferido do role.</li>
|
||||
<li>UI deve consumir apenas views oficiais.</li>
|
||||
<li>Plano core nunca deve ser renomeado.</li>
|
||||
<li>Preço sempre deve ter vigência ativa.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Agência PSI — Billing & Subscriptions v1.2<br>
|
||||
Documento gerado automaticamente.
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user