Browse Source

Adiciona solicitação de convênios

pull/159/head
Sesostris Vieira 3 years ago
parent
commit
321d35d33f
  1. 9
      sigi/apps/casas/models.py
  2. 25
      sigi/apps/convenios/migrations/0031_alter_projeto_modelo_minuta_and_more.py
  3. 85
      sigi/apps/convenios/models.py
  4. 0
      sigi/apps/convenios/templates/convenios/oficio_padrao.html
  5. 109
      sigi/apps/eventos/views.py
  6. 23
      sigi/apps/home/templates/home/openmap.html
  7. 12
      sigi/apps/ocorrencias/admin_urls.py
  8. 111
      sigi/apps/ocorrencias/forms.py
  9. 60
      sigi/apps/ocorrencias/migrations/0009_alter_comentario_options_categoria_projeto_and_more.py
  10. 78
      sigi/apps/ocorrencias/models.py
  11. 69
      sigi/apps/ocorrencias/static/ocorrencias/css/ocorrencia.css
  12. 53
      sigi/apps/ocorrencias/templates/ocorrencias/convenio/atualiza_casa.html
  13. 48
      sigi/apps/ocorrencias/templates/ocorrencias/convenio/base_convenio.html
  14. 216
      sigi/apps/ocorrencias/templates/ocorrencias/convenio/ocorrencia.html
  15. 40
      sigi/apps/ocorrencias/templates/ocorrencias/convenio/painel_convenio.html
  16. 245
      sigi/apps/ocorrencias/templates/ocorrencias/convenio/painel_convenio_detail.html
  17. 53
      sigi/apps/ocorrencias/templates/ocorrencias/convenio/seleciona_casa.html
  18. 9
      sigi/apps/ocorrencias/urls.py
  19. 380
      sigi/apps/ocorrencias/views.py
  20. 2
      sigi/apps/parlamentares/models.py
  21. 5
      sigi/apps/parlamentares/urls.py
  22. 13
      sigi/apps/parlamentares/views.py
  23. 18
      sigi/apps/servidores/migrations/0010_servidor_sigi.py
  24. 3
      sigi/apps/servidores/models.py
  25. 2
      sigi/menu_conf.yaml
  26. 3
      sigi/static/css/base_block.css
  27. 7
      sigi/templates/admin/base_block.html
  28. 3
      sigi/urls.py

9
sigi/apps/casas/models.py

@ -175,6 +175,15 @@ class Orgao(models.Model):
def contato_interlegis(self): def contato_interlegis(self):
return self.funcionario_set.filter(setor="contato_interlegis").first() return self.funcionario_set.filter(setor="contato_interlegis").first()
def get_sigla(self):
return self.sigla or "".join(
[
w[0]
for w in self.nome.split()
if w.lower() not in ["da", "de", "do"]
]
)
def __str__(self): def __str__(self):
return self.nome return self.nome

25
sigi/apps/convenios/migrations/0031_alter_projeto_modelo_minuta_and_more.py

File diff suppressed because one or more lines are too long

85
sigi/apps/convenios/models.py

@ -4,11 +4,16 @@ from django.db import models
from django.db.models import Q, fields from django.db.models import Q, fields
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.validators import FileExtensionValidator from django.core.validators import FileExtensionValidator
from django.template import Template, Context
from django.template.exceptions import TemplateSyntaxError
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_weasyprint.utils import django_url_fetcher
from docx import Document
from tinymce.models import HTMLField from tinymce.models import HTMLField
from weasyprint import HTML
from sigi.apps.contatos.models import Municipio, UnidadeFederativa from sigi.apps.contatos.models import Municipio, UnidadeFederativa
from sigi.apps.eventos.models import Evento from sigi.apps.eventos.models import Evento
from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.parlamentares.models import Parlamentar
@ -22,7 +27,6 @@ class Projeto(models.Model):
OFICIO_HELP = editor_help( OFICIO_HELP = editor_help(
"texto_oficio", "texto_oficio",
[ [
("evento", Evento),
("casa", Orgao), ("casa", Orgao),
("presidente", Parlamentar), ("presidente", Parlamentar),
("contato", Funcionario), ("contato", Funcionario),
@ -35,7 +39,6 @@ class Projeto(models.Model):
MINUTA_HELP = editor_help( MINUTA_HELP = editor_help(
"modelo_minuta", "modelo_minuta",
[ [
("evento", Evento),
("casa", Orgao), ("casa", Orgao),
("presidente", Parlamentar), ("presidente", Parlamentar),
("contato", Funcionario), ("contato", Funcionario),
@ -71,6 +74,84 @@ class Projeto(models.Model):
class Meta: class Meta:
ordering = ("nome",) ordering = ("nome",)
def gerar_oficio(self, file_object, casa, presidente, contato, path):
texto = self.texto_oficio
template_string = (
'{% extends "convenios/oficio_padrao.html" %}'
"{% load pdf %}"
f"{{% block text_body %}}{texto}{{% endblock %}}"
)
context = Context(
{
"casa": casa,
"presidente": presidente,
"contato": contato,
"data": timezone.localdate(),
"doravante": casa.tipo.nome.split(" ")[0],
}
)
string = Template(template_string).render(context)
pdf = HTML(
string=string,
url_fetcher=django_url_fetcher,
encoding="utf-8",
base_url=path,
)
if file_object.closed:
file_object.open(mode="wb")
pdf.write_pdf(target=file_object)
file_object.flush()
def gerar_minuta(self, file_path, casa, presidente, contato):
doc = Document(self.modelo_minuta.path)
if casa.tipo.sigla == "CM":
ente = (
f"Município de {casa.municipio.nome}, "
f"{casa.municipio.uf.sigla}"
)
else:
ente = f"Estado de {casa.municipio.uf.nome}"
doc_context = Context(
{
"casa": casa,
"presidente": presidente,
"contato": contato,
"data": timezone.localdate(),
"ente": ente,
"doravante": casa.tipo.nome.split(" ")[0],
}
)
self.processa_paragrafos(doc.paragraphs, doc_context)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
self.processa_paragrafos(
cell.paragraphs,
doc_context,
)
doc.save(file_path)
def processa_paragrafos(self, paragrafos, context):
for paragrafo in paragrafos:
run_final = None
for run in paragrafo.runs:
if run_final is None:
run_final = run
else:
run_final.text += run.text
run.text = ""
if run_final.text.count("{{") != run_final.text.count("}}"):
continue
try:
run_final.text = Template(run_final.text).render(context)
run_final = None
except TemplateSyntaxError:
pass
class StatusConvenio(models.Model): class StatusConvenio(models.Model):
nome = models.CharField(max_length=100) nome = models.CharField(max_length=100)

0
sigi/apps/eventos/templates/eventos/oficio_padrao.html → sigi/apps/convenios/templates/convenios/oficio_padrao.html

109
sigi/apps/eventos/views.py

@ -10,7 +10,6 @@ from django.contrib.auth.decorators import login_required
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect, render, get_object_or_404 from django.shortcuts import redirect, render, get_object_or_404
from django.template import Template, Context from django.template import Template, Context
from django.template.exceptions import TemplateSyntaxError
from django.utils import timezone from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import ( from django.utils.translation import (
@ -22,7 +21,6 @@ from django.utils.translation import (
from django.urls import reverse from django.urls import reverse
from django_weasyprint.utils import django_url_fetcher from django_weasyprint.utils import django_url_fetcher
from django_weasyprint.views import WeasyTemplateResponse from django_weasyprint.views import WeasyTemplateResponse
from docx import Document
from weasyprint import HTML from weasyprint import HTML
from sigi.apps.casas.models import Funcionario, Orgao from sigi.apps.casas.models import Funcionario, Orgao
from sigi.apps.convenios.models import Projeto from sigi.apps.convenios.models import Projeto
@ -208,23 +206,6 @@ def evento(request, id):
def convida_casa(request, evento_id, casa_id): def convida_casa(request, evento_id, casa_id):
def processa_paragrafos(paragrafos, context):
for paragrafo in paragrafos:
run_final = None
for run in paragrafo.runs:
if run_final is None:
run_final = run
else:
run_final.text += run.text
run.text = ""
if run_final.text.count("{{") != run_final.text.count("}}"):
continue
try:
run_final.text = Template(run_final.text).render(context)
run_final = None
except TemplateSyntaxError:
pass
if not request.user.servidor: if not request.user.servidor:
messages.error( messages.error(
request, _("Você não é servidor, não pode registrar convites") request, _("Você não é servidor, não pode registrar convites")
@ -294,52 +275,39 @@ def convida_casa(request, evento_id, casa_id):
query_str = "" query_str = ""
projeto = get_object_or_404(Projeto, id=proj_id) projeto = get_object_or_404(Projeto, id=proj_id)
if projeto.texto_oficio: if projeto.texto_oficio:
oficio = gerar_anexo( oficio = Anexo(
evento=evento,
descricao=f"Ofício de solicitação de {projeto.sigla}",
)
sigla_casa = casa.sigla or "".join(
[
w[0].upper()
for w in casa.nome.replace("de", "").split()
]
)
oficio.arquivo.name = (
f"{Anexo.arquivo.field.upload_to}/"
f"oficio_{projeto.sigla}_{sigla_casa}.pdf"
)
projeto.gerar_oficio(
oficio.arquivo,
casa, casa,
presidente, presidente,
contato, contato,
path=request.build_absolute_uri("/"), request.build_absolute_uri("/"),
nome=f"Ofício de solicitação de {projeto.sigla}",
modelo="oficio_padrao.html",
texto=projeto.texto_oficio,
) )
oficio.evento = evento
oficio.save() oficio.save()
query_str += f"anexo_id={oficio.id}&" query_str += f"anexo_id={oficio.id}&"
if projeto.modelo_minuta: if projeto.modelo_minuta:
doc = Document(projeto.modelo_minuta.path)
if casa.tipo.sigla == "CM":
ente = (
f"Município de {casa.municipio.nome}, "
f"{casa.municipio.uf.sigla}"
)
else:
ente = f"Estado de {casa.municipio.uf.nome}"
doc_context = Context(
{
"evento": evento,
"casa": casa,
"presidente": presidente,
"contato": contato,
"data": timezone.localdate(),
"ente": ente,
"doravante": casa.tipo.nome.split(" ")[0],
}
)
processa_paragrafos(doc.paragraphs, doc_context)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
processa_paragrafos(
cell.paragraphs,
doc_context,
)
nome = f"Minuta de {projeto.sigla} da {casa.nome}"[:70] nome = f"Minuta de {projeto.sigla} da {casa.nome}"[:70]
minuta = Anexo(descricao=nome, evento=evento) minuta = Anexo(descricao=nome, evento=evento)
minuta.arquivo.name = ( minuta.arquivo.name = (
f"{Anexo.arquivo.field.upload_to}/{slugify(nome)}.docx" f"{Anexo.arquivo.field.upload_to}/"
f"minuta_{projeto.sigla}_{sigla_casa}.docx"
)
projeto.gerar_minuta(
minuta.arquivo.path, casa, presidente, contato
) )
doc.save(minuta.arquivo.path)
minuta.save() minuta.save()
query_str += f"anexo_id={minuta.id}" query_str += f"anexo_id={minuta.id}"
@ -393,39 +361,6 @@ def presidente_form(request, presidente_id):
) )
def gerar_anexo(casa, presidente, contato, path, modelo, nome, texto):
template_string = (
f'{{% extends "eventos/{modelo}" %}}'
"{% load pdf %}"
f"{{% block text_body %}}{texto}{{% endblock %}}"
)
context = Context(
{
"evento": evento,
"casa": casa,
"presidente": presidente,
"contato": contato,
"data": timezone.localdate(),
"doravante": casa.tipo.nome.split(" ")[0],
}
)
string = Template(template_string).render(context)
pdf = HTML(
string=string,
url_fetcher=django_url_fetcher,
encoding="utf-8",
base_url=path,
)
nome = (nome + f" da {casa.nome}")[:70]
anexo = Anexo(descricao=nome)
anexo.arquivo.name = slugify(nome) + ".pdf"
f = anexo.arquivo.open("wb")
pdf.write_pdf(target=f)
f.flush()
f.close()
return anexo
@login_required @login_required
def alocacao_equipe(request): def alocacao_equipe(request):
ano_pesquisa = int(request.GET.get("ano", timezone.localdate().year)) ano_pesquisa = int(request.GET.get("ano", timezone.localdate().year))

23
sigi/apps/home/templates/home/openmap.html

@ -34,6 +34,7 @@
{% block usertools %} {% block usertools %}
<div id="user-tools"> <div id="user-tools">
<a href="{% url 'admin:index' %}">{% trans "Entrar" %}</a> <a href="{% url 'admin:index' %}">{% trans "Entrar" %}</a>
<a href="{% url 'ocorrencias-seleciona-casa' %}">{% trans "Solicitar convênio" %}</a>
</div> </div>
{% endblock %} {% endblock %}
@ -49,9 +50,23 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="map"> <div id="aviso" class="modal">
<div class="modal-content">
<h5>{% trans "Solicite seu ACT ao Interlegis!" %}</h5>
<p>{% trans "Para utilizar os produtos e serviços do Interlgis / Senado Federal, é necessário formalizar um Acordo de Cooperação Técnica - ACT" %}</p>
<p>{% trans "O ACT é o instrumento público que permite que as Casas Legislativas utilizem, sem nenhum custo, os produtos e serviços do Programa Interlegis / Senado Federal, como o domínio .LEG.br, Portal Modelo, Sistema de Automação do Processo Legislativo (SAPL), e-mail legislativo e e-Democracia." %}</p>
<p>{% trans "Agora ficou muito mais fácil solicitar o ACT! Basta clicar no link 'Solicitar convênio', preencher os formulários e aguardar a conclusão do Processo Administrativo." %}</p>
<p>{% trans "Junte-se à maior comunidade brasileira do legislativo, modernize sua Casa e propicie que o cidadão acompanhe e participe das atividades polícitas de sua Câmara!" %}</p>
</div>
<div class="modal-footer">
<a href="{% url 'ocorrencias-seleciona-casa' %}" class="modal-close waves-effect btn-flat">{% trans "Quero conveniar minha Casa!" %}</a>
<a href="#!" class="modal-close waves-effect btn-flat right">{% trans "Close" %}</a>
</div>
</div>
<div id="map">
<!-- open street map --> <!-- open street map -->
</div> </div>
{% endblock %} {% endblock %}
{% block sidebar %}{% endblock %} {% block sidebar %}{% endblock %}
@ -65,6 +80,10 @@
var options = { color: 'blue', fillColor: 'red', fillOpacity: 0.4, radius: 500 }; var options = { color: 'blue', fillColor: 'red', fillOpacity: 0.4, radius: 500 };
var unfiltred_options = { color: 'red', fillColor: 'red', fillOpacity: 0, radius: 1000 }; var unfiltred_options = { color: 'red', fillColor: 'red', fillOpacity: 0, radius: 1000 };
$(document).ready(function () { $(document).ready(function () {
var aviso = M.Modal.init($('#aviso'));
aviso[0].open();
$("input[type=checkbox]").change(filtra); $("input[type=checkbox]").change(filtra);
mymap = L.map('map', { zoomSnap: 0.01 }).setView(map_center, 4.5); mymap = L.map('map', { zoomSnap: 0.01 }).setView(map_center, 4.5);

12
sigi/apps/ocorrencias/admin_urls.py

@ -0,0 +1,12 @@
from django.urls import path
from sigi.apps.ocorrencias import views
urlpatterns = [
path("painel/", views.painel_ocorrencias, name="painel-ocorrencias"),
path("convenio/painel/", views.painel_convenio, name="painel-convenio"),
path(
"convenio/painel/<int:ocorrencia_id>/",
views.painel_convenio,
name="painel-convenio",
),
]

111
sigi/apps/ocorrencias/forms.py

@ -1,14 +1,18 @@
from django import forms from django import forms
from sigi.apps.ocorrencias.models import Ocorrencia, Comentario, Anexo from django.contrib import admin
from sigi.apps.servidores.models import Servico from django.contrib.admin.widgets import AutocompleteSelect
from django.utils.encoding import force_str from django.core.validators import FileExtensionValidator
from django.utils.html import format_html
from django.forms.utils import flatatt from django.forms.utils import flatatt
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.encoding import force_str
from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ngettext, gettext as _
from material.admin.widgets import MaterialAdminTextareaWidget from material.admin.widgets import MaterialAdminTextareaWidget
from django.contrib.admin.widgets import AutocompleteSelect from sigi.apps.casas.models import Funcionario, Orgao
from django.contrib import admin from sigi.apps.ocorrencias.models import Ocorrencia, Comentario, Anexo
from sigi.apps.servidores.models import Servico
from sigi.apps.parlamentares.models import Parlamentar
class AjaxSelect(forms.TextInput): class AjaxSelect(forms.TextInput):
@ -96,3 +100,98 @@ class OcorrenciaForm(forms.ModelForm):
# attrs={'size':100} # attrs={'size':100}
# ), # ),
# } # }
class OcorrenciaChangeForm(forms.ModelForm):
class Meta:
model = Ocorrencia
fields = ["prioridade", "processo_sigad"]
class CasaForm(forms.ModelForm):
class Meta:
model = Orgao
fields = [
"cnpj",
"data_instalacao",
"horario_funcionamento",
"logradouro",
"bairro",
"cep",
"telefone_geral",
"email",
"pagina_web",
"foto",
"brasao",
]
class PresidenteForm(forms.ModelForm):
parlamentar = forms.ModelChoiceField(queryset=Parlamentar.objects.none())
class Meta:
model = Parlamentar
fields = [
"parlamentar",
"data_nascimento",
"cpf",
"identidade",
"telefones",
"email",
"redes_sociais",
]
widgets = {
"redes_sociais": MaterialAdminTextareaWidget,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields[
"parlamentar"
].queryset = self.instance.casa_legislativa.parlamentar_set.all()
class ContatoForm(forms.ModelForm):
class Meta:
model = Funcionario
fields = [
"nome",
"sexo",
"cpf",
"identidade",
"nota",
"email",
"redes_sociais",
]
widgets = {
"nota": MaterialAdminTextareaWidget,
"redes_sociais": MaterialAdminTextareaWidget,
}
class DocumentoForm(forms.ModelForm):
arquivo = forms.FileField(
label=_("Solicitação assinada"),
help_text=_("Utilize o formato PDF apenas"),
validators=[
FileExtensionValidator(
["pdf"], _("Somente arquivos em formato PDF"), "pdf_only"
)
],
)
class Meta:
model = Anexo
fields = ["arquivo"]
class ComentarioForm(forms.ModelForm):
class Meta:
model = Comentario
fields = ["descricao"]
class ComentarioInternoForm(forms.ModelForm):
class Meta:
model = Comentario
fields = ["descricao", "interno", "novo_status"]

60
sigi/apps/ocorrencias/migrations/0009_alter_comentario_options_categoria_projeto_and_more.py

@ -0,0 +1,60 @@
# Generated by Django 4.0.5 on 2022-07-07 21:51
import django.core.serializers.json
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('convenios', '0031_alter_projeto_modelo_minuta_and_more'),
('ocorrencias', '0008_remove_categoria_setor_responsavel'),
]
operations = [
migrations.AlterModelOptions(
name='comentario',
options={'ordering': ['-data_criacao'], 'verbose_name': 'comentário', 'verbose_name_plural': 'comentários'},
),
migrations.AddField(
model_name='categoria',
name='projeto',
field=models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('texto_oficio', ''), _negated=True), models.Q(('modelo_minuta', ''), _negated=True)), null=True, on_delete=django.db.models.deletion.PROTECT, to='convenios.projeto', verbose_name='projeto'),
),
migrations.AddField(
model_name='categoria',
name='tipo',
field=models.CharField(choices=[('C', 'Solicitação de convênio (ACT)'), ('O', 'Outras')], default='O', max_length=1, verbose_name='Tipo de solicitação'),
),
migrations.AddField(
model_name='comentario',
name='interno',
field=models.BooleanField(default=False, verbose_name='Comentário interno'),
),
migrations.AddField(
model_name='ocorrencia',
name='casa_brasao',
field=models.ImageField(blank=True, null=True, upload_to='ocorrencias/img/', verbose_name='brasão da casa'),
),
migrations.AddField(
model_name='ocorrencia',
name='casa_foto',
field=models.ImageField(blank=True, editable=False, null=True, upload_to='ocorrencias/img/', verbose_name='foto da Casa'),
),
migrations.AddField(
model_name='ocorrencia',
name='infos',
field=models.JSONField(blank=True, editable=False, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True, verbose_name='dados estruturados'),
),
migrations.AddField(
model_name='ocorrencia',
name='processo_sigad',
field=models.CharField(blank=True, max_length=20, verbose_name='Nº processo SIGAD'),
),
migrations.AddField(
model_name='tipocontato',
name='ind_site',
field=models.BooleanField(default=False, verbose_name='Contato pelo SIGI'),
),
]

78
sigi/apps/ocorrencias/models.py

@ -1,13 +1,37 @@
from collections import OrderedDict
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.core.serializers.json import DjangoJSONEncoder
from sigi.apps.convenios.models import Projeto
class Categoria(models.Model): class Categoria(models.Model):
TIPO_CHOICES = (
("C", _("Solicitação de convênio (ACT)")),
("O", _("Outras")),
)
nome = models.CharField(_("Categoria"), max_length=50) nome = models.CharField(_("Categoria"), max_length=50)
descricao = models.TextField(_("descrição"), blank=True, null=True) descricao = models.TextField(_("descrição"), blank=True, null=True)
tipo = models.CharField(
_("Tipo de solicitação"),
max_length=1,
choices=TIPO_CHOICES,
default="O",
)
projeto = models.ForeignKey(
Projeto,
verbose_name=_("projeto"),
blank=True,
null=True,
on_delete=models.PROTECT,
limit_choices_to=(
~models.Q(texto_oficio="") & ~models.Q(modelo_minuta="")
),
)
class Meta: class Meta:
verbose_name = _("Categoria") verbose_name = _("Categoria")
@ -17,9 +41,15 @@ class Categoria(models.Model):
def __str__(self): def __str__(self):
return self.nome return self.nome
def save(self, *args, **kwargs):
if self.tipo == "C":
Categoria.objects.filter(tipo="C").update(tipo="O")
return super().save(*args, **kwargs)
class TipoContato(models.Model): class TipoContato(models.Model):
descricao = models.CharField(_("Descrição"), max_length=50) descricao = models.CharField(_("Descrição"), max_length=50)
ind_site = models.BooleanField(_("Contato pelo SIGI"), default=False)
class Meta: class Meta:
verbose_name = _("Tipo de contato") verbose_name = _("Tipo de contato")
@ -28,6 +58,11 @@ class TipoContato(models.Model):
def __str__(self): def __str__(self):
return self.descricao return self.descricao
def save(self, *args, **kwargs):
if self.ind_site:
TipoContato.objects.filter(ind_site=True).update(ind_site=False)
return super().save(*args, **kwargs)
class Ocorrencia(models.Model): class Ocorrencia(models.Model):
STATUS_ABERTO = 1 STATUS_ABERTO = 1
@ -52,6 +87,13 @@ class Ocorrencia(models.Model):
(5, _("Baixíssimo")), (5, _("Baixíssimo")),
) )
INFO_KEYS = (
("casa_legislativa", _("Casa legislativa")),
("presidente", _("Presidente")),
("contato", _("Contato Interlegis")),
("documento", _("Documento assinado")),
)
casa_legislativa = models.ForeignKey( casa_legislativa = models.ForeignKey(
"casas.Orgao", "casas.Orgao",
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -93,6 +135,30 @@ class Ocorrencia(models.Model):
null=True, null=True,
help_text=_("Número do ticket no osTicket"), help_text=_("Número do ticket no osTicket"),
) )
processo_sigad = models.CharField(
_("Nº processo SIGAD"), max_length=20, blank=True
)
infos = models.JSONField(
_("dados estruturados"),
blank=True,
null=True,
encoder=DjangoJSONEncoder,
editable=False,
)
casa_foto = models.ImageField(
_("foto da Casa"),
upload_to="ocorrencias/img/",
blank=True,
null=True,
editable=False,
)
casa_brasao = models.ImageField(
_("brasão da casa"),
upload_to="ocorrencias/img/",
blank=True,
null=True,
editable=True,
)
class Meta: class Meta:
verbose_name = _("ocorrência") verbose_name = _("ocorrência")
@ -125,6 +191,12 @@ class Ocorrencia(models.Model):
def get_ticket_url(self): def get_ticket_url(self):
return mark_safe(settings.OSTICKET_URL % self.ticket) return mark_safe(settings.OSTICKET_URL % self.ticket)
def get_infos_details(self):
infos = self.infos or {}
return OrderedDict(
{key: [key in infos, label] for key, label in Ocorrencia.INFO_KEYS}
)
class Comentario(models.Model): class Comentario(models.Model):
ocorrencia = models.ForeignKey( ocorrencia = models.ForeignKey(
@ -148,6 +220,12 @@ class Comentario(models.Model):
blank=True, blank=True,
null=True, null=True,
) )
interno = models.BooleanField(_("Comentário interno"), default=False)
class Meta:
verbose_name = _("comentário")
verbose_name_plural = _("comentários")
ordering = ["-data_criacao"]
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.novo_status and (self.novo_status != self.ocorrencia.status): if self.novo_status and (self.novo_status != self.ocorrencia.status):

69
sigi/apps/ocorrencias/static/ocorrencias/css/ocorrencia.css

@ -0,0 +1,69 @@
#content {
display: block;
}
#container.indent {
padding-left: unset;
}
.main-content {
margin-top: 24px;
}
.card .card-content p {
margin: 0 0 1em 0;
}
.search-result {
padding: 3em;
}
.search-result-item {
display: list-item;
list-style-type: none;
padding: 3px 3px 3px 20px;
font-weight: bold;
}
.tab-panel {
background-color: #fff;
height: 100%;
}
.tab-content {
padding: 10px 24px;
height: 100%;
overflow-y: auto;
scrollbar-width: thin;
}
.tab-content::-webkit-scrollbar {
width: 3px;
background-color: #fff;
}
.tab-content::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 2px #fff;
background-color: #fff;
border-radius: 10px;
}
.tab-content::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 2px var(--main-hover-color);
background-color: var(--main-hover-color);
}
span.title {
color: var(--main-bg-color);
border-color: var(--main-bg-color);
display: block;
line-height: 32px;
margin-bottom: 8px;
font-size: 24px;
font-weight: 300;
}
.dropdown-content.select-dropdown {
top: 0px !important;
}

53
sigi/apps/ocorrencias/templates/ocorrencias/convenio/atualiza_casa.html

@ -0,0 +1,53 @@
{% extends "ocorrencias/convenio/base_convenio.html" %}
{% load static i18n %}
{% block content %}
<div class="main-content">
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Solicitar convênio" %}</span>
{% blocktrans %}
<p>Para que uma Casa Legislativa possa utilizar, gratuitamente, os
serviços do Interlegis / Senado Federal, é necessário formalizar
um convênio, na forma de um Acordo de Cooperação Técnica (ACT),
na forma da lei <a href="http://www.planalto.gov.br/ccivil_03/_ato2019-2022/2021/lei/L14133.htm">Lei Nº 14.133/2021</a>
e da lei <a href="http://www.planalto.gov.br/ccivil_03/leis/l8666cons.htm">Lei Nº 8.666/1993</a>.
</p>
<p>Para solicitar o ACT, serão necessárias as seguintes informações:</p>
<ul class="browser-default">
<li>Dados cadastrais da Casa Legislativa, como CNPJ, endereço, e-mail, telefone.</li>
<li>Dados cadastrais do Presidente, como nome, CPF, identidade, e-mail, telefone, redes sociais.</li>
<li>Designação de um servidor como Contato Interlegis.</li>
</ul>
{% endblocktrans %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Identifique sua Casa Legislativa" %}</span>
<p>{% trans "Informe o nome do município ou Estado da sua Casa Legislativa" %}:</p>
<div class="input-field">
<i class="material-icons prefix">search</i>
<input type="text" class="search-text" placeholder="{% trans "Procurar" %}" aria-label="{% trans "Procurar" %}" data-source="{% url 'openmapsearch' %}" data-param="q">
<div class="search-result hide" data-item-click="seleciona_casa"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block footer %}
{{ block.super }}
<script>
function seleciona_casa(data) {
$(location).attr("href", `{% url 'ocorrencias-seleciona-casa' %}?casa_id=${data.id}`);
}
</script>
{% endblock %}

48
sigi/apps/ocorrencias/templates/ocorrencias/convenio/base_convenio.html

@ -0,0 +1,48 @@
{% extends "admin/index.html" %}
{% load static i18n %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.0/themes/smoothness/jquery-ui.css" />
<link rel="stylesheet" href="{% static "admin/css/changelists.css" %}" type="text/css" />
<link rel="stylesheet" href="{% static 'ocorrencias/css/ocorrencia.css' %}" type="text/css" />
{% endblock %}
{% block extrahead %}
{{ block.super }}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/xhtml; charset=UTF-8" />
<meta name="robots" content="NONE,NOARCHIVE" />
<script type="text/javascript">
//<![CDATA[
window.__admin_media_prefix__ = "{% filter escapejs %}{% static "admin / " %}{% endfilter %}";
//]]>
</script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js" integrity="sha256-eTyxS0rkjpLEo16uXTS0uVCS4815lc40K2iVpWDvdSY=" crossorigin="anonymous"></script>
<script type="text/javascript" src="{% static "admin/js/core.js" %}"></script>
{% endblock %}
{% block usertools %}
<div id="user-tools">
<a href="/">{% trans "Início" %}</a>
<a href="{% url 'admin:index' %}">{% trans "Entrar" %}</a>
</div>
{% endblock %}
{% block side_nav %}
{% endblock %}
{% block content %}
<div id="content">
<!-- content_page -->
</div>
{% endblock %}
{% block sidebar %}{% endblock %}
{% block footer %}
{{ block.super }}
<script type="text/javascript" src="{% static 'js/search.js' %}"></script>
{% endblock %}

216
sigi/apps/ocorrencias/templates/ocorrencias/convenio/ocorrencia.html

@ -0,0 +1,216 @@
{% extends "ocorrencias/convenio/base_convenio.html" %}
{% load static i18n %}
{% block content %}
<div class="main-content">
<div class="row">
<div class="col s12">
<span class="title">{{ ocorrencia.assunto }}</span>
<p>{% blocktrans with name=ocorrencia.casa_legislativa.nome %}A solicitação da {{ name }} está na nossa base de dados {% endblocktrans %}</p>
</div>
</div>
<div class="row">
<div class="col s12">
<ul class="tabs">
{% if 'casa_legislativa' not in infos.aplicados %}<li class="tab col"><a href="#casa"{% if 'casa_legislativa' not in infos %} class="active"{% endif %}>{% trans "Casa legislativa" %}</a></li>{% endif %}
{% if 'presidente' not in infos.aplicados %}<li class="tab col"><a href="#presidente"{% if 'presidente' not in infos %} class="active"{% endif %}>{% trans "Presidente" %}</a></li>{% endif %}
{% if 'contato' not in infos.aplicados %}<li class="tab col"><a href="#contato"{% if 'contato' not in infos %} class="active"{% endif %}>{% trans "Contato Interlegis" %}</a></li>{% endif %}
<li class="tab col"><a href="#documentos"{% if 'documento' not in infos %} class="active"{% endif %}>{% trans "Documentos" %}</a></li>
<li class="tab col"><a href="#resumo"{% if 'documento' in infos %} class="active"{% endif %}>{% trans "Resumo" %}</a></li>
</ul>
</div>
{% if 'casa_legislativa' not in infos.aplicados %}
<div id="casa" class="col s12">
<div class="tab-panel">
<div class="tab-content">
<form action="" method="post" name="casa_legislativa" enctype="multipart/form-data">
{% csrf_token %}
<div class="card">
<div class="card-content">
<span class="card-title">
{% trans "Atualizar as informações cadastrais da Casa" %}
</span>
{{ casa_form }}
<div class="card-action">
<button class="btn waves-effect waves-light" type="submit" name="salva_casa">
{% trans "Save" %}
<i class="material-icons right">send</i>
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endif %}
{% if 'presidente' not in infos.aplicados %}
<div id="presidente" class="col s12">
<div class="tab-panel">
<div class="tab-content">
<form action="" method="post" name="presidente">
{% csrf_token %}
<div class="card">
<div class="card-content">
<span class="card-title">
{% trans "Selecionar e atualizar o presidente" %}
</span>
{{ presidente_form }}
<div class="card-action">
<button class="btn waves-effect waves-light" type="submit" name="salva_presidente">
{% trans "Save" %}
<i class="material-icons right">send</i>
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endif %}
{% if 'contato' not in infos.aplicados %}
<div id="contato" class="col s12 tabpanel">
<div class="tab-panel">
<div class="tab-content">
<form action="" method="post" name="presidente">
{% csrf_token %}
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Contato Interlegis" %}</span>
<p>Designe um servidor da Casa Legislativa para ser o contato técnico junto ao Interlegis.</p>
<p>Este servidor será o responsável por acompanhar a formalização do ACT, prestar informações e esclarecimentos, e solicitar a instalação de produtos e serviços ao Interlegis.</p>
{{ contato_form }}
<div class="card-action">
<button class="btn waves-effect waves-light" type="submit" name="salva_contato">
{% trans "Save" %}
<i class="material-icons right">send</i>
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endif %}
<div id="documentos" class="col s12 tabpanel">
<div class="tab-panel">
<div class="tab-content">
<form action="" method="post" name="presidente" enctype="multipart/form-data">
{% csrf_token %}
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Documentos de formalização" %}</span>
<p>Os documentos para formalização do convênio foram gerados e estão disponíveis abaixo.</p>
<p>Você precisa realizar os seguintes passos para que possamos dar prosseguimento ao processo:</p>
<ul>
<li>Baixe (dowload) o ofício de solicitação do convênio.</li>
<li>Providencie que o ofício de solicitação seja assinado pelo Presidente da Casa. Se for possível, assine o PDF com uma assinatura digital. Se o Presidente não possui uma assinatura digital válida, pode-se imprimir o documento, assinar com caneta azul, e escanear o documento assinado.</li>
<li>Suba (upload) o ofício assinado no campo Arquivo, abaixo, em formato PDF</li>
<li>O documento Minuta de ACT é a minuta do convênio que tramitará no Senado e está disponível para consulta e avaliação da consultoria jurídica da sua Casa Legislativa. Após assinatura do ACt pela diretoria do Senado, a cópia definitiva será enviada à sua Casa para colher também a assinatura do Presidente.</p>
</ul>
<p>Assim que recebermos o ofício devidamente assinado, iniciaremos o procedimento burocrático para formalização do convênio. Você poderá acompanhar a evolução deste processo nesta tela, na aba RESUMO</p>
<table class="striped">
<thead>
<caption>Arquivos do processo</caption>
<tr>
<th>Descrição</th><th>Arquivo</th>
</tr>
</thead>
<tbody>
{% for anexo in ocorrencia.anexo_set.all %}
<tr>
<td>{{ anexo.descricao }}</td>
<td><a href="{{ anexo.arquivo.url }}">{{ anexo.arquivo.name }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{{ documento_form }}
<div class="card-action">
<button class="btn waves-effect waves-light" type="submit" name="salva_documento">
{% trans "Save" %}
<i class="material-icons right">send</i>
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="resumo" class="col s12 tabpanel">
<div class="tab-panel">
<div class="tab-content">
<form action="" method="post" name="comentario">
{% csrf_token %}
<div class="card">
<div class="card-content">
<table>
<tr>
<th>{% trans "Casa legislativa" %}</th><td>{{ ocorrencia.casa_legislativa.nome }}</td>
<th>{% trans "Descrição" %}</th><td>{{ ocorrencia.descricao }}</td>
<th>{% trans "Solicitado em" %}</th><td>{{ ocorrencia.data_criacao|date:"DATE_FORMAT" }}</td>
</tr>
<tr>
<th>{% trans "Situação" %}</th><td>{{ ocorrencia.get_status_display }}</td>
<th>{% trans "Prioridade" %}</th><td>{{ ocorrencia.get_prioridade_display }}</td>
<th>{% trans "Última atualização" %}</th><td>{{ ocorrencia.data_modificacao|date:"DATE_FORMAT" }}</td>
</tr>
</table>
<table>
<thead>
<caption><span class="card-title">{% trans "Progresso do processo" %}</span></caption>
<tr><th>{% trans "Data" %}</th><th>{% trans "Descrição" %}</th><th>{% trans "Novo status" %}</th><th>{% trans "Registrado por" %}</th></tr>
</thead>
<tbody>
{% for comentario in ocorrencia.comentarios.all %}
{% if not comentario.interno %}
<tr>
<td>{{ comentario.data_criacao|date:"SHORT_DATE_FORMAT" }}</td>
<td>{{ comentario.descricao }}</td>
<td>{{ comentario.get_novo_status_display|default:"-" }}</td>
<td>{{ comentario.usuario }}</td>
</tr>
{% endif %}
{% empty %}
<tr><td colspan="4">{% trans "Aguardando andamento no Senado" %}</td></tr>
{% endfor %}
</tbody>
</table>
{{ comentario_form }}
<div class="card-action">
<button class="btn waves-effect waves-light" type="submit" name="salva_comentario">
{% trans "Comentar" %}
<i class="material-icons left">comment</i>
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block footer %}
{{ block.super }}
<script>
$(document).ready(function() {
M.Tabs.init($('.tabs'), {swipeable: true});
$("select#id_presidente-parlamentar").on("change", function() {
$.get("{% url 'parlamentar-data' %}", `id=${this.value}`, function(data) {
for(var campo of Object.keys(data[0].fields)) {
$(`#id_presidente-${campo}`).val(data[0].fields[campo]);
}
})
})
});
function seleciona_casa(data) {
$(location).attr("href", `{% url 'ocorrencias-seleciona-casa' %}?casa_id=${data.id}`);
}
</script>
{% endblock %}

40
sigi/apps/ocorrencias/templates/ocorrencias/convenio/painel_convenio.html

@ -0,0 +1,40 @@
{% extends "admin/base_block.html" %}
{% load i18n %}
{% block content %}
<h4>{% trans "Solicitações de convenio" %}</h4>
<table class="striped">
<thead>
<tr>
<th>{% trans "Data abertura" %}</th>
<th>{% trans "Casa solicitante" %}</th>
<th>{% trans "Assunto" %}</th>
<th>{% trans "Comentários" %}
<th>{% trans "Situação" %}</th>
<th>{% trans "Detalhes" %}</th>
</tr>
</thead>
<tbody>
{% for ocorrencia in ocorrencias %}
<tr>
<td>{{ ocorrencia.data_criacao|date:"SHORT_DATE_FORMAT" }}</td>
<td>{{ ocorrencia.casa_legislativa }}</td>
<td>{{ ocorrencia.assunto }}</td>
<td><span class="badge">{{ ocorrencia.comentarios.count }}</span></td>
<td>
<ul>
{% for key, value in ocorrencia.get_infos_details.items %}
<li>
<i class="material-icons {% if value.0 %}green-text{% else %}red-text{% endif %}">{% if value.0 %}done{% else %}clear{% endif %}</i>
{{ value.1 }}
</li>
{% endfor %}
</ul>
</td>
<td>
<a href="{% url 'painel-convenio' ocorrencia.id %}"><i class="material-icons">details</i></a>
</td>
{% endfor %}
</tbody>
</table>
{% endblock %}

245
sigi/apps/ocorrencias/templates/ocorrencias/convenio/painel_convenio_detail.html

@ -0,0 +1,245 @@
{% extends "admin/base_block.html" %}
{% load i18n model_fields %}
{% block content %}
<div class="row">
<div class="col s12">
<form action="" method="post" name="ocorrencia">
{% csrf_token %}
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Resumo da ocorrência" %}</span>
<table>
<tbody>
{% for campo in campos_ocorrencia %}
<tr>
<th>{{ ocorrencia|verbose_name:campo|capfirst }}</th>
<td>{{ ocorrencia|field_value:campo|default:"-" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ ocorrencia_form }}
</div>
<div class="card-action">
<button class="btn waves-effect waves-light" type="submit" name="salva_ocorrencia">
{% trans "Salvar" %}
<i class="material-icons left">save</i>
</button>
<a class="btn waves-effect waves-light white-text" href="{% url 'painel-convenio' %}">{% trans "Voltar ao painel" %}</a>
</div>
</div>
</form>
</div>
</div>
{% if infos.aplicados %}
{% if 'casa_legislativa' in infos.aplicados and 'presidente' in infos.aplicados and 'contato' in infos.aplicados %}
<p class=""> {% trans "Todas as alterações foram aplicadas" %}</p>
{% else %}
<ul>
{% if 'casa_legislativa' in infos.aplicados %}
<li>As alterações nos dados da Casa Legislativa foram aplicadas</li>
{% endif %}
{% if 'presidente' in infos.aplicados %}
<li>As alterações nos dados do Presidente foram aplicadas</li>
{% endif %}
{% if 'contato' in infos.aplicados %}
<li>As alterações nos dados do Contato Interlegis foram aplicadas</li>
{% endif %}
</ul>
{% endif %}
{% endif %}
{% if 'casa_legislativa' not in infos.aplicados %}
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Casa legislativa" %}</span>
<table class="striped">
<thead>
<tr>
<th>{% trans "Campo" %}</th>
<th>{% trans "Valor original" %}</th>
<th>{% trans "Valor alterado" %}</th>
</tr>
</thead>
<tbody>
{% for key, value in infos.casa_legislativa.items %}
<tr>
<th>{{ casa|verbose_name:key|capfirst }}</th>
<td>{{ casa|field_value:key|default:"-" }}</td>
<td>{{ value|default:"-" }}</td>
</tr>
{% endfor %}
<tr>
<th>Foto</th>
<td>{% if casa.foto %}<img src="{{ casa.foto.url }}" width=120 height=120/>{% else %}{% trans "Sem imagem" %}{% endif %}</td>
<td>{% if ocorrencia.casa_foto %}<img src="{{ ocorrencia.casa_foto.url }}" width=120 height=120/>{% else %}{% trans "Sem alteração" %}{% endif %}
</tr>
<tr>
<th>Brasão</th>
<td>{% if casa.brasao %}<img src="{{ casa.brasao.url }}" width=120 height=120/>{% else %}{% trans "Sem imagem" %}{% endif %}</td>
<td>{% if ocorrencia.casa_brasao %}<img src="{{ ocorrencia.casa_brasao.url }}" width=120 height=120/>{% else %}{% trans "Sem alteração" %}{% endif %}
</tr>
</tbody>
</table>
</div>
<div class="card-action">
<a class="btn waves-effect waves-light white-text" href="?apply=casa">{% trans "Aplicar mudanças na Casa Legislativa" %}</a>
</div>
</div>
</div>
</div>
{% endif %}
{% if 'presidente' not in infos.aplicados %}
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Presidente" %}</span>
{% if casa.presidente %}
<p>{% trans "Presidente anterior" %}: {{ casa.presidente.nome_completo }}</p>
{% else %}
<p>{% trans "A Casa não tinha Presidente definido anteriormente" %}</p>
{% endif %}
<p>{% trans "Presidente selecionado" %}: {{ novo_presidente.nome_completo }}</p>
<table class="striped">
<thead>
<tr>
<th>{% trans "Campo" %}</th>
<th>{% trans "Valor original" %}</th>
<th>{% trans "Valor alterado" %}</th>
</tr>
</thead>
<tbody>
{% for key, value in infos.presidente.items %}
<tr>
<th>{{ novo_presidente|verbose_name:key|capfirst }}</th>
<td>{{ novo_presidente|field_value:key|default:"-" }}</td>
<td>{{ value|default:"-" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-action">
<a class="btn waves-effect waves-light white-text" href="?apply=presidente">{% trans "Aplicar mudanças no Parlamentar" %}</a>
</div>
</div>
</div>
</div>
{% endif %}
{% if 'contato' not in infos.aplicados %}
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Contato Interlegis" %}</span>
<table class="striped">
<thead>
<tr>
<th>{% trans "Campo" %}</th>
<th>{% trans "Valor original" %}</th>
<th>{% trans "Valor alterado" %}</th>
</tr>
</thead>
<tbody>
{% for key, value in infos.contato.items %}
<tr>
<th>{{ contato|verbose_name:key|capfirst }}</th>
<td>{{ contato|field_value:key|default:"-" }}</td>
<td>{{ value|default:"-" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-action">
<a class="btn waves-effect waves-light white-text" href="?apply=contato">{% trans "Aplicar mudanças no Contato Interlegis" %}</a>
</div>
</div>
</div>
</div>
{% endif %}
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Anexos (documentos)" %}</span>
<table class="striped">
<thead>
<tr>
<th>{% trans "Data de inclusão" %}</th>
<th>{% trans "Descrição" %}</th>
<th>{% trans "Arquivo" %}</th>
</tr>
</thead>
<tbody>
{% for anexo in ocorrencia.anexo_set.all %}
<tr>
<th>{{ anexo.data_pub|date:"SHORT_DATE_FORMAT" }}</th>
<td>{{ anexo.descricao|default:"-" }}</td>
<td><a href="{{ anexo.arquivo.url }}">{{ anexo.arquivo.name }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Comentários" %}</span>
<table class="striped">
<thead>
<tr>
<th>{% trans "Data" %}</th>
<th>{% trans "Descrição" %}</th>
<th>{% trans "Servidor" %}</th>
<th>{% trans "Novo status" %}</th>
<th>{% trans "Visibilidade" %}</th>
</tr>
</thead>
<tbody>
{% for comentario in ocorrencia.comentarios.all %}
<tr>
<th>{{ comentario.data_criacao|date:"SHORT_DATETIME_FORMAT" }}</th>
<td>{{ comentario.descricao|default:"-" }}</td>
<td>{{ comentario.usuario.nome_completo|default:"-" }}</td>
<td>{{ comentario.get_novo_status_display|default:"-" }}</td>
<td>
{% if comentario.interno %}
<i class="material-icons green-text">visibility_off</i> Interno
{% else %}
<i class="material-icons red-text">visibility</i> Público
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-action">
<form action="" method="post" name="comentario">
{% csrf_token %}
{{ comentario_form }}
<button class="btn waves-effect waves-light" type="submit" name="salva_comentario">
{% trans "Comentar" %}
<i class="material-icons left">comment</i>
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

53
sigi/apps/ocorrencias/templates/ocorrencias/convenio/seleciona_casa.html

@ -0,0 +1,53 @@
{% extends "ocorrencias/convenio/base_convenio.html" %}
{% load static i18n %}
{% block content %}
<div class="main-content">
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Solicitar convênio" %}</span>
{% blocktrans %}
<p>Para que uma Casa Legislativa possa utilizar, gratuitamente, os
serviços do Interlegis / Senado Federal, é necessário formalizar
um convênio, na forma de um Acordo de Cooperação Técnica (ACT),
na forma da lei <a href="http://www.planalto.gov.br/ccivil_03/_ato2019-2022/2021/lei/L14133.htm">Lei Nº 14.133/2021</a>
e da lei <a href="http://www.planalto.gov.br/ccivil_03/leis/l8666cons.htm">Lei Nº 8.666/1993</a>.
</p>
<p>Para solicitar o ACT, serão necessárias as seguintes informações:</p>
<ul class="browser-default">
<li>Dados cadastrais da Casa Legislativa, como CNPJ, endereço, e-mail, telefone.</li>
<li>Dados cadastrais do Presidente, como nome, CPF, identidade, e-mail, telefone, redes sociais.</li>
<li>Designação de um servidor como Contato Interlegis.</li>
</ul>
{% endblocktrans %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Identifique sua Casa Legislativa" %}</span>
<p>{% trans "Informe o nome do município ou Estado da sua Casa Legislativa" %}:</p>
<div class="input-field">
<i class="material-icons prefix">search</i>
<input type="text" class="search-text" placeholder="{% trans "Procurar" %}" aria-label="{% trans "Procurar" %}" data-source="{% url 'openmapsearch' %}" data-param="q">
<div class="search-result hide" data-item-click="seleciona_casa"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block footer %}
{{ block.super }}
<script>
function seleciona_casa(data) {
$(location).attr("href", `{% url 'ocorrencias-seleciona-casa' %}?casa_id=${data.id}`);
}
</script>
{% endblock %}

9
sigi/apps/ocorrencias/urls.py

@ -1,8 +1,13 @@
from django.urls import path from django.urls import path
from sigi.apps.ocorrencias.views import painel_ocorrencias from sigi.apps.ocorrencias import views
urlpatterns = [ urlpatterns = [
path("painel/", painel_ocorrencias, name="painel-ocorrencias"), path("convenio/", views.seleciona_casa, name="ocorrencias-seleciona-casa"),
path(
"ocorrencia/<int:ocorrencia_id>",
views.ocorrencia,
name="ocorrencias_ocorrencia",
),
] ]

380
sigi/apps/ocorrencias/views.py

@ -1,21 +1,39 @@
# -*- coding: utf-8 -*- import copy
from django.http import JsonResponse, Http404
from django.db.models import Q, Count from django.db.models import Q, Count
from django.utils.translation import ngettext, gettext as _ from django.contrib import messages
from django.shortcuts import get_object_or_404, render, HttpResponse from django.contrib.admin.sites import site
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST from django.http import JsonResponse, Http404
from django.shortcuts import get_object_or_404, redirect, render, HttpResponse
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.template import RequestContext from django.template import RequestContext
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ngettext, gettext as _
from django.views.decorators.http import require_POST
from sigi.apps import ocorrencias
from sigi.apps.parlamentares.models import Parlamentar
from sigi.apps.utils import to_ascii from sigi.apps.utils import to_ascii
from sigi.apps.casas.models import Orgao from sigi.apps.casas.models import Funcionario, Orgao
from sigi.apps.contatos.models import UnidadeFederativa from sigi.apps.contatos.models import UnidadeFederativa
from sigi.apps.servidores.models import Servidor, Servico from sigi.apps.servidores.models import Servidor, Servico
from sigi.apps.ocorrencias.models import Ocorrencia, Anexo from sigi.apps.ocorrencias.models import (
Categoria,
Comentario,
Ocorrencia,
Anexo,
TipoContato,
)
from sigi.apps.ocorrencias.forms import ( from sigi.apps.ocorrencias.forms import (
AnexoForm, AnexoForm,
ComentarioForm, ComentarioForm,
ComentarioInternoForm,
ContatoForm,
DocumentoForm,
OcorrenciaChangeForm,
OcorrenciaForm, OcorrenciaForm,
CasaForm,
PresidenteForm,
) )
from django.utils.html import escape from django.utils.html import escape
@ -327,3 +345,351 @@ def inclui_ocorrencia(request):
) )
return JsonResponse(data) return JsonResponse(data)
def seleciona_casa(request):
context = site.each_context(request) or {}
casa_id = request.GET.get("casa_id", None)
if casa_id:
casa = get_object_or_404(Orgao, pk=casa_id)
categoria = get_object_or_404(Categoria, tipo="C")
tipo_contato = get_object_or_404(TipoContato, ind_site=True)
if request.user.is_anonymous or not (
request.user.is_staff and request.user.servidor is not None
):
servidor = get_object_or_404(Servidor, sigi=True)
else:
servidor = request.user.servidor
if casa.convenio_set.filter(projeto__sigla="ACT").exists():
# TODO: Fluxo para Casa já conveniada
messages.info(request, "Já conveniada")
return redirect("/")
ocorrencia = casa.ocorrencia_set.filter(
categoria=categoria,
status__in=[Ocorrencia.STATUS_ABERTO, Ocorrencia.STATUS_REABERTO],
).first()
if not ocorrencia:
ocorrencia = Ocorrencia(
casa_legislativa=casa,
categoria=categoria,
tipo_contato=tipo_contato,
assunto=_("Solicitação de Adesão ao Programa Interlegis"),
descricao=_(
f"A {casa.nome} solicita adesão ao Programa Interlegis"
),
servidor_registro=servidor,
)
ocorrencia.save()
return redirect(reverse("ocorrencias_ocorrencia", args=[ocorrencia.id]))
return render(request, "ocorrencias/convenio/seleciona_casa.html", context)
def bound_copy(instance, bound_data, removes=None):
data = bound_data.copy()
if removes:
for remove_key in removes:
data.pop(remove_key)
for key, value in data.items():
setattr(instance, key, value)
def ocorrencia(request, ocorrencia_id):
ANEXO_DESCRICAO = _("Solicitação de convenio assinada")
def set_instances():
casa.foto = ocorrencia.casa_foto if ocorrencia.casa_foto else casa.foto
casa.brasao = (
ocorrencia.casa_brasao if ocorrencia.casa_brasao else casa.brasao
)
if "casa_legislativa" in infos:
bound_copy(casa, infos["casa_legislativa"])
if "presidente" in infos:
bound_copy(presidente, infos["presidente"], ["id"])
if "contato" in infos:
bound_copy(contato, infos["contato"])
ocorrencia = get_object_or_404(Ocorrencia, pk=ocorrencia_id)
infos = ocorrencia.infos or {}
casa = ocorrencia.casa_legislativa
presidente = casa.presidente or (
Parlamentar.objects.get(id=infos["presidente"]["id"])
if "presidente" in infos
else Parlamentar(casa_legislativa=casa)
)
contato = casa.contato_interlegis or Funcionario(
casa_legislativa=casa, setor="contato_interlegis"
)
documento = (
ocorrencia.anexo_set.get(id=infos["documento"]["id"])
if "documento" in infos
else Anexo(ocorrencia=ocorrencia, descricao=ANEXO_DESCRICAO)
)
set_instances()
contato_form = ContatoForm(instance=contato, prefix="contato")
documento_form = DocumentoForm(instance=documento)
if request.method == "POST":
if "salva_casa" in request.POST:
casa_form = CasaForm(
request.POST, request.FILES, instance=casa, prefix="casa"
)
if casa_form.is_valid():
cleaned = casa_form.cleaned_data.copy()
foto = cleaned.pop("foto")
brasao = cleaned.pop("brasao")
if "foto" in casa_form.changed_data:
if foto == False:
if ocorrencia.casa_foto:
ocorrencia.casa_foto.delete(save=True)
else:
ocorrencia.casa_foto = foto
if "brasao" in casa_form.changed_data:
if brasao == False:
if ocorrencia.casa_brasao:
ocorrencia.casa_brasao.delete(save=True)
else:
ocorrencia.casa_brasao = brasao
infos["casa_legislativa"] = cleaned
ocorrencia.infos = infos
ocorrencia.save()
else:
messages.error(
request,
_("Corrija os erros no cadastro da Casa Legislativa"),
)
elif "salva_presidente" in request.POST:
presidente_form = PresidenteForm(
request.POST, instance=presidente, prefix="presidente"
)
if presidente_form.is_valid():
cleaned = presidente_form.cleaned_data.copy()
presidente = cleaned.pop("parlamentar")
cleaned["id"] = presidente.id
infos["presidente"] = cleaned
ocorrencia.infos = infos
ocorrencia.save()
elif "salva_contato" in request.POST:
contato_form = ContatoForm(
request.POST, instance=contato, prefix="contato"
)
if contato_form.is_valid():
infos["contato"] = contato_form.cleaned_data.copy()
ocorrencia.infos = infos
ocorrencia.save()
elif "salva_documento" in request.POST:
documento_form = DocumentoForm(
request.POST, request.FILES, instance=documento
)
if documento_form.is_valid():
documento = documento_form.save()
infos["documento"] = {"id": documento.id}
ocorrencia.infos = infos
ocorrencia.save()
elif "salva_comentario" in request.POST:
comentario_form = ComentarioForm(request.POST)
if comentario_form.is_valid():
comentario = comentario_form.save(commit=False)
comentario.ocorrencia = ocorrencia
comentario.usuario = ocorrencia.servidor_registro
if ocorrencia.status not in [
ocorrencia.STATUS_ABERTO,
ocorrencia.STATUS_REABERTO,
]:
comentario.novo_status = ocorrencia.STATUS_REABERTO
comentario.save()
set_instances()
if {"casa_legislativa", "presidente", "contato"}.issubset(
infos
) and "documento" not in infos:
ocorrencia.anexo_set.all().delete()
documento = Anexo(ocorrencia=ocorrencia, descricao=ANEXO_DESCRICAO)
documento_form = DocumentoForm(instance=documento)
projeto = ocorrencia.categoria.projeto
oficio = Anexo(
ocorrencia=ocorrencia,
descricao=f"Solicitação de {projeto.sigla}",
)
oficio.arquivo.name = (
f"{Anexo.arquivo.field.upload_to}/"
f"solicitacao_{projeto.sigla}_{casa.get_sigla()}.pdf"
)
projeto.gerar_oficio(
oficio.arquivo,
casa,
presidente,
contato,
request.build_absolute_uri("/"),
)
oficio.save()
minuta = Anexo(
ocorrencia=ocorrencia, descricao=f"Minuta de {projeto.sigla}"
)
minuta.arquivo.name = (
f"{Anexo.arquivo.field.upload_to}/"
f"minuta_{projeto.sigla}_{casa.get_sigla()}.docx"
)
projeto.gerar_minuta(minuta.arquivo.path, casa, presidente, contato)
minuta.save()
if presidente.id:
bounds = {
f"presidente-{key}": value
for key, value in infos["presidente"].items()
if key != "id"
}
bounds["presidente-parlamentar"] = presidente
presidente_form = PresidenteForm(
bounds, instance=presidente, prefix="presidente"
)
else:
presidente_form = PresidenteForm(
instance=presidente, prefix="presidente"
)
context = site.each_context(request) or {}
context.update(
{
"ocorrencia": ocorrencia,
"casa_form": CasaForm(instance=casa, prefix="casa"),
"presidente_form": presidente_form,
"contato_form": contato_form,
"documento_form": documento_form,
"comentario_form": ComentarioForm(),
"infos": infos,
}
)
return render(request, "ocorrencias/convenio/ocorrencia.html", context)
@login_required
def painel_convenio(request, ocorrencia_id=None):
context = site.each_context(request) or {}
if ocorrencia_id:
ocorrencia = get_object_or_404(Ocorrencia, id=ocorrencia_id)
if request.method == "POST":
if "salva_ocorrencia" in request.POST:
ocorrencia_form = OcorrenciaChangeForm(
request.POST, instance=ocorrencia
)
if ocorrencia_form.is_valid():
ocorrencia = ocorrencia_form.save()
if "processo_sigad" in ocorrencia_form.changed_data:
Comentario(
ocorrencia=ocorrencia,
descricao=_(
f"criado processo administrativo nº {ocorrencia.processo_sigad}"
),
usuario=request.user.servidor,
).save()
if "salva_comentario" in request.POST:
comentario_form = ComentarioInternoForm(request.POST)
if comentario_form.is_valid():
comentario = comentario_form.save(commit=False)
comentario.ocorrencia = ocorrencia
comentario.usuario = request.user.servidor
comentario.save()
casa = ocorrencia.casa_legislativa
novo_presidente = (
get_object_or_404(
Parlamentar, id=ocorrencia.infos["presidente"]["id"]
)
if ocorrencia.infos["presidente"]
else None
)
contato = casa.contato_interlegis or Funcionario()
if not "aplicados" in ocorrencia.infos:
ocorrencia.infos["aplicados"] = []
apply = request.GET.get("apply", None)
if (
apply == "casa"
and "casa_legislativa" in ocorrencia.infos
and not "casa_legislativa" in ocorrencia.infos["aplicados"]
):
casa.foto = (
ocorrencia.casa_foto if ocorrencia.casa_foto else casa.foto
)
casa.brasao = (
ocorrencia.casa_brasao
if ocorrencia.casa_brasao
else casa.brasao
)
bound_copy(casa, ocorrencia.infos["casa_legislativa"])
casa.save()
ocorrencia.infos["aplicados"].append("casa_legislativa")
ocorrencia.save()
if (
apply == "presidente"
and "presidente" in ocorrencia.infos
and "presidente" not in ocorrencia.infos["aplicados"]
):
bound_copy(novo_presidente, ocorrencia.infos["presidente"], ["id"])
novo_presidente.save()
ocorrencia.infos["aplicados"].append("presidente")
ocorrencia.save()
if (
apply == "contato"
and "contato" in ocorrencia.infos
and "contato" not in ocorrencia.infos["aplicados"]
):
bound_copy(contato, ocorrencia.infos["contato"])
contato.save()
ocorrencia.infos["aplicados"].append("contato")
ocorrencia.save()
infos = copy.deepcopy(ocorrencia.infos)
if infos["presidente"]:
del infos["presidente"]["id"]
context.update(
{
"ocorrencia": ocorrencia,
"campos_ocorrencia": [
"assunto",
"casa_legislativa",
"categoria",
"descricao",
"data_criacao",
"data_modificacao",
],
"infos": infos,
"casa": casa,
"novo_presidente": novo_presidente,
"contato": contato,
"comentario_form": ComentarioInternoForm(),
"ocorrencia_form": OcorrenciaChangeForm(instance=ocorrencia),
}
)
return render(
request, "ocorrencias/convenio/painel_convenio_detail.html", context
)
base_query = Ocorrencia.objects.filter(
status__in=[Ocorrencia.STATUS_ABERTO, Ocorrencia.STATUS_REABERTO],
categoria__tipo="C",
)
ocorrencias = base_query.filter(infos__has_any_keys=Ocorrencia.INFO_KEYS)
ocorrencias = ocorrencias.union(
base_query.exclude(infos__has_any_keys=Ocorrencia.INFO_KEYS)
)
context["ocorrencias"] = ocorrencias
return render(request, "ocorrencias/convenio/painel_convenio.html", context)

2
sigi/apps/parlamentares/models.py

@ -92,8 +92,6 @@ class Parlamentar(models.Model):
verbose_name_plural = _("parlamentares") verbose_name_plural = _("parlamentares")
def __str__(self): def __str__(self):
if self.nome_parlamentar:
return self.nome_parlamentar
return self.nome_completo return self.nome_completo
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

5
sigi/apps/parlamentares/urls.py

@ -3,8 +3,9 @@ from sigi.apps.parlamentares import views
urlpatterns = [ urlpatterns = [
path( path(
"parlamentar_json/<int:casa_id>/", "parlamentarjson/<int:casa_id>/",
views.parlamentar_json, views.parlamentares_casa,
name="parlamentar-json", name="parlamentar-json",
), ),
path("parlamentardata/", views.parlamentar_data, name="parlamentar-data"),
] ]

13
sigi/apps/parlamentares/views.py

@ -1,6 +1,7 @@
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, InvalidPage, EmptyPage from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.core.serializers import serialize
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import render, get_list_or_404 from django.shortcuts import render, get_list_or_404
from django.template import Context, loader, RequestContext from django.template import Context, loader, RequestContext
@ -9,8 +10,7 @@ from sigi.apps.casas.models import Orgao
from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.parlamentares.models import Parlamentar
@login_required def parlamentares_casa(request, casa_id):
def parlamentar_json(request, casa_id):
return JsonResponse( return JsonResponse(
{ {
p.nome_completo: { p.nome_completo: {
@ -20,3 +20,12 @@ def parlamentar_json(request, casa_id):
for p in Parlamentar.objects.filter(casa_legislativa_id=casa_id) for p in Parlamentar.objects.filter(casa_legislativa_id=casa_id)
} }
) )
def parlamentar_data(request):
return HttpResponse(
serialize(
"json", Parlamentar.objects.filter(id=request.GET.get("id", None))
),
content_type="application/json",
)

18
sigi/apps/servidores/migrations/0010_servidor_sigi.py

@ -0,0 +1,18 @@
# Generated by Django 4.0.5 on 2022-06-30 21:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('servidores', '0009_servidor_cargo'),
]
operations = [
migrations.AddField(
model_name='servidor',
name='sigi',
field=models.BooleanField(default=False, editable=False, verbose_name='Servidor SIGI'),
),
]

3
sigi/apps/servidores/models.py

@ -57,6 +57,9 @@ class Servidor(models.Model):
_("órgão de origem, "), max_length=100, blank=True _("órgão de origem, "), max_length=100, blank=True
) )
qualificacoes = models.TextField(_("qualificações"), blank=True) qualificacoes = models.TextField(_("qualificações"), blank=True)
sigi = models.BooleanField(
_("Servidor SIGI"), default=False, editable=False
)
class Meta: class Meta:
ordering = ("nome_completo",) ordering = ("nome_completo",)

2
sigi/menu_conf.yaml

@ -63,6 +63,8 @@ main_menu:
- title: Registro de ocorrências - title: Registro de ocorrências
view_name: admin:ocorrencias_ocorrencia_changelist view_name: admin:ocorrencias_ocorrencia_changelist
querystr: minhas=S&status__in=1,2 querystr: minhas=S&status__in=1,2
- title: Solicitações de convênio
view_name: painel-convenio
- title: Eventos - title: Eventos
icon: school icon: school
children: children:

3
sigi/static/css/base_block.css

@ -0,0 +1,3 @@
#content {
display: block;
}

7
sigi/templates/admin/base_block.html

@ -0,0 +1,7 @@
{% extends "admin/base_site.html" %}
{% load static %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/base_block.css' %}">
{% endblock %}

3
sigi/urls.py

@ -21,7 +21,8 @@ from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path("admin/casas/", include("sigi.apps.casas.urls")), path("admin/casas/", include("sigi.apps.casas.urls")),
path("admin/convenios/", include("sigi.apps.convenios.urls")), path("admin/convenios/", include("sigi.apps.convenios.urls")),
path("admin/ocorrencias/", include("sigi.apps.ocorrencias.urls")), path("admin/ocorrencias/", include("sigi.apps.ocorrencias.admin_urls")),
path("ocorrencias/", include("sigi.apps.ocorrencias.urls")),
path("eventos/", include("sigi.apps.eventos.urls")), path("eventos/", include("sigi.apps.eventos.urls")),
path("parlamentares/", include("sigi.apps.parlamentares.urls")), path("parlamentares/", include("sigi.apps.parlamentares.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),

Loading…
Cancel
Save