Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8ec36ad
feat: cria MesaDiretoraCrud e ComposicaoMesaCrud sem forms e regras d…
LeandroJatai Apr 13, 2026
ef96f77
feat: cria filterset para seleção de legislatura
LeandroJatai Apr 13, 2026
3cb9bdf
feat: add field titulo
LeandroJatai Apr 13, 2026
745a59e
feat: impl tabs na listagem de mesa diretora perfil público
LeandroJatai Apr 13, 2026
86913cd
ajustes no layout publico de mesa
LeandroJatai Apr 13, 2026
9318943
ajustes de scss para telas mobiles
LeandroJatai Apr 14, 2026
8ae8641
altera link para button nas tabs de mesa diretora
LeandroJatai Apr 14, 2026
0c64cad
add forms com regras de negócio
LeandroJatai Apr 14, 2026
586e3df
add testes para mesa e composição
LeandroJatai Apr 14, 2026
f6582f8
ajustes nos testes e no form mesa diretora
LeandroJatai Apr 14, 2026
0ec9585
remove código substituído
LeandroJatai Apr 14, 2026
b89a64f
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083470632
LeandroJatai Apr 17, 2026
8128831
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083471759
LeandroJatai Apr 17, 2026
5d3b742
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083757964
LeandroJatai Apr 17, 2026
ef36793
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093425814
LeandroJatai Apr 17, 2026
c228b04
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083452572
LeandroJatai Apr 17, 2026
4161598
fix: https://github.com/interlegis/sapl/pull/3829/changes/BASE..958e8…
LeandroJatai Apr 17, 2026
962fb4f
fix: reviews
LeandroJatai Apr 17, 2026
fd734b2
fix: ajuste em subnav_mesa.yaml
LeandroJatai Apr 17, 2026
04facfb
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093455410
LeandroJatai Apr 17, 2026
286b2f6
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093434428
LeandroJatai Apr 17, 2026
024a756
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093327707
LeandroJatai Apr 17, 2026
7b5ac2d
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093438428
LeandroJatai Apr 18, 2026
d1c572e
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093737607
LeandroJatai Apr 18, 2026
4f0dc4e
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093543652
LeandroJatai Apr 18, 2026
5af98ee
fix: ajuste de related_name
LeandroJatai Apr 18, 2026
38fa86a
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3182291955
LeandroJatai May 30, 2026
95df206
Merge branch '3.1.x' into 3828_refatorar_mesa_diretora
joaohortsenado Aug 4, 2026
dc2c46e
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3182291971
LeandroJatai Aug 22, 2026
3eee457
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3182291977
LeandroJatai Aug 22, 2026
5edbc36
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3182291982
LeandroJatai Aug 22, 2026
08c3a69
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3182291993
LeandroJatai Aug 22, 2026
f37706f
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3182291998
LeandroJatai Aug 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/__global/scss/layouts/_globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ small {
hyphens: auto;*/
}


@media print {
a[href]:after {
content: none !important;
Expand Down
89 changes: 89 additions & 0 deletions frontend/src/__global/scss/libs/bootstrap/_nav_tabs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
@import "~bootstrap/scss/variables";

// Estilização do tab-content conectado ao nav-tabs (substitui inline style)
.nav-tabs + .tab-content {
border: 1px solid $nav-tabs-border-color;
border-top: 0;
border-radius: 0 0 $border-radius $border-radius;
}

@media (max-width: 992px) {
.nav-tabs {
position: relative;
flex-direction: column;
border: 1px solid $nav-tabs-border-color;
border-radius: $border-radius; // Totalmente arredondado — parece um botão/select
background-color: $white;
overflow: hidden; // Recorta filhos nas bordas arredondadas

// Seta indicando dropdown
&::after {
content: "▾";
position: absolute;
right: 0.75rem;
top: 0.6rem;
font-size: 1rem;
color: $secondary;
pointer-events: none;
transition: transform 0.2s ease;
}

// Oculta todos os itens por padrão
.nav-item {
display: none;
width: 100%;

.nav-link {
border: none;
border-bottom: 1px solid $nav-tabs-border-color;
border-radius: 0;
width: 100%;
text-align: left;
padding-right: 2rem;
margin-bottom: 0;

&.active {
background-color: $nav-tabs-link-active-bg;
color: $nav-tabs-link-active-color;
border-color: transparent;
}

&:hover:not(.active) {
background-color: $light;
}
}

&:last-child .nav-link {
border-bottom: none;
}
}

// CSS nativo: exibe somente o item ativo (browsers com suporte a :has)
.nav-item:has(.nav-link.active) {
display: block;
}

// Estado expandido: via :focus-within (nativo) ou .nav-tabs--open (fallback JS)
&:focus-within,
&.nav-tabs--open {
border-radius: $border-radius $border-radius 0 0; // Arredonda apenas topo quando aberto
overflow: visible;
z-index: $zindex-dropdown;

&::after {
transform: rotate(180deg);
}

.nav-item {
display: block;
}
}
}

// Em mobile, tab-content é visualmente independente do nav-tabs (que vira um select)
.nav-tabs + .tab-content {
border-top: 1px solid $nav-tabs-border-color;
border-radius: $border-radius; // Rounding completo — desconectado do nav-tabs
margin-top: 0.5rem;
}
}
1 change: 1 addition & 0 deletions frontend/src/__global/scss/libs/libs.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import "./bootstrap/nav_navbar";
@import "./bootstrap/nav_tabs";
@import "./bootstrap/table";
56 changes: 53 additions & 3 deletions sapl/parlamentares/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.rules import SAPL_GROUP_VOTANTE
from sapl.utils import FileFieldCheckMixin
from sapl.utils import FileFieldCheckMixin, SelectSubmitChangeWidget

from .models import (Coligacao, ComposicaoColigacao, Filiacao, Frente, Legislatura,
Mandato, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro)
from .models import (Coligacao, ComposicaoColigacao, ComposicaoMesa, Filiacao, Frente, Legislatura,
Mandato, MesaDiretora, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro)


class CustomImageCropWidget(ImageCropWidget):
Expand Down Expand Up @@ -752,3 +752,53 @@ def clean(self):
_("Parlamentar já é membro do bloco parlamentar."))

return cd

class MesaDiretoraFilterSet(django_filters.FilterSet):

legislatura = django_filters.ModelChoiceFilter(
label='',
queryset=Legislatura.objects.all(),
widget=SelectSubmitChangeWidget)

class Meta:
model = MesaDiretora
fields = ['legislatura']

def __init__(self, *args, **kwargs):
super(MesaDiretoraFilterSet, self).__init__(*args, **kwargs)

row0 = to_row([('legislatura', 5)])

self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Escolha da Legislatura'),
row0,)
)


class MesaDiretoraForm(ModelForm):

class Meta:
model = MesaDiretora
fields = '__all__'


class ComposicaoMesaForm(ModelForm):

class Meta:
model = ComposicaoMesa
fields = (
'parlamentar',
'cargo'
)

def __init__(self, *args, **kwargs):
super(ComposicaoMesaForm, self).__init__(*args, **kwargs)
mesa = self.initial.get('mesa_diretora')
if mesa is not None:
self.instance.mesa_diretora = mesa
self.fields['parlamentar'].queryset = (
self.fields['parlamentar'].queryset
.filter(mandato__legislatura=mesa.legislatura)
)
34 changes: 34 additions & 0 deletions sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 2.2.28 on 2026-04-13 01:48

from django.db import migrations, models
import django.db.models.deletion
from datetime import date

def add_legislatura_to_mesa_diretora(apps, schema_editor):
schema_editor.execute("""
UPDATE parlamentares_mesadiretora md
SET
legislatura_id = sl.legislatura_id,
data_inicio = sl.data_inicio,
data_fim = sl.data_fim
FROM
parlamentares_sessaolegislativa sl
WHERE
sl.id = md.sessao_legislativa_id
""")
Comment on lines +8 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A migração pode gravar mesas fora do período da própria legislatura

O SQL está correto e rápido (as 59 migrations rodaram em 12,8s numa base real — o ponto do @edwardoliveira em r3093425814 ficou bem resolvido). O problema é que a SessaoLegislativa de origem nem sempre respeita o intervalo da Legislatura, e a nova regra de contenção introduzida no clean() passa a rejeitar o resultado.

Testando contra um dump de produção real, 2 das 64 mesas ficaram assim:

[id=10] Mesa Diretora 1959/1959  mesa: 1959-06-06 a 1959-12-31 | legislatura: 1959-06-06 a 1959-12-30
[id=21] Mesa Diretora 1977/1977  mesa: 1977-01-01 a 1977-12-31 | legislatura: 1977-01-31 a 1983-01-30

Somadas às 2 de borda coincidente que comentei em models.py, são 4 de 64 mesas (6%) inválidas logo após o upgrade. A migração em si não quebra — o efeito aparece depois, quando alguém tenta editar.

Não acho que a migration deva "consertar" datas silenciosamente: essa informação foi perdida pela modelagem anterior e qualquer chute pode piorar. Duas alternativas que me parecem melhores:

  1. Um management command de diagnóstico (algo como ./manage.py verifica_mesas_diretoras) que lista os registros que não passam no full_clean(), para a casa ajustar manualmente — junto de uma nota na documentação de upgrade.
  2. Ou, no mínimo, uma RunPython adicional que apenas registra em log quais mesas ficaram inconsistentes, para não ser uma descoberta silenciosa em produção.

(Registrando também que a preocupação que eu tinha levantado sobre IntegrityError na 0049 não se confirmou: sessao_legislativa era FK NOT NULL e as datas da sessão também, então o UPDATE sempre popula. Já resolvi aquela thread.)



class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0045_auto_20251201_1531'),
]

operations = [
migrations.AddField(
model_name='mesadiretora',
name='legislatura',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Legislatura', verbose_name='Legislatura'),
),
migrations.RunPython(add_legislatura_to_mesa_diretora),
]
23 changes: 23 additions & 0 deletions sapl/parlamentares/migrations/0047_auto_20260412_2256.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 2.2.28 on 2026-04-13 01:56

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0046_mesadiretora_legislatura'),
]

operations = [
migrations.AlterField(
model_name='mesadiretora',
name='legislatura',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Legislatura', verbose_name='Legislatura'),
),
migrations.RemoveField(
model_name='mesadiretora',
name='sessao_legislativa',
),
]
42 changes: 42 additions & 0 deletions sapl/parlamentares/migrations/0048_auto_20260413_1049.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 2.2.28 on 2026-04-13 13:49

from django.db import migrations, models
import django.db.models.deletion

def preencher_titulo_mesa_diretora(apps, schema_editor):
schema_editor.execute("""
UPDATE parlamentares_mesadiretora
SET titulo = 'Mesa Diretora' ||
CASE WHEN EXTRACT(YEAR FROM data_fim)::integer - EXTRACT(YEAR FROM data_inicio)::integer = 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rótulo "Biênio" aplicado a mesas de aproximadamente um ano

A heurística compara os anos das datas, então marca como biênio qualquer mesa que apenas atravesse o Réveillon. Numa base real de produção, 12 dos 64 títulos gerados saíram assim:

Mesa Diretora Biênio 1973/1974 -> 1973-01-31 a 1974-01-30 (364 dias)
Mesa Diretora Biênio 1971/1972 -> 1971-02-03 a 1972-01-31 (362 dias)
Mesa Diretora Biênio 1969/1970 -> 1969-01-31 a 1970-01-30 (364 dias)
Mesa Diretora Biênio 1982/1983 -> 1982-01-01 a 1983-01-30 (394 dias)

Como o titulo agora é o rótulo principal na tela pública, é texto factualmente errado visível ao cidadão — e, uma vez gravado, só sai com edição manual mesa a mesa.

Comparar a duração real resolve:

SET titulo = 'Mesa Diretora' ||
    CASE WHEN data_fim - data_inicio > 400
         THEN ' Biênio'
         ELSE ''
    END || ' ' || ...

(400 dias em vez de 365 para dar folga a mesas de ~13 meses, que pelo visto são comuns em bases antigas.)

THEN ' Biênio'
ELSE ''
END || ' ' ||
EXTRACT(YEAR FROM data_inicio)::integer::text || '/' ||
EXTRACT(YEAR FROM data_fim)::integer::text
WHERE data_inicio IS NOT NULL AND data_fim IS NOT NULL
""")


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0047_auto_20260412_2256'),
]

operations = [
migrations.AlterModelOptions(
name='mesadiretora',
options={'ordering': ('-legislatura', '-data_inicio'), 'verbose_name': 'Mesa Diretora', 'verbose_name_plural': 'Mesas Diretoras'},
),
migrations.AddField(
model_name='mesadiretora',
name='titulo',
field=models.CharField(default='', max_length=100, verbose_name='Título da Mesa Diretora'),
),
migrations.AlterField(
model_name='composicaomesa',
name='mesa_diretora',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='composicaomesa_set', to='parlamentares.MesaDiretora'),
),
migrations.RunPython(preencher_titulo_mesa_diretora),
]
39 changes: 39 additions & 0 deletions sapl/parlamentares/migrations/0049_auto_20260417_1917.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 2.2.28 on 2026-04-17 22:17

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0048_auto_20260413_1049'),
]

operations = [
migrations.AlterField(
model_name='composicaomesa',
name='cargo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.CargoMesa', verbose_name='Cargo'),
),
migrations.AlterField(
model_name='composicaomesa',
name='mesa_diretora',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='composicaomesa_set', to='parlamentares.MesaDiretora', verbose_name='Mesa Diretora'),
),
migrations.AlterField(
model_name='composicaomesa',
name='parlamentar',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Parlamentar', verbose_name='Parlamentar'),
),
migrations.AlterField(
model_name='mesadiretora',
name='data_fim',
field=models.DateField(verbose_name='Data Fim'),
),
migrations.AlterField(
model_name='mesadiretora',
name='data_inicio',
field=models.DateField(verbose_name='Data Início'),
Comment thread
joaohortsenado marked this conversation as resolved.
),
]
19 changes: 19 additions & 0 deletions sapl/parlamentares/migrations/0050_auto_20260417_2235.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.2.28 on 2026-04-18 01:35

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0049_auto_20260417_1917'),
]

operations = [
migrations.AlterField(
model_name='mesadiretora',
name='legislatura',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='mesadiretora_set', to='parlamentares.Legislatura', verbose_name='Legislatura'),
),
]
Loading