From 00a847a928085ef6e805a3685ae52a6fb41c6fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?LeandroJata=C3=AD?= Date: Sun, 14 Aug 2022 22:38:00 -0300 Subject: [PATCH] =?UTF-8?q?Implementa=20correspond=C3=AAncias=20em=20sess?= =?UTF-8?q?=C3=A3o=20plen=C3=A1ria=20(#3587)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- sapl/crispy_layout_mixin.py | 25 +- sapl/crud/base.py | 22 +- sapl/relatorios/views.py | 89 +++-- sapl/rules/group_sessao.py | 3 +- sapl/sessao/forms.py | 141 +++++++- .../sessao/migrations/0065_correspondencia.py | 31 ++ .../migrations/0066_auto_20220813_1431.py | 25 ++ .../migrations/0067_auto_20220813_2233.py | 98 +++++ sapl/sessao/models.py | 54 +++ sapl/sessao/urls.py | 19 +- sapl/sessao/views.py | 340 +++++++++++++++++- sapl/static/sapl/css/relatorio.css | 9 +- .../correspondencias.html | 18 + .../materias_expediente.html | 75 ++-- .../materias_ordemdia.html | 71 ++-- sapl/templates/relatorios/relatorio_ata.html | 5 +- .../relatorios/relatorio_pauta_sessao.html | 142 ++++---- .../relatorios/relatorio_sessao_plenaria.html | 5 +- .../sessao/blocos_ata/correspondencias.html | 12 + ...esenca.html => lista_presenca_sessao.html} | 0 .../blocos_resumo/correspondencias.html | 17 + ...esenca.html => lista_presenca_sessao.html} | 0 .../sessao/correspondencia_form.html | 50 +++ .../sessao/correspondencia_list.html | 14 + .../sessao/em_lote/correspondencia.html | 89 +++++ sapl/templates/sessao/layouts.yaml | 14 +- .../templates/sessao/pauta_sessao_detail.html | 16 + sapl/templates/sessao/resumo.html | 2 + sapl/templates/sessao/resumo_ata.html | 1 + sapl/templates/sessao/subnav-solene.yaml | 4 +- sapl/templates/sessao/subnav.yaml | 8 +- 31 files changed, 1172 insertions(+), 227 deletions(-) create mode 100644 sapl/sessao/migrations/0065_correspondencia.py create mode 100644 sapl/sessao/migrations/0066_auto_20220813_1431.py create mode 100644 sapl/sessao/migrations/0067_auto_20220813_2233.py create mode 100644 sapl/templates/relatorios/blocos_sessao_plenaria/correspondencias.html create mode 100644 sapl/templates/sessao/blocos_ata/correspondencias.html rename sapl/templates/sessao/blocos_ata/{lista_presenca.html => lista_presenca_sessao.html} (100%) create mode 100644 sapl/templates/sessao/blocos_resumo/correspondencias.html rename sapl/templates/sessao/blocos_resumo/{lista_presenca.html => lista_presenca_sessao.html} (100%) create mode 100644 sapl/templates/sessao/correspondencia_form.html create mode 100644 sapl/templates/sessao/correspondencia_list.html create mode 100644 sapl/templates/sessao/em_lote/correspondencia.html diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index 0f85b669f..7bd099748 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -257,21 +257,30 @@ class CrispyLayoutFormMixin: if '|' in fieldname: fieldname, func = tuple(fieldname.split('|')) + try: + verbose_name, field_display = get_field_display(obj, fieldname) + except: + verbose_name, field_display = '', '' + if func: - verbose_name, text = getattr(self, func)(obj, fieldname) - else: - hook_fieldname = 'hook_%s' % fieldname - if hasattr(self, hook_fieldname): - verbose_name, text = getattr( + verbose_name, field_display = getattr(self, func)(obj, fieldname) + + hook_fieldname = 'hook_%s' % fieldname + if hasattr(self, hook_fieldname): + 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) - else: - verbose_name, text = get_field_display(obj, fieldname) + elif not func: + verbose_name, field_display = get_field_display(obj, fieldname) return { 'id': fieldname, 'span': span, 'verbose_name': verbose_name, - 'text': text, + 'text': field_display, } def fk_urlize_for_detail(self, obj, fieldname): diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 435bf06a6..5d6d8a3c1 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -429,14 +429,20 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView): m = f.related_model except: f = None - hook = 'hook_header_{}'.format(''.join(fn)) - if hasattr(self, hook): - header = getattr(self, hook)() - s.append(header) - elif f: - s.append(force_text(f.verbose_name)) - - s = ' / '.join(s) + if f: + hook = 'hook_header_{}'.format(''.join(fn)) + if hasattr(self, hook): + header = getattr(self, hook)() + s.append(force_text(header)) + else: + 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(filter(lambda x: x, s)) r.append(s) return r diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 93a6cf33c..f1a90e4db 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -3,17 +3,17 @@ import html import logging import re import tempfile -import unidecode from django.core.exceptions import ObjectDoesNotExist 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.utils import timezone 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 +from sapl.base.models import Autor, CasaLegislativa, AppConfig as SaplAppConfig from sapl.comissoes.models import Comissao from sapl.materia.models import (Autoria, MateriaLegislativa, Numeracao, Tramitacao, UnidadeTramitacao, ConfigEtiquetaMateriaLegislativa) @@ -25,18 +25,19 @@ from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, OcorrenciaSessao, - RegistroVotacao, VotoParlamentar, OradorOrdemDia, + RegistroVotacao, VotoParlamentar, OradorOrdemDia, 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, get_presenca_sessao, get_expedientes, get_materias_expediente, get_oradores_expediente, get_presenca_ordem_do_dia, get_materias_ordem_do_dia, get_oradores_ordemdia, - get_oradores_explicacoes_pessoais, get_consideracoes_finais, - get_ocorrencias_da_sessao, get_assinaturas) + get_oradores_explicacoes_pessoais, get_consideracoes_finais, + 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, 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_protocolo_gerar, pdf_sessao_plenaria_gerar) -from weasyprint import HTML, CSS - def get_kwargs_params(request, fields): kwargs = {} @@ -510,7 +509,7 @@ def is_empty(value): return True if not txt.strip() else False -def get_sessao_plenaria(sessao, casa): +def get_sessao_plenaria(sessao, casa, user): inf_basicas_dic = { "num_sessao_plen": str(sessao.numero), "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" }) + # 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 lst_expedientes = [] expedientes = ExpedienteSessao.objects.filter( @@ -874,6 +896,7 @@ def get_sessao_plenaria(sessao, casa): lst_mesa, lst_presenca_sessao, lst_ausencia_sessao, + lst_correspondencias, lst_expedientes, lst_expediente_materia, lst_expediente_materia_vot_nom, @@ -937,6 +960,7 @@ def relatorio_sessao_plenaria(request, pk): lst_mesa, lst_presenca_sessao, lst_ausencia_sessao, + lst_correspondencias, lst_expedientes, lst_expediente_materia, lst_expediente_materia_vot_nom, @@ -947,7 +971,7 @@ def relatorio_sessao_plenaria(request, pk): lst_oradores_ordemdia, lst_oradores, 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)): txt_expedientes = lst_expedientes[idx]['txt_expediente'] @@ -963,6 +987,7 @@ def relatorio_sessao_plenaria(request, pk): lst_mesa, lst_presenca_sessao, lst_ausencia_sessao, + lst_correspondencias, lst_expedientes, lst_expediente_materia, 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_mesa_diretora(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_materias_expediente(sessao_plenaria)) context.update(get_oradores_expediente(sessao_plenaria)) @@ -1497,6 +1523,7 @@ def relatorio_sessao_plenaria_pdf(request, pk): lst_mesa, lst_presenca_sessao, lst_ausencia_sessao, + lst_correspondencias, lst_expedientes, lst_expediente_materia, lst_expediente_materia_vot_nom, @@ -1507,10 +1534,11 @@ def relatorio_sessao_plenaria_pdf(request, pk): lst_oradores_ordemdia, lst_oradores, lst_ocorrencias, - lst_consideracoes) = get_sessao_plenaria(sessao, casa) + lst_consideracoes) = get_sessao_plenaria(sessao, casa, request.user) dict_ord_template = { 'cont_mult': 'conteudo_multimidia.html', + 'correspondencias': 'correspondencias.html', 'exp': 'expedientes.html', 'id_basica': 'identificacao_basica.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_presenca_sessao": lst_presenca_sessao, "lst_ausencia_sessao": lst_ausencia_sessao, + "lst_correspondencias": lst_correspondencias, "lst_expedientes": lst_expedientes, "lst_expediente_materia": lst_expediente_materia, "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_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro], '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: # 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', 'terceiro_ordenacao': 'mesa_diretora.html', 'quarto_ordenacao': 'lista_presenca_sessao.html', - 'quinto_ordenacao': 'expedientes.html', - 'sexto_ordenacao': 'materias_expediente.html', - 'setimo_ordenacao': 'votos_nominais_expediente.html', - 'oitavo_ordenacao': 'oradores_expediente.html', - 'nono_ordenacao': 'lista_presenca_ordemdia.html', - 'decimo_ordenacao': 'materias_ordemdia.html', - 'decimo_primeiro_ordenacao': 'votos_nominais_ordemdia.html', - 'decimo_segundo_ordenacao': 'oradores_ordemdia.html', - 'decimo_terceiro_ordenacao': 'oradores_explicacoes.html', - 'decimo_quarto_ordenacao': 'ocorrencias_da_sessao.html', - 'decimo_quinto_ordenacao': 'consideracoes_finais.html' + 'quinto_ordenacao': 'correspondencias.html', + 'sexto_ordenacao': 'expedientes.html', + 'setimo_ordenacao': 'materias_expediente.html', + 'oitavo_ordenacao': 'votos_nominais_expediente.html', + 'nono_ordenacao': 'oradores_expediente.html', + 'decimo_ordenacao': 'lista_presenca_ordemdia.html', + 'decimo_primeiro_ordenacao': 'materias_ordemdia.html', + 'decimo_segundo_ordenacao': 'votos_nominais_ordemdia.html', + 'decimo_terceiro_ordenacao': 'oradores_ordemdia.html', + 'decimo_quarto_ordenacao': 'oradores_explicacoes.html', + 'decimo_quinto_ordenacao': 'ocorrencias_da_sessao.html', + 'decimo_sexto_ordenacao': 'consideracoes_finais.html' }) html_template = render_to_string( @@ -1627,7 +1658,7 @@ def gera_etiqueta_ml(materia_legislativa, base_url): max_ementa_size = 240 ementa = materia_legislativa.ementa ementa = ementa if len( - ementa) < max_ementa_size else ementa[:max_ementa_size]+"..." + ementa) < max_ementa_size else ementa[:max_ementa_size] + "..." context = { 'numero': materia_legislativa.numero, diff --git a/sapl/rules/group_sessao.py b/sapl/rules/group_sessao.py index 0e30c1706..5bc217f05 100644 --- a/sapl/rules/group_sessao.py +++ b/sapl/rules/group_sessao.py @@ -21,6 +21,7 @@ rules_group_sessao = { (sessao.JustificativaAusencia, __base__, __perms_publicas__), (sessao.RetiradaPauta, __base__, __perms_publicas__), (sessao.RegistroLeitura, __base__, __perms_publicas__), - (sessao.ConsideracoesFinais, __base__, __perms_publicas__) + (sessao.ConsideracoesFinais, __base__, __perms_publicas__), + (sessao.Correspondencia, __base__, __perms_publicas__) ] } diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 30089075f..4a7f6838a 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -1,8 +1,8 @@ +from datetime import datetime +import logging import re from crispy_forms.layout import Button, Fieldset, HTML, Layout -from datetime import datetime - from django import forms from django.contrib.contenttypes.models import ContentType 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 _ import django_filters -import sapl.utils from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import (form_actions, to_row, SaplFormHelper, SaplFormLayout) @@ -21,6 +20,9 @@ from sapl.materia.forms import MateriaLegislativaFilterSet from sapl.materia.models import (MateriaLegislativa, StatusTramitacao, TipoMateriaLegislativa) 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, choice_anos_com_sessaoplenaria, FileFieldCheckMixin, @@ -28,6 +30,7 @@ from sapl.utils import (autor_label, autor_modal, MateriaPesquisaOrderingFilter, RANGE_DIAS_MES, RANGE_MESES, TIME_PATTERN, timezone, validar_arquivo) +import sapl.utils from .models import (Bancada, ExpedienteMateria, JustificativaAusencia, OcorrenciaSessao, Orador, @@ -37,6 +40,7 @@ from .models import (Bancada, ExpedienteMateria, SessaoPlenaria, SessaoPlenariaPresenca, TipoResultadoVotacao, TipoRetiradaPauta, Tramitacao) + MES_CHOICES = RANGE_MESES DIA_CHOICES = RANGE_DIAS_MES @@ -114,7 +118,7 @@ class SessaoPlenariaForm(FileFieldCheckMixin, ModelForm): if upload_pauta: validar_arquivo(upload_pauta, "Pauta da Sessão") - + if upload_ata: validar_arquivo(upload_ata, "Ata da Sessão") @@ -380,7 +384,8 @@ class ExpedienteMateriaForm(ModelForm): try: 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: if self.cleaned_data['tramitacao_select'] != '': raise ValidationError( @@ -822,6 +827,10 @@ class ResumoOrdenacaoForm(forms.Form): label='15°', choices=ORDENACAO_RESUMO ) + decimo_sexto = forms.ChoiceField( + label='16°', + choices=ORDENACAO_RESUMO + ) def __init__(self, *args, **kwargs): row1 = to_row( @@ -856,13 +865,16 @@ class ResumoOrdenacaoForm(forms.Form): row15 = to_row( [('decimo_quinto', 12)] ) + row16 = to_row( + [('decimo_sexto', 12)] + ) self.helper = SaplFormHelper() self.helper.layout = Layout( Fieldset(_(''), row1, row2, row3, row4, row5, row6, row7, row8, row9, row10, - row11, row12, row13, row14, row15, + row11, row12, row13, row14, row15, row16, form_actions(label='Atualizar')) ) @@ -905,6 +917,7 @@ class ResumoOrdenacaoForm(forms.Form): ordenacao.decimo_terceiro = cleaned_data['decimo_terceiro'] ordenacao.decimo_quarto = cleaned_data['decimo_quarto'] ordenacao.decimo_quinto = cleaned_data['decimo_quinto'] + ordenacao.decimo_sexto = cleaned_data['decimo_sexto'] ordenacao.save() @@ -1028,7 +1041,7 @@ class OrdemExpedienteLeituraForm(forms.ModelForm): 'ordem', 'expediente', 'observacao', - 'user', + 'user', 'ip'] widgets = {'materia': forms.HiddenInput(), 'ordem': forms.HiddenInput(), @@ -1040,14 +1053,14 @@ class OrdemExpedienteLeituraForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + instance = self.initial['instance'] if instance: self.instance = instance.first() self.fields['observacao'].initial = self.instance.observacao row1 = to_row( - [('observacao', 12)]) + [('observacao', 12)]) actions = [HTML('Cancelar Leitura')] @@ -1056,11 +1069,115 @@ class OrdemExpedienteLeituraForm(forms.ModelForm): self.helper.form_method = 'POST' self.helper.layout = Layout( Fieldset(_('Leitura de Matéria'), - HTML(''' + HTML(''' Matéria: {{materia}}
Ementa: {{materia.ementa}}
'''), row1, form_actions(more=actions), - ) - ) \ No newline at end of file + ) + ) + + +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'))) diff --git a/sapl/sessao/migrations/0065_correspondencia.py b/sapl/sessao/migrations/0065_correspondencia.py new file mode 100644 index 000000000..125a1d70b --- /dev/null +++ b/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',), + }, + ), + ] diff --git a/sapl/sessao/migrations/0066_auto_20220813_1431.py b/sapl/sessao/migrations/0066_auto_20220813_1431.py new file mode 100644 index 000000000..9ddde02c6 --- /dev/null +++ b/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'), + ), + ] diff --git a/sapl/sessao/migrations/0067_auto_20220813_2233.py b/sapl/sessao/migrations/0067_auto_20220813_2233.py new file mode 100644 index 000000000..1ec771b19 --- /dev/null +++ b/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) + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 944c1e81d..129703fe4 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -13,6 +13,7 @@ from sapl.materia.models import MateriaLegislativa from sapl.materia.models import Tramitacao from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar, Partido, SessaoLegislativa) +from sapl.protocoloadm.models import DocumentoAdministrativo from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation, get_settings_auth_user_model, restringe_tipos_de_arquivo_txt, texto_upload_path, @@ -248,6 +249,17 @@ class SessaoPlenaria(models.Model): choices=YES_NO_CHOICES, verbose_name=_('Publicar Pauta?')) + correspondencias = models.ManyToManyField( + DocumentoAdministrativo, + blank=True, + through='Correspondencia', + related_name='sessoesplenarias', + through_fields=( + 'sessao_plenaria', + 'documento' + ) + ) + class Meta: verbose_name = _('Sessão Plenária') verbose_name_plural = _('Sessões Plenárias') @@ -730,6 +742,7 @@ ORDENACAO_RESUMO = [ ('cont_mult', 'Conteúdo Multimídia'), ('mesa_d', 'Mesa Diretora'), ('lista_p', 'Lista de Presença'), + ('correspondencia', 'Correspondências'), ('exp', 'Expedientes'), ('mat_exp', '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, default=ORDENACAO_RESUMO[14][0] ) + decimo_sexto = models.CharField( + max_length=50, + default=ORDENACAO_RESUMO[15][0] + ) class Meta: 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 ' 'ordem ou expediente preenchido. Ambos estão preenchidos: ' '{}, {}'. 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) diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index a2269972c..8464c2d78 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -37,7 +37,8 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, retirar_leitura, TransferenciaMateriasExpediente, TransferenciaMateriasOrdemDia, filtra_materias_copia_sessao_ajax, verifica_materia_sessao_plenaria_ajax, - recuperar_tramitacao) + recuperar_tramitacao, CorrespondenciaEmLoteView, + CorrespondenciaCrud, recuperar_documento) from .apps import AppConfig @@ -52,7 +53,13 @@ urlpatterns = [ JustificativaAusenciaCrud.get_urls() + MateriaOrdemDiaCrud.get_urls() + OradorOrdemDiaCrud.get_urls() + - RetiradaPautaCrud.get_urls())), + RetiradaPautaCrud.get_urls() + + CorrespondenciaCrud.get_urls() + )), + + + url(r'^sessao/(?P\d+)/correspondencia-em-lote', CorrespondenciaEmLoteView.as_view(), + name='correspondencia_em_lote'), url(r'^sessao/(?P\d+)/mesa$', MesaView.as_view(), name='mesa'), @@ -68,6 +75,7 @@ urlpatterns = [ 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-tramitacao/', recuperar_tramitacao), url(r'^sessao/recuperar-numero-sessao/', @@ -81,8 +89,8 @@ urlpatterns = [ sessao_legislativa_legislatura_ajax, name='sessao_legislativa_legislatura_ajax_view'), url(r'^sessao/filtra-materias-copia-sessao-ajax/', - filtra_materias_copia_sessao_ajax, - name='filtra_materias_copia_sessao_ajax_view'), + filtra_materias_copia_sessao_ajax, + name='filtra_materias_copia_sessao_ajax_view'), url(r'^sessao/verifica-materia-sessao-plenaria-ajax/', verifica_materia_sessao_plenaria_ajax, name='verifica_materia_sessao_plenaria_ajax_view'), @@ -91,7 +99,8 @@ urlpatterns = [ abrir_votacao, name="abrir_votacao"), - url(r'^sessao/(?P\d+)/reordena/(?P[\w\-]+)/(?P\d+)/$', reordena_materias, name="reordena_materias"), + url(r'^sessao/(?P\d+)/reordena/(?P[\w\-]+)/(?P\d+)/$', + reordena_materias, name="reordena_materias"), url(r'^sistema/sessao-plenaria/tipo/', include(TipoSessaoCrud.get_urls())), diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 94824b248..1c9074a75 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -13,9 +13,11 @@ from django.db.models import Max, Q from django.http import JsonResponse from django.http.response import Http404, HttpResponseRedirect from django.urls import reverse +from django.urls.base import reverse_lazy from django.utils import timezone from django.utils.datastructures import MultiValueDictKeyError from django.utils.decorators import method_decorator +from django.utils.encoding import force_text from django.utils.html import strip_tags from django.utils.translation import ugettext_lazy as _ 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.parlamentares.models import (Filiacao, Legislatura, Mandato, Parlamentar, SessaoLegislativa) +from sapl.protocoloadm.models import TipoDocumentoAdministrativo,\ + DocumentoAdministrativo 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.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_terceiro': self.get_tupla(ordenacao.decimo_terceiro), '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 @@ -1968,6 +1975,36 @@ def get_presenca_sessao(sessao_plenaria): '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): expediente = ExpedienteSessao.objects.filter( sessao_plenaria_id=sessao_plenaria.id).order_by('tipo__ordenacao', 'tipo__nome') @@ -2301,6 +2338,9 @@ class ResumoView(DetailView): # Presença Sessão context.update(get_presenca_sessao(self.object)) # ===================================================================== + # Correspondências + context.update(get_correspondencias(self.object, self.request.user)) + # ===================================================================== # Expedientes context.update(get_expedientes(self.object)) # ===================================================================== @@ -2351,9 +2391,10 @@ class ResumoView(DetailView): # Indica a ordem com a qual o template será renderizado dict_ord_template = { 'cont_mult': 'conteudo_multimidia.html', + 'correspondencia': 'correspondencias.html', 'exp': 'expedientes.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', 'mat_exp': '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_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro], '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: self.logger.error("KeyError: " + str(e) + ". Erro ao tentar utilizar " @@ -2393,18 +2435,19 @@ class ResumoView(DetailView): 'primeiro_ordenacao': 'identificacao_basica.html', 'segundo_ordenacao': 'conteudo_multimidia.html', 'terceiro_ordenacao': 'mesa_diretora.html', - 'quarto_ordenacao': 'lista_presenca.html', - 'quinto_ordenacao': 'expedientes.html', - 'sexto_ordenacao': 'materias_expediente.html', - 'setimo_ordenacao': 'votos_nominais_materias_expediente.html', - 'oitavo_ordenacao': 'oradores_expediente.html', - 'nono_ordenacao': 'lista_presenca_ordem_dia.html', - 'decimo_ordenacao': 'materias_ordem_dia.html', - 'decimo_primeiro_ordenacao': 'votos_nominais_materias_ordem_dia.html', - 'decimo_segundo_ordenacao': 'oradores_ordemdia.html', - 'decimo_terceiro_ordenacao': 'oradores_explicacoes.html', - 'decimo_quarto_ordenacao': 'ocorrencias_da_sessao.html', - 'decimo_quinto_ordenacao': 'consideracoes_finais.html' + 'quarto_ordenacao': 'lista_presenca_sessao.html', + 'quinto_ordenacao': 'correspondencias.html', + 'sexto_ordenacao': 'expedientes.html', + 'setimo_ordenacao': 'materias_expediente.html', + 'oitavo_ordenacao': 'votos_nominais_materias_expediente.html', + 'nono_ordenacao': 'oradores_expediente.html', + 'decimo_ordenacao': 'lista_presenca_ordem_dia.html', + 'decimo_primeiro_ordenacao': 'materias_ordem_dia.html', + 'decimo_segundo_ordenacao': 'votos_nominais_materias_ordem_dia.html', + 'decimo_terceiro_ordenacao': 'oradores_ordemdia.html', + 'decimo_quarto_ordenacao': 'oradores_explicacoes.html', + 'decimo_quinto_ordenacao': 'ocorrencias_da_sessao.html', + 'decimo_sexto_ordenacao': 'consideracoes_finais.html' }) 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()] }) 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 = [] @@ -4985,3 +5053,243 @@ def retirar_leitura(request, pk, iso, oid): ordem_expediente.votacao_aberta = False ordem_expediente.save() 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'{d.epigrafe}
{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 ({})').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) diff --git a/sapl/static/sapl/css/relatorio.css b/sapl/static/sapl/css/relatorio.css index 5411b70b5..c3489dfe2 100644 --- a/sapl/static/sapl/css/relatorio.css +++ b/sapl/static/sapl/css/relatorio.css @@ -1,7 +1,7 @@ @page{ margin-top: 5.2cm; size: A4 portrait; -} +} h2.gray-title{ color: gray; @@ -13,11 +13,11 @@ h2.gray-title{ h3 { font-size: 10pt; break-after: avoid-page; - page-break-after: avoid; + page-break-after: avoid; } p, a { - font-size: 10pt; + font-size: 10pt; text-align: justify; text-justify: inter-word; } @@ -68,6 +68,7 @@ table.grayTable { } table.grayTable td, table.grayTable th { border: 1px solid #000000; + padding: 5px; } table.grayTable tbody td { font-size: 10px; @@ -91,4 +92,4 @@ table.grayTable thead th { } table.grayTable thead th:first-child { border-left: none; -} \ No newline at end of file +} \ No newline at end of file diff --git a/sapl/templates/relatorios/blocos_sessao_plenaria/correspondencias.html b/sapl/templates/relatorios/blocos_sessao_plenaria/correspondencias.html new file mode 100644 index 000000000..696621c40 --- /dev/null +++ b/sapl/templates/relatorios/blocos_sessao_plenaria/correspondencias.html @@ -0,0 +1,18 @@ +{% load common_tags %} + +

Correspondências

+ + + + {% for c in lst_correspondencias%} + + + + {% endfor %} + + +
+ ({{c.tipo}}) {{c.epigrafe}}
+ Data do Documento: {{c.data}}
+ Assunto: {{c.assunto}} +
\ No newline at end of file diff --git a/sapl/templates/relatorios/blocos_sessao_plenaria/materias_expediente.html b/sapl/templates/relatorios/blocos_sessao_plenaria/materias_expediente.html index 3deb0f4d8..a83a2aff6 100644 --- a/sapl/templates/relatorios/blocos_sessao_plenaria/materias_expediente.html +++ b/sapl/templates/relatorios/blocos_sessao_plenaria/materias_expediente.html @@ -1,37 +1,38 @@ - {% load common_tags %} - -

Matérias do Expediente

- - - - - - - - - - - {% for materia in lst_expediente_materia%} - - - - - - {% endfor %} - - -
MatériaEmentaResultado da Votação
-
-
{{materia.num_ordem}} - {{materia.id_materia}}
-
Turno: {{materia.des_turno}}
-
{{materia.num_autores}}: {{materia.nom_autor}}
-
-
-
- {{materia.txt_ementa}} - {% if materia.ordem_observacao %}

Obs.: {{materia.ordem_observacao}} {% endif %} -
-
-  {{materia.nom_resultado}} -

{{materia.votacao_observacao}} -
\ No newline at end of file +{% load common_tags %} +

Matérias do Expediente

+ +{% for materia in lst_expediente_materia%} +{% if forloop.first %} + + + + + + + + +{% endif %} + + + + + +{% if forloop.last %} + +{% endif %} +{% endfor %} +
MatériaEmentaResultado da Votação
+
+
{{materia.num_ordem}} - {{materia.id_materia}}
+
Turno: {{materia.des_turno}}
+
{{materia.num_autores}}: {{materia.nom_autor}}
+
+
+
+ {{materia.txt_ementa}} + {% if materia.ordem_observacao %}

Obs.: {{materia.ordem_observacao}} {% endif %} +
+
+  {{materia.nom_resultado}} +

{{materia.votacao_observacao}} +
\ No newline at end of file diff --git a/sapl/templates/relatorios/blocos_sessao_plenaria/materias_ordemdia.html b/sapl/templates/relatorios/blocos_sessao_plenaria/materias_ordemdia.html index e40209d8c..a593b1bcc 100644 --- a/sapl/templates/relatorios/blocos_sessao_plenaria/materias_ordemdia.html +++ b/sapl/templates/relatorios/blocos_sessao_plenaria/materias_ordemdia.html @@ -1,35 +1,38 @@ -

Matérias da Ordem do Dia

+

Matérias da Ordem do Dia

- - - - - - - - - - {% for materia in lst_votacao%} - - - - - - {% endfor %} - - -
MatériaEmentaResultado da Votação
-
-
{{materia.num_ordem}} - {{materia.id_materia}}
-
Turno: {{materia.des_turno}}
-
{{materia.num_autores}}: {{materia.nom_autor}}
-
-
-
- {{materia.txt_ementa}} - {% if materia.ordem_observacao %}

Obs.: {{materia.ordem_observacao}} {% endif %} -
-
-  {{materia.nom_resultado}} -

{{materia.votacao_observacao}} -
\ No newline at end of file +{% for materia in lst_votacao%} +{% if forloop.first %} + + + + + + + + + +{% endif %} + + + + + +{% if forloop.last %} + +
MatériaEmentaResultado da Votação
+
+
{{materia.num_ordem}} - {{materia.id_materia}}
+
Turno: {{materia.des_turno}}
+
{{materia.num_autores}}: {{materia.nom_autor}}
+
+
+
+ {{materia.txt_ementa}} + {% if materia.ordem_observacao %}

Obs.: {{materia.ordem_observacao}} {% endif %} +
+
+  {{materia.nom_resultado}} +

{{materia.votacao_observacao}} +
+{% endif %} +{% endfor %} \ No newline at end of file diff --git a/sapl/templates/relatorios/relatorio_ata.html b/sapl/templates/relatorios/relatorio_ata.html index b6ebc76d7..012ae4108 100644 --- a/sapl/templates/relatorios/relatorio_ata.html +++ b/sapl/templates/relatorios/relatorio_ata.html @@ -5,7 +5,8 @@

Ata Eletrônica da {{sessaoplenaria}}

{% include 'sessao/blocos_ata/identificacao_basica.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/materias_expediente.html' %} {% include 'sessao/blocos_ata/oradores_expediente.html' %} @@ -15,7 +16,7 @@ {% include 'sessao/blocos_ata/oradores_explicacoes.html' %} {% include 'sessao/blocos_ata/ocorrencias_da_sessao.html' %} {% include 'sessao/blocos_ata/consideracoes_finais.html' %} - + {% if assinatura_mesa or assinatura_presentes %}
diff --git a/sapl/templates/relatorios/relatorio_pauta_sessao.html b/sapl/templates/relatorios/relatorio_pauta_sessao.html index 43968c905..f5a9cbfc7 100644 --- a/sapl/templates/relatorios/relatorio_pauta_sessao.html +++ b/sapl/templates/relatorios/relatorio_pauta_sessao.html @@ -5,83 +5,97 @@ {% load static %} {% block content %} - +

Identificação Básica

{% for b in basica %} {{ b }}
{% endfor %} +

Correspondências

+ + + {% for c in correspondencias%} + + + + {% endfor %} + +
+ ({{c.tipo}}) {{c.epigrafe}}
+ Data do Documento: {{c.data}}
+ Assunto: {{c.assunto}} +

Expedientes

{% for e in expedientes %} - {{ e.tipo }}:

{{ e.conteudo|safe }}

+ {{ e.tipo }}:

{{ e.conteudo|safe }}

{% endfor %}

Matérias do Expediente

{% if materia_expediente %} - - - {% for m in materia_expediente %} - - - - - - {% endfor %} -
MatériaEmentaSituação
- {{ m.numero }} - {{ m.titulo }}
- Processo: {{ m.processo }}
- Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} -
- {{ m.ementa|safe }} - {% if m.observacao %}

Obs.: {{m.observacao}} {% endif %} -
{{m.situacao|linebreaksbr|safe}}
+ + + {% for m in materia_expediente %} + + + + + + {% endfor %} +
MatériaEmentaSituação
+ {{ m.numero }} - {{ m.titulo }}
+ Processo: {{ m.processo }}
+ Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} +
+ {{ m.ementa|safe }} + {% if m.observacao %}

Obs.: {{m.observacao}} {% endif %} +
{{m.situacao|linebreaksbr|safe}}
{% else %} - Não existem Matérias de Expediente para essa Sessão Plenária + Não existem Matérias de Expediente para essa Sessão Plenária {% endif %}

Matérias da Ordem do Dia

{% if materias_ordem %} - - - {% for m in materias_ordem %} - - - - - - {% endfor %} -
MatériaEmentaSituação
- {{m.numero}} - {{m.titulo}}
- Processo: {{ m.processo }}
- Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} -
- {{m.ementa|safe}} - {% if m.observacao %}

Obs.: {{m.observacao}} {% endif %} -
{{m.situacao|linebreaksbr|safe}}
+ + + {% for m in materias_ordem %} + + + + + + {% endfor %} +
MatériaEmentaSituação
+ {{m.numero}} - {{m.titulo}}
+ Processo: {{ m.processo }}
+ Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} +
+ {{m.ementa|safe}} + {% if m.observacao %}

Obs.: {{m.observacao}} {% endif %} +
{{m.situacao|linebreaksbr|safe}}
{% else %} - Não existem Matérias de Ordem do Dia para essa Sessão Plenária + Não existem Matérias de Ordem do Dia para essa Sessão Plenária {% endif %} -
- {% for n in assinatura_mesa %} - {% if n.parlamentar %} -
-

- {% for p in assinatura_mesa %} -


-
-
___________________________________________
- {{p.parlamentar.nome_completo}}
{{p.cargo}} -


-
-
- {% endfor %} - {% endif %} - {% endfor %} -
-
+
+ {% for n in assinatura_mesa %} + {% if n.parlamentar %} +
+

+ {% for p in assinatura_mesa %} +


+
+
___________________________________________
+ {{p.parlamentar.nome_completo}}
{{p.cargo}} +


+
+
+ {% endfor %} + {% endif %} + {% endfor %} +
+
{% endblock content %} diff --git a/sapl/templates/relatorios/relatorio_sessao_plenaria.html b/sapl/templates/relatorios/relatorio_sessao_plenaria.html index e459da2b3..821c2fe78 100644 --- a/sapl/templates/relatorios/relatorio_sessao_plenaria.html +++ b/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_sexto_ordenacao %} +
-{% endblock content %} - \ No newline at end of file +{% endblock content %} diff --git a/sapl/templates/sessao/blocos_ata/correspondencias.html b/sapl/templates/sessao/blocos_ata/correspondencias.html new file mode 100644 index 000000000..02c3932a1 --- /dev/null +++ b/sapl/templates/sessao/blocos_ata/correspondencias.html @@ -0,0 +1,12 @@ +{% if correspondencias %} +
+

+ Correspondências: + {% for c in correspondencias %} + {{forloop.counter}}) {{c.tipo}} - {{c.epigrafe}} - + Data: {{c.data}} - + Assunto: {{c.assunto}}; + {% endfor %} +

+
+{% endif %} \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_ata/lista_presenca.html b/sapl/templates/sessao/blocos_ata/lista_presenca_sessao.html similarity index 100% rename from sapl/templates/sessao/blocos_ata/lista_presenca.html rename to sapl/templates/sessao/blocos_ata/lista_presenca_sessao.html diff --git a/sapl/templates/sessao/blocos_resumo/correspondencias.html b/sapl/templates/sessao/blocos_resumo/correspondencias.html new file mode 100644 index 000000000..4d5a69013 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/correspondencias.html @@ -0,0 +1,17 @@ +{% if correspondencias %} +
+ Correspondências + + {% for c in correspondencias %} + + + + {% endfor %} +
+ ({{c.tipo}}) {{c.epigrafe}}
+ Data do Documento: {{c.data}}
+ Assunto: {{c.assunto}} +
+
+


+{% endif %} \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/lista_presenca.html b/sapl/templates/sessao/blocos_resumo/lista_presenca_sessao.html similarity index 100% rename from sapl/templates/sessao/blocos_resumo/lista_presenca.html rename to sapl/templates/sessao/blocos_resumo/lista_presenca_sessao.html diff --git a/sapl/templates/sessao/correspondencia_form.html b/sapl/templates/sessao/correspondencia_form.html new file mode 100644 index 000000000..8c45de887 --- /dev/null +++ b/sapl/templates/sessao/correspondencia_form.html @@ -0,0 +1,50 @@ +{% extends "crud/form.html" %} +{% load i18n %} + +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/sessao/correspondencia_list.html b/sapl/templates/sessao/correspondencia_list.html new file mode 100644 index 000000000..aab2482c5 --- /dev/null +++ b/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 %} + + {% trans "Adicionar Correspondencias em Lote" %} + +{% endif %} + +{% endblock more_buttons %} diff --git a/sapl/templates/sessao/em_lote/correspondencia.html b/sapl/templates/sessao/em_lote/correspondencia.html new file mode 100644 index 000000000..d445c6737 --- /dev/null +++ b/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 %} +

{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 documento.'%}

+ {% else %} +

Foram encontrados {{ numero_res }} documentos.

+ {% endif %} +
+ {% csrf_token %} +
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+ Documentos para Vincular em Lote + +
+
+ +

+ OBS: Documentos já inseridos na sessão atual não aparecem na lista abaixo. +
+
+ + + {% for documento in object_list %} + + + + {% endfor %} + +
Documento
+ +
+
+ +
+ {% else %} +

Nenhuma documento encontrado.

+ {% endif %} + {% endif %} +{% endblock detail_content %} + +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/sessao/layouts.yaml b/sapl/templates/sessao/layouts.yaml index 78517b708..0fc779f08 100644 --- a/sapl/templates/sessao/layouts.yaml +++ b/sapl/templates/sessao/layouts.yaml @@ -53,7 +53,7 @@ OradorOrdemDia: - numero_ordem parlamentar - url_discurso observacao - upload_anexo - + ExpedienteMateria: {% trans 'Matéria do Expediente' %}: - data_ordem numero_ordem @@ -123,3 +123,15 @@ RetiradaPauta: - tipo_de_retirada materia - data parlamentar - 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 \ No newline at end of file diff --git a/sapl/templates/sessao/pauta_sessao_detail.html b/sapl/templates/sessao/pauta_sessao_detail.html index bf58a3cea..b35e9ac7f 100644 --- a/sapl/templates/sessao/pauta_sessao_detail.html +++ b/sapl/templates/sessao/pauta_sessao_detail.html @@ -16,6 +16,22 @@ +
+ Corresondências + + + {% for c in correspondencias %} + + + + {% endfor %} + +
+ ({{c.tipo}}) {{c.epigrafe}}
+ Data do Documento: {{c.data}}
+ Assunto: {{c.assunto}} +
+
Expedientes diff --git a/sapl/templates/sessao/resumo.html b/sapl/templates/sessao/resumo.html index 539cec810..280443807 100644 --- a/sapl/templates/sessao/resumo.html +++ b/sapl/templates/sessao/resumo.html @@ -52,5 +52,7 @@ {% include 'sessao/blocos_resumo/'|add:decimo_quinto_ordenacao %} + {% include 'sessao/blocos_resumo/'|add:decimo_sexto_ordenacao %} + {% endblock detail_content %} diff --git a/sapl/templates/sessao/resumo_ata.html b/sapl/templates/sessao/resumo_ata.html index 6b4b224b6..a887e81d6 100644 --- a/sapl/templates/sessao/resumo_ata.html +++ b/sapl/templates/sessao/resumo_ata.html @@ -34,5 +34,6 @@ {% include 'sessao/blocos_ata/'|add:decimo_terceiro_ordenacao %} {% include 'sessao/blocos_ata/'|add:decimo_quarto_ordenacao %} {% include 'sessao/blocos_ata/'|add:decimo_quinto_ordenacao %} + {% include 'sessao/blocos_ata/'|add:decimo_sexto_ordenacao %} {% include 'sessao/blocos_ata/assinaturas.html' %} {% endblock detail_content %} diff --git a/sapl/templates/sessao/subnav-solene.yaml b/sapl/templates/sessao/subnav-solene.yaml index deef59805..0af45ae7b 100644 --- a/sapl/templates/sessao/subnav-solene.yaml +++ b/sapl/templates/sessao/subnav-solene.yaml @@ -18,7 +18,9 @@ - title: {% trans 'Expedientes' %} children: - title: {% trans 'Expediente Diversos' %} - url: expediente + url: expediente + - title: {% trans 'Correspondências' %} + url: correspondencia_list - title: {% trans 'Oradores do Expediente' %} url: oradorexpediente_list diff --git a/sapl/templates/sessao/subnav.yaml b/sapl/templates/sessao/subnav.yaml index b868e5a88..e349f4b02 100644 --- a/sapl/templates/sessao/subnav.yaml +++ b/sapl/templates/sessao/subnav.yaml @@ -21,9 +21,11 @@ - title: {% trans 'Expedientes' %} children: + - title: {% trans 'Correspondências' %} + url: correspondencia_list - title: {% trans 'Expediente Diversos' %} url: expediente - - title: {% trans 'Matérias Expediente' %} + - title: {% trans 'Matérias do Expediente' %} url: expedientemateria_list - title: {% trans 'Oradores do Expediente' %} url: oradorexpediente_list @@ -37,9 +39,9 @@ - title: {% trans 'Ordem do Dia' %} children: - - title: {% trans 'Matérias Ordem do Dia' %} + - title: {% trans 'Matérias da Ordem do Dia' %} url: ordemdia_list - - title: {% trans 'Presença Ordem do Dia' %} + - title: {% trans 'Presença na Ordem do Dia' %} url: presencaordemdia - title: {% trans 'Oradores da Ordem do Dia' %} url: oradorordemdia_list