Browse Source

Importação de convenios do Gescon

pull/89/head
Sesostris Vieira 3 years ago
parent
commit
5a24039aa8
  1. 10
      sigi/apps/convenios/admin.py
  2. 30
      sigi/apps/convenios/migrations/0014_gescon.py
  3. 387
      sigi/apps/convenios/models.py
  4. 18
      sigi/apps/convenios/templates/convenios/importar_gescon.html
  5. 1
      sigi/apps/convenios/urls.py
  6. 11
      sigi/apps/convenios/views.py
  7. 1
      templates/admin/base_site.html

10
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
@ -170,8 +171,13 @@ 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)
admin.site.register(Convenio, ConvenioAdmin)
admin.site.register(EquipamentoPrevisto, EquipamentoPrevistoAdmin)
admin.site.register(EquipamentoPrevisto, EquipamentoPrevistoAdmin)

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,),
),
]

387
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
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):
@ -360,3 +365,383 @@ 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)
except Exception as e:
self.add_message(
_(u"\tErro ao acessar {url}: {errmsg}").format(
url=url,
errmsg=str(e)
)
)
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
verificados = 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 == 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']
)
convenio.save()
novos += 1
elif chk == 1:
convenio = convenios.get()
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']
)
)
erros += 1
continue
if convenio.num_processo_sf != sigad:
sigi_nums = filter(
type(convenio.num_processo_sf).isdigit,
convenio.num_processo_sf
).zfill(17)
gesc_nums = filter(type(sigad).isdigit, sigad).zfill(17)
if ( sigi_nums == gesc_nums or
convenio.num_processo_sf == ""):
# Número SIGAD incorreto no SIGI, podemos corrigir
convenio.num_processso_sf = sigad
convenio.save()
else:
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})").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
)
)
erros += 1
continue
if convenio.num_convenio != numero:
sigi_nums = filter(type(convenio.num_convenio).isdigit,
convenio.num_convenio).zfill(8)
gesc_nums = filter(type(numero).isdigit,
numero).zfill(8)
if (sigi_nums == gesc_nums or
convenio.num_convenio == ""):
# Número gescon errado no SIGI mas podemos corrigir
convenio.num_convenio = numero
else:
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})").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
)
)
erros += 1
continue
if contrato['objeto'] not in convenio.observacao:
convenio.observacao += "\n" + contrato['objeto']
convenio.data_sigad=contrato['assinatura']
convenio.data_retorno_assinatura=contrato['inicioVigencia']
convenio.data_termino_vigencia=contrato['terminoVigencia']
convenio.data_pub_diario=contrato['publicacao']
try:
convenio.save()
except Exception:
import ipdb; ipdb.set_trace()
print contrato
raise
atualizados += 1
verificados += 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, {verificados} confirmados e "
u"{erros} reportados com erro.").format(
novos=novos,
atualizados=atualizados,
verificados=verificados,
erros=erros
)
)
self.save()
@classmethod
def load(cls):
obj, created = cls.objects.get_or_create(pk=1)
return obj

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'),
)

11
sigi/apps/convenios/views.py

@ -13,7 +13,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,
@ -372,3 +372,12 @@ def export_csv(request):
csv_writer.writerow(lista)
return response
def importar_gescon(request):
action = request.GET.get('action', "")
gescon = Gescon.load()
if action == 'importar':
gescon.importa_contratos()
return render(request, "convenios/importar_gescon.html", {'gescon': gescon})

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 %}

Loading…
Cancel
Save