REF-202-01: Entitlement Lifecycle
ADR: ADR-202 — Entitlement: Agregador CentralEscopo: Diagrama de estados, transições permitidas, payment recovery flow, upgrade/downgrade, cancellation, win-back, expiração e grace period
1. Diagrama de Estados
┌──────────┐
Criação ─────► │ active │
(auto/manual) └────┬──┬──┘
│ │
┌──────────────┘ └──────────────┐
│ │
▼ ▼
┌───────────┐ ┌──────────┐
│ suspended │ │ expired │
└─────┬──┬──┘ └────┬──┬──┘
│ │ │ │
│ └──► active (reativado) │ └──► active (renovado)
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ cancelled │◄───────────────────│ cancelled │
└─────┬─────┘ └───────────┘
│
└──► active (win-back)2. Transições Permitidas
| De | Para | Trigger | Quem Pode |
|---|---|---|---|
| — | active | Criação (auto-provisioning ou manual) | Sistema / Admin |
| active | suspended | Falha de pagamento / hold admin | Sistema / Admin |
| active | expired | Data de expiração atingida | Cron (auto) |
| active | cancelled | Cancelamento direto | Admin |
| suspended | active | Pagamento regularizado / lift admin | Sistema / Admin |
| suspended | cancelled | Timeout configurável ou decisão admin | Sistema / Admin |
| expired | active | Renovação (novo pagamento) | Sistema / Admin |
| expired | cancelled | Timeout configurável | Sistema / Admin |
| cancelled | active | Win-back (reativa mesmo entitlement) | Admin (somente) |
Transições proibidas: Nenhum estado pode pular para outro fora da tabela acima.
3. Payment Recovery Flow (Falha de Pagamento)
Invoice.payment_failed (Stripe webhook)
│
├── PaymentRecoveryPolicy consultada
│ ├── Suspensão imediata? → Entitlement → suspended
│ └── Suspensão antecipada? → suspended (clientes com histórico irregular)
│
├── Stripe continua retry (retry padrão Stripe)
│ └── middag-account espelha cada evento invoice.payment_failed
│
├── Retry bem-sucedido → Entitlement → active (automático)
│
└── Falha final (todos retries esgotados)
└── Entitlement permanece suspended
└── Período suspended→cancelled: configurável (PaymentRecoveryPolicy)
├── Admin pode cancelar manualmente a qualquer momento
└── Cron cancela automaticamente após timeoutNotificações: Stripe cuida das notificações de cobrança. middag-account não duplica.
4. Upgrade/Downgrade
| Aspecto | Regra |
|---|---|
| Código do entitlement | Preservado (mesmo código, tier muda como atributo) |
| Histórico | Completo — todas mudanças de tier registradas |
| Efeito | Imediato ou próximo ciclo (configurável via TierChangePolicy) |
| Downgrade | Configurável por produto — pode exigir aprovação bidirecional (admin e/ou cliente) |
| Créditos | Seguem ciclo de billing (cobrança no próximo ciclo = créditos no próximo ciclo) |
| Cooldown | Tempo mínimo entre mudanças de tier (configurável, evita gaming) |
| Quem executa | Admin manualmente (v5.0). Self-serve no portal futuro. |
5. Cancellation & Win-Back
| Aspecto | Regra |
|---|---|
| Grace period com serviço | Não existe — expired = acesso cortado imediato |
| Visibilidade pós-expiração | Configurável por produto (CancellationPolicy) |
| Expired → cancelled | Tempo configurável com default (CancellationPolicy) |
| Retenção de dados fiscais | Prazo legal (LGPD/fiscal) — não configurável |
| Retenção de dados operacionais | Período configurável (CancellationPolicy) — custo de armazenamento considerado |
| Portal pós-cancel | Dashboard admin acessível. Produtos/serviços com licenciamento ativo: bloqueados. |
| Exportação de dados | Oferecida no processo de cancelamento. Recuperação posterior como serviço pago. |
| Win-back | Reativa mesmo Entitlement — preserva histórico, mesmo código |
| Quem faz win-back | Admin somente (decisão humana) |
6. Expiração Automática (Cron)
Cron diário verifica:
├── Entitlements active com expiration_date < now()
│ └── Transição: active → expired
│ └── Hook: middag_entitlement_status_changed($ent, 'active', 'expired')
│
└── Entitlements expired com (expired_at + CancellationPolicy.timeout) < now()
└── Transição: expired → cancelled
└── Hook: middag_entitlement_status_changed($ent, 'expired', 'cancelled')7. Hooks de Lifecycle
| Hook | Parâmetros | Quando |
|---|---|---|
middag_entitlement_status_changed | $entitlement, $old_status, $new_status | Qualquer transição |
middag_entitlement_suspended | $entitlement, $reason | active → suspended |
middag_entitlement_expired | $entitlement | active → expired |
middag_entitlement_cancelled | $entitlement, $reason | Qualquer → cancelled |
middag_entitlement_reactivated | $entitlement, $from_status | suspended/expired/cancelled → active |
8. EntitlementEntity — Campos
| Campo | Tipo | Notas |
|---|---|---|
entitlement_id | int | Auto-increment, interno |
entitlement_code | string | Unique, imutável. Formato: {CLASS}-{YEAR}{MONTH}{SEQ:4d} |
entitlement_class | enum | PLG, ENV, SVC, ORD, AFL, EDU. Imutável. |
organization_id | int | FK para Organization. Obrigatório. |
product_name | string | Nome legível para o usuário |
product_description | text | Nullable |
status | enum | active, suspended, expired, cancelled |
company | enum | middag_br, middag_global — qual entidade MIDDAG |
lifecycle | enum | ongoing, project (SVC). Nullable para outras classes. |
type | string | Subtipo do serviço (SVC): hosting, support, infrastructure, consulting, development, mobile-apps, migration, upgrade, project-management. Valores do enum category do catálogo. |
parent_entitlement_id | int | FK nullable. Hierarquia parent-child (ver REF-202-03). |
materialized_path | string | Path para queries eficientes (ver REF-202-03) |
created_at | datetime | Auto |
updated_at | datetime | Auto |
expires_at | datetime | Nullable. Null = perpétuo. |
auto_created | bool | True se criado via auto-provisioning pós-pagamento |
quote_id | int | FK nullable. Vínculo com Quote de origem. |
metadata | JSON | Extensível por classe. Dados específicos da classe. |
9. REST API Endpoints
| Método | Endpoint | Descrição | Scope necessário |
|---|---|---|---|
| GET | /middag-account/v1/entitlements | Lista entitlements da org. Filtro por classe/status. | entitlements |
| GET | /middag-account/v1/entitlements/{id} | Detalhe com Orders, Licenses, Contracts vinculados | entitlements |
| GET | /middag-account/v1/entitlements/code/{code} | Busca por código (ex: PLG-2026040142) | entitlements |
| POST | /middag-account/v1/entitlements | Criação manual (admin only) | admin |
| PUT | /middag-account/v1/entitlements/{id} | Atualização (admin only) | admin |
| GET | /middag-account/v1/entitlements/{id}/orders | Pedidos vinculados | entitlements |
| GET | /middag-account/v1/entitlements/{id}/licenses | Licenças vinculadas | entitlements |
| GET | /middag-account/v1/entitlements/{id}/contracts | Contratos vinculados | entitlements |
| GET | /middag-account/v1/entitlements/{id}/children | Entitlements filhos | entitlements |