mirror of https://github.com/interlegis/sigi.git
21 changed files with 649 additions and 809 deletions
@ -0,0 +1,75 @@ |
|||
from django import forms |
|||
from django.utils.translation import gettext_lazy as _ |
|||
|
|||
|
|||
class AppConfigForm(forms.Form): |
|||
form_title = _("Definições para convênios") |
|||
form_subtitle = _("Importação do Gescon") |
|||
url_gescon = forms.URLField( |
|||
required=True, |
|||
label=_("Webservice Gescon"), |
|||
initial=( |
|||
"https://adm.senado.gov.br/gestao-contratos/api/contratos" |
|||
"/busca?subespecie={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ção {s}.<br/><strong>Por exemplo:</strong> " |
|||
"https://adm.senado.gov.br/gestao-contratos/api/contratos" |
|||
"/busca?subespecie=<strong>{s}</strong>" |
|||
), |
|||
) |
|||
subespecies = forms.CharField( |
|||
required=True, |
|||
label=_("Subespécies"), |
|||
initial="AC=ACT\nTC=TC", |
|||
help_text=_( |
|||
"Informe as siglas das subespécies 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ão encontradas serão ignoradas." |
|||
), |
|||
widget=forms.Textarea(), |
|||
) |
|||
palavras = forms.CharField( |
|||
required=True, |
|||
label=_("Palavras de filtro"), |
|||
initial="ILB\nINTERLEGIS\nSCCO", |
|||
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á " |
|||
"importado.</li></ul>" |
|||
), |
|||
widget=forms.Textarea(), |
|||
) |
|||
palavras_excluir = forms.CharField( |
|||
required=True, |
|||
label=_("palavras de exclusão"), |
|||
initial="DTCOM", |
|||
help_text=_( |
|||
"Palavras que não podem aparecer no campo OBJETO dos dados do " |
|||
"Gescon." |
|||
"<ul><li>Informe uma palavra por linha.</li>" |
|||
"<li>Ocorrendo qualquer uma das palavras, o contrato será " |
|||
"ignorado.</li></ul>" |
|||
), |
|||
widget=forms.Textarea(), |
|||
) |
|||
orgaos_gestores = forms.CharField( |
|||
required=True, |
|||
label=_("Órgãos gestores"), |
|||
initial="SCCO", |
|||
help_text=_( |
|||
"Siglas de órgãos gestores que devem aparecer no campo" |
|||
"ORGAOSGESTORESTITULARES" |
|||
"<ul><li>Informe um sigla por linha.</li>" |
|||
"<li>Ocorrendo qualquer uma das siglas, o contrato será " |
|||
"importado.</li></ul>" |
|||
), |
|||
widget=forms.Textarea(), |
|||
) |
|||
@ -1,16 +1,522 @@ |
|||
import re |
|||
import sys |
|||
from django.forms.models import model_to_dict |
|||
import requests |
|||
from appconfig import config |
|||
from hashlib import md5 |
|||
from url_normalize import url_normalize |
|||
from django.db.models import Q, F |
|||
from django.contrib.sites.shortcuts import get_current_site |
|||
from django.urls import reverse |
|||
from django.utils import timezone |
|||
from django.utils.translation import gettext as _ |
|||
from django_extensions.management.jobs import HourlyJob |
|||
from sigi.apps.convenios.models import Gescon |
|||
from sigi.apps.utils import mask_cnpj, to_ascii |
|||
from sigi.apps.casas.models import Orgao |
|||
from sigi.apps.convenios.models import Projeto, Convenio |
|||
|
|||
|
|||
class Job(HourlyJob): |
|||
help = "Carga de dados do Gescon." |
|||
|
|||
gescon = None |
|||
dominio = None |
|||
subespecies = None |
|||
todos_orgaos = None |
|||
|
|||
novos = 0 |
|||
erros = 0 |
|||
atualizados = 0 |
|||
|
|||
def execute(self): |
|||
gescon = Gescon.load() |
|||
errors = gescon.importa_contratos() |
|||
if errors: |
|||
print(gescon.ultima_importacao, file=sys.stderr) |
|||
print( |
|||
"Importação iniciada em {timezone.localtime():%d/%m/%Y %H:%M:%S}\n" |
|||
"==========================================================\n" |
|||
) |
|||
|
|||
if not self.setup(): |
|||
return |
|||
|
|||
for sigla_gescon, sigla_sigi in self.subespecies: |
|||
print( |
|||
_("\n**Importando subespécie {sigla_gescon}**").format( |
|||
sigla_gescon=sigla_gescon |
|||
) |
|||
) |
|||
try: |
|||
projeto = Projeto.objects.get(sigla=sigla_sigi) |
|||
except Projeto.DoesNotExist: |
|||
print( |
|||
_( |
|||
"Projeto com a sigla {sigla_sigi} não existe no SIGI" |
|||
).format(sigla_sigi=sigla_sigi), |
|||
file=sys.stderr, |
|||
) |
|||
continue |
|||
except Projeto.MultipleObjectsReturned: |
|||
print( |
|||
_( |
|||
"A sigla {sigla_sigi} foi cadastrada com duplicidade " |
|||
"no SIGI" |
|||
).format(sigla_sigi=sigla_sigi), |
|||
file=sys.stderr, |
|||
) |
|||
continue |
|||
|
|||
nossos = self.get_contratos_gescon(sigla_gescon) |
|||
if not nossos: |
|||
print( |
|||
_( |
|||
"Nenhum contrato do tipo {sigla_gescon} pertence ao " |
|||
"ILB / Interlegis" |
|||
).format(sigla_gescon=sigla_gescon) |
|||
) |
|||
continue |
|||
|
|||
# Desmarca erros de importações passadas |
|||
Convenio.objects.filter(projeto=projeto).update(erro_gescon=False) |
|||
|
|||
for contrato in nossos: |
|||
if contrato["numero"] is None: |
|||
print( |
|||
_( |
|||
"\t* O contrato de id {id} subespécie {sub} foi " |
|||
"cadastrado no Gescon sem número e não será " |
|||
"importado." |
|||
).format(id=contrato["id"], sub=contrato["subEspecie"]), |
|||
file=sys.stderr, |
|||
) |
|||
continue |
|||
numero = re.sub( |
|||
r"(\d{4})(\d{4})", r"\1/\2", contrato["numero"].zfill(8) |
|||
) |
|||
sigad = re.sub( |
|||
r"(\d{5})(\d{6})(\d{4})(\d{2})", |
|||
r"\1.\2/\3-\4", |
|||
contrato["processo"].zfill(17), |
|||
) |
|||
|
|||
if contrato["cnpjCpfFornecedor"]: |
|||
cnpj = contrato["cnpjCpfFornecedor"].zfill(14) |
|||
cnpj_masked = mask_cnpj(cnpj) |
|||
else: |
|||
cnpj = None |
|||
cnpj_masked = None |
|||
|
|||
if contrato["nomeFornecedor"]: |
|||
nome = to_ascii( |
|||
contrato["nomeFornecedor"] |
|||
.replace("VEREADORES DE", "") |
|||
.replace("DO ESTADO", "") |
|||
.split("-")[0] |
|||
.split("/")[0] |
|||
.strip() |
|||
.replace(" ", " ") |
|||
) |
|||
else: |
|||
nome = None |
|||
|
|||
convenio = self.get_convenio( |
|||
contrato, projeto, sigad, numero, cnpj_masked |
|||
) |
|||
|
|||
if convenio: |
|||
# Encontrou um único convênio, basta atualizar seus dados |
|||
self.update_convenio( |
|||
convenio, contrato, projeto, sigad, numero |
|||
) |
|||
self.update_orgao(convenio.casa_legislativa, cnpj_masked) |
|||
else: |
|||
# Não encontrou o convênio. Um novo convênio precisa ser |
|||
# criado. Primeiro, é preciso identificar qual órgão |
|||
# consta no contrato do Gescon |
|||
orgao = self.get_orgao( |
|||
contrato, cnpj, cnpj_masked, nome, numero, sigad |
|||
) |
|||
|
|||
if orgao is None: |
|||
# Não encontrou o órgão... bora reportar o erro |
|||
print( |
|||
_( |
|||
"\t* Órgão não encontrado no SIGI ou mais de " |
|||
"um órgão encontrado com o mesmo CNPJ ou nome. " |
|||
"Favor regularizar o cadastro: \n" |
|||
"\t * CNPJ: {cnpj}\n" |
|||
"\t * Nome: {nome}" |
|||
).format( |
|||
cnpj=contrato["cnpjCpfFornecedor"], |
|||
nome=contrato["nomeFornecedor"], |
|||
), |
|||
file=sys.stderr, |
|||
) |
|||
self.erros += 1 |
|||
else: |
|||
print(gescon.ultima_importacao) |
|||
# Bora criar o convênio |
|||
self.create_convenio( |
|||
orgao, contrato, projeto, sigad, numero |
|||
) |
|||
self.update_orgao(orgao, cnpj_masked) |
|||
self.novos += 1 |
|||
|
|||
print("\n\nRESUMO DA IMPORTAÇÃO") |
|||
print("--------------------\n\n") |
|||
print( |
|||
"Novos convenios adicionados ao SIGI: {novos}".format( |
|||
novos=self.novos |
|||
) |
|||
) |
|||
print( |
|||
"Convênios atualizados..............: {atualizados}".format( |
|||
atualizados=self.atualizados |
|||
) |
|||
) |
|||
print( |
|||
"Erros encontrados..................: {erros}".format( |
|||
erros=self.erros |
|||
) |
|||
) |
|||
|
|||
def setup(self): |
|||
self.novos = 0 |
|||
self.erros = 0 |
|||
self.atualizados = 0 |
|||
|
|||
self.gescon = config.convenios |
|||
self.gescon.ultima_importacao = None |
|||
if self.gescon.checksums is None: |
|||
self.gescon.checksums = {} |
|||
|
|||
self.dominio = get_current_site(None).domain |
|||
|
|||
if self.gescon.palavras == "": |
|||
print( |
|||
_("Nenhuma palavra de pesquisa definida - processo abortado."), |
|||
file=sys.stderr, |
|||
) |
|||
return False |
|||
|
|||
if self.gescon.orgaos_gestores == "": |
|||
print( |
|||
_("Nenhum órgão gestor definido - processo abortado"), |
|||
file=sys.stderr, |
|||
) |
|||
return False |
|||
|
|||
if self.gescon.subespecies == "": |
|||
print( |
|||
_("Nenhuma subespécie definida - processo abortado."), |
|||
file=sys.stderr, |
|||
) |
|||
return False |
|||
|
|||
if "{s}" not in self.gescon.url_gescon: |
|||
print( |
|||
_( |
|||
"Falta a marcação {s} na URL para indicar o local onde " |
|||
"inserir a sigla da subespécia na consulta ao webservice " |
|||
"- processo abortado." |
|||
), |
|||
file=sys.stderr, |
|||
) |
|||
return False |
|||
|
|||
self.subespecies = { |
|||
tuple(s.split("=")) for s in self.gescon.subespecies.split() |
|||
} |
|||
self.todos_orgaos = [ |
|||
(o, f"{to_ascii(o.nome)} - {o.uf_sigla}".lower()) |
|||
for o in Orgao.objects.all() |
|||
.order_by() |
|||
.annotate(uf_sigla=F("municipio__uf__sigla")) |
|||
] |
|||
|
|||
self.palavras = self.gescon.palavras.splitlines() |
|||
self.orgaos = self.gescon.orgaos_gestores.splitlines() |
|||
self.excludentes = self.gescon.palavras_excluir.splitlines() |
|||
|
|||
return True |
|||
|
|||
def get_contratos_gescon(self, sigla_gescon): |
|||
requests.packages.urllib3.disable_warnings() |
|||
|
|||
url = self.gescon.url_gescon.format(s=sigla_gescon) |
|||
|
|||
try: |
|||
response = requests.get(url, verify=False) |
|||
except Exception as e: |
|||
print( |
|||
_("\tErro ao acessar {url}: {message}").format( |
|||
url=url, message=e.message |
|||
), |
|||
file=sys.stderr, |
|||
) |
|||
return None |
|||
|
|||
if response.status_code != 200: |
|||
print( |
|||
_( |
|||
"\tErro na leitura dos dados de {url}: " |
|||
"[{status_code}] {reason}" |
|||
).format( |
|||
url=url, |
|||
status_code=response.status_code, |
|||
reason=response.reason, |
|||
), |
|||
file=sys.stderr, |
|||
) |
|||
return None |
|||
|
|||
if "application/json" not in response.headers.get("Content-Type", ""): |
|||
print( |
|||
_( |
|||
"\tResultado da consulta à {url} não retornou dados " |
|||
"em formato json: {content_type}" |
|||
).format( |
|||
url=url, |
|||
content_type=response.headers.get("Content-Type", ""), |
|||
), |
|||
file=sys.stderr, |
|||
) |
|||
return None |
|||
|
|||
md5sum = md5(response.text.encode(response.encoding)).hexdigest() |
|||
if ( |
|||
sigla_gescon in self.gescon.checksums |
|||
and self.gescon.checksums[sigla_gescon] == md5sum |
|||
): |
|||
print( |
|||
_( |
|||
"\tDados da subespécie {sigla_gescon} inalterados no " |
|||
"Gescon. Processamento desnecessário." |
|||
).format(sigla_gescon=sigla_gescon) |
|||
) |
|||
return None |
|||
self.gescon.checksums[sigla_gescon] = md5sum |
|||
|
|||
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 self.palavras) |
|||
or any( |
|||
orgao in c["orgaosGestoresTitulares"] |
|||
for orgao in self.orgaos |
|||
if c["orgaosGestoresTitulares"] is not None |
|||
) |
|||
) |
|||
and not any(palavra in c["objeto"] for palavra in self.excludentes) |
|||
] |
|||
|
|||
print( |
|||
_("\t{qty} contratos encontrados no Gescon").format(qty=len(nossos)) |
|||
) |
|||
|
|||
return nossos |
|||
|
|||
def get_convenio(self, contrato, projeto, sigad, numero, cnpj_masked): |
|||
# Buscar o convenio pelo ID do Gescon |
|||
try: |
|||
convenio = Convenio.objects.get(id_gescon=contrato["id"]) |
|||
return convenio |
|||
except Convenio.DoesNotExist: |
|||
# Não existe, mas vamos buscar com outras chaves candidatas |
|||
pass |
|||
|
|||
# Buscar o Convenio pelo NUP # |
|||
convenios = Convenio.objects.filter( |
|||
projeto=projeto, num_processo_sf=sigad |
|||
) |
|||
num_convenios = convenios.count() |
|||
if num_convenios == 0: |
|||
# Nenhum convênio. Vamos continuar buscando em todos convenios |
|||
# desse tipo de projeto |
|||
convenios = Convenio.objects.filter(projeto=projeto) |
|||
elif num_convenios == 1: |
|||
# Encontrou um único, então deve ser ele |
|||
return convenios.get() |
|||
|
|||
# Se chegou aqui, é porque encontrou vários ou nenhum. |
|||
# Vamos tentar diferenciar pelas outras chaves candidatas |
|||
|
|||
convenios = convenios.filter( |
|||
Q(Q(num_convenio=numero) | Q(num_processo_sf=numero)) |
|||
) |
|||
num_convenios = convenios.count() |
|||
|
|||
if num_convenios == 0: |
|||
# Nenhum encontrado. Já finaliza como não encontrado |
|||
return None |
|||
elif num_convenios == 1: |
|||
# Achou um. Deve ser ele |
|||
return convenios.get() |
|||
|
|||
# Encontrou N: Marcamos todos como erro e reportamos |
|||
urls = [] |
|||
for c in convenios: |
|||
uri = reverse("admin:convenios_convenio_change", args=[c.id]) |
|||
url = url_normalize(f"{self.dominio}{uri}") |
|||
urls.append(f'<a href="{url}">{c.id}</a>') |
|||
urls = ", ".join(urls) |
|||
convenios.update( |
|||
erro_gescon=True, |
|||
observacao_gescon=_( |
|||
"Este convênio possui o mesmo número dos convenios {urls}" |
|||
).format(urls=urls), |
|||
) |
|||
print( |
|||
_( |
|||
"\t* O contrato {numero} no Gescon pode " |
|||
"ser relacionado aos seguintes convênios " |
|||
"do SIGI: {urls}" |
|||
).format(numero=numero, urls=urls), |
|||
file=sys.stderr, |
|||
) |
|||
self.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: |
|||
# Achou exatamente o único que deveria existir. |
|||
return convenios.get() |
|||
# Continua ambíguo. Não dá pra fazer nada. |
|||
return None |
|||
|
|||
def get_orgao(self, contrato, cnpj, cnpj_masked, nome, numero, sigad): |
|||
if (cnpj is None) and (nome is None): |
|||
print( |
|||
_( |
|||
"\t* O contrato {numero} no Gescon não informa nem o CNPJ " |
|||
"nem o nome do órgão, então não é possível importar " |
|||
"para o SIGI." |
|||
).format(numero=numero), |
|||
file=sys.stderr, |
|||
) |
|||
self.erros += 1 |
|||
return None |
|||
# Vamos tentar primeiro com o CNPJ |
|||
if cnpj is not None: |
|||
try: |
|||
orgao = Orgao.objects.get(cnpj=cnpj_masked) |
|||
return orgao |
|||
except Orgao.MultipleObjectsReturned: |
|||
# Pode acontecer de uma câmara usar o mesmo CNPJ da prefeitura, |
|||
# e ambos terem convênio com o ILB. Podemos tentar desambiguar |
|||
# pelo nome mais semelhante. |
|||
orgaos = Orgao.get_semelhantes( |
|||
to_ascii(contrato["nomeFornecedor"]).lower(), |
|||
[ |
|||
( |
|||
o, |
|||
f"{to_ascii(o.nome)} - {o.uf_sigla}".lower(), |
|||
) |
|||
for o in Orgao.objects.filter(cnpj=cnpj_masked) |
|||
.order_by() |
|||
.annotate(uf_sigla=F("municipio__uf__sigla")) |
|||
], |
|||
min_ratio=0, |
|||
) |
|||
if orgaos: |
|||
# Retorna o mais semelhante |
|||
return orgaos[0][0] |
|||
except Orgao.DoesNotExist: |
|||
# Não encontrou nenhum. Vamos seguir sem órgao e tentar |
|||
# encontrar pelo nome logo abaixo |
|||
pass |
|||
# Não achou pelo CNPJ. Bora ver se acha por similaridade do nome |
|||
if nome is None: |
|||
# Também não tem nome... então temos que reportar erro |
|||
print( |
|||
_( |
|||
"\t* O contrato {numero} no Gescon com NUP sigad {sigad}, " |
|||
"fornecedor {cnpj_masked} não pode ser imortado porque " |
|||
"não é possível identificar o órgão no SIGI. Cadastre um " |
|||
"órgão com o CNPJ desse fornecedor, que na próxima " |
|||
"importação este contrato será importado." |
|||
).format(numero=numero, sigad=sigad, cnpj_masked=cnpj_masked), |
|||
file=sys.stderr, |
|||
) |
|||
self.erros += 1 |
|||
return None |
|||
# Tentar primeiro com o nome igual veio do GESCON |
|||
semelhantes = Orgao.get_semelhantes( |
|||
to_ascii(contrato["nomeFornecedor"]).lower(), |
|||
self.todos_orgaos, |
|||
) |
|||
if not semelhantes: |
|||
# Não achou, então vamos tentar com o nome limpado |
|||
semelhantes = Orgao.get_semelhantes( |
|||
to_ascii(nome).lower(), |
|||
self.todos_orgaos, |
|||
) |
|||
if len(semelhantes) > 0: |
|||
# Encontrou algo semelhante.... bora usar. |
|||
return semelhantes[0][0] |
|||
# Não encontrou nada parecido. Bora reportar como erro |
|||
print( |
|||
_( |
|||
"\t* O contrato {numero} no Gescon com NUP Sigad {sigad}, " |
|||
"indica o fornecedor com CNPJ {cnpj_masked} e com o nome " |
|||
"{nome}, que não tem correspondência no SIGI. Este convênio " |
|||
"precisa ser cadastrado manualmente no SIGI para este erro " |
|||
"parar de acontecer." |
|||
).format( |
|||
numero=numero, |
|||
sigad=sigad, |
|||
cnpj_masked=cnpj_masked, |
|||
nome=contrato["nomeFornecedor"], |
|||
), |
|||
file=sys.stderr, |
|||
) |
|||
self.erros += 1 |
|||
return None |
|||
|
|||
def update_convenio(self, convenio, contrato, projeto, sigad, numero): |
|||
antes = model_to_dict(convenio) |
|||
convenio.projeto = projeto |
|||
convenio.num_processo_sf = sigad |
|||
convenio.num_convenio = numero |
|||
convenio.data_sigad = contrato["assinatura"] |
|||
convenio.observacao = contrato["objeto"] |
|||
convenio.data_retorno_assinatura = contrato["inicioVigencia"] |
|||
convenio.data_termino_vigencia = contrato["terminoVigencia"] |
|||
convenio.data_pub_diario = contrato["publicacao"] |
|||
convenio.atualizacao_gescon = timezone.localtime() |
|||
convenio.erro_gescon = False |
|||
convenio.observacao_gescon = "" |
|||
convenio.id_contrato_gescon = contrato["codTextoContrato"] or "" |
|||
convenio.id_gescon = contrato["id"] |
|||
if antes != model_to_dict(convenio): |
|||
convenio.save() |
|||
self.atualizados += 1 |
|||
|
|||
def create_convenio(self, orgao, contrato, projeto, sigad, numero): |
|||
convenio = Convenio( |
|||
casa_legislativa=orgao, |
|||
projeto=projeto, |
|||
num_processo_sf=sigad, |
|||
num_convenio=numero, |
|||
data_sigi=timezone.localdate(), |
|||
data_sigad=contrato["assinatura"], |
|||
observacao=contrato["objeto"], |
|||
data_retorno_assinatura=contrato["inicioVigencia"], |
|||
data_termino_vigencia=contrato["terminoVigencia"], |
|||
data_pub_diario=contrato["publicacao"], |
|||
atualizacao_gescon=timezone.localtime(), |
|||
observacao_gescon=_("Importado integralmente do Gescon"), |
|||
id_contrato_gescon=(contrato["codTextoContrato"] or ""), |
|||
id_gescon=contrato["id"], |
|||
) |
|||
convenio.save() |
|||
|
|||
def update_orgao(self, orgao, cnpj_masked): |
|||
# Corrigir o CNPJ do órgão se estiver diferente do |
|||
# Gescon. O gescon é um pouquinho mais confiável, |
|||
# por enquanto. |
|||
if cnpj_masked and orgao.cnpj != cnpj_masked: |
|||
orgao.cnpj = cnpj_masked |
|||
orgao.save() |
|||
|
|||
@ -0,0 +1,19 @@ |
|||
# Generated by Django 6.0.5 on 2026-09-22 12:23 |
|||
|
|||
from django.db import migrations |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
( |
|||
"convenios", |
|||
"0040_alter_convenio_options_convenio_id_gescon_and_more", |
|||
), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.DeleteModel( |
|||
name="Gescon", |
|||
), |
|||
] |
|||
@ -1,11 +0,0 @@ |
|||
{% extends "utils/report/report_pdf.html" %} |
|||
{% load i18n %} |
|||
|
|||
{% block main_content %} |
|||
{{ block.super }} |
|||
<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 %} |
|||
@ -0,0 +1,15 @@ |
|||
from django import forms |
|||
from django.utils.translation import gettext_lazy as _ |
|||
|
|||
|
|||
class AppConfigForm(forms.Form): |
|||
encerra_inscricao = forms.IntegerField( |
|||
min_value=0, |
|||
required=True, |
|||
label=_("Encerra inscrições de oficinas no Portal"), |
|||
initial=30, |
|||
help_text=_( |
|||
"Número de dias a esperar após o término da oficina antes de " |
|||
"a remover do Portal Interlegis" |
|||
), |
|||
) |
|||
@ -1,10 +0,0 @@ |
|||
from django.urls import path, include |
|||
from sigi.apps.utils import views |
|||
|
|||
urlpatterns = [ |
|||
path( |
|||
"runjob/<str:job_name>/", |
|||
views.user_run_job, |
|||
name="utils_runjob", |
|||
), |
|||
] |
|||
@ -0,0 +1,16 @@ |
|||
# Generated by Django 6.0.5 on 2026-09-22 11:22 |
|||
|
|||
from django.db import migrations |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
("utils", "0008_sigialert_repeticao_alertviews"), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.DeleteModel( |
|||
name="Config", |
|||
), |
|||
] |
|||
@ -1,18 +0,0 @@ |
|||
{% extends "admin/base_site.html" %} |
|||
{% load static i18n %} |
|||
|
|||
{% block extrastyle %} |
|||
{{ block.super }} |
|||
<style type="text/css"> |
|||
h1 { |
|||
font-size: 2.5rem; |
|||
} |
|||
h2 { |
|||
font-size: 2rem; |
|||
} |
|||
h3 { |
|||
font-size: 1.5rem; |
|||
} |
|||
</style> |
|||
{% endblock %} |
|||
{% block content %}{{ content|safe }}{% endblock %} |
|||
Loading…
Reference in new issue