diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 455d34e..75a7f6e 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -2,6 +2,7 @@ docutils==0.19 gunicorn==20.1.0 ibge==0.0.5 ipython==8.5.0 +pandas==1.5.1 Pillow==9.2.0 psycopg2-binary==2.9.3 python-docx==0.8.11 diff --git a/sigi/apps/home/templates/home/dashboard/resumo_convenios.html b/sigi/apps/home/templates/home/dashboard/resumo_convenios.html index d89bcb0..d971faa 100644 --- a/sigi/apps/home/templates/home/dashboard/resumo_convenios.html +++ b/sigi/apps/home/templates/home/dashboard/resumo_convenios.html @@ -1,5 +1,22 @@ {% load i18n %} - + Centro Oeste Nordeste Norte @@ -8,15 +25,16 @@ - {% for item in tabela_resumo_camara.cabecalho_topo %} - + + {% for item in tabela_resumo_camara.data_frame.columns %} + {% endfor %} - {% for cabecalho,lista in tabela_resumo_camara.lista_zip %} + {% for label, values in tabela_resumo_camara.data_frame.iterrows %} - - {% for item in lista %} - + + {% for value in values %} + {% endfor %} {% endfor %} @@ -24,18 +42,18 @@
{{item}} {{ item }}
{{cabecalho}}{{item}}{{ label }}{{ value }}
- + - - + + - - + + @@ -43,20 +61,20 @@ listfile_download - + - + - +
{% trans 'Total de câmaras' %}{% blocktrans with label_tipo=label_tipo %}Total de {{ label_tipo }} do país{% endblocktrans %} {{ tabela_resumo_camara.total_camaras }}
{% trans 'Câmaras conveniadas' %}{{ tabela_resumo_camara.total_camaras_projetos_vigentes }}{% blocktrans with label_tipo=label_tipo %}{{ label_tipo }} com convênios vigentes{% endblocktrans %}{{ tabela_resumo_camara.total_camaras_convenios_vigentes }}
{% trans 'Câmaras sem processo' %}{{ tabela_resumo_camara.camaras_sem_processo }}{% blocktrans with label_tipo=label_tipo %}{{ label_tipo }} sem nenhum convênio{% endblocktrans %}{{ tabela_resumo_camara.camaras_sem_convenio }}
{{ tabela_resumo_camara.sem_convenio.hospedagem|length }}{{ tabela_resumo_camara.sem_convenio.hospedagem }}
{% trans 'Casas sem convenio que utilizam somente serviço de registro' %} list file_download {{ tabela_resumo_camara.sem_convenio.registro|length }}{{ tabela_resumo_camara.sem_convenio.registro }}
{% trans 'Casas sem convenio que utilizam algum serviço de registro e/ou hospedagem' %} list file_download {{ tabela_resumo_camara.sem_convenio.total|length }}{{ tabela_resumo_camara.sem_convenio.total }}
\ No newline at end of file diff --git a/sigi/apps/home/templates/home/dashboard/servicos_ativos_snippet.html b/sigi/apps/home/templates/home/dashboard/servicos_ativos_snippet.html new file mode 100644 index 0000000..63a97fd --- /dev/null +++ b/sigi/apps/home/templates/home/dashboard/servicos_ativos_snippet.html @@ -0,0 +1,29 @@ +{% load static i18n %} + + \ No newline at end of file diff --git a/sigi/apps/home/urls.py b/sigi/apps/home/urls.py index 7c17303..b989559 100644 --- a/sigi/apps/home/urls.py +++ b/sigi/apps/home/urls.py @@ -26,6 +26,11 @@ urlpatterns = [ views.chart_atualizacao_servicos, name="home_chartatualizacao", ), + path( + "home/chartservicosano/", + views.chart_servicos_ano, + name="home_chartservicosano", + ), path( "home/chartperformance/", views.chart_performance, @@ -82,16 +87,3 @@ urlpatterns = [ name="home_reportsemconvenio", ), ] - -# from django.conf.urls import patterns, url - - -# urlpatterns = patterns('sigi.apps.home.views', -# url(r'^$', 'index', name='sigi_index'), -# url(r'^home/chartseit/$', 'chart_seit', name="home_chartseit"), -# url(r'^home/chartconvenios/$', 'chart_convenios', name="home_chartconvenios"), -# url(r'^home/chartcarteira/$', 'chart_carteira', name="home_chartcarteira"), -# url(r'^home/chartperformance/$', 'chart_performance', name="home_chartperformance"), -# url(r'^home/report/semconvenio/$', 'report_sem_convenio', name="home_reportsemconvenio"), - -# ) diff --git a/sigi/apps/home/views.py b/sigi/apps/home/views.py index 4d71677..dd9ecda 100644 --- a/sigi/apps/home/views.py +++ b/sigi/apps/home/views.py @@ -1,6 +1,9 @@ import calendar import csv import datetime +import locale +import numpy as np +import pandas as pd from itertools import cycle from random import randint, seed from django import forms @@ -23,7 +26,7 @@ from django.template.loader import render_to_string from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.text import slugify -from django.utils.translation import gettext as _ +from django.utils.translation import to_locale, get_language, gettext as _ from django.views.decorators.cache import never_cache from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import ( @@ -453,7 +456,41 @@ def card_add(request): @never_cache @login_required def resumo_convenios(request): - context = {"tabela_resumo_camara": busca_informacoes_camara()} + tipo = request.GET.get("tipo", "CM") + filtros = { + t.sigla: t.nome + for t in TipoOrgao.objects.filter(sigla__in=["CM", "AL"]) + } + filtros["legislativo"] = _("Todo o legislativo") + filtros["outros"] = _("Demais órgãos") + + if tipo == "CM": + label_tipo = _("Câmaras Municipais") + tipos = [ + tipo, + ] + elif tipo == "AL": + label_tipo = _("Assembleias Legislativas") + tipos = [ + tipo, + ] + elif tipo == "legislativo": + label_tipo = _("Órgãos do legislativo") + tipos = TipoOrgao.objects.filter(legislativo=True).values_list( + "sigla", flat=True + ) + else: + label_tipo = _("Outros órgãos") + tipos = TipoOrgao.objects.filter(legislativo=False).values_list( + "sigla", flat=True + ) + + context = { + "tabela_resumo_camara": busca_informacoes_camara(tipos, label_tipo), + "filtros": filtros, + "tipo": filtros[tipo], + "label_tipo": label_tipo, + } return render(request, "home/dashboard/resumo_convenios.html", context) @@ -630,6 +667,78 @@ def chart_atualizacao_servicos(request): return JsonResponse(chart) +@never_cache +@login_required +def chart_servicos_ano(request): + ano = request.GET.get("ano", None) + anos = ( + Servico.objects.filter(hospedagem_interlegis=True) + .order_by("data_ativacao__year") + .values_list("data_ativacao__year", flat=True) + .distinct("data_ativacao__year") + ) + if ano: + dados = list( + Servico.objects.filter( + hospedagem_interlegis=True, data_ativacao__year=ano + ) + .order_by("data_ativacao__month", "tipo_servico__sigla") + .values("data_ativacao__month", "tipo_servico__sigla") + .annotate(total=Count("id")) + ) + date_field = "data_ativacao__month" + lang = to_locale(get_language()) + ".UTF-8" + locale.setlocale(locale.LC_ALL, lang) + map_function = lambda x: _(calendar.month_abbr[x]) + else: + dados = list( + Servico.objects.filter(hospedagem_interlegis=True) + .order_by("data_ativacao__year", "tipo_servico__sigla") + .values("data_ativacao__year", "tipo_servico__sigla") + .annotate(total=Count("id")) + ) + date_field = "data_ativacao__year" + map_function = str + + labels_x = list({r[date_field] for r in dados}) + labels_x.sort() + labels_x = list(map(map_function, labels_x)) + + series = {} + for d in dados: + sigla = d["tipo_servico__sigla"] + label = map_function(d[date_field]) + if sigla not in series: + series[sigla] = dict(zip(labels_x, [0] * len(labels_x))) + series[sigla][label] = d["total"] + + chart = { + "data": { + "datasets": [ + { + "type": "bar", + "label": s, + "data": series[s], + "backgroundColor": getcolor(s), + } + for s in series + ], + "labels": labels_x, + }, + "options": { + "scales": {"x": {"stacked": True}, "y": {"stacked": True}}, + "plugins": {"tooltip": {"mode": "index"}}, + }, + "actionblock": render_to_string( + "home/dashboard/servicos_ativos_snippet.html", + context={"anos": anos, "ano": ano}, + request=request, + ), + } + + return JsonResponse(chart) + + # Gerente ###################################################################### @@ -836,17 +945,17 @@ def report_sem_convenio(request): modo = request.GET.get("modo", None) fmt = request.GET.get("f", "pdf") - sc = sem_convenio() + sc = sem_convenio(detalhe=True) if modo == "H": casas = sc["hospedagem"] titulo = _( - "Casas sem convenio que utilizam algum serviço de " "hospedagem" + "Casas sem convenio que utilizam algum serviço de hospedagem" ) elif modo == "R": casas = sc["registro"] titulo = _( - "Casas sem convenio que utilizam somente serviço de " "registro" + "Casas sem convenio que utilizam somente serviço de registro" ) else: casas = sc["total"] @@ -911,109 +1020,100 @@ def report_sem_convenio(request): ) -def busca_informacoes_camara(): - camaras = Orgao.objects.filter(tipo__sigla="CM") +def busca_informacoes_camara(tipos=["CM"], label_tipo=_("Câmaras Municipais")): + camaras = Orgao.objects.filter(tipo__sigla__in=tipos) convenios = Convenio.objects.filter(casa_legislativa__tipo__sigla="CM") - projetos = Projeto.objects.all() - convenios_assinados = convenios.exclude(data_retorno_assinatura=None) convenios_em_andamento = convenios.filter(data_retorno_assinatura=None) - - convenios_sem_adesao = convenios.filter(data_adesao=None) - convenios_com_adesao = convenios.exclude(data_adesao=None) - - convenios_com_aceite = convenios.exclude(data_termo_aceite=None) - - camaras_projetos_vigentes = camaras.exclude(convenio=None).exclude( - convenio__data_termino_vigencia__lt="2022-10-26" + convenios_vencidos = convenios.filter( + data_termino_vigencia__lt=timezone.localdate() ) - camaras_sem_processo = camaras.filter(convenio=None) - - # Criacao das listas para o resumo de camaras por projeto - - cabecalho_topo = [ - "", - ] # Cabecalho superior da tabela - - lista_total = [] - lista_nao_aderidas = [] - lista_aderidas = [] - lista_convenios_assinados = [] - lista_convenios_em_andamento = [] - lista_camaras_equipadas = [] - for projeto in projetos: - conv_assinados_proj = convenios_assinados.filter(projeto=projeto) - conv_em_andamento_proj = convenios_em_andamento.filter(projeto=projeto) - total = camaras.filter(convenio__projeto=projeto).count() - conv_assinados = camaras.filter( - convenio__in=conv_assinados_proj - ).count() - conv_andamento = camaras.filter( - convenio__in=conv_em_andamento_proj - ).count() - - if (total + conv_assinados + conv_andamento) > 0: - cabecalho_topo.append(projeto.sigla) - lista_total.append(total) - lista_convenios_assinados.append(conv_assinados) - lista_convenios_em_andamento.append(conv_andamento) - - # Cabecalho da esquerda na tabela - cabecalho_esquerda = ( - _("Câmaras municipais"), - _("Câmaras municipais com convênios assinados"), - _("Câmaras municipais convênios em andamento"), + camaras_projetos_vigentes = camaras.exclude(convenio=None).exclude( + convenio__in=convenios_vencidos ) - linhas = ( - lista_total, - lista_convenios_assinados, - lista_convenios_em_andamento, - ) + # Dataframe do resumo de camaras por projeto # + dataset = { + d.pop("convenio__projeto__sigla"): d + for d in camaras.values("convenio__projeto__sigla").annotate( + total=Count("id"), + assinados=Count("id", filter=Q(convenio__in=convenios_assinados)), + andamento=Count( + "id", filter=Q(convenio__in=convenios_em_andamento) + ), + vigentes=Count("id", filter=Q(id__in=camaras_projetos_vigentes)), + vencidos=Count("id", filter=~Q(id__in=camaras_projetos_vigentes)), + ) + } - # Unindo as duas listas para que o cabecalho da esquerda fique junto com sua - # respectiva linha - lista_zip = zip(cabecalho_esquerda, linhas) + if None in dataset: + rec_none = dataset.pop(None) + camaras_sem_convenio = rec_none["total"] + else: + camaras_sem_convenio = 0 + + df = pd.DataFrame(dataset) + df.rename( + index={ + "total": _(f"Total de {label_tipo} conveniados"), + "assinados": _(f"{label_tipo} com convênios assinados"), + "andamento": _(f"{label_tipo} com convênios em andamento"), + "vigentes": _(f"{label_tipo} com convênios vigentes"), + "vencidos": _(f"{label_tipo} com convênios vencidos"), + }, + inplace=True, + ) # Retornando listas em forma de dicionario return { - "cabecalho_topo": cabecalho_topo, - "lista_zip": lista_zip, + "data_frame": df, "total_camaras": camaras.count(), - "total_camaras_projetos_vigentes": camaras_projetos_vigentes.count(), - "camaras_sem_processo": camaras_sem_processo.count(), + "total_camaras_convenios_vigentes": camaras_projetos_vigentes.count(), + "camaras_sem_convenio": camaras_sem_convenio, "sem_convenio": sem_convenio(), } -def sem_convenio(): - total = ( - Orgao.objects.exclude(servico=None) - .filter(servico__data_desativacao=None, convenio=None) - .order_by("municipio__uf__sigla", "nome") - .distinct("municipio__uf__sigla", "nome") - ) - hospedagem = ( - Orgao.objects.exclude(servico=None) - .filter( - servico__data_desativacao=None, - servico__tipo_servico__modo="H", - convenio=None, +def sem_convenio(detalhe=False): + if detalhe: + total = ( + Orgao.objects.exclude(servico=None) + .filter(servico__data_desativacao=None, convenio=None) + .order_by("municipio__uf__sigla", "nome") + .distinct("municipio__uf__sigla", "nome") + .prefetch_related("servico_set", "gerentes_interlegis") ) - .order_by("municipio__uf__sigla", "nome") - .distinct("municipio__uf__sigla", "nome") - ) - reg_keys = set(total.values_list("pk", flat=True)).difference( - set(hospedagem.values_list("pk", flat=True)) - ) - registro = Orgao.objects.filter(pk__in=reg_keys).order_by( - "municipio__uf__sigla", "nome" - ) - return { - "total": total, - "hospedagem": hospedagem, - "registro": registro, - } + hospedagem = ( + Orgao.objects.exclude(servico=None) + .filter( + servico__data_desativacao=None, + servico__tipo_servico__modo="H", + convenio=None, + ) + .order_by("municipio__uf__sigla", "nome") + .distinct("municipio__uf__sigla", "nome") + .prefetch_related("servico_set", "gerentes_interlegis") + ) + result = { + "total": total, + "hospedagem": hospedagem, + "registro": total.exclude(id__in=hospedagem), + } + else: + result = dict( + Orgao.objects.exclude(servico=None) + .filter(servico__data_desativacao=None, convenio=None) + .aggregate( + total=Count("id", distinct=True), + hospedagem=Count( + "id", + filter=Q(servico__tipo_servico__modo="H"), + distinct=True, + ), + ) + ) + result["registro"] = result["total"] - result["hospedagem"] + return result def busca_informacoes_seit(mes_atual=None): diff --git a/sigi/templates/material/admin/index.html b/sigi/templates/material/admin/index.html index 715f450..0f6e128 100644 --- a/sigi/templates/material/admin/index.html +++ b/sigi/templates/material/admin/index.html @@ -7,7 +7,7 @@ {% endblock %} {% block extrahead %} - + {{ block.super }} {% endblock %}