diff --git a/sapl/api/views_materia.py b/sapl/api/views_materia.py index 1810aea2e..a37036ede 100644 --- a/sapl/api/views_materia.py +++ b/sapl/api/views_materia.py @@ -1,7 +1,9 @@ from django.apps.registry import apps +from django.db import IntegrityError, transaction from django.db.models import Q from rest_framework.decorators import action +from rest_framework.status import HTTP_201_CREATED, HTTP_409_CONFLICT from rest_framework.response import Response from drfautoapi.drfautoapi import ApiViewSetConstrutor, \ @@ -90,6 +92,60 @@ class _MateriaLegislativaViewSet: class Meta: ordering = ['-ano', 'tipo', 'numero'] + _MAX_RETRIES_NUMERO = 3 + + def create(self, request, *args, **kwargs): + data = dict(request.data) + tipo = data.get('tipo', None) + numero = data.get('numero', None) + ano = data.get('ano', None) + + if tipo and not numero: + # Número não fornecido pelo cliente: auto-gerar próximo disponível. + # select_for_update() em get_proximo_numero previne race conditions. + # Retry como camada extra de segurança contra IntegrityError residual. + for tentativa in range(self._MAX_RETRIES_NUMERO): + try: + with transaction.atomic(): + numero_gerado, ano_gerado = MateriaLegislativa.get_proximo_numero( + tipo=tipo, ano=ano + ) + data['numero'] = numero_gerado + data['ano'] = ano_gerado + + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=HTTP_201_CREATED, headers=headers) + except IntegrityError: + if tentativa == self._MAX_RETRIES_NUMERO - 1: + return Response( + {'detail': 'Não foi possível gerar um número único após ' + '%d tentativas. Tente novamente.' % self._MAX_RETRIES_NUMERO}, + status=HTTP_409_CONFLICT + ) + continue + + # Número fornecido pelo cliente (ou tipo ausente): + # respeitar os dados enviados. Se houver conflito de unicidade, + # retornar erro explícito em vez de sobrescrever silenciosamente. + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + try: + with transaction.atomic(): + self.perform_create(serializer) + except IntegrityError: + return Response( + {'numero': [ + 'O número %s já está em uso para este tipo/ano. ' + 'Remova o campo "numero" para auto-gerar o próximo disponível.' % numero + ]}, + status=HTTP_409_CONFLICT + ) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=HTTP_201_CREATED, headers=headers) + @action(detail=True, methods=['GET']) def ultima_tramitacao(self, request, *args, **kwargs): diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index c685109ae..3615fee47 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -2458,47 +2458,12 @@ class ConfirmarProposicaoForm(ProposicaoForm): if self.instance.tipo.content_type.model_class( ) == TipoMateriaLegislativa: - numeracao = None - try: - self.logger.debug( - "Tentando obter modelo de sequência de numeração.") - numeracao = BaseAppConfig.objects.last( - ).sequencia_numeracao_protocolo - except AttributeError as e: - self.logger.error("Erro ao obter modelo. " + str(e)) - pass - tipo = self.instance.tipo.tipo_conteudo_related - if tipo.sequencia_numeracao: - numeracao = tipo.sequencia_numeracao - ano = timezone.now().year - if numeracao == 'A': - numero = MateriaLegislativa.objects.filter( - ano=ano, tipo=tipo).aggregate(Max('numero')) - elif numeracao == 'L': - legislatura = Legislatura.objects.filter( - data_inicio__year__lte=ano, - data_fim__year__gte=ano).first() - data_inicio = legislatura.data_inicio - data_fim = legislatura.data_fim - numero = MateriaLegislativa.objects.filter( - data_apresentacao__gte=data_inicio, - data_apresentacao__lte=data_fim, - tipo=tipo).aggregate( - Max('numero')) - elif numeracao == 'U': - numero = MateriaLegislativa.objects.filter( - tipo=tipo).aggregate(Max('numero')) - if numeracao is None: - numero['numero__max'] = 0 - - if cd['numero_materia_futuro'] and not MateriaLegislativa.objects.filter(tipo=tipo, - ano=ano, - numero=cd['numero_materia_futuro']): - max_numero = cd['numero_materia_futuro'] - else: - max_numero = numero['numero__max'] + \ - 1 if numero['numero__max'] else 1 + max_numero, ano = MateriaLegislativa.get_proximo_numero( + tipo=tipo, + ano=None, + numero_candidato=cd.get('numero_materia_futuro', None) + ) # dados básicos materia = MateriaLegislativa() diff --git a/sapl/materia/models.py b/sapl/materia/models.py index da2e0ac91..bdeb402e3 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -3,15 +3,19 @@ from datetime import datetime from django.contrib.auth.models import Group from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ValidationError from django.db import models +from django.db.models import Max from django.db.models.functions import Concat from django.template import defaultfilters from django.utils import formats, timezone from django.utils.translation import ugettext_lazy as _ from model_utils import Choices -from sapl.base.models import SEQUENCIA_NUMERACAO_PROTOCOLO, Autor + +from sapl.base.models import SEQUENCIA_NUMERACAO_PROTOCOLO, Autor, AppConfig as BaseAppConfig from sapl.comissoes.models import Comissao, Reuniao +from sapl.parlamentares.models import Legislatura from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, TextoArticulado) from sapl.parlamentares.models import Parlamentar @@ -382,6 +386,106 @@ class MateriaLegislativa(models.Model): using=using, update_fields=update_fields) + @staticmethod + def get_proximo_numero(tipo, ano=None, numero_candidato=None): + """ + Retorna o próximo número disponível para uma MateriaLegislativa + baseado no tipo e nas configurações de numeração. + + IMPORTANTE: Este método utiliza select_for_update() e DEVE ser + chamado dentro de uma transação (transaction.atomic) para garantir + proteção contra race conditions em acessos concorrentes. + + Args: + tipo: TipoMateriaLegislativa ou int/str - o tipo da matéria + ano: int - o ano da matéria (default: ano atual) + numero_candidato: int - número candidato/desejado (opcional). + Se fornecido e disponível, será retornado. Caso contrário, + retorna o próximo sequencial. + + Returns: + tuple[int, int]: Uma tupla contendo (numero, ano) da matéria. + """ + + if ano is None: + ano = timezone.now().year + + # Obtém a configuração de numeração + numeracao = None + try: + numeracao = BaseAppConfig.objects.last( + ).sequencia_numeracao_protocolo + except AttributeError: + pass + + if not isinstance(tipo, TipoMateriaLegislativa): + if tipo is None: + raise ValidationError(_("O tipo é obrigatório.")) + + try: + tipo_id = int(tipo) + except (ValueError, TypeError): + raise ValidationError(_("Tipo inválido: '%s'") % tipo) + + try: + tipo = TipoMateriaLegislativa.objects.get(pk=tipo_id) + except TipoMateriaLegislativa.DoesNotExist: + raise TipoMateriaLegislativa.DoesNotExist( + _("TipoMateriaLegislativa with pk '%s' does not exist.") % tipo_id + ) + + # Lock na linha do TipoMateriaLegislativa para serializar + # gerações concorrentes de número do mesmo tipo. + # Requer que o chamador esteja dentro de transaction.atomic(). + TipoMateriaLegislativa.objects.select_for_update().get(pk=tipo.pk) + + # O tipo pode sobrescrever a configuração global + if tipo.sequencia_numeracao: + numeracao = tipo.sequencia_numeracao + + # Calcula o próximo número baseado no tipo de numeração + materias_select_for_update = MateriaLegislativa.objects.select_for_update() + if numeracao == 'A': # Por ano + numero = materias_select_for_update.filter( + ano=ano, tipo=tipo).aggregate(Max('numero')) + elif numeracao == 'L': # Por legislatura + legislatura = Legislatura.objects.filter( + data_inicio__year__lte=ano, + data_fim__year__gte=ano).first() + if legislatura: + data_inicio = legislatura.data_inicio + data_fim = legislatura.data_fim + numero = materias_select_for_update.filter( + data_apresentacao__gte=data_inicio, + data_apresentacao__lte=data_fim, + tipo=tipo).aggregate(Max('numero')) + else: + numero = {'numero__max': 0} + elif numeracao == 'U': # Único/Universal + numero = materias_select_for_update.filter( + tipo=tipo).aggregate(Max('numero')) + else: + numero = {'numero__max': 0} + + # Converte o número candidato para inteiro, se possível + numero_candidato_int = None + if numero_candidato is not None: + try: + numero_candidato_int = int(numero_candidato) + except (TypeError, ValueError): + numero_candidato_int = None + + # Verifica se o número candidato está disponível + if numero_candidato_int is not None and not materias_select_for_update.filter( + tipo=tipo, + ano=ano, + numero=numero_candidato_int).exists(): + return numero_candidato_int, ano + + # Retorna o próximo número sequencial + max_numero = numero['numero__max'] + return ((max_numero + 1) if max_numero else 1), ano + class Autoria(models.Model): autor = models.ForeignKey(Autor, diff --git a/sapl/materia/tests/test_materia_proximo_numero.py b/sapl/materia/tests/test_materia_proximo_numero.py new file mode 100644 index 000000000..d9ad070eb --- /dev/null +++ b/sapl/materia/tests/test_materia_proximo_numero.py @@ -0,0 +1,385 @@ +from datetime import date + +import pytest +from django.db import IntegrityError, transaction +from model_bakery import baker +from rest_framework.test import APIClient + +from sapl.base.models import AppConfig as BaseAppConfig +from sapl.materia.models import (MateriaLegislativa, RegimeTramitacao, + TipoMateriaLegislativa) +from sapl.parlamentares.models import Legislatura + + +# --------------------------------------------------------------------------- +# Helpers / fixtures +# --------------------------------------------------------------------------- + +@pytest.fixture +def tipo_materia(db): + """TipoMateriaLegislativa sem sequencia_numeracao própria (usa global).""" + return baker.make(TipoMateriaLegislativa, descricao='Projeto de Lei', + sigla='PL', sequencia_numeracao='') + + +@pytest.fixture +def tipo_materia_anual(db): + return baker.make(TipoMateriaLegislativa, descricao='Requerimento', + sigla='REQ', sequencia_numeracao='A') + + +@pytest.fixture +def tipo_materia_unico(db): + return baker.make(TipoMateriaLegislativa, descricao='Indicação', + sigla='IND', sequencia_numeracao='U') + + +@pytest.fixture +def tipo_materia_legislatura(db): + return baker.make(TipoMateriaLegislativa, descricao='PEC', + sigla='PEC', sequencia_numeracao='L') + + +@pytest.fixture +def regime(db): + return baker.make(RegimeTramitacao, descricao='Normal') + + +@pytest.fixture +def app_config(db): + return BaseAppConfig.objects.create(sequencia_numeracao_protocolo='A') + + +def _criar_materia(tipo, numero, ano, regime, **kwargs): + """Atalho para criar MateriaLegislativa com campos obrigatórios.""" + defaults = dict( + tipo=tipo, + numero=numero, + ano=ano, + data_apresentacao=date(ano, 1, 1), + regime_tramitacao=regime, + ementa='Ementa de teste', + ) + defaults.update(kwargs) + return MateriaLegislativa.objects.create(**defaults) + + +# =========================================================================== +# Testes de get_proximo_numero – numeração por ano (padrão) +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_proximo_numero_primeiro_do_ano(tipo_materia_anual, regime, app_config): + """Sem matérias existentes, o primeiro número gerado deve ser 1.""" + with transaction.atomic(): + numero, ano = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual, ano=2024) + assert numero == 1 + assert ano == 2024 + + +@pytest.mark.django_db(transaction=False) +def test_proximo_numero_sequencial(tipo_materia_anual, regime, app_config): + """Com matérias existentes 1, 2, 3 → próximo deve ser 4.""" + for n in (1, 2, 3): + _criar_materia(tipo_materia_anual, n, 2024, regime) + + with transaction.atomic(): + numero, ano = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual, ano=2024) + assert numero == 4 + assert ano == 2024 + + +@pytest.mark.django_db(transaction=False) +def test_proximo_numero_ignora_outro_ano(tipo_materia_anual, regime, app_config): + """Matérias em anos diferentes não influenciam a sequência.""" + _criar_materia(tipo_materia_anual, 10, 2023, regime, + data_apresentacao=date(2023, 6, 1)) + _criar_materia(tipo_materia_anual, 1, 2024, regime) + + with transaction.atomic(): + numero, ano = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual, ano=2024) + assert numero == 2 + + +@pytest.mark.django_db(transaction=False) +def test_proximo_numero_ignora_outro_tipo(tipo_materia_anual, regime, app_config): + """Matérias de outro tipo não influenciam a sequência.""" + outro_tipo = baker.make(TipoMateriaLegislativa, descricao='Moção', + sigla='MOC', sequencia_numeracao='A') + _criar_materia(outro_tipo, 50, 2024, regime) + _criar_materia(tipo_materia_anual, 3, 2024, regime) + + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual, ano=2024) + assert numero == 4 + + +# =========================================================================== +# Testes de get_proximo_numero – numeração única +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_proximo_numero_unico(tipo_materia_unico, regime, app_config): + """Numeração 'U' ignora o ano – sequência é global por tipo.""" + _criar_materia(tipo_materia_unico, 1, 2022, regime, + data_apresentacao=date(2022, 1, 1)) + _criar_materia(tipo_materia_unico, 2, 2023, regime, + data_apresentacao=date(2023, 1, 1)) + + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_unico, ano=2024) + assert numero == 3 + + +# =========================================================================== +# Testes de get_proximo_numero – numeração por legislatura +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_proximo_numero_por_legislatura(tipo_materia_legislatura, regime, + app_config): + """Numeração 'L' conta apenas matérias dentro da legislatura vigente.""" + baker.make(Legislatura, numero=1, + data_inicio=date(2021, 1, 1), + data_fim=date(2024, 12, 31), + data_eleicao=date(2020, 11, 15)) + + _criar_materia(tipo_materia_legislatura, 1, 2022, regime, + data_apresentacao=date(2022, 3, 1)) + _criar_materia(tipo_materia_legislatura, 2, 2023, regime, + data_apresentacao=date(2023, 5, 1)) + + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_legislatura, ano=2024) + assert numero == 3 + + +@pytest.mark.django_db(transaction=False) +def test_proximo_numero_legislatura_inexistente(tipo_materia_legislatura, + regime, app_config): + """Sem legislatura vigente, número inicia em 1.""" + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_legislatura, ano=2050) + assert numero == 1 + + +# =========================================================================== +# Testes de get_proximo_numero – numero_candidato +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_numero_candidato_disponivel(tipo_materia_anual, regime, app_config): + """Se o número candidato está disponível, deve ser retornado.""" + _criar_materia(tipo_materia_anual, 1, 2024, regime) + + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual, ano=2024, numero_candidato=5) + assert numero == 5 + + +@pytest.mark.django_db(transaction=False) +def test_numero_candidato_em_uso_retorna_sequencial(tipo_materia_anual, + regime, app_config): + """Se o número candidato já existe, retorna o sequencial.""" + _criar_materia(tipo_materia_anual, 5, 2024, regime) + + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual, ano=2024, numero_candidato=5) + assert numero == 6 + + +# =========================================================================== +# Testes de get_proximo_numero – resolução de tipo por pk (int/str) +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_tipo_passado_como_int(tipo_materia_anual, app_config): + """O tipo pode ser passado como int (pk).""" + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual.pk, ano=2024) + assert numero == 1 + + +@pytest.mark.django_db(transaction=False) +def test_tipo_passado_como_str(tipo_materia_anual, app_config): + """O tipo pode ser passado como str representando o pk.""" + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=str(tipo_materia_anual.pk), ano=2024) + assert numero == 1 + + +@pytest.mark.django_db(transaction=False) +def test_tipo_inexistente_levanta_excecao(app_config): + """Pk inexistente deve levantar DoesNotExist.""" + with pytest.raises(TipoMateriaLegislativa.DoesNotExist): + with transaction.atomic(): + MateriaLegislativa.get_proximo_numero(tipo=99999, ano=2024) + + +@pytest.mark.django_db(transaction=False) +def test_tipo_invalido_levanta_validation_error(app_config): + """Tipo não conversível para int deve levantar ValidationError.""" + from django.core.exceptions import ValidationError + with pytest.raises(ValidationError): + with transaction.atomic(): + MateriaLegislativa.get_proximo_numero(tipo='abc', ano=2024) + + +# =========================================================================== +# Testes de get_proximo_numero – ano padrão +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_ano_none_usa_ano_atual(tipo_materia_anual, app_config): + """Se ano=None, deve usar o ano corrente.""" + from django.utils import timezone + with transaction.atomic(): + _, ano = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia_anual, ano=None) + assert ano == timezone.now().year + + +# =========================================================================== +# Testes de get_proximo_numero – select_for_update (proteção concorrência) +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_unique_together_protege_duplicata(tipo_materia_anual, regime, + app_config): + """O unique_together (tipo, numero, ano) impede duplicatas no banco.""" + _criar_materia(tipo_materia_anual, 1, 2024, regime) + with pytest.raises(IntegrityError): + with transaction.atomic(): + _criar_materia(tipo_materia_anual, 1, 2024, regime) + + +# =========================================================================== +# Testes de global config – tipo sem sequencia_numeracao usa global +# =========================================================================== + +@pytest.mark.django_db(transaction=False) +def test_tipo_sem_sequencia_usa_config_global(tipo_materia, regime, app_config): + """Tipo com sequencia_numeracao vazio deve usar a config global ('A').""" + _criar_materia(tipo_materia, 1, 2024, regime) + _criar_materia(tipo_materia, 2, 2024, regime) + _criar_materia(tipo_materia, 10, 2023, regime, + data_apresentacao=date(2023, 1, 1)) + + with transaction.atomic(): + numero, _ = MateriaLegislativa.get_proximo_numero( + tipo=tipo_materia, ano=2024) + # global default 'A' → sequencial por ano → MAX(2024) = 2 → próximo = 3 + assert numero == 3 + + +# =========================================================================== +# Testes do endpoint API create (MateriaLegislativaViewSet) +# =========================================================================== + +@pytest.fixture +def api_client_autenticado(db): + """APIClient autenticado como superusuário (tem todas as permissões).""" + from django.contrib.auth import get_user_model + User = get_user_model() + user = User.objects.create_superuser( + username='admin_api', password='secret123', email='a@b.com') + client = APIClient() + client.force_authenticate(user=user) + return client + + +@pytest.mark.django_db(transaction=False) +def test_api_create_sem_numero_auto_gera(api_client_autenticado, + tipo_materia_anual, regime, + app_config): + """POST sem 'numero' deve auto-gerar próximo sequencial.""" + _criar_materia(tipo_materia_anual, 1, 2024, regime) + + response = api_client_autenticado.post( + '/api/materia/materialegislativa/', + { + 'tipo': tipo_materia_anual.pk, + 'ano': 2024, + 'data_apresentacao': '2024-06-01', + 'regime_tramitacao': regime.pk, + 'ementa': 'Teste auto-gerar número', + }, + format='json', + ) + assert response.status_code == 201 + assert response.data['numero'] == 2 + + +@pytest.mark.django_db(transaction=False) +def test_api_create_com_numero_respeita_dado(api_client_autenticado, + tipo_materia_anual, regime, + app_config): + """POST com 'numero' explícito deve criar com o número informado.""" + response = api_client_autenticado.post( + '/api/materia/materialegislativa/', + { + 'tipo': tipo_materia_anual.pk, + 'numero': 42, + 'ano': 2024, + 'data_apresentacao': '2024-06-01', + 'regime_tramitacao': regime.pk, + 'ementa': 'Número exato', + }, + format='json', + ) + assert response.status_code == 201 + assert response.data['numero'] == 42 + + +@pytest.mark.django_db(transaction=False) +def test_api_create_numero_duplicado_retorna_erro(api_client_autenticado, + tipo_materia_anual, regime, + app_config): + """POST com número já existente retorna erro. + + O DRF detecta a violação de unique_together via UniqueTogetherValidator + no serializer e retorna 400 antes de chegar ao banco. + """ + _criar_materia(tipo_materia_anual, 42, 2024, regime) + + response = api_client_autenticado.post( + '/api/materia/materialegislativa/', + { + 'tipo': tipo_materia_anual.pk, + 'numero': 42, + 'ano': 2024, + 'data_apresentacao': '2024-06-01', + 'regime_tramitacao': regime.pk, + 'ementa': 'Duplicata', + }, + format='json', + ) + assert response.status_code == 400 + + +@pytest.mark.django_db(transaction=False) +def test_api_create_sem_tipo_valida_serializer(api_client_autenticado, regime): + """POST sem 'tipo' deve falhar na validação do serializer (400).""" + response = api_client_autenticado.post( + '/api/materia/materialegislativa/', + { + 'numero': 1, + 'ano': 2024, + 'data_apresentacao': '2024-06-01', + 'regime_tramitacao': regime.pk, + 'ementa': 'Sem tipo', + }, + format='json', + ) + assert response.status_code == 400 diff --git a/sapl/materia/views.py b/sapl/materia/views.py index fd05d5e2a..d80aebae3 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -16,6 +16,7 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, ValidationError +from django.db import transaction from django.db.models import Max, Q from django.http import HttpResponse, JsonResponse from django.http.response import Http404, HttpResponseRedirect @@ -338,55 +339,17 @@ class ProposicaoTaView(IntegracaoTaView): return self.get_redirect_deactivated() -@permission_required('materia.detail_materialegislativa') +@transaction.atomic def recuperar_materia(request): - logger = logging.getLogger(__name__) - username = request.user.username tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo']) - ano = request.GET.get('ano', '') - - if not (tipo and ano): - return JsonResponse({'numero': '', 'ano': ''}) - - numeracao = None - try: - logger.debug("user=" + username + - ". Tentando obter numeração da matéria.") - numeracao = sapl.base.models.AppConfig.objects.last( - ).sequencia_numeracao_protocolo - except AttributeError as e: - logger.error("user=" + username + ". " + str(e) + - " Numeracao da matéria definida como None.") - pass + ano = request.GET.get('ano', None) - if tipo.sequencia_numeracao: - numeracao = tipo.sequencia_numeracao - - if numeracao == 'A': - numero = MateriaLegislativa.objects.filter( - ano=ano, tipo=tipo).aggregate(Max('numero')) - elif numeracao == 'L': - legislatura = Legislatura.objects.filter( - data_inicio__year__lte=ano, - data_fim__year__gte=ano).first() - data_inicio = legislatura.data_inicio - data_fim = legislatura.data_fim - numero = MateriaLegislativa.objects.filter( - data_apresentacao__gte=data_inicio, - data_apresentacao__lte=data_fim, - tipo=tipo).aggregate( - Max('numero')) - elif numeracao == 'U': - numero = MateriaLegislativa.objects.filter( - tipo=tipo).aggregate(Max('numero')) - - if numeracao is None: - numero['numero__max'] = 0 - - max_numero = numero['numero__max'] + 1 if numero['numero__max'] else 1 + max_numero, ano = MateriaLegislativa.get_proximo_numero( + tipo=tipo, + ano=int(ano) if ano else None + ) response = JsonResponse({'numero': max_numero, 'ano': ano}) - return response