Browse Source

Relatórios de erros de importação Gescon. Gertiq #160537

pull/174/head
Sesóstris Vieira 7 months ago
parent
commit
3c21ce5369
  1. 4
      sigi/apps/casas/admin_urls.py
  2. 8
      sigi/apps/casas/forms.py
  3. 44
      sigi/apps/casas/templates/casas/cnpj_duplicado.html
  4. 19
      sigi/apps/casas/templates/casas/cnpj_duplicado_pdf.html
  5. 57
      sigi/apps/casas/templates/casas/cnpj_errado.html
  6. 19
      sigi/apps/casas/templates/casas/cnpj_errado_pdf.html
  7. 37
      sigi/apps/casas/templates/casas/snippets/cnpj_duplicado_snippet.html
  8. 38
      sigi/apps/casas/templates/casas/snippets/cnpj_errado_snippet.html
  9. 120
      sigi/apps/casas/views.py
  10. 8
      sigi/apps/convenios/admin.py
  11. 20
      sigi/apps/convenios/migrations/0036_convenio_erro_gescon.py
  12. 134
      sigi/apps/convenios/models.py
  13. 41
      sigi/apps/convenios/templates/convenios/erros_gescon.html
  14. 21
      sigi/apps/convenios/templates/convenios/erros_gescon_pdf.html
  15. 39
      sigi/apps/convenios/templates/convenios/snippets/erros_gescon_snippet.html
  16. 5
      sigi/apps/convenios/urls.py
  17. 82
      sigi/apps/convenios/views.py
  18. 6
      sigi/menu_conf.yaml
  19. 3
      sigi/settings.py

4
sigi/apps/casas/admin_urls.py

@ -1,7 +1,9 @@
from django.urls import path, include from django.urls import path
from sigi.apps.casas import views from sigi.apps.casas import views
urlpatterns = [ urlpatterns = [
path("carteira/", views.painel_relacionamento, name="casas_carteira"), path("carteira/", views.painel_relacionamento, name="casas_carteira"),
path("gerentes/", views.GerentesListView.as_view(), name="casas_gerentes"), path("gerentes/", views.GerentesListView.as_view(), name="casas_gerentes"),
path("cnpjduplicado/", views.cnpj_duplicado, name="casas_cnpj_duplicado"),
path("cnpjerrado/", views.cnpj_errado, name="casas_cnpj_errado"),
] ]

8
sigi/apps/casas/forms.py

@ -105,3 +105,11 @@ class FuncionarioForm(forms.ModelForm):
widgets = { widgets = {
"redes_sociais": MaterialAdminTextareaWidget, "redes_sociais": MaterialAdminTextareaWidget,
} }
class CnpjErradoForm(forms.Form):
has_convenio = forms.BooleanField(
label=_("Mostrar apenas órgãos com convênio"),
required=False,
initial=False,
)

44
sigi/apps/casas/templates/casas/cnpj_duplicado.html

@ -0,0 +1,44 @@
{% extends "admin/base_site.html" %}
{% load static i18n %}
{% block extrastyle %}
{{ block.super }}
<style type="text/css">
table {
width: auto;
}
.bordered {
border-top: 1px solid var(--body-fg);
}
</style>
<link rel="stylesheet" type="text/css" href="/static/css/calendario.css">
{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block content_title %}
<h5>{% blocktranslate with count=orgaos.count %}{{ count }} órgãos com CNPJ duplicado{% endblocktranslate %}</h5>
{% endblock %}
{% block content %}
<div class="fixed-action-btn">
<a class="btn-floating">
<i class="large material-icons">print</i>
</a>
<ul>
<li><a class="btn-floating" href="?fmt=pdf" title="{% trans 'Exportar para PDF' %}" ><i class="material-icons">picture_as_pdf</i></a></li>
<li><a class="btn-floating" href="?fmt=csv" title="{% trans 'Exportar para CSV' %}" ><i class="material-icons">file_download</i></a></li>
</ul>
</div>
{% include "casas/snippets/cnpj_duplicado_snippet.html" with mode="html" %}
{% endblock %}
{% block footer %}
{{ block.super }}
<script>
$(document).ready(function(){
M.FloatingActionButton.init($('.fixed-action-btn'), {hoverEnabled: false});
M.Modal.init($(".modal"));
})
</script>
{% endblock %}

19
sigi/apps/casas/templates/casas/cnpj_duplicado_pdf.html

@ -0,0 +1,19 @@
{% extends "pdf/base_report.html" %}
{% load static i18n %}
{% block page_size %}A4 landscape{% endblock %}
{% block extra_style %}
{{ block.super }}
.bordered td {
border-top: 1px solid #000;
}
{% endblock %}
{% block report_name %}
{% blocktranslate with count=orgaos.count %}{{ count }} órgãos com CNPJ duplicado{% endblocktranslate %}
{% endblock report_name %}
{% block main_content %}
{% include "casas/snippets/cnpj_duplicado_snippet.html" %}
{% endblock %}

57
sigi/apps/casas/templates/casas/cnpj_errado.html

@ -0,0 +1,57 @@
{% extends "admin/base_site.html" %}
{% load static i18n %}
{% block extrastyle %}
{{ block.super }}
<style type="text/css">
table {
width: auto;
}
.bordered {
border-top: 1px solid var(--body-fg);
}
</style>
<link rel="stylesheet" type="text/css" href="/static/css/calendario.css">
{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block content_title %}
<h5>{% blocktranslate with count=orgaos|length %}{{ count }} órgãos com CNPJ digitado errado{% endblocktranslate %}</h5>
{% endblock %}
{% block content %}
<form>
<div class="card">
<div class="card-content">
<label for="{{ form.has_convenio.id_for_label }}">
{{ form.has_convenio }}
<span>{{ form.has_convenio.label }}</span>
</label>
</div>
<div class="card-action">
<button class="waves-effect waves-light btn" type="submit">{% translate "Submit" %}</button>
</div>
</div>
<div class="fixed-action-btn">
<a class="btn-floating">
<i class="large material-icons">print</i>
</a>
<ul>
<li><button class="btn-floating" type="submit" name="fmt" value="pdf" title="{% trans 'Exportar para PDF' %}" ><i class="material-icons">picture_as_pdf</i></button></li>
<li><button class="btn-floating" type="submit" name="fmt" value="csv" title="{% trans 'Exportar para CSV' %}" ><i class="material-icons">file_download</i></button></li>
</ul>
</div>
</form>
{% include "casas/snippets/cnpj_errado_snippet.html" with mode="html" %}
{% endblock %}
{% block footer %}
{{ block.super }}
<script>
$(document).ready(function(){
M.FloatingActionButton.init($('.fixed-action-btn'), {hoverEnabled: false});
M.Modal.init($(".modal"));
})
</script>
{% endblock %}

19
sigi/apps/casas/templates/casas/cnpj_errado_pdf.html

@ -0,0 +1,19 @@
{% extends "pdf/base_report.html" %}
{% load static i18n %}
{% block page_size %}A4 landscape{% endblock %}
{% block extra_style %}
{{ block.super }}
.bordered td {
border-top: 1px solid #000;
}
{% endblock %}
{% block report_name %}
{% blocktranslate with count=orgaos|length %}{{ count }} órgãos com CNPJ digitado errado{% endblocktranslate %}
{% endblock report_name %}
{% block main_content %}
{% include "casas/snippets/cnpj_errado_snippet.html" %}
{% endblock %}

37
sigi/apps/casas/templates/casas/snippets/cnpj_duplicado_snippet.html

@ -0,0 +1,37 @@
{% load i18n %}
<div class="card">
<div class="card-content">
<table class="striped">
<thead>
<tr>
<th>{% translate "ID" %}</th>
<th>{% translate "CNPJ" %}</th>
<th>{% translate "Tipo de órgão" %}</th>
<th>{% translate "Sigla" %}</th>
<th>{% translate "Nome" %}</th>
<th>{% translate "Cidade" %}</th>
<th>{% translate "UF" %}</th>
</tr>
</thead>
<tbody>
{% for orgao in orgaos %}
<tr{% ifchanged orgao.cnpj %} class="bordered"{% endifchanged %}>
<td><a href="{% url 'admin:casas_orgao_change' orgao.id %}">{{ orgao.id|stringformat:"s" }}</a></td>
<td>{{ orgao.cnpj }}</td>
<td>{{ orgao.tipo.nome }}</td>
<td>{{ orgao.sigla }}</td>
<td>{{ orgao.nome }}</td>
<td>{{ orgao.municipio.nome }}</td>
<td>{{ orgao.municipio.uf.sigla }}</td>
</tr>
{% empty %}
<tr>
<td colspam="7">
{% translate "Nenhum órgão com CNPJ duplicado" %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

38
sigi/apps/casas/templates/casas/snippets/cnpj_errado_snippet.html

@ -0,0 +1,38 @@
{% load i18n %}
<div class="card">
<div class="card-content">
<table class="striped">
<thead>
<tr>
<th>{% translate "ID" %}</th>
<th>{% translate "CNPJ" %}</th>
<th>{% translate "Sigla" %}</th>
<th>{% translate "Nome" %}</th>
<th>{% translate "Cidade" %}</th>
<th>{% translate "UF" %}</th>
</tr>
</thead>
<tbody>
{% for orgao in orgaos %}
{% ifchanged orgao.tipo_nome %}
<tr><th colspan="6">{{ orgao.tipo_nome }}</th></tr>
{% endifchanged %}
<tr>
<td><a href="{% url 'admin:casas_orgao_change' orgao.id %}">{{ orgao.id|stringformat:"s" }}</a></td>
<td>{{ orgao.cnpj }}</td>
<td>{{ orgao.sigla }}</td>
<td>{{ orgao.nome }}</td>
<td>{{ orgao.municipio_nome }}</td>
<td>{{ orgao.uf_sigla }}</td>
</tr>
{% empty %}
<tr>
<td colspam="7">
{% translate "Nenhum órgão com CNPJ digitado errado" %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

120
sigi/apps/casas/views.py

@ -1,7 +1,5 @@
import csv import csv
from functools import reduce from django.db.models import Count, Q, Prefetch, F
from django.db.models import Count, Q, Prefetch
from django.contrib.admin.sites import site
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -19,10 +17,10 @@ from django.views.generic import (
DeleteView, DeleteView,
ListView, ListView,
UpdateView, UpdateView,
DetailView,
) )
from django_weasyprint.views import WeasyTemplateResponse
from rest_framework import generics, filters from rest_framework import generics, filters
from sigi.apps.casas.forms import FuncionarioForm from sigi.apps.casas.forms import FuncionarioForm, CnpjErradoForm
from sigi.apps.casas.models import Funcionario, Orgao, TipoOrgao from sigi.apps.casas.models import Funcionario, Orgao, TipoOrgao
from sigi.apps.casas.serializers import OrgaoAtendidoSerializer from sigi.apps.casas.serializers import OrgaoAtendidoSerializer
from sigi.apps.home.mixins import ContatoInterlegisViewMixin from sigi.apps.home.mixins import ContatoInterlegisViewMixin
@ -36,6 +34,7 @@ from sigi.apps.ocorrencias.models import Ocorrencia
from sigi.apps.servicos.models import Servico, TipoServico from sigi.apps.servicos.models import Servico, TipoServico
from sigi.apps.eventos.models import Evento, TipoEvento from sigi.apps.eventos.models import Evento, TipoEvento
from sigi.apps.convenios.models import Convenio from sigi.apps.convenios.models import Convenio
from sigi.apps.utils import valida_cnpj
def resumo_carteira(casas): def resumo_carteira(casas):
@ -339,6 +338,117 @@ def painel_relacionamento(request):
return render(request, "casas/painel.html", context) return render(request, "casas/painel.html", context)
@login_required
@staff_member_required
def cnpj_duplicado(request):
formato = request.GET.get("fmt", "html")
dups = (
Orgao.objects.exclude(cnpj="")
.order_by("cnpj")
.values("cnpj")
.annotate(tot=Count("cnpj"))
.filter(tot__gt=1)
.values("cnpj")
)
orgaos = (
Orgao.objects.filter(cnpj__in=dups)
.order_by("cnpj", "nome", "municipio__nome", "municipio__uf")
.prefetch_related("tipo", "municipio", "municipio__uf")
)
context = {
"orgaos": orgaos,
"title": _("Órgãos com CNPJ duplicado"),
}
if formato == "pdf":
return WeasyTemplateResponse(
filename="cnpj_duplicado.pdf",
request=request,
template="casas/cnpj_duplicado_pdf.html",
context=context,
content_type="application/pdf",
)
elif formato == "csv":
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = (
'attachment; filename="cnpj_duplicado.csv"'
)
fieldnames = [
"id",
"cnpj",
"tipo__nome",
"sigla",
"nome",
"municipio__nome",
"municipio__uf__sigla",
]
writer = csv.DictWriter(response, fieldnames)
writer.writeheader()
writer.writerows(orgaos.values(*fieldnames))
return response
return render(request, "casas/cnpj_duplicado.html", context=context)
@login_required
@staff_member_required
def cnpj_errado(request):
formato = request.GET.get("fmt", "html")
form = CnpjErradoForm(request.GET)
if form.is_valid():
has_convenio = form.cleaned_data.get("has_convenio", False)
else:
has_convenio = False
todos_orgaos = (
Orgao.objects.exclude(cnpj="")
.order_by("tipo", "cnpj", "nome")
.annotate(
tipo_nome=F("tipo__nome"),
municipio_nome=F("municipio__nome"),
uf_sigla=F("municipio__uf__sigla"),
)
)
if has_convenio:
todos_orgaos = todos_orgaos.exclude(convenio=None)
orgaos = []
for orgao in todos_orgaos:
if not valida_cnpj(orgao.cnpj):
orgaos.append(orgao)
context = {
"orgaos": orgaos,
"form": form,
"title": _("Órgãos com CNPJ digitado errado"),
}
if formato == "pdf":
return WeasyTemplateResponse(
filename="cnpj_errado.pdf",
request=request,
template="casas/cnpj_errado_pdf.html",
context=context,
content_type="application/pdf",
)
elif formato == "csv":
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = (
'attachment; filename="cnpj_errado.csv"'
)
fieldnames = [
"id",
"cnpj",
"tipo_nome",
"sigla",
"nome",
"municipio_nome",
"uf_sigla",
]
writer = csv.DictWriter(response, fieldnames)
writer.writeheader()
writer.writerows(
[{f: getattr(o, f) for f in fieldnames} for o in orgaos]
)
return response
return render(request, "casas/cnpj_errado.html", context=context)
class GerentesListView(PermissionRequiredMixin, ListView): class GerentesListView(PermissionRequiredMixin, ListView):
template_name = "admin/casas/gerentes_list.html" template_name = "admin/casas/gerentes_list.html"
_tipos = None _tipos = None

8
sigi/apps/convenios/admin.py

@ -1,6 +1,5 @@
from django.db.models import Q from django.db.models import Q
from django.contrib import admin from django.contrib import admin
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -14,12 +13,10 @@ from sigi.apps.convenios.models import (
Convenio, Convenio,
EquipamentoPrevisto, EquipamentoPrevisto,
Anexo, Anexo,
Tramitacao,
Gescon, Gescon,
) )
from sigi.apps.utils.mixins import AsciifyQParameter from sigi.apps.utils.mixins import AsciifyQParameter
from sigi.apps.servidores.models import Servidor from sigi.apps.casas.admin import GerentesInterlegisFilter
from sigi.apps.casas.admin import ConveniosInline, GerentesInterlegisFilter
from sigi.apps.utils.mixins import ( from sigi.apps.utils.mixins import (
ReturnMixin, ReturnMixin,
CartExportReportMixin, CartExportReportMixin,
@ -172,6 +169,7 @@ class ConvenioAdmin(
_("Gescon"), _("Gescon"),
{ {
"fields": ( "fields": (
"erro_gescon",
"atualizacao_gescon", "atualizacao_gescon",
"observacao_gescon", "observacao_gescon",
"link_gescon", "link_gescon",
@ -181,6 +179,7 @@ class ConvenioAdmin(
) )
readonly_fields = ( readonly_fields = (
"data_sigi", "data_sigi",
"erro_gescon",
"atualizacao_gescon", "atualizacao_gescon",
"observacao_gescon", "observacao_gescon",
"link_gescon", "link_gescon",
@ -210,6 +209,7 @@ class ConvenioAdmin(
"conveniada", "conveniada",
"equipada", "equipada",
"casa_legislativa__municipio__uf", "casa_legislativa__municipio__uf",
"erro_gescon",
) )
ordering = ( ordering = (
"casa_legislativa__municipio__uf__sigla", "casa_legislativa__municipio__uf__sigla",

20
sigi/apps/convenios/migrations/0036_convenio_erro_gescon.py

@ -0,0 +1,20 @@
# Generated by Django 5.0.4 on 2024-05-15 11:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("convenios", "0035_convenio_data_extincao_convenio_motivo_extincao"),
]
operations = [
migrations.AddField(
model_name="convenio",
name="erro_gescon",
field=models.BooleanField(
default=False, max_length=1, verbose_name="erro no Gescon"
),
),
]

134
sigi/apps/convenios/models.py

@ -5,6 +5,7 @@ from hashlib import md5
from pathlib import Path from pathlib import Path
from django.db import models from django.db import models
from django.db.models import Q, F from django.db.models import Q, F
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.validators import FileExtensionValidator from django.core.validators import FileExtensionValidator
from django.template import Template, Context from django.template import Template, Context
@ -20,7 +21,7 @@ from weasyprint import HTML
from sigi.apps.contatos.models import Municipio, UnidadeFederativa from sigi.apps.contatos.models import Municipio, UnidadeFederativa
from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.parlamentares.models import Parlamentar
from sigi.apps.utils import to_ascii from sigi.apps.utils import to_ascii
from sigi.apps.casas.models import Funcionario, Orgao, TipoOrgao from sigi.apps.casas.models import Funcionario, Orgao
from sigi.apps.servidores.models import Servidor, Servico from sigi.apps.servidores.models import Servidor, Servico
from sigi.apps.utils import editor_help, mask_cnpj from sigi.apps.utils import editor_help, mask_cnpj
@ -327,6 +328,11 @@ class Convenio(models.Model):
atualizacao_gescon = models.DateTimeField( atualizacao_gescon = models.DateTimeField(
_("Data de atualização pelo Gescon"), blank=True, null=True _("Data de atualização pelo Gescon"), blank=True, null=True
) )
erro_gescon = models.BooleanField(
_("erro no Gescon"),
max_length=1,
default=False,
)
observacao_gescon = models.TextField( observacao_gescon = models.TextField(
_("Observações da atualização do Gescon"), blank=True _("Observações da atualização do Gescon"), blank=True
) )
@ -636,7 +642,7 @@ class Gescon(models.Model):
importados/atualizados) importados/atualizados)
""" """
def mathnames(nome, orgaos): def mathnames(nome, orgaos, all=False):
for o, nome_canonico in orgaos: for o, nome_canonico in orgaos:
ratio = SequenceMatcher( ratio = SequenceMatcher(
None, to_ascii(nome).lower(), nome_canonico None, to_ascii(nome).lower(), nome_canonico
@ -644,9 +650,9 @@ class Gescon(models.Model):
if ratio > 0.9: if ratio > 0.9:
yield (o, ratio) yield (o, ratio)
def get_semelhantes(nome, orgaos): def get_semelhantes(nome, orgaos, all=False):
return sorted( return sorted(
mathnames(nome, orgaos), mathnames(nome, orgaos, all),
key=lambda m: m[1], key=lambda m: m[1],
) )
@ -700,6 +706,9 @@ class Gescon(models.Model):
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
report_user = False report_user = False
Convenio.objects.update(erro_gescon=False)
dominio = get_current_site(None).domain
for sigla_gescon, sigla_sigi in subespecies: for sigla_gescon, sigla_sigi in subespecies:
self.add_message(_(f"\n**Importando subespécie {sigla_gescon}**")) self.add_message(_(f"\n**Importando subespécie {sigla_gescon}**"))
@ -784,6 +793,7 @@ class Gescon(models.Model):
cnpj_masked = mask_cnpj(cnpj) cnpj_masked = mask_cnpj(cnpj)
else: else:
cnpj = None cnpj = None
cnpj_masked = None
if contrato["nomeFornecedor"]: if contrato["nomeFornecedor"]:
nome = to_ascii( nome = to_ascii(
@ -799,70 +809,58 @@ class Gescon(models.Model):
nome = None nome = None
# Buscar o Convenio pelo NUP # # Buscar o Convenio pelo NUP #
try: convenios = Convenio.objects.filter(
convenio = Convenio.objects.get(
projeto=projeto, num_processo_sf=sigad projeto=projeto, num_processo_sf=sigad
) )
except Convenio.DoesNotExist: if convenios.count() == 0:
# Encontrou 0: Pode ser que só exista com o código Gescon # Encontrou 0: Pode ser que só exista com o código Gescon
try: convenios = Convenio.objects.filter(
convenio = Convenio.objects.get(
Q(projeto=projeto) Q(projeto=projeto)
& Q( & Q(Q(num_convenio=numero) | Q(num_processo_sf=numero))
Q(num_convenio=numero) )
| Q(num_processo_sf=numero) if convenios.count() > 1:
) # Encontrou N: Marcamos todos como erro e reportamos
) urls = ", ".join(
except Convenio.DoesNotExist:
# Encontrou 0: Não existe mesmo. Precisa ser criado.
# Para não esticar muito a profundidade do código,
# vou setar convenio para None e tratar o caso lá na
# frente.
convenio = None
except Convenio.MultipleObjectsReturned:
# Encontrou N: Reportar erro
self.add_message(
_(
f"\t* O contrato {numero} no Gescon pode ser "
"relacionado aos seguintes convênios do SIGI:"
+ ", ".join(
[ [
reverse( '<a href="{dominio}{uri}">{id}</a>'.format(
dominio=dominio,
uri=reverse(
"admin:convenios_convenio_change", "admin:convenios_convenio_change",
args=[c.id], args=[c.id],
),
id=c.id,
) )
for c in Convenio.objects.filter( for c in convenios
Q(num_convenio=numero)
| Q(num_processo_sf=numero)
)
] ]
) )
convenios.update(
erro_gescon=True,
observacao_gescon=_(
"Este convênio possui o mesmo número dos "
f"convenios {urls}"
),
) )
)
erros += 1
continue
except Convenio.MultipleObjectsReturned:
self.add_message( self.add_message(
_( _(
f"\t* O contrato {numero} no Gescon pode ser " f"\t* O contrato {numero} no Gescon pode "
"relacionado aos seguintes convênios do SIGI: " "ser relacionado aos seguintes convênios "
+ ", ".join( f"do SIGI: {urls}"
[
reverse(
"admin:convenios_convenio_change",
args=[c.id],
)
for c in Convenio.objects.filter(
num_processo_sf=sigad
)
]
)
) )
) )
erros += 1 erros += 1
# Porém, talvez seja possível ser desambiguado pelo CNPJ do
# fornecedor
if cnpj_masked is not None:
convenios = convenios.filter(
casa_legislativa__cnpj=cnpj_masked
)
if convenios.count() != 1:
# Continua ambíguo. Não dá pra fazer nada.
continue continue
if convenio is not None: if convenios.count() == 1:
# Encontrou 1: Basta atualizar # Achou exatamente o único que deveria existir. Basta
# atualizar os dados
convenio = convenios.get()
convenio.projeto = projeto convenio.projeto = projeto
convenio.num_processo_sf = sigad convenio.num_processo_sf = sigad
convenio.num_convenio = numero convenio.num_convenio = numero
@ -876,10 +874,25 @@ class Gescon(models.Model):
] ]
convenio.data_pub_diario = contrato["publicacao"] convenio.data_pub_diario = contrato["publicacao"]
convenio.atualizacao_gescon = timezone.localtime() convenio.atualizacao_gescon = timezone.localtime()
convenio.erro_gescon = False
convenio.observacao_gescon = ""
convenio.id_contrato_gescon = (
contrato["codTextoContrato"] or ""
)
convenio.save()
atualizados += 1 atualizados += 1
# Corrigir o CNPJ do órgão se estiver diferente do Gescon
# O gescon é um pouquinho mais confiável, por enquanto.
if (
cnpj_masked
and convenio.casa_legislativa.cnpj != cnpj_masked
):
convenio.casa_legislativa.cnpj = cnpj_masked
convenio.casa_legislativa.save()
continue continue
# Não encontrou o convênio. Vamos tentar criar... # Se chegou aqui, é porque não encontrou o convênio.
# Um novo convênio precisa ser criado.
# Primeiro, é preciso identificar qual órgão consta no # Primeiro, é preciso identificar qual órgão consta no
# contrato do Gescon # contrato do Gescon
if (cnpj is None) and (nome is None): if (cnpj is None) and (nome is None):
@ -897,7 +910,7 @@ class Gescon(models.Model):
try: try:
orgao = Orgao.objects.get(cnpj=cnpj_masked) orgao = Orgao.objects.get(cnpj=cnpj_masked)
except Orgao.MultipleObjectsReturned: except Orgao.MultipleObjectsReturned:
# Pode acontecer de uma cãmara usar o mesmo CNPJ # Pode acontecer de uma câmara usar o mesmo CNPJ
# da prefeitura, e ambos terem convênio com o ILB. # da prefeitura, e ambos terem convênio com o ILB.
# Podemos tentar desambiguar pelo nome mais # Podemos tentar desambiguar pelo nome mais
# semelhante. # semelhante.
@ -912,12 +925,12 @@ class Gescon(models.Model):
.order_by() .order_by()
.annotate(uf_sigla=F("municipio__uf__sigla")) .annotate(uf_sigla=F("municipio__uf__sigla"))
], ],
all=True,
)[0][0] )[0][0]
except Orgao.DoesNotExist: except Orgao.DoesNotExist:
# Encontrou 0: Vamos seguir sem órgao e tentar # Encontrou 0: Vamos seguir sem órgao e tentar
# encontrar pelo nome logo abaixo # encontrar pelo nome logo abaixo
orgao = None orgao = None
if orgao is None: if orgao is None:
# Não achou pelo CNPJ. Bora ver se acha por similaridade # Não achou pelo CNPJ. Bora ver se acha por similaridade
# do nome # do nome
@ -936,7 +949,7 @@ class Gescon(models.Model):
) )
erros += 1 erros += 1
continue continue
# Primeiro com o nome igual veio do GESCON # Tentar primeiro com o nome igual veio do GESCON
semelhantes = get_semelhantes( semelhantes = get_semelhantes(
to_ascii(contrato["nomeFornecedor"]).lower(), to_ascii(contrato["nomeFornecedor"]).lower(),
todos_orgaos, todos_orgaos,
@ -997,9 +1010,20 @@ class Gescon(models.Model):
observacao_gescon=_( observacao_gescon=_(
"Importado integralmente do Gescon" "Importado integralmente do Gescon"
), ),
id_contrato_gescon=(
contrato["codTextoContrato"] or ""
),
) )
convenio.save() convenio.save()
novos += 1 novos += 1
# Corrigir o CNPJ do órgão se estiver diferente do Gescon
# O gescon é um pouquinho mais confiável, por enquanto.
if (
cnpj_masked
and convenio.casa_legislativa.cnpj != cnpj_masked
):
convenio.casa_legislativa.cnpj = cnpj_masked
convenio.casa_legislativa.save()
continue continue
if novos or erros or alertas or atualizados: if novos or erros or alertas or atualizados:

41
sigi/apps/convenios/templates/convenios/erros_gescon.html

@ -0,0 +1,41 @@
{% extends "admin/base_site.html" %}
{% load static i18n %}
{% block extrastyle %}
{{ block.super }}
<style type="text/css">
table {
width: auto;
}
</style>
<link rel="stylesheet" type="text/css" href="/static/css/calendario.css">
{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block content_title %}
<h5>
{% blocktranslate count counter=convenios.count %}
Um convênio com erro na importação do Gescon
{% plural %}
{{ counter }} convênios com erro na importação do Gescon
{% endblocktranslate %}
</h5>
{% endblock %}
{% block breadcrumbs %}
{% endblock %}
{% block content %}
{% include "convenios/snippets/erros_gescon_snippet.html" with mode="html" %}
{% endblock %}
{% block footer %}
{{ block.super }}
<script>
$(document).ready(function(){
M.FloatingActionButton.init($('.fixed-action-btn'), {hoverEnabled: false});
M.Modal.init($(".modal"));
})
</script>
{% endblock %}

21
sigi/apps/convenios/templates/convenios/erros_gescon_pdf.html

@ -0,0 +1,21 @@
{% extends "pdf/base_report.html" %}
{% load static i18n %}
{% block page_size %}A4 landscape{% endblock %}
{% block report_name %}
{% blocktranslate count counter=convenios.count %}
Um convênio com erro na importação do Gescon
{% plural %}
{{ counter }} convênios com erro na importação do Gescon
{% endblocktranslate %}
{% endblock report_name %}
{% block main_content %}
{% include "convenios/snippets/erros_gescon_snippet.html" %}
<div class="new-page"></div>
<div style="padding: 24px;">
<h1>{% translate "Resumo da última importação de dados do Gescon" %}</h1>
{{ ultima_importacao }}
</div>
{% endblock %}

39
sigi/apps/convenios/templates/convenios/snippets/erros_gescon_snippet.html

@ -0,0 +1,39 @@
{% load i18n %}
<div class="card">
<div class="card-content">
<table class="striped">
<thead>
<tr>
<th>{% translate "id SIGI" %}</th>
<th>{% translate "NUP sigad" %}</th>
<th>{% translate "Número" %}</th>
<th>{% translate "Projeto" %}</th>
<th>{% translate "Órgão conveniado" %}</th>
<th>{% translate "UF" %}</th>
<th>{% translate "Erro encontrado" %}</th>
</tr>
</thead>
<tbody>
{% for convenio in convenios %}
<tr>
<td>
<a href="{% url 'admin:convenios_convenio_change' convenio.id %}">{{ convenio.id|stringformat:"s" }}</a>
</td>
<td>{{ convenio.num_processo_sf }}</td>
<td>{{ convenio.num_convenio }}</td>
<td>{{ convenio.projeto.nome }}</td>
<td>{{ convenio.casa_legislativa }}</td>
<td>{{ convenio.casa_legislativa.municipio.uf.sigla }}</td>
<td>{{ convenio.observacao_gescon|safe }}</td>
</tr>
{% empty %}
<tr>
<td colspam="7">
{% translate "Nenhum convênio com erro de importação do Gescon." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

5
sigi/apps/convenios/urls.py

@ -2,6 +2,11 @@ from django.urls import path
from sigi.apps.convenios import views from sigi.apps.convenios import views
urlpatterns = [ urlpatterns = [
path(
"errosgescon/",
views.report_erros_gescon,
name="convenios-report_erros_gescon",
),
path( path(
"reportsRegiao/<str:regiao>/", "reportsRegiao/<str:regiao>/",
views.report_regiao, views.report_regiao,

82
sigi/apps/convenios/views.py

@ -1,14 +1,8 @@
import csv import csv
from docutils.core import publish_parts
# from django.contrib import messages from django.utils.safestring import mark_safe
from django.contrib import admin from django.http import HttpResponse, HttpResponseForbidden
from django.http.response import HttpResponseForbidden
# from django.conf import settings
# from django.core.paginator import Paginator, InvalidPage, EmptyPage
# from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, get_list_or_404 from django.shortcuts import render, get_list_or_404
from django.template import Context, loader
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -17,12 +11,70 @@ 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, Gescon, Projeto from sigi.apps.convenios.models import Convenio, Gescon, Projeto
# from sigi.apps.convenios.reports import (ConvenioReport,
# ConvenioReportSemAceite, @login_required
# ConvenioPorCMReport, @staff_member_required
# ConvenioPorALReport, def report_erros_gescon(request):
# ConvenioReportSemAceiteAL, formato = request.GET.get("fmt", "html")
# ConvenioReportSemAceiteCM) convenios = (
Convenio.objects.filter(erro_gescon=True)
.order_by(
"num_processo_sf",
"projeto",
"num_convenio",
"casa_legislativa",
)
.prefetch_related(
"casa_legislativa", "casa_legislativa__municipio__uf"
)
)
rst = Gescon.load().ultima_importacao
parts = publish_parts(
rst,
writer_name="html5",
settings_overrides={
"input_encoding": "unicode",
"output_encoding": "unicode",
},
)
context = {
"convenios": convenios,
"ultima_importacao": mark_safe(parts["html_body"]),
"title": "Convênios com erros no Gescon",
}
if formato == "pdf":
return WeasyTemplateResponse(
filename="erros_gescon.pdf",
request=request,
template="convenios/erros_gescon_pdf.html",
context=context,
content_type="application/pdf",
)
elif formato == "csv":
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = (
'attachment; filename="erros_gescon.csv"'
)
fieldnames = [
"id",
"num_processo_sf",
"num_convenio",
"projeto__nome",
"casa_legislativa__nome",
"casa_legislativa__municipio__uf__sigla",
"observacao_gescon",
]
writer = csv.DictWriter(response, fieldnames)
writer.writeheader()
writer.writerows(convenios.values(*fieldnames))
response.write("\n\nResumo da última importação do Gescon\n\n")
response.write('"' + rst.replace("\n", '"\n"') + '"')
return response
return render(
request,
"convenios/erros_gescon.html",
context=context,
)
@login_required @login_required

6
sigi/menu_conf.yaml

@ -29,6 +29,12 @@ main_menu:
- title: Relatórios - title: Relatórios
icon: print icon: print
children: children:
- title: Erros importação Gescon
view_name: convenios-report_erros_gescon
- title: Órgãos com CNPJ duplicado
view_name: casas_cnpj_duplicado
- title: Órgãos com CNPJ errado
view_name: casas_cnpj_errado
- title: Eventos por UF - title: Eventos por UF
view_name: eventos_eventosporuf view_name: eventos_eventosporuf
- title: Solicitações de eventos por período - title: Solicitações de eventos por período

3
sigi/settings.py

@ -66,6 +66,7 @@ INSTALLED_APPS = [
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.contrib.sites",
"django_extensions", "django_extensions",
"django_filters", "django_filters",
] ]
@ -81,6 +82,8 @@ MIDDLEWARE = [
"sigi.apps.utils.middleware.SigiAlertsMiddleware", "sigi.apps.utils.middleware.SigiAlertsMiddleware",
] ]
SITE_ID = 1
if DEBUG: if DEBUG:
INSTALLED_APPS = [ INSTALLED_APPS = [
"debug_toolbar", "debug_toolbar",

Loading…
Cancel
Save