fix(suspensao): reativar só fala de cobrança com empresa suspensa de fato

O pré-check do 409 suspensao_de_cobranca perguntava "a empresa está
parada?" em vez de "está suspensa?". Uma empresa redigida pela LGPD é
parada e guarda o tipo residual 'cobranca' (o redact não limpa a coluna),
e o admin receberia a instrução errada de negociar pagamento. Agora a
régua é status = 'suspended'; a função responde nao_suspensa como sempre.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
melgarafael
2026-09-30 13:45:05 -03:00
co-authored by Claude Opus 5.5
parent 41da980e6a
commit d0aaa8761f
3 changed files with 18 additions and 6 deletions
@@ -76,6 +76,17 @@ describe("POST /admin/tenants/[id]/reactivate", () => {
expect(h.rpc).not.toHaveBeenCalled();
});
it("org REDIGIDA com tipo residual 'cobranca' → não é 409 de cobrança (a função responde)", async () => {
// O lgpd-redact-worker troca status para 'redacted' sem limpar o tipo:
// parada, mas não suspensa — negociar pagamento seria instrução errada.
h.org = { id: TENANT, slug: "acme", status: "redacted", suspended_kind: "cobranca" };
h.rpc.mockResolvedValue({ data: { changed: false, motivo: "nao_suspensa" }, error: null });
const res = await POST(pedido({ reason: MOTIVO }), ctx);
expect(res.status).toBe(200);
expect((await res.json()).data).toEqual({ changed: false, motivo: "nao_suspensa" });
expect(h.rpc).toHaveBeenCalledTimes(1);
});
it("administrativa reativa pela função com kind exigido e audita com o motivo", async () => {
const res = await POST(pedido({ reason: MOTIVO }), ctx);
expect(res.status).toBe(200);
@@ -17,7 +17,7 @@ import {
requirePlatformAdminEscrita,
type PlatformAdminContext,
} from "@/lib/auth/requirePlatformAdmin";
import { ehOperante, type TipoDeSuspensao } from "@/lib/organizacao/operante";
import { type TipoDeSuspensao } from "@/lib/organizacao/operante";
import { createAdminClient } from "@/lib/supabase/admin";
import { ok, fail } from "@/lib/api/wrappers";
import { audit } from "@/lib/audit";
@@ -67,9 +67,10 @@ export async function POST(
return fail("not_found", "Tenant not found", 404, { requestId });
}
// `suspended_kind` só significa algo com a org parada (operante.ts). A função
// também recusa o kind divergente; aqui é para a mensagem ter nome.
if (!ehOperante(org.status) && org.suspended_kind === KIND_DE_COBRANCA) {
// `suspended_kind` só significa algo com status='suspended' (operante.ts): a
// org redigida é parada e guarda o tipo residual, e não se negocia pagamento
// com ela. A função também recusa o kind divergente; aqui é para a mensagem ter nome.
if (org.status === "suspended" && org.suspended_kind === KIND_DE_COBRANCA) {
return fail(
"suspensao_de_cobranca",
"Esta suspensão é por falta de pagamento. Use Dar prazo ou Tornar isenta.",
+2 -2
View File
@@ -11,8 +11,8 @@
* ── Leitura de `suspended_kind` ──────────────────────────────────────────────
* Só significa algo com status='suspended'. O lgpd-redact-worker troca o status
* para 'redacted' sem limpar o tipo, e por isso o banco NÃO tem CHECK de
* coerência entre as duas colunas: quem lê o tipo confere antes que a org está
* parada. Suspensão com tipo NULO (gravada por uma imagem anterior à 0501,
* coerência entre as duas colunas: quem lê o tipo confere antes que o status é
* 'suspended' — "parada" não basta, a redigida também é parada. Suspensão com tipo NULO (gravada por uma imagem anterior à 0501,
* depois de um rollback) vale como `administrativa`, como nas funções de estado.
*
* ── Deliberadamente NÃO gatilhados (spec §4, decisões D-11 e D-12) ───────────