Browse Source

Novos campos e aba de anexos em Solicitações de eventos. Gertiq #163934

pull/166/head
Sesóstris Vieira 1 year ago
parent
commit
67dff817c0
  1. 188
      sigi/apps/eventos/admin.py
  2. 104
      sigi/apps/eventos/migrations/0041_solicitacao_data_analise_solicitacao_justificativa_and_more.py
  3. 38
      sigi/apps/eventos/migrations/0042_atualiza_status_solicitacao.py
  4. 81
      sigi/apps/eventos/models.py
  5. 2
      sigi/menu_conf.yaml

188
sigi/apps/eventos/admin.py

@ -5,6 +5,7 @@ from moodle import Moodle
from django.db.models import Q from django.db.models import Q
from django.conf import settings from django.conf import settings
from django.contrib import admin, messages from django.contrib import admin, messages
from django.core.exceptions import ValidationError
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.template import Template, Context from django.template import Template, Context
@ -25,6 +26,7 @@ from sigi.apps.eventos.models import (
Modulo, Modulo,
TipoEvento, TipoEvento,
Solicitacao, Solicitacao,
AnexoSolicitacao,
ItemSolicitado, ItemSolicitado,
Funcao, Funcao,
Evento, Evento,
@ -42,54 +44,7 @@ from sigi.apps.utils.mixins import (
) )
class SolicitacaoStatusFilter(admin.SimpleListFilter):
title = _("status")
parameter_name = "status"
def lookups(self, request, model_admin):
return (
("aberto", _("Aberto")),
("analise", _("Análise")),
("inconcluso", _("Inconcluso (aberto + análise)")),
("concluido", _("Concluído")),
)
def queryset(self, request, queryset):
if self.value() == "aberto":
return queryset.exclude(
itemsolicitado__status__in=[
ItemSolicitado.STATUS_AUTORIZADO,
ItemSolicitado.STATUS_REJEITADO,
]
).distinct()
elif self.value() == "analise":
return (
queryset.filter(
itemsolicitado__status=ItemSolicitado.STATUS_SOLICITADO
)
.filter(
itemsolicitado__status__in=[
ItemSolicitado.STATUS_AUTORIZADO,
ItemSolicitado.STATUS_REJEITADO,
]
)
.distinct()
)
elif self.value() == "inconcluso":
return queryset.exclude(
id__in=Solicitacao.objects.exclude(
itemsolicitado__status=ItemSolicitado.STATUS_SOLICITADO
).only("id")
)
elif self.value() == "concluido":
return queryset.exclude(
itemsolicitado__status=ItemSolicitado.STATUS_SOLICITADO
).distinct()
return queryset
class SolicitacaoResource(LabeledResourse): class SolicitacaoResource(LabeledResourse):
status = Field(column_name="status")
oficinas = Field(column_name="oficinas solicitadas") oficinas = Field(column_name="oficinas solicitadas")
oficinas_uf = Field(column_name="número de oficinas realizadas na UF") oficinas_uf = Field(column_name="número de oficinas realizadas na UF")
@ -114,7 +69,7 @@ class SolicitacaoResource(LabeledResourse):
export_order = fields export_order = fields
def dehydrate_status(self, obj): def dehydrate_status(self, obj):
return obj.get_status() return obj.get_status_display()
def dehydrate_oficinas(self, obj): def dehydrate_oficinas(self, obj):
return ", ".join( return ", ".join(
@ -234,6 +189,11 @@ class ItemSolicitadoInline(admin.StackedInline):
autocomplete_fields = ("tipo_evento",) autocomplete_fields = ("tipo_evento",)
class AnexoSolicitacaoInline(admin.TabularInline):
model = AnexoSolicitacao
readonly_fields = ("data_pub",)
@admin.register(TipoEvento) @admin.register(TipoEvento)
class TipoEventoAdmin(admin.ModelAdmin): class TipoEventoAdmin(admin.ModelAdmin):
list_display = ["nome", "categoria"] list_display = ["nome", "categoria"]
@ -248,7 +208,7 @@ class SolicitacaoAdmin(CartExportMixin, admin.ModelAdmin):
list_display = ( list_display = (
"casa", "casa",
"get_sigad_url", "get_sigad_url",
"get_status", "status",
"senador", "senador",
"data_pedido", "data_pedido",
"data_recebido_coperi", "data_recebido_coperi",
@ -266,7 +226,7 @@ class SolicitacaoAdmin(CartExportMixin, admin.ModelAdmin):
"casa__municipio__uf__regiao", "casa__municipio__uf__regiao",
"senador", "senador",
"itemsolicitado__tipo_evento", "itemsolicitado__tipo_evento",
SolicitacaoStatusFilter, "status",
) )
list_select_related = ["casa", "casa__municipio", "casa__municipio__uf"] list_select_related = ["casa", "casa__municipio", "casa__municipio__uf"]
list_display_links = ("casa",) list_display_links = ("casa",)
@ -277,10 +237,71 @@ class SolicitacaoAdmin(CartExportMixin, admin.ModelAdmin):
"senador", "senador",
) )
date_hierarchy = "data_pedido" date_hierarchy = "data_pedido"
inlines = (ItemSolicitadoInline,) fieldsets = (
(
None,
{
"fields": [
"casa",
"senador",
"num_processo",
"descricao",
"data_pedido",
"data_recebido_coperi",
]
},
),
(
_("Autorização"),
{
"fields": [
"status",
"servidor",
"data_analise",
"justificativa",
]
},
),
(
_("Contato da Casa"),
{
"fields": [
"contato",
"email_contato",
"telefone_contato",
"whatsapp_contato",
]
},
),
(
_("Participação esperada"),
{"fields": ["estimativa_casas", "estimativa_servidores"]},
),
)
readonly_fields = ("servidor", "data_analise")
inlines = (ItemSolicitadoInline, AnexoSolicitacaoInline)
autocomplete_fields = ("casa",) autocomplete_fields = ("casa",)
def save_model(self, request, obj, form, change):
if change:
old_obj = Solicitacao.objects.get(id=obj.id)
else:
old_obj = obj
if (
obj.status != Solicitacao.STATUS_SOLICITADO
and obj.status != old_obj.status
):
obj.servidor = (
request.user.servidor
if hasattr(request.user, "servidor")
else None
)
obj.data_analise = timezone.localtime()
return super().save_model(request, obj, form, change)
def save_formset(self, request, form, formset, change): def save_formset(self, request, form, formset, change):
if formset.model == ItemSolicitado:
obj = form.instance
instances = formset.save(commit=False) instances = formset.save(commit=False)
if hasattr(request.user, "servidor"): if hasattr(request.user, "servidor"):
@ -291,6 +312,49 @@ class SolicitacaoAdmin(CartExportMixin, admin.ModelAdmin):
agora = timezone.localtime() agora = timezone.localtime()
for item in instances: for item in instances:
if (
obj.status == Solicitacao.STATUS_SOLICITADO
and item.status != ItemSolicitado.STATUS_SOLICITADO
):
item.status = ItemSolicitado.STATUS_SOLICITADO
self.message_user(
request,
_(
f"O item {item} teve o status mudado para "
"SOLICITADO porque a solicitação ainda não foi "
"autorizada"
),
messages.WARNING,
)
if (
obj.status == Solicitacao.STATUS_REJEITADO
and item.status != ItemSolicitado.STATUS_REJEITADO
):
item.status = ItemSolicitado.STATUS_REJEITADO
self.message_user(
request,
_(
f"O item {item} teve o status mudado para "
"REJEITADO porque a solicitação inteira foi "
"rejeitada"
),
messages.WARNING,
)
if (
obj.status == Solicitacao.STATUS_CONCLUIDO
and item.status == ItemSolicitado.STATUS_SOLICITADO
):
item.status = ItemSolicitado.STATUS_REJEITADO
self.message_user(
request,
_(
f"O item {item} teve o status mudado para "
"REJEITADO porque a solicitação foi concluída e "
"ele ainda estava em aberto"
),
messages.WARNING,
)
if ( if (
item.status == ItemSolicitado.STATUS_SOLICITADO item.status == ItemSolicitado.STATUS_SOLICITADO
and item.evento is not None and item.evento is not None
@ -325,7 +389,9 @@ class SolicitacaoAdmin(CartExportMixin, admin.ModelAdmin):
data_recebido_coperi=item.solicitacao.data_recebido_coperi, data_recebido_coperi=item.solicitacao.data_recebido_coperi,
data_inicio=item.inicio_desejado, data_inicio=item.inicio_desejado,
data_termino=item.inicio_desejado data_termino=item.inicio_desejado
+ datetime.timedelta(days=item.tipo_evento.duracao), + datetime.timedelta(
days=item.tipo_evento.duracao
),
casa_anfitria=item.solicitacao.casa, casa_anfitria=item.solicitacao.casa,
observacao=_( observacao=_(
f"Autorizado por {servidor} com a justificativa '{item.justificativa}" f"Autorizado por {servidor} com a justificativa '{item.justificativa}"
@ -399,6 +465,24 @@ class SolicitacaoAdmin(CartExportMixin, admin.ModelAdmin):
item.evento.telefone = item.solicitacao.telefone_contato item.evento.telefone = item.solicitacao.telefone_contato
item.evento.save() item.evento.save()
item.save() item.save()
if (
obj.status == Solicitacao.STATUS_AUTORIZADO
and not obj.itemsolicitado_set.filter(
status=ItemSolicitado.STATUS_SOLICITADO
).exists()
):
obj.status = Solicitacao.STATUS_CONCLUIDO
obj.save()
self.message_user(
request,
_(
"Status da solicitação alterado automaticamente para "
"Concluído pois não há mais itens a serem analisados"
),
messages.INFO,
)
return super().save_formset(request, form, formset, change) return super().save_formset(request, form, formset, change)
@admin.display(description=_("Oficinas solicitadas")) @admin.display(description=_("Oficinas solicitadas"))

104
sigi/apps/eventos/migrations/0041_solicitacao_data_analise_solicitacao_justificativa_and_more.py

@ -0,0 +1,104 @@
# Generated by Django 4.2.4 on 2023-09-13 22:15
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("servidores", "0013_servidor_moodle_userid"),
("eventos", "0040_alter_itemsolicitado_data_analise_and_more"),
]
operations = [
migrations.AddField(
model_name="solicitacao",
name="data_analise",
field=models.DateTimeField(
blank=True,
editable=False,
null=True,
verbose_name="data de autorização/rejeição",
),
),
migrations.AddField(
model_name="solicitacao",
name="justificativa",
field=models.TextField(blank=True, verbose_name="Justificativa"),
),
migrations.AddField(
model_name="solicitacao",
name="servidor",
field=models.ForeignKey(
blank=True,
editable=False,
help_text="Servidor que autorizou ou rejeitou a realização do evento",
limit_choices_to={"externo": False},
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="servidores.servidor",
verbose_name="servidor analisador",
),
),
migrations.AddField(
model_name="solicitacao",
name="status",
field=models.CharField(
choices=[
("S", "Solicitado"),
("A", "Autorizado"),
("R", "Rejeitado"),
("C", "Concluído"),
],
default="S",
max_length=1,
verbose_name="Status",
),
),
migrations.CreateModel(
name="AnexoSolicitacao",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"arquivo",
models.FileField(
max_length=500,
upload_to="apps/eventos/solicitacao/anexo/arquivo",
),
),
(
"descricao",
models.CharField(max_length=70, verbose_name="descrição"),
),
(
"data_pub",
models.DateTimeField(
default=django.utils.timezone.localtime,
verbose_name="data da publicação do anexo",
),
),
(
"solicitacao",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="eventos.solicitacao",
verbose_name="evento",
),
),
],
options={
"verbose_name": "Anexo",
"verbose_name_plural": "Anexos",
"ordering": ("-data_pub",),
},
),
]

38
sigi/apps/eventos/migrations/0042_atualiza_status_solicitacao.py

@ -0,0 +1,38 @@
# Generated by Django 4.2.4 on 2023-09-13 22:20
from django.db import migrations
def forwards(apps, schema_editor):
Solicitacao = apps.get_model("eventos", "Solicitacao")
for s in Solicitacao.objects.all():
statuses = list(
s.itemsolicitado_set.values_list("status", flat=True).distinct(
"status"
)
)
if statuses == ["S"]:
s.status = "S"
elif statuses == ["A"]:
s.status = "C"
elif statuses == ["R"]:
s.status = "R"
elif "S" in statuses and ("A" in statuses or "R" in statuses):
s.status = "A"
s.save()
class Migration(migrations.Migration):
dependencies = [
(
"eventos",
"0041_solicitacao_data_analise_solicitacao_justificativa_and_more",
),
]
operations = [
migrations.RunPython(
forwards,
)
]

81
sigi/apps/eventos/models.py

@ -1,3 +1,4 @@
from collections.abc import Iterable
import datetime import datetime
import re import re
from tinymce.models import HTMLField from tinymce.models import HTMLField
@ -71,6 +72,16 @@ class TipoEvento(models.Model):
class Solicitacao(models.Model): class Solicitacao(models.Model):
STATUS_SOLICITADO = "S"
STATUS_AUTORIZADO = "A"
STATUS_REJEITADO = "R"
STATUS_CONCLUIDO = "C"
STATUS_CHOICES = (
(STATUS_SOLICITADO, _("Solicitado")),
(STATUS_AUTORIZADO, _("Autorizado")),
(STATUS_REJEITADO, _("Rejeitado")),
(STATUS_CONCLUIDO, _("Concluído")),
)
casa = models.ForeignKey( casa = models.ForeignKey(
Orgao, verbose_name=_("casa solicitante"), on_delete=models.PROTECT Orgao, verbose_name=_("casa solicitante"), on_delete=models.PROTECT
) )
@ -92,6 +103,33 @@ class Solicitacao(models.Model):
blank=True, blank=True,
help_text=_("Data em que o pedido chegou na COPERI"), help_text=_("Data em que o pedido chegou na COPERI"),
) )
status = models.CharField(
_("Status"),
max_length=1,
choices=STATUS_CHOICES,
default=STATUS_SOLICITADO,
)
servidor = models.ForeignKey(
Servidor,
verbose_name=_("servidor analisador"),
help_text=_(
"Servidor que autorizou ou rejeitou a realização do evento"
),
on_delete=models.PROTECT,
limit_choices_to={"externo": False},
blank=True,
null=True,
editable=False,
)
data_analise = models.DateTimeField(
_("data de autorização/rejeição"),
blank=True,
null=True,
editable=False,
)
justificativa = models.TextField(
verbose_name=_("Justificativa"), blank=True
)
contato = models.CharField( contato = models.CharField(
_("pessoa de contato na Casa"), max_length=100, blank=True _("pessoa de contato na Casa"), max_length=100, blank=True
) )
@ -121,28 +159,6 @@ class Solicitacao(models.Model):
def __str__(self): def __str__(self):
return _(f"{self.num_processo}: {self.casa} / Senador {self.senador}") return _(f"{self.num_processo}: {self.casa} / Senador {self.senador}")
@admin.display(description="Status")
def get_status(self):
# TODO: Definir status do pedido com base no status de seus ítens:
# Aberto: Todos os itens estão em estado Solicitado
# Análise: Parte dos pedidos estão Autorizados/Rejeitados e o restante
# está Solicitado
# Concluído: Nenhum pedido está em estado Solicitado
item_status = set(
self.itemsolicitado_set.distinct("status").values_list(
"status", flat=True
)
)
if {ItemSolicitado.STATUS_SOLICITADO} == item_status:
return _("Aberto")
elif ItemSolicitado.STATUS_SOLICITADO in item_status and (
ItemSolicitado.STATUS_AUTORIZADO in item_status
or ItemSolicitado.STATUS_REJEITADO in item_status
):
return _("Análise")
else:
return _("Concluído")
@admin.display(description=_("SIGAD"), ordering="num_processo") @admin.display(description=_("SIGAD"), ordering="num_processo")
def get_sigad_url(self): def get_sigad_url(self):
m = re.match( m = re.match(
@ -256,6 +272,27 @@ class ItemSolicitado(models.Model):
return _(f"{self.tipo_evento}: {self.get_status_display()}") return _(f"{self.tipo_evento}: {self.get_status_display()}")
class AnexoSolicitacao(models.Model):
solicitacao = models.ForeignKey(
Solicitacao, on_delete=models.CASCADE, verbose_name=_("evento")
)
arquivo = models.FileField(
upload_to="apps/eventos/solicitacao/anexo/arquivo", max_length=500
)
descricao = models.CharField(_("descrição"), max_length=70)
data_pub = models.DateTimeField(
_("data da publicação do anexo"), default=timezone.localtime
)
class Meta:
ordering = ("-data_pub",)
verbose_name = _("Anexo")
verbose_name_plural = _("Anexos")
def __str__(self):
return _(f"{self.descricao} publicado em {self.data_pub}")
class Evento(models.Model): class Evento(models.Model):
STATUS_PLANEJAMENTO = "E" STATUS_PLANEJAMENTO = "E"
STATUS_AGUARDANDOSIGAD = "G" STATUS_AGUARDANDOSIGAD = "G"

2
sigi/menu_conf.yaml

@ -76,7 +76,7 @@ main_menu:
children: children:
- title: Solicitações - title: Solicitações
view_name: admin:eventos_solicitacao_changelist view_name: admin:eventos_solicitacao_changelist
querystr: status=inconcluso querystr: status__exact=S
- title: Todos os eventos - title: Todos os eventos
view_name: admin:eventos_evento_changelist view_name: admin:eventos_evento_changelist
- title: Cursos - title: Cursos

Loading…
Cancel
Save