diff --git a/sapl/api/views_materia.py b/sapl/api/views_materia.py index 1810aea2e..805453ed7 100644 --- a/sapl/api/views_materia.py +++ b/sapl/api/views_materia.py @@ -1,7 +1,10 @@ +from copy import deepcopy from django.apps.registry import apps +from django.db import transaction from django.db.models import Q from rest_framework.decorators import action +from rest_framework.status import HTTP_201_CREATED from rest_framework.response import Response from drfautoapi.drfautoapi import ApiViewSetConstrutor, \ @@ -90,6 +93,31 @@ class _MateriaLegislativaViewSet: class Meta: ordering = ['-ano', 'tipo', 'numero'] + @transaction.atomic + def create(self, request, *args, **kwargs): + data = deepcopy(request.data) + tipo = data.get('tipo', None) + numero = data.get('numero', None) + ano = data.get('ano', None) + + if tipo: + numero, ano = MateriaLegislativa.get_proximo_numero( + tipo=tipo, + ano=ano, + numero_preferido=numero + ) + data['numero'] = numero + data['ano'] = ano + + 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) + + + @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..9c39c58df 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_preferido=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..efbe1a186 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -3,6 +3,7 @@ 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.functions import Concat from django.template import defaultfilters @@ -382,6 +383,98 @@ class MateriaLegislativa(models.Model): using=using, update_fields=update_fields) + @staticmethod + def get_proximo_numero(tipo, ano=None, numero_preferido=None): + """ + Retorna o próximo número disponível para uma MateriaLegislativa + baseado no tipo e nas configurações de numeração. + + Args: + tipo: TipoMateriaLegislativa - o tipo da matéria + ano: int - o ano da matéria (default: ano atual) + numero_preferido: int - número preferido/desejado (opcional) + + Returns: + tuple[int, int]: Uma tupla contendo (numero, ano) da matéria. + """ + from django.db.models import Max + from sapl.parlamentares.models import Legislatura + import sapl.base.models + + if ano is None: + ano = timezone.now().year + + # Obtém a configuração de numeração + numeracao = None + try: + numeracao = sapl.base.models.AppConfig.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 + ) + + # 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 + if numeracao == 'A': # Por ano + numero = MateriaLegislativa.objects.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 = MateriaLegislativa.objects.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 = MateriaLegislativa.objects.filter( + tipo=tipo).aggregate(Max('numero')) + else: + numero = {'numero__max': 0} + + # Converte o número preferido para inteiro, se possível + numero_preferido_int = None + if numero_preferido: + try: + numero_preferido_int = int(numero_preferido) + except (TypeError, ValueError): + numero_preferido_int = None + + # Verifica se o número preferido está disponível + if numero_preferido_int is not None and not MateriaLegislativa.objects.filter( + tipo=tipo, + ano=ano, + numero=numero_preferido_int).exists(): + return numero_preferido_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/views.py b/sapl/materia/views.py index fd05d5e2a..e256bd43b 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -340,53 +340,15 @@ class ProposicaoTaView(IntegracaoTaView): @permission_required('materia.detail_materialegislativa') 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