From 5a24039aa87f859f13cd6aa81b7b9586704a3f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ses=C3=B3stris=20Vieira?= Date: Thu, 2 Sep 2021 10:33:29 -0300 Subject: [PATCH] =?UTF-8?q?Importa=C3=A7=C3=A3o=20de=20convenios=20do=20Ge?= =?UTF-8?q?scon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/apps/convenios/admin.py | 10 +- sigi/apps/convenios/migrations/0014_gescon.py | 30 ++ sigi/apps/convenios/models.py | 387 +++++++++++++++++- .../templates/convenios/importar_gescon.html | 18 + sigi/apps/convenios/urls.py | 1 + sigi/apps/convenios/views.py | 11 +- templates/admin/base_site.html | 1 + 7 files changed, 454 insertions(+), 4 deletions(-) create mode 100644 sigi/apps/convenios/migrations/0014_gescon.py create mode 100644 sigi/apps/convenios/templates/convenios/importar_gescon.html diff --git a/sigi/apps/convenios/admin.py b/sigi/apps/convenios/admin.py index dcaa156..2f5435f 100644 --- a/sigi/apps/convenios/admin.py +++ b/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) \ No newline at end of file diff --git a/sigi/apps/convenios/migrations/0014_gescon.py b/sigi/apps/convenios/migrations/0014_gescon.py new file mode 100644 index 0000000..7fe216c --- /dev/null +++ b/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}.
Por exemplo: https://adm.senado.gov.br/gestao-contratos/api/contratos/busca?especie={s}', 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. ', 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,), + ), + ] diff --git a/sigi/apps/convenios/models.py b/sigi/apps/convenios/models.py index 59f00fa..4982a5f 100644 --- a/sigi/apps/convenios/models.py +++ b/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}.
Por exemplo: " + u"https://adm.senado.gov.br/gestao-contratos/api/contratos" + u"/busca?especie={s}") + ) + 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"") + ) + 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 \ No newline at end of file diff --git a/sigi/apps/convenios/templates/convenios/importar_gescon.html b/sigi/apps/convenios/templates/convenios/importar_gescon.html new file mode 100644 index 0000000..49f068a --- /dev/null +++ b/sigi/apps/convenios/templates/convenios/importar_gescon.html @@ -0,0 +1,18 @@ +{% extends 'admin/base_site.html' %} +{% load i18n %} + +{% block content_title %}

{% trans 'Importar dados do Gescon' %}

{% endblock %} +{% block object-tools-items %} + + +{% endblock %} +{% block content %} + {% if gescon.ultima_importacao %} +
{{ gescon.ultima_importacao }}
+ {% else %} + {% blocktrans %} +

Nenhuma importação anterior foi realizada!

+

Configure a conexão com o Gescon para realizar a primeira importação.

+ {% endblocktrans %} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/sigi/apps/convenios/urls.py b/sigi/apps/convenios/urls.py index d3b753a..1986ecc 100644 --- a/sigi/apps/convenios/urls.py +++ b/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\w+)/$', 'report_regiao', name='convenios-report_regiao_pdf'), + url(r'^importar/$', 'importar_gescon', name='importar-gescon'), ) diff --git a/sigi/apps/convenios/views.py b/sigi/apps/convenios/views.py index 6760828..61aa3bf 100644 --- a/sigi/apps/convenios/views.py +++ b/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}) \ No newline at end of file diff --git a/templates/admin/base_site.html b/templates/admin/base_site.html index 6f61a15..f7d3120 100644 --- a/templates/admin/base_site.html +++ b/templates/admin/base_site.html @@ -41,6 +41,7 @@
  • {% trans 'Sites' %}
  • {% trans 'Diagnósticos' %}
  • {% trans 'Importar dados de Casas' %}
  • +
  • {% trans 'Importar convênios do Gescon' %}
  • {% endif %}