From e1a3654d9e3f1b6e6b1677869684dfe8c1d582c9 Mon Sep 17 00:00:00 2001 From: joaohortsenado <100957576+joaohortsenado@users.noreply.github.com> Date: Thu, 4 Aug 2022 11:39:28 -0300 Subject: [PATCH] Tramitacao customizada ordemdia expediente (#3584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Campo tramitação para expediente e ordem do dia das pautas de sessoes plenarias * Migration para tramitacao customizada de ordem dia e expediente da pauta de sessao * ajuste automático feito pela IDE para pep8 * Ajuste de usabilidade - encaminha além da descrição do status, também a data de tramitação, unidade local e unidade de destino de cada tramitação - recebe os dados acima na interface, monta options com data, e status. - qualquer alteração no select, monta um alert, assim como para matéria selecionada, com os dados cima da tramitação Co-authored-by: joao Co-authored-by: LeandroJatai --- sapl/sessao/forms.py | 58 ++++++++++---- .../migrations/0064_auto_20220713_2335.py | 25 ++++++ sapl/sessao/models.py | 7 ++ sapl/sessao/urls.py | 4 +- sapl/sessao/views.py | 78 ++++++++++++++----- .../sessao/expedientemateria_form.html | 59 +++++++++++++- sapl/templates/sessao/layouts.yaml | 4 + 7 files changed, 197 insertions(+), 38 deletions(-) create mode 100644 sapl/sessao/migrations/0064_auto_20220713_2335.py diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 6aea339ff..30089075f 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -1,7 +1,8 @@ -from datetime import datetime 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 @@ -12,6 +13,7 @@ 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) @@ -33,13 +35,16 @@ from .models import (Bancada, ExpedienteMateria, ORDENACAO_RESUMO, PresencaOrdemDia, RegistroLeitura, ResumoOrdenacao, RetiradaPauta, SessaoPlenaria, SessaoPlenariaPresenca, - TipoResultadoVotacao, TipoRetiradaPauta) - + TipoResultadoVotacao, TipoRetiradaPauta, Tramitacao) MES_CHOICES = RANGE_MESES DIA_CHOICES = RANGE_DIAS_MES +def tramitacao_select_validation(): + return True + + class SessaoPlenariaForm(FileFieldCheckMixin, ModelForm): class Meta: @@ -109,7 +114,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") @@ -117,12 +122,12 @@ class SessaoPlenariaForm(FileFieldCheckMixin, ModelForm): validar_arquivo(upload_anexo, "Anexo da Sessão") hora_inicio = self.cleaned_data['hora_inicio'] - if not re.match(TIME_PATTERN, hora_inicio): + if not re.match(sapl.utils.TIME_PATTERN, hora_inicio): raise ValidationError(f'Formato ou valores de horário de ' f'abertura errados: {hora_inicio}') hora_fim = self.cleaned_data['hora_fim'] - if hora_fim and not re.match(TIME_PATTERN, hora_fim): + if hora_fim and not re.match(sapl.utils.TIME_PATTERN, hora_fim): raise ValidationError(f'Formato ou valores de horário de ' f'encerramento errados: {hora_fim}.') @@ -294,6 +299,12 @@ class BancadaForm(ModelForm): return bancada +class DependentChoiceField(forms.ChoiceField): + + def validate(self, value): + return True + + class ExpedienteMateriaForm(ModelForm): _model = ExpedienteMateria @@ -306,6 +317,10 @@ class ExpedienteMateriaForm(ModelForm): empty_label='Selecione', widget=forms.Select(attrs={'autocomplete': 'off'})) + tramitacao_select = DependentChoiceField( + label=_('Situação Atual'), + widget=forms.Select()) + numero_materia = forms.CharField( label='Número Matéria', required=True, widget=forms.TextInput(attrs={'autocomplete': 'off'})) @@ -326,7 +341,7 @@ class ExpedienteMateriaForm(ModelForm): class Meta: model = ExpedienteMateria fields = ['data_ordem', 'numero_ordem', 'tipo_materia', 'observacao', - 'numero_materia', 'ano_materia', 'tipo_votacao'] + 'numero_materia', 'ano_materia', 'tramitacao_select', 'tipo_votacao'] def clean_numero_ordem(self): sessao = self.instance.sessao_plenaria @@ -363,11 +378,28 @@ class ExpedienteMateriaForm(ModelForm): else: cleaned_data['materia'] = materia + 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) + except ObjectDoesNotExist: + if self.cleaned_data['tramitacao_select'] != '': + raise ValidationError( + _('Tramitação selecionada não existe para a Matéria: %(value)s'), + code='invalid', + params={'value': self.cleaned_data['tramitacao_select']}, + ) + else: + cleaned_data['tramitacao'] = False + else: + cleaned_data['tramitacao'] = tramitacao + return cleaned_data def save(self, commit=False): expediente = super(ExpedienteMateriaForm, self).save(commit) expediente.materia = self.cleaned_data['materia'] + if self.cleaned_data['tramitacao'] is not False: + expediente.tramitacao = self.cleaned_data['tramitacao'] expediente.save() return expediente @@ -996,7 +1028,7 @@ class OrdemExpedienteLeituraForm(forms.ModelForm): 'ordem', 'expediente', 'observacao', - 'user', + 'user', 'ip'] widgets = {'materia': forms.HiddenInput(), 'ordem': forms.HiddenInput(), @@ -1008,14 +1040,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')] @@ -1024,11 +1056,11 @@ 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 diff --git a/sapl/sessao/migrations/0064_auto_20220713_2335.py b/sapl/sessao/migrations/0064_auto_20220713_2335.py new file mode 100644 index 000000000..0c0c34866 --- /dev/null +++ b/sapl/sessao/migrations/0064_auto_20220713_2335.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.28 on 2022-07-14 02:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0081_auto_20220321_0934'), + ('sessao', '0063_merge_20220609_0838'), + ] + + operations = [ + migrations.AddField( + model_name='expedientemateria', + name='tramitacao', + field=models.ForeignKey(blank=True, default='', null=True, on_delete=django.db.models.deletion.PROTECT, to='materia.Tramitacao', verbose_name='Situação Atual'), + ), + migrations.AddField( + model_name='ordemdia', + name='tramitacao', + field=models.ForeignKey(blank=True, default='', null=True, on_delete=django.db.models.deletion.PROTECT, to='materia.Tramitacao', verbose_name='Situação Atual'), + ), + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 39f144c2c..944c1e81d 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -10,6 +10,7 @@ import reversion from sapl.base.models import Autor from sapl.materia.models import MateriaLegislativa +from sapl.materia.models import Tramitacao from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar, Partido, SessaoLegislativa) from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation, @@ -360,6 +361,12 @@ class AbstractOrdemDia(models.Model): materia = models.ForeignKey(MateriaLegislativa, on_delete=models.PROTECT, verbose_name=_('Matéria')) + tramitacao = models.ForeignKey(Tramitacao, + on_delete=models.PROTECT, + verbose_name=_('Situação Atual'), + blank=True, + default='', + null=True) data_ordem = models.DateField(verbose_name=_('Data da Sessão')) observacao = models.TextField( blank=True, verbose_name=_('Observação')) diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index af66c2d83..a2269972c 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -36,7 +36,8 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, OrdemDiaLeituraView, retirar_leitura, TransferenciaMateriasExpediente, TransferenciaMateriasOrdemDia, - filtra_materias_copia_sessao_ajax, verifica_materia_sessao_plenaria_ajax) + filtra_materias_copia_sessao_ajax, verifica_materia_sessao_plenaria_ajax, + recuperar_tramitacao) from .apps import AppConfig @@ -68,6 +69,7 @@ urlpatterns = [ name='remove_parlamentar_composicao'), url(r'^sessao/recuperar-materia/', recuperar_materia), + url(r'^sessao/recuperar-tramitacao/', recuperar_tramitacao), url(r'^sessao/recuperar-numero-sessao/', recuperar_numero_sessao_view, name='recuperar_numero_sessao_view' diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 94e5419a0..94824b248 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -1,12 +1,8 @@ - from collections import OrderedDict from datetime import datetime -from re import sub - -import pytz - -from sapl.settings import TIME_ZONE +import json import logging +from re import sub from django.conf import settings from django.contrib import messages @@ -28,6 +24,7 @@ from django.views.generic.base import RedirectView from django.views.generic.detail import DetailView from django.views.generic.edit import FormMixin from django_filters.views import FilterView +import pytz from sapl.base.models import AppConfig as AppsAppConfig from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, @@ -41,6 +38,7 @@ from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato, Parlamentar, SessaoLegislativa) from sapl.sessao.apps import AppConfig from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedienteLeituraForm +from sapl.settings import TIME_ZONE from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, @@ -754,7 +752,7 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): class BaseMixin(MasterDetailCrud.BaseMixin): list_field_names = ['numero_ordem', 'materia', - ('materia__ementa', '', 'observacao'), + ('materia__ementa', '', 'tramitacao', 'observacao'), 'resultado'] class CreateView(MasterDetailCrud.CreateView): @@ -790,6 +788,7 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): context["tipo_materia_salvo"] = self.object.materia.tipo.id context["numero_materia_salvo"] = self.object.materia.numero context["ano_materia_salvo"] = self.object.materia.ano + context["tramitacao_salvo"] = None if not self.object.tramitacao else self.object.tramitacao.id return context @@ -800,6 +799,7 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): initial['numero_materia'] = self.object.materia.numero initial['ano_materia'] = self.object.materia.ano initial['numero_ordem'] = self.object.numero_ordem + initial['tramitacao'] = None if not self.object.tramitacao else self.object.tramitacao.id return initial @@ -840,6 +840,33 @@ def recuperar_materia(request): return response +def recuperar_tramitacao(request): + tipo = request.GET['tipo_materia'] + numero = request.GET['numero_materia'] + ano = request.GET['ano_materia'] + + try: + materia = MateriaLegislativa.objects.get(tipo_id=tipo, + ano=ano, + numero=numero) + tramitacao = {} + for obj in materia.tramitacao_set.all(): + tramitacao[obj.id] = { + 'status': obj.status.descricao, + 'texto': obj.texto, + 'data_tramitacao': obj.data_tramitacao.strftime('%d/%m/%Y'), + 'unidade_tramitacao_local': str(obj.unidade_tramitacao_local), + 'unidade_tramitacao_destino': str(obj.unidade_tramitacao_destino) + + } + + response = JsonResponse(tramitacao) + except ObjectDoesNotExist: + response = JsonResponse({'id': 0}) + + return response + + class ExpedienteMateriaCrud(MasterDetailCrud): model = ExpedienteMateria parent_field = 'sessao_plenaria' @@ -904,6 +931,7 @@ class ExpedienteMateriaCrud(MasterDetailCrud): context["tipo_materia_salvo"] = self.object.materia.tipo.id context["numero_materia_salvo"] = self.object.materia.numero context["ano_materia_salvo"] = self.object.materia.ano + context["tramitacao_salvo"] = self.object.tramitacao.id if self.object.tramitacao is not None else '' return context @@ -914,6 +942,7 @@ class ExpedienteMateriaCrud(MasterDetailCrud): initial['numero_materia'] = self.object.materia.numero initial['ano_materia'] = self.object.materia.ano initial['numero_ordem'] = self.object.numero_ordem + initial['tramitacao'] = self.object.tramitacao.id if self.object.tramitacao is not None else '' return initial @@ -2081,15 +2110,17 @@ def get_assinaturas(sessao_plenaria): return context + def get_assinaturas_presidente(sessao_plenaria): mesa_dia = get_mesa_diretora(sessao_plenaria)['mesa'] - presidente_dia = [m['parlamentar'] for m in mesa_dia if m['cargo'].descricao == 'Presidente'] - presidente_dia = presidente_dia[0] if presidente_dia else '' + presidente_dia = [m['parlamentar'] + for m in mesa_dia if m['cargo'].descricao == 'Presidente'] + presidente_dia = presidente_dia[0] if presidente_dia else '' context = {} assinatura_presidente = [ - {'parlamentar': presidente_dia, 'cargo': "Presidente"}] + {'parlamentar': presidente_dia, 'cargo': "Presidente"}] context.update({'assinatura_mesa': assinatura_presidente}) return context @@ -3703,7 +3734,8 @@ class PautaSessaoView(TemplateView): template_name = "sessao/pauta_inexistente.html" def get(self, request, *args, **kwargs): - sessao = SessaoPlenaria.objects.filter(publicar_pauta = True).order_by("-data_inicio").first() + sessao = SessaoPlenaria.objects.filter( + publicar_pauta=True).order_by("-data_inicio").first() if not sessao: return self.render_to_response({}) @@ -3755,10 +3787,12 @@ class PautaSessaoDetailView(DetailView): sessao_plenaria = SessaoPlenaria.objects.get(id=self.object.id) data_sessao = sessao_plenaria.data_inicio.strftime("%Y-%m-%d ") - data_hora_sessao = datetime.strptime(data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") - data_hora_sessao_utc = pytz.timezone(TIME_ZONE).localize(data_hora_sessao).astimezone(pytz.utc) - ultima_tramitacao = m.materia.tramitacao_set.filter(timestamp__lt = data_hora_sessao_utc).order_by( - '-data_tramitacao', '-id').first() + data_hora_sessao = datetime.strptime( + data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") + data_hora_sessao_utc = pytz.timezone(TIME_ZONE).localize( + data_hora_sessao).astimezone(pytz.utc) + ultima_tramitacao = m.materia.tramitacao_set.filter(timestamp__lt=data_hora_sessao_utc).order_by( + '-data_tramitacao', '-id').first() if m.tramitacao is None else m.tramitacao numeracao = m.materia.numeracao_set.first() materias_expediente.append({ @@ -3810,10 +3844,12 @@ class PautaSessaoDetailView(DetailView): sessao_plenaria = SessaoPlenaria.objects.get(id=self.object.id) data_sessao = sessao_plenaria.data_inicio.strftime("%Y-%m-%d ") - data_hora_sessao = datetime.strptime(data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") - data_hora_sessao_utc = pytz.timezone(TIME_ZONE).localize(data_hora_sessao).astimezone(pytz.utc) + data_hora_sessao = datetime.strptime( + data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") + data_hora_sessao_utc = pytz.timezone(TIME_ZONE).localize( + data_hora_sessao).astimezone(pytz.utc) ultima_tramitacao = o.materia.tramitacao_set.filter(timestamp__lt=data_hora_sessao_utc).order_by( - '-data_tramitacao', '-id').first() + '-data_tramitacao', '-id').first() if o.tramitacao is None else o.tramitacao numeracao = o.materia.numeracao_set.first() materias_ordem.append({ @@ -3919,7 +3955,7 @@ class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView): def get_filterset_kwargs(self, filterset_class): kwargs = super().get_filterset_kwargs(filterset_class) qs = kwargs.get('queryset') - qs = qs.filter(publicar_pauta = True) + qs = qs.filter(publicar_pauta=True) kwargs['queryset'] = qs return kwargs @@ -3948,14 +3984,14 @@ def verifica_materia_sessao_plenaria_ajax(request): sessao_plenaria=pk_sessao_plenaria, materia=id_materia_selecionada ).exists() is_materia_presente_any_sessao = ExpedienteMateria.objects.filter( - materia=id_materia_selecionada + materia=id_materia_selecionada ).exists() elif tipo_materia_sessao == MATERIAS_ORDEMDIA: is_materia_presente = OrdemDia.objects.filter( sessao_plenaria=pk_sessao_plenaria, materia=id_materia_selecionada ).exists() is_materia_presente_any_sessao = OrdemDia.objects.filter( - materia=id_materia_selecionada + materia=id_materia_selecionada ).exists() return JsonResponse({'is_materia_presente': is_materia_presente, 'is_materia_presente_any_sessao': is_materia_presente_any_sessao}) diff --git a/sapl/templates/sessao/expedientemateria_form.html b/sapl/templates/sessao/expedientemateria_form.html index 424679dd5..c11517dd2 100644 --- a/sapl/templates/sessao/expedientemateria_form.html +++ b/sapl/templates/sessao/expedientemateria_form.html @@ -17,7 +17,7 @@ { tipo_materia: tipo_materia, numero_materia: numero_materia, ano_materia: ano_materia }, function(data, status) { if ($(".ementa-materia").length === 0){ - $("#div_id_tipo_materia").closest('.row').after($('
').append($('
').append( + $("#div_id_tipo_materia").closest('.row').after($('
').append($('
').append( $('
').html(data.ementa) ))) } @@ -54,11 +54,64 @@ } } + function recuperar_tramitacao() { + let tipo_materia = $("#id_tipo_materia").val() + let numero_materia = $("#id_numero_materia").val() + let ano_materia = $("#id_ano_materia").val() + let tramitacao_salvo = "{{ tramitacao_salvo }}" + + if (tipo_materia && numero_materia && ano_materia) { + $.get("/sessao/recuperar-tramitacao", + { tipo_materia: tipo_materia, numero_materia: numero_materia, ano_materia: ano_materia }, + function(data, status) { + if (status == 'success') { + $('#id_tramitacao_select').off('change') + $('#id_tramitacao_select').find('option').remove() + $('#id_tramitacao_select').append(''); + for (const property in data) { + console.log(tramitacao_salvo + "===" + property) + const option = $(``) + option[0].data = data[property] + $('#id_tramitacao_select').append(option) + if (property == tramitacao_salvo) { + $("#id_tramitacao_select option[value='"+ property +"']").attr("selected", "selected"); + } + } + $('#id_tramitacao_select').on('change', function(event) { + const option = event.currentTarget.selectedOptions[0] + + if (option.data === undefined) + return + + const html_alert = `Data da Tramitação: ${option.data.data_tramitacao}
+ De: ${option.data.unidade_tramitacao_local} - Para: ${option.data.unidade_tramitacao_destino}
+ Status Atual: ${option.data.status}
+ Texto da Ação: ${option.data.texto}` + + if ($(".tramitacao-materia").length === 0){ + $("#id_tramitacao_select").closest('.row').after($('
').append($('
').append( + $('
').html(html_alert) + ))) + } + else { + $('.tramitacao-materia').html(html_alert) + } + + }).trigger('change') + } + }); + } + } + var fields = ["#id_tipo_materia", "#id_numero_materia", "#id_ano_materia"]; for (i = 0; i < fields.length; i++){ - $(fields[i]).change(recuperar_materia) + $(fields[i]).change(function() { + recuperar_materia(); + recuperar_tramitacao(); + }); } - recuperar_materia() + recuperar_materia(); + recuperar_tramitacao(); var modal_estilos = 'display: block; width: 85%; max-width: 600px; background: #fff; padding: 15px; border-radius: 5px;' +'-webkit-box-shadow: 0px 6px 14px -2px rgba(0, 0, 0, 0.75); -moz-box-shadow: 0px 6px 14px -2px rgba(0, 0, 0, 0.75);' diff --git a/sapl/templates/sessao/layouts.yaml b/sapl/templates/sessao/layouts.yaml index 814319878..78517b708 100644 --- a/sapl/templates/sessao/layouts.yaml +++ b/sapl/templates/sessao/layouts.yaml @@ -60,6 +60,7 @@ ExpedienteMateria: - tipo_materia numero_materia ano_materia - tipo_votacao - apenas_leitura + - tramitacao_select - observacao OrdemDia: @@ -68,6 +69,7 @@ OrdemDia: - tipo_materia numero_materia ano_materia - tipo_votacao - apenas_leitura + - tramitacao_select - observacao ExpedienteMateriaDetail: @@ -75,6 +77,7 @@ ExpedienteMateriaDetail: - materia - ementa - tipo_votacao + - tramitacao - observacao OrdemDiaDetail: @@ -82,6 +85,7 @@ OrdemDiaDetail: - materia - ementa - tipo_votacao + - tramitacao - observacao Bancada: