Browse Source

Merge branch 'stable/2.2'

revisaoSidenav
Sesostris Vieira 3 years ago
parent
commit
a90d910184
  1. 1
      requirements/requirements.txt
  2. 100
      sigi/apps/casas/admin.py
  3. 13
      sigi/apps/casas/models.py
  4. 9
      sigi/apps/casas/views.py
  5. 42
      sigi/apps/convenios/admin.py
  6. 43
      sigi/apps/convenios/management/commands/duracao_act.py
  7. 20
      sigi/apps/convenios/migrations/0010_auto_20210819_0833.py
  8. 20
      sigi/apps/convenios/migrations/0011_convenio_data_termino_vigencia.py
  9. 38
      sigi/apps/convenios/migrations/0012_auto_20210831_0844.py
  10. 18
      sigi/apps/convenios/migrations/0013_remove_convenio_duracao.py
  11. 30
      sigi/apps/convenios/migrations/0014_gescon.py
  12. 18
      sigi/apps/convenios/migrations/0015_remove_convenio_search_text.py
  13. 26
      sigi/apps/convenios/migrations/0016_auto_20210909_0732.py
  14. 20
      sigi/apps/convenios/migrations/0017_convenio_id_contrato_gescon.py
  15. 21
      sigi/apps/convenios/migrations/0018_auto_20211208_1256.py
  16. 484
      sigi/apps/convenios/models.py
  17. 120
      sigi/apps/convenios/reports.py
  18. 16
      sigi/apps/convenios/templates/convenios/change_list.html
  19. 18
      sigi/apps/convenios/templates/convenios/importar_gescon.html
  20. 1
      sigi/apps/convenios/urls.py
  21. 22
      sigi/apps/convenios/views.py
  22. 49
      sigi/apps/eventos/admin.py
  23. 32
      sigi/apps/eventos/forms.py
  24. 26
      sigi/apps/eventos/migrations/0008_auto_20211104_1253.py
  25. 20
      sigi/apps/eventos/migrations/0009_tipoevento_categoria.py
  26. 37
      sigi/apps/eventos/migrations/0010_modulo.py
  27. 24
      sigi/apps/eventos/migrations/0011_auto_20211117_0633.py
  28. 28
      sigi/apps/eventos/migrations/0012_auto_20211117_0657.py
  29. 30
      sigi/apps/eventos/migrations/0013_modelodeclaracao.py
  30. 21
      sigi/apps/eventos/migrations/0014_auto_20211124_0736.py
  31. 29
      sigi/apps/eventos/migrations/0015_anexo.py
  32. 172
      sigi/apps/eventos/models.py
  33. 10
      sigi/apps/eventos/templates/admin/eventos/evento/change_form.html
  34. 0
      sigi/apps/eventos/templates/admin/eventos/evento/change_list.html
  35. 24
      sigi/apps/eventos/templates/eventos/declaracao_pdf.html
  36. 25
      sigi/apps/eventos/templates/eventos/seleciona_modelo.html
  37. 3
      sigi/apps/eventos/urls.py
  38. 103
      sigi/apps/eventos/views.py
  39. 16
      sigi/apps/home/templatetags/menu_conf.yaml
  40. 2
      sigi/apps/metas/templates/metas/openmap.html
  41. 4
      sigi/apps/metas/templatetags/mapa_tags.py
  42. 14
      sigi/apps/ocorrencias/admin.py
  43. 77
      sigi/apps/servicos/admin.py
  44. 4
      sigi/apps/servicos/templates/admin/servicos/servico/change_list.html
  45. 33
      sigi/apps/servicos/templates/servico/change_list.html
  46. 100
      sigi/apps/servicos/templates/servicos/carrinho.html
  47. 4
      sigi/apps/servicos/urls.py
  48. 194
      sigi/apps/servicos/views.py
  49. 12
      sigi/apps/servidores/models.py
  50. 11
      sigi/settings/base.py
  51. 22
      sigi/shortcuts.py
  52. 1
      sigi/urls.py
  53. 1
      templates/admin/base_site.html
  54. 18
      templates/base_report.html

1
requirements/requirements.txt

@ -21,3 +21,4 @@ requests==2.8.1
six==1.10.0
djangorestframework==2.4.8
django-ipware==1.1.6
django-tinymce==2.6.0

100
sigi/apps/casas/admin.py

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from unicodedata import name
from django.contrib import admin
from django.contrib.contenttypes import generic
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import ugettext as _
#from geraldo.site.newsite.django_1_0.django.forms import extras
from image_cropping import ImageCroppingMixin
from sigi.apps.casas.forms import OrgaoForm
@ -50,7 +52,31 @@ class PresidenteInline(admin.StackedInline):
extra = 1
max_num = 1
verbose_name_plural = _(u'Presidente')
def get_queryset(self, request):
return (self.model.objects.exclude(desativado=True)
.extra(select={'ult_null': 'ult_alteracao is null'})
.order_by('ult_null', '-ult_alteracao')
# A função extra foi usada para quando existir um registro com o campo igual a null não aparecer na frente dos mais novos
)
class ContatoInterlegisInline(admin.StackedInline):
model = Funcionario
fields = ('nome', 'sexo', 'data_nascimento', 'nota', 'email', 'cargo',
'funcao', 'setor', 'tempo_de_servico', 'ult_alteracao',
'endereco', 'municipio', 'bairro', 'cep', 'redes_sociais',
'desativado', 'observacoes')
raw_id_fields = ('municipio',)
readonly_fields = ('ult_alteracao',)
extra = 1
inlines = (TelefonesInline,)
verbose_name_plural = _(u'Contato(s) Interlegis Vigente(s)')
def get_queryset(self, request):
return (self.model.objects.filter(setor='contato_interlegis')
.extra(select={'ult_null': 'ult_alteracao is null'}).order_by('-ult_alteracao')
)
def get_extra(self, request, obj=None , **kwargs):
extra = 0
return extra
class FuncionariosInline(admin.StackedInline):
model = Funcionario
@ -59,6 +85,7 @@ class FuncionariosInline(admin.StackedInline):
'endereco', 'municipio', 'bairro', 'cep', 'redes_sociais',
'desativado', 'observacoes')
raw_id_fields = ('municipio',)
# fieldsets = ((None, {
# 'fields': (
# ('nome', 'sexo', 'data_nascimento'),
@ -74,22 +101,22 @@ class FuncionariosInline(admin.StackedInline):
readonly_fields = ('ult_alteracao',)
extra = 1
inlines = (TelefonesInline,)
verbose_name_plural = _(u'Outros Contatos da Casa')
def get_queryset(self, request):
return (self.model.objects.exclude(
cargo='Presidente').exclude(desativado=True)
return (self.model.objects.exclude(cargo='Presidente',)
.exclude(desativado=True).extra(select={'ult_null': 'ult_alteracao is null'})
.order_by('ult_null', '-ult_alteracao')
# A função extra foi usada para quando existir um registro com o campo igual a null não aparecer na frente dos mais novos
)
class ConveniosInline(admin.TabularInline):
model = Convenio
fieldsets = (
(None, {'fields': (
('link_sigad', 'status_convenio', 'num_convenio',
'projeto', 'observacao'),
('data_adesao', 'data_retorno_assinatura', 'data_termo_aceite',
'data_pub_diario', 'data_devolucao_via', 'data_postagem_correio'),
('data_devolucao_sem_assinatura', 'data_retorno_sem_assinatura',),
('data_retorno_assinatura', 'data_pub_diario',),
('get_anexos',),
('link_convenio',),
)}),
@ -103,6 +130,7 @@ class ConveniosInline(admin.TabularInline):
extra = 0
can_delete = False
template = 'admin/casas/convenios_inline.html'
ordering = ('-data_retorno_assinatura',)
def has_add_permission(self, request):
return False
@ -210,10 +238,10 @@ class ServicoInline(admin.TabularInline):
model = Servico
fields = ('link_url', 'contato_tecnico', 'contato_administrativo',
'hospedagem_interlegis', 'data_ativacao', 'data_alteracao',
'data_desativacao')
'data_desativacao', 'link_servico')
readonly_fields = ['link_url', 'contato_tecnico', 'contato_administrativo',
'hospedagem_interlegis', 'data_ativacao',
'data_alteracao', 'data_desativacao']
'data_alteracao', 'data_desativacao', 'link_servico']
extra = 0
max_num = 0
can_delete = False
@ -225,6 +253,24 @@ class ServicoInline(admin.TabularInline):
link_url.short_description = _(u'URL do serviço')
link_url.allow_tags = True
ordering = ('-data_alteracao',)
def link_servico(self, obj):
if obj.pk is None:
return ""
url = reverse('admin:%s_%s_change' % (obj._meta.app_label, obj._meta.module_name), args=[obj.pk])
url = url + '?_popup=1'
return """<input id="edit_convenio-%s" type="hidden"/>
<a id="lookup_edit_convenio-%s" href="%s" class="changelink" onclick="return showRelatedObjectLookupPopup(this)">
Editar
</a>""" % (obj.pk, obj.pk, url)
link_servico.short_description = _(u'Editar Serviço')
link_servico.allow_tags = True
def has_add_permission(self, request):
return False
# class PlanoDiretorInline(admin.TabularInline):
# model = PlanoDiretor
@ -237,6 +283,8 @@ class OcorrenciaInline(admin.TabularInline):
can_delete = False
template = 'admin/casas/ocorrencia_inline.html'
ordering = ('-data_modificacao',)
def link_editar(self, obj):
if obj.pk is None:
return ""
@ -264,7 +312,7 @@ class ConvenioFilter(admin.SimpleListFilter):
def lookups(self, request, model_admin):
return (
('SC', _(u"Sem nenhum convênio")),
('CC', _(u"Com algum convênio"))
('CC', _(u"Com algum convênio")),
) + tuple([(p.pk, p.sigla) for p in Projeto.objects.all()])
def queryset(self, request, queryset):
@ -278,6 +326,19 @@ class ConvenioFilter(admin.SimpleListFilter):
return queryset.distinct('municipio__uf__nome', 'nome')
class ExcluirConvenioFilter(admin.SimpleListFilter):
title=_(u"Excluir convênio da pesquisa")
parameter_name = 'excluir_convenio'
def lookups(self, request, model_admin):
return tuple([(p.pk, p.sigla) for p in Projeto.objects.all()])
def queryset(self, request, queryset):
if (self.value() is None):
return queryset
else:
queryset = queryset.exclude(convenio__projeto_id=self.value()).distinct('municipio__uf__nome', 'nome')
return queryset
class ServicoFilter(admin.SimpleListFilter):
title = _(u"Serviço")
@ -313,17 +374,34 @@ class ServicoFilter(admin.SimpleListFilter):
return queryset.distinct('municipio__uf__nome', 'nome')
class ServicoAtivoFilter(admin.SimpleListFilter):
title = _(u"Serviço ativo")
parameter_name = 'ativo'
def lookups(self, request, model_admin):
return (
('ativo', _(u"Ativo")),
('desativado', _(u"Desativado")),
)
def queryset(self, request, queryset):
if self.value() is not None:
if self.value() == 'ativo':
queryset = queryset.filter(servico__data_desativacao__isnull=True)
else:
queryset = queryset.filter(servico__data_desativacao__isnull=False)
return queryset
@admin.register(Orgao)
class OrgaoAdmin(ImageCroppingMixin, BaseModelAdmin):
form = OrgaoForm
actions = ['adicionar_casas', ]
inlines = (TelefonesInline, PresidenteInline, FuncionariosInline,
inlines = (TelefonesInline, PresidenteInline, ContatoInterlegisInline, FuncionariosInline,
ConveniosInline, ServicoInline, OcorrenciaInline,)
list_display = ('id', 'sigla', 'nome', 'get_uf', 'get_gerentes', 'get_convenios',
'get_servicos')
list_display_links = ('sigla', 'nome',)
list_filter = ('tipo', ('gerentes_interlegis', GerentesInterlegisFilter),
'municipio__uf__nome', ConvenioFilter, ServicoFilter,
'municipio__uf__nome', ConvenioFilter, ServicoAtivoFilter, ExcluirConvenioFilter, ServicoFilter,
'inclusao_digital',)
ordering = ('municipio__uf__nome', 'nome')
queryset = queryset_ascii

13
sigi/apps/casas/models.py

@ -195,6 +195,19 @@ class Orgao(models.Model):
except Funcionario.DoesNotExist:
return None
@property
def contato_interlegis(self):
""" Link para acessar diretamente o contato do presidente da casa
Util para relatorios antigos
"""
try:
if self.funcionario_set.filter(setor='contato_interlegis').count() > 1:
return self.funcionario_set.filter(setor='contato_interlegis')[0]
else:
return self.funcionario_set.get(setor='contato_interlegis')
except Funcionario.DoesNotExist:
return None
@property
def total_parlamentares(self):
"""

9
sigi/apps/casas/views.py

@ -2,6 +2,9 @@
import csv
from datetime import datetime
from functools import reduce
from django.contrib import messages
from sigi.apps.utils import to_ascii
from geraldo.generators import PDFGenerator
from django.conf import settings
@ -257,9 +260,10 @@ class importa_casas(View):
for reg in reader:
self.total_registros += 1
reg[self.ERROS] = []
nome_orgao = to_ascii(reg[self.MUNICIPIO])
orgao = Orgao.objects.filter(
tipo__sigla=reg[self.TIPO],
municipio__nome=reg[self.MUNICIPIO],
municipio__search_text__icontains=nome_orgao,
municipio__uf__sigla=reg[self.UF]
)
if orgao.count() == 0:
@ -444,7 +448,8 @@ def visualizar_carrinho(request):
def excluir_carrinho(request):
if 'carrinho_casas' in request.session:
del request.session['carrinho_casas']
return HttpResponseRedirect('.')
messages.info(request, u'O carrinho foi esvaziado')
return HttpResponseRedirect('../../')
@login_required

42
sigi/apps/convenios/admin.py

@ -6,7 +6,8 @@ from geraldo.generators import PDFGenerator
from sigi.apps.convenios.models import (Projeto, StatusConvenio,
TipoSolicitacao, Convenio,
EquipamentoPrevisto, Anexo, Tramitacao)
EquipamentoPrevisto, Anexo, Tramitacao,
Gescon)
from sigi.apps.convenios.reports import ConvenioReport
from sigi.apps.convenios.views import adicionar_convenios_carrinho
from sigi.apps.utils import queryset_ascii
@ -51,35 +52,37 @@ class ConvenioAdmin(BaseModelAdmin):
'projeto', 'data_sigi',)}
),
(_(u"Acompanhamento no gabinete"),
{'fields': ('data_solicitacao', 'data_sigad', 'tipo_solicitacao',
'status', 'acompanha', 'observacao',)}
{'fields': ('data_solicitacao', 'data_sigad', 'observacao',)}
),
(_(u"Gestão do convênio"),
{'fields': ('servico_gestao', 'servidor_gestao',)}
),
(_(u'Datas'),
{'fields': ('data_retorno_assinatura', 'duracao',
{'fields': ('data_retorno_assinatura', 'data_termino_vigencia',
'data_pub_diario',)}
),
(_(u'Gescon'),
{'fields': ('atualizacao_gescon', 'observacao_gescon', 'link_gescon')}
),
)
readonly_fields = ('data_sigi',)
readonly_fields = ('data_sigi', 'atualizacao_gescon', 'observacao_gescon', 'link_gescon')
actions = ['adicionar_convenios']
inlines = (AnexosInline,)
list_display = ('num_convenio', 'casa_legislativa', 'get_uf',
list_display = ('num_convenio', 'projeto','casa_legislativa', 'get_uf',
'status_convenio', 'link_sigad', 'data_retorno_assinatura',
'duracao', 'projeto', 'status', 'acompanha',)
'data_termino_vigencia',)
list_display_links = ('num_convenio', 'casa_legislativa',)
list_filter = ('status', ('acompanha', AcompanhaFilter),
('casa_legislativa__gerentes_interlegis',
list_filter = (('casa_legislativa__gerentes_interlegis',
GerentesInterlegisFilter), 'projeto',
'casa_legislativa__tipo', 'conveniada','equipada',
'casa_legislativa__municipio__uf',)
#date_hierarchy = 'data_adesao'
ordering = ('casa_legislativa__tipo__sigla', 'casa_legislativa__municipio__uf', 'casa_legislativa')
ordering = ('casa_legislativa', '-data_retorno_assinatura')
raw_id_fields = ('casa_legislativa',)
get_queryset = queryset_ascii
search_fields = ('id', 'search_text', # 'casa_legislativa__nome',
'num_processo_sf', 'num_convenio')
search_fields = ('id', 'casa_legislativa__search_text',
'casa_legislativa__sigla', 'num_processo_sf',
'num_convenio')
def get_uf(self, obj):
return obj.casa_legislativa.municipio.uf.sigla
@ -112,6 +115,16 @@ class ConvenioAdmin(BaseModelAdmin):
link_sigad.short_description = _("Processo no Senado")
link_sigad.allow_tags = True
def link_gescon(self, obj):
if not obj.id_contrato_gescon:
return u""
return (
u"<a href='https://adm.senado.gov.br/gestao-contratos/api/"
u"contratos/buscaTexto/{id}'>https://adm.senado.gov.br/"
u"gestao-contratos/api/{id}</a>").format(id=obj.id_contrato_gescon)
link_gescon.short_description = _("Download MINUTA ASSINADA do Gescon")
link_gescon.allow_tags = True
def changelist_view(self, request, extra_context=None):
from sigi.apps.convenios.views import normaliza_data
request.GET._mutable = True
@ -172,6 +185,11 @@ class EquipamentoPrevistoAdmin(BaseModelAdmin):
search_fields = ('convenio__id', 'equipamento__fabricante__nome',
'equipamento__modelo__modelo', 'equipamento__modelo__tipo__tipo')
@admin.register(Gescon)
class GesconAdmin(admin.ModelAdmin):
list_display = ('url_gescon', 'email', 'ultima_importacao')
readonly_fields = ('ultima_importacao',)
admin.site.register(Projeto)
admin.site.register(StatusConvenio)
admin.site.register(TipoSolicitacao)

43
sigi/apps/convenios/management/commands/duracao_act.py

@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
#
# sigi.apps.casas.management.commands.importa_gerentes
#
# Copyright (c) 2015 by Interlegis
#
# GNU General Public License (GPL)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
from django.core.management.base import BaseCommand, CommandError
from sigi.apps.convenios.models import Projeto, Convenio
class Command(BaseCommand):
help = u"""Define a duração de todos os ACT para 60 meses.
* A sigla do Projeto precisa ser ACT;
* O campo duracao precisa estar em branco.
"""
def handle(self, *args, **options):
self.stdout.write(u"Atualizando ACTs... ")
act = Projeto.objects.get(sigla='ACT')
for conv in Convenio.objects.filter(projeto=act, duracao=None):
conv.duracao = 60
conv.save()
self.stdout.write(u"\tACT {sigad} da Casa {casa} atualizado".format(
sigad=conv.num_processo_sf, casa=conv.casa_legislativa.nome
))
self.stdout.write(u"Pronto!")

20
sigi/apps/convenios/migrations/0010_auto_20210819_0833.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('convenios', '0009_auto_20210611_0946'),
]
operations = [
migrations.AlterField(
model_name='convenio',
name='data_retorno_assinatura',
field=models.DateField(help_text='Conv\xeanio firmado.', null=True, verbose_name='data in\xedcio vig\xeancia', blank=True),
preserve_default=True,
),
]

20
sigi/apps/convenios/migrations/0011_convenio_data_termino_vigencia.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('convenios', '0010_auto_20210819_0833'),
]
operations = [
migrations.AddField(
model_name='convenio',
name='data_termino_vigencia',
field=models.DateField(help_text='T\xe9rmino da vig\xeancia do conv\xeanio.', null=True, verbose_name='Data t\xe9rmino vig\xeancia', blank=True),
preserve_default=True,
),
]

38
sigi/apps/convenios/migrations/0012_auto_20210831_0844.py

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import date
from django.db import models, migrations
def migra_data_termino_vigencia(apps, schema_editor):
Convenio = apps.get_model('convenios', 'Convenio')
for c in Convenio.objects.all():
if (c.data_retorno_assinatura is None or c.duracao is None):
continue
ano = c.data_retorno_assinatura.year + int(c.duracao / 12)
mes = int(c.data_retorno_assinatura.month + int(c.duracao % 12))
if mes > 12:
ano = ano + 1
mes = mes - 12
dia = c.data_retorno_assinatura.day
while True:
try:
data_fim = date(year=ano, month=mes,day=dia)
break
except:
dia = dia - 1
c.data_termino_vigencia = data_fim
c.save()
class Migration(migrations.Migration):
dependencies = [
('convenios', '0011_convenio_data_termino_vigencia'),
]
operations = [
migrations.RunPython(migra_data_termino_vigencia),
]

18
sigi/apps/convenios/migrations/0013_remove_convenio_duracao.py

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('convenios', '0012_auto_20210831_0844'),
]
operations = [
migrations.RemoveField(
model_name='convenio',
name='duracao',
),
]

30
sigi/apps/convenios/migrations/0014_gescon.py

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('convenios', '0013_remove_convenio_duracao'),
]
operations = [
migrations.CreateModel(
name='Gescon',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('url_gescon', models.URLField(default='https://adm.senado.gov.br/gestao-contratos/api/contratos/busca?especie={s}', help_text='Informe o ponto de consulta do webservice do Gescon, inclusive com a querystring. No ponto onde deve ser inserida a sigla da subespecie do contrato, use a marca\xe7\xe3o {s}.<br/><strong>Por exemplo:</strong> https://adm.senado.gov.br/gestao-contratos/api/contratos/busca?especie=<strong>{s}</strong>', verbose_name='Webservice Gescon')),
('subespecies', models.TextField(default='AC=ACT\nPI=PI\nCN=PML\nTA=PML', help_text='Informe as siglas das subesp\xe9cies de contratos que devem ser pesquisados no Gescon com a sigla correspondente do projeto no SIGI. Coloque um par de siglas por linha, no formato SIGLA_GESTON=SIGLA_SIGI. As siglas n\xe3o encontradas ser\xe3o ignoradas.', verbose_name='Subesp\xe9cies')),
('palavras', models.TextField(default='ILB\nINTERLEGIS', help_text='Palavras que devem aparecer no campo OBJETO dos dados do Gescon para identificar se o contrato pertence ao ILB. <ul><li>Informe uma palavra por linha.</li><li>Ocorrendo qualquer uma das palavras, o contrato ser\xe1 importado.</li></ul>', verbose_name='Palavras de filtro')),
('email', models.EmailField(help_text='Caixa de e-mail para onde o relat\xf3rio di\xe1rio de importa\xe7\xe3o ser\xe1 enviado.', max_length=75, verbose_name='E-mail')),
('ultima_importacao', models.TextField(verbose_name='Resultado da \xfaltima importa\xe7\xe3o', blank=True)),
],
options={
'verbose_name': 'Configura\xe7\xe3o do Gescon',
'verbose_name_plural': 'Configura\xe7\xf5es do Gescon',
},
bases=(models.Model,),
),
]

18
sigi/apps/convenios/migrations/0015_remove_convenio_search_text.py

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('convenios', '0014_gescon'),
]
operations = [
migrations.RemoveField(
model_name='convenio',
name='search_text',
),
]

26
sigi/apps/convenios/migrations/0016_auto_20210909_0732.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('convenios', '0015_remove_convenio_search_text'),
]
operations = [
migrations.AddField(
model_name='convenio',
name='atualizacao_gescon',
field=models.DateTimeField(null=True, verbose_name='Data de atualiza\xe7\xe3o pelo Gescon', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='convenio',
name='observacao_gescon',
field=models.TextField(verbose_name='Observa\xe7\xf5es da atualiza\xe7\xe3o do Gescon', blank=True),
preserve_default=True,
),
]

20
sigi/apps/convenios/migrations/0017_convenio_id_contrato_gescon.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('convenios', '0016_auto_20210909_0732'),
]
operations = [
migrations.AddField(
model_name='convenio',
name='id_contrato_gescon',
field=models.CharField(default=b'', verbose_name='ID do contrato no Gescon', max_length=20, editable=False, blank=True),
preserve_default=True,
),
]

21
sigi/apps/convenios/migrations/0018_auto_20211208_1256.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('convenios', '0017_convenio_id_contrato_gescon'),
]
operations = [
migrations.AlterField(
model_name='convenio',
name='projeto',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, verbose_name='Tipo de Convenio', to='convenios.Projeto'),
preserve_default=True,
),
]

484
sigi/apps/convenios/models.py

@ -1,9 +1,14 @@
#-*- coding: utf-8 -*-
import re
import requests
from datetime import datetime, date
from django.db import models
from django.db.models import Q, fields
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from sigi.apps.utils import SearchField
from sigi.apps.utils import SearchField, to_ascii
from sigi.apps.casas.models import Orgao
from sigi.apps.servidores.models import Servidor, Servico
class Projeto(models.Model):
@ -49,10 +54,10 @@ class Convenio(models.Model):
verbose_name=_(u'órgão conveniado')
)
# campo de busca em caixa baixa e sem acentos
search_text = SearchField(field_names=['casa_legislativa'])
projeto = models.ForeignKey(
Projeto,
on_delete=models.PROTECT,
verbose_name=_(u'Tipo de Convenio')
)
# numero designado pelo Senado Federal para o convênio
num_processo_sf = models.CharField(
@ -61,11 +66,19 @@ class Convenio(models.Model):
blank=True,
help_text=_(u'Formatos:<br/>Antigo: <em>XXXXXX/XX-X</em>.<br/><em>SIGAD: XXXXX.XXXXXX/XXXX-XX</em>')
)
# link_processo_stf = ('get_sigad_url')
num_convenio = models.CharField(
_(u'número do convênio'),
max_length=10,
blank=True
)
id_contrato_gescon = models.CharField(
_(u"ID do contrato no Gescon"),
max_length=20,
blank=True,
default="",
editable=False
)
data_sigi = models.DateField(
_(u"data de cadastro no SIGI"),
blank=True,
@ -130,16 +143,16 @@ class Convenio(models.Model):
blank=True,
)
data_retorno_assinatura = models.DateField(
_(u'conveniadas'),
_(u'data início vigência'),
null=True,
blank=True,
help_text=_(u'Convênio firmado.')
)
duracao = models.PositiveIntegerField(
_(u"duração (meses)"),
data_termino_vigencia = models.DateField(
_(u'Data término vigência'),
null=True,
blank=True,
help_text=_(u"Deixar em branco caso a duração seja indefinida")
help_text=_(u'Término da vigência do convênio.')
)
data_pub_diario = models.DateField(
_(u'data da publicação no Diário Oficial'),
@ -177,35 +190,23 @@ class Convenio(models.Model):
)
conveniada = models.BooleanField(default=False)
equipada = models.BooleanField(default=False)
def get_termino_convenio(self):
if (self.data_retorno_assinatura is None or
self.duracao is None):
return None
ano = self.data_retorno_assinatura.year + int(self.duracao / 12)
mes = int(self.data_retorno_assinatura.month + int(self.duracao % 12))
if mes > 12:
ano = ano + 1
mes = mes - 12
dia = self.data_retorno_assinatura.day
while True:
try:
data_fim = date(year=ano, month=mes,day=dia)
break
except:
dia = dia - 1
return data_fim
atualizacao_gescon = models.DateTimeField(
_(u"Data de atualização pelo Gescon"),
blank=True,
null=True
)
observacao_gescon = models.TextField(
_(u"Observações da atualização do Gescon"),
blank=True
)
def get_status(self):
if self.status and self.status.cancela:
return _(u"Cancelado")
if self.data_retorno_assinatura is not None:
if self.duracao is not None:
if date.today() >= self.get_termino_convenio():
if self.data_termino_vigencia is not None:
if date.today() >= self.data_termino_vigencia:
return _(u"Vencido")
return _(u"Vigente")
@ -219,6 +220,14 @@ class Convenio(models.Model):
return _(u"Indefinido")
def link_sigad(self, obj):
if obj.pk is None:
return ""
return obj.get_sigad_url()
link_sigad.short_description = _("Processo no Senado")
link_sigad.allow_tags = True
def get_sigad_url(self):
m = re.match(
r'(?P<orgao>00100|00200)\.(?P<sequencial>\d{6})/(?P<ano>\d{4})-\d{2}',
@ -257,18 +266,18 @@ class Convenio(models.Model):
if ((self.data_retorno_assinatura is None) and
(self.equipada and self.data_termo_aceite is not None)):
return _(u"Convênio{number} - equipada em {date} pelo {project}"
return _(u"{project}{number} - equipada em {date}"
).format(number=self.num_convenio,
date=self.data_termo_aceite.strftime('%d/%m/%Y'),
project=self.projeto.sigla)
elif self.data_retorno_assinatura is None:
return _(u"Convênio nº {number} - adesão ao projeto {project}, "
return _(u"{project}, nº {number}, início "
u"em {date}").format(number=self.num_convenio,
project=self.projeto.sigla,
date=self.data_adesao)
if ((self.data_retorno_assinatura is not None) and not
(self.equipada and self.data_termo_aceite is not None)):
return _(u"Convênio nº {number} - conveniada ao {project} em "
return _(u"{project}, nº {number}, inicio em "
u"{date}. Status: {status}").format(
number=self.num_convenio,
project=self.projeto.sigla,
@ -276,7 +285,7 @@ class Convenio(models.Model):
status=self.get_status())
if ((self.data_retorno_assinatura is not None) and
(self.equipada and self.data_termo_aceite is not None)):
return _(u"Convẽnio nº {number} - conveniada ao {project} em {date}"
return _(u"{project}, nº {number}, início em {date}"
u" e equipada em {equipped_date}. Status: {status}"
).format(number=self.num_convenio,
project=self.projeto.sigla,
@ -381,3 +390,412 @@ class Tramitacao(models.Model):
if self.observacao:
result = result + u" (%s)" % (self.observacao)
return unicode(result) # XXX is this unicode(...) really necessary???
class Gescon(models.Model):
url_gescon = models.URLField(
_(u"Webservice Gescon"),
default=(u"https://adm.senado.gov.br/gestao-contratos/api/contratos"
u"/busca?especie={s}"),
help_text=_(u"Informe o ponto de consulta do webservice do Gescon, "
u"inclusive com a querystring. No ponto onde deve ser "
u"inserida a sigla da subespecie do contrato, use a "
u"marcação {s}.<br/><strong>Por exemplo:</strong> "
u"https://adm.senado.gov.br/gestao-contratos/api/contratos"
u"/busca?especie=<strong>{s}</strong>")
)
subespecies = models.TextField(
_(u"Subespécies"),
default=u"AC=ACT\nPI=PI\nCN=PML\nTA=PML",
help_text=_(u"Informe as siglas das subespécies de contratos que "
u"devem ser pesquisados no Gescon com a sigla "
u"correspondente do projeto no SIGI. Coloque um par de "
u"siglas por linha, no formato SIGLA_GESTON=SIGLA_SIGI. "
u"As siglas não encontradas serão ignoradas.")
)
palavras = models.TextField(
_(u"Palavras de filtro"),
default=u"ILB\nINTERLEGIS",
help_text=_(u"Palavras que devem aparecer no campo OBJETO dos dados do "
u"Gescon para identificar se o contrato pertence ao ILB. "
u"<ul><li>Informe uma palavra por linha.</li>"
u"<li>Ocorrendo qualquer uma das palavras, o contrato será "
u"importado.</li></ul>")
)
email = models.EmailField(
_(u"E-mail"),
help_text=_(u"Caixa de e-mail para onde o relatório diário de "
u"importação será enviado.")
)
ultima_importacao = models.TextField(
_(u"Resultado da última importação"),
blank=True
)
class Meta:
verbose_name = _(u"Configuração do Gescon")
verbose_name_plural = _(u"Configurações do Gescon")
def __unicode__(self):
return self.url_gescon
def save(self, *args, **kwargs):
self.pk = 1 # Highlander (singleton pattern)
return super(Gescon, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
pass # Highlander is immortal
def add_message(self, msg, save=False):
self.ultima_importacao += msg + "\n"
if save:
self.save()
self.email_report()
def email_report(self):
if self.email:
send_mail(
subject=_(u"Relatório de importação GESCON"),
message=self.ultima_importacao,
recipient_list=self.email,
fail_silently=True
)
else:
self.ultima_importacao += _(
u"\n\n*Não foi definida uma caixa de e-mail nas configurações "
u"do Gescon*"
)
self.save()
def importa_contratos(self):
self.ultima_importacao = ""
self.add_message(
_(u"Importação iniciada em {:%d/%m/%Y %H:%M:%S}\n"
u"==========================================\n").format(
datetime.now()
)
)
if self.palavras == "":
self.add_message(_(u"Nenhuma palavra de pesquisa definida - "
u"processo abortado."), True)
return
if self.subespecies == "":
self.add_message(_(u"Nenhuma subespécie definida - processo "
u"abortado."), True)
return
if "{s}" not in self.url_gescon:
self.add_message(
_(
u"Falta a marcação {s} na URL para indicar o local onde "
u"inserir a sigla da subespécia na consulta ao webservice "
u"- processo abortado."
),
True
)
return
palavras = self.palavras.split()
subespecies = {tuple(s.split("=")) for s in self.subespecies.split()}
for sigla_gescon, sigla_sigi in subespecies:
self.add_message(_(u"\nImportando subespécie {s}".format(
s=sigla_gescon)))
url = self.url_gescon.format(s=sigla_gescon)
projeto = Projeto.objects.get(sigla=sigla_sigi)
try:
response = requests.get(url, verify=False)
except Exception as e:
self.add_message(
_(u"\tErro ao acessar {url}: {errmsg}").format(
url=url,
errmsg=e.message.decode("utf8")
)
)
continue
if not response.ok:
self.add_message(
_(u"\tErro ao acessar {url}: {reason}").format(
url=url,
reason=response.reason
)
)
continue
if not 'application/json' in response.headers.get('Content-Type'):
self.add_message(_(u"\tResultado da consulta à {url} não "
u"retornou dados em formato json").format(
url=url
)
)
continue
contratos = response.json()
# Pegar só os contratos que possuem alguma das palavras-chave
nossos = [c for c in contratos
if any(palavra in c['objeto'] for palavra in palavras)]
self.add_message(
_(u"\t{count} contratos encontrados no Gescon").format(
count=len(nossos)
)
)
novos = 0
erros = 0
alertas = 0
atualizados = 0
for contrato in nossos:
numero = contrato['numero'].zfill(8)
numero = "{}/{}".format(numero[:4], numero[4:])
sigad = contrato['processo'].zfill(17)
sigad = "{}.{}/{}-{}".format(sigad[:5], sigad[5:11],
sigad[11:15], sigad[15:])
if contrato['cnpjCpfFornecedor']:
cnpj = contrato['cnpjCpfFornecedor'].zfill(14)
cnpj = "{}.{}.{}/{}-{}".format(cnpj[:2], cnpj[2:5],
cnpj[5:8], cnpj[8:12],
cnpj[12:])
else:
cnpj = None
if contrato['nomeFornecedor']:
nome = contrato['nomeFornecedor']
nome = nome.replace(u'VEREADORES DE', '')
nome = nome.split('-')[0]
nome = nome.split('/')[0]
nome = nome.strip()
nome = nome.replace(" ", " ")
nome = to_ascii(nome)
else:
nome = None
if (cnpj is None) and (nome is None):
self.add_message(
_(u"\tO contrato {numero} no Gescon não informa o CNPJ "
u"nem o nome do órgão.").format(numero=numero)
)
erros += 1
continue
orgao = None
if cnpj is not None:
try:
orgao = Orgao.objects.get(cnpj=cnpj)
except (
Orgao.DoesNotExist,
Orgao.MultipleObjectsReturned) as e:
orgao = None
pass
if (orgao is None) and (nome is not None):
try:
orgao = Orgao.objects.get(search_text__iexact=nome)
except (
Orgao.DoesNotExist,
Orgao.MultipleObjectsReturned) as e:
orgao = None
pass
if orgao is None:
self.add_message(
_(u"\tÓrgão não encontrado no SIGI ou mais de um órgão"
u"encontrado com o mesmo CNPJ ou nome. Favor "
u"regularizar o cadastro: CNPJ: {cnpj}, "
u"Nome: {nome}".format(
cnpj=contrato['cnpjCpfFornecedor'],
nome=contrato['nomeFornecedor']
)
)
)
erros += 1
continue
# O mais seguro é o NUP sigad
convenios = Convenio.objects.filter(num_processo_sf=sigad)
chk = convenios.count()
if chk == 0:
# NUP não encontrado, talvez exista apenas com o número
# do GESCON
convenios = Convenio.objects.filter(
Q(num_convenio=numero) |
Q(num_processo_sf=numero)
)
chk = convenios.count()
if chk > 1:
# Pode ser que existam vários contratos de subespécies
# diferentes com o mesmo número Gescon. Neste caso, o
# ideal é filtrar pelo tipo de projeto. Existindo, é
# ele mesmo. Se não existir, então segue com os
# múltiplos para registrar o problema mais adiante
if convenios.filter(projeto=projeto).count() == 1:
convenios = convenios.filter(projeto=projeto)
chk = 1
if chk == 0:
convenio = Convenio(
casa_legislativa=orgao,
projeto=projeto,
num_processo_sf=sigad,
num_convenio=numero,
data_sigi=date.today(),
data_sigad=contrato['assinatura'],
observacao=contrato['objeto'],
data_retorno_assinatura=contrato['inicioVigencia'],
data_termino_vigencia=contrato['terminoVigencia'],
data_pub_diario=contrato['publicacao'],
atualizacao_gescon=datetime.now(),
observacao_gescon=_(u"Importado integralmente do"
u"Gescon")
)
convenio.save()
novos += 1
continue
elif chk == 1:
convenio = convenios.get()
convenio.atualizacao_gescon = datetime.now()
convenio.observacao_gescon = ''
if convenio.casa_legislativa != orgao:
self.add_message(
_(u"\tO órgao no convênio {url} diverge do que "
u"consta no Gescon ({cnpj}, {nome})").format(
url=reverse('admin:%s_%s_change' % (
convenio._meta.app_label,
convenio._meta.model_name),
args=[convenio.id]),
cnpj=cnpj,
nome=contrato['nomeFornecedor']
)
)
convenio.observacao_gescon = _(
u'ERRO: Órgão diverge do Gescon. Não atualizado!'
)
convenio.save()
erros += 1
continue
if convenio.num_processo_sf != sigad:
self.add_message(
_(u"\tO contrato Gescon nº {numero} corresponde"
u" ao convênio SIGI {url}, mas o NUP sigad "
u"diverge (Gescon: {sigad_gescon}, "
u"SIGI: {sigad_sigi}). CORRIGIDO!").format(
numero=numero,
url=reverse('admin:%s_%s_change' % (
convenio._meta.app_label,
convenio._meta.model_name),
args=[convenio.id]),
sigad_gescon=sigad,
sigad_sigi=convenio.num_processo_sf
)
)
convenio.num_processo_sf = sigad
convenio.observacao_gescon += _(
u"Número do SIGAD atualizado.\n"
)
alertas += 1
if convenio.num_convenio != numero:
self.add_message(
_(u"\tO contrato Gescon ID {id} corresponde ao "
u"convênio SIGI {url}, mas o número do convênio"
u" diverge (Gescon: {numero_gescon}, SIGI: "
u"{numero_sigi}). CORRIGIDO!").format(
id=contrato['id'],
url=reverse('admin:%s_%s_change' % (
convenio._meta.app_label,
convenio._meta.model_name),
args=[convenio.id]
),
numero_gescon=numero,
numero_sigi=convenio.num_convenio
)
)
convenio.num_convenio = numero
convenio.observacao_gescon += _(
u"Número do convênio atualizado.\n"
)
alertas += 1
if contrato['objeto'] not in convenio.observacao:
convenio.observacao += "\n" + contrato['objeto']
convenio.observacao_gescon += _(
u"Observação atualizada.\n"
)
convenio.data_sigad = contrato['assinatura']
convenio.data_retorno_assinatura = contrato[
'inicioVigencia'
]
convenio.data_termino_vigencia = contrato[
'terminoVigencia'
]
convenio.data_pub_diario = contrato['publicacao']
if contrato['codTextoContrato']:
convenio.id_contrato_gescon = contrato[
'codTextoContrato'
]
else:
convenio.id_contrato_gescon = ""
try:
convenio.save()
except Exception as e:
self.add_message(
_(u"Ocorreu um erro ao salvar o convênio {url} no "
u"SIGI. Alguma informação do Gescon pode ter "
u"quebrado o sistema. Informe ao suporte. Erro:"
u"{errmsg}").format(
url=reverse('admin:%s_%s_change' % (
convenio._meta.app_label,
convenio._meta.model_name),
args=[convenio.id]
),
errmsg=e.message.decode("utf8")
)
)
erros += 1
continue
atualizados += 1
else:
self.add_message(_(u"\tExistem {count} convênios no SIGI "
u"que correspondem ao mesmo contrato no "
u"Gescon (contrato {numero}, sigad "
u"{sigad})").format(
count=chk,
numero=numero,
sigad=sigad
)
)
erros += 1
continue
self.add_message(
_(u"\t{novos} novos convenios adicionados ao SIGI, "
u"{atualizados} atualizados, sendo {alertas} com alertas, e "
u"{erros} reportados com erro.").format(
novos=novos,
atualizados=atualizados,
alertas=alertas,
erros=erros
)
)
self.save()
@classmethod
def load(cls):
obj, created = cls.objects.get_or_create(pk=1)
return obj

120
sigi/apps/convenios/reports.py

@ -26,12 +26,12 @@ class SemEquipamentosReport(object):
class ConvenioReport(ReportDefault):
title = _(u'Relatório de Convênios')
title = _(u'Relatório de Parcerias')
class band_page_header(ReportDefault.band_page_header):
label_top = ReportDefault.band_page_header.label_top
label_left = [0, 1.5, 7, 9, 11, 13, 15, 17]
label_left = [0, 2.5, 6, 8, 10, 12, 14, 16]
elements = list(ReportDefault.band_page_header.elements)
height = 4.7 * cm
@ -47,15 +47,15 @@ class ConvenioReport(ReportDefault):
top=label_top + 0.4 * cm,
),
Label(
text=_(u"Data de Adesão"),
text=_(u"Número do Convênio"),
left=label_left[2] * cm,
top=label_top,
width=2 * cm,
),
Label(
text=_(u"Número do Convênio"),
text=_(u"Projeto"),
left=label_left[3] * cm,
top=label_top,
top=label_top + 0.4 * cm,
width=2 * cm,
),
Label(
@ -71,14 +71,8 @@ class ConvenioReport(ReportDefault):
width=2 * cm,
),
Label(
text=_(u"Data de Aceite"),
text=_(u"Orgão"),
left=label_left[6] * cm,
top=label_top,
width=2 * cm,
),
Label(
text=_(u"Projeto"),
left=label_left[7] * cm,
top=label_top + 0.4 * cm,
width=2 * cm,
),
@ -89,7 +83,7 @@ class ConvenioReport(ReportDefault):
class band_detail(ReportDefault.band_detail):
label_left = [0, 1.5, 7, 9, 11, 13, 15, 17]
label_left = [0, 2.5, 6, 8, 10, 12, 14, 16]
elements = [
ObjectValue(
@ -101,13 +95,11 @@ class ConvenioReport(ReportDefault):
left=label_left[1] * cm
),
ObjectValue(
attribute_name='data_adesao',
left=label_left[2] * cm,
get_value=lambda instance:
instance.data_adesao.strftime('%d/%m/%Y') if instance.data_adesao is not None else '-'
attribute_name='num_convenio',
left=label_left[2] * cm
),
ObjectValue(
attribute_name='num_convenio',
attribute_name='projeto.sigla',
left=label_left[3] * cm
),
ObjectValue(
@ -123,30 +115,24 @@ class ConvenioReport(ReportDefault):
instance.data_pub_diario.strftime('%d/%m/%Y') if instance.data_pub_diario is not None else '-'
),
ObjectValue(
attribute_name='data_termo_aceite',
left=label_left[6] * cm,
get_value=lambda instance:
instance.data_termo_aceite.strftime('%d/%m/%Y') if instance.data_termo_aceite is not None else '-'
),
ObjectValue(
attribute_name='projeto.sigla',
left=label_left[7] * cm
attribute_name='casa_legislativa.nome',
left=label_left[6] * cm
),
]
groups = [
ReportGroup(attribute_name='casa_legislativa.municipio.uf',
band_header=ReportBand(
height=0.7 * cm,
elements=[
ObjectValue(attribute_name='casa_legislativa.municipio.uf',
get_Value=lambda instance: '%s: %s' % (_(u'Casa Legislativa'), instance.casa_legislativa.uf)
)
],
borders={'top': True},
)
)
]
#groups = [
# ReportGroup(attribute_name='casa_legislativa.municipio.uf',
# band_header=ReportBand(
# height=0.7 * cm,
# elements=[
# ObjectValue(attribute_name='casa_legislativa.municipio.uf',
# get_Value=lambda instance: '%s: %s' % (_(u'Casa Legislativa'), instance.casa_legislativa.uf)
# )
# ],
# borders={'top': True},
# )
# )
#]
class ConvenioReportSemAceite(ConvenioReport):
@ -154,7 +140,7 @@ class ConvenioReportSemAceite(ConvenioReport):
class band_page_header(ReportDefault.band_page_header):
label_top = ReportDefault.band_page_header.label_top
label_left = [0, 1.5, 7, 9, 11, 13, 15, 17]
label_left = [0, 2.5, 6, 8, 10, 12, 14, 16]
elements = list(ReportDefault.band_page_header.elements)
height = 4.7 * cm
@ -169,33 +155,27 @@ class ConvenioReportSemAceite(ConvenioReport):
left=label_left[1] * cm,
top=label_top + 0.4 * cm,
),
Label(
text=_(u"Data de Adesão"),
left=label_left[3] * cm,
top=label_top,
width=2 * cm,
),
Label(
text=_(u"Número do Convênio"),
left=label_left[4] * cm,
left=label_left[2] * cm,
top=label_top,
width=2 * cm,
),
Label(
text=_(u"Data do Convênio"),
left=label_left[5] * cm,
text=_(u"Projeto"),
left=label_left[3] * cm,
top=label_top,
width=2 * cm,
),
Label(
text=_(u"Data de Publicação"),
left=label_left[6] * cm,
text=_(u"Data do Convênio"),
left=label_left[4] * cm,
top=label_top,
width=2 * cm,
),
Label(
text=_(u"Projeto"),
left=label_left[7] * cm,
text=_(u"Orgão"),
left=label_left[5] * cm,
top=label_top + 0.4 * cm,
width=2 * cm,
),
@ -203,7 +183,7 @@ class ConvenioReportSemAceite(ConvenioReport):
class band_detail(ReportDefault.band_detail):
label_left = [0, 1.5, 7, 9, 11, 13, 15, 17]
label_left = [0, 2.5, 6, 8, 10, 12, 14, 16]
elements = [
ObjectValue(
@ -215,30 +195,22 @@ class ConvenioReportSemAceite(ConvenioReport):
left=label_left[1] * cm
),
ObjectValue(
attribute_name='data_adesao',
left=label_left[3] * cm,
get_value=lambda instance:
instance.data_adesao.strftime('%d/%m/%Y') if instance.data_adesao is not None else '-'
attribute_name='num_convenio',
left=label_left[2] * cm
),
ObjectValue(
attribute_name='num_convenio',
left=label_left[4] * cm
attribute_name='projeto.sigla',
left=label_left[3],
),
ObjectValue(
attribute_name='data_retorno_assinatura',
left=label_left[5] * cm,
left=label_left[4] * cm,
get_value=lambda instance:
instance.data_retorno_assinatura.strftime('%d/%m/%Y') if instance.data_retorno_assinatura is not None else '-'
),
ObjectValue(
attribute_name='data_pub_diario',
left=label_left[6] * cm,
get_value=lambda instance:
instance.data_pub_diario.strftime('%d/%m/%Y') if instance.data_pub_diario is not None else '-'
),
ObjectValue(
attribute_name='projeto.sigla',
left=label_left[7] * cm
attribute_name='casa_legislativa.nome',
left=label_left[5] * cm
),
]
@ -247,7 +219,7 @@ float_duas_casas = lambda instance: '%.2f' % (instance)
class ConvenioReportRegiao(ReportDefault):
title = _(u'Relatório de Convênios por Região')
title = _(u'Relatório de Parcerias por Região')
class band_page_header(ReportDefault.band_page_header):
label_top = ReportDefault.band_page_header.label_top
@ -298,16 +270,16 @@ class ConvenioReportRegiao(ReportDefault):
class ConvenioPorCMReport(ConvenioReport):
title = _(u'Relatório de Convênios por Câmara Municipal')
title = _(u'Relatório de Parcerias por Câmara Municipal')
class ConvenioPorALReport(ConvenioReport):
title = _(u'Relatório de Convênios por Assembléia Legislativa')
title = _(u'Relatório de Parcerias por Assembléia Legislativa')
class ConvenioReportSemAceiteCM(ConvenioReportSemAceite):
title = _(u'Relatório de Convênios por Câmara Municipal')
title = _(u'Relatório de Parcerias por Câmara Municipal')
class ConvenioReportSemAceiteAL(ConvenioReportSemAceite):
title = _(u'Relatório de Convênios por Assembléia Legislativa')
title = _(u'Relatório de Parcerias por Assembléia Legislativa')

16
sigi/apps/convenios/templates/convenios/change_list.html

@ -14,32 +14,34 @@
<a class="navbar-brand" href="#">Filtro de datas</a>
<p class="navbar-text">Use AAAA, AAAA-MM ou AAAA-MM-DD</p>
</div>
<br>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<form class="navbar-form navbar-left" role="extra-search" id="changelist-extrasearch" action="" method="get">
<div class="form-group">
<label for="data_retorno_assinatura__gte">{% trans 'Conveniadas a partir de' %}:</label>
<label for="data_retorno_assinatura__gte">{% trans 'Vigência a partir de' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_retorno_assinatura__gte" value="" id="data_retorno_assinatura__gte">
<label for="data_retorno_assinatura__lte">{% trans 'até' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_retorno_assinatura__lte" value="" id="data_retorno_assinatura__lte">
</div>
<div class="form-group">
<label for="data_sigad__gte">{% trans 'SIGAD a partir de' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_sigad__gte" value="" id="data_sigad__gte">
<label for="data_sigad__lte">{% trans 'até' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_sigad__lte" value="" id="data_sigad__lte">
</div>
<div class="form-group">
<label for="data_sigi__gte">{% trans 'SIGI a partir de' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_sigi__gte" value="" id="data_sigi__gte">
<label for="data_sigi__lte">{% trans 'até' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_sigi__lte" value="" id="data_sigi__lte">
</div>
<br>
<div class="form-group">
<label for="data_solicitacao__gte">{% trans 'E-mail a partir de' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_solicitacao__gte" value="" id="data_solicitacao__gte">
<label for="data_solicitacao__lte">{% trans 'até' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_solicitacao__lte" value="" id="data_solicitacao__lte">
</div>
<div class="form-group">
<label for="data_sigad__gte">{% trans 'SIGAD a partir de' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_sigad__gte" value="" id="data_sigad__gte">
<label for="data_sigad__lte">{% trans 'até' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_sigad__lte" value="" id="data_sigad__lte">
</div>
<button type="submit" class="btn btn-default navbar-btn ">
<span class="glyphicon glyphicon-search"></span>
</button>

18
sigi/apps/convenios/templates/convenios/importar_gescon.html

@ -0,0 +1,18 @@
{% extends 'admin/base_site.html' %}
{% load i18n %}
{% block content_title %}<h1>{% trans 'Importar dados do Gescon' %}</h1>{% endblock %}
{% block object-tools-items %}
<li class="nav-item"><a class="nav-link active" href="{% url 'importar-gescon' %}?action=importar">Importar</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'admin:convenios_gescon_change' gescon.id %}">Configurações</a></li>
{% endblock %}
{% block content %}
{% if gescon.ultima_importacao %}
<pre><code>{{ gescon.ultima_importacao }}</code></pre>
{% else %}
{% blocktrans %}
<p class="alert alert-danger"><strong>Nenhuma importação anterior foi realizada!</strong></p>
<p class="">Configure a conexão com o Gescon para realizar a primeira importação.</p>
{% endblocktrans %}
{% endif %}
{% endblock %}

1
sigi/apps/convenios/urls.py

@ -11,4 +11,5 @@ urlpatterns = patterns(
url(r'^convenio/carrinho/deleta_itens_carrinho$', 'deleta_itens_carrinho', name='deleta-itens-carrinho'), # tagerror
url(r'^convenio/csv/$', 'export_csv', name='convenios-csv'),
url(r'^reportsRegiao/(?P<regiao>\w+)/$', 'report_regiao', name='convenios-report_regiao_pdf'),
url(r'^importar/$', 'importar_gescon', name='importar-gescon'),
)

22
sigi/apps/convenios/views.py

@ -2,6 +2,8 @@
import csv
import datetime
from django.contrib import messages
from django.http.response import HttpResponseForbidden
import ho.pisa as pisa
from django.conf import settings
from django.core.paginator import Paginator, InvalidPage, EmptyPage
@ -13,7 +15,7 @@ from geraldo.generators import PDFGenerator
from sigi.apps.casas.models import Orgao
from sigi.apps.contatos.models import UnidadeFederativa
from sigi.apps.convenios.models import Convenio, Projeto
from sigi.apps.convenios.models import Convenio, Gescon, Projeto
from sigi.apps.convenios.reports import (ConvenioReport,
ConvenioReportSemAceite,
ConvenioPorCMReport,
@ -86,6 +88,8 @@ def carrinhoOrGet_for_qs(request):
if 'carrinho_convenios' in request.session:
ids = request.session['carrinho_convenios']
qs = Convenio.objects.filter(pk__in=ids)
qs = qs.order_by("casa_legislativa__municipio__uf", "casa_legislativa__municipio")
qs = get_for_qs(request.GET, qs)
else:
qs = Convenio.objects.all()
if request.GET:
@ -111,7 +115,8 @@ def adicionar_convenios_carrinho(request, queryset=None, id=None):
def excluir_carrinho(request):
if 'carrinho_convenios' in request.session:
del request.session['carrinho_convenios']
return HttpResponseRedirect('.')
messages.info(request, u'O carrinho foi esvaziado')
return HttpResponseRedirect('../../')
@login_required
def deleta_itens_carrinho(request):
@ -370,3 +375,16 @@ def export_csv(request):
csv_writer.writerow(lista)
return response
@login_required
def importar_gescon(request):
if not request.user.is_superuser:
return HttpResponseForbidden()
action = request.GET.get('action', "")
gescon = Gescon.load()
if action == 'importar':
gescon.importa_contratos()
return render(request, "convenios/importar_gescon.html", {'gescon': gescon})

49
sigi/apps/eventos/admin.py

@ -20,29 +20,13 @@
from django import forms
from django.contrib import admin
from django.db import models
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from sigi.apps.eventos.models import TipoEvento, Funcao, Evento, Equipe, Convite
from sigi.apps.eventos.models import (ModeloDeclaracao, Modulo, TipoEvento,
Funcao, Evento, Equipe, Convite, Anexo)
from sigi.apps.eventos.views import adicionar_eventos_carrinho
class EventoAdminForm(forms.ModelForm):
class Meta:
model = Evento
fields = ('tipo_evento', 'nome', 'descricao', 'virtual', 'solicitante',
'data_inicio', 'data_termino', 'carga_horaria',
'casa_anfitria', 'municipio', 'local', 'publico_alvo',
'status', 'data_cancelamento', 'motivo_cancelamento', )
def clean(self):
cleaned_data = super(EventoAdminForm, self).clean()
data_inicio = cleaned_data.get("data_inicio")
data_termino = cleaned_data.get("data_termino")
if data_inicio > data_termino:
raise forms.ValidationError(
_(u"Data término deve ser posterior à data inicio"),
code="invalid_period"
)
from sigi.apps.eventos.forms import EventoAdminForm
@admin.register(TipoEvento)
class TipoEventAdmin(admin.ModelAdmin):
@ -53,6 +37,10 @@ class FuncaoAdmin(admin.ModelAdmin):
list_display = ('nome', 'descricao',)
search_fields = ('nome', 'descricao',)
@admin.register(ModeloDeclaracao)
class ModeloDeclaracaoAdmin(admin.ModelAdmin):
list_display = ('nome', 'formato')
class EquipeInline(admin.TabularInline):
model = Equipe
@ -60,18 +48,26 @@ class ConviteInline(admin.TabularInline):
model = Convite
raw_id_fields = ('casa',)
class ModuloInline(admin.TabularInline):
model = Modulo
class AnexoInline(admin.TabularInline):
model = Anexo
exclude = ('data_pub',)
@admin.register(Evento)
class EventoAdmin(admin.ModelAdmin):
form = EventoAdminForm
date_hierarchy = 'data_inicio'
list_display = ('nome', 'tipo_evento', 'status', 'data_inicio',
'data_termino', 'municipio', 'solicitante')
list_filter = ('status', 'tipo_evento', 'virtual', 'municipio__uf',
'solicitante')
'data_termino', 'municipio', 'solicitante',
'total_participantes',)
list_filter = ('status', 'tipo_evento', 'tipo_evento__categoria', 'virtual',
'municipio__uf', 'solicitante')
raw_id_fields = ('casa_anfitria', 'municipio',)
search_fields = ('nome', 'tipo_evento__nome', 'casa_anfitria__search_text',
'municipio__search_text', 'solicitante')
inlines = (EquipeInline, ConviteInline)
inlines = (EquipeInline, ConviteInline, ModuloInline, AnexoInline)
actions = ['adicionar_eventos', ]
def adicionar_eventos(self, request, queryset):
@ -91,3 +87,8 @@ class EventoAdmin(admin.ModelAdmin):
return HttpResponseRedirect('.')
adicionar_eventos.short_description = _(u"Armazenar eventos no carrinho "
u"para exportar")
def lookup_allowed(self, lookup, value):
return (super(EventoAdmin, self).lookup_allowed(lookup, value) or
lookup in ['tipo_evento__nome__exact',
'tipo_evento__nome__contains'])

32
sigi/apps/eventos/forms.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from django import forms
from django.utils.translation import ugettext as _
from sigi.apps.eventos.models import ModeloDeclaracao, Evento
class EventoAdminForm(forms.ModelForm):
class Meta:
model = Evento
fields = ('tipo_evento', 'nome', 'descricao', 'virtual', 'solicitante',
'data_inicio', 'data_termino', 'carga_horaria',
'casa_anfitria', 'municipio', 'local', 'publico_alvo',
'total_participantes', 'status', 'data_cancelamento',
'motivo_cancelamento', )
def clean(self):
cleaned_data = super(EventoAdminForm, self).clean()
data_inicio = cleaned_data.get("data_inicio")
data_termino = cleaned_data.get("data_termino")
if data_inicio > data_termino:
raise forms.ValidationError(
_(u"Data término deve ser posterior à data inicio"),
code="invalid_period"
)
class SelecionaModeloForm(forms.Form):
modelo = forms.ModelChoiceField(
queryset=ModeloDeclaracao.objects.all(),
required=True,
label=_(u"Modelo de declaração"),
)

26
sigi/apps/eventos/migrations/0008_auto_20211104_1253.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('eventos', '0007_auto_20210417_0744'),
]
operations = [
migrations.AlterField(
model_name='evento',
name='data_inicio',
field=models.DateTimeField(null=True, verbose_name='Data/hora do In\xedcio', blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='evento',
name='data_termino',
field=models.DateTimeField(null=True, verbose_name='Data/hora do Termino', blank=True),
preserve_default=True,
),
]

20
sigi/apps/eventos/migrations/0009_tipoevento_categoria.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('eventos', '0008_auto_20211104_1253'),
]
operations = [
migrations.AddField(
model_name='tipoevento',
name='categoria',
field=models.CharField(default='E', max_length=1, verbose_name='Categoaria', choices=[(b'C', 'Curso'), (b'E', 'Encontro'), (b'O', 'Oficina'), (b'S', 'Semin\xe1rio'), (b'V', 'Visita')]),
preserve_default=False,
),
]

37
sigi/apps/eventos/migrations/0010_modulo.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('servidores', '0007_auto_20210430_0735'),
('eventos', '0009_tipoevento_categoria'),
]
operations = [
migrations.CreateModel(
name='Modulo',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('nome', models.CharField(max_length=100, verbose_name='Nome')),
('descricao', models.TextField(verbose_name='Descri\xe7\xe3o do m\xf3dulo')),
('tipo', models.CharField(max_length=1, verbose_name='Tipo', choices=[(b'A', 'Aula'), (b'P', 'Palestra'), (b'R', 'Apresenta\xe7\xe3o')])),
('inicio', models.DateTimeField(null=True, verbose_name='Data/hora de in\xedcio', blank=True)),
('termino', models.DateTimeField(null=True, verbose_name='Data/hora de t\xe9rmino', blank=True)),
('carga_horaria', models.PositiveIntegerField(default=0, verbose_name='carga hor\xe1ria')),
('qtde_participantes', models.PositiveIntegerField(default=0, help_text='Deixar Zero significa que todos os participantes do evento participaram do m\xf3dulo', verbose_name='n\xfamero de participantes')),
('apresentador', models.ForeignKey(related_name='modulo_apresentador', on_delete=django.db.models.deletion.PROTECT, verbose_name='Apresentador', blank=True, to='servidores.Servidor', null=True)),
('evento', models.ForeignKey(verbose_name='Evento', to='eventos.Evento')),
('monitor', models.ForeignKey(related_name='modulo_monitor', on_delete=django.db.models.deletion.PROTECT, blank=True, to='servidores.Servidor', help_text='Monitor, mediador, auxiliar, etc.', null=True, verbose_name='Monitor')),
],
options={
'verbose_name': 'M\xf3dulo do evento',
'verbose_name_plural': 'M\xf3dulos do evento',
},
bases=(models.Model,),
),
]

24
sigi/apps/eventos/migrations/0011_auto_20211117_0633.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('eventos', '0010_modulo'),
]
operations = [
migrations.AlterModelOptions(
name='modulo',
options={'ordering': ('inicio',), 'verbose_name': 'M\xf3dulo do evento', 'verbose_name_plural': 'M\xf3dulos do evento'},
),
migrations.AddField(
model_name='evento',
name='total_participantes',
field=models.PositiveIntegerField(default=0, help_text='Se informar quantidade de participantes na aba de convites, este campo ser\xe1 ajustado com a somat\xf3ria dos participantes naquela aba.', verbose_name='Total de participantes'),
preserve_default=True,
),
]

28
sigi/apps/eventos/migrations/0012_auto_20211117_0657.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db.models import Sum
def atualiza_participantes(apps, schema_editor):
if schema_editor.connection.alias != 'default':
return
Evento = apps.get_model('eventos', 'Evento')
for e in Evento.objects.all():
total = e.convite_set.aggregate(total=Sum('qtde_participantes'))
total = total['total']
if (total is not None) or (total > 0):
e.total_participantes = total
e.save()
class Migration(migrations.Migration):
dependencies = [
('eventos', '0011_auto_20211117_0633'),
]
operations = [
migrations.RunPython(atualiza_participantes),
]

30
sigi/apps/eventos/migrations/0013_modelodeclaracao.py

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import tinymce.models
class Migration(migrations.Migration):
dependencies = [
('eventos', '0012_auto_20211117_0657'),
]
operations = [
migrations.CreateModel(
name='ModeloDeclaracao',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('nome', models.CharField(max_length=100, verbose_name='Nome do modelo')),
('formato', models.CharField(default=b'A4 portrait', max_length=30, verbose_name='Formato da p\xe1gina', choices=[(b'A4 portrait', 'A4 retrato'), (b'A4 landscape', 'A4 paisagem'), (b'letter portrait', 'Carta retrato'), (b'letter landscape', 'Carta paisagem')])),
('margem', models.PositiveIntegerField(default=4, help_text='Margem da p\xe1gina em cent\xedmetros', verbose_name='Margem')),
('texto', tinymce.models.HTMLField(help_text='Use as seguintes marca\xe7\xf5es:<ul><li>{{ casa }} para o nome da Casa Legislativa / \xf3rg\xe3o</li><li>{{ nome }} para o nome do visitante</li><li>{{ data }} para a data de emiss\xe3o da declara\xe7\xe3o</li><li>{{ evento.data_inicio }} para a data/hora do in\xedcio da visita</li><li>{{ evento.data_termino }} para a data/hora do t\xe9rmino da visita</li><li>{{ evento.nome }} para o nome do evento</li><li>{{ evento.descricao }} para a descri\xe7\xe3o do evento</li></ul>', verbose_name='Texto da declara\xe7\xe3o')),
],
options={
'verbose_name': 'modelo de declara\xe7\xe3o',
'verbose_name_plural': 'modelos de declara\xe7\xe3o',
},
bases=(models.Model,),
),
]

21
sigi/apps/eventos/migrations/0014_auto_20211124_0736.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import tinymce.models
class Migration(migrations.Migration):
dependencies = [
('eventos', '0013_modelodeclaracao'),
]
operations = [
migrations.AlterField(
model_name='modelodeclaracao',
name='texto',
field=tinymce.models.HTMLField(help_text='Use as seguintes marca\xe7\xf5es:<ul><li>{{ casa.nome }} para o nome da Casa Legislativa / \xf3rg\xe3o</li><li>{{ casa.municipio.uf.sigla }} para a sigla da UF da Casa legislativa</li><li>{{ nome }} para o nome do visitante</li><li>{{ data }} para a data de emiss\xe3o da declara\xe7\xe3o</li><li>{{ evento.data_inicio }} para a data/hora do in\xedcio da visita</li><li>{{ evento.data_termino }} para a data/hora do t\xe9rmino da visita</li><li>{{ evento.nome }} para o nome do evento</li><li>{{ evento.descricao }} para a descri\xe7\xe3o do evento</li></ul>', verbose_name='Texto da declara\xe7\xe3o'),
preserve_default=True,
),
]

29
sigi/apps/eventos/migrations/0015_anexo.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('eventos', '0014_auto_20211124_0736'),
]
operations = [
migrations.CreateModel(
name='Anexo',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('arquivo', models.FileField(max_length=500, upload_to=b'apps/eventos/anexo/arquivo')),
('descricao', models.CharField(max_length=b'70', verbose_name='descri\xe7\xe3o')),
('data_pub', models.DateTimeField(default=datetime.datetime.now, verbose_name='data da publica\xe7\xe3o do anexo')),
('evento', models.ForeignKey(verbose_name='evento', to='eventos.Evento')),
],
options={
'ordering': ('-data_pub',),
},
bases=(models.Model,),
),
]

172
sigi/apps/eventos/models.py

@ -1,16 +1,32 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import random
from django.db import models
from django.db.models import Sum
from django.utils.functional import lazy
from django.utils.translation import ugettext as _
from django.contrib.contenttypes import generic
from sigi.apps.casas.models import Orgao
from sigi.apps.contatos.models import Municipio
from sigi.apps.servidores.models import Servidor
from django.core.exceptions import ValidationError
from tinymce.models import HTMLField
class TipoEvento(models.Model):
nome = models.CharField(_(u"Nome"), max_length=100)
CATEGORIA_CHOICES = (
('C', _(u'Curso')),
('E', _(u'Encontro')),
('O', _(u'Oficina')),
('S', _(u'Seminário')),
('V', _(u'Visita')),
)
nome = models.CharField(_(u"Nome"), max_length=100)
categoria = models.CharField(
_(u'Categoaria'),
max_length=1,
choices=CATEGORIA_CHOICES
)
class Meta:
ordering = ("nome",)
verbose_name, verbose_name_plural = _(u"Tipo de evento"), _(u"Tipos de evento")
@ -35,8 +51,16 @@ class Evento(models.Model):
descricao = models.TextField(_(u"Descrição do evento"))
virtual = models.BooleanField(_("Virtual"), default=False)
solicitante = models.CharField(_(u"Solicitante"), max_length=100)
data_inicio = models.DateField(_(u"Data de início"))
data_termino = models.DateField(_(u"Data de término"))
data_inicio = models.DateTimeField(
_(u"Data/hora do Início"),
null=True,
blank=True
)
data_termino = models.DateTimeField(
_(u"Data/hora do Termino"),
null=True,
blank=True
)
carga_horaria = models.PositiveIntegerField(
_(u"carga horária"),
default=0
@ -54,6 +78,13 @@ class Evento(models.Model):
)
local = models.TextField(_(u"Local do evento"), blank=True)
publico_alvo = models.TextField(_(u"Público alvo"), blank=True)
total_participantes = models.PositiveIntegerField(
_(u"Total de participantes"),
default=0,
help_text=_(u"Se informar quantidade de participantes na aba de "
u"convites, este campo será ajustado com a somatória "
u"dos participantes naquela aba.")
)
status = models.CharField(_(u"Status"), max_length=1, choices=STATUS_CHOICES)
data_cancelamento = models.DateField(_(u"Data de cancelamento"), blank=True, null=True)
motivo_cancelamento = models.TextField(_(u"Motivo do cancelamento"), blank=True)
@ -74,7 +105,12 @@ class Evento(models.Model):
self.data_cancelamento = None
self.motivo_cancelamento = ""
if self.data_inicio > self.data_termino:
raise ValidationError(_(u"Data de término deve ser posterior à data de início"))
raise ValidationError(_(u"Data de término deve ser posterior à "
u"data de início"))
total = self.convite_set.aggregate(total=Sum('qtde_participantes'))
total = total['total']
if (total is not None) or (total > 0):
self.total_participantes = total
return super(Evento, self).save(*args, **kwargs)
class Funcao(models.Model):
@ -143,4 +179,128 @@ class Convite(models.Model):
class Meta:
ordering = ('evento', 'casa', '-data_convite')
unique_together = ('evento', 'casa')
verbose_name, verbose_name_plural = _(u"Casa convidada"), _(u"Casas convidadas")
verbose_name = _(u"Casa convidada")
verbose_name_plural = _(u"Casas convidadas")
class Modulo(models.Model):
TIPO_CHOICES = (
('A', _(u'Aula')),
('P', _(u'Palestra')),
('R', _(u'Apresentação')),
)
evento = models.ForeignKey(Evento, verbose_name=_(u"Evento"))
nome = models.CharField(_(u"Nome"), max_length=100)
descricao = models.TextField(_(u"Descrição do módulo"))
tipo = models.CharField(_(u'Tipo'), max_length=1, choices=TIPO_CHOICES)
inicio = models.DateTimeField(
_(u"Data/hora de início"),
null=True,
blank=True
)
termino = models.DateTimeField(
_(u"Data/hora de término"),
null=True,
blank=True
)
carga_horaria = models.PositiveIntegerField(
_(u"carga horária"),
default=0
)
apresentador = models.ForeignKey(
Servidor,
on_delete=models.PROTECT,
related_name="modulo_apresentador",
null=True,
blank=True,
verbose_name=_(u"Apresentador"),
)
monitor = models.ForeignKey(
Servidor,
on_delete=models.PROTECT,
related_name="modulo_monitor",
null=True,
blank=True,
verbose_name=_(u"Monitor"),
help_text=_(u"Monitor, mediador, auxiliar, etc.")
)
qtde_participantes = models.PositiveIntegerField(
_(u"número de participantes"),
default=0,
help_text=_(u"Deixar Zero significa que todos os participantes "
u"do evento participaram do módulo"),
)
class Meta:
ordering = ('inicio',)
verbose_name = _(u"Módulo do evento")
verbose_name_plural = _(u"Módulos do evento")
def __unicode__(self):
return _(u"{nome} ({tipo})").format(
nome=self.nome,
tipo=self.get_tipo_display()
)
class ModeloDeclaracao(models.Model):
FORMATO_CHOICES = (
('A4 portrait', _(u"A4 retrato")),
('A4 landscape', _(u"A4 paisagem")),
('letter portrait', _(u"Carta retrato")),
('letter landscape', _(u"Carta paisagem"))
)
nome = models.CharField(_(u"Nome do modelo"), max_length=100)
formato = models.CharField(
_(u"Formato da página"),
max_length=30,
choices=FORMATO_CHOICES,
default=FORMATO_CHOICES[0][0]
)
margem = models.PositiveIntegerField(
_(u"Margem"),
help_text=_(u"Margem da página em centímetros"),
default=4
)
texto = HTMLField(
_(u"Texto da declaração"),
help_text=_(u"Use as seguintes marcações:<ul><li>{{ casa.nome }} para o"
u" nome da Casa Legislativa / órgão</li>"
u"<li>{{ casa.municipio.uf.sigla }} para a sigla da UF da "
u"Casa legislativa</li><li>{{ nome }} "
u"para o nome do visitante</li><li>{{ data }} para a data "
u"de emissão da declaração</li><li>{{ evento.data_inicio }}"
u" para a data/hora do início da visita</li>"
u"<li>{{ evento.data_termino }} para a data/hora do "
u"término da visita</li><li>{{ evento.nome }} para o nome "
u"do evento</li><li>{{ evento.descricao }} para a descrição"
u" do evento</li></ul>")
)
class Meta:
verbose_name = _(u"modelo de declaração")
verbose_name_plural = _(u"modelos de declaração")
def __unicode__(self):
return _(u"{nome} ({formato})").format(
nome=self.nome,
formato=self.get_formato_display()
)
class Anexo(models.Model):
evento = models.ForeignKey(
Evento,
on_delete=models.CASCADE,
verbose_name=_(u'evento')
)
# caminho no sistema para o documento anexo
arquivo = models.FileField(upload_to='apps/eventos/anexo/arquivo', max_length=500)
descricao = models.CharField(_(u'descrição'), max_length='70')
data_pub = models.DateTimeField(
_(u'data da publicação do anexo'),
default=datetime.now
)
class Meta:
ordering = ('-data_pub',)
def __unicode__(self):
return unicode("%s publicado em %s" % (self.descricao, self.data_pub))

10
sigi/apps/eventos/templates/admin/eventos/evento/change_form.html

@ -0,0 +1,10 @@
{% extends "base_change_form.html" %}
{% load i18n %}
{% block object-tools-items %}
<li><a href="declaracao/">
<span class="glyphicon glyphicon-print"></span>
{% trans 'Declaração' %}
</a></li>
{{ block.super }}
{% endblock %}

0
sigi/apps/eventos/templates/admin/eventos/change_list.html → sigi/apps/eventos/templates/admin/eventos/evento/change_list.html

24
sigi/apps/eventos/templates/eventos/declaracao_pdf.html

@ -0,0 +1,24 @@
{% extends 'base_report.html' %}
{% load i18n %}
{% block pagesize %}{{ pagesize }}{% endblock pagesize %}
{% block pagemargin %}4cm {{ pagemargin }}cm {{ pagemargin }}cm 2cm{% endblock pagemargin %}
{% block report %}
{% for convite in evento.convite_set.all %}
{% with convite.casa as casa %}
{% for nome in convite.nomes_participantes.splitlines %}
{% block text_body %}{% endblock %}
<pdf:nextpage />
{% endfor %}
{% endwith %}
{% endfor %}
{% endblock %}
{%block page_foot%}
<table>
<tr>
<td class="footer-left">{% trans 'Emissão:' %} {% now "d/m/Y H:i:s" %}</td>
</tr>
</table>
{% endblock %}

25
sigi/apps/eventos/templates/eventos/seleciona_modelo.html

@ -0,0 +1,25 @@
{% extends "admin/base_site.html" %}
{% load i18n bootstrap3 %}
{% block content_title %}
<h1 class="pull-left">{% trans 'Emitir declaração de comparecimento' %}</h1>
{% endblock %}
{% block content %}
{% if error %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endif %}
<div id="content-main">
<form action="" method="post">{% csrf_token %}
{% csrf_token %}
<div class="form-group">
{% bootstrap_form form %}
</div>
<input type="submit" value="Imprimir" class="btn btn-primary"/>
<a class="btn btn-danger" role="button" href="{% url 'admin:eventos_evento_change' evento_id %}">{% trans "Voltar" %}</a>
</form>
</div>
{% endblock %}

3
sigi/apps/eventos/urls.py

@ -15,5 +15,8 @@ urlpatterns = patterns(
url(r'^evento/carrinho/deleta_itens_carrinho$', 'deleta_itens_carrinho',
name='deleta-itens-carrinho-evento'), # Error
url(r'^evento/csv/$', 'export_csv', name='evento-export-csv'), # Error
url(r'^evento/(?P<id>\w+)/declaracao/$', 'declaracao',
name='evento-declaracao'),
)

103
sigi/apps/eventos/views.py

@ -21,18 +21,21 @@
import calendar
import datetime
import locale
import csv
from django import template
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.shortcuts import get_object_or_404, render
from django.utils import translation
from django.utils.translation import ungettext, ugettext as _
from sigi.apps.eventos.models import Evento, Equipe, Convite
from sigi.apps.servidores.models import Servidor
from sigi.shortcuts import render_to_pdf
import csv
from django.http.response import JsonResponse, HttpResponse
from django.template import Template, Context
from sigi.apps.eventos.models import Evento, Equipe, Convite, Modulo
from sigi.apps.eventos.forms import SelecionaModeloForm
from sigi.apps.servidores.models import Servidor
from sigi.shortcuts import render_to_pdf, pdf_renderer
@login_required
def calendario(request):
@ -62,17 +65,17 @@ def calendario(request):
for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa,
data_inicio__month=mes_pesquisa).order_by('data_inicio'):
start = dates.index(evento.data_inicio)
if not evento.data_termino in dates:
start = dates.index(evento.data_inicio.date())
if not evento.data_termino.date() in dates:
lastday = dates[-1]
while lastday < evento.data_termino:
while lastday < evento.data_termino.date():
lastday = lastday + datetime.timedelta(days=1)
dates.append(lastday)
eventos.append({'evento': evento, 'start': start})
# Calcula a distância dos eventos para as bordas do calendário
for evento in eventos:
end = dates.index(evento['evento'].data_termino)
end = dates.index(evento['evento'].data_termino.date())
evento['duration'] = end-evento['start']+1
evento['close'] = len(dates)-end-1
@ -84,10 +87,10 @@ def calendario(request):
for linha in linhas:
sobrepoe = False
for e in linha:
if (((evento['evento'].data_inicio >= e['evento'].data_inicio) and
(evento['evento'].data_inicio <= e['evento'].data_termino)) or
((evento['evento'].data_termino >= e['evento'].data_inicio) and
(evento['evento'].data_termino <= e['evento'].data_termino))):
if (((evento['evento'].data_inicio.date() >= e['evento'].data_inicio.date()) and
(evento['evento'].data_inicio.date() <= e['evento'].data_termino.date())) or
((evento['evento'].data_termino.date() >= e['evento'].data_inicio.date()) and
(evento['evento'].data_termino.date() <= e['evento'].data_termino.date()))):
sobrepoe = True
break
if not sobrepoe:
@ -106,7 +109,7 @@ def calendario(request):
if anterior is None:
anterior = evento
continue
anterior['close'] = (evento['evento'].data_inicio - anterior['evento'].data_termino).days-1
anterior['close'] = (evento['evento'].data_inicio.date() - anterior['evento'].data_termino.date()).days-1
evento['start'] = 0
anterior = evento
@ -297,6 +300,13 @@ def deleta_itens_carrinho(request):
@login_required
def export_csv(request):
def rm_rows(lista,reg):
for a in lista:
if a in lista:
reg.pop(a,None)
else:
pass
def serialize(r, field):
value = (getattr(r, 'get_{0}_display'.format(field.name), None) or
getattr(r, field.name, ""))
@ -315,22 +325,50 @@ def export_csv(request):
max_equipe = max([e.equipe_set.count() for e in eventos])
mun_casa = u'Município da Casa Anfitriã'.encode('utf8')
uf_casa = u'UF da Casa Anfitriã'.encode('utf8')
reg_casa = u'Região da Casa Anfitriã'.encode('utf8')
head = [f.verbose_name.encode('utf8') for f in Evento._meta.fields]
head.extend([mun_casa, uf_casa, reg_casa])
head.extend([f.verbose_name.encode('utf8')+"_{0}".format(i+1)
for i in range(max_equipe) for f in Equipe._meta.fields
if f.name not in ('id', 'evento')])
head.extend([f.verbose_name.encode('utf8') for f in Convite._meta.fields
if f.name not in ('id', 'evento')])
head.extend([f.verbose_name.encode('utf8') for f in Modulo._meta.fields
if f.name not in ('id', 'evento')])
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=eventos.csv'
rm_list = ['Descrição do evento', 'Local do evento', 'Público alvo', 'Motivo do cancelamento', 'Descrição do módulo']
for a in head:
if 'Observações_' in a:
rm_list.append(a)
for a in rm_list:
if a in head:
head.remove(a)
else:
pass
writer = csv.DictWriter(response, fieldnames=head)
writer.writeheader()
for evento in eventos:
reg = {f.verbose_name.encode('utf8'): serialize(evento, f)
for f in Evento._meta.fields}
if evento.casa_anfitria is None:
reg[mun_casa] = ""
reg[uf_casa] = ""
reg[reg_casa] = ""
else:
reg[mun_casa] = evento.casa_anfitria.municipio.nome.encode('utf8')
reg[uf_casa] = evento.casa_anfitria.municipio.uf.sigla.\
encode('utf8')
reg[reg_casa] = evento.casa_anfitria.municipio.uf.\
get_regiao_display().encode('utf8')
idx = 1
for membro in evento.equipe_set.all():
reg.update(
@ -347,9 +385,46 @@ def export_csv(request):
for f in Convite._meta.fields
if f.name not in ('id', 'evento')}
)
rm_rows(rm_list,reg)
writer.writerow(reg)
if evento.convite_set.count() == 0:
rm_rows(rm_list,reg)
writer.writerow(reg)
return response
@login_required
def declaracao(request, id):
if request.method == 'POST':
form = SelecionaModeloForm(request.POST)
if form.is_valid():
evento = get_object_or_404(Evento, id=id)
modelo = form.cleaned_data['modelo']
template_string = (
"""
{% extends "eventos/declaracao_pdf.html" %}
{% block text_body %}""" +
modelo.texto + """
{% endblock %}
"""
)
context = Context(
{'pagesize': modelo.formato,
'pagemargin': modelo.margem,
'evento': evento,
'data': datetime.date.today(),
}
)
template = Template(template_string)
# return HttpResponse(template.render(context))
return pdf_renderer(template, context, 'declaracao.pdf')
else:
form = SelecionaModeloForm()
return render(
request,
'eventos/seleciona_modelo.html',
{'form': form, 'evento_id': id}
)

16
sigi/apps/home/templatetags/menu_conf.yaml

@ -45,8 +45,18 @@ main_menu:
url: ocorrencias/ocorrencia/?minhas=S&status__in=1,2
- title: Eventos
children:
- title: Eventos
- title: Todos os eventos
url: eventos/evento/
- title: Cursos
url: eventos/evento/?tipo_evento__categoria__exact=C
- title: Encontros
url: eventos/evento/?tipo_evento__categoria__exact=E
- title: Oficinas
url: eventos/evento/?tipo_evento__categoria__exact=O
- title: Seminários
url: eventos/evento/?tipo_evento__categoria__exact=S
- title: Visitas Interlegis
url: eventos/evento/?tipo_evento__categoria__exact=V
- title: Calendário mensal
url: eventos/calendario
- title: Alocação de equipe
@ -65,6 +75,8 @@ main_menu:
url: convenios/statusconvenio/
- title: Tipos de serviço SEIT
url: servicos/tiposervico/
- title: Tipos de projeto
url: convenios/projeto/
- title: Categorias de ocorrências
url: ocorrencias/categoria/
- title: Tipos de contato
@ -73,6 +85,8 @@ main_menu:
url: eventos/tipoevento/
- title: Funções na equipe de eventos
url: eventos/funcao/
- title: Modelos de declaração
url: eventos/modelodeclaracao/
# Removidos

2
sigi/apps/metas/templates/metas/openmap.html

@ -46,7 +46,7 @@
<a href="#" style="float: right;" id="options-toggler" type="button" data-toggle="collapse" data-target="#filterbox" aria-expanded="false" aria-controls="collapseExample">
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
<div class="collapse" id="filterbox">
<div class="collapse in" id="filterbox">
<form id="searchform" class="form-inline ui-front">
<input type="text" id="search-text" class="form-control" placeholder="Procurar" aria-label="Procurar" aria-describedby="basic-addon2">
</form>

4
sigi/apps/metas/templatetags/mapa_tags.py

@ -3,7 +3,7 @@ from django import template
from django.utils.safestring import mark_safe
from sigi.apps.casas.models import Orgao
from sigi.apps.metas.views import parliament_summary
from sigi.apps.metas.views import openmap
register = template.Library()
@ -14,7 +14,7 @@ def descricao_servicos(casa):
if not isinstance(casa, Orgao):
return ""
summary = parliament_summary(casa)
summary = openmap(casa)
result = ''.join('<li>%s</li>' % info for info in summary['info'])
return mark_safe(result)
descricao_servicos.is_safe = True

14
sigi/apps/ocorrencias/admin.py

@ -85,13 +85,13 @@ class OcorrenciaAdmin(BaseModelAdmin):
def get_changelist(self, request, **kwargs):
return OcorrenciaChangeList
def get_readonly_fields(self, request, obj=None):
fields = list(self.readonly_fields)
if obj is not None:
fields.extend(['casa_legislativa', 'categoria', 'tipo_contato', 'assunto', 'status', 'descricao', ])
if obj.status in [Ocorrencia.STATUS_RESOLVIDO, Ocorrencia.STATUS_FECHADO, Ocorrencia.STATUS_DUPLICADO]: # Fechados
fields.append('prioridade')
return fields
# def get_readonly_fields(self, request, obj=None):
# fields = list(self.readonly_fields)
# if obj is not None:
# fields.extend(['casa_legislativa', 'categoria', 'tipo_contato', 'assunto', 'status', 'descricao', ])
# if obj.status in [Ocorrencia.STATUS_RESOLVIDO, Ocorrencia.STATUS_FECHADO, Ocorrencia.STATUS_DUPLICADO]: # Fechados
# fields.append('prioridade')
# return fields
def get_fieldsets(self, request, obj=None):
if obj is None:

77
sigi/apps/servicos/admin.py

@ -11,6 +11,7 @@ from sigi.apps.casas.admin import FuncionariosInline, GerentesInterlegisFilter
from sigi.apps.casas.models import Orgao
from sigi.apps.servicos.models import (Servico, LogServico, CasaAtendida,
TipoServico)
from sigi.apps.servicos.views import adicionar_servicos_carrinho
from sigi.apps.utils.base_admin import BaseModelAdmin
@ -90,12 +91,28 @@ class DataUtimoUsoFilter(admin.SimpleListFilter):
timedelta(days=0)
)
queryset = queryset.filter(data_ultimo_uso__range=(de, ate))
print (de, ate, queryset.count())
return queryset
class ServicoAtivoFilter(admin.SimpleListFilter):
title = _(u"Serviço ativo")
parameter_name = 'ativo'
def lookups(self, request, model_admin):
return (
('ativo', _(u"Ativo")),
('desativado', _(u"Desativado")),
)
def queryset(self, request, queryset):
if self.value() is not None:
if self.value() == 'ativo':
queryset = queryset.filter(data_desativacao__isnull=True)
else:
queryset = queryset.filter(data_desativacao__isnull=False)
return queryset
class ServicoAdmin(BaseModelAdmin):
change_list_template = "servico/change_list.html"
form = ServicoFormAdmin
actions = ['calcular_data_uso', ]
list_display = ('casa_legislativa', 'get_codigo_interlegis', 'get_uf', 'tipo_servico', 'hospedagem_interlegis',
@ -116,11 +133,13 @@ class ServicoAdmin(BaseModelAdmin):
list_filter = (
'tipo_servico',
'hospedagem_interlegis',
ServicoAtivoFilter,
DataUtimoUsoFilter,
('casa_legislativa__gerentes_interlegis', GerentesInterlegisFilter),
'casa_legislativa__municipio__uf',
)
list_display_links = []
actions = ['adicionar_servicos']
ordering = ('casa_legislativa__municipio__uf', 'casa_legislativa', 'tipo_servico',)
inlines = (LogServicoInline,)
search_fields = ('casa_legislativa__search_text',)
@ -153,6 +172,22 @@ class ServicoAdmin(BaseModelAdmin):
get_link_erro.short_description = _(u"Erro na atualização")
get_link_erro.admin_order_field = 'erro_atualizacao'
def adicionar_servicos(self, request, queryset):
if 'carrinho_servicos' in request.session:
q1 = len(request.session['carrinho_servicos'])
else:
q1 = 0
adicionar_servicos_carrinho(request, queryset=queryset)
q2 = len(request.session['carrinho_servicos'])
quant = q2 - q1
if quant:
self.message_user(request, str(q2 - q1) + _(u" Serviços adicionados no carrinho"))
else:
self.message_user(request, _(u"Os Serviços selecionados já foram adicionadas anteriormente"))
return HttpResponseRedirect('.')
adicionar_servicos.short_description = _(u"Armazenar serviços no carrinho para exportar")
def calcular_data_uso(self, request, queryset):
for servico in queryset:
servico.atualiza_data_uso()
@ -170,7 +205,7 @@ class ServicoAdmin(BaseModelAdmin):
def lookup_allowed(self, lookup, value):
return super(ServicoAdmin, self).lookup_allowed(lookup, value) or \
lookup in ['casa_legislativa__municipio__uf__codigo_ibge__exact']
lookup in ['casa_legislativa__municipio__uf__codigo_ibge__exact', ]
def add_view(self, request, form_url='', extra_context=None):
id_casa = request.GET.get('id_casa', None)
@ -219,6 +254,34 @@ class ServicoAdmin(BaseModelAdmin):
return obj
def changelist_view(self, request, extra_context=None):
from sigi.apps.convenios.views import normaliza_data
request.GET._mutable = True
normaliza_data(request.GET, 'data_ativacao__gte')
normaliza_data(request.GET, 'data_ativacao__lte')
request.GET._mutable = False
return super(ServicoAdmin, self).changelist_view(
request,
extra_context={'query_str': '?' + request.META['QUERY_STRING']}
)
def adicionar_servicos(self, request, queryset):
if 'carrinho_servicos' in request.session:
q1 = len(request.session['carrinho_servicos'])
else:
q1 = 0
adicionar_servicos_carrinho(request, queryset=queryset)
q2 = len(request.session['carrinho_servicos'])
quant = q2 - q1
if quant:
self.message_user(request, str(q2 - q1) + _(u" Convênios adicionados no carrinho"))
else:
self.message_user(request, _(u"Os Convênios selecionados já foram adicionadas anteriormente"))
return HttpResponseRedirect('.')
adicionar_servicos.short_description = _(u"Armazenar Serviços no carrinho para exportar")
class ContatosInline(FuncionariosInline):
can_delete = False # Equipe do SEIT não pode excluir pessoas de contato
@ -226,6 +289,14 @@ class ContatosInline(FuncionariosInline):
def get_queryset(self, request):
return self.model.objects.all()
def get_queryset(self, request):
return (self.model.objects.exclude(desativado=True)
.extra(select={'ult_null': 'ult_alteracao is null'})
.order_by('ult_null', '-ult_alteracao')
# A função extra foi usada para quando existir um registro com o campo igual a null não aparecer na frente dos mais novos
)
class CasaAtendidaAdmin(BaseModelAdmin):
actions = None
list_display = ('codigo_interlegis', 'nome', 'get_servicos',)

4
sigi/apps/servicos/templates/admin/servicos/servico/change_list.html

@ -1,4 +0,0 @@
{% extends "admin/change_list.html" %}
{% block object-tools-items %}
{% endblock %}

33
sigi/apps/servicos/templates/servico/change_list.html

@ -0,0 +1,33 @@
{% extends "change_list_with_cart.html" %}
{% load i18n %}
{% block extra_search %}
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Filtro de datas</a>
<p class="navbar-text">Use AAAA, AAAA-MM ou AAAA-MM-DD</p>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<form class="navbar-form navbar-left" role="extra-search" id="changelist-extrasearch" action="" method="get">
<div class="form-group">
<label for="data_ativacao__gte">{% trans 'Ativados a partir de' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_ativacao__gte" value="" id="data_ativacao__gte">
<label for="data_ativacao__lte">{% trans 'até' %}:</label>
<input type="text" class="form-control search-query" size="10" name="data_ativacao__lte" value="" id="data_ativacao__lte">
</div>
<button type="submit" class="btn btn-default navbar-btn ">
<span class="glyphicon glyphicon-search"></span>
</button>
</form>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{% endblock %}

100
sigi/apps/servicos/templates/servicos/carrinho.html

@ -0,0 +1,100 @@
{% extends "admin/carrinho.html" %}
{% load admin_list i18n %}
{% block extrastyle %}
{{ block.super }}
{% include "admin/tabs_style.html" %}
{% endblock %}
{% block title %}{% trans 'Serviços no Carrinho | SIGI' %}{% endblock %}
{% block content_title %}<h1>{% trans 'Serviços no Carrinho' %}</h1>{% endblock %}
{% block mensagem%}
<ul class="messagelist">
{%if carIsEmpty%}
<li class="warning">{% trans 'O carrinho está vazio, sendo assim todos os Serviços entram na lista para exportação de acordo com os filtros aplicados.' %}</li>
{%else%}
<li>{{paginas.paginator.count}} {% trans 'Serviços no carrinho' %}.</li>
{%endif%}
</ul>
{% endblock %}
{% block action %}deleta_itens_carrinho{% endblock %}
{% block tabela %}
<table class="table table-striped">
<thead class="thead-dark">
<tr>
{%if not carIsEmpty%}
<th class="sorted ascending"><!-- <input type="checkbox" id="action-toggle" style="display: inline;">-->
</th>
{% endif %}
<th class="sorted ascending">{% trans 'Casa Legislativa' %}</th>
<th class="sorted ascending">{% trans 'UF' %}</th>
<th class="sorted ascending">{% trans 'Email' %}</th>
<th class="sorted ascending">{% trans 'Telefone' %}</th>
<th class="sorted ascending">{% trans 'Contato Interlegis' %}</th>
<th class="sorted ascending">{% trans 'Tipo de Serviço' %}</th>
<th class="sorted ascending">{% trans 'Data Ativação' %}</th>
</tr>
</thead>
<tbody>
{% for servico in paginas.object_list %}
<tr>
{%if not carIsEmpty%}
<th><input type="checkbox" name="_selected_action"
value="{{servico.id|safe}}" class="action-select" /></th>
{% endif %}
<td style="text-align: left;">{{servico.casa_legislativa}}</td>
<td>{{servico.casa_legislativa.municipio.uf.sigla}}</td>
<td>{{servico.casa_legislativa.email}}</td>
<td>{{servico.casa_legislativa.telefone}}</td>
<td>{{servico.casa_legislativa.contato_interlegis}}</td>
<td>{{servico.casa_legislativa.contato_interlegis.email}}</td>
<td>{{servico.tipo_servico}}</td>
<td>{{servico.data_ativacao}}</td></tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% block botoes %}
<ul class="nav nav-tabs" role="tablist">
<li class="active" role="presentation"><a href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">{% trans 'Arquivo CSV (Excel, Calc)' %}</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="tabs-2">
<form action="../csv/{{query_str}}" method="post">{% csrf_token %}
<fieldset>
<legend>{% trans 'Escolha os atributos para exportar' %}</legend>
<ul id="sortable" class="tabs-conteudo">
<li>
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<input type="checkbox" name="itens_csv_selected" value="Casa Legislativa"
class="action-select" checked="checked" />
<label>{% trans 'Casa Legislativa' %}</label>
</li>
<li>
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<input type="checkbox" name="itens_csv_selected" value="Contato Interlegis"
class="action-select" checked="checked" />
<label>{% trans 'Contato Interlegis' %}</label>
</li>
<li>
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<input type="checkbox" name="itens_csv_selected" value="Produto"
class="action-select" checked="checked" />
<label>{% trans 'Produto' %}</label>
</li>
<li>
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<input type="checkbox" name="itens_csv_selected" value="Data de Ativação"
class="action-select" checked="checked" />
<label>{% trans 'Data de Ativação' %}</label>
</li>
</ul>
</fieldset>
<input type="submit" value="Exportar CSV" type="button" class="btn btn-primary"/>
</form>
</div>
</div>
{% endblock %}

4
sigi/apps/servicos/urls.py

@ -9,6 +9,10 @@ urlpatterns = patterns(
'sigi.apps.servicos.views',
url(r'^manifesta/$', 'casa_manifesta_view', name="casa-manifesta-view"),
url(r'^servico/carrinho/$', 'visualizar_carrinho', name='visualizar-carrinho'),
url(r'^servico/carrinho/excluir_carrinho/$', 'excluir_carrinho', name='excluir-carrinho'), # tagerror
url(r'^servico/carrinho/deleta_itens_carrinho$', 'deleta_itens_carrinho', name='deleta-itens-carrinho'), # tagerror
url(r'^servico/csv/$', 'export_csv', name='servicos-csv'),
url(r'^munatenjson/(?P<servico>\w+)/$', 'municipios_atendidos', name="municipios-atendidos"),
url(r'^mapa/(?P<servico>\w+)/$', MapaView.as_view(), name="servicos-mapa"),
# url(r'^listacasas/(?P<sigla>\w+)', 'casas_usam_servico', name="casas-usam-servico"),

194
sigi/apps/servicos/views.py

@ -1,18 +1,24 @@
# -*- coding: utf-8 -*-
import csv
import json as simplejson # XXX trocar isso por simplesmente import json e refatorar o codigo
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.paginator import EmptyPage, InvalidPage, Paginator
from django.db.models import Q
from django.forms.forms import BoundField
from django.http import HttpResponse
from django.shortcuts import render_to_response, get_object_or_404
from django.http.response import HttpResponseRedirect
from django.shortcuts import render, render_to_response, get_object_or_404
from django.template.context import RequestContext
from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateView
from sigi.apps.casas.models import Orgao
from sigi.apps.contatos.models import UnidadeFederativa
from sigi.apps.servicos.models import (TipoServico, CasaManifesta, CasaAtendida,
from sigi.apps.convenios.views import normaliza_data, query_ordena
from sigi.apps.servicos.models import (Servico, TipoServico, CasaManifesta, CasaAtendida,
ServicoManifesto)
@ -144,3 +150,187 @@ def casa_manifesta_view(request):
extra_context = {'uf_list': UnidadeFederativa.objects.all()}
return render_to_response('servicos/casa_manifesta.html', extra_context, context_instance=RequestContext(request))
def adicionar_servicos_carrinho(request, queryset=None, id=None):
if request.method == 'POST':
ids_selecionados = request.POST.getlist('_selected_action')
if 'carrinho_servicos' not in request.session:
request.session['carrinho_servicos'] = ids_selecionados
else:
lista = request.session['carrinho_servicos']
# Verifica se id já não está adicionado
for id in ids_selecionados:
if id not in lista:
lista.append(id)
request.session['carrinho_servicos'] = lista
def carrinhoOrGet_for_qs(request):
"""
Verifica se existe convênios na sessão se não verifica get e retorna qs correspondente.
"""
if 'carrinho_servicos' in request.session:
ids = request.session['carrinho_servicos']
qs = Servico.objects.filter(pk__in=ids)
qs = qs.order_by("casa_legislativa__municipio__uf", "casa_legislativa__municipio")
qs = get_for_qs(request.GET, qs)
else:
qs = Servico.objects.all()
if request.GET:
qs = qs.order_by("casa_legislativa__municipio__uf", "casa_legislativa__municipio")
qs = get_for_qs(request.GET, qs)
return qs
def adicionar_servicos_carrinho(request, queryset=None, id=None):
if request.method == 'POST':
ids_selecionados = request.POST.getlist('_selected_action')
if 'carrinho_servicos' not in request.session:
request.session['carrinho_servicos'] = ids_selecionados
else:
lista = request.session['carrinho_servicos']
# Verifica se id já não está adicionado
for id in ids_selecionados:
if id not in lista:
lista.append(id)
request.session['carrinho_servicos'] = lista
@login_required
def excluir_carrinho(request):
if 'carrinho_servicos' in request.session:
del request.session['carrinho_servicos']
messages.info(request, u'O carrinho foi esvaziado')
return HttpResponseRedirect('../../')
@login_required
def deleta_itens_carrinho(request):
if request.method == 'POST':
ids_selecionados = request.POST.getlist('_selected_action')
if 'carrinho_servicos' in request.session:
lista = request.session['carrinho_servicos']
for item in ids_selecionados:
lista.remove(item)
if lista:
request.session['carrinho_servicos'] = lista
else:
del lista
del request.session['carrinho_servicos']
return HttpResponseRedirect('.')
@login_required
def visualizar_carrinho(request):
qs = carrinhoOrGet_for_qs(request)
paginator = Paginator(qs, 100)
# Make sure page request is an int. If not, deliver first page.
# Esteja certo de que o `page request` é um inteiro. Se não, mostre a primeira página.
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
# Se o page request (9999) está fora da lista, mostre a última página.
try:
paginas = paginator.page(page)
except (EmptyPage, InvalidPage):
paginas = paginator.page(paginator.num_pages)
carrinhoIsEmpty = not('carrinho_servicos' in request.session)
return render(
request,
'servicos/carrinho.html',
{
'carIsEmpty': carrinhoIsEmpty,
'paginas': paginas,
'query_str': '?' + request.META['QUERY_STRING']
}
)
def get_for_qs(get, qs):
kwargs = {}
ids = 0
get._mutable = True
normaliza_data(get, 'data_ativacao__gte')
normaliza_data(get, 'data_ativacao__lte')
get._mutable = False
for k, v in get.iteritems():
if k not in ['page', 'pop', 'q', '_popup']:
if not k == 'o':
if k == "ot":
qs = query_ordena(qs, get["o"], get["ot"])
else:
kwargs[str(k)] = v
if(str(k) == 'ids'):
ids = 1
break
qs = qs.filter(**kwargs)
if ids:
query = 'id IN (' + kwargs['ids'].__str__() + ')'
qs = Servico.objects.extra(where=[query])
return qs
@login_required
def export_csv(request):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=servicos.csv'
csv_writer = csv.writer(response)
servicos = carrinhoOrGet_for_qs(request)
if not servicos:
return HttpResponseRedirect('../')
atributos = [_(u"Casa Legislativa"), _(u"Contato Interlegis"), _(u"Produto"),
_(u"Data de Ativação"), ]
if request.POST:
atributos = request.POST.getlist("itens_csv_selected")
col_titles = atributos
if _(u"Casa Legislativa") in col_titles:
pos = col_titles.index(_(u"Casa Legislativa")) + 1
col_titles.insert(pos, _(u"uf"))
pos+=1
col_titles.insert(pos, _(u"email"))
pos+=1
col_titles.insert(pos, _(u"telefone"))
if _(u"Contato Interlegis") in col_titles:
pos = col_titles.index(_(u"Contato Interlegis")) + 1
col_titles.insert(pos, _(u"Email do contato"))
csv_writer.writerow([s.encode("utf-8") for s in col_titles])
for servico in servicos:
lista = []
for atributo in atributos:
if _(u"Casa Legislativa") == atributo:
lista.append(servico.casa_legislativa.nome.encode("utf-8"))
lista.append(servico.casa_legislativa.municipio.uf.sigla.encode("utf-8"))
lista.append(servico.casa_legislativa.email.encode("utf-8"))
if servico.casa_legislativa.telefone is not None:
lista.append(servico.casa_legislativa.telefone)
else:
lista.append("")
elif _(u"Contato Interlegis") == atributo:
if servico.casa_legislativa.contato_interlegis is not None:
lista.append(servico.casa_legislativa.contato_interlegis)
lista.append(servico.casa_legislativa.contato_interlegis.email.encode("utf-8"))
else:
lista.append("")
lista.append("")
elif _(u"Produto") == atributo:
lista.append(servico.tipo_servico.nome.encode("utf-8"))
elif _(u"Data de Ativação") == atributo:
data = ''
if servico.data_ativacao:
data = servico.data_ativacao.strftime("%d/%m/%Y")
lista.append(data.encode("utf-8"))
else:
pass
csv_writer.writerow(lista)
return response

12
sigi/apps/servidores/models.py

@ -2,7 +2,7 @@
from django.contrib.auth.models import User
from django.contrib.contenttypes import generic
from django.db import models
from django.db.models.signals import post_save
from django.db.models.signals import post_save, pre_save
from django.utils.translation import ugettext as _
class Servico(models.Model):
@ -90,3 +90,13 @@ def create_user_profile(sender, instance, created, **kwargs):
)
post_save.connect(create_user_profile, sender=User)
# Hack horrível para ajustar o first_name e o last_name do User criado pelo
# Django-ldap. Os campos first_name e last_name têm o tamanho máximo de
# 30 caracteres, mas o LDAP não tem esse limite, e alguns usuários podem ter
# nomes maiores que isso, o que provoca erro ao salvar o usuário.j
def ajusta_nome_usuario(sender, instance, *args, **kwargs):
instance.first_name = instance.first_name[:30]
instance.last_name = instance.last_name[:30]
pre_save.connect(ajusta_nome_usuario, sender=User)

11
sigi/settings/base.py

@ -74,7 +74,7 @@ INSTALLED_APPS = (
'easy_thumbnails',
'image_cropping',
'rest_framework',
'tinymce',
)
MIDDLEWARE_CLASSES = (
@ -95,6 +95,7 @@ LANGUAGE_CODE = 'pt-br'
USE_I18N = True
USE_L10N = True
USE_THOUSAND_SEPARATOR = True
TIME_ZONE = "America/Sao_Paulo"
gettext_noop = lambda s: s # for gettext discovery
LANGUAGES = (
@ -188,3 +189,11 @@ REST_FRAMEWORK = {
WHOIS_WHITELIST = [
'127.0.0.1',
]
# TinyMCE configuration
TINYMCE_SPELLCHECKER = True
TINYMCE_COMPRESSOR = True
TINYMCE_DEFAULT_CONFIG = {
'theme': "advanced",
"height": 500,
}

22
sigi/shortcuts.py

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from cgi import escape
from datetime import datetime
import os
from django.conf import settings
@ -23,17 +24,22 @@ def fetch_resources(uri, rel):
uri.replace(settings.MEDIA_URL, ""))
return path
def pdf_renderer(template, context, filename='report.pdf'):
html = template.render(context)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=' + filename
pdf = pisa.CreatePDF(html, dest=response,
link_callback=fetch_resources)
if pdf.err:
return HttpResponse(_(u'We had some errors<pre>%s</pre>') % escape(html))
return response
def render_to_pdf(template_src, context_dict):
filename = template_src.replace('.html', '').replace('_pdf', '.pdf')
template = get_template(template_src)
context = Context(context_dict)
html = template.render(context)
result = StringIO.StringIO()
pdf = pisa.pisaDocument(StringIO.StringIO(html.encode('utf-8')), result, link_callback=fetch_resources)
if not pdf.err:
response = HttpResponse(result.getvalue(), content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=' + filename
return response
return HttpResponse(_(u'We had some errors<pre>%s</pre>') % escape(html))
return pdf_renderer(template, context,filename)

1
sigi/urls.py

@ -20,6 +20,7 @@ urlpatterns = patterns(
url(r'^ocorrencias/', include('sigi.apps.ocorrencias.urls')),
url(r'^eventos/', include('sigi.apps.eventos.urls')),
url(r'^whois/', include('sigi.apps.whois.urls')),
url(r'^tinymce/', include('tinymce.urls')),
url(r'^', include('sigi.apps.home.urls')),
url(r'^', include(admin.site.urls)),

1
templates/admin/base_site.html

@ -41,6 +41,7 @@
<li><a href="/sites/site/">{% trans 'Sites' %}</a></li>
<li><a href="/diagnosticos/">{% trans 'Diagnósticos' %}</a></li>
<li><a href="{% url 'importar-casas' %}">{% trans 'Importar dados de Casas' %}</a></li>
<li><a href="{% url 'importar-gescon' %}">{% trans 'Importar convênios do Gescon' %}</a></li>
</ul>
</li>
{% endif %}

18
templates/base_report.html

@ -11,11 +11,14 @@
text-align: center;
}
td.header_text p {
margin: 0px;
font-size: 1.4em;
margin: 5px;
font-size: 1.2em;
text-align: center;
}
td.header_text {
width: 550px;
.orgao_name {
margin-bottom: 0px;
margin-top: 0px;
font-weight: bold;
}
h1 {
font-size: 2em;
@ -58,12 +61,12 @@
}
@page {
size: {% block pagesize %}{{ pagesize }}{% endblock pagesize %};
margin: 4cm 1cm 1cm 2cm;
margin: {% block pagemargin %}4cm 1cm 1cm 2cm{% endblock pagemargin %};
font-family: "Helvetica, Arial, sans-serif";
font-size: 2em;
@frame header {
-pdf-frame-content: header;
top: 1cm;
{% block header-settings %}top: 1cm;{% endblock header-settings %}
}
@frame footer {
-pdf-frame-content: footer;
@ -84,7 +87,8 @@
<td class="logo"><img src="{% static 'img/logo-senado.jpg' %}" /></td>
<td class="header_text">
<p><strong>{% trans 'SENADO FEDERAL' %}</strong></p>
<p><strong>{% trans 'ILB - PROGRAMA INTERLEGIS' %}</strong></p>
<p class="orgao_name">{% trans 'Instituto Legislativo Brasileiro' %}</p>
<p class="orgao_name">{% trans "ILB / Interlegis" %}</p>
<p>{% block subsecretaria %}{% endblock %}</p>
</td>
<td class="logo"><img src="{% static 'img/logo-interlegis.jpg' %}" /></td>

Loading…
Cancel
Save