Corrigir atualização de dados na tabela de militantes
This commit is contained in:
427
app.py
427
app.py
@@ -25,7 +25,7 @@ from functions.database import (
|
|||||||
)
|
)
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker, joinedload
|
from sqlalchemy.orm import sessionmaker, joinedload
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, date
|
||||||
from flask_bootstrap import Bootstrap5
|
from flask_bootstrap import Bootstrap5
|
||||||
from functions.validations import validar_cpf
|
from functions.validations import validar_cpf
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@@ -43,7 +43,6 @@ import qrcode
|
|||||||
import base64
|
import base64
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from create_admin import create_admin_user
|
from create_admin import create_admin_user
|
||||||
from create_test_users import create_test_users
|
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||||
import random
|
import random
|
||||||
@@ -51,6 +50,7 @@ import string
|
|||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from flask_wtf.csrf import CSRFProtect
|
from flask_wtf.csrf import CSRFProtect
|
||||||
import json
|
import json
|
||||||
|
from utils.date_utils import validar_data, converter_data, validar_sequencia_datas, calcular_idade
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -615,14 +615,17 @@ def create_app():
|
|||||||
@require_login
|
@require_login
|
||||||
def novo_pagamento():
|
def novo_pagamento():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
militante_id = request.form.get("militante_id")
|
|
||||||
tipo_pagamento_id = request.form.get("tipo_pagamento_id")
|
|
||||||
valor = float(request.form.get("valor"))
|
|
||||||
data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date()
|
|
||||||
|
|
||||||
# Criar novo pagamento
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
try:
|
||||||
|
militante_id = request.form.get("militante_id")
|
||||||
|
tipo_pagamento_id = request.form.get("tipo_pagamento_id")
|
||||||
|
valor = float(request.form.get("valor"))
|
||||||
|
data_pagamento = converter_data(request.form.get("data_pagamento"))
|
||||||
|
|
||||||
|
if not validar_data(data_pagamento):
|
||||||
|
flash('Data de pagamento inválida ou futura', 'danger')
|
||||||
|
return redirect(url_for('novo_pagamento'))
|
||||||
|
|
||||||
|
db = get_db_connection()
|
||||||
pagamento = Pagamento(
|
pagamento = Pagamento(
|
||||||
militante_id=militante_id,
|
militante_id=militante_id,
|
||||||
tipo_pagamento_id=tipo_pagamento_id,
|
tipo_pagamento_id=tipo_pagamento_id,
|
||||||
@@ -635,9 +638,9 @@ def create_app():
|
|||||||
return redirect(url_for('listar_pagamentos'))
|
return redirect(url_for('listar_pagamentos'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
print(f"Erro ao cadastrar pagamento: {e}")
|
app.logger.error(f"Erro ao cadastrar pagamento: {e}")
|
||||||
flash('Erro ao cadastrar pagamento', 'danger')
|
flash('Erro ao cadastrar pagamento', 'danger')
|
||||||
return render_template("novo_pagamento.html")
|
return redirect(url_for('novo_pagamento'))
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
@@ -687,7 +690,7 @@ def create_app():
|
|||||||
militante_id = request.form.get("militante_id")
|
militante_id = request.form.get("militante_id")
|
||||||
tipo_pagamento = request.form.get("tipo_pagamento")
|
tipo_pagamento = request.form.get("tipo_pagamento")
|
||||||
valor = float(request.form.get("valor"))
|
valor = float(request.form.get("valor"))
|
||||||
data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date()
|
data_pagamento = converter_data(request.form.get("data_pagamento"))
|
||||||
|
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
pagamento = Pagamento(
|
pagamento = Pagamento(
|
||||||
@@ -718,7 +721,7 @@ def create_app():
|
|||||||
tipo_material_id = request.form.get('tipo_material_id')
|
tipo_material_id = request.form.get('tipo_material_id')
|
||||||
descricao = request.form.get('descricao')
|
descricao = request.form.get('descricao')
|
||||||
valor = float(request.form.get('valor'))
|
valor = float(request.form.get('valor'))
|
||||||
data_venda = datetime.strptime(request.form.get('data_venda'), '%Y-%m-%d')
|
data_venda = converter_data(request.form.get('data_venda'))
|
||||||
|
|
||||||
material = MaterialVendido(
|
material = MaterialVendido(
|
||||||
militante_id=militante_id,
|
militante_id=militante_id,
|
||||||
@@ -775,44 +778,51 @@ def create_app():
|
|||||||
@require_login
|
@require_login
|
||||||
@require_permission(Permission.VIEW_CELL_REPORTS)
|
@require_permission(Permission.VIEW_CELL_REPORTS)
|
||||||
def nova_venda_jornal():
|
def nova_venda_jornal():
|
||||||
db = get_db_connection()
|
if request.method == "POST":
|
||||||
try:
|
try:
|
||||||
militante_id = request.form.get('militante_id')
|
militante_id = request.form.get('militante_id')
|
||||||
quantidade = int(request.form.get('quantidade'))
|
quantidade = int(request.form.get('quantidade'))
|
||||||
valor_total = float(request.form.get('valor_total'))
|
valor_total = float(request.form.get('valor_total'))
|
||||||
data_venda = datetime.strptime(request.form.get('data_venda'), '%Y-%m-%d')
|
data_venda = converter_data(request.form.get('data_venda'))
|
||||||
|
|
||||||
venda = VendaJornalAvulso(
|
if not validar_data(data_venda):
|
||||||
militante_id=militante_id,
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
quantidade=quantidade,
|
return jsonify({
|
||||||
valor_total=valor_total,
|
'status': 'error',
|
||||||
data_venda=data_venda
|
'message': 'Data de venda inválida ou futura'
|
||||||
)
|
}), 400
|
||||||
|
flash('Data de venda inválida ou futura', 'danger')
|
||||||
|
return redirect(url_for('nova_venda_jornal'))
|
||||||
|
|
||||||
db.add(venda)
|
db = get_db_connection()
|
||||||
db.commit()
|
venda = VendaJornalAvulso(
|
||||||
|
militante_id=militante_id,
|
||||||
|
quantidade=quantidade,
|
||||||
|
valor_total=valor_total,
|
||||||
|
data_venda=data_venda
|
||||||
|
)
|
||||||
|
db.add(venda)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'message': 'Venda cadastrada com sucesso!'
|
'message': 'Venda cadastrada com sucesso!'
|
||||||
})
|
})
|
||||||
|
flash('Venda cadastrada com sucesso!', 'success')
|
||||||
flash('Venda cadastrada com sucesso!', 'success')
|
return redirect(url_for('listar_vendas_jornal'))
|
||||||
return redirect(url_for('listar_vendas_jornal'))
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
except Exception as e:
|
app.logger.error(f"Erro ao cadastrar venda: {e}")
|
||||||
db.rollback()
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
return jsonify({
|
||||||
return jsonify({
|
'status': 'error',
|
||||||
'status': 'error',
|
'message': 'Erro ao cadastrar venda'
|
||||||
'message': 'Erro ao cadastrar venda. Por favor, tente novamente.'
|
}), 400
|
||||||
}), 400
|
flash('Erro ao cadastrar venda', 'danger')
|
||||||
|
return redirect(url_for('nova_venda_jornal'))
|
||||||
flash('Erro ao cadastrar venda. Por favor, tente novamente.', 'error')
|
finally:
|
||||||
return redirect(url_for('listar_vendas_jornal'))
|
db.close()
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
# Rota para listar vendas de jornal
|
# Rota para listar vendas de jornal
|
||||||
@app.route("/jornais")
|
@app.route("/jornais")
|
||||||
@@ -835,36 +845,56 @@ def create_app():
|
|||||||
@require_permission(Permission.VIEW_CELL_REPORTS)
|
@require_permission(Permission.VIEW_CELL_REPORTS)
|
||||||
def novo_relatorio_cotas():
|
def novo_relatorio_cotas():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
setor_id = request.form.get("setor_id")
|
|
||||||
comite_id = request.form.get("comite_id")
|
|
||||||
total_cotas = float(request.form.get("total_cotas"))
|
|
||||||
data_relatorio = datetime.strptime(request.form.get("data_relatorio"), "%Y-%m-%d").date()
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
try:
|
||||||
relatorio_cotas_mensais = RelatorioCotasMensais(
|
setor_id = request.form.get("setor_id")
|
||||||
setor_id=setor_id,
|
comite_id = request.form.get("comite_id")
|
||||||
comite_id=comite_id,
|
total_cotas = float(request.form.get("total_cotas"))
|
||||||
total_cotas=total_cotas,
|
data_relatorio = request.form.get("data_relatorio")
|
||||||
data_relatorio=data_relatorio
|
|
||||||
)
|
# Validar data
|
||||||
db.add(relatorio_cotas_mensais)
|
if not validar_data(data_relatorio):
|
||||||
db.commit()
|
flash('Data do relatório inválida', 'danger')
|
||||||
flash('Relatório de cotas cadastrado com sucesso!', 'success')
|
return render_template("novo_relatorio_cotas.html")
|
||||||
return redirect(url_for('listar_relatorios_cotas'))
|
|
||||||
except Exception as e:
|
# Converter data
|
||||||
db.rollback()
|
data_relatorio = converter_data(data_relatorio)
|
||||||
print(f"Erro ao cadastrar relatório de cotas: {e}")
|
|
||||||
flash('Erro ao cadastrar relatório de cotas', 'danger')
|
# Validar data futura
|
||||||
|
if data_relatorio > date.today():
|
||||||
|
flash('A data do relatório não pode ser futura', 'danger')
|
||||||
|
return render_template("novo_relatorio_cotas.html")
|
||||||
|
|
||||||
|
db = get_db_connection()
|
||||||
|
try:
|
||||||
|
relatorio_cotas_mensais = RelatorioCotasMensais(
|
||||||
|
setor_id=setor_id,
|
||||||
|
comite_id=comite_id,
|
||||||
|
total_cotas=total_cotas,
|
||||||
|
data_relatorio=data_relatorio
|
||||||
|
)
|
||||||
|
db.add(relatorio_cotas_mensais)
|
||||||
|
db.commit()
|
||||||
|
flash('Relatório de cotas cadastrado com sucesso!', 'success')
|
||||||
|
return redirect(url_for('listar_relatorios_cotas'))
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
app.logger.error(f"Erro ao cadastrar relatório de cotas: {e}")
|
||||||
|
flash('Erro ao cadastrar relatório de cotas', 'danger')
|
||||||
|
return render_template("novo_relatorio_cotas.html")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
except ValueError as e:
|
||||||
|
flash(str(e), 'danger')
|
||||||
return render_template("novo_relatorio_cotas.html")
|
return render_template("novo_relatorio_cotas.html")
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
try:
|
try:
|
||||||
setores = db.query(Setor).order_by(Setor.nome).all()
|
setores = db.query(Setor).order_by(Setor.nome).all()
|
||||||
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all()
|
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all()
|
||||||
return render_template("novo_relatorio_cotas.html", setores=setores, comites=comites)
|
return render_template("novo_relatorio_cotas.html",
|
||||||
|
setores=setores,
|
||||||
|
comites=comites,
|
||||||
|
hoje=date.today().strftime('%Y-%m-%d'))
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
@@ -890,36 +920,56 @@ def create_app():
|
|||||||
@require_permission(Permission.VIEW_CELL_REPORTS)
|
@require_permission(Permission.VIEW_CELL_REPORTS)
|
||||||
def novo_relatorio_vendas():
|
def novo_relatorio_vendas():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
setor_id = request.form.get("setor_id")
|
|
||||||
comite_id = request.form.get("comite_id")
|
|
||||||
total_vendas = float(request.form.get("total_vendas"))
|
|
||||||
data_relatorio = datetime.strptime(request.form.get("data_relatorio"), "%Y-%m-%d").date()
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
try:
|
||||||
relatorio_vendas_materiais = RelatorioVendasMateriais(
|
setor_id = request.form.get("setor_id")
|
||||||
setor_id=setor_id,
|
comite_id = request.form.get("comite_id")
|
||||||
comite_id=comite_id,
|
total_vendas = float(request.form.get("total_vendas"))
|
||||||
total_vendas=total_vendas,
|
data_relatorio = request.form.get("data_relatorio")
|
||||||
data_relatorio=data_relatorio
|
|
||||||
)
|
# Validar data
|
||||||
db.add(relatorio_vendas_materiais)
|
if not validar_data(data_relatorio):
|
||||||
db.commit()
|
flash('Data do relatório inválida', 'danger')
|
||||||
flash('Relatório de vendas cadastrado com sucesso!', 'success')
|
return render_template("novo_relatorio_vendas.html")
|
||||||
return redirect(url_for('listar_relatorios_vendas'))
|
|
||||||
except Exception as e:
|
# Converter data
|
||||||
db.rollback()
|
data_relatorio = converter_data(data_relatorio)
|
||||||
print(f"Erro ao cadastrar relatório de vendas: {e}")
|
|
||||||
flash('Erro ao cadastrar relatório de vendas', 'danger')
|
# Validar data futura
|
||||||
|
if data_relatorio > date.today():
|
||||||
|
flash('A data do relatório não pode ser futura', 'danger')
|
||||||
|
return render_template("novo_relatorio_vendas.html")
|
||||||
|
|
||||||
|
db = get_db_connection()
|
||||||
|
try:
|
||||||
|
relatorio_vendas_materiais = RelatorioVendasMateriais(
|
||||||
|
setor_id=setor_id,
|
||||||
|
comite_id=comite_id,
|
||||||
|
total_vendas=total_vendas,
|
||||||
|
data_relatorio=data_relatorio
|
||||||
|
)
|
||||||
|
db.add(relatorio_vendas_materiais)
|
||||||
|
db.commit()
|
||||||
|
flash('Relatório de vendas cadastrado com sucesso!', 'success')
|
||||||
|
return redirect(url_for('listar_relatorios_vendas'))
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
app.logger.error(f"Erro ao cadastrar relatório de vendas: {e}")
|
||||||
|
flash('Erro ao cadastrar relatório de vendas', 'danger')
|
||||||
|
return render_template("novo_relatorio_vendas.html")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
except ValueError as e:
|
||||||
|
flash(str(e), 'danger')
|
||||||
return render_template("novo_relatorio_vendas.html")
|
return render_template("novo_relatorio_vendas.html")
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
try:
|
try:
|
||||||
setores = db.query(Setor).order_by(Setor.nome).all()
|
setores = db.query(Setor).order_by(Setor.nome).all()
|
||||||
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all()
|
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all()
|
||||||
return render_template("novo_relatorio_vendas.html", setores=setores, comites=comites)
|
return render_template("novo_relatorio_vendas.html",
|
||||||
|
setores=setores,
|
||||||
|
comites=comites,
|
||||||
|
hoje=date.today().strftime('%Y-%m-%d'))
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
@@ -945,8 +995,8 @@ def create_app():
|
|||||||
@require_permission('gerenciar_militantes')
|
@require_permission('gerenciar_militantes')
|
||||||
def editar_militante(militante_id):
|
def editar_militante(militante_id):
|
||||||
try:
|
try:
|
||||||
# Verificar se o militante existe
|
db = get_db_connection()
|
||||||
militante = db_session.query(Militante).get(militante_id)
|
militante = db.query(Militante).get(militante_id)
|
||||||
if not militante:
|
if not militante:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
@@ -964,15 +1014,29 @@ def create_app():
|
|||||||
telefone2 = request.form.get('telefone2')
|
telefone2 = request.form.get('telefone2')
|
||||||
email = request.form.get('email')
|
email = request.form.get('email')
|
||||||
|
|
||||||
# Converter datas para objetos date
|
# Validar e converter datas
|
||||||
data_nascimento = datetime.strptime(data_nascimento, '%Y-%m-%d').date() if data_nascimento else None
|
try:
|
||||||
data_entrada_oci = datetime.strptime(data_entrada_oci, '%Y-%m-%d').date() if data_entrada_oci else None
|
data_nascimento = converter_data(data_nascimento) if data_nascimento else None
|
||||||
data_efetivacao_oci = datetime.strptime(data_efetivacao_oci, '%Y-%m-%d').date() if data_efetivacao_oci else None
|
data_entrada_oci = converter_data(data_entrada_oci) if data_entrada_oci else None
|
||||||
|
data_efetivacao_oci = converter_data(data_efetivacao_oci) if data_efetivacao_oci else None
|
||||||
|
|
||||||
|
# Validar sequência lógica das datas
|
||||||
|
validar_sequencia_datas(
|
||||||
|
data_nascimento=data_nascimento,
|
||||||
|
data_entrada=data_entrada_oci,
|
||||||
|
data_efetivacao=data_efetivacao_oci
|
||||||
|
)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': str(e)
|
||||||
|
}), 400
|
||||||
|
|
||||||
# Atualizar dados básicos
|
# Atualizar dados básicos
|
||||||
militante.nome = nome
|
if nome: militante.nome = nome
|
||||||
militante.cpf = cpf
|
if cpf: militante.cpf = cpf
|
||||||
militante.titulo_eleitoral = titulo_eleitoral
|
if titulo_eleitoral: militante.titulo_eleitoral = titulo_eleitoral
|
||||||
militante.data_nascimento = data_nascimento
|
militante.data_nascimento = data_nascimento
|
||||||
militante.data_entrada_oci = data_entrada_oci
|
militante.data_entrada_oci = data_entrada_oci
|
||||||
militante.data_efetivacao_oci = data_efetivacao_oci
|
militante.data_efetivacao_oci = data_efetivacao_oci
|
||||||
@@ -981,70 +1045,45 @@ def create_app():
|
|||||||
|
|
||||||
# Atualizar email
|
# Atualizar email
|
||||||
if email:
|
if email:
|
||||||
# Verificar se já existe um email para este militante
|
# Verificar se já existe email
|
||||||
email_existente = db_session.query(EmailMilitante).filter_by(militante_id=militante_id).first()
|
email_existente = db.query(EmailMilitante).filter_by(militante_id=militante_id).first()
|
||||||
if email_existente:
|
if email_existente:
|
||||||
email_existente.endereco_email = email
|
email_existente.endereco_email = email
|
||||||
else:
|
else:
|
||||||
novo_email = EmailMilitante(endereco_email=email, militante_id=militante_id)
|
novo_email = EmailMilitante(endereco_email=email, militante_id=militante_id)
|
||||||
db_session.add(novo_email)
|
db.add(novo_email)
|
||||||
|
|
||||||
# Atualizar endereço
|
# Calcular idade
|
||||||
endereco = militante.endereco or Endereco(militante_id=militante_id)
|
militante.idade = calcular_idade(data_nascimento) if data_nascimento else None
|
||||||
endereco.cep = request.form.get('cep')
|
|
||||||
endereco.estado = request.form.get('estado')
|
|
||||||
endereco.cidade = request.form.get('cidade')
|
|
||||||
endereco.bairro = request.form.get('bairro')
|
|
||||||
endereco.rua = request.form.get('rua')
|
|
||||||
endereco.numero = request.form.get('numero')
|
|
||||||
endereco.complemento = request.form.get('complemento')
|
|
||||||
|
|
||||||
if not militante.endereco:
|
|
||||||
militante.endereco = endereco
|
|
||||||
db_session.add(endereco)
|
|
||||||
|
|
||||||
# Atualizar dados profissionais
|
|
||||||
militante.empresa = request.form.get('empresa')
|
|
||||||
militante.contratante = request.form.get('contratante')
|
|
||||||
militante.instituicao_ensino = request.form.get('instituicao_ensino')
|
|
||||||
militante.tipo_instituicao = request.form.get('tipo_instituicao')
|
|
||||||
|
|
||||||
# Atualizar dados sindicais
|
|
||||||
militante.sindicato = request.form.get('sindicato')
|
|
||||||
militante.cargo_sindical = request.form.get('cargo_sindical')
|
|
||||||
militante.central_sindical = request.form.get('central_sindical')
|
|
||||||
militante.dirigente_sindical = request.form.get('dirigente_sindical') == 'true'
|
|
||||||
|
|
||||||
# Atualizar responsabilidades
|
|
||||||
try:
|
try:
|
||||||
responsabilidades_valor = request.form.get('responsabilidades_valor')
|
db.commit()
|
||||||
if responsabilidades_valor:
|
return jsonify({
|
||||||
militante.responsabilidades = int(responsabilidades_valor)
|
'status': 'success',
|
||||||
print(f"Responsabilidades atualizadas para: {militante.responsabilidades}")
|
'message': 'Militante atualizado com sucesso',
|
||||||
else:
|
'data': {
|
||||||
militante.responsabilidades = 0
|
'nome': militante.nome,
|
||||||
print("Nenhuma responsabilidade definida")
|
'cpf': militante.cpf,
|
||||||
except (ValueError, TypeError) as e:
|
'idade': militante.idade,
|
||||||
print(f"Erro ao processar responsabilidades: {e}")
|
'emails': [e.endereco_email for e in militante.emails],
|
||||||
militante.responsabilidades = 0
|
'telefone1': militante.telefone1,
|
||||||
|
'celula_id': str(militante.celula_id) if militante.celula_id else None,
|
||||||
# Salvar alterações
|
'responsabilidades_valor': militante.responsabilidades
|
||||||
db_session.commit()
|
}
|
||||||
print("Alterações salvas com sucesso!")
|
})
|
||||||
|
except Exception as e:
|
||||||
# Retornar resposta
|
db.rollback()
|
||||||
return jsonify({
|
app.logger.error(f"Erro ao salvar militante: {e}")
|
||||||
'status': 'success',
|
return jsonify({
|
||||||
'message': 'Militante atualizado com sucesso!',
|
'status': 'error',
|
||||||
'responsabilidades': militante.get_responsabilidades()
|
'message': 'Erro ao salvar alterações no banco de dados'
|
||||||
})
|
}), 500
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db_session.rollback()
|
app.logger.error(f"Erro ao editar militante: {e}")
|
||||||
print(f"Erro ao salvar alterações: {e}")
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': str(e)
|
'message': 'Erro interno do servidor'
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
# Rota para criar um novo usuário
|
# Rota para criar um novo usuário
|
||||||
@@ -1521,64 +1560,64 @@ def create_app():
|
|||||||
@require_login
|
@require_login
|
||||||
@require_permission('gerenciar_militantes')
|
@require_permission('gerenciar_militantes')
|
||||||
def buscar_dados_militante(militante_id):
|
def buscar_dados_militante(militante_id):
|
||||||
"""Retorna os dados de um militante"""
|
"""Busca os dados de um militante específico"""
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
try:
|
try:
|
||||||
militante = db.query(Militante).options(
|
militante = db.query(Militante).get(militante_id)
|
||||||
joinedload(Militante.endereco),
|
|
||||||
joinedload(Militante.emails)
|
|
||||||
).get(militante_id)
|
|
||||||
|
|
||||||
if not militante:
|
if not militante:
|
||||||
|
print(f"Militante não encontrado: ID {militante_id}")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': 'Militante não encontrado'
|
'message': 'Militante não encontrado'
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
# Preparar dados do militante
|
# Função auxiliar para formatar data com validação
|
||||||
dados = {
|
def formatar_data_segura(data):
|
||||||
|
try:
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
return data.strftime('%Y-%m-%d')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao formatar data: {str(e)}, valor: {data}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Formatar datas com validação
|
||||||
|
data_nascimento = formatar_data_segura(militante.data_nascimento)
|
||||||
|
data_entrada_oci = formatar_data_segura(militante.data_entrada_oci)
|
||||||
|
data_efetivacao_oci = formatar_data_segura(militante.data_efetivacao_oci)
|
||||||
|
|
||||||
|
print(f"Dados do militante {militante_id} recuperados com sucesso")
|
||||||
|
print(f"Datas formatadas: nascimento={data_nascimento}, entrada={data_entrada_oci}, efetivação={data_efetivacao_oci}")
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
'id': militante.id,
|
'id': militante.id,
|
||||||
'nome': militante.nome,
|
'nome': militante.nome,
|
||||||
'cpf': militante.cpf,
|
'cpf': militante.cpf,
|
||||||
'titulo_eleitoral': militante.titulo_eleitoral,
|
'titulo_eleitoral': militante.titulo_eleitoral,
|
||||||
'data_nascimento': militante.data_nascimento.strftime('%Y-%m-%d') if militante.data_nascimento else None,
|
'data_nascimento': data_nascimento,
|
||||||
'data_entrada_oci': militante.data_entrada_oci.strftime('%Y-%m-%d') if militante.data_entrada_oci else None,
|
'data_entrada_oci': data_entrada_oci,
|
||||||
'data_efetivacao_oci': militante.data_efetivacao_oci.strftime('%Y-%m-%d') if militante.data_efetivacao_oci else None,
|
'data_efetivacao_oci': data_efetivacao_oci,
|
||||||
|
'emails': [email.endereco_email for email in militante.emails] if militante.emails else [],
|
||||||
'telefone1': militante.telefone1,
|
'telefone1': militante.telefone1,
|
||||||
'telefone2': militante.telefone2,
|
'telefone2': militante.telefone2,
|
||||||
'emails': [email.endereco_email for email in militante.emails],
|
'celula_id': militante.celula_id,
|
||||||
'profissao': militante.profissao,
|
'responsabilidades_valor': militante.responsabilidades,
|
||||||
'regime_trabalho': militante.regime_trabalho,
|
|
||||||
'empresa': militante.empresa,
|
|
||||||
'contratante': militante.contratante,
|
|
||||||
'instituicao_ensino': militante.instituicao_ensino,
|
|
||||||
'tipo_instituicao': militante.tipo_instituicao,
|
|
||||||
'sindicato': militante.sindicato,
|
'sindicato': militante.sindicato,
|
||||||
'cargo_sindical': militante.cargo_sindical,
|
'cargo_sindical': militante.cargo_sindical,
|
||||||
'dirigente_sindical': militante.dirigente_sindical,
|
|
||||||
'central_sindical': militante.central_sindical,
|
'central_sindical': militante.central_sindical,
|
||||||
'celula_id': militante.celula_id,
|
'dirigente_sindical': militante.dirigente_sindical
|
||||||
'estado': militante.estado.name if militante.estado else None,
|
})
|
||||||
'responsabilidades_valor': militante.responsabilidades,
|
|
||||||
'endereco': {
|
|
||||||
'cep': militante.endereco.cep if militante.endereco else None,
|
|
||||||
'rua': militante.endereco.rua if militante.endereco else None,
|
|
||||||
'numero': militante.endereco.numero if militante.endereco else None,
|
|
||||||
'complemento': militante.endereco.complemento if militante.endereco else None,
|
|
||||||
'bairro': militante.endereco.bairro if militante.endereco else None,
|
|
||||||
'cidade': militante.endereco.cidade if militante.endereco else None,
|
|
||||||
'estado': militante.endereco.estado if militante.endereco else None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonify(dados)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erro ao buscar dados do militante: {str(e)}")
|
import traceback
|
||||||
|
print(f"Erro ao buscar dados do militante {militante_id}:")
|
||||||
|
print(traceback.format_exc())
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': 'Erro ao buscar dados do militante'
|
'message': f'Erro ao buscar dados do militante: {str(e)}'
|
||||||
}), 500
|
}), 500
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
@@ -1624,7 +1663,7 @@ def init_system():
|
|||||||
print("OTP: Nova configuração gerada")
|
print("OTP: Nova configuração gerada")
|
||||||
else:
|
else:
|
||||||
# Criar usuário de teste se não existir
|
# Criar usuário de teste se não existir
|
||||||
create_test_users()
|
create_admin_user()
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -561,3 +561,51 @@ input.btn-secondary:hover,
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Estilos para alertas */
|
||||||
|
.alert {
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 9999;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 600px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem 2.5rem 1rem 1rem;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert .btn-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 1rem;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: #0f5132;
|
||||||
|
background-color: #d1e7dd;
|
||||||
|
border-color: #badbcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
color: #842029;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-color: #f5c2c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: #664d03;
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-color: #ffecb5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: #055160;
|
||||||
|
background-color: #cff4fc;
|
||||||
|
border-color: #b6effb;
|
||||||
|
}
|
||||||
53
static/css/styles.css
Normal file
53
static/css/styles.css
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/* Estilos globais para alertas do sistema */
|
||||||
|
.alert {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilo base para o botão de fechar */
|
||||||
|
.alert .btn-close {
|
||||||
|
filter: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Success */
|
||||||
|
.alert-success .btn-close {
|
||||||
|
background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23198754'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Danger */
|
||||||
|
.alert-danger .btn-close {
|
||||||
|
background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23842029'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Warning */
|
||||||
|
.alert-warning .btn-close {
|
||||||
|
background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23997404'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Info */
|
||||||
|
.alert-info .btn-close {
|
||||||
|
background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23055160'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Efeito hover para todos os botões de fechar */
|
||||||
|
.alert .btn-close:hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilo das abas do modal */
|
||||||
|
.nav-tabs .nav-link {
|
||||||
|
/* remover estilos */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
/* remover estilos */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link:hover:not(.active) {
|
||||||
|
/* remover estilos */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link i {
|
||||||
|
/* remover estilos */
|
||||||
|
}
|
||||||
1
static/img/favicon.ico
Normal file
1
static/img/favicon.ico
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -106,30 +106,87 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Validação de datas
|
// Validação de datas
|
||||||
const dateInputs = document.querySelectorAll('input[type="date"]');
|
const dateInputs = document.querySelectorAll('input[type="date"], input.date-mask');
|
||||||
dateInputs.forEach(input => {
|
dateInputs.forEach(input => {
|
||||||
input.addEventListener('change', function() {
|
input.addEventListener('change', function() {
|
||||||
const date = new Date(this.value);
|
console.log('Validando data:', this.value);
|
||||||
const today = new Date();
|
|
||||||
|
|
||||||
if (this.hasAttribute('min')) {
|
let dataValida = true;
|
||||||
const minDate = new Date(this.getAttribute('min'));
|
let mensagemErro = '';
|
||||||
if (date < minDate) {
|
|
||||||
this.setCustomValidity(`A data não pode ser anterior a ${minDate.toLocaleDateString()}`);
|
// Se for um campo com máscara, validar o formato
|
||||||
this.classList.add('is-invalid');
|
if (this.classList.contains('date-mask')) {
|
||||||
return;
|
if (!validarData(this.value)) {
|
||||||
|
dataValida = false;
|
||||||
|
mensagemErro = 'Por favor, insira uma data válida no formato DD/MM/AAAA';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Para campos type="date", converter para Date
|
||||||
|
const date = new Date(this.value);
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
dataValida = false;
|
||||||
|
mensagemErro = 'Data inválida';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasAttribute('max')) {
|
// Validar limites de data
|
||||||
const maxDate = new Date(this.getAttribute('max'));
|
if (dataValida) {
|
||||||
if (date > maxDate) {
|
const hoje = new Date();
|
||||||
this.setCustomValidity(`A data não pode ser posterior a ${maxDate.toLocaleDateString()}`);
|
hoje.setHours(0, 0, 0, 0);
|
||||||
this.classList.add('is-invalid');
|
|
||||||
return;
|
let dataComparacao;
|
||||||
|
if (this.classList.contains('date-mask')) {
|
||||||
|
const [dia, mes, ano] = this.value.split('/').map(Number);
|
||||||
|
dataComparacao = new Date(ano, mes - 1, dia);
|
||||||
|
} else {
|
||||||
|
dataComparacao = new Date(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar data mínima
|
||||||
|
if (this.hasAttribute('min')) {
|
||||||
|
const minDate = new Date(this.getAttribute('min'));
|
||||||
|
if (dataComparacao < minDate) {
|
||||||
|
dataValida = false;
|
||||||
|
mensagemErro = `A data não pode ser anterior a ${minDate.toLocaleDateString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar data máxima
|
||||||
|
if (this.hasAttribute('max')) {
|
||||||
|
const maxDate = new Date(this.getAttribute('max'));
|
||||||
|
if (dataComparacao > maxDate) {
|
||||||
|
dataValida = false;
|
||||||
|
mensagemErro = `A data não pode ser posterior a ${maxDate.toLocaleDateString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se é data futura (quando não permitido)
|
||||||
|
if (this.hasAttribute('data-no-future') && dataComparacao > hoje) {
|
||||||
|
dataValida = false;
|
||||||
|
mensagemErro = 'A data não pode ser futura';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Atualizar validação do campo
|
||||||
|
if (!dataValida) {
|
||||||
|
console.warn('Data inválida:', this.value, mensagemErro);
|
||||||
|
this.setCustomValidity(mensagemErro);
|
||||||
|
this.classList.add('is-invalid');
|
||||||
|
|
||||||
|
// Atualizar mensagem de feedback
|
||||||
|
const feedback = this.nextElementSibling;
|
||||||
|
if (feedback && feedback.classList.contains('invalid-feedback')) {
|
||||||
|
feedback.textContent = mensagemErro;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Data válida:', this.value);
|
||||||
|
this.setCustomValidity('');
|
||||||
|
this.classList.remove('is-invalid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Limpar validação ao começar a digitar
|
||||||
|
input.addEventListener('input', function() {
|
||||||
this.setCustomValidity('');
|
this.setCustomValidity('');
|
||||||
this.classList.remove('is-invalid');
|
this.classList.remove('is-invalid');
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,10 +6,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
language: {
|
language: {
|
||||||
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
||||||
},
|
},
|
||||||
order: [[3, 'desc']], // Ordenar por data de pagamento (decrescente)
|
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{ targets: -1, orderable: false } // Desabilitar ordenação na coluna de ações
|
{
|
||||||
]
|
targets: 3, // Coluna de data
|
||||||
|
type: 'date-br',
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'sort') {
|
||||||
|
return data.split('/').reverse().join('');
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: 2, // Coluna de valor
|
||||||
|
type: 'numeric',
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'sort') {
|
||||||
|
return parseFloat(data.replace('R$ ', '').replace(',', '.'));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ targets: -1, orderable: false } // Coluna de ações
|
||||||
|
],
|
||||||
|
order: [[3, 'desc']] // Ordenar por data decrescente por padrão
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configuração do modal de edição
|
// Configuração do modal de edição
|
||||||
@@ -253,4 +273,44 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Funções de validação e formatação de datas
|
||||||
|
function validarData(data) {
|
||||||
|
if (!data) return false;
|
||||||
|
|
||||||
|
const dataObj = new Date(data);
|
||||||
|
if (isNaN(dataObj.getTime())) return false;
|
||||||
|
|
||||||
|
const hoje = new Date();
|
||||||
|
hoje.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return dataObj <= hoje;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatarData(data) {
|
||||||
|
if (!data) return '';
|
||||||
|
|
||||||
|
const dataObj = new Date(data);
|
||||||
|
if (isNaN(dataObj.getTime())) return '';
|
||||||
|
|
||||||
|
return dataObj.toLocaleDateString('pt-BR');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar campos de data
|
||||||
|
const camposData = document.querySelectorAll('input[type="date"]');
|
||||||
|
camposData.forEach(campo => {
|
||||||
|
// Definir data máxima como hoje
|
||||||
|
const hoje = new Date().toISOString().split('T')[0];
|
||||||
|
campo.setAttribute('max', hoje);
|
||||||
|
|
||||||
|
campo.addEventListener('change', function() {
|
||||||
|
if (!validarData(this.value)) {
|
||||||
|
this.setCustomValidity('Data inválida ou futura');
|
||||||
|
this.classList.add('is-invalid');
|
||||||
|
} else {
|
||||||
|
this.setCustomValidity('');
|
||||||
|
this.classList.remove('is-invalid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
200
static/js/table_sort.js
Normal file
200
static/js/table_sort.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
// Função para converter data DD/MM/YYYY para objeto Date
|
||||||
|
function converterDataParaComparacao(dataStr) {
|
||||||
|
console.log('Convertendo data para comparação:', dataStr);
|
||||||
|
|
||||||
|
if (!dataStr) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Se já estiver no formato ISO
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}/.test(dataStr)) {
|
||||||
|
const data = new Date(dataStr);
|
||||||
|
console.log('Data ISO convertida:', data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se estiver no formato DD/MM/YYYY
|
||||||
|
if (/^\d{2}\/\d{2}\/\d{4}/.test(dataStr)) {
|
||||||
|
const [dia, mes, ano] = dataStr.split('/').map(Number);
|
||||||
|
const data = new Date(ano, mes - 1, dia);
|
||||||
|
console.log('Data DD/MM/YYYY convertida:', data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('Formato de data não reconhecido:', dataStr);
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao converter data:', error, 'Data:', dataStr);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para ordenar tabelas
|
||||||
|
function configurarOrdenacaoTabela(tabelaId) {
|
||||||
|
console.log('Configurando ordenação para tabela:', tabelaId);
|
||||||
|
|
||||||
|
const table = document.getElementById(tabelaId);
|
||||||
|
if (!table) {
|
||||||
|
console.warn('Tabela não encontrada:', tabelaId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = table.querySelectorAll('th[data-sort]');
|
||||||
|
headers.forEach(header => {
|
||||||
|
if (header.dataset.sort) {
|
||||||
|
header.addEventListener('click', () => {
|
||||||
|
const column = header.dataset.sort;
|
||||||
|
const tbody = table.getElementsByTagName('tbody')[0];
|
||||||
|
const rows = Array.from(tbody.getElementsByTagName('tr'));
|
||||||
|
|
||||||
|
console.log('Ordenando coluna:', column);
|
||||||
|
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const aValue = a.querySelector(`td[data-${column}]`).dataset[column];
|
||||||
|
const bValue = b.querySelector(`td[data-${column}]`).dataset[column];
|
||||||
|
|
||||||
|
// Ordenação por data
|
||||||
|
if (column === 'data' ||
|
||||||
|
column === 'data_vencimento' ||
|
||||||
|
column === 'data_alteracao' ||
|
||||||
|
column === 'data_pagamento' ||
|
||||||
|
column === 'data_venda' ||
|
||||||
|
column === 'data_relatorio') {
|
||||||
|
const aDate = converterDataParaComparacao(aValue);
|
||||||
|
const bDate = converterDataParaComparacao(bValue);
|
||||||
|
|
||||||
|
// Se alguma data for inválida
|
||||||
|
if (!aDate && !bDate) return 0;
|
||||||
|
if (!aDate) return 1;
|
||||||
|
if (!bDate) return -1;
|
||||||
|
|
||||||
|
return aDate - bDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenação por valor monetário
|
||||||
|
if (column === 'valor' ||
|
||||||
|
column === 'valor_total' ||
|
||||||
|
column === 'valor_antigo' ||
|
||||||
|
column === 'valor_novo') {
|
||||||
|
const aNum = parseFloat(aValue.replace(/[^\d,-]/g, '').replace(',', '.'));
|
||||||
|
const bNum = parseFloat(bValue.replace(/[^\d,-]/g, '').replace(',', '.'));
|
||||||
|
return aNum - bNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenação padrão para texto
|
||||||
|
return aValue.localeCompare(bValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Alternar direção da ordenação
|
||||||
|
if (header.classList.contains('asc')) {
|
||||||
|
rows.reverse();
|
||||||
|
header.classList.remove('asc');
|
||||||
|
header.classList.add('desc');
|
||||||
|
console.log('Ordenação descendente');
|
||||||
|
} else {
|
||||||
|
header.classList.remove('desc');
|
||||||
|
header.classList.add('asc');
|
||||||
|
console.log('Ordenação ascendente');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualizar tabela
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar ordenação para todas as tabelas que precisam
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('Configurando ordenação para todas as tabelas...');
|
||||||
|
|
||||||
|
const tabelas = [
|
||||||
|
'materiaisTable',
|
||||||
|
'vendasTable',
|
||||||
|
'cotasTable',
|
||||||
|
'pagamentosTable'
|
||||||
|
];
|
||||||
|
|
||||||
|
tabelas.forEach(tabelaId => {
|
||||||
|
configurarOrdenacaoTabela(tabelaId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('Carregando script table_sort.js...');
|
||||||
|
|
||||||
|
// Função para comparar datas no formato DD/MM/YYYY
|
||||||
|
function compararDatas(a, b) {
|
||||||
|
if (!a || !b) return 0;
|
||||||
|
|
||||||
|
const [diaA, mesA, anoA] = a.split('/').map(Number);
|
||||||
|
const [diaB, mesB, anoB] = b.split('/').map(Number);
|
||||||
|
|
||||||
|
const dataA = new Date(anoA, mesA - 1, diaA);
|
||||||
|
const dataB = new Date(anoB, mesB - 1, diaB);
|
||||||
|
|
||||||
|
return dataA - dataB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para comparar valores monetários
|
||||||
|
function compararValores(a, b) {
|
||||||
|
const valorA = parseFloat(a.replace('R$ ', '').replace('.', '').replace(',', '.'));
|
||||||
|
const valorB = parseFloat(b.replace('R$ ', '').replace('.', '').replace(',', '.'));
|
||||||
|
|
||||||
|
if (isNaN(valorA)) return -1;
|
||||||
|
if (isNaN(valorB)) return 1;
|
||||||
|
|
||||||
|
return valorA - valorB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar ordenação para todas as tabelas com classe 'table-sort'
|
||||||
|
document.querySelectorAll('table.table-sort').forEach(tabela => {
|
||||||
|
const tbody = tabela.querySelector('tbody');
|
||||||
|
const headers = tabela.querySelectorAll('th[data-sort]');
|
||||||
|
|
||||||
|
headers.forEach(header => {
|
||||||
|
const tipoOrdenacao = header.dataset.sort;
|
||||||
|
|
||||||
|
header.addEventListener('click', () => {
|
||||||
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||||
|
const colIndex = Array.from(header.parentNode.children).indexOf(header);
|
||||||
|
|
||||||
|
rows.sort((rowA, rowB) => {
|
||||||
|
const cellA = rowA.children[colIndex].dataset[tipoOrdenacao] || rowA.children[colIndex].textContent.trim();
|
||||||
|
const cellB = rowB.children[colIndex].dataset[tipoOrdenacao] || rowB.children[colIndex].textContent.trim();
|
||||||
|
|
||||||
|
switch (tipoOrdenacao) {
|
||||||
|
case 'data':
|
||||||
|
return compararDatas(cellA, cellB);
|
||||||
|
case 'valor':
|
||||||
|
return compararValores(cellA, cellB);
|
||||||
|
case 'numero':
|
||||||
|
return parseFloat(cellA) - parseFloat(cellB);
|
||||||
|
default:
|
||||||
|
return cellA.localeCompare(cellB);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (header.classList.contains('asc')) {
|
||||||
|
rows.reverse();
|
||||||
|
header.classList.remove('asc');
|
||||||
|
header.classList.add('desc');
|
||||||
|
} else {
|
||||||
|
header.classList.remove('desc');
|
||||||
|
header.classList.add('asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remover classes de ordenação de outros headers
|
||||||
|
headers.forEach(h => {
|
||||||
|
if (h !== header) {
|
||||||
|
h.classList.remove('asc', 'desc');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Atualizar tabela
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
284
static/js/testes.js
Normal file
284
static/js/testes.js
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
// Testes para o formulário de edição de militantes
|
||||||
|
console.log('Iniciando testes do formulário de edição...');
|
||||||
|
|
||||||
|
// Lista de campos que devem existir no formulário
|
||||||
|
const camposEsperados = {
|
||||||
|
'edit_militante_id': { tipo: 'hidden', obrigatorio: true },
|
||||||
|
'edit_nome': { tipo: 'text', obrigatorio: true },
|
||||||
|
'edit_cpf': { tipo: 'text', obrigatorio: true },
|
||||||
|
'edit_titulo_eleitoral': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_data_nascimento': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_data_entrada_oci': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_data_efetivacao_oci': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_email': { tipo: 'email', obrigatorio: true },
|
||||||
|
'edit_telefone1': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_telefone2': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_cep': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_estado': { tipo: 'select', obrigatorio: false },
|
||||||
|
'edit_cidade': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_bairro': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_rua': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_numero': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_complemento': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_empresa': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_contratante': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_instituicao_ensino': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_tipo_instituicao': { tipo: 'select', obrigatorio: false },
|
||||||
|
'edit_sindicato': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_cargo_sindical': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_central_sindical': { tipo: 'text', obrigatorio: false },
|
||||||
|
'edit_celula': { tipo: 'select', obrigatorio: false },
|
||||||
|
'responsabilidades_values': { tipo: 'hidden', obrigatorio: false }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Função para testar a existência e configuração dos campos
|
||||||
|
function testarCamposFormulario() {
|
||||||
|
console.log('Testando campos do formulário...');
|
||||||
|
const form = document.getElementById('formEditarMilitante');
|
||||||
|
const erros = [];
|
||||||
|
|
||||||
|
if (!form) {
|
||||||
|
console.error('Formulário não encontrado!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testar cada campo esperado
|
||||||
|
for (const [id, config] of Object.entries(camposEsperados)) {
|
||||||
|
const campo = document.getElementById(id);
|
||||||
|
if (!campo) {
|
||||||
|
erros.push(`Campo ${id} não encontrado`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar tipo
|
||||||
|
if (campo.type !== config.tipo && config.tipo !== 'select') {
|
||||||
|
erros.push(`Campo ${id} tem tipo ${campo.type}, esperado ${config.tipo}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar obrigatoriedade
|
||||||
|
if (config.obrigatorio && !campo.hasAttribute('required')) {
|
||||||
|
erros.push(`Campo ${id} deveria ser obrigatório`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se o campo tem name attribute
|
||||||
|
if (!campo.hasAttribute('name')) {
|
||||||
|
erros.push(`Campo ${id} não tem atributo name`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reportar erros encontrados
|
||||||
|
if (erros.length > 0) {
|
||||||
|
console.error('Erros encontrados nos campos:', erros);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Todos os campos estão configurados corretamente');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para testar o carregamento de dados
|
||||||
|
async function testarCarregamentoDados(militanteId) {
|
||||||
|
console.log('Testando carregamento de dados...');
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/militantes/dados/${militanteId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erro HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Dados recebidos:', data);
|
||||||
|
|
||||||
|
// Verificar se os dados foram carregados corretamente
|
||||||
|
const erros = [];
|
||||||
|
|
||||||
|
// Verificar campos básicos
|
||||||
|
if (!data.nome) erros.push('Nome não carregado');
|
||||||
|
if (!data.cpf) erros.push('CPF não carregado');
|
||||||
|
|
||||||
|
// Verificar se os campos foram preenchidos
|
||||||
|
for (const [id, config] of Object.entries(camposEsperados)) {
|
||||||
|
const campo = document.getElementById(id);
|
||||||
|
if (!campo) continue;
|
||||||
|
|
||||||
|
// Mapear campos do servidor para campos do formulário
|
||||||
|
let valorEsperado = '';
|
||||||
|
switch(id) {
|
||||||
|
case 'edit_nome': valorEsperado = data.nome; break;
|
||||||
|
case 'edit_cpf': valorEsperado = data.cpf; break;
|
||||||
|
case 'edit_email': valorEsperado = data.emails?.[0]; break;
|
||||||
|
case 'edit_telefone1': valorEsperado = data.telefone1; break;
|
||||||
|
case 'edit_celula': valorEsperado = data.celula_id?.toString(); break;
|
||||||
|
case 'edit_cargo_sindical': valorEsperado = data.cargo_sindical; break;
|
||||||
|
case 'edit_central_sindical': valorEsperado = data.central_sindical; break;
|
||||||
|
case 'edit_sindicato': valorEsperado = data.sindicato; break;
|
||||||
|
// Adicione mais campos conforme necessário
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.obrigatorio && !valorEsperado) {
|
||||||
|
erros.push(`Campo obrigatório ${id} não tem valor no servidor`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valorEsperado && campo.value !== valorEsperado) {
|
||||||
|
erros.push(`Campo ${id} tem valor diferente do servidor. Esperado: ${valorEsperado}, Atual: ${campo.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (erros.length > 0) {
|
||||||
|
console.error('Erros no carregamento:', erros);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Dados carregados corretamente');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao carregar dados:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para testar o salvamento de dados
|
||||||
|
async function testarSalvamentoDados(militanteId) {
|
||||||
|
console.log('Testando salvamento de dados...');
|
||||||
|
try {
|
||||||
|
const form = document.getElementById('formEditarMilitante');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
// Guardar valores originais para comparação
|
||||||
|
const valoresOriginais = {
|
||||||
|
nome: formData.get('nome'),
|
||||||
|
cpf: formData.get('cpf'),
|
||||||
|
email: formData.get('email'),
|
||||||
|
celula: formData.get('celula'),
|
||||||
|
cargo_sindical: formData.get('cargo_sindical'),
|
||||||
|
central_sindical: formData.get('central_sindical'),
|
||||||
|
sindicato: formData.get('sindicato'),
|
||||||
|
responsabilidades: formData.get('responsabilidades_values')
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`/militantes/editar/${militanteId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erro HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Resposta do servidor:', data);
|
||||||
|
|
||||||
|
// Verificar se os dados foram salvos corretamente
|
||||||
|
const row = document.querySelector(`tr[data-militante="${militanteId}"]`);
|
||||||
|
if (!row) {
|
||||||
|
console.error('Linha da tabela não encontrada após salvamento');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const erros = [];
|
||||||
|
|
||||||
|
// Verificar dados básicos na tabela
|
||||||
|
const nome = row.querySelector('td[data-nome]')?.textContent;
|
||||||
|
const cpf = row.querySelector('td[data-cpf]')?.textContent;
|
||||||
|
const email = row.querySelector('td[data-email]')?.textContent;
|
||||||
|
|
||||||
|
if (nome !== valoresOriginais.nome) erros.push(`Nome não atualizado na tabela. Esperado: ${valoresOriginais.nome}, Atual: ${nome}`);
|
||||||
|
if (cpf !== valoresOriginais.cpf) erros.push(`CPF não atualizado na tabela. Esperado: ${valoresOriginais.cpf}, Atual: ${cpf}`);
|
||||||
|
if (email !== valoresOriginais.email) erros.push(`Email não atualizado na tabela. Esperado: ${valoresOriginais.email}, Atual: ${email}`);
|
||||||
|
|
||||||
|
// Verificar atributos para filtros
|
||||||
|
const celulaId = row.getAttribute('data-celula-id');
|
||||||
|
const responsabilidades = row.getAttribute('data-responsabilidades');
|
||||||
|
|
||||||
|
if (celulaId !== valoresOriginais.celula) erros.push(`Célula não atualizada na tabela. Esperado: ${valoresOriginais.celula}, Atual: ${celulaId}`);
|
||||||
|
if (responsabilidades !== valoresOriginais.responsabilidades) erros.push(`Responsabilidades não atualizadas na tabela. Esperado: ${valoresOriginais.responsabilidades}, Atual: ${responsabilidades}`);
|
||||||
|
|
||||||
|
// Verificar botão de edição
|
||||||
|
const btnEditar = row.querySelector('button[data-bs-target="#modalEditarMilitante"]');
|
||||||
|
if (btnEditar) {
|
||||||
|
if (btnEditar.getAttribute('data-militante-nome') !== valoresOriginais.nome) {
|
||||||
|
erros.push('Nome não atualizado no botão de edição');
|
||||||
|
}
|
||||||
|
if (btnEditar.getAttribute('data-celula-id') !== valoresOriginais.celula) {
|
||||||
|
erros.push('Célula não atualizada no botão de edição');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (erros.length > 0) {
|
||||||
|
console.error('Erros no salvamento:', erros);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Dados salvos e atualizados corretamente');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao salvar dados:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função principal de teste
|
||||||
|
async function testarFormularioEdicao(militanteId) {
|
||||||
|
console.log('Iniciando teste completo do formulário...');
|
||||||
|
|
||||||
|
// Testar campos do formulário
|
||||||
|
if (!testarCamposFormulario()) {
|
||||||
|
console.error('Teste dos campos falhou');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testar carregamento de dados
|
||||||
|
if (!await testarCarregamentoDados(militanteId)) {
|
||||||
|
console.error('Teste de carregamento falhou');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testar salvamento de dados
|
||||||
|
if (!await testarSalvamentoDados(militanteId)) {
|
||||||
|
console.error('Teste de salvamento falhou');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Todos os testes passaram com sucesso!');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executar testes quando o documento estiver carregado
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Adicionar botão de teste na interface
|
||||||
|
const btnTeste = document.createElement('button');
|
||||||
|
btnTeste.className = 'btn btn-info me-2';
|
||||||
|
btnTeste.innerHTML = '<i class="fas fa-vial me-2"></i>Testar Formulário';
|
||||||
|
btnTeste.onclick = function() {
|
||||||
|
// Pegar ID do primeiro militante da lista
|
||||||
|
const primeiraLinha = document.querySelector('#militantesTable tbody tr');
|
||||||
|
if (!primeiraLinha) {
|
||||||
|
mostrarAlerta('danger', 'Nenhum militante encontrado para teste');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const militanteId = primeiraLinha.getAttribute('data-militante');
|
||||||
|
if (!militanteId) {
|
||||||
|
mostrarAlerta('danger', 'ID do militante não encontrado');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executar testes
|
||||||
|
testarFormularioEdicao(militanteId).then(sucesso => {
|
||||||
|
if (sucesso) {
|
||||||
|
mostrarAlerta('success', 'Testes concluídos com sucesso!');
|
||||||
|
} else {
|
||||||
|
mostrarAlerta('danger', 'Alguns testes falharam. Verifique o console para mais detalhes.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adicionar botão ao lado do botão de exportar
|
||||||
|
const btnExportar = document.querySelector('.btn-exportar');
|
||||||
|
if (btnExportar && btnExportar.parentNode) {
|
||||||
|
btnExportar.parentNode.insertBefore(btnTeste, btnExportar);
|
||||||
|
}
|
||||||
|
});
|
||||||
119
static/js/vendas.js
Normal file
119
static/js/vendas.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('Carregando script vendas.js...');
|
||||||
|
|
||||||
|
// Funções de validação e formatação de datas
|
||||||
|
function validarData(data) {
|
||||||
|
if (!data) return false;
|
||||||
|
|
||||||
|
const dataObj = new Date(data);
|
||||||
|
if (isNaN(dataObj.getTime())) return false;
|
||||||
|
|
||||||
|
const hoje = new Date();
|
||||||
|
hoje.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return dataObj <= hoje;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatarData(data) {
|
||||||
|
if (!data) return '';
|
||||||
|
|
||||||
|
const dataObj = new Date(data);
|
||||||
|
if (isNaN(dataObj.getTime())) return '';
|
||||||
|
|
||||||
|
return dataObj.toLocaleDateString('pt-BR');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar campos de data
|
||||||
|
const camposData = document.querySelectorAll('input[type="date"]');
|
||||||
|
camposData.forEach(campo => {
|
||||||
|
// Definir data máxima como hoje
|
||||||
|
const hoje = new Date().toISOString().split('T')[0];
|
||||||
|
campo.setAttribute('max', hoje);
|
||||||
|
|
||||||
|
campo.addEventListener('change', function() {
|
||||||
|
if (!validarData(this.value)) {
|
||||||
|
this.setCustomValidity('Data inválida ou futura');
|
||||||
|
this.classList.add('is-invalid');
|
||||||
|
} else {
|
||||||
|
this.setCustomValidity('');
|
||||||
|
this.classList.remove('is-invalid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configurar tabela de vendas
|
||||||
|
const tabelaVendas = $('#vendasTable').DataTable({
|
||||||
|
language: {
|
||||||
|
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
||||||
|
},
|
||||||
|
columnDefs: [
|
||||||
|
{
|
||||||
|
targets: 3, // Coluna de data
|
||||||
|
type: 'date-br',
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'sort') {
|
||||||
|
return data.split('/').reverse().join('');
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: 2, // Coluna de valor
|
||||||
|
type: 'numeric',
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'sort') {
|
||||||
|
return parseFloat(data.replace('R$ ', '').replace(',', '.'));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ targets: -1, orderable: false } // Coluna de ações
|
||||||
|
],
|
||||||
|
order: [[3, 'desc']] // Ordenar por data decrescente por padrão
|
||||||
|
});
|
||||||
|
|
||||||
|
// Atualizar valor total ao mudar quantidade ou material
|
||||||
|
const campoQuantidade = document.getElementById('quantidade');
|
||||||
|
const campoMaterial = document.getElementById('material_id');
|
||||||
|
const campoValorTotal = document.getElementById('valor_total');
|
||||||
|
|
||||||
|
function atualizarValorTotal() {
|
||||||
|
if (!campoQuantidade || !campoMaterial || !campoValorTotal) return;
|
||||||
|
|
||||||
|
const quantidade = parseInt(campoQuantidade.value) || 0;
|
||||||
|
const materialSelecionado = campoMaterial.options[campoMaterial.selectedIndex];
|
||||||
|
const preco = materialSelecionado ? parseFloat(materialSelecionado.dataset.preco) || 0 : 0;
|
||||||
|
|
||||||
|
campoValorTotal.value = (quantidade * preco).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (campoQuantidade) {
|
||||||
|
campoQuantidade.addEventListener('change', atualizarValorTotal);
|
||||||
|
}
|
||||||
|
if (campoMaterial) {
|
||||||
|
campoMaterial.addEventListener('change', atualizarValorTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar modal de edição
|
||||||
|
const modalEditarVenda = document.getElementById('modalEditarVenda');
|
||||||
|
if (modalEditarVenda) {
|
||||||
|
modalEditarVenda.addEventListener('show.bs.modal', function(event) {
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
const vendaId = button.getAttribute('data-venda-id');
|
||||||
|
const militanteId = button.getAttribute('data-militante-id');
|
||||||
|
const materialId = button.getAttribute('data-material-id');
|
||||||
|
const quantidade = button.getAttribute('data-quantidade');
|
||||||
|
const valorTotal = button.getAttribute('data-valor-total');
|
||||||
|
const dataVenda = button.getAttribute('data-data-venda');
|
||||||
|
|
||||||
|
document.getElementById('editVendaId').value = vendaId;
|
||||||
|
document.getElementById('editMilitanteId').value = militanteId;
|
||||||
|
document.getElementById('editMaterialId').value = materialId;
|
||||||
|
document.getElementById('editQuantidade').value = quantidade;
|
||||||
|
document.getElementById('editValorTotal').value = valorTotal;
|
||||||
|
document.getElementById('editDataVenda').value = dataVenda;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
|
||||||
<title>{% block title %}{% endblock %} - Controles OCI</title>
|
<title>{% block title %}{% endblock %} - Controles OCI</title>
|
||||||
|
|
||||||
<!-- Bootstrap 5 CSS -->
|
<!-- Bootstrap 5 CSS -->
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><h6 class="dropdown-header">Célula</h6></li>
|
<li><h6 class="dropdown-header">Célula</h6></li>
|
||||||
{% for celula in celulas %}
|
{% for celula in celulas %}
|
||||||
<li><a class="dropdown-item" href="#" data-filter="celula" data-celula="{{ celula.nome }}">{{ celula.nome }}</a></li>
|
<li><a class="dropdown-item" href="#" data-filter="celula" data-celula="{{ celula.id }}">{{ celula.nome }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,47 +3,47 @@
|
|||||||
{% block title %}Listar Relatórios de Cotas{% endblock %}
|
{% block title %}Listar Relatórios de Cotas{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container mt-4">
|
||||||
<div class="row">
|
<div class="card">
|
||||||
<div class="col-md-12">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h1 class="mb-4">Lista de Relatórios de Cotas</h1>
|
<h5 class="mb-0"><i class="fas fa-file-invoice-dollar me-2"></i>Relatórios de Cotas</h5>
|
||||||
|
<a href="{{ url_for('novo_relatorio_cotas') }}" class="btn btn-success">
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
<i class="fas fa-plus me-2"></i>Novo Relatório
|
||||||
{% if messages %}
|
</a>
|
||||||
{% for category, message in messages %}
|
</div>
|
||||||
<div class="alert alert-{{ category }}">{{ message }}</div>
|
<div class="card-body">
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-4">
|
|
||||||
<a href="{{ url_for('novo_relatorio_cotas') }}" class="btn btn-success">Novo Relatório</a>
|
|
||||||
<a href="{{ url_for('home') }}" class="btn btn-outline-primary">Início</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-hover" id="relatoriosTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th data-sort="id">ID <i class="fas fa-sort"></i></th>
|
||||||
<th>Setor</th>
|
<th data-sort="setor">Setor <i class="fas fa-sort"></i></th>
|
||||||
<th>Comitê Central</th>
|
<th data-sort="comite">Comitê Central <i class="fas fa-sort"></i></th>
|
||||||
<th>Total de Cotas</th>
|
<th data-sort="total">Total de Cotas <i class="fas fa-sort"></i></th>
|
||||||
<th>Data do Relatório</th>
|
<th data-sort="data">Data do Relatório <i class="fas fa-sort"></i></th>
|
||||||
<th>Ações</th>
|
<th class="text-end">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for relatorio in relatorios %}
|
{% for relatorio in relatorios %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ relatorio.id }}</td>
|
<td data-id="{{ relatorio.id }}">{{ relatorio.id }}</td>
|
||||||
<td>{{ relatorio.setor.nome }}</td>
|
<td data-setor="{{ relatorio.setor.nome }}">{{ relatorio.setor.nome }}</td>
|
||||||
<td>{{ relatorio.comite.nome }}</td>
|
<td data-comite="{{ relatorio.comite.nome }}">{{ relatorio.comite.nome }}</td>
|
||||||
<td>R$ {{ "%.2f"|format(relatorio.total_cotas) }}</td>
|
<td data-total="{{ relatorio.total_cotas }}">R$ {{ "%.2f"|format(relatorio.total_cotas) }}</td>
|
||||||
<td>{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }}</td>
|
<td data-data="{{ relatorio.data_relatorio }}">{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }}</td>
|
||||||
<td>
|
<td class="text-end">
|
||||||
<a href="{{ url_for('editar_relatorio_cotas', id=relatorio.id) }}" class="btn btn-primary btn-sm">Editar</a>
|
<a href="{{ url_for('editar_relatorio_cotas', id=relatorio.id) }}"
|
||||||
<a href="{{ url_for('deletar_relatorio_cotas', id=relatorio.id) }}" class="btn btn-danger btn-sm" onclick="return confirm('Tem certeza que deseja excluir este relatório?')">Excluir</a>
|
class="btn btn-primary btn-sm"
|
||||||
|
title="Editar">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('deletar_relatorio_cotas', id=relatorio.id) }}"
|
||||||
|
class="btn btn-danger btn-sm"
|
||||||
|
onclick="return confirm('Tem certeza que deseja excluir este relatório?')"
|
||||||
|
title="Excluir">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -53,5 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='js/table_sort.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -3,47 +3,47 @@
|
|||||||
{% block title %}Listar Relatórios de Vendas{% endblock %}
|
{% block title %}Listar Relatórios de Vendas{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container mt-4">
|
||||||
<div class="row">
|
<div class="card">
|
||||||
<div class="col-md-12">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h1 class="mb-4">Lista de Relatórios de Vendas</h1>
|
<h5 class="mb-0"><i class="fas fa-file-invoice me-2"></i>Relatórios de Vendas</h5>
|
||||||
|
<a href="{{ url_for('novo_relatorio_vendas') }}" class="btn btn-success">
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
<i class="fas fa-plus me-2"></i>Novo Relatório
|
||||||
{% if messages %}
|
</a>
|
||||||
{% for category, message in messages %}
|
</div>
|
||||||
<div class="alert alert-{{ category }}">{{ message }}</div>
|
<div class="card-body">
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-4">
|
|
||||||
<a href="{{ url_for('novo_relatorio_vendas') }}" class="btn btn-success">Novo Relatório</a>
|
|
||||||
<a href="{{ url_for('home') }}" class="btn btn-outline-primary">Início</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-hover" id="relatoriosTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th data-sort="id">ID <i class="fas fa-sort"></i></th>
|
||||||
<th>Setor</th>
|
<th data-sort="setor">Setor <i class="fas fa-sort"></i></th>
|
||||||
<th>Comitê Central</th>
|
<th data-sort="comite">Comitê Central <i class="fas fa-sort"></i></th>
|
||||||
<th>Total de Vendas</th>
|
<th data-sort="total">Total de Vendas <i class="fas fa-sort"></i></th>
|
||||||
<th>Data do Relatório</th>
|
<th data-sort="data">Data do Relatório <i class="fas fa-sort"></i></th>
|
||||||
<th>Ações</th>
|
<th class="text-end">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for relatorio in relatorios %}
|
{% for relatorio in relatorios %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ relatorio.id }}</td>
|
<td data-id="{{ relatorio.id }}">{{ relatorio.id }}</td>
|
||||||
<td>{{ relatorio.setor.nome }}</td>
|
<td data-setor="{{ relatorio.setor.nome }}">{{ relatorio.setor.nome }}</td>
|
||||||
<td>{{ relatorio.comite.nome }}</td>
|
<td data-comite="{{ relatorio.comite.nome }}">{{ relatorio.comite.nome }}</td>
|
||||||
<td>R$ {{ "%.2f"|format(relatorio.total_vendas) }}</td>
|
<td data-total="{{ relatorio.total_vendas }}">R$ {{ "%.2f"|format(relatorio.total_vendas) }}</td>
|
||||||
<td>{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }}</td>
|
<td data-data="{{ relatorio.data_relatorio }}">{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }}</td>
|
||||||
<td>
|
<td class="text-end">
|
||||||
<a href="{{ url_for('editar_relatorio_vendas', id=relatorio.id) }}" class="btn btn-primary btn-sm">Editar</a>
|
<a href="{{ url_for('editar_relatorio_vendas', id=relatorio.id) }}"
|
||||||
<a href="{{ url_for('deletar_relatorio_vendas', id=relatorio.id) }}" class="btn btn-danger btn-sm" onclick="return confirm('Tem certeza que deseja excluir este relatório?')">Excluir</a>
|
class="btn btn-primary btn-sm"
|
||||||
|
title="Editar">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('deletar_relatorio_vendas', id=relatorio.id) }}"
|
||||||
|
class="btn btn-danger btn-sm"
|
||||||
|
onclick="return confirm('Tem certeza que deseja excluir este relatório?')"
|
||||||
|
title="Excluir">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -53,4 +53,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='js/table_sort.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -21,3 +21,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{% include 'modals/militante_editar.html' %}
|
||||||
|
{% include 'modals/militante_excluir.html' %}
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="{{ url_for('static', filename='js/militantes.js') }}"></script>
|
||||||
|
{% if config.DEBUG %}
|
||||||
|
<script src="{{ url_for('static', filename='js/tests/militantes.test.js') }}"></script>
|
||||||
|
<script>ativarTestesMilitantes();</script>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,249 +1,275 @@
|
|||||||
<!-- Modal de Editar Militante -->
|
<!-- Modal de Editar Militante -->
|
||||||
<div class="modal fade" id="modalEditarMilitante" tabindex="-1">
|
<div class="modal fade" id="modalEditarMilitante" tabindex="-1" aria-labelledby="modalEditarMilitanteLabel" aria-hidden="true" data-bs-backdrop="true" data-bs-keyboard="true">
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">
|
<h5 class="modal-title" id="modalEditarMilitanteLabel">
|
||||||
<i class="fas fa-user-edit me-2"></i>Editar Militante
|
<i class="fas fa-user-edit me-2"></i>Editar Militante
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fechar"></button>
|
||||||
</div>
|
</div>
|
||||||
<form id="formEditarMilitante" method="POST">
|
<form id="formEditarMilitante" method="POST" action="/militantes/editar/" novalidate>
|
||||||
<input type="hidden" id="edit_militante_id" name="militante_id" value="">
|
<input type="hidden" id="edit_militante_id" name="militante_id" value="">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" id="responsabilidades_values" name="responsabilidades_valor" value="0">
|
||||||
|
|
||||||
<div class="modal-body">
|
<!-- Tabs de navegação -->
|
||||||
<!-- Nav tabs -->
|
<ul class="nav nav-tabs nav-fill" role="tablist">
|
||||||
<ul class="nav nav-tabs mb-3" role="tablist">
|
<li class="nav-item" role="presentation">
|
||||||
<li class="nav-item" role="presentation">
|
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#edit-dados-basicos" type="button" role="tab">
|
||||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#edit-dados-basicos" type="button">
|
<i class="fas fa-user me-2"></i>Dados Básicos
|
||||||
<i class="fas fa-user me-2"></i>Dados Básicos
|
</button>
|
||||||
</button>
|
</li>
|
||||||
</li>
|
<li class="nav-item" role="presentation">
|
||||||
<li class="nav-item" role="presentation">
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-contato" type="button" role="tab">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-contato" type="button">
|
<i class="fas fa-address-book me-2"></i>Contato
|
||||||
<i class="fas fa-address-book me-2"></i>Contato
|
</button>
|
||||||
</button>
|
</li>
|
||||||
</li>
|
<li class="nav-item" role="presentation">
|
||||||
<li class="nav-item" role="presentation">
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-profissional" type="button" role="tab">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-profissional" type="button">
|
<i class="fas fa-briefcase me-2"></i>Profissional
|
||||||
<i class="fas fa-briefcase me-2"></i>Profissional
|
</button>
|
||||||
</button>
|
</li>
|
||||||
</li>
|
<li class="nav-item" role="presentation">
|
||||||
<li class="nav-item" role="presentation">
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-organizacao" type="button" role="tab">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-organizacao" type="button">
|
<i class="fas fa-sitemap me-2"></i>Organização
|
||||||
<i class="fas fa-sitemap me-2"></i>Organização
|
</button>
|
||||||
</button>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content">
|
<!-- Conteúdo das tabs -->
|
||||||
<!-- Dados Básicos -->
|
<div class="tab-content p-3">
|
||||||
<div class="tab-pane fade show active" id="edit-dados-basicos">
|
<!-- Dados Básicos -->
|
||||||
<div class="row">
|
<div class="tab-pane fade show active" id="edit-dados-basicos">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="row">
|
||||||
<label for="edit_nome" class="form-label">Nome</label>
|
<div class="col-md-6 mb-3">
|
||||||
<input type="text" class="form-control" id="edit_nome" name="nome" required>
|
<label for="edit_nome" class="form-label">Nome</label>
|
||||||
<div class="invalid-feedback">
|
<input type="text" class="form-control" id="edit_nome" name="nome" required>
|
||||||
Por favor, insira o nome do militante.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_cpf" class="form-label">CPF</label>
|
|
||||||
<input type="text" class="form-control" id="edit_cpf" name="cpf" required>
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
Por favor, insira um CPF válido.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_titulo_eleitoral" class="form-label">Título Eleitoral</label>
|
|
||||||
<input type="text" class="form-control" id="edit_titulo_eleitoral" name="titulo_eleitoral">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_data_nascimento" class="form-label">Data de Nascimento</label>
|
|
||||||
<input type="text" class="form-control date-mask" id="edit_data_nascimento" name="data_nascimento"
|
|
||||||
placeholder="DD/MM/AAAA" maxlength="10">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_data_entrada" class="form-label">Data de Entrada na OCI</label>
|
|
||||||
<input type="text" class="form-control date-mask" id="edit_data_entrada_oci" name="data_entrada_oci"
|
|
||||||
placeholder="DD/MM/AAAA">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_data_efetivacao" class="form-label">Data de Efetivação na OCI</label>
|
|
||||||
<input type="text" class="form-control date-mask" id="edit_data_efetivacao_oci" name="data_efetivacao_oci"
|
|
||||||
placeholder="DD/MM/AAAA">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Contato -->
|
|
||||||
<div class="tab-pane fade" id="edit-contato">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_telefone1" class="form-label">Telefone Principal</label>
|
|
||||||
<input type="text" class="form-control" id="edit_telefone1" name="telefone1">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_telefone2" class="form-label">Telefone Alternativo</label>
|
|
||||||
<input type="text" class="form-control" id="edit_telefone2" name="telefone2">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Email Principal -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="edit_email" class="form-label">Email Principal</label>
|
|
||||||
<input type="email"
|
|
||||||
class="form-control"
|
|
||||||
id="edit_email"
|
|
||||||
name="email"
|
|
||||||
required>
|
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, insira um email válido.
|
Por favor, insira o nome do militante.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
<!-- Endereço -->
|
<label for="edit_cpf" class="form-label">CPF</label>
|
||||||
<div class="endereco-container">
|
<input type="text" class="form-control" id="edit_cpf" name="cpf" required>
|
||||||
<div class="row">
|
<div class="invalid-feedback">
|
||||||
<div class="col-md-4 mb-3">
|
Por favor, insira um CPF válido.
|
||||||
<label for="edit_cep" class="form-label">CEP</label>
|
|
||||||
<input type="text" class="form-control" id="edit_cep" name="cep">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label for="edit_estado" class="form-label">Estado</label>
|
|
||||||
<select class="form-select" id="edit_estado" name="estado">
|
|
||||||
<option value="">Selecione...</option>
|
|
||||||
<!-- Estados serão carregados via JavaScript -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label for="edit_cidade" class="form-label">Cidade</label>
|
|
||||||
<input type="text" class="form-control" id="edit_cidade" name="cidade">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label for="edit_bairro" class="form-label">Bairro</label>
|
|
||||||
<input type="text" class="form-control" id="edit_bairro" name="bairro">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_rua" class="form-label">Rua</label>
|
|
||||||
<input type="text" class="form-control" id="edit_rua" name="rua">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2 mb-3">
|
|
||||||
<label for="edit_numero" class="form-label">Número</label>
|
|
||||||
<input type="text" class="form-control" id="edit_numero" name="numero">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="edit_complemento" class="form-label">Complemento</label>
|
|
||||||
<input type="text" class="form-control" id="edit_complemento" name="complemento">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
<!-- Profissional -->
|
<div class="col-md-6 mb-3">
|
||||||
<div class="tab-pane fade" id="edit-profissional">
|
<label for="edit_titulo_eleitoral" class="form-label">Título Eleitoral</label>
|
||||||
<div class="row">
|
<input type="text" class="form-control" id="edit_titulo_eleitoral" name="titulo_eleitoral">
|
||||||
<div class="col-md-6 mb-3">
|
</div>
|
||||||
<label for="edit_empresa" class="form-label">Empresa</label>
|
<div class="col-md-6 mb-3">
|
||||||
<input type="text" class="form-control" id="edit_empresa" name="empresa">
|
<label for="edit_data_nascimento" class="form-label">Data de Nascimento</label>
|
||||||
</div>
|
<input type="text"
|
||||||
<div class="col-md-6 mb-3">
|
class="form-control date-mask"
|
||||||
<label for="edit_contratante" class="form-label">Contratante</label>
|
id="edit_data_nascimento"
|
||||||
<input type="text" class="form-control" id="edit_contratante" name="contratante">
|
name="data_nascimento"
|
||||||
<small class="text-muted">Para terceirizados</small>
|
placeholder="DD/MM/AAAA"
|
||||||
|
maxlength="10"
|
||||||
|
pattern="\d{2}/\d{2}/\d{4}"
|
||||||
|
title="Data no formato DD/MM/AAAA">
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Por favor, insira uma data válida no formato DD/MM/AAAA.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
</div>
|
||||||
<!-- Dados Acadêmicos -->
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_data_entrada_oci" class="form-label">Data de Entrada na OCI</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control date-mask"
|
||||||
|
id="edit_data_entrada_oci"
|
||||||
|
name="data_entrada_oci"
|
||||||
|
placeholder="DD/MM/AAAA"
|
||||||
|
maxlength="10"
|
||||||
|
pattern="\d{2}/\d{2}/\d{4}"
|
||||||
|
title="Data no formato DD/MM/AAAA">
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Por favor, insira uma data válida no formato DD/MM/AAAA.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_data_efetivacao_oci" class="form-label">Data de Efetivação na OCI</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control date-mask"
|
||||||
|
id="edit_data_efetivacao_oci"
|
||||||
|
name="data_efetivacao_oci"
|
||||||
|
placeholder="DD/MM/AAAA"
|
||||||
|
maxlength="10"
|
||||||
|
pattern="\d{2}/\d{2}/\d{4}"
|
||||||
|
title="Data no formato DD/MM/AAAA">
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Por favor, insira uma data válida no formato DD/MM/AAAA.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contato -->
|
||||||
|
<div class="tab-pane fade" id="edit-contato">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_telefone1" class="form-label">Telefone Principal</label>
|
||||||
|
<input type="text" class="form-control" id="edit_telefone1" name="telefone1">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_telefone2" class="form-label">Telefone Alternativo</label>
|
||||||
|
<input type="text" class="form-control" id="edit_telefone2" name="telefone2">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email Principal -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="edit_email" class="form-label">Email Principal</label>
|
||||||
|
<input type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="edit_email"
|
||||||
|
name="email"
|
||||||
|
required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Por favor, insira um email válido.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Endereço -->
|
||||||
|
<div class="endereco-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="edit_instituicao_ensino" class="form-label">Instituição de Ensino</label>
|
<label for="edit_cep" class="form-label">CEP</label>
|
||||||
<input type="text" class="form-control" id="edit_instituicao_ensino" name="instituicao_ensino">
|
<input type="text" class="form-control" id="edit_cep" name="cep">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="edit_tipo_instituicao" class="form-label">Tipo</label>
|
<label for="edit_estado" class="form-label">Estado</label>
|
||||||
<select class="form-select" id="edit_tipo_instituicao" name="tipo_instituicao">
|
<select class="form-select" id="edit_estado" name="estado">
|
||||||
<option value="">Selecione...</option>
|
<option value="">Selecione...</option>
|
||||||
<option value="Federal">Federal</option>
|
<!-- Estados serão carregados via JavaScript -->
|
||||||
<option value="Estadual">Estadual</option>
|
|
||||||
<option value="Municipal">Municipal</option>
|
|
||||||
<option value="Privada">Privada</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label for="edit_cidade" class="form-label">Cidade</label>
|
||||||
|
<input type="text" class="form-control" id="edit_cidade" name="cidade">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label for="edit_bairro" class="form-label">Bairro</label>
|
||||||
|
<input type="text" class="form-control" id="edit_bairro" name="bairro">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_rua" class="form-label">Rua</label>
|
||||||
|
<input type="text" class="form-control" id="edit_rua" name="rua">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 mb-3">
|
||||||
|
<label for="edit_numero" class="form-label">Número</label>
|
||||||
|
<input type="text" class="form-control" id="edit_numero" name="numero">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="edit_complemento" class="form-label">Complemento</label>
|
||||||
|
<input type="text" class="form-control" id="edit_complemento" name="complemento">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profissional -->
|
||||||
|
<div class="tab-pane fade" id="edit-profissional">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_empresa" class="form-label">Empresa</label>
|
||||||
|
<input type="text" class="form-control" id="edit_empresa" name="empresa">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_contratante" class="form-label">Contratante</label>
|
||||||
|
<input type="text" class="form-control" id="edit_contratante" name="contratante">
|
||||||
|
<small class="text-muted">Para terceirizados</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<!-- Dados Acadêmicos -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 mb-3">
|
||||||
|
<label for="edit_instituicao_ensino" class="form-label">Instituição de Ensino</label>
|
||||||
|
<input type="text" class="form-control" id="edit_instituicao_ensino" name="instituicao_ensino">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label for="edit_tipo_instituicao" class="form-label">Tipo</label>
|
||||||
|
<select class="form-select" id="edit_tipo_instituicao" name="tipo_instituicao">
|
||||||
|
<option value="">Selecione...</option>
|
||||||
|
<option value="Federal">Federal</option>
|
||||||
|
<option value="Estadual">Estadual</option>
|
||||||
|
<option value="Municipal">Municipal</option>
|
||||||
|
<option value="Privada">Privada</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Organização -->
|
||||||
|
<div class="tab-pane fade" id="edit-organizacao">
|
||||||
|
<!-- Dados Sindicais -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_sindicato" class="form-label">Sindicato</label>
|
||||||
|
<input type="text" class="form-control" id="edit_sindicato" name="sindicato">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_cargo_sindical" class="form-label">Cargo Sindical</label>
|
||||||
|
<input type="text" class="form-control" id="edit_cargo_sindical" name="cargo_sindical">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit_central_sindical" class="form-label">Central Sindical</label>
|
||||||
|
<input type="text" class="form-control" id="edit_central_sindical" name="central_sindical">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3 d-flex align-items-center">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="edit_dirigente_sindical" name="dirigente_sindical">
|
||||||
|
<label class="form-check-label" for="edit_dirigente_sindical">Dirigente Sindical</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
<!-- Organização -->
|
<!-- Estado na Organização -->
|
||||||
<div class="tab-pane fade" id="edit-organizacao">
|
<div class="row">
|
||||||
<!-- Dados Sindicais -->
|
<div class="col-md-6 mb-3">
|
||||||
<div class="row">
|
<label for="edit_estado_militante" class="form-label">Estado</label>
|
||||||
<div class="col-md-6 mb-3">
|
<select class="form-select" id="edit_estado_militante" name="estado">
|
||||||
<label for="edit_sindicato" class="form-label">Sindicato</label>
|
<option value="ATIVO">Ativo</option>
|
||||||
<input type="text" class="form-control" id="edit_sindicato" name="sindicato">
|
<option value="LICENCIADO">Licenciado</option>
|
||||||
</div>
|
<option value="SUSPENSO">Suspenso</option>
|
||||||
<div class="col-md-6 mb-3">
|
<option value="DESLIGADO">Desligado</option>
|
||||||
<label for="edit_cargo_sindical" class="form-label">Cargo Sindical</label>
|
</select>
|
||||||
<input type="text" class="form-control" id="edit_cargo_sindical" name="cargo_sindical">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="col-md-6 mb-3">
|
<label for="edit_celula" class="form-label">Célula</label>
|
||||||
<label for="edit_central_sindical" class="form-label">Central Sindical</label>
|
<select class="form-select" id="edit_celula" name="celula_id">
|
||||||
<input type="text" class="form-control" id="edit_central_sindical" name="central_sindical">
|
<option value="">Selecione...</option>
|
||||||
</div>
|
{% for celula in celulas %}
|
||||||
<div class="col-md-6 mb-3 d-flex align-items-center">
|
<option value="{{ celula.id }}">{{ celula.nome }}</option>
|
||||||
<div class="form-check">
|
{% endfor %}
|
||||||
<input type="checkbox" class="form-check-input" id="edit_dirigente_sindical" name="dirigente_sindical">
|
</select>
|
||||||
<label class="form-check-label" for="edit_dirigente_sindical">Dirigente Sindical</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
</div>
|
||||||
<!-- Estado na Organização -->
|
<!-- Responsabilidades -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-12">
|
||||||
<label for="edit_estado_militante" class="form-label">Estado</label>
|
<label class="form-label">Responsabilidades</label>
|
||||||
<select class="form-select" id="edit_estado_militante" name="estado">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<option value="ATIVO">Ativo</option>
|
<span class="badge badge-clickable bg-secondary" data-value="{{ Militante.SECRETARIO }}" data-original-class="bg-secondary" title="Secretário">SEC</span>
|
||||||
<option value="LICENCIADO">Licenciado</option>
|
<span class="badge badge-clickable bg-warning" data-value="{{ Militante.TESOUREIRO }}" data-original-class="bg-warning" title="Tesoureiro">TES</span>
|
||||||
<option value="SUSPENSO">Suspenso</option>
|
<span class="badge badge-clickable bg-danger" data-value="{{ Militante.IMPRENSA }}" data-original-class="bg-danger" title="Imprensa">IMP</span>
|
||||||
<option value="DESLIGADO">Desligado</option>
|
<span class="badge badge-clickable bg-purple" data-value="{{ Militante.MNS }}" data-original-class="bg-purple" title="MNS">MNS</span>
|
||||||
</select>
|
<span class="badge badge-clickable bg-teal" data-value="{{ Militante.MPS }}" data-original-class="bg-teal" title="MPS">MPS</span>
|
||||||
</div>
|
<span class="badge badge-clickable bg-orange" data-value="{{ Militante.JUVENTUDE }}" data-original-class="bg-orange" title="Juventude">JUV</span>
|
||||||
<div class="col-md-6 mb-3">
|
<span class="badge badge-clickable bg-success" data-value="{{ Militante.QUADRO_ORIENTADOR }}" data-original-class="bg-success" title="Quadro-Orientador">QOR</span>
|
||||||
<label for="edit_celula" class="form-label">Célula</label>
|
<span class="badge badge-clickable bg-primary" data-value="{{ Militante.RESPONSAVEL_FINANCAS }}" data-original-class="bg-primary" title="Responsável de Finanças">RFI</span>
|
||||||
<select class="form-select" id="edit_celula" name="celula_id">
|
<span class="badge badge-clickable bg-info" data-value="{{ Militante.RESPONSAVEL_IMPRENSA }}" data-original-class="bg-info" title="Responsável de Imprensa">RIM</span>
|
||||||
<option value="">Selecione...</option>
|
<span class="badge badge-clickable bg-dark" data-value="{{ Militante.ASPIRANTE }}" data-original-class="bg-dark" title="Aspirante">ASP</span>
|
||||||
{% for celula in celulas %}
|
|
||||||
<option value="{{ celula.id }}">{{ celula.nome }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Responsabilidades -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<label class="form-label">Responsabilidades</label>
|
|
||||||
<div class="d-flex flex-wrap gap-2">
|
|
||||||
<span class="badge badge-clickable bg-secondary" data-value="{{ Militante.SECRETARIO }}" data-original-class="bg-secondary" title="Secretário">SEC</span>
|
|
||||||
<span class="badge badge-clickable bg-warning" data-value="{{ Militante.TESOUREIRO }}" data-original-class="bg-warning" title="Tesoureiro">TES</span>
|
|
||||||
<span class="badge badge-clickable bg-danger" data-value="{{ Militante.IMPRENSA }}" data-original-class="bg-danger" title="Imprensa">IMP</span>
|
|
||||||
<span class="badge badge-clickable bg-purple" data-value="{{ Militante.MNS }}" data-original-class="bg-purple" title="MNS">MNS</span>
|
|
||||||
<span class="badge badge-clickable bg-teal" data-value="{{ Militante.MPS }}" data-original-class="bg-teal" title="MPS">MPS</span>
|
|
||||||
<span class="badge badge-clickable bg-orange" data-value="{{ Militante.JUVENTUDE }}" data-original-class="bg-orange" title="Juventude">JUV</span>
|
|
||||||
<span class="badge badge-clickable bg-success" data-value="{{ Militante.QUADRO_ORIENTADOR }}" data-original-class="bg-success" title="Quadro-Orientador">QOR</span>
|
|
||||||
<span class="badge badge-clickable bg-primary" data-value="{{ Militante.RESPONSAVEL_FINANCAS }}" data-original-class="bg-primary" title="Responsável de Finanças">RFI</span>
|
|
||||||
<span class="badge badge-clickable bg-info" data-value="{{ Militante.RESPONSAVEL_IMPRENSA }}" data-original-class="bg-info" title="Responsável de Imprensa">RIM</span>
|
|
||||||
<span class="badge badge-clickable bg-dark" data-value="{{ Militante.ASPIRANTE }}" data-original-class="bg-dark" title="Aspirante">ASP</span>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" id="responsabilidades_values" name="responsabilidades_valor" value="0">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -319,4 +345,90 @@
|
|||||||
.active.bg-info { background-color: #0dcaf0 !important; color: white !important; }
|
.active.bg-info { background-color: #0dcaf0 !important; color: white !important; }
|
||||||
.active.bg-danger { background-color: #dc3545 !important; color: white !important; }
|
.active.bg-danger { background-color: #dc3545 !important; color: white !important; }
|
||||||
.active.bg-dark { background-color: #212529 !important; color: white !important; }
|
.active.bg-dark { background-color: #212529 !important; color: white !important; }
|
||||||
|
|
||||||
|
/* Estilos para as tabs */
|
||||||
|
.nav-tabs {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link {
|
||||||
|
border: none;
|
||||||
|
color: var(--bs-danger);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link:hover {
|
||||||
|
border: none;
|
||||||
|
color: var(--bs-danger);
|
||||||
|
background-color: rgba(var(--bs-danger-rgb), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
color: var(--bs-danger);
|
||||||
|
background-color: rgba(var(--bs-danger-rgb), 0.1);
|
||||||
|
border-bottom: 2px solid var(--bs-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adicionar nav-fill para distribuir as abas igualmente */
|
||||||
|
.nav-tabs {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilos para o conteúdo das tabs */
|
||||||
|
.tab-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 0 0 0.25rem 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const modalEditarMilitante = document.getElementById('modalEditarMilitante');
|
||||||
|
if (modalEditarMilitante) {
|
||||||
|
modalEditarMilitante.addEventListener('hidden.bs.modal', function() {
|
||||||
|
// Limpar formulário
|
||||||
|
const form = this.querySelector('form');
|
||||||
|
if (form) {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpar campos hidden
|
||||||
|
document.getElementById('edit_militante_id').value = '';
|
||||||
|
document.getElementById('responsabilidades_values').value = '0';
|
||||||
|
|
||||||
|
// Resetar badges
|
||||||
|
this.querySelectorAll('.badge-clickable').forEach(badge => {
|
||||||
|
badge.classList.remove('active');
|
||||||
|
const originalClass = badge.getAttribute('data-original-class');
|
||||||
|
if (originalClass) {
|
||||||
|
badge.className = `badge badge-clickable ${originalClass}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Limpar mensagens de erro
|
||||||
|
this.querySelectorAll('.is-invalid').forEach(field => {
|
||||||
|
field.classList.remove('is-invalid');
|
||||||
|
});
|
||||||
|
this.querySelectorAll('.invalid-feedback').forEach(feedback => {
|
||||||
|
feedback.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Voltar para a primeira aba
|
||||||
|
const firstTab = this.querySelector('button[data-bs-target="#edit-dados-basicos"]');
|
||||||
|
if (firstTab) {
|
||||||
|
firstTab.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -3,11 +3,12 @@
|
|||||||
{% block title %}Novo Relatório de Cotas{% endblock %}
|
{% block title %}Novo Relatório de Cotas{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container mt-4">
|
||||||
<div class="row">
|
<div class="card">
|
||||||
<div class="col-md-12">
|
<div class="card-header">
|
||||||
<h1 class="mb-4">Novo Relatório de Cotas</h1>
|
<h5 class="mb-0"><i class="fas fa-file-invoice-dollar me-2"></i>Novo Relatório de Cotas</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for category, message in messages %}
|
{% for category, message in messages %}
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="setor_id" class="form-label">Setor</label>
|
<label for="setor_id" class="form-label">Setor</label>
|
||||||
<select class="form-select" id="setor_id" name="setor_id" required>
|
<select class="form-select" id="setor_id" name="setor_id" required>
|
||||||
<option value="">Selecione um setor</option>
|
<option value="">Selecione o setor</option>
|
||||||
{% for setor in setores %}
|
{% for setor in setores %}
|
||||||
<option value="{{ setor.id }}">{{ setor.nome }}</option>
|
<option value="{{ setor.id }}">{{ setor.nome }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -33,35 +34,53 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="comite_id" class="form-label">Comitê Central</label>
|
<label for="comite_id" class="form-label">Comitê Central</label>
|
||||||
<select class="form-select" id="comite_id" name="comite_id" required>
|
<select class="form-select" id="comite_id" name="comite_id" required>
|
||||||
<option value="">Selecione um comitê</option>
|
<option value="">Selecione o comitê</option>
|
||||||
{% for comite in comites %}
|
{% for comite in comites %}
|
||||||
<option value="{{ comite.id }}">{{ comite.nome }}</option>
|
<option value="{{ comite.id }}">{{ comite.nome }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, selecione o comitê central.
|
Por favor, selecione o comitê.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="total_cotas" class="form-label">Total de Cotas</label>
|
<label for="total_cotas" class="form-label">Total de Cotas</label>
|
||||||
<input type="number" class="form-control" id="total_cotas" name="total_cotas" step="0.01" required>
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">R$</span>
|
||||||
|
<input type="number"
|
||||||
|
class="form-control"
|
||||||
|
id="total_cotas"
|
||||||
|
name="total_cotas"
|
||||||
|
step="0.01"
|
||||||
|
min="0.01"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, insira o total de cotas.
|
Por favor, insira um valor válido para o total de cotas.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="data_relatorio" class="form-label">Data do Relatório</label>
|
<label for="data_relatorio" class="form-label">Data do Relatório</label>
|
||||||
<input type="date" class="form-control" id="data_relatorio" name="data_relatorio" required>
|
<input type="date"
|
||||||
|
class="form-control"
|
||||||
|
id="data_relatorio"
|
||||||
|
name="data_relatorio"
|
||||||
|
max="{{ hoje }}"
|
||||||
|
required>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, insira a data do relatório.
|
Por favor, insira uma data válida não futura.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<button type="submit" class="btn btn-success">Registrar</button>
|
<button type="submit" class="btn btn-success">
|
||||||
<a href="{{ url_for('listar_relatorios_cotas') }}" class="btn btn-outline-secondary">Voltar</a>
|
<i class="fas fa-save me-2"></i>Registrar
|
||||||
|
</button>
|
||||||
|
<a href="{{ url_for('listar_relatorios_cotas') }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Voltar
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,20 +92,51 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
var forms = document.querySelectorAll('.needs-validation')
|
const forms = document.querySelectorAll('.needs-validation');
|
||||||
|
|
||||||
Array.prototype.slice.call(forms)
|
forms.forEach(form => {
|
||||||
.forEach(function (form) {
|
form.addEventListener('submit', event => {
|
||||||
form.addEventListener('submit', function (event) {
|
if (!form.checkValidity()) {
|
||||||
if (!form.checkValidity()) {
|
event.preventDefault();
|
||||||
event.preventDefault()
|
event.stopPropagation();
|
||||||
event.stopPropagation()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
form.classList.add('was-validated')
|
// Validar valor mínimo
|
||||||
}, false)
|
const totalCotas = form.querySelector('#total_cotas');
|
||||||
})
|
if (totalCotas.value <= 0) {
|
||||||
})()
|
totalCotas.setCustomValidity('O valor deve ser maior que zero');
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
totalCotas.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar data não futura
|
||||||
|
const dataRelatorio = form.querySelector('#data_relatorio');
|
||||||
|
const hoje = new Date();
|
||||||
|
hoje.setHours(0, 0, 0, 0);
|
||||||
|
const dataSelecionada = new Date(dataRelatorio.value);
|
||||||
|
|
||||||
|
if (dataSelecionada > hoje) {
|
||||||
|
dataRelatorio.setCustomValidity('A data não pode ser futura');
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
dataRelatorio.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// Limpar validação ao mudar valor
|
||||||
|
const inputs = form.querySelectorAll('input, select');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
{% block title %}Novo Relatório de Vendas{% endblock %}
|
{% block title %}Novo Relatório de Vendas{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container mt-4">
|
||||||
<div class="row">
|
<div class="card">
|
||||||
<div class="col-md-12">
|
<div class="card-header">
|
||||||
<h1 class="mb-4">Novo Relatório de Vendas</h1>
|
<h5 class="mb-0"><i class="fas fa-file-invoice me-2"></i>Novo Relatório de Vendas</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for category, message in messages %}
|
{% for category, message in messages %}
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="setor_id" class="form-label">Setor</label>
|
<label for="setor_id" class="form-label">Setor</label>
|
||||||
<select class="form-select" id="setor_id" name="setor_id" required>
|
<select class="form-select" id="setor_id" name="setor_id" required>
|
||||||
<option value="">Selecione um setor</option>
|
<option value="">Selecione o setor</option>
|
||||||
{% for setor in setores %}
|
{% for setor in setores %}
|
||||||
<option value="{{ setor.id }}">{{ setor.nome }}</option>
|
<option value="{{ setor.id }}">{{ setor.nome }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -33,35 +34,53 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="comite_id" class="form-label">Comitê Central</label>
|
<label for="comite_id" class="form-label">Comitê Central</label>
|
||||||
<select class="form-select" id="comite_id" name="comite_id" required>
|
<select class="form-select" id="comite_id" name="comite_id" required>
|
||||||
<option value="">Selecione um comitê</option>
|
<option value="">Selecione o comitê</option>
|
||||||
{% for comite in comites %}
|
{% for comite in comites %}
|
||||||
<option value="{{ comite.id }}">{{ comite.nome }}</option>
|
<option value="{{ comite.id }}">{{ comite.nome }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, selecione o comitê central.
|
Por favor, selecione o comitê.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="total_vendas" class="form-label">Total de Vendas</label>
|
<label for="total_vendas" class="form-label">Total de Vendas</label>
|
||||||
<input type="number" class="form-control" id="total_vendas" name="total_vendas" step="0.01" required>
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">R$</span>
|
||||||
|
<input type="number"
|
||||||
|
class="form-control"
|
||||||
|
id="total_vendas"
|
||||||
|
name="total_vendas"
|
||||||
|
step="0.01"
|
||||||
|
min="0.01"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, insira o total de vendas.
|
Por favor, insira um valor válido para o total de vendas.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="data_relatorio" class="form-label">Data do Relatório</label>
|
<label for="data_relatorio" class="form-label">Data do Relatório</label>
|
||||||
<input type="date" class="form-control" id="data_relatorio" name="data_relatorio" required>
|
<input type="date"
|
||||||
|
class="form-control"
|
||||||
|
id="data_relatorio"
|
||||||
|
name="data_relatorio"
|
||||||
|
max="{{ hoje }}"
|
||||||
|
required>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, insira a data do relatório.
|
Por favor, insira uma data válida não futura.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<button type="submit" class="btn btn-success">Registrar</button>
|
<button type="submit" class="btn btn-success">
|
||||||
<a href="{{ url_for('listar_relatorios_vendas') }}" class="btn btn-outline-secondary">Voltar</a>
|
<i class="fas fa-save me-2"></i>Registrar
|
||||||
|
</button>
|
||||||
|
<a href="{{ url_for('listar_relatorios_vendas') }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Voltar
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,19 +92,50 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
var forms = document.querySelectorAll('.needs-validation')
|
const forms = document.querySelectorAll('.needs-validation');
|
||||||
|
|
||||||
Array.prototype.slice.call(forms)
|
forms.forEach(form => {
|
||||||
.forEach(function (form) {
|
form.addEventListener('submit', event => {
|
||||||
form.addEventListener('submit', function (event) {
|
if (!form.checkValidity()) {
|
||||||
if (!form.checkValidity()) {
|
event.preventDefault();
|
||||||
event.preventDefault()
|
event.stopPropagation();
|
||||||
event.stopPropagation()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
form.classList.add('was-validated')
|
// Validar valor mínimo
|
||||||
}, false)
|
const totalVendas = form.querySelector('#total_vendas');
|
||||||
})
|
if (totalVendas.value <= 0) {
|
||||||
})()
|
totalVendas.setCustomValidity('O valor deve ser maior que zero');
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
totalVendas.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar data não futura
|
||||||
|
const dataRelatorio = form.querySelector('#data_relatorio');
|
||||||
|
const hoje = new Date();
|
||||||
|
hoje.setHours(0, 0, 0, 0);
|
||||||
|
const dataSelecionada = new Date(dataRelatorio.value);
|
||||||
|
|
||||||
|
if (dataSelecionada > hoje) {
|
||||||
|
dataRelatorio.setCustomValidity('A data não pode ser futura');
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
dataRelatorio.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// Limpar validação ao mudar valor
|
||||||
|
const inputs = form.querySelectorAll('input, select');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
171
utils/date_utils.py
Normal file
171
utils/date_utils.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
from datetime import datetime, date
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def validar_data(data_str: str, formato: str = '%Y-%m-%d') -> bool:
|
||||||
|
"""
|
||||||
|
Valida se uma string representa uma data válida no formato especificado.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_str: String contendo a data
|
||||||
|
formato: Formato esperado da data (default: YYYY-MM-DD)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True se a data é válida, False caso contrário
|
||||||
|
"""
|
||||||
|
if not data_str:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
datetime.strptime(data_str, formato)
|
||||||
|
return True
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(f"Data inválida: {data_str} (formato esperado: {formato}). Erro: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def converter_data(data_str: str, formato_entrada: str = '%Y-%m-%d', formato_saida: str = None) -> date:
|
||||||
|
"""
|
||||||
|
Converte uma string de data para um objeto date.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_str: String contendo a data
|
||||||
|
formato_entrada: Formato da data de entrada (default: YYYY-MM-DD)
|
||||||
|
formato_saida: Se especificado, retorna a data como string neste formato
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
date: Objeto date se formato_saida=None, string formatada caso contrário
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: Se a data for inválida
|
||||||
|
"""
|
||||||
|
if not data_str:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = datetime.strptime(data_str, formato_entrada).date()
|
||||||
|
|
||||||
|
if formato_saida:
|
||||||
|
return data.strftime(formato_saida)
|
||||||
|
return data
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(f"Erro ao converter data '{data_str}': {e}")
|
||||||
|
raise ValueError(f"Data inválida: {data_str}. Use o formato {formato_entrada}")
|
||||||
|
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user