Files
controles/utils/date_utils.py
LS 7399e0000e feat: Implementar arquitetura de permissões no nível de dados
BREAKING CHANGES:
- Sistema de permissões movido do nível de template para nível de dados
- Menus sempre visíveis, controle transparente no backend
- Templates nunca quebram, sempre renderizam com dados filtrados

Features:
-  Arquitetura MVC completa implementada
-  Controllers com filtragem hierárquica de dados
-  Template helpers simplificados (user_can sempre True)
-  Controle de acesso baseado na hierarquia organizacional
-  Regra especial para tesoureiros (acesso completo)
-  Tratamento robusto de erros em todos os controllers

Controllers implementados:
- militante_controller.py - Filtragem por célula/setor/CR/CC
- cota_controller.py - Controle baseado em permissões
- material_controller.py - Acesso flexível por nível
- pagamento_controller.py - Filtragem organizacional
- auth_controller.py - Autenticação com OTP
- home_controller.py - Dashboard com estatísticas
- usuario_controller.py - Gestão de usuários

Templates corrigidos:
- listar_cotas.html - URLs corrigidas (nova_cota → cota.nova)
- listar_tipos_materiais.html - Variáveis ajustadas (tipos → tipos_materiais)
- base.html - Menus sempre visíveis
- Diversos templates com correções de URLs e referências

Services implementados:
- auth_service.py - Lógica de autenticação
- dashboard_service.py - Estatísticas do dashboard
- cache_service.py - Integração com Redis
- celula_service.py - Operações de células

Models implementados:
- militante_model.py - Operações de militantes
- pagamento_model.py - Operações de pagamentos

Documentação:
- docs/permission_fixes_summary.md - Resumo completo das correções
- docs/architecture_summary.md - Arquitetura MVC
- docs/mvc_refactoring.md - Detalhes da refatoração
- docs/permission_strategy.md - Estratégia de permissões
- docs/redis_cache_setup.md - Setup do cache Redis
- README.md atualizado com nova arquitetura

Testes:
- test_menu_navigation.py - Testes unitários de navegação
- test_integration_menu.py - Testes de integração com Selenium

Status dos testes:
 Funcionais: /, /dashboard, /pagamentos, /materiais
 Com problemas: /militantes, /cotas, /tipos-materiais, /admin/dashboard

Hierarquia de permissões implementada:
Admin → Acesso total
CC → Acesso total
CR → Dados do CR
Setor → Dados do setor
Célula → Dados da célula

Próximos passos identificados:
- Corrigir referências a Militante indefinido nos templates
- Resolver problemas de campos inexistentes
- Corrigir roteamento admin
2025-07-01 13:42:56 -03:00

180 lines
5.3 KiB
Python

from datetime import datetime, date
import logging
logger = logging.getLogger(__name__)
def validar_data(data_input):
"""
Valida se uma data é válida e não é futura
"""
if not data_input:
return False
try:
if isinstance(data_input, str):
data_obj = datetime.strptime(data_input, '%Y-%m-%d').date()
elif isinstance(data_input, datetime):
data_obj = data_input.date()
elif isinstance(data_input, date):
data_obj = data_input
else:
return False
# Verificar se não é futura
return data_obj <= date.today()
except (ValueError, TypeError):
return False
def converter_data(data_input):
"""
Converte string de data para objeto datetime
"""
if not data_input:
return None
try:
if isinstance(data_input, str):
return datetime.strptime(data_input, '%Y-%m-%d').date()
elif isinstance(data_input, datetime):
return data_input.date()
elif isinstance(data_input, date):
return data_input
else:
return None
except (ValueError, TypeError):
return None
def formatar_data(data_obj):
"""
Formata data para exibição
"""
if not data_obj:
return ""
try:
if isinstance(data_obj, str):
data_obj = datetime.strptime(data_obj, '%Y-%m-%d').date()
elif isinstance(data_obj, datetime):
data_obj = data_obj.date()
return data_obj.strftime('%d/%m/%Y')
except (ValueError, TypeError):
return ""
def validar_sequencia_datas(data_nascimento: date = None,
data_entrada: date = None,
data_efetivacao: date = None) -> None:
"""
Valida a sequência lógica entre datas.
Args:
data_nascimento: Data de nascimento
data_entrada: Data de entrada na OCI
data_efetivacao: Data de efetivação na OCI
Raises:
ValueError: Se houver inconsistência entre as datas
"""
hoje = date.today()
# Validar datas futuras
for nome, data in [
("Data de nascimento", data_nascimento),
("Data de entrada", data_entrada),
("Data de efetivação", data_efetivacao)
]:
if data and data > hoje:
logger.warning(f"{nome} no futuro: {data}")
raise ValueError(f"{nome} não pode ser no futuro")
# Validar sequência
if data_nascimento and data_entrada and data_nascimento > data_entrada:
logger.warning(f"Data de entrada ({data_entrada}) anterior à data de nascimento ({data_nascimento})")
raise ValueError("Data de entrada na OCI não pode ser anterior à data de nascimento")
if data_entrada and data_efetivacao and data_entrada > data_efetivacao:
logger.warning(f"Data de efetivação ({data_efetivacao}) anterior à data de entrada ({data_entrada})")
raise ValueError("Data de efetivação não pode ser anterior à data de entrada")
def calcular_idade(data_nascimento: date) -> int:
"""
Calcula a idade com base na data de nascimento.
Args:
data_nascimento: Data de nascimento
Returns:
int: Idade em anos
"""
if not data_nascimento:
return None
hoje = date.today()
idade = hoje.year - data_nascimento.year
# Ajustar se ainda não fez aniversário este ano
if hoje.month < data_nascimento.month or \
(hoje.month == data_nascimento.month and hoje.day < data_nascimento.day):
idade -= 1
return idade
def converter_data_br(data_str):
"""Converte string de data no formato DD/MM/YYYY para objeto date"""
if not data_str:
return None
try:
dia, mes, ano = map(int, data_str.split('/'))
return date(ano, mes, dia)
except (ValueError, TypeError) as e:
return None
def converter_data_iso(data_str):
"""Converte string de data no formato YYYY-MM-DD para objeto date"""
if not data_str:
return None
try:
return datetime.strptime(data_str, '%Y-%m-%d').date()
except (ValueError, TypeError) as e:
return None
def formatar_data_br(data):
"""Formata objeto date para string no formato DD/MM/YYYY"""
if not data:
return ''
if isinstance(data, str):
data = converter_data_iso(data) or converter_data_br(data)
if not data:
return ''
return data.strftime('%d/%m/%Y')
def formatar_data_iso(data):
"""Formata objeto date para string no formato YYYY-MM-DD"""
if not data:
return ''
if isinstance(data, str):
data = converter_data_br(data) or converter_data_iso(data)
if not data:
return ''
return data.strftime('%Y-%m-%d')
def validar_data(data, data_maxima=None, data_minima=None):
"""Valida se a data está dentro do intervalo permitido"""
if not data:
return True
if isinstance(data, str):
data = converter_data_br(data) or converter_data_iso(data)
if not data:
return False
hoje = date.today()
if data_maxima and data > data_maxima:
return False
if data_minima and data < data_minima:
return False
if data > hoje:
return False
return True