#3821 centralizar geração próximo número para create de matérias legislativas#3822
#3821 centralizar geração próximo número para create de matérias legislativas#3822LeandroJatai wants to merge 16 commits intointerlegis:3.1.xfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR centralizes the logic for generating the next sequential number for legislative matters (MateriaLegislativa) into a single static method get_proximo_numero() in the MateriaLegislativa model. Previously, this logic was duplicated in ConfirmarProposicaoForm.save() and recuperar_materia() view function. The refactoring removes code duplication and adds support for the same numbering logic in the API's create endpoint.
Changes:
- Added static method
get_proximo_numero()to MateriaLegislativa model to centralize number generation logic - Refactored
recuperar_materia()view andConfirmarProposicaoForm.save()to use the new centralized method - Implemented custom
create()method in the API viewset to apply automatic numbering to API-created matters
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| sapl/materia/models.py | Added centralized get_proximo_numero() static method that handles all numbering sequence types (A, L, U) and supports preferred numbers |
| sapl/materia/views.py | Refactored recuperar_materia() to use the new centralized method, removing ~40 lines of duplicated logic |
| sapl/materia/forms.py | Refactored ConfirmarProposicaoForm.save() to use the new centralized method, removing ~35 lines of duplicated logic |
| sapl/api/views_materia.py | Added custom create() method to apply automatic numbering when creating MateriaLegislativa via API |
| sapl/api/serializers.py | Added import for MateriaLegislativa model to support API changes |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
joaohortsenado
left a comment
There was a problem hiding this comment.
@LeandroJatai no geral achei a mudança ótima e necessária, sempre que pudermos melhorar a reusabilidade do código é excelente.
Fiz comentários em alguns pontos, @edwardoliveira se você puder dar uma conferida, podem haver casos de tech debt antigo que talvez possamos passar em PR específico.
sapl/api/views_materia.py
Outdated
| class Meta: | ||
| ordering = ['-ano', 'tipo', 'numero'] | ||
|
|
||
| @transaction.atomic |
There was a problem hiding this comment.
O @transaction.atomic não resolve o problema de concorrência. Duas requests simultâneas podem chamar get_proximo_numero(), obter o mesmo número, e só uma delas vai conseguir salvar (ou pior, ambas salvam se não houver unique_together).
There was a problem hiding this comment.
+1.
Felizmente nós temos unique_together em MateriaLegislativa: https://github.com/interlegis/sapl/blob/3.1.x/sapl/materia/models.py#L310, mas realmente precisamos de um mecanismo para evitar race conditions (select_for_update() ou algo similar).
There was a problem hiding this comment.
@joaohortsenado as alterações e testes do primeiro commit de hoje foram feitas pelo "claude opus 4.6" tendo como contexto todos os comentários. Após isso chequei todos os comentários cruzando com a implementação.
| numero = {'numero__max': 0} | ||
| elif numeracao == 'U': # Único/Universal | ||
| numero = MateriaLegislativa.objects.filter( | ||
| tipo=tipo).aggregate(Max('numero')) |
There was a problem hiding this comment.
Aqui está sendo feito um SELECT MAX(numero) sem select_for_update(), então o número gerado pode já estar em uso quando osave()ocorrer.
Minha sugestão é usar select_for_update() nas queries dentro de get_proximo_numero quando chamado dentro de uma transaction, ou adicionar retry logic.
sapl/materia/models.py
Outdated
| 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( |
There was a problem hiding this comment.
Se o numero_preferido já está em uso, o método silenciosamente ignora e retorna o próximo sequencial. Isso pode ser confuso na API: o cliente envia numero=5 e recebe a matéria criada com numero=47 sem nenhum aviso.
Acho que poderia retornar um erro ou ao menos um aviso quando o número preferido não pode ser usado.... o que acha?
There was a problem hiding this comment.
+1. IMHO, um erro seria melhor.
sapl/api/views_materia.py
Outdated
| numero, ano = MateriaLegislativa.get_proximo_numero( | ||
| tipo=tipo, | ||
| ano=ano, | ||
| numero_preferido=numero |
There was a problem hiding this comment.
O campo numero do payload é passado como numero_preferido. Isso significa que na API nunca é possível forçar um número específico, ele sempre pode ser sobrescrito. Se o cliente envia numero=5 e ele já existe, recebe outro número sem erro. Isso quebra a expectativa de uma API REST onde o cliente espera que os dados enviados sejam respeitados ou rejeitados.
There was a problem hiding this comment.
+1. IMO, deveria haver uma rejeição se numero_preferido informado mas já existir.
sapl/api/views_materia.py
Outdated
|
|
||
| @transaction.atomic | ||
| def create(self, request, *args, **kwargs): | ||
| data = deepcopy(request.data) |
There was a problem hiding this comment.
O deepcopy pode ter comportamento inesperado dependendo do content type. Um simples request.data.copy() (ou dict(request.data)) é mais idiomático para django rest framework
| @@ -340,53 +340,15 @@ def get(self, request, *args, **kwargs): | |||
|
|
|||
| @permission_required('materia.detail_materialegislativa') | |||
| def recuperar_materia(request): | |||
There was a problem hiding this comment.
O código original retornava vazio quando tipo ou ano estavam ausentes. A nova versão não tem essa guarda. Se ano não vier no GET, get_proximo_numero usa o ano atual, o que muda o comportamento da view. Pode ser intencional, mas vale verificar se o frontend depende do comportamento anterior. (@edwardoliveira se puder opinar sobre isso, você tem mais conhecimento que eu... talvez nao seja um problema, o que acha?)
sapl/materia/models.py
Outdated
| Returns: | ||
| tuple[int, int]: Uma tupla contendo (numero, ano) da matéria. | ||
| """ | ||
| from django.db.models import Max |
There was a problem hiding this comment.
O Max pode ser importado no topo do arquivo sem problema. Para os outros, o import local é aceitável, mas vale documentar o motivo com um comentário (provavelmente para evitar imports circulares??)
sapl/api/views_materia.py
Outdated
| class Meta: | ||
| ordering = ['-ano', 'tipo', 'numero'] | ||
|
|
||
| @transaction.atomic |
There was a problem hiding this comment.
+1.
Felizmente nós temos unique_together em MateriaLegislativa: https://github.com/interlegis/sapl/blob/3.1.x/sapl/materia/models.py#L310, mas realmente precisamos de um mecanismo para evitar race conditions (select_for_update() ou algo similar).
sapl/materia/models.py
Outdated
| update_fields=update_fields) | ||
|
|
||
| @staticmethod | ||
| def get_proximo_numero(tipo, ano=None, numero_preferido=None): |
There was a problem hiding this comment.
Nit: renomear para numero_candidato
sapl/materia/models.py
Outdated
| 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( |
There was a problem hiding this comment.
+1. IMHO, um erro seria melhor.
sapl/api/views_materia.py
Outdated
| numero, ano = MateriaLegislativa.get_proximo_numero( | ||
| tipo=tipo, | ||
| ano=ano, | ||
| numero_preferido=numero |
There was a problem hiding this comment.
+1. IMO, deveria haver uma rejeição se numero_preferido informado mas já existir.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
f96a98f to
a9f66a9
Compare
Atualmente a geração de próximo número para matérias é um código replicado em dois pontos:
Um terceiro local se faz necessário, no create da API, em
_MateriaLegislativaViewSet, além de outros que possam surgir no futuro.Este PR centraliza a geração de próximo número em um método estático no model
MateriaLegislativa, refatoraConfirmarProposicaoFormerecuperar_materia, além de criar o método create na viewset para que em POSTs seja aplicada a mesma lógica de numeração configurada em Configurações da Aplicação e na sequencia de numeração do tipo de matéria.Descrição
Issue Relacionada
#3821
Motivação e Contexto
Centraliza a geração de próximo número para matéria legislativa e remove duplicidade de código.