Especialização em Sistemas com Python 3.12+ • Capítulo 08


🎯 Demonstração Prática: Gestão de Contratos B2B e Assinaturas com Dataclasses e StrEnum

O exemplo abaixo demonstra a modelagem rica de domínio sob os princípios de DDD tático em Python 3.12+:

  • Objetos de Valor imutáveis (Value Objects) protegidos por @dataclass(frozen=True, slots=True).
  • Uso de enum.StrEnum para estados semânticos serializáveis diretamente.
  • Entidade de Contrato com composição de múltiplos objetos e validações de invariantes em __post_init__.
  • Métodos de transição de estado que preservam as regras de negócio de faturamento recorrente.
"""
demonstracao_dominio_e_dataclasses.py
Modelagem de Domínio Rica (DDD), Value Objects, StrEnum e Dataclasses com Slots.
Compatível com Python 3.12+.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from decimal import Decimal, ROUND_HALF_EVEN
from enum import StrEnum, auto
import re
import uuid
from typing import Final

# ==============================================================================
# 1. ENUMERAÇÕES SEMÂNTICAS (STRENUM PYTHON 3.11+)
# ==============================================================================
class CicloFaturamento(StrEnum):
    MENSAL = auto()
    TRIMESTRAL = auto()
    ANUAL = auto()

class StatusContrato(StrEnum):
    RASCUNHO = auto()
    EM_ANALISE_RISCO = auto()
    VIGENTE = auto()
    SUSPENSO_INADIMPLENCIA = auto()
    ENCERRADO = auto()

# ==============================================================================
# 2. VALUE OBJECTS (IMUTÁVEIS E AUTOCONTIDOS)
# ==============================================================================
@dataclass(frozen=True, slots=True)
class CNPJ:
    valor: str

    def __post_init__(self) -> None:
        apenas_digitos = re.sub(r"\D", "", self.valor)
        if len(apenas_digitos) != 14:
            raise ValueError(f"CNPJ inválido: '{self.valor}'. Deve conter exatamente 14 dígitos.")
        # Congelamento normalizado
        object.__setattr__(self, "valor", apenas_digitos)

    def formatado(self) -> str:
        d = self.valor
        return f"{d[:2]}.{d[2:5]}.{d[5:8]}/{d[8:12]}-{d[12:]}"

@dataclass(frozen=True, slots=True)
class EnderecoComercial:
    logradouro: str
    numero: str
    bairro: str
    cidade: str
    uf: str
    cep: str

# ==============================================================================
# 3. ENTIDADES DE DOMÍNIO E AGREGADOS
# ==============================================================================
@dataclass(slots=True)
class ClienteCorporativo:
    id_cliente: uuid.UUID
    razao_social: str
    cnpj: CNPJ
    endereco: EnderecoComercial
    email_faturamento: str

@dataclass(slots=True)
class ContratoAssinatura:
    id_contrato: str
    cliente: ClienteCorporativo
    valor_plano: Decimal
    ciclo: CicloFaturamento
    status: StatusContrato = StatusContrato.RASCUNHO
    criado_em: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    itens_inclusos: list[str] = field(default_factory=list)

    def __post_init__(self) -> None:
        """Validação de invariantes na instanciação da entidade."""
        if self.valor_plano <= Decimal("0.00"):
            raise ValueError(f"Valor do contrato deve ser positivo: {self.valor_plano}")
        if not self.id_contrato.startswith("CT-"):
            raise ValueError(f"Identificador de contrato malformado: {self.id_contrato}")

    def submeter_para_analise(self) -> None:
        if self.status != StatusContrato.RASCUNHO:
            raise RuntimeError(f"Transição inválida: Não pode submeter a partir do status [{self.status.value}]")
        self.status = StatusContrato.EM_ANALISE_RISCO

    def aprovar_e_ativar(self) -> None:
        if self.status != StatusContrato.EM_ANALISE_RISCO:
            raise RuntimeError(f"Apenas contratos em análise podem ser ativados diretamente.")
        self.status = StatusContrato.VIGENTE

    def aplicar_reajuste_anual(self, percentual_indice: Decimal) -> Decimal:
        """Aplica índice inflacionário anual sobre a mensalidade do plano."""
        if self.status != StatusContrato.VIGENTE:
            raise RuntimeError("Apenas contratos vigentes admitem reajuste anual de índice.")
        
        fator_reajuste = Decimal("1.00") + percentual_indice
        self.valor_plano = (self.valor_plano * fator_reajuste).quantize(Decimal("0.01"), ROUND_HALF_EVEN)
        return self.valor_plano

# ==============================================================================
# 4. EXECUÇÃO DEMONSTRATIVA
# ==============================================================================
if __name__ == "__main__":
    print("=" * 80)
    print("      GESTÃO DE CONTRATOS SAAS COM DATACLASSES E VALUE OBJECTS")
    print("=" * 80)

    # Criação dos Value Objects
    cnpj_cliente = CNPJ("45.123.789/0001-92")
    endereco_sede = EnderecoComercial(
        logradouro="Avenida Brigadeiro Faria Lima",
        numero="3477",
        bairro="Itaim Bibi",
        cidade="São Paulo",
        uf="SP",
        cep="04538-133"
    )

    # Criação da Entidade Cliente
    cliente_tech = ClienteCorporativo(
        id_cliente=uuid.uuid4(),
        razao_social="OmniCloud Infraestrutura e Inteligência Ltda",
        cnpj=cnpj_cliente,
        endereco=endereco_sede,
        email_faturamento="billing@omnicloud.com.br"
    )

    # Criação do Contrato Agregado
    contrato = ContratoAssinatura(
        id_contrato="CT-2026-9042",
        cliente=cliente_tech,
        valor_plano=Decimal("12500.00"),
        ciclo=CicloFaturamento.ANUAL,
        itens_inclusos=["Cluster Kubernetes Dedicado", "Suporte 24/7 Nível 3", "Backup Geodistribuído"]
    )

    print(f"Contrato ID       : {contrato.id_contrato}")
    print(f"Cliente Titular   : {contrato.cliente.razao_social}")
    print(f"CNPJ Formatado    : {contrato.cliente.cnpj.formatado()}")
    print(f"Sede Operacional  : {contrato.cliente.endereco.cidade}/{contrato.cliente.endereco.uf}")
    print(f"Status Inicial    : [{contrato.status.value}]")
    print(f"Valor Contratado  : R$ {contrato.valor_plano:>10.2f} ({contrato.ciclo.value.upper()})")
    print("-" * 80)

    print(">> Avançando ciclo de vida contratual...")
    contrato.submeter_para_analise()
    print(f"Status Atualizado : [{contrato.status.value}]")

    contrato.aprovar_e_ativar()
    print(f"Status Atualizado : [{contrato.status.value}] (Contrato em Plena Vigência)")
    print("-" * 80)

    print(">> Aplicando reajuste IPCA de 4.85% após 12 meses de vigência...")
    novo_valor = contrato.aplicar_reajuste_anual(Decimal("0.0485"))
    print(f"Novo Valor Mensal : R$ {novo_valor:>10.2f} (Atualizado com precisão contábil)")
    print("=" * 80)