mirror of https://github.com/interlegis/sapl.git
Browse Source
O relatório de presença exibia percentuais acima de 100% — em Capanema/PR,
um vereador aparecia com 46 sessões em um período de 45. A contagem não
estava errada: existiam mesmo duas linhas de presença dele na sessão nº 22
de 30/06/2025, e o relatório contava linhas com Count('id').
A origem é PresencaView/PresencaOrdemDiaView. `presentes_banco` vem do banco
como inteiros e `marcados` vem do POST como strings, então
`set(presentes_banco) - set(marcados)` nunca casava e resultava em apagar
todas as presenças da sessão a cada salvamento, recriando-as em seguida.
Sequencialmente o resultado é correto, mas duas submissões concorrentes do
formulário — um duplo clique em Salvar — passam ambas pela janela entre o
apagar e o recriar e gravam duas linhas por parlamentar. Os ids gravados em
Capanema confirmam: pares adjacentes intercalados na ordem alfabética da
tela, assinatura de dois laços concorrentes.
Nada no banco impedia isso. Não é um caso isolado: Capanema tinha 625 linhas
excedentes em 313 sessões, e a base de Agudo/RS, 9 linhas em 1 sessão.
Corrige em quatro frentes:
- compara os ids com o mesmo tipo e passa a criar apenas quem ainda não tem
presença, sem apagar e recriar quem permanece marcado;
- usa bulk_create(ignore_conflicts=True), para que a submissão concorrente
descarte a inserção repetida em vez de estourar IntegrityError;
- adiciona unique_together (sessao_plenaria, parlamentar) nos dois modelos,
que é a proteção efetiva contra concorrência, com migração que remove as
duplicatas existentes antes de criar a restrição;
- conta sessões distintas no relatório, em vez de linhas, para que bases
ainda não migradas não exibam percentuais impossíveis.
OSTicket #212222
fix/presencas-duplicadas-sessao
5 changed files with 203 additions and 27 deletions
@ -0,0 +1,55 @@ |
|||
# Generated by Django 2.2.28 on 2026-08-05 16:00 |
|||
|
|||
from django.db import migrations |
|||
from django.db.models import Count |
|||
|
|||
|
|||
def remove_presencas_duplicadas(apps, schema_editor): |
|||
"""Mantém uma única presença por (sessão, parlamentar). |
|||
|
|||
Presença é um sim/não, então as linhas repetidas não carregam informação |
|||
adicional: preserva-se a de menor id e descartam-se as demais. Em |
|||
SessaoPlenariaPresenca dá-se preferência a uma linha com data_sessao |
|||
preenchida, caso exista, para não perder o único campo que as diferencia. |
|||
""" |
|||
for model_name, tem_data_sessao in (('SessaoPlenariaPresenca', True), |
|||
('PresencaOrdemDia', False)): |
|||
model = apps.get_model('sessao', model_name) |
|||
# list() para materializar antes de apagar dentro do laço |
|||
duplicados = list(model.objects.values_list( |
|||
'sessao_plenaria_id', 'parlamentar_id').annotate( |
|||
total=Count('id')).filter(total__gt=1)) |
|||
|
|||
for sessao_id, parlamentar_id, _total in duplicados: |
|||
linhas = list(model.objects.filter( |
|||
sessao_plenaria_id=sessao_id, |
|||
parlamentar_id=parlamentar_id).order_by('id')) |
|||
manter = linhas[0] |
|||
if tem_data_sessao: |
|||
com_data = [x for x in linhas if x.data_sessao is not None] |
|||
if com_data: |
|||
manter = com_data[0] |
|||
model.objects.filter( |
|||
sessao_plenaria_id=sessao_id, |
|||
parlamentar_id=parlamentar_id).exclude( |
|||
id=manter.id).delete() |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('sessao', '0069_auto_20220919_1705'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.RunPython(remove_presencas_duplicadas, |
|||
migrations.RunPython.noop), |
|||
migrations.AlterUniqueTogether( |
|||
name='sessaoplenariapresenca', |
|||
unique_together={('sessao_plenaria', 'parlamentar')}, |
|||
), |
|||
migrations.AlterUniqueTogether( |
|||
name='presencaordemdia', |
|||
unique_together={('sessao_plenaria', 'parlamentar')}, |
|||
), |
|||
] |
|||
Loading…
Reference in new issue