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
324 lines
13 KiB
HTML
324 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Cotas{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h1 class="mb-0">
|
|
<i class="fas fa-money-bill me-2"></i>Cotas
|
|
</h1>
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalNovaCota">
|
|
<i class="fas fa-plus me-2"></i>Nova Cota
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="input-group">
|
|
<span class="input-group-text">
|
|
<i class="fas fa-search"></i>
|
|
</span>
|
|
<input type="text" class="form-control" id="searchInput" placeholder="Pesquisar cotas...">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 text-end">
|
|
<button id="btnExportar" class="btn btn-outline-primary">
|
|
<i class="fas fa-download me-2"></i>Exportar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" id="cotasTable">
|
|
<thead>
|
|
<tr>
|
|
<th data-sort="militante">Militante <i class="fas fa-sort"></i></th>
|
|
<th data-sort="valor_antigo">Valor Antigo <i class="fas fa-sort"></i></th>
|
|
<th data-sort="valor_novo">Valor Novo <i class="fas fa-sort"></i></th>
|
|
<th data-sort="data_alteracao">Data de Alteração <i class="fas fa-sort"></i></th>
|
|
<th data-sort="data_vencimento">Data de Vencimento <i class="fas fa-sort"></i></th>
|
|
<th data-sort="status">Status <i class="fas fa-sort"></i></th>
|
|
<th class="text-end">Ações</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for cota in cotas %}
|
|
<tr>
|
|
<td data-militante="{{ cota.militante.nome }}">{{ cota.militante.nome }}</td>
|
|
<td data-valor_antigo="{{ cota.valor_antigo }}">R$ {{ "%.2f"|format(cota.valor_antigo) }}</td>
|
|
<td data-valor_novo="{{ cota.valor_novo }}">R$ {{ "%.2f"|format(cota.valor_novo) }}</td>
|
|
<td data-data_alteracao="{{ cota.data_alteracao }}">{{ cota.data_alteracao.strftime('%d/%m/%Y') }}</td>
|
|
<td data-data_vencimento="{{ cota.data_vencimento }}">{{ cota.data_vencimento.strftime('%d/%m/%Y') }}</td>
|
|
<td data-status="{{ cota.status }}">
|
|
{% if cota.status == 'paga' %}
|
|
<span class="badge bg-success">Paga</span>
|
|
{% elif cota.status == 'atrasada' %}
|
|
<span class="badge bg-danger">Atrasada</span>
|
|
{% else %}
|
|
<span class="badge bg-warning text-dark">Pendente</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-end">
|
|
<div class="btn-group">
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-primary"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#modalEditarCota"
|
|
data-cota-id="{{ cota.id }}"
|
|
data-cota-militante="{{ cota.militante_id }}"
|
|
data-cota-valor-antigo="{{ cota.valor_antigo }}"
|
|
data-cota-valor-novo="{{ cota.valor_novo }}"
|
|
data-cota-data-alteracao="{{ cota.data_alteracao.strftime('%Y-%m-%d') }}"
|
|
data-cota-data-vencimento="{{ cota.data_vencimento.strftime('%Y-%m-%d') }}"
|
|
data-cota-pago="{{ 'true' if cota.pago else 'false' }}"
|
|
title="Editar">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-danger"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#deleteModal"
|
|
data-cota-id="{{ cota.id }}"
|
|
data-cota-info="{{ cota.militante.nome }} - R$ {{ "%.2f"|format(cota.valor_novo) }}"
|
|
title="Excluir">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Nova Cota -->
|
|
<div class="modal fade" id="modalNovaCota" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-plus me-2"></i>Nova Cota
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="formNovaCota" method="post" action="{{ url_for('cota.novo') }}">
|
|
<div class="mb-3">
|
|
<label for="militante_id" class="form-label">Militante:</label>
|
|
<select class="form-select" id="militante_id" name="militante_id" required>
|
|
<option value="">Selecione um militante</option>
|
|
{% for militante in militantes %}
|
|
<option value="{{ militante.id }}">{{ militante.nome }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="valor_antigo" class="form-label">Valor Antigo:</label>
|
|
<input type="number" step="0.01" class="form-control" id="valor_antigo" name="valor_antigo" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="valor_novo" class="form-label">Valor Novo:</label>
|
|
<input type="number" step="0.01" class="form-control" id="valor_novo" name="valor_novo" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="data_alteracao" class="form-label">Data de Alteração:</label>
|
|
<input type="date" class="form-control" id="data_alteracao" name="data_alteracao" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="data_vencimento" class="form-label">Data de Vencimento:</label>
|
|
<input type="date" class="form-control" id="data_vencimento" name="data_vencimento" required>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<button type="submit" form="formNovaCota" class="btn btn-success">
|
|
<i class="fas fa-save me-2"></i>Salvar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal de Edição -->
|
|
<div class="modal fade" id="modalEditarCota" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-edit me-2"></i>Editar Cota
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="formEditarCota" method="post">
|
|
<div class="mb-3">
|
|
<label for="editMilitanteNome" class="form-label">Militante:</label>
|
|
<input type="text" class="form-control bg-light" id="editMilitanteNome" readonly>
|
|
<input type="hidden" id="editMilitante" name="militante_id">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editValorAntigo" class="form-label">Valor Antigo:</label>
|
|
<input type="number" step="0.01" class="form-control" id="editValorAntigo" name="valor_antigo" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editValorNovo" class="form-label">Valor Novo:</label>
|
|
<input type="number" step="0.01" class="form-control" id="editValorNovo" name="valor_novo" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editDataAlteracao" class="form-label">Data de Alteração:</label>
|
|
<input type="date" class="form-control" id="editDataAlteracao" name="data_alteracao" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editDataVencimento" class="form-label">Data de Vencimento:</label>
|
|
<input type="date" class="form-control" id="editDataVencimento" name="data_vencimento" required>
|
|
</div>
|
|
<div class="mb-3 form-check">
|
|
<input type="checkbox" class="form-check-input" id="editPago" name="pago">
|
|
<label class="form-check-label" for="editPago">Pago</label>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<button type="submit" form="formEditarCota" class="btn btn-success">
|
|
<i class="fas fa-save me-2"></i>Salvar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal de Exclusão -->
|
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Confirmar Exclusão</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Tem certeza que deseja excluir a cota de <strong id="cotaInfo"></strong>?</p>
|
|
<p class="text-danger mb-0">Esta ação não pode ser desfeita.</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<form action="" method="POST" id="deleteForm" class="d-inline">
|
|
<button type="submit" class="btn btn-danger">
|
|
<i class="fas fa-trash me-2"></i>Excluir
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="{{ url_for('static', filename='js/cotas.js') }}"></script>
|
|
{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* Estilo para colunas ordenáveis */
|
|
th[data-sort] {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
th[data-sort] i {
|
|
margin-left: 5px;
|
|
color: #ccc;
|
|
}
|
|
|
|
th[data-sort].sort-asc i,
|
|
th[data-sort].sort-desc i {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
/* Animação para linhas da tabela */
|
|
#cotasTable tbody tr {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
#cotasTable tbody tr:hover {
|
|
background-color: rgba(0,0,0,0.02);
|
|
transform: translateX(5px);
|
|
}
|
|
|
|
/* Estilo para botões de ação */
|
|
.btn-group .btn {
|
|
padding: 0.25rem 0.5rem;
|
|
}
|
|
|
|
.btn-group .btn i {
|
|
width: 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Responsividade */
|
|
@media (max-width: 768px) {
|
|
.btn-group {
|
|
display: flex;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
flex: 1;
|
|
}
|
|
}
|
|
|
|
/* Estilo para o backdrop com blur em todos os modais */
|
|
.modal-backdrop.show {
|
|
backdrop-filter: blur(8px);
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
}
|
|
|
|
/* Estilo para o botão de fechar dos modais */
|
|
.btn-close {
|
|
background-color: transparent;
|
|
padding: 0.5rem;
|
|
opacity: 0.8;
|
|
transition: opacity 0.2s;
|
|
filter: invert(1) grayscale(100%) brightness(200%);
|
|
}
|
|
|
|
.btn-close:hover {
|
|
opacity: 1;
|
|
background-color: transparent;
|
|
}
|
|
|
|
/* Estilo para modais */
|
|
.modal-content {
|
|
border: none;
|
|
border-radius: 12px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.modal-header {
|
|
background: linear-gradient(to right, var(--bs-gray-dark), var(--bs-gray));
|
|
color: white;
|
|
border-radius: 12px 12px 0 0;
|
|
border-bottom: none;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.modal-footer {
|
|
border-top: 1px solid #eee;
|
|
padding: 1rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|