Browse Source

Implementa correspondências em sessão plenária (#3587)

* cria model Correspondencia

* Vincula Correspondencia a SessaoPlenaria para suporte m2m

* Impl crud p correspondências de sessão e ajusta rel e resumos

- Cria crud para Correspondência
- Inserção em lote de correspondências
- cria decimo_sexto em ResumoOrdenacao e inclui correspondencia
- inclui as correspondências, no resumo de sessão, resumo de pauta, ata e em suas versões em pdf

* inclui correspondencia em rules e add alert em add em lote
pull/3596/head
LeandroJataí 2 years ago
committed by GitHub
parent
commit
00a847a928
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      sapl/crispy_layout_mixin.py
  2. 12
      sapl/crud/base.py
  3. 85
      sapl/relatorios/views.py
  4. 3
      sapl/rules/group_sessao.py
  5. 127
      sapl/sessao/forms.py
  6. 31
      sapl/sessao/migrations/0065_correspondencia.py
  7. 25
      sapl/sessao/migrations/0066_auto_20220813_1431.py
  8. 98
      sapl/sessao/migrations/0067_auto_20220813_2233.py
  9. 54
      sapl/sessao/models.py
  10. 15
      sapl/sessao/urls.py
  11. 340
      sapl/sessao/views.py
  12. 1
      sapl/static/sapl/css/relatorio.css
  13. 18
      sapl/templates/relatorios/blocos_sessao_plenaria/correspondencias.html
  14. 21
      sapl/templates/relatorios/blocos_sessao_plenaria/materias_expediente.html
  15. 15
      sapl/templates/relatorios/blocos_sessao_plenaria/materias_ordemdia.html
  16. 3
      sapl/templates/relatorios/relatorio_ata.html
  17. 14
      sapl/templates/relatorios/relatorio_pauta_sessao.html
  18. 3
      sapl/templates/relatorios/relatorio_sessao_plenaria.html
  19. 12
      sapl/templates/sessao/blocos_ata/correspondencias.html
  20. 0
      sapl/templates/sessao/blocos_ata/lista_presenca_sessao.html
  21. 17
      sapl/templates/sessao/blocos_resumo/correspondencias.html
  22. 0
      sapl/templates/sessao/blocos_resumo/lista_presenca_sessao.html
  23. 50
      sapl/templates/sessao/correspondencia_form.html
  24. 14
      sapl/templates/sessao/correspondencia_list.html
  25. 89
      sapl/templates/sessao/em_lote/correspondencia.html
  26. 12
      sapl/templates/sessao/layouts.yaml
  27. 16
      sapl/templates/sessao/pauta_sessao_detail.html
  28. 2
      sapl/templates/sessao/resumo.html
  29. 1
      sapl/templates/sessao/resumo_ata.html
  30. 2
      sapl/templates/sessao/subnav-solene.yaml
  31. 8
      sapl/templates/sessao/subnav.yaml

21
sapl/crispy_layout_mixin.py

@ -257,21 +257,30 @@ class CrispyLayoutFormMixin:
if '|' in fieldname: if '|' in fieldname:
fieldname, func = tuple(fieldname.split('|')) fieldname, func = tuple(fieldname.split('|'))
try:
verbose_name, field_display = get_field_display(obj, fieldname)
except:
verbose_name, field_display = '', ''
if func: if func:
verbose_name, text = getattr(self, func)(obj, fieldname) verbose_name, field_display = getattr(self, func)(obj, fieldname)
else:
hook_fieldname = 'hook_%s' % fieldname hook_fieldname = 'hook_%s' % fieldname
if hasattr(self, hook_fieldname): if hasattr(self, hook_fieldname):
verbose_name, text = getattr( try:
verbose_name, field_display = getattr(
self, hook_fieldname)(obj, verbose_name=verbose_name, field_display=field_display)
except:
verbose_name, field_display = getattr(
self, hook_fieldname)(obj) self, hook_fieldname)(obj)
else: elif not func:
verbose_name, text = get_field_display(obj, fieldname) verbose_name, field_display = get_field_display(obj, fieldname)
return { return {
'id': fieldname, 'id': fieldname,
'span': span, 'span': span,
'verbose_name': verbose_name, 'verbose_name': verbose_name,
'text': text, 'text': field_display,
} }
def fk_urlize_for_detail(self, obj, fieldname): def fk_urlize_for_detail(self, obj, fieldname):

12
sapl/crud/base.py

@ -429,14 +429,20 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
m = f.related_model m = f.related_model
except: except:
f = None f = None
if f:
hook = 'hook_header_{}'.format(''.join(fn)) hook = 'hook_header_{}'.format(''.join(fn))
if hasattr(self, hook): if hasattr(self, hook):
header = getattr(self, hook)() header = getattr(self, hook)()
s.append(header) s.append(force_text(header))
elif f: else:
s.append(force_text(f.verbose_name)) s.append(force_text(f.verbose_name))
else:
hook = 'hook_header_{}'.format(''.join(fn))
if hasattr(self, hook):
header = getattr(self, hook)()
s.append(header)
s = ' / '.join(s) s = ' / '.join(filter(lambda x: x, s))
r.append(s) r.append(s)
return r return r

85
sapl/relatorios/views.py

@ -3,17 +3,17 @@ import html
import logging import logging
import re import re
import tempfile import tempfile
import unidecode
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.utils.translation import ugettext_lazy as _
import unidecode
from weasyprint import HTML, CSS
from sapl.settings import MEDIA_URL from sapl.base.models import Autor, CasaLegislativa, AppConfig as SaplAppConfig
from sapl.base.models import Autor, CasaLegislativa
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao
from sapl.materia.models import (Autoria, MateriaLegislativa, Numeracao, from sapl.materia.models import (Autoria, MateriaLegislativa, Numeracao,
Tramitacao, UnidadeTramitacao, ConfigEtiquetaMateriaLegislativa) Tramitacao, UnidadeTramitacao, ConfigEtiquetaMateriaLegislativa)
@ -27,16 +27,17 @@ from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao,
SessaoPlenariaPresenca, OcorrenciaSessao, SessaoPlenariaPresenca, OcorrenciaSessao,
RegistroVotacao, VotoParlamentar, OradorOrdemDia, RegistroVotacao, VotoParlamentar, OradorOrdemDia,
ConsideracoesFinais, TipoExpediente, ResumoOrdenacao) ConsideracoesFinais, TipoExpediente, ResumoOrdenacao)
from sapl.settings import STATIC_ROOT
from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode
from sapl.sessao.views import (get_identificacao_basica, get_mesa_diretora, from sapl.sessao.views import (get_identificacao_basica, get_mesa_diretora,
get_presenca_sessao, get_expedientes, get_presenca_sessao, get_expedientes,
get_materias_expediente, get_oradores_expediente, get_materias_expediente, get_oradores_expediente,
get_presenca_ordem_do_dia, get_materias_ordem_do_dia, get_presenca_ordem_do_dia, get_materias_ordem_do_dia,
get_oradores_ordemdia, get_oradores_ordemdia,
get_oradores_explicacoes_pessoais, get_consideracoes_finais, get_oradores_explicacoes_pessoais, get_consideracoes_finais,
get_ocorrencias_da_sessao, get_assinaturas) get_ocorrencias_da_sessao, get_assinaturas,
get_correspondencias)
from sapl.settings import MEDIA_URL
from sapl.settings import STATIC_ROOT
from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode
from .templates import (pdf_capa_processo_gerar, from .templates import (pdf_capa_processo_gerar,
pdf_documento_administrativo_gerar, pdf_espelho_gerar, pdf_documento_administrativo_gerar, pdf_espelho_gerar,
@ -44,8 +45,6 @@ from .templates import (pdf_capa_processo_gerar,
pdf_ordem_dia_gerar, pdf_pauta_sessao_gerar, pdf_ordem_dia_gerar, pdf_pauta_sessao_gerar,
pdf_protocolo_gerar, pdf_sessao_plenaria_gerar) pdf_protocolo_gerar, pdf_sessao_plenaria_gerar)
from weasyprint import HTML, CSS
def get_kwargs_params(request, fields): def get_kwargs_params(request, fields):
kwargs = {} kwargs = {}
@ -510,7 +509,7 @@ def is_empty(value):
return True if not txt.strip() else False return True if not txt.strip() else False
def get_sessao_plenaria(sessao, casa): def get_sessao_plenaria(sessao, casa, user):
inf_basicas_dic = { inf_basicas_dic = {
"num_sessao_plen": str(sessao.numero), "num_sessao_plen": str(sessao.numero),
"nom_sessao": sessao.tipo.nome, "nom_sessao": sessao.tipo.nome,
@ -567,6 +566,29 @@ def get_sessao_plenaria(sessao, casa):
"tipo": "Matéria" if ausente.ausencia == 1 else "Sessão" "tipo": "Matéria" if ausente.ausencia == 1 else "Sessão"
}) })
# Exibe as Correspondencias
lst_correspondencias = []
qs = sessao.correspondencia_set.all()
is_anon = user.is_anonymous
is_ostensivo = SaplAppConfig.attr('documentos_administrativos') == 'O'
if is_anon and not is_ostensivo:
qs = qs.none()
elif is_anon:
qs = qs.filter(documento__restrito=False)
for c in qs:
d = c.documento
lst_correspondencias.append(
{
'id': d.id,
'tipo': c.get_tipo_display(),
'epigrafe': d.epigrafe,
'data': d.data.strftime('%d/%m/%Y'),
'assunto': d.assunto,
'restrito': d.restrito,
'is_ostensivo': is_ostensivo
}
)
# Exibe os Expedientes # Exibe os Expedientes
lst_expedientes = [] lst_expedientes = []
expedientes = ExpedienteSessao.objects.filter( expedientes = ExpedienteSessao.objects.filter(
@ -874,6 +896,7 @@ def get_sessao_plenaria(sessao, casa):
lst_mesa, lst_mesa,
lst_presenca_sessao, lst_presenca_sessao,
lst_ausencia_sessao, lst_ausencia_sessao,
lst_correspondencias,
lst_expedientes, lst_expedientes,
lst_expediente_materia, lst_expediente_materia,
lst_expediente_materia_vot_nom, lst_expediente_materia_vot_nom,
@ -937,6 +960,7 @@ def relatorio_sessao_plenaria(request, pk):
lst_mesa, lst_mesa,
lst_presenca_sessao, lst_presenca_sessao,
lst_ausencia_sessao, lst_ausencia_sessao,
lst_correspondencias,
lst_expedientes, lst_expedientes,
lst_expediente_materia, lst_expediente_materia,
lst_expediente_materia_vot_nom, lst_expediente_materia_vot_nom,
@ -947,7 +971,7 @@ def relatorio_sessao_plenaria(request, pk):
lst_oradores_ordemdia, lst_oradores_ordemdia,
lst_oradores, lst_oradores,
lst_ocorrencias, lst_ocorrencias,
lst_consideracoes) = get_sessao_plenaria(sessao, casa) lst_consideracoes) = get_sessao_plenaria(sessao, casa, request.user)
for idx in range(len(lst_expedientes)): for idx in range(len(lst_expedientes)):
txt_expedientes = lst_expedientes[idx]['txt_expediente'] txt_expedientes = lst_expedientes[idx]['txt_expediente']
@ -963,6 +987,7 @@ def relatorio_sessao_plenaria(request, pk):
lst_mesa, lst_mesa,
lst_presenca_sessao, lst_presenca_sessao,
lst_ausencia_sessao, lst_ausencia_sessao,
lst_correspondencias,
lst_expedientes, lst_expedientes,
lst_expediente_materia, lst_expediente_materia,
lst_expediente_materia_vot_nom, lst_expediente_materia_vot_nom,
@ -1345,6 +1370,7 @@ def resumo_ata_pdf(request, pk):
context.update(get_identificacao_basica(sessao_plenaria)) context.update(get_identificacao_basica(sessao_plenaria))
context.update(get_mesa_diretora(sessao_plenaria)) context.update(get_mesa_diretora(sessao_plenaria))
context.update(get_presenca_sessao(sessao_plenaria)) context.update(get_presenca_sessao(sessao_plenaria))
context.update(get_correspondencias(sessao_plenaria, request.user))
context.update(get_expedientes(sessao_plenaria)) context.update(get_expedientes(sessao_plenaria))
context.update(get_materias_expediente(sessao_plenaria)) context.update(get_materias_expediente(sessao_plenaria))
context.update(get_oradores_expediente(sessao_plenaria)) context.update(get_oradores_expediente(sessao_plenaria))
@ -1497,6 +1523,7 @@ def relatorio_sessao_plenaria_pdf(request, pk):
lst_mesa, lst_mesa,
lst_presenca_sessao, lst_presenca_sessao,
lst_ausencia_sessao, lst_ausencia_sessao,
lst_correspondencias,
lst_expedientes, lst_expedientes,
lst_expediente_materia, lst_expediente_materia,
lst_expediente_materia_vot_nom, lst_expediente_materia_vot_nom,
@ -1507,10 +1534,11 @@ def relatorio_sessao_plenaria_pdf(request, pk):
lst_oradores_ordemdia, lst_oradores_ordemdia,
lst_oradores, lst_oradores,
lst_ocorrencias, lst_ocorrencias,
lst_consideracoes) = get_sessao_plenaria(sessao, casa) lst_consideracoes) = get_sessao_plenaria(sessao, casa, request.user)
dict_ord_template = { dict_ord_template = {
'cont_mult': 'conteudo_multimidia.html', 'cont_mult': 'conteudo_multimidia.html',
'correspondencias': 'correspondencias.html',
'exp': 'expedientes.html', 'exp': 'expedientes.html',
'id_basica': 'identificacao_basica.html', 'id_basica': 'identificacao_basica.html',
'lista_p': 'lista_presenca_sessao.html', 'lista_p': 'lista_presenca_sessao.html',
@ -1534,6 +1562,7 @@ def relatorio_sessao_plenaria_pdf(request, pk):
"lst_expediente_materia_vot_nom": lst_expediente_materia_vot_nom, "lst_expediente_materia_vot_nom": lst_expediente_materia_vot_nom,
"lst_presenca_sessao": lst_presenca_sessao, "lst_presenca_sessao": lst_presenca_sessao,
"lst_ausencia_sessao": lst_ausencia_sessao, "lst_ausencia_sessao": lst_ausencia_sessao,
"lst_correspondencias": lst_correspondencias,
"lst_expedientes": lst_expedientes, "lst_expedientes": lst_expedientes,
"lst_expediente_materia": lst_expediente_materia, "lst_expediente_materia": lst_expediente_materia,
"lst_oradores_expediente": lst_oradores_expediente, "lst_oradores_expediente": lst_oradores_expediente,
@ -1565,7 +1594,8 @@ def relatorio_sessao_plenaria_pdf(request, pk):
'decimo_segundo_ordenacao': dict_ord_template[ordenacao.decimo_segundo], 'decimo_segundo_ordenacao': dict_ord_template[ordenacao.decimo_segundo],
'decimo_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro], 'decimo_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro],
'decimo_quarto_ordenacao': dict_ord_template[ordenacao.decimo_quarto], 'decimo_quarto_ordenacao': dict_ord_template[ordenacao.decimo_quarto],
'decimo_quinto_ordenacao': dict_ord_template[ordenacao.decimo_quinto] 'decimo_quinto_ordenacao': dict_ord_template[ordenacao.decimo_quinto],
'decimo_sexto_ordenacao': dict_ord_template[ordenacao.decimo_sexto]
}) })
except KeyError as e: except KeyError as e:
# self.logger.error("KeyError: " + str(e) + ". Erro ao tentar utilizar " # self.logger.error("KeyError: " + str(e) + ". Erro ao tentar utilizar "
@ -1575,17 +1605,18 @@ def relatorio_sessao_plenaria_pdf(request, pk):
'segundo_ordenacao': 'conteudo_multimidia.html', 'segundo_ordenacao': 'conteudo_multimidia.html',
'terceiro_ordenacao': 'mesa_diretora.html', 'terceiro_ordenacao': 'mesa_diretora.html',
'quarto_ordenacao': 'lista_presenca_sessao.html', 'quarto_ordenacao': 'lista_presenca_sessao.html',
'quinto_ordenacao': 'expedientes.html', 'quinto_ordenacao': 'correspondencias.html',
'sexto_ordenacao': 'materias_expediente.html', 'sexto_ordenacao': 'expedientes.html',
'setimo_ordenacao': 'votos_nominais_expediente.html', 'setimo_ordenacao': 'materias_expediente.html',
'oitavo_ordenacao': 'oradores_expediente.html', 'oitavo_ordenacao': 'votos_nominais_expediente.html',
'nono_ordenacao': 'lista_presenca_ordemdia.html', 'nono_ordenacao': 'oradores_expediente.html',
'decimo_ordenacao': 'materias_ordemdia.html', 'decimo_ordenacao': 'lista_presenca_ordemdia.html',
'decimo_primeiro_ordenacao': 'votos_nominais_ordemdia.html', 'decimo_primeiro_ordenacao': 'materias_ordemdia.html',
'decimo_segundo_ordenacao': 'oradores_ordemdia.html', 'decimo_segundo_ordenacao': 'votos_nominais_ordemdia.html',
'decimo_terceiro_ordenacao': 'oradores_explicacoes.html', 'decimo_terceiro_ordenacao': 'oradores_ordemdia.html',
'decimo_quarto_ordenacao': 'ocorrencias_da_sessao.html', 'decimo_quarto_ordenacao': 'oradores_explicacoes.html',
'decimo_quinto_ordenacao': 'consideracoes_finais.html' 'decimo_quinto_ordenacao': 'ocorrencias_da_sessao.html',
'decimo_sexto_ordenacao': 'consideracoes_finais.html'
}) })
html_template = render_to_string( html_template = render_to_string(
@ -1627,7 +1658,7 @@ def gera_etiqueta_ml(materia_legislativa, base_url):
max_ementa_size = 240 max_ementa_size = 240
ementa = materia_legislativa.ementa ementa = materia_legislativa.ementa
ementa = ementa if len( ementa = ementa if len(
ementa) < max_ementa_size else ementa[:max_ementa_size]+"..." ementa) < max_ementa_size else ementa[:max_ementa_size] + "..."
context = { context = {
'numero': materia_legislativa.numero, 'numero': materia_legislativa.numero,

3
sapl/rules/group_sessao.py

@ -21,6 +21,7 @@ rules_group_sessao = {
(sessao.JustificativaAusencia, __base__, __perms_publicas__), (sessao.JustificativaAusencia, __base__, __perms_publicas__),
(sessao.RetiradaPauta, __base__, __perms_publicas__), (sessao.RetiradaPauta, __base__, __perms_publicas__),
(sessao.RegistroLeitura, __base__, __perms_publicas__), (sessao.RegistroLeitura, __base__, __perms_publicas__),
(sessao.ConsideracoesFinais, __base__, __perms_publicas__) (sessao.ConsideracoesFinais, __base__, __perms_publicas__),
(sessao.Correspondencia, __base__, __perms_publicas__)
] ]
} }

127
sapl/sessao/forms.py

@ -1,8 +1,8 @@
from datetime import datetime
import logging
import re import re
from crispy_forms.layout import Button, Fieldset, HTML, Layout from crispy_forms.layout import Button, Fieldset, HTML, Layout
from datetime import datetime
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
@ -13,7 +13,6 @@ from django.forms.widgets import CheckboxSelectMultiple
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters import django_filters
import sapl.utils
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import (form_actions, to_row, from sapl.crispy_layout_mixin import (form_actions, to_row,
SaplFormHelper, SaplFormLayout) SaplFormHelper, SaplFormLayout)
@ -21,6 +20,9 @@ from sapl.materia.forms import MateriaLegislativaFilterSet
from sapl.materia.models import (MateriaLegislativa, StatusTramitacao, from sapl.materia.models import (MateriaLegislativa, StatusTramitacao,
TipoMateriaLegislativa) TipoMateriaLegislativa)
from sapl.parlamentares.models import Mandato, Parlamentar from sapl.parlamentares.models import Mandato, Parlamentar
from sapl.protocoloadm.models import TipoDocumentoAdministrativo,\
DocumentoAdministrativo
from sapl.sessao.models import Correspondencia
from sapl.utils import (autor_label, autor_modal, from sapl.utils import (autor_label, autor_modal,
choice_anos_com_sessaoplenaria, choice_anos_com_sessaoplenaria,
FileFieldCheckMixin, FileFieldCheckMixin,
@ -28,6 +30,7 @@ from sapl.utils import (autor_label, autor_modal,
MateriaPesquisaOrderingFilter, MateriaPesquisaOrderingFilter,
RANGE_DIAS_MES, RANGE_MESES, RANGE_DIAS_MES, RANGE_MESES,
TIME_PATTERN, timezone, validar_arquivo) TIME_PATTERN, timezone, validar_arquivo)
import sapl.utils
from .models import (Bancada, ExpedienteMateria, from .models import (Bancada, ExpedienteMateria,
JustificativaAusencia, OcorrenciaSessao, Orador, JustificativaAusencia, OcorrenciaSessao, Orador,
@ -37,6 +40,7 @@ from .models import (Bancada, ExpedienteMateria,
SessaoPlenaria, SessaoPlenariaPresenca, SessaoPlenaria, SessaoPlenariaPresenca,
TipoResultadoVotacao, TipoRetiradaPauta, Tramitacao) TipoResultadoVotacao, TipoRetiradaPauta, Tramitacao)
MES_CHOICES = RANGE_MESES MES_CHOICES = RANGE_MESES
DIA_CHOICES = RANGE_DIAS_MES DIA_CHOICES = RANGE_DIAS_MES
@ -380,7 +384,8 @@ class ExpedienteMateriaForm(ModelForm):
try: try:
id_t = self.cleaned_data['tramitacao_select'] if self.cleaned_data['tramitacao_select'] != '' else -1 id_t = self.cleaned_data['tramitacao_select'] if self.cleaned_data['tramitacao_select'] != '' else -1
tramitacao = materia.tramitacao_set.get(pk=self.cleaned_data['tramitacao_select'] if self.cleaned_data['tramitacao_select'] != '' else -1) tramitacao = materia.tramitacao_set.get(
pk=self.cleaned_data['tramitacao_select'] if self.cleaned_data['tramitacao_select'] != '' else -1)
except ObjectDoesNotExist: except ObjectDoesNotExist:
if self.cleaned_data['tramitacao_select'] != '': if self.cleaned_data['tramitacao_select'] != '':
raise ValidationError( raise ValidationError(
@ -822,6 +827,10 @@ class ResumoOrdenacaoForm(forms.Form):
label='15°', label='15°',
choices=ORDENACAO_RESUMO choices=ORDENACAO_RESUMO
) )
decimo_sexto = forms.ChoiceField(
label='16°',
choices=ORDENACAO_RESUMO
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
row1 = to_row( row1 = to_row(
@ -856,13 +865,16 @@ class ResumoOrdenacaoForm(forms.Form):
row15 = to_row( row15 = to_row(
[('decimo_quinto', 12)] [('decimo_quinto', 12)]
) )
row16 = to_row(
[('decimo_sexto', 12)]
)
self.helper = SaplFormHelper() self.helper = SaplFormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Fieldset(_(''), Fieldset(_(''),
row1, row2, row3, row4, row5, row1, row2, row3, row4, row5,
row6, row7, row8, row9, row10, row6, row7, row8, row9, row10,
row11, row12, row13, row14, row15, row11, row12, row13, row14, row15, row16,
form_actions(label='Atualizar')) form_actions(label='Atualizar'))
) )
@ -905,6 +917,7 @@ class ResumoOrdenacaoForm(forms.Form):
ordenacao.decimo_terceiro = cleaned_data['decimo_terceiro'] ordenacao.decimo_terceiro = cleaned_data['decimo_terceiro']
ordenacao.decimo_quarto = cleaned_data['decimo_quarto'] ordenacao.decimo_quarto = cleaned_data['decimo_quarto']
ordenacao.decimo_quinto = cleaned_data['decimo_quinto'] ordenacao.decimo_quinto = cleaned_data['decimo_quinto']
ordenacao.decimo_sexto = cleaned_data['decimo_sexto']
ordenacao.save() ordenacao.save()
@ -1064,3 +1077,107 @@ class OrdemExpedienteLeituraForm(forms.ModelForm):
form_actions(more=actions), form_actions(more=actions),
) )
) )
class CorrespondenciaForm(ModelForm):
logger = logging.getLogger(__name__)
tipo_documento = forms.ModelChoiceField(
label='Tipo do Documento',
required=True,
queryset=TipoDocumentoAdministrativo.objects.all(),
empty_label='Selecione',
)
numero_documento = forms.IntegerField(label='Número', required=True)
ano_documento = forms.CharField(label='Ano', required=True)
class Meta:
model = Correspondencia
fields = ['tipo', 'numero_ordem', 'observacao',
'tipo_documento', 'numero_documento', 'ano_documento']
def __init__(self, *args, **kwargs):
return super().__init__(*args, **kwargs)
def clean(self):
super().clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data
try:
self.logger.info("Tentando obter objeto Documento Administrativo (numero={}, ano={}, tipo={})."
.format(cleaned_data['numero_documento'], cleaned_data['ano_documento'], cleaned_data['tipo_documento']))
documento = DocumentoAdministrativo.objects.filter(
numero=cleaned_data['numero_documento'],
ano=cleaned_data['ano_documento'],
tipo=cleaned_data['tipo_documento']).order_by('-id').first()
if not documento:
raise ObjectDoesNotExist()
except ObjectDoesNotExist:
msg = _('{} {}/{} não existe no cadastro de documentos administrativos.'
.format(cleaned_data['tipo_documento'], cleaned_data['numero_documento'], cleaned_data['ano_documento']))
self.logger.warning(
"O Documento Administrativo não existe no cadastro.")
raise ValidationError(msg)
if Correspondencia.objects.filter(
sessao_plenaria=self.instance.sessao_plenaria, documento=documento
).exclude(pk=self.instance.pk).exists():
self.logger.error(
"Documento Administrativo já se encontra nesta Sessão.")
raise ValidationError(
_('Documento Administrativo já se encontra nesta Sessão.'))
cleaned_data['documento'] = documento
return cleaned_data
def clean_numero_ordem(self):
sessao = self.instance.sessao_plenaria
numero_ordem_exists = Correspondencia.objects.filter(
sessao_plenaria=sessao,
numero_ordem=self.cleaned_data['numero_ordem']).exists()
if numero_ordem_exists and not self.instance.pk:
msg = _('Esse número de ordem já existe.')
raise ValidationError(msg)
return self.cleaned_data['numero_ordem']
def save(self, commit=False):
correspondencia = super().save(commit)
correspondencia.documento = self.cleaned_data['documento']
correspondencia.save()
return correspondencia
class CorrespondenciaEmLoteFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
model = DocumentoAdministrativo
fields = ['tipo', 'data']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo do documento'
self.filters['data'].label = 'Data (Inicial - Final)'
self.form.fields['tipo'].required = True
self.form.fields['data'].required = True
row1 = to_row([('tipo', 12)])
row2 = to_row([('data', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Documentos Administrativos'),
row1, row2, form_actions(label='Pesquisar')))

31
sapl/sessao/migrations/0065_correspondencia.py

@ -0,0 +1,31 @@
# Generated by Django 2.2.28 on 2022-08-13 16:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0042_auto_20220805_1236'),
('sessao', '0064_auto_20220713_2335'),
]
operations = [
migrations.CreateModel(
name='Correspondencia',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('observacao', models.TextField(blank=True, verbose_name='Observação')),
('numero_ordem', models.PositiveIntegerField(verbose_name='Nº Ordem')),
('tipo', models.PositiveIntegerField(choices=[(1, 'Recebida'), (2, 'Enviada')], default=1, verbose_name='Tipo da Correspondência')),
('documento', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='protocoloadm.DocumentoAdministrativo', verbose_name='Documento Administrativo')),
('sessao_plenaria', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sessao.SessaoPlenaria')),
],
options={
'verbose_name': 'Correspondência',
'verbose_name_plural': 'Correspondências',
'ordering': ('numero_ordem',),
},
),
]

25
sapl/sessao/migrations/0066_auto_20220813_1431.py

@ -0,0 +1,25 @@
# Generated by Django 2.2.28 on 2022-08-13 17:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0042_auto_20220805_1236'),
('sessao', '0065_correspondencia'),
]
operations = [
migrations.AddField(
model_name='sessaoplenaria',
name='correspondencias',
field=models.ManyToManyField(blank=True, related_name='sessoesplenarias', through='sessao.Correspondencia', to='protocoloadm.DocumentoAdministrativo'),
),
migrations.AlterField(
model_name='correspondencia',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='correspondencia_set', to='sessao.SessaoPlenaria'),
),
]

98
sapl/sessao/migrations/0067_auto_20220813_2233.py

@ -0,0 +1,98 @@
# Generated by Django 2.2.28 on 2022-08-14 01:33
from django.db import migrations, models
def insere_correspondencia_antes_de_exp(apps, schema_editor):
ResumoOrdenacao = apps.get_model('sessao', 'ResumoOrdenacao')
ordenacao = ResumoOrdenacao.objects.get_or_create()[0]
fields = list(map(lambda x: x.name, ResumoOrdenacao._meta.get_fields()))
fields.reverse()
for i, f in enumerate(fields):
setattr(ordenacao, f, getattr(ordenacao, fields[i + 1]))
if i + 2 == len(fields):
return
if getattr(ordenacao, f) == 'exp':
setattr(ordenacao, fields[i + 1], 'correspondencia')
ordenacao.save()
return
class Migration(migrations.Migration):
dependencies = [
('sessao', '0066_auto_20220813_1431'),
]
operations = [
migrations.AddField(
model_name='resumoordenacao',
name='decimo_sexto',
field=models.CharField(default='cons_finais', max_length=50),
),
migrations.AlterField(
model_name='correspondencia',
name='tipo',
field=models.PositiveIntegerField(choices=[(1, 'Recebida'), (2, 'Enviada'), (
2, 'Interna')], default=1, verbose_name='Tipo da Correspondência'),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo',
field=models.CharField(default='lista_p_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_primeiro',
field=models.CharField(default='mat_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_quarto',
field=models.CharField(default='oradores_expli', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_quinto',
field=models.CharField(default='ocorr_sessao', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_segundo',
field=models.CharField(default='v_n_mat_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_terceiro',
field=models.CharField(default='oradores_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='nono',
field=models.CharField(default='oradores_exped', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='oitavo',
field=models.CharField(default='v_n_mat_exp', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='quinto',
field=models.CharField(default='correspondencia', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='setimo',
field=models.CharField(default='mat_exp', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='sexto',
field=models.CharField(default='exp', max_length=50),
),
migrations.RunPython(insere_correspondencia_antes_de_exp)
]

54
sapl/sessao/models.py

@ -13,6 +13,7 @@ from sapl.materia.models import MateriaLegislativa
from sapl.materia.models import Tramitacao from sapl.materia.models import Tramitacao
from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar, from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar,
Partido, SessaoLegislativa) Partido, SessaoLegislativa)
from sapl.protocoloadm.models import DocumentoAdministrativo
from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation, from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation,
get_settings_auth_user_model, get_settings_auth_user_model,
restringe_tipos_de_arquivo_txt, texto_upload_path, restringe_tipos_de_arquivo_txt, texto_upload_path,
@ -248,6 +249,17 @@ class SessaoPlenaria(models.Model):
choices=YES_NO_CHOICES, choices=YES_NO_CHOICES,
verbose_name=_('Publicar Pauta?')) verbose_name=_('Publicar Pauta?'))
correspondencias = models.ManyToManyField(
DocumentoAdministrativo,
blank=True,
through='Correspondencia',
related_name='sessoesplenarias',
through_fields=(
'sessao_plenaria',
'documento'
)
)
class Meta: class Meta:
verbose_name = _('Sessão Plenária') verbose_name = _('Sessão Plenária')
verbose_name_plural = _('Sessões Plenárias') verbose_name_plural = _('Sessões Plenárias')
@ -730,6 +742,7 @@ ORDENACAO_RESUMO = [
('cont_mult', 'Conteúdo Multimídia'), ('cont_mult', 'Conteúdo Multimídia'),
('mesa_d', 'Mesa Diretora'), ('mesa_d', 'Mesa Diretora'),
('lista_p', 'Lista de Presença'), ('lista_p', 'Lista de Presença'),
('correspondencia', 'Correspondências'),
('exp', 'Expedientes'), ('exp', 'Expedientes'),
('mat_exp', 'Matérias do Expediente'), ('mat_exp', 'Matérias do Expediente'),
('v_n_mat_exp', 'Votações Nominais - Matérias do Expediente'), ('v_n_mat_exp', 'Votações Nominais - Matérias do Expediente'),
@ -810,6 +823,10 @@ class ResumoOrdenacao(models.Model):
max_length=50, max_length=50,
default=ORDENACAO_RESUMO[14][0] default=ORDENACAO_RESUMO[14][0]
) )
decimo_sexto = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[15][0]
)
class Meta: class Meta:
verbose_name = _('Ordenação do Resumo de uma Sessão') verbose_name = _('Ordenação do Resumo de uma Sessão')
@ -1018,3 +1035,40 @@ class RegistroLeitura(models.Model):
'RegistroLeitura deve ter exatamente um dos campos ' 'RegistroLeitura deve ter exatamente um dos campos '
'ordem ou expediente preenchido. Ambos estão preenchidos: ' 'ordem ou expediente preenchido. Ambos estão preenchidos: '
'{}, {}'. format(self.ordem, self.expediente)) '{}, {}'. format(self.ordem, self.expediente))
@reversion.register()
class Correspondencia(models.Model):
TIPO_CHOICES = Choices(
(1, 'recebida', 'Recebida'),
(2, 'enviada', 'Enviada'),
(2, 'interna', 'Interna'),
)
sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.CASCADE,
related_name='correspondencia_set')
documento = models.ForeignKey(DocumentoAdministrativo,
on_delete=models.PROTECT,
verbose_name=_('Documento Administrativo'))
observacao = models.TextField(
blank=True, verbose_name=_('Observação'))
numero_ordem = models.PositiveIntegerField(verbose_name=_('Nº Ordem'))
tipo = models.PositiveIntegerField(
verbose_name=_('Tipo da Correspondência'),
choices=TIPO_CHOICES, default=1)
class Meta:
verbose_name = _('Correspondência')
verbose_name_plural = _('Correspondências')
ordering = ('numero_ordem',)
@property
def assunto(self):
return self.documento.assunto
def __str__(self):
return _('Correspondência: {}').format(self.documento.epigrafe)

15
sapl/sessao/urls.py

@ -37,7 +37,8 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
retirar_leitura, retirar_leitura,
TransferenciaMateriasExpediente, TransferenciaMateriasOrdemDia, TransferenciaMateriasExpediente, TransferenciaMateriasOrdemDia,
filtra_materias_copia_sessao_ajax, verifica_materia_sessao_plenaria_ajax, filtra_materias_copia_sessao_ajax, verifica_materia_sessao_plenaria_ajax,
recuperar_tramitacao) recuperar_tramitacao, CorrespondenciaEmLoteView,
CorrespondenciaCrud, recuperar_documento)
from .apps import AppConfig from .apps import AppConfig
@ -52,7 +53,13 @@ urlpatterns = [
JustificativaAusenciaCrud.get_urls() + JustificativaAusenciaCrud.get_urls() +
MateriaOrdemDiaCrud.get_urls() + MateriaOrdemDiaCrud.get_urls() +
OradorOrdemDiaCrud.get_urls() + OradorOrdemDiaCrud.get_urls() +
RetiradaPautaCrud.get_urls())), RetiradaPautaCrud.get_urls() +
CorrespondenciaCrud.get_urls()
)),
url(r'^sessao/(?P<pk>\d+)/correspondencia-em-lote', CorrespondenciaEmLoteView.as_view(),
name='correspondencia_em_lote'),
url(r'^sessao/(?P<pk>\d+)/mesa$', MesaView.as_view(), name='mesa'), url(r'^sessao/(?P<pk>\d+)/mesa$', MesaView.as_view(), name='mesa'),
@ -68,6 +75,7 @@ urlpatterns = [
remove_parlamentar_composicao, remove_parlamentar_composicao,
name='remove_parlamentar_composicao'), name='remove_parlamentar_composicao'),
url(r'^sessao/recuperar-documento/', recuperar_documento),
url(r'^sessao/recuperar-materia/', recuperar_materia), url(r'^sessao/recuperar-materia/', recuperar_materia),
url(r'^sessao/recuperar-tramitacao/', recuperar_tramitacao), url(r'^sessao/recuperar-tramitacao/', recuperar_tramitacao),
url(r'^sessao/recuperar-numero-sessao/', url(r'^sessao/recuperar-numero-sessao/',
@ -91,7 +99,8 @@ urlpatterns = [
abrir_votacao, abrir_votacao,
name="abrir_votacao"), name="abrir_votacao"),
url(r'^sessao/(?P<pk>\d+)/reordena/(?P<tipo>[\w\-]+)/(?P<ordenacao>\d+)/$', reordena_materias, name="reordena_materias"), url(r'^sessao/(?P<pk>\d+)/reordena/(?P<tipo>[\w\-]+)/(?P<ordenacao>\d+)/$',
reordena_materias, name="reordena_materias"),
url(r'^sistema/sessao-plenaria/tipo/', url(r'^sistema/sessao-plenaria/tipo/',
include(TipoSessaoCrud.get_urls())), include(TipoSessaoCrud.get_urls())),

340
sapl/sessao/views.py

@ -13,9 +13,11 @@ from django.db.models import Max, Q
from django.http import JsonResponse from django.http import JsonResponse
from django.http.response import Http404, HttpResponseRedirect from django.http.response import Http404, HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
from django.urls.base import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.datastructures import MultiValueDictKeyError from django.utils.datastructures import MultiValueDictKeyError
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.encoding import force_text
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -36,8 +38,12 @@ from sapl.materia.models import (Autoria, TipoMateriaLegislativa,
from sapl.materia.views import MateriaLegislativaPesquisaView from sapl.materia.views import MateriaLegislativaPesquisaView
from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato, from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato,
Parlamentar, SessaoLegislativa) Parlamentar, SessaoLegislativa)
from sapl.protocoloadm.models import TipoDocumentoAdministrativo,\
DocumentoAdministrativo
from sapl.sessao.apps import AppConfig from sapl.sessao.apps import AppConfig
from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedienteLeituraForm from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedienteLeituraForm,\
CorrespondenciaForm, CorrespondenciaEmLoteFilterSet
from sapl.sessao.models import Correspondencia
from sapl.settings import TIME_ZONE from sapl.settings import TIME_ZONE
from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip
@ -1888,7 +1894,8 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
'decimo_segundo': self.get_tupla(ordenacao.decimo_segundo), 'decimo_segundo': self.get_tupla(ordenacao.decimo_segundo),
'decimo_terceiro': self.get_tupla(ordenacao.decimo_terceiro), 'decimo_terceiro': self.get_tupla(ordenacao.decimo_terceiro),
'decimo_quarto': self.get_tupla(ordenacao.decimo_quarto), 'decimo_quarto': self.get_tupla(ordenacao.decimo_quarto),
'decimo_quinto': self.get_tupla(ordenacao.decimo_quinto) 'decimo_quinto': self.get_tupla(ordenacao.decimo_quinto),
'decimo_sexto': self.get_tupla(ordenacao.decimo_sexto)
} }
return initial return initial
@ -1968,6 +1975,36 @@ def get_presenca_sessao(sessao_plenaria):
'justificativa_ausencia': ausentes_sessao}) 'justificativa_ausencia': ausentes_sessao})
def get_correspondencias(sessao_plenaria, user):
qs = sessao_plenaria.correspondencia_set.all()
is_anon = user.is_anonymous
is_ostensivo = AppsAppConfig.attr(
'documentos_administrativos') == 'O'
if is_anon and not is_ostensivo:
qs = qs.none()
if is_anon:
qs = qs.filter(documento__restrito=False)
results = []
for c in qs:
d = c.documento
results.append(
{
'id': d.id,
'tipo': c.get_tipo_display(),
'epigrafe': d.epigrafe,
'data': d.data.strftime('%d/%m/%Y'),
'assunto': d.assunto,
'restrito': d.restrito,
'is_ostensivo': is_ostensivo
}
)
return {'correspondencias': results}
def get_expedientes(sessao_plenaria): def get_expedientes(sessao_plenaria):
expediente = ExpedienteSessao.objects.filter( expediente = ExpedienteSessao.objects.filter(
sessao_plenaria_id=sessao_plenaria.id).order_by('tipo__ordenacao', 'tipo__nome') sessao_plenaria_id=sessao_plenaria.id).order_by('tipo__ordenacao', 'tipo__nome')
@ -2301,6 +2338,9 @@ class ResumoView(DetailView):
# Presença Sessão # Presença Sessão
context.update(get_presenca_sessao(self.object)) context.update(get_presenca_sessao(self.object))
# ===================================================================== # =====================================================================
# Correspondências
context.update(get_correspondencias(self.object, self.request.user))
# =====================================================================
# Expedientes # Expedientes
context.update(get_expedientes(self.object)) context.update(get_expedientes(self.object))
# ===================================================================== # =====================================================================
@ -2351,9 +2391,10 @@ class ResumoView(DetailView):
# Indica a ordem com a qual o template será renderizado # Indica a ordem com a qual o template será renderizado
dict_ord_template = { dict_ord_template = {
'cont_mult': 'conteudo_multimidia.html', 'cont_mult': 'conteudo_multimidia.html',
'correspondencia': 'correspondencias.html',
'exp': 'expedientes.html', 'exp': 'expedientes.html',
'id_basica': 'identificacao_basica.html', 'id_basica': 'identificacao_basica.html',
'lista_p': 'lista_presenca.html', 'lista_p': 'lista_presenca_sessao.html',
'lista_p_o_d': 'lista_presenca_ordem_dia.html', 'lista_p_o_d': 'lista_presenca_ordem_dia.html',
'mat_exp': 'materias_expediente.html', 'mat_exp': 'materias_expediente.html',
'v_n_mat_exp': 'votos_nominais_materias_expediente.html', 'v_n_mat_exp': 'votos_nominais_materias_expediente.html',
@ -2384,7 +2425,8 @@ class ResumoView(DetailView):
'decimo_segundo_ordenacao': dict_ord_template[ordenacao.decimo_segundo], 'decimo_segundo_ordenacao': dict_ord_template[ordenacao.decimo_segundo],
'decimo_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro], 'decimo_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro],
'decimo_quarto_ordenacao': dict_ord_template[ordenacao.decimo_quarto], 'decimo_quarto_ordenacao': dict_ord_template[ordenacao.decimo_quarto],
'decimo_quinto_ordenacao': dict_ord_template[ordenacao.decimo_quinto] 'decimo_quinto_ordenacao': dict_ord_template[ordenacao.decimo_quinto],
'decimo_sexto_ordenacao': dict_ord_template[ordenacao.decimo_sexto]
}) })
except KeyError as e: except KeyError as e:
self.logger.error("KeyError: " + str(e) + ". Erro ao tentar utilizar " self.logger.error("KeyError: " + str(e) + ". Erro ao tentar utilizar "
@ -2393,18 +2435,19 @@ class ResumoView(DetailView):
'primeiro_ordenacao': 'identificacao_basica.html', 'primeiro_ordenacao': 'identificacao_basica.html',
'segundo_ordenacao': 'conteudo_multimidia.html', 'segundo_ordenacao': 'conteudo_multimidia.html',
'terceiro_ordenacao': 'mesa_diretora.html', 'terceiro_ordenacao': 'mesa_diretora.html',
'quarto_ordenacao': 'lista_presenca.html', 'quarto_ordenacao': 'lista_presenca_sessao.html',
'quinto_ordenacao': 'expedientes.html', 'quinto_ordenacao': 'correspondencias.html',
'sexto_ordenacao': 'materias_expediente.html', 'sexto_ordenacao': 'expedientes.html',
'setimo_ordenacao': 'votos_nominais_materias_expediente.html', 'setimo_ordenacao': 'materias_expediente.html',
'oitavo_ordenacao': 'oradores_expediente.html', 'oitavo_ordenacao': 'votos_nominais_materias_expediente.html',
'nono_ordenacao': 'lista_presenca_ordem_dia.html', 'nono_ordenacao': 'oradores_expediente.html',
'decimo_ordenacao': 'materias_ordem_dia.html', 'decimo_ordenacao': 'lista_presenca_ordem_dia.html',
'decimo_primeiro_ordenacao': 'votos_nominais_materias_ordem_dia.html', 'decimo_primeiro_ordenacao': 'materias_ordem_dia.html',
'decimo_segundo_ordenacao': 'oradores_ordemdia.html', 'decimo_segundo_ordenacao': 'votos_nominais_materias_ordem_dia.html',
'decimo_terceiro_ordenacao': 'oradores_explicacoes.html', 'decimo_terceiro_ordenacao': 'oradores_ordemdia.html',
'decimo_quarto_ordenacao': 'ocorrencias_da_sessao.html', 'decimo_quarto_ordenacao': 'oradores_explicacoes.html',
'decimo_quinto_ordenacao': 'consideracoes_finais.html' 'decimo_quinto_ordenacao': 'ocorrencias_da_sessao.html',
'decimo_sexto_ordenacao': 'consideracoes_finais.html'
}) })
sessao = context['object'] sessao = context['object']
@ -3808,6 +3851,31 @@ class PautaSessaoDetailView(DetailView):
'autor': [str(x.autor) for x in m.materia.autoria_set.select_related('autor').all()] 'autor': [str(x.autor) for x in m.materia.autoria_set.select_related('autor').all()]
}) })
context.update({'materia_expediente': materias_expediente}) context.update({'materia_expediente': materias_expediente})
# =====================================================================
# Correspondencias
correspondencias = []
qs = self.object.correspondencia_set.all()
is_anon = request.user.is_anonymous
is_ostensivo = AppsAppConfig.attr('documentos_administrativos') == 'O'
if is_anon and not is_ostensivo:
qs = qs.none()
elif is_anon:
qs = qs.filter(documento__restrito=False)
for c in qs:
d = c.documento
correspondencias.append(
{
'id': d.id,
'tipo': c.get_tipo_display(),
'epigrafe': d.epigrafe,
'data': d.data.strftime('%d/%m/%Y'),
'assunto': d.assunto,
'restrito': d.restrito,
'is_ostensivo': is_ostensivo
}
)
context.update({'correspondencias': correspondencias})
# ===================================================================== # =====================================================================
# Expedientes # Expedientes
expedientes = [] expedientes = []
@ -4985,3 +5053,243 @@ def retirar_leitura(request, pk, iso, oid):
ordem_expediente.votacao_aberta = False ordem_expediente.votacao_aberta = False
ordem_expediente.save() ordem_expediente.save()
return HttpResponseRedirect(succ_url) return HttpResponseRedirect(succ_url)
def recuperar_documento(request):
tipo = request.GET['tipo_documento']
numero = request.GET['numero_documento']
ano = request.GET['ano_documento']
is_ostensivo = AppsAppConfig.attr('documentos_administrativos') == 'O'
qs = DocumentoAdministrativo.objects.order_by('-id')
qs = qs.filter(tipo_id=tipo, ano=ano, numero=numero)
is_anon = request.user.is_anonymous
is_restrito = qs.filter(restrito=True).exists()
if is_anon and not is_ostensivo or is_anon and is_restrito or not qs.exists():
return JsonResponse({'detail': 'Documento administrativo não encontrado.'})
d = qs.first()
return JsonResponse(
{
'id': d.id,
'epigrafe': d.epigrafe,
'data': d.data.strftime('%d/%m/%Y'),
'assunto': d.assunto,
'restrito': d.restrito,
'is_ostensivo': is_ostensivo
}
)
class CorrespondenciaCrud(MasterDetailCrud):
model = Correspondencia
parent_field = 'sessao_plenaria'
help_topic = 'sessaoplenaria_correspondencia'
public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = [('ordem_tipo'),
'correspondencia', 'documento__data', 'documento']
@property
def verbose_name(self):
return _('Correspondência')
@property
def verbose_name_plural(self):
return _('Correspondências')
@property
def title(self):
return self.object.sessao_plenaria
class ListView(MasterDetailCrud.ListView):
def get_queryset(self):
qs = super().get_queryset()
is_anon = self.request.user.is_anonymous
is_ostensivo = AppsAppConfig.attr(
'documentos_administrativos') == 'O'
if is_anon and not is_ostensivo:
return qs.none()
if is_anon:
return qs.filter(documento__restrito=False)
return qs
def hook_header_ordem_tipo(self, *args, **kwargs):
return force_text(_('Ordem / Tipo')) if not self.request.user.is_anonymous else force_text(_('Tipo'))
def hook_ordem_tipo(self, obj, ss, url):
if not self.request.user.is_anonymous:
return f'{obj.numero_ordem} - {obj.get_tipo_display()}', url
else:
return f'{obj.get_tipo_display()}', url
def hook_header_correspondencia(self, *args, **kwargs):
return force_text(_('Correspondência'))
def hook_correspondencia(self, obj, ss, url):
return obj.documento.epigrafe, reverse_lazy(
'sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': obj.documento.id})
class CreateView(MasterDetailCrud.CreateView):
form_class = CorrespondenciaForm
def get_initial(self):
initial = super().get_initial()
max_numero_ordem = Correspondencia.objects.filter(
sessao_plenaria=self.kwargs['pk']).aggregate(
Max('numero_ordem'))['numero_ordem__max']
initial['numero_ordem'] = (
max_numero_ordem if max_numero_ordem else 0) + 1
return initial
class UpdateView(MasterDetailCrud.UpdateView):
form_class = CorrespondenciaForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.object.documento.epigrafe
return context
def get_initial(self):
initial = super().get_initial()
initial['tipo_documento'] = self.object.documento.tipo.id
initial['numero_documento'] = self.object.documento.numero
initial['ano_documento'] = self.object.documento.ano
return initial
class DetailView(MasterDetailCrud.DetailView):
@property
def layout_key(self):
return 'CorrespondenciaDetail'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.object.sessao_plenaria
return context
def hook_documento(self, obj, verbose_name=None, field_display=None):
d = obj.documento
url = reverse(
'sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': d.id}
)
return (
verbose_name,
f'<a href="{url}">{d.epigrafe}</a><br>{d.assunto}'
)
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
is_anon = self.request.user.is_anonymous
is_ostensivo = AppsAppConfig.attr(
'documentos_administrativos') == 'O'
if is_anon and not is_ostensivo:
raise Http404()
if is_anon and obj.documento.restrito:
raise Http404()
return obj
class CorrespondenciaEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = CorrespondenciaEmLoteFilterSet
template_name = 'sessao/em_lote/correspondencia.html'
permission_required = ('sessao.add_correspondencia',)
def get_queryset(self):
qs = super().get_queryset()
return qs.filter(sessao_plenaria_id=self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['root_pk'] = self.kwargs['pk']
s = SessaoPlenaria.objects.get(pk=self.kwargs['pk'])
context['subnav_template_name'] = 'sessao/subnav.yaml'
context['title'] = _(
'Correspondencias em Lote <small>({})</small>').format(s)
# Verifica se os campos foram preenchidos
msg = None
if not self.request.GET.get('tipo', " "):
msg = _('Por favor, selecione um tipo de documento administrativo.')
messages.add_message(self.request, messages.ERROR, msg)
if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "):
msg = _('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
if msg:
return context
qr = self.request.GET.copy()
if not len(qr):
context['object_list'] = []
else:
context['object_list'] = context['object_list'].order_by(
'numero', '-ano')
sessao_plenaria = SessaoPlenaria.objects.get(
pk=self.kwargs['pk'])
not_list = [self.kwargs['pk']] + \
[m for m in sessao_plenaria.correspondencias.values_list(
'id', flat=True)]
context['object_list'] = context['object_list'].exclude(
pk__in=not_list)
context['numero_res'] = len(context['object_list'])
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
return context
def post(self, request, *args, **kwargs):
marcados = request.POST.getlist('documento_id')
if len(marcados) == 0:
msg = _('Nenhum documento foi selecionado.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
sessao_plenaria = SessaoPlenaria.objects.get(pk=kwargs['pk'])
max_numero_ordem = Correspondencia.objects.filter(
sessao_plenaria=self.kwargs['pk']).aggregate(
Max('numero_ordem'))['numero_ordem__max'] or 0
for documento in DocumentoAdministrativo.objects.filter(
id__in=marcados
).values_list('id', flat=True):
max_numero_ordem += 1
c = Correspondencia()
c.numero_ordem = max_numero_ordem
c.sessao_plenaria = sessao_plenaria
c.documento_id = documento
c.tipo = request.POST.get('tipo')
c.save()
msg = _('Correspondencias adicionadas.')
messages.add_message(request, messages.SUCCESS, msg)
success_url = reverse('sapl.sessao:correspondencia_list',
kwargs={'pk': kwargs['pk']})
return HttpResponseRedirect(success_url)

1
sapl/static/sapl/css/relatorio.css

@ -68,6 +68,7 @@ table.grayTable {
} }
table.grayTable td, table.grayTable th { table.grayTable td, table.grayTable th {
border: 1px solid #000000; border: 1px solid #000000;
padding: 5px;
} }
table.grayTable tbody td { table.grayTable tbody td {
font-size: 10px; font-size: 10px;

18
sapl/templates/relatorios/blocos_sessao_plenaria/correspondencias.html

@ -0,0 +1,18 @@
{% load common_tags %}
<h2 class="gray-title">Correspondências</h2>
<table class="grayTable">
<tbody>
{% for c in lst_correspondencias%}
<tr>
<td>
<strong>({{c.tipo}})</strong> <strong>{{c.epigrafe}}</strong><br>
<strong>Data do Documento: </strong>{{c.data}}<br>
<strong>Assunto:</strong> {{c.assunto}}
</td>
</tr>
{% endfor %}
</tbody>
</table>

21
sapl/templates/relatorios/blocos_sessao_plenaria/materias_expediente.html

@ -1,8 +1,8 @@
{% load common_tags %} {% load common_tags %}
<h2 class="gray-title">Matérias do Expediente</h2>
<h2 class="gray-title">Matérias do Expediente</h2> <table class="grayTable">
{% for materia in lst_expediente_materia%}
<table class="grayTable"> {% if forloop.first %}
<thead> <thead>
<tr> <tr>
<th>Matéria</th> <th>Matéria</th>
@ -11,7 +11,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for materia in lst_expediente_materia%} {% endif %}
<tr> <tr>
<td > <td >
<dl> <dl>
@ -31,7 +31,8 @@
<br><br>{{materia.votacao_observacao}} <br><br>{{materia.votacao_observacao}}
</td> </td>
</tr> </tr>
{% endfor %} {% if forloop.last %}
</tbody>
</tbody> {% endif %}
</table> {% endfor %}
</table>

15
sapl/templates/relatorios/blocos_sessao_plenaria/materias_ordemdia.html

@ -1,6 +1,8 @@
<h2 class="gray-title">Matérias da Ordem do Dia</h2> <h2 class="gray-title">Matérias da Ordem do Dia</h2>
<table class="grayTable" style="height: 145px;" width="443"> {% for materia in lst_votacao%}
{% if forloop.first %}
<table class="grayTable" style="height: 145px;" width="443">
<thead> <thead>
<tr> <tr>
<th>Matéria</th> <th>Matéria</th>
@ -9,7 +11,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for materia in lst_votacao%} {% endif %}
<tr> <tr>
<td> <td>
<dl> <dl>
@ -29,7 +31,8 @@
<br><br>{{materia.votacao_observacao}} <br><br>{{materia.votacao_observacao}}
</td> </td>
</tr> </tr>
{% endfor %} {% if forloop.last %}
</tbody> </tbody>
</table> </table>
{% endif %}
{% endfor %}

3
sapl/templates/relatorios/relatorio_ata.html

@ -5,7 +5,8 @@
<h3 style="text-align:center;">Ata Eletrônica da {{sessaoplenaria}}</h3> <h3 style="text-align:center;">Ata Eletrônica da {{sessaoplenaria}}</h3>
{% include 'sessao/blocos_ata/identificacao_basica.html' %} {% include 'sessao/blocos_ata/identificacao_basica.html' %}
{% include 'sessao/blocos_ata/mesa_diretora.html' %} {% include 'sessao/blocos_ata/mesa_diretora.html' %}
{% include 'sessao/blocos_ata/lista_presenca.html' %} {% include 'sessao/blocos_ata/lista_presenca_sessao.html' %}
{% include 'sessao/blocos_ata/correspondencias.html' %}
{% include 'sessao/blocos_ata/expedientes.html' %} {% include 'sessao/blocos_ata/expedientes.html' %}
{% include 'sessao/blocos_ata/materias_expediente.html' %} {% include 'sessao/blocos_ata/materias_expediente.html' %}
{% include 'sessao/blocos_ata/oradores_expediente.html' %} {% include 'sessao/blocos_ata/oradores_expediente.html' %}

14
sapl/templates/relatorios/relatorio_pauta_sessao.html

@ -18,6 +18,20 @@
{% for b in basica %} {% for b in basica %}
{{ b }}<br/> {{ b }}<br/>
{% endfor %} {% endfor %}
<h2 class="gray-title">Correspondências</h2>
<table class="grayTable">
<tbody>
{% for c in correspondencias%}
<tr>
<td>
<strong>({{c.tipo}})</strong> <strong>{{c.epigrafe}}</strong><br>
<strong>Data do Documento: </strong>{{c.data}}<br>
<strong>Assunto:</strong> {{c.assunto}}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2 class="gray-title">Expedientes</h2> <h2 class="gray-title">Expedientes</h2>
{% for e in expedientes %} {% for e in expedientes %}
<b>{{ e.tipo }}: </b><br/><p>{{ e.conteudo|safe }}</p> <b>{{ e.tipo }}: </b><br/><p>{{ e.conteudo|safe }}</p>

3
sapl/templates/relatorios/relatorio_sessao_plenaria.html

@ -34,8 +34,9 @@
{% include 'relatorios/blocos_sessao_plenaria/'|add:decimo_quinto_ordenacao %} {% include 'relatorios/blocos_sessao_plenaria/'|add:decimo_quinto_ordenacao %}
{% include 'relatorios/blocos_sessao_plenaria/'|add:decimo_sexto_ordenacao %}
</div> </div>
{% endblock content %} {% endblock content %}

12
sapl/templates/sessao/blocos_ata/correspondencias.html

@ -0,0 +1,12 @@
{% if correspondencias %}
<fieldset>
<p align="justify">
<strong>Correspondências:</strong>
{% for c in correspondencias %}
{{forloop.counter}}) <strong>{{c.tipo}}</strong> - <strong>{{c.epigrafe}}</strong> -
<strong>Data: </strong>{{c.data}} -
<strong>Assunto:</strong> {{c.assunto}};
{% endfor %}
</p>
</fieldset>
{% endif %}

0
sapl/templates/sessao/blocos_ata/lista_presenca.html → sapl/templates/sessao/blocos_ata/lista_presenca_sessao.html

17
sapl/templates/sessao/blocos_resumo/correspondencias.html

@ -0,0 +1,17 @@
{% if correspondencias %}
<fieldset>
<legend>Correspondências</legend>
<table class="table">
{% for c in correspondencias %}
<tr>
<td>
<strong>({{c.tipo}})</strong> <strong>{{c.epigrafe}}</strong><br>
<strong>Data do Documento: </strong>{{c.data}}<br>
<strong>Assunto:</strong> {{c.assunto}}
</td>
</tr>
{% endfor %}
</table>
</fieldset>
<br /><br /><br />
{% endif %}

0
sapl/templates/sessao/blocos_resumo/lista_presenca.html → sapl/templates/sessao/blocos_resumo/lista_presenca_sessao.html

50
sapl/templates/sessao/correspondencia_form.html

@ -0,0 +1,50 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% block extra_js %}
<script language="Javascript">
// document.getElementById("id_observacao").readOnly = true;
function recuperar_documento() {
var tipo_documento = $("#id_tipo_documento").val()
var numero_documento = $("#id_numero_documento").val()
var ano_documento = $("#id_ano_documento").val()
if (tipo_documento && numero_documento && ano_documento) {
$.get("/sessao/recuperar-documento/",
{ tipo_documento: tipo_documento, numero_documento: numero_documento, ano_documento: ano_documento },
function(data, status) {
$('.assunto-documento').closest('.row').remove()
$("#div_id_tipo_documento").closest('.row').after(
$('<div class="row"/>').append(
$('<div class="col-12"/>').append(
$(`<div class="alert alert-${data.restrito || !data.is_ostensivo ? 'danger' : 'info'} assunto-documento"/>`).html(
(data.id === undefined) ? `<strong>${data.detail}</strong>`
:
`
${data.restrito? '<strong>ATENÇÃO, este é um documento restrito!!!</strong><br>':''}
${!data.is_ostensivo? '<strong>ATENÇÃO, os documentos administrativos estão configurados globalmente como restritos!!!</strong><br>':''}
<strong>${data.epigrafe}</strong><br>
<strong>Data do Documento: </strong>${data.data}<br>
<strong>Assunto:</strong> ${data.assunto}<br><br>
`
)
)
)
)
}
)
}
}
var fields = ["#id_tipo_documento", "#id_numero_documento", "#id_ano_documento"];
for (i = 0; i < fields.length; i++){
$(fields[i]).change(function() {
recuperar_documento();
});
}
recuperar_documento();
$(document).ready( function() {
});
</script>
{% endblock %}

14
sapl/templates/sessao/correspondencia_list.html

@ -0,0 +1,14 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load common_tags %}
{% block more_buttons %}
{% if perms|get_add_perm:view %}
<a href="{% url 'sapl.sessao:correspondencia_em_lote' root_pk %}" class="btn btn-outline-primary">
{% trans "Adicionar Correspondencias em Lote" %}
</a>
{% endif %}
{% endblock more_buttons %}

89
sapl/templates/sessao/em_lote/correspondencia.html

@ -0,0 +1,89 @@
{% extends "crud/detail.html" %}
{% load i18n crispy_forms_tags %}
{% block actions %}{% endblock %}
{% block detail_content %}
{% if not show_results %}
{% crispy filter.form %}
{% endif %}
{% if show_results %}
{% if numero_res > 0 %}
{% if numero_res == 1 %}
<h3 style="text-align: right;">{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 documento.'%}</h3>
{% else %}
<h3 style="text-align: right;">Foram encontrados {{ numero_res }} documentos.</h3>
{% endif %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Tipo*</label>
<div class="d-flex flex-row ">
<label for="tip_recebido" class="d-flex flex-row align-items-center px-2">
<input type="radio" id="tip_recebido" name="tipo" value="1" class="form-control" required="True">
Recebidas
</label>
<label for="tip_enviado" class="d-flex flex-row align-items-center px-2">
<input type="radio" id="tip_enviado" name="tipo" value="2" class="form-control" required="True">
Enviadas
</label>
<label for="tip_interno" class="d-flex flex-row align-items-center px-2">
<input type="radio" id="tip_interno" name="tipo" value="3" class="form-control" required="True">
Internas
</label>
</div>
</div>
</div>
</div>
</fieldset>
<br />
<fieldset>
<legend>Documentos para Vincular em Lote</legend>
<table class="table table-striped table-hover">
<div class="controls">
<div class="checkbox">
<label for="id_check_all"><input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos</label>
<br><br>
<small>OBS: Documentos já inseridos na sessão atual não aparecem na lista abaixo.</small>
</div>
</div>
<thead><tr><th>Documento</th></tr></thead>
<tbody>
{% for documento in object_list %}
<tr>
<td class="p-0">
<label for="doc_{{documento.id}}" class="d-flex w-100 p-3">
<input type="checkbox" id="doc_{{documento.id}}" name="documento_id" value="{{documento.id}}" {% if check %} checked {% endif %}/>
<span>
{{documento.epigrafe}} - {{documento.assunto}}
{% if documento.restrito %}<br><small class="text-danger" >(Documento Restrito)</small> {% endif %}
</span>
</label>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</fieldset>
<input type="submit" value="Salvar" class="btn btn-primary"S>
</form>
{% else %}
<tr><td><h3 style="text-align: right;">Nenhuma documento encontrado.</h3></td></tr>
{% endif %}
{% endif %}
{% endblock detail_content %}
{% block extra_js %}
<script language="JavaScript">
function checkAll(elem) {
let checkboxes = document.getElementsByName('documento_id');
for (let i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].type == 'checkbox')
checkboxes[i].checked = elem.checked;
}
}
</script>
{% endblock %}

12
sapl/templates/sessao/layouts.yaml

@ -123,3 +123,15 @@ RetiradaPauta:
- tipo_de_retirada materia - tipo_de_retirada materia
- data parlamentar - data parlamentar
- observacao - observacao
Correspondencia:
{% trans 'Documento Administrativo' %}:
- numero_ordem:3 tipo:4
- tipo_documento:6 numero_documento:3 ano_documento:3
- observacao
CorrespondenciaDetail:
{% trans 'Correspondencia' %}:
- numero_ordem:2 tipo:3 sessao_plenaria
- documento|fk_urlize_for_detail
- observacao

16
sapl/templates/sessao/pauta_sessao_detail.html

@ -16,6 +16,22 @@
</thead> </thead>
</table> </table>
</fieldset> </fieldset>
<fieldset>
<legend>Corresondências</legend>
<table class="table">
<thead class="thead-default">
{% for c in correspondencias %}
<tr>
<td>
<strong>({{c.tipo}})</strong> <strong>{{c.epigrafe}}</strong><br>
<strong>Data do Documento: </strong>{{c.data}}<br>
<strong>Assunto:</strong> {{c.assunto}}
</td>
</tr>
{% endfor %}
</thead>
</table>
</fieldset>
<fieldset> <fieldset>
<legend>Expedientes</legend> <legend>Expedientes</legend>
<table class="table"> <table class="table">

2
sapl/templates/sessao/resumo.html

@ -52,5 +52,7 @@
{% include 'sessao/blocos_resumo/'|add:decimo_quinto_ordenacao %} {% include 'sessao/blocos_resumo/'|add:decimo_quinto_ordenacao %}
{% include 'sessao/blocos_resumo/'|add:decimo_sexto_ordenacao %}
{% endblock detail_content %} {% endblock detail_content %}

1
sapl/templates/sessao/resumo_ata.html

@ -34,5 +34,6 @@
{% include 'sessao/blocos_ata/'|add:decimo_terceiro_ordenacao %} {% include 'sessao/blocos_ata/'|add:decimo_terceiro_ordenacao %}
{% include 'sessao/blocos_ata/'|add:decimo_quarto_ordenacao %} {% include 'sessao/blocos_ata/'|add:decimo_quarto_ordenacao %}
{% include 'sessao/blocos_ata/'|add:decimo_quinto_ordenacao %} {% include 'sessao/blocos_ata/'|add:decimo_quinto_ordenacao %}
{% include 'sessao/blocos_ata/'|add:decimo_sexto_ordenacao %}
{% include 'sessao/blocos_ata/assinaturas.html' %} {% include 'sessao/blocos_ata/assinaturas.html' %}
{% endblock detail_content %} {% endblock detail_content %}

2
sapl/templates/sessao/subnav-solene.yaml

@ -19,6 +19,8 @@
children: children:
- title: {% trans 'Expediente Diversos' %} - title: {% trans 'Expediente Diversos' %}
url: expediente url: expediente
- title: {% trans 'Correspondências' %}
url: correspondencia_list
- title: {% trans 'Oradores do Expediente' %} - title: {% trans 'Oradores do Expediente' %}
url: oradorexpediente_list url: oradorexpediente_list

8
sapl/templates/sessao/subnav.yaml

@ -21,9 +21,11 @@
- title: {% trans 'Expedientes' %} - title: {% trans 'Expedientes' %}
children: children:
- title: {% trans 'Correspondências' %}
url: correspondencia_list
- title: {% trans 'Expediente Diversos' %} - title: {% trans 'Expediente Diversos' %}
url: expediente url: expediente
- title: {% trans 'Matérias Expediente' %} - title: {% trans 'Matérias do Expediente' %}
url: expedientemateria_list url: expedientemateria_list
- title: {% trans 'Oradores do Expediente' %} - title: {% trans 'Oradores do Expediente' %}
url: oradorexpediente_list url: oradorexpediente_list
@ -37,9 +39,9 @@
- title: {% trans 'Ordem do Dia' %} - title: {% trans 'Ordem do Dia' %}
children: children:
- title: {% trans 'Matérias Ordem do Dia' %} - title: {% trans 'Matérias da Ordem do Dia' %}
url: ordemdia_list url: ordemdia_list
- title: {% trans 'Presença Ordem do Dia' %} - title: {% trans 'Presença na Ordem do Dia' %}
url: presencaordemdia url: presencaordemdia
- title: {% trans 'Oradores da Ordem do Dia' %} - title: {% trans 'Oradores da Ordem do Dia' %}
url: oradorordemdia_list url: oradorordemdia_list

Loading…
Cancel
Save