From 9661ce93872b8dda9b8a52087b52c5f0c6916d28 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 26 Nov 2025 07:30:18 -0300 Subject: [PATCH] =?UTF-8?q?Feat(integracao):=20Endpoint=20para=20integra?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + LOCALHOST_SETUP.md | 40 +++++++++++ requirements/requirements.txt | 4 +- sapl/api/views_sessao.py | 124 +++++++++++++++++++++++++++++++++- 4 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 LOCALHOST_SETUP.md diff --git a/.gitignore b/.gitignore index e4f343ca5..5f6cd48c8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ node_modules/ # Distribution / packaging .Python env/ +.venv/ build/ develop-eggs/ dist/ diff --git a/LOCALHOST_SETUP.md b/LOCALHOST_SETUP.md new file mode 100644 index 000000000..8d57cc683 --- /dev/null +++ b/LOCALHOST_SETUP.md @@ -0,0 +1,40 @@ +# Subir o SAPL apontando para o banco remoto (localhost) + +Passo a passo para subir o ambiente local usando o Postgres já disponível em `legisinc.com.br:5432`. + +## 1) Preparar o ambiente Python + +```bash +cd /root/dev/sapl +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements/requirements.txt +``` + +## 2) Configurar variáveis de ambiente + +Edite `sapl/.env` com os valores do banco remoto: + +```env +DATABASE_URL=postgresql://kemuel:kasepulvida@legisinc.com.br:5432/sapl +SECRET_KEY= +DEBUG=True +EMAIL_USE_TLS=True +EMAIL_PORT=587 +``` + +> Observação: não rode `migrate` contra esse banco se ele for de produção. + +## 3) Testar conexão (opcional) + +```bash +python manage.py showmigrations --plan +``` + +## 4) Subir o servidor Django + +```bash +python manage.py runserver 0.0.0.0:8001 +``` + +Se preferir sem autoreload: `python manage.py runserver 0.0.0.0:8001 --noreload`. diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 71760e624..3dc23a2cf 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -16,14 +16,14 @@ django-ratelimit==3.0.1 easy-thumbnails==2.8.5 python-decouple==3.1 dj-database-url==0.5.0 -psycopg2-binary==2.9.9 +psycopg2-binary==2.9.8 pyyaml==6.0.1 pytz==2019.3 python-magic==0.4.15 unipath==1.1 Pillow==10.3.0 reportlab==3.6.13 -WeasyPrint==66 +WeasyPrint==66.0 trml2pdf==0.6 gunicorn==23.0.0 more-itertools==8.2.0 diff --git a/sapl/api/views_sessao.py b/sapl/api/views_sessao.py index 75807f8cd..cd4c4cf9f 100644 --- a/sapl/api/views_sessao.py +++ b/sapl/api/views_sessao.py @@ -1,4 +1,3 @@ - from django.apps.registry import apps from rest_framework.decorators import action from rest_framework.response import Response @@ -9,6 +8,10 @@ from sapl.api.serializers import ChoiceSerializer,\ SessaoPlenariaECidadaniaSerializer from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao from sapl.utils import choice_anos_com_sessaoplenaria +# Add imports for building materias payload +from sapl.sessao.models import ExpedienteMateria, OrdemDia +from sapl.materia.models import MateriaLegislativa, Tramitacao +from django.urls import reverse ApiViewSetConstrutor.build_class( @@ -45,3 +48,122 @@ class _SessaoPlenariaViewSet: def ecidadania_list(self, request, *args, **kwargs): self.serializer_class = SessaoPlenariaECidadaniaSerializer return self.list(request, *args, **kwargs) + + # New endpoint: materias da sessão (expediente e ordem do dia) + @action(detail=True, methods=['get'], url_path='materias') + def materias(self, request, *args, **kwargs): + sessao: SessaoPlenaria = self.get_object() + pk = sessao.pk + # Data de referência da sessão para status de tramitação + data_ref = sessao.data_fim or sessao.data_inicio + + def autores_info(m: MateriaLegislativa): + # retorna lista de {id, nome} + return list(m.autores.values('id', 'nome')) + + def tramitacao_info(m: MateriaLegislativa): + t = (Tramitacao.objects + .filter(materia=m, data_tramitacao__lte=data_ref) + .order_by('-data_tramitacao', '-id') + .select_related('status') + .first()) + status = '' + fase = '' + try: + status = t.status.descricao if t and t.status else '' + except Exception: + status = '' + try: + fase = t.get_turno_display() if t and t.turno else '' + except Exception: + fase = '' + return status, fase + + def material_item(m: MateriaLegislativa, status_sessao: str, extra: dict = None): + extra = extra or {} + pdf_url = None + try: + # só expõe URL se houver ao menos 1 PDF acessório + if m.documentoacessorio_set.filter(arquivo__iendswith='pdf').exists(): + pdf_url = request.build_absolute_uri( + reverse('sapl.materia:merge_docacessorios', kwargs={'pk': m.pk}) + ) + except Exception: + pdf_url = None + + status_tram, fase_materia = tramitacao_info(m) + + return { + 'id': m.pk, + 'nome_documento': str(m), + 'autoria': autores_info(m), # [{id, nome}] + 'tipo_materia': { + 'id': m.tipo_id, + 'nome': getattr(m.tipo, 'descricao', None) + }, + 'ementa': m.ementa, + 'status': status_sessao, # alias de status da sessão + 'status_sessao': status_sessao, + 'status_tramitacao': status_tram, + 'fase_materia': fase_materia, + 'quorum': getattr(m.tipo, 'quorum_minimo_votacao', None), + 'pdf_url': pdf_url, + 'tramitacao_pdf_url': request.build_absolute_uri( + reverse('sapl.relatorios:relatorio_materia_tramitacao', kwargs={'pk': m.pk}) + ), + **extra + } + + # Expediente + expediente_items = [] + for em in ExpedienteMateria.objects.select_related('materia', 'materia__tipo').filter(sessao_plenaria_id=pk): + m = em.materia + # Determina status no contexto da sessão (votação/retirada/leitura) + rv = em.registrovotacao_set.filter(materia=m).first() + rp = em.retiradapauta_set.filter(materia=m).first() + rl = em.registroleitura_set.filter(materia=m).first() + if rv: + status_sessao = rv.tipo_resultado_votacao.nome + elif rp: + status_sessao = 'Retirado' + elif rl: + status_sessao = 'Leitura' + else: + status_sessao = '' + expediente_items.append(material_item( + m, + status_sessao, + { + 'tipo_votacao': em.get_tipo_votacao_display(), + } + )) + + # Ordem do Dia + ordem_items = [] + for od in OrdemDia.objects.select_related('materia', 'materia__tipo').filter(sessao_plenaria_id=pk): + m = od.materia + rv = od.registrovotacao_set.filter(materia=m).first() + rp = od.retiradapauta_set.filter(materia=m).first() + rl = od.registroleitura_set.filter(materia=m).first() + if rv: + status_sessao = rv.tipo_resultado_votacao.nome + elif rp: + status_sessao = 'Retirado' + elif rl: + status_sessao = 'Leitura' + else: + status_sessao = '' + ordem_items.append(material_item( + m, + status_sessao, + { + 'numero_ordem': od.numero_ordem, + 'tipo_votacao': od.get_tipo_votacao_display(), + } + )) + + return Response({ + 'sessao': pk, + 'expediente': expediente_items, + 'ordem_dia': ordem_items + })