Browse Source

Feat (Proposição): Como cliente, quero poder inserir +1 autor durante o cadastro das matérias [AB#1324]

pull/3858/head
KemuelAlves 4 months ago
parent
commit
a2a3adc769
  1. 60
      sapl/materia/forms.py
  2. 30
      sapl/materia/migrations/0095_add_autoria_proposicao.py
  3. 34
      sapl/materia/models.py
  4. 15
      sapl/templates/materia/proposicao_detail.html
  5. 53
      sapl/templates/materia/proposicao_form.html

60
sapl/materia/forms.py

@ -45,7 +45,7 @@ from sapl.utils import (autor_label, autor_modal, timing,
GoogleRecapthaMixin, get_client_ip) GoogleRecapthaMixin, get_client_ip)
from sapl.utils_template import adicionar_cabecalho_materia from sapl.utils_template import adicionar_cabecalho_materia
from .models import (AcompanhamentoMateria, Anexada, Autoria, from .models import (AcompanhamentoMateria, Anexada, Autoria, AutoriaProposicao,
DespachoInicial, DocumentoAcessorio, Numeracao, DespachoInicial, DocumentoAcessorio, Numeracao,
Proposicao, Relatoria, TipoMateriaLegislativa, Proposicao, Relatoria, TipoMateriaLegislativa,
Tramitacao, UnidadeTramitacao) Tramitacao, UnidadeTramitacao)
@ -1999,6 +1999,19 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
numero_materia_futuro = forms.IntegerField( numero_materia_futuro = forms.IntegerField(
label='Número (Opcional)', required=False) label='Número (Opcional)', required=False)
coautores = forms.ModelMultipleChoiceField(
label=_('Co-autores'),
required=False,
queryset=Autor.objects.all(),
widget=forms.SelectMultiple(attrs={
'class': 'select2-coautores',
'style': 'width: 100%',
'data-placeholder': _('Selecione os co-autores...')
}),
help_text=_('Selecione os demais autores deste documento. '
'Eles serão adicionados como co-autores ao incorporar a proposição.')
)
class Meta: class Meta:
model = Proposicao model = Proposicao
fields = ['tipo', fields = ['tipo',
@ -2044,6 +2057,7 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
dismiss=False), 12)), dismiss=False), 12)),
to_column(('descricao', 12)), to_column(('descricao', 12)),
to_column(('observacao', 12)), to_column(('observacao', 12)),
to_column(('coautores', 12)),
] ]
@ -2181,6 +2195,13 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
'ano_materia' 'ano_materia'
].initial = self.instance.materia_de_vinculo.ano ].initial = self.instance.materia_de_vinculo.ano
# Pré-popular co-autores existentes
coautores_pks = list(
self.instance.coautores.values_list('autor_id', flat=True)
)
if coautores_pks:
self.fields['coautores'].initial = coautores_pks
def clean_texto_original(self): def clean_texto_original(self):
texto_original = self.cleaned_data.get('texto_original', False) texto_original = self.cleaned_data.get('texto_original', False)
@ -2272,7 +2293,9 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
inst.texto_original.delete() inst.texto_original.delete()
self.gerar_hash(inst, receber_recibo) self.gerar_hash(inst, receber_recibo)
return super().save(commit) result = super().save(commit)
self._salvar_coautores(result)
return result
inst.ano = timezone.now().year inst.ano = timezone.now().year
sequencia_numeracao = BaseAppConfig.attr( sequencia_numeracao = BaseAppConfig.attr(
@ -2291,9 +2314,27 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
self.gerar_hash(inst, receber_recibo) self.gerar_hash(inst, receber_recibo)
inst.save() inst.save()
self._salvar_coautores(inst)
return inst return inst
def _salvar_coautores(self, inst):
"""Sincroniza os co-autores selecionados no formulário com AutoriaProposicao."""
coautores = self.cleaned_data.get('coautores', [])
# Remove co-autores não mais selecionados
inst.coautores.exclude(autor__in=coautores).delete()
# Adiciona novos co-autores
autores_existentes = set(
inst.coautores.values_list('autor_id', flat=True)
)
for autor in coautores:
if autor.pk not in autores_existentes:
AutoriaProposicao.objects.create(
proposicao=inst,
autor=autor,
primeiro_autor=False
)
class DevolverProposicaoForm(forms.ModelForm): class DevolverProposicaoForm(forms.ModelForm):
@ -2796,6 +2837,21 @@ class ConfirmarProposicaoForm(ProposicaoForm):
'Autoria registrada para (%s)' 'Autoria registrada para (%s)'
) % str(autoria.autor)) ) % str(autoria.autor))
# Transferir co-autores da proposição para Autoria da matéria
for coautoria in proposicao.coautores.all():
# Não duplicar se o co-autor for o mesmo que o autor principal
if coautoria.autor != proposicao.autor:
Autoria.objects.get_or_create(
autor=coautoria.autor,
materia=materia,
defaults={
'primeiro_autor': coautoria.primeiro_autor
}
)
self.instance.results['messages']['success'].append(_(
'Co-autoria registrada para (%s)'
) % str(coautoria.autor))
# Transferir anexos da proposição para DocumentoAcessorio # Transferir anexos da proposição para DocumentoAcessorio
from sapl.materia.models import AnexoProposicao from sapl.materia.models import AnexoProposicao
anexos = AnexoProposicao.objects.filter( anexos = AnexoProposicao.objects.filter(

30
sapl/materia/migrations/0095_add_autoria_proposicao.py

@ -0,0 +1,30 @@
# Generated by Django 2.2.28 on 2026-05-12 12:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('base', '0064_appconfig_revisao_setor_legislativo'),
('materia', '0094_add_anexoproposicao'),
]
operations = [
migrations.CreateModel(
name='AutoriaProposicao',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('primeiro_autor', models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Primeiro Autor')),
('autor', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='base.Autor', verbose_name='Co-autor')),
('proposicao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='coautores', to='materia.Proposicao', verbose_name='Proposição')),
],
options={
'verbose_name': 'Co-autoria da Proposição',
'verbose_name_plural': 'Co-autorias da Proposição',
'ordering': ('-primeiro_autor', 'autor__nome'),
'unique_together': {('proposicao', 'autor')},
},
),
]

34
sapl/materia/models.py

@ -1158,6 +1158,40 @@ class Proposicao(models.Model):
update_fields=update_fields) update_fields=update_fields)
class AutoriaProposicao(models.Model):
"""
Modelo para co-autores de uma Proposição.
Permite que o autor da proposição indique múltiplos co-autores
que serão transferidos para a Autoria da matéria ao incorporar.
"""
proposicao = models.ForeignKey(
Proposicao,
on_delete=models.CASCADE,
verbose_name=_('Proposição'),
related_name='coautores'
)
autor = models.ForeignKey(
Autor,
on_delete=models.PROTECT,
verbose_name=_('Co-autor')
)
primeiro_autor = models.BooleanField(
verbose_name=_('Primeiro Autor'),
choices=YES_NO_CHOICES,
default=False
)
class Meta:
verbose_name = _('Co-autoria da Proposição')
verbose_name_plural = _('Co-autorias da Proposição')
unique_together = (('proposicao', 'autor'),)
ordering = ('-primeiro_autor', 'autor__nome')
def __str__(self):
return _('Co-autoria: %(autor)s - %(proposicao)s') % {
'autor': self.autor, 'proposicao': self.proposicao}
class HistoricoProposicao(models.Model): class HistoricoProposicao(models.Model):
STATUS_PROPOSICAO = Choices(('E', 'ENVIADA', _('Enviada')), STATUS_PROPOSICAO = Choices(('E', 'ENVIADA', _('Enviada')),
('R', 'RECEBIDA', _('Recebida')), ('R', 'RECEBIDA', _('Recebida')),

15
sapl/templates/materia/proposicao_detail.html

@ -460,6 +460,21 @@
</div> </div>
{% endif %} {% endif %}
<!-- Co-autores -->
{% if object.coautores.exists %}
<div class="prop-section">
<div class="prop-section-title">{% trans "Co-autores" %}</div>
<div class="prop-meta">
{% for coautoria in object.coautores.all %}
<div class="prop-meta-item" style="flex: 0 0 auto; min-width: 200px;">
<label>{{ coautoria.autor.tipo }}</label>
<span><i class="fa fa-user"></i> {{ coautoria.autor.nome }}</span>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Conteúdo Gerado / Vínculo --> <!-- Conteúdo Gerado / Vínculo -->
{% if object.conteudo_gerado_related or object.materia_de_vinculo %} {% if object.conteudo_gerado_related or object.materia_de_vinculo %}
<div class="prop-section"> <div class="prop-section">

53
sapl/templates/materia/proposicao_form.html

@ -2,8 +2,46 @@
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block extra_js %} {% block extra_css %}
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<style> <style>
/* Select2 co-autores */
.select2-container--default .select2-selection--multiple {
border: 1px solid #ced4da;
border-radius: 4px;
min-height: 38px;
padding: 2px 6px;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #007bff;
border: none;
color: #fff;
border-radius: 3px;
padding: 2px 8px;
font-size: 0.9em;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: #fff;
margin-right: 5px;
}
#div_id_coautores {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
border: 1px solid #e9ecef;
margin-bottom: 15px;
}
#div_id_coautores > label {
font-weight: 600;
color: #495057;
margin-bottom: 8px;
display: block;
}
#div_id_coautores .form-text {
color: #6c757d;
font-size: 0.875em;
margin-top: 5px;
}
/* ===================================================== /* =====================================================
Estilo para o container do tipo de proposição Estilo para o container do tipo de proposição
===================================================== */ ===================================================== */
@ -188,10 +226,23 @@
padding: 8px 25px; padding: 8px 25px;
} }
</style> </style>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
// =====================================================
// Inicializar Select2 para co-autores
// =====================================================
$('.select2-coautores').select2({
placeholder: '{% trans "Selecione os co-autores..." %}',
allowClear: true,
language: 'pt-BR',
width: '100%'
});
// ===================================================== // =====================================================
// Configuração do botão "Novo Tipo" // Configuração do botão "Novo Tipo"
// ===================================================== // =====================================================

Loading…
Cancel
Save