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, from sigi.apps.convenios.models import (Projeto, StatusConvenio,
TipoSolicitacao, Convenio, TipoSolicitacao, Convenio,
EquipamentoPrevisto, Anexo, Tramitacao) EquipamentoPrevisto, Anexo, Tramitacao,
Gescon)
from sigi.apps.convenios.reports import ConvenioReport from sigi.apps.convenios.reports import ConvenioReport
from sigi.apps.convenios.views import adicionar_convenios_carrinho from sigi.apps.convenios.views import adicionar_convenios_carrinho
from sigi.apps.utils import queryset_ascii from sigi.apps.utils import queryset_ascii
@ -170,8 +171,13 @@ class EquipamentoPrevistoAdmin(BaseModelAdmin):
search_fields = ('convenio__id', 'equipamento__fabricante__nome', search_fields = ('convenio__id', 'equipamento__fabricante__nome',
'equipamento__modelo__modelo', 'equipamento__modelo__tipo__tipo') '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(Projeto)
admin.site.register(StatusConvenio) admin.site.register(StatusConvenio)
admin.site.register(TipoSolicitacao) admin.site.register(TipoSolicitacao)
admin.site.register(Convenio, ConvenioAdmin) 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 -*- #-*- coding: utf-8 -*-
import re import re
import requests
from datetime import datetime, date from datetime import datetime, date
from django.db import models 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 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 from sigi.apps.servidores.models import Servidor, Servico
class Projeto(models.Model): class Projeto(models.Model):
@ -360,3 +365,383 @@ class Tramitacao(models.Model):
if self.observacao: if self.observacao:
result = result + u" (%s)" % (self.observacao) result = result + u" (%s)" % (self.observacao)
return unicode(result) # XXX is this unicode(...) really necessary??? 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/carrinho/deleta_itens_carrinho$', 'deleta_itens_carrinho', name='deleta-itens-carrinho'), # tagerror
url(r'^convenio/csv/$', 'export_csv', name='convenios-csv'), url(r'^convenio/csv/$', 'export_csv', name='convenios-csv'),
url(r'^reportsRegiao/(?P<regiao>\w+)/$', 'report_regiao', name='convenios-report_regiao_pdf'), 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.casas.models import Orgao
from sigi.apps.contatos.models import UnidadeFederativa 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, from sigi.apps.convenios.reports import (ConvenioReport,
ConvenioReportSemAceite, ConvenioReportSemAceite,
ConvenioPorCMReport, ConvenioPorCMReport,
@ -372,3 +372,12 @@ def export_csv(request):
csv_writer.writerow(lista) csv_writer.writerow(lista)
return response 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="/sites/site/">{% trans 'Sites' %}</a></li>
<li><a href="/diagnosticos/">{% trans 'Diagnósticos' %}</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-casas' %}">{% trans 'Importar dados de Casas' %}</a></li>
<li><a href="{% url 'importar-gescon' %}">{% trans 'Importar convênios do Gescon' %}</a></li>
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

Loading…
Cancel
Save