mirror of https://github.com/interlegis/sapl.git
Browse Source
Adiciona fluxo intermediário de revisão pelo Setor Legislativo antes do envio ao Protocolo, protegido por feature flag (desabilitado por padrão). - Novos campos: data_envio_setor, data_retorno_setor, usuario_envio_setor, usuario_retorno_setor, observacao_setor em Proposicao - Flag revisao_setor_legislativo em AppConfig (Sistema > Configurações) - Novo grupo "Operador do Setor Legislativo" com permissões específicas - Ações send_setor e return_from_setor no DetailView - Views ProposicaoPendenteSetor (inbox) e RevisarProposicaoSetor - OnlyOffice: setor pode editar documento quando proposição está com eles - Status S (Enviada ao Setor) e V (Revisada pelo Setor) no histórico - Templates: badges, alertas, botões condicionais e subnavpull/3858/head
17 changed files with 602 additions and 25 deletions
@ -0,0 +1,18 @@ |
|||||
|
# Generated by Django 2.2.28 on 2026-02-23 14:02 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('base', '0063_merge_20260210_1053'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AddField( |
||||
|
model_name='appconfig', |
||||
|
name='revisao_setor_legislativo', |
||||
|
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Habilitar etapa de revisão pelo Setor Legislativo?'), |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,50 @@ |
|||||
|
# Generated by Django 2.2.28 on 2026-02-23 14:02 |
||||
|
|
||||
|
from django.conf import settings |
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
|
('materia', '0091_add_codigo_autenticacao'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AlterModelOptions( |
||||
|
name='proposicao', |
||||
|
options={'ordering': ['-data_recebimento'], 'permissions': (('detail_proposicao_enviada', 'Pode acessar detalhes de uma proposição enviada.'), ('detail_proposicao_devolvida', 'Pode acessar detalhes de uma proposição devolvida.'), ('detail_proposicao_incorporada', 'Pode acessar detalhes de uma proposição incorporada.'), ('detail_proposicao_em_revisao_setor', 'Pode acessar detalhes de uma proposição em revisão pelo setor.')), 'verbose_name': 'Proposição', 'verbose_name_plural': 'Proposições'}, |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='proposicao', |
||||
|
name='data_envio_setor', |
||||
|
field=models.DateTimeField(blank=True, null=True, verbose_name='Data de Envio ao Setor Legislativo'), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='proposicao', |
||||
|
name='data_retorno_setor', |
||||
|
field=models.DateTimeField(blank=True, null=True, verbose_name='Data de Retorno do Setor Legislativo'), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='proposicao', |
||||
|
name='observacao_setor', |
||||
|
field=models.TextField(blank=True, default='', verbose_name='Observação do Setor Legislativo'), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='proposicao', |
||||
|
name='usuario_envio_setor', |
||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='proposicoes_enviadas_setor', to=settings.AUTH_USER_MODEL, verbose_name='Usuário Responsável pelo Envio ao Setor'), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='proposicao', |
||||
|
name='usuario_retorno_setor', |
||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='proposicoes_retornadas_setor', to=settings.AUTH_USER_MODEL, verbose_name='Usuário Responsável pelo Retorno do Setor'), |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='historicoproposicao', |
||||
|
name='status', |
||||
|
field=models.CharField(choices=[('E', 'Enviada'), ('R', 'Recebida'), ('T', 'Retornada'), ('D', 'Devolvida'), ('S', 'Enviada ao Setor Legislativo'), ('V', 'Revisada pelo Setor Legislativo')], db_index=True, max_length=1, verbose_name='Status de Proposição'), |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,17 @@ |
|||||
|
from sapl.compilacao import models as compilacao |
||||
|
from sapl.materia import models as materia |
||||
|
from sapl.rules import SGVP_GROUP_SETOR_LEGISLATIVO, RP_LIST, RP_DETAIL, RP_CHANGE, \ |
||||
|
__listdetailchange__, __perms_publicas__ |
||||
|
|
||||
|
rules_group_setor_legislativo = { |
||||
|
'group': SGVP_GROUP_SETOR_LEGISLATIVO, |
||||
|
'rules': [ |
||||
|
(materia.Proposicao, __listdetailchange__ + |
||||
|
['detail_proposicao_enviada', |
||||
|
'detail_proposicao_devolvida', |
||||
|
'detail_proposicao_incorporada', |
||||
|
'detail_proposicao_em_revisao_setor'], set()), |
||||
|
(materia.HistoricoProposicao, [RP_LIST, RP_DETAIL], set()), |
||||
|
(compilacao.TextoArticulado, ['view_restricted_textoarticulado'], __perms_publicas__), |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
{% extends "materia/proposicao_base.html" %} |
||||
|
{% load i18n %} |
||||
|
{% load tz %} |
||||
|
{% load common_tags %} |
||||
|
{% block base_content %} |
||||
|
<div class="proposicao-content"> |
||||
|
<div class="proposicao-header"> |
||||
|
<h2>{% trans "Revisão do Setor Legislativo" %}</h2> |
||||
|
<p class="text-muted">{% trans "Proposições enviadas ao setor aguardando revisão" %}</p> |
||||
|
</div> |
||||
|
{% if not object_list %} |
||||
|
<p>{{ NO_ENTRIES_MSG }}</p> |
||||
|
{% else %} |
||||
|
<table class="table table-striped table-hover"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
{% with 'Data Envio ao Setor,Tipo,Descrição,Autor' as list %} |
||||
|
{% for name in list|split:"," %} |
||||
|
<th> |
||||
|
<a title="{% trans 'Clique para alterar a ordem a listagem'%}" href="?o={% if 'o' not in request.GET and forloop.counter == 1 or 'o' in request.GET and forloop.counter|safe == request.GET.o %}-{%endif%}{{forloop.counter}}"> |
||||
|
{{ name }} |
||||
|
{% if 'o' in request.GET %} |
||||
|
{% if 'o' not in request.GET and forloop.counter == 1 or 'o' in request.GET and forloop.counter|safe == request.GET.o %} |
||||
|
<span class="caret top" title="{% trans 'Listado na Ordem Ascendente'%}"></span> |
||||
|
{% elif 'o' in request.GET and forloop.counter == request.GET.o|str2intabs %} |
||||
|
<span class="caret" title="{% trans 'Listado na Ordem Descendente'%}"></span> |
||||
|
{%endif%} |
||||
|
{%endif%} |
||||
|
</a> |
||||
|
</th> |
||||
|
{% endfor %} |
||||
|
<th>{% trans "Ações" %}</th> |
||||
|
{% endwith %} |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{% if 'o' in request.GET %} |
||||
|
{% define object_list|sort_by_keys:request.GET.o as list %} |
||||
|
{% else %} |
||||
|
{% define object_list as list %} |
||||
|
{% endif %} |
||||
|
|
||||
|
{% if 'page' in request.GET %} |
||||
|
{% define request.GET.page as pagina %} |
||||
|
{% else %} |
||||
|
{% define '1' as pagina %} |
||||
|
{% endif %} |
||||
|
|
||||
|
{% for prop in list %} |
||||
|
|
||||
|
{% if forloop.counter > pagina|paginacao_limite_inferior and forloop.counter <= pagina|paginacao_limite_superior %} |
||||
|
<tr> |
||||
|
<td> |
||||
|
<a href="{% url 'sapl.materia:proposicao_detail' prop.pk %}">{{ prop.data_envio_setor|localtime|date:"d/m/Y H:i:s" }}</a> |
||||
|
</td> |
||||
|
<td>{{ prop.tipo.descricao }}</td> |
||||
|
<td>{{ prop.descricao }}</td> |
||||
|
<td>{{ prop.autor }}</td> |
||||
|
<td> |
||||
|
<a href="{% url 'sapl.materia:revisao-proposicao-setor' prop.pk %}" class="btn btn-sm btn-primary" title="{% trans 'Revisar Proposição' %}"> |
||||
|
<i class="fas fa-pencil-alt"></i> {% trans "Revisar" %} |
||||
|
</a> |
||||
|
<a href="{% url 'sapl.materia:proposicao_detail' prop.pk %}" class="btn btn-sm btn-outline-secondary" title="{% trans 'Ver Detalhes' %}"> |
||||
|
<i class="fas fa-eye"></i> |
||||
|
</a> |
||||
|
</td> |
||||
|
</tr> |
||||
|
{% endif %} |
||||
|
{% endfor %} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
{% endif %} |
||||
|
</div> |
||||
|
|
||||
|
{% include 'paginacao.html'%} |
||||
|
{% endblock %} |
||||
@ -0,0 +1,80 @@ |
|||||
|
{% extends "materia/proposicao_base.html" %} |
||||
|
{% load i18n %} |
||||
|
{% load tz %} |
||||
|
{% load crispy_forms_tags %} |
||||
|
|
||||
|
{% block base_content %} |
||||
|
<div class="proposicao-content"> |
||||
|
<div class="proposicao-header"> |
||||
|
<h2>{% trans "Revisar Proposição" %}</h2> |
||||
|
<p class="text-muted">{{ proposicao.tipo }} — {{ proposicao.descricao|truncatechars:80 }}</p> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Informações da proposição --> |
||||
|
<div class="card mb-3"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-3"> |
||||
|
<strong>{% trans "Tipo" %}</strong><br> |
||||
|
{{ proposicao.tipo }} |
||||
|
</div> |
||||
|
<div class="col-md-3"> |
||||
|
<strong>{% trans "Autor" %}</strong><br> |
||||
|
{{ proposicao.autor }} |
||||
|
</div> |
||||
|
<div class="col-md-3"> |
||||
|
<strong>{% trans "Envio ao Setor" %}</strong><br> |
||||
|
{{ proposicao.data_envio_setor|date:"d/m/Y H:i" }} |
||||
|
</div> |
||||
|
<div class="col-md-3"> |
||||
|
<strong>{% trans "Documento" %}</strong><br> |
||||
|
{% if proposicao.texto_original %} |
||||
|
<i class="fa fa-check-circle" style="color: #28a745;"></i> {% trans "Criado" %} |
||||
|
{% else %} |
||||
|
<i class="fa fa-times-circle" style="color: #dc3545;"></i> {% trans "Não criado" %} |
||||
|
{% endif %} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Ementa --> |
||||
|
<div class="card mb-3"> |
||||
|
<div class="card-header"> |
||||
|
<strong>{% trans "Ementa" %}</strong> |
||||
|
</div> |
||||
|
<div class="card-body"> |
||||
|
{{ proposicao.descricao }} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Ações do documento --> |
||||
|
<div class="card mb-3"> |
||||
|
<div class="card-header"> |
||||
|
<strong>{% trans "Documento" %}</strong> |
||||
|
</div> |
||||
|
<div class="card-body text-center"> |
||||
|
{% if proposicao.texto_original %} |
||||
|
<a href="{% url 'sapl.materia:onlyoffice_editor' proposicao.pk %}" class="btn btn-primary mr-2"> |
||||
|
<i class="fa fa-edit"></i> {% trans "Editar com OnlyOffice" %} |
||||
|
</a> |
||||
|
<a href="{% url 'sapl.materia:proposicao_texto' proposicao.pk %}" class="btn btn-outline-secondary" target="_blank"> |
||||
|
<i class="fa fa-download"></i> {% trans "Baixar Documento" %} |
||||
|
</a> |
||||
|
{% else %} |
||||
|
<p class="text-muted">{% trans "Nenhum documento anexado." %}</p> |
||||
|
{% endif %} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Formulário de revisão --> |
||||
|
<div class="card mb-3"> |
||||
|
<div class="card-body"> |
||||
|
<form method="post" enctype="multipart/form-data"> |
||||
|
{% csrf_token %} |
||||
|
{% crispy form %} |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
Loading…
Reference in new issue