AFService Map v0.76.0 — Correção de arquitetura do financeiro
Backup/ponto de rollback: tag v0.75.1. Trabalho feito diretamente por
mim (Claude), a partir da correção explícita mandada pelo usuário:
"Os clientes (Provedores ISP e Projetistas) NÃO criam clientes e NÃO
gerenciam outros usuários em seus painéis. Eles são os meus clientes
finais. Quem cria e gerencia tudo sou eu, no painel Superadmin."
Com migration (remove o model Customer por completo).
O erro de modelo, corrigido
Desde a v0.74.0 eu tinha construído o financeiro assumindo que cada
Provedor ISP cobraria os próprios assinantes de internet — por
isso a v0.75.1 chegou a ligar o financeiro aos clientes já sincronizados
do IXCSoft (IXCCustomer). Estava errado: quem é cobrado nesta
plataforma é a empresa cliente da AFService (Provedor ISP ou
Projetista) — a mensalidade é pela licença/uso do sistema, não pela
internet que o provedor vende aos assinantes dele. Isso nunca teve
nenhum dado real (a funcionalidade tinha dias, e todos os prints
mostravam "nenhum cliente cadastrado"), então a correção pôde remover o
model antigo sem migração de dado nenhuma.
O que muda
Painel do cliente (Provedor ISP / Projetista) — só consulta
Removido por completo: botão "Novo cliente", cadastro, edição e
detalhe por cliente. O painel agora é só leitura: mensalidade, dia de
vencimento, o que está em aberto, atrasado e pago, histórico de faturas,
exportação em CSV. Continua exigindo CompanyMembership.role == EDIT —
perfil VIEW não acessa, mesma regra restrita de antes (essa parte já
estava certa, não mudou).
Painel Superadmin — único lugar que cadastra e gerencia
Já existia desde a v0.75.0 o cadastro de empresa (Provedor/Projetista +
primeiro usuário responsável) fora do Django Admin — isso continua
igual. O que entrou agora, por empresa
(/painel/plataforma/empresas/<id>/financeiro/):
- Editar assinatura — mensalidade, dia de vencimento, se gera
cobrança automática. - Lançar cobrança manual avulsa — fatura fora do ciclo mensal
automático, pra qualquer cobrança pontual. - Registrar pagamento por fatura em aberto.
- Controle de status: Reativar, Bloquear, Cancelar, Desativar
empresa, Excluir. "Excluir" não apaga nada do banco — desativa a
empresa e cancela a assinatura junto, mesma filosofia não-destrutiva
já usada no resto do sistema (nunca apaga equipamento, cabo, fusão
etc.). Reversível pela mesma tela. - Exportar CSV — por empresa e consolidado de todas as empresas.
"Liberação de confiança"
Botão no painel do cliente (e na tela de bloqueio): concede mais 2
dias de acesso mesmo com a assinatura bloqueada/cancelada. Limitado a
1 uso por mês corrido, validado no servidor
(apps.billing.services.request_trust_release — checa
TrustRelease.objects.filter(company=..., created_at__year=.., created_at__month=..)
antes de conceder, não só desabilita o botão na tela).
Bloqueio de acesso de verdade
Novo middleware apps.billing.middleware.SubscriptionAccessMiddleware:
quando a assinatura de uma empresa está bloqueada ou cancelada e não há
liberação de confiança em vigor, qualquer usuário dessa empresa (exceto
Superadmin, que nunca é afetado) é redirecionado pra uma tela de "conta
bloqueada" em qualquer página que tente acessar — login, logout,
arquivos estáticos, /admin/ e /api/ ficam de fora do bloqueio.
Decisão de segurança mais importante desta rodada: empresa sem
CompanySubscription cadastrada (hoje, a imensa maioria — o recurso é
nascendo agora) nunca é bloqueada. O bloqueio só passa a valer
quando o Superadmin bloqueia/cancela explicitamente pela tela de
financeiro daquela empresa. Testei essa regra como função pura antes de
sequer escrever o middleware (ver seção de verificação) — não queria
correr o risco de travar todo mundo fora do sistema por um bug de dia
1.
O que ficou de fora, de propósito
- Chamada real a gateway de pagamento — só a tela de credencial
(já existia desde a v0.75.0), sem integração de verdade. - Cobrança automática puxando o gateway quando a fatura vence — a
geração de fatura recorrente continua automática (Celery Beat), mas
"cobrar" ainda é manual (Superadmin registra o pagamento recebido).
Verificação feita
python -m py_compileem todos os arquivos tocados/novos.is_access_blockedehas_trust_release_this_monthsão funções
puras, sem banco — executei de verdade via script Python direto:
6 asserções prais_access_blocked(incluindo o caso crítico "sem
assinatura cadastrada nunca bloqueia", "bloqueada mas com carência no
futuro não bloqueia", "cancelada sem carência bloqueia") e 4 pra
has_trust_release_this_month(incluindo mês/ano diferentes) — todas
passaram.- Migration revisada campo a campo (sem GDAL neste ambiente de
preparação): ordem correta — remove a constraint única, depois o
índice, depois o campocustomerda fatura, só então apaga o model
Customerinteiro. Nomes de constraint/índice conferidos contra a
migration0001_initial.pyque os criou, pra garantir que bate
exatamente com o que existe no banco. - Chaves do
static/css/app.cssbalanceadas (282/282). {% if %}/{% endif %}/{% for %}/{% endfor %}/{% block %}/
{% endblock %}balanceados em todos os templates tocados e novos.git diff --checklimpo.- Grep no projeto inteiro confirmando que nenhuma referência solta a
Customer/billing-customers/billing-customer-*sobrou fora das
migrations antigas (onde é esperado — são registro histórico).
manage.py check, manage.py migrate, manage.py test e o teste
real dependem de banco, GDAL e navegador — não rodam neste ambiente de
preparação.
Atualização
Execute apply no servidor — esta versão tem migration (remove o
model Customer inteiro). Depois, um Ctrl+F5 no navegador.
Teste sugerido: como Superadmin, abrir o financeiro de uma empresa de
teste, lançar uma cobrança manual e registrar o pagamento; bloquear a
assinatura dessa empresa e confirmar (logado como usuário EDIT dela)
que qualquer página redireciona pra "conta bloqueada"; pedir liberação
de confiança e confirmar os +2 dias de acesso; tentar pedir de novo no
mesmo mês e confirmar que é bloqueado; confirmar que um usuário VIEW
continua sem acessar /painel/financeiro/ de forma nenhuma; confirmar
que uma empresa qualquer sem assinatura configurada continua
acessando o sistema normalmente (fail-safe).