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. 382
      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

382
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,95 +237,129 @@ 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_formset(self, request, form, formset, change): def save_model(self, request, obj, form, change):
instances = formset.save(commit=False) if change:
old_obj = Solicitacao.objects.get(id=obj.id)
if hasattr(request.user, "servidor"):
servidor = request.user.servidor
else: else:
servidor = None old_obj = obj
if (
agora = timezone.localtime() 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)
for item in instances: def save_formset(self, request, form, formset, change):
if ( if formset.model == ItemSolicitado:
item.status == ItemSolicitado.STATUS_SOLICITADO obj = form.instance
and item.evento is not None instances = formset.save(commit=False)
):
item.evento.status = Evento.STATUS_ACONFIRMAR if hasattr(request.user, "servidor"):
self.message_user( servidor = request.user.servidor
request, else:
_( servidor = None
f"Status do evento {item.evento} alterado para "
f"{item.evento.get_status_display()}" agora = timezone.localtime()
),
messages.INFO, for item in instances:
) if (
elif item.status == ItemSolicitado.STATUS_AUTORIZADO: obj.status == Solicitacao.STATUS_SOLICITADO
item.servidor = servidor and item.status != ItemSolicitado.STATUS_SOLICITADO
item.data_analise = agora ):
if item.evento is None: item.status = ItemSolicitado.STATUS_SOLICITADO
item.evento = Evento( self.message_user(
tipo_evento=item.tipo_evento, request,
nome=_( _(
f"{item.tipo_evento} em {item.solicitacao.casa}"[ f"O item {item} teve o status mudado para "
:100 "SOLICITADO porque a solicitação ainda não foi "
] "autorizada"
),
descricao=_(
f"{item.tipo_evento} em {item.solicitacao.casa}"
),
virtual=item.virtual,
solicitante=item.solicitacao.senador,
num_processo=item.solicitacao.num_processo,
data_pedido=item.solicitacao.data_pedido,
data_recebido_coperi=item.solicitacao.data_recebido_coperi,
data_inicio=item.inicio_desejado,
data_termino=item.inicio_desejado
+ datetime.timedelta(days=item.tipo_evento.duracao),
casa_anfitria=item.solicitacao.casa,
observacao=_(
f"Autorizado por {servidor} com a justificativa '{item.justificativa}"
), ),
status=Evento.STATUS_CONFIRMADO, messages.WARNING,
contato=item.solicitacao.contato,
telefone=item.solicitacao.telefone_contato,
) )
if (
obj.status == Solicitacao.STATUS_REJEITADO
and item.status != ItemSolicitado.STATUS_REJEITADO
):
item.status = ItemSolicitado.STATUS_REJEITADO
self.message_user( self.message_user(
request, request,
_(f"Evento {item.evento} criado automaticamente."), _(
messages.INFO, f"O item {item} teve o status mudado para "
) "REJEITADO porque a solicitação inteira foi "
else: "rejeitada"
item.evento.status = Evento.STATUS_CONFIRMADO ),
item.evento.observacao += _( messages.WARNING,
f"\nConfirmado por {servidor} com a justificativa: {item.justificativa}"
) )
item.evento.data_cancelamento = None if (
item.evento.motivo_cancelamento = "" obj.status == Solicitacao.STATUS_CONCLUIDO
and item.status == ItemSolicitado.STATUS_SOLICITADO
):
item.status = ItemSolicitado.STATUS_REJEITADO
self.message_user( self.message_user(
request, request,
_( _(
f"Status do evento {item.evento} alterado para " f"O item {item} teve o status mudado para "
f"{item.evento.get_status_display()}" "REJEITADO porque a solicitação foi concluída e "
"ele ainda estava em aberto"
), ),
messages.INFO, messages.WARNING,
)
elif item.status == ItemSolicitado.STATUS_REJEITADO:
item.servidor = servidor
item.data_analise = agora
if item.evento is not None:
item.evento.status = Evento.STATUS_CANCELADO
item.evento.observacao += _(
f"\nCancelado por {servidor} com a justificativa: {item.justificativa}"
)
item.evento.data_cancelamento = timezone.localdate()
item.evento.motivo_cancelamento = _(
f"\nCancelado por {servidor} com a justificativa: {item.justificativa}"
) )
if (
item.status == ItemSolicitado.STATUS_SOLICITADO
and item.evento is not None
):
item.evento.status = Evento.STATUS_ACONFIRMAR
self.message_user( self.message_user(
request, request,
_( _(
@ -374,31 +368,121 @@ class SolicitacaoAdmin(CartExportMixin, admin.ModelAdmin):
), ),
messages.INFO, messages.INFO,
) )
if item.evento: elif item.status == ItemSolicitado.STATUS_AUTORIZADO:
item.evento.tipo_evento = item.tipo_evento item.servidor = servidor
item.evento.nome = _( item.data_analise = agora
f"{item.tipo_evento} em {item.solicitacao.casa}"[:100] if item.evento is None:
) item.evento = Evento(
item.evento.descricao = _( tipo_evento=item.tipo_evento,
f"{item.tipo_evento} em {item.solicitacao.casa}" nome=_(
) f"{item.tipo_evento} em {item.solicitacao.casa}"[
item.evento.virtual = item.virtual :100
item.evento.solicitante = item.solicitacao.senador ]
item.evento.num_processo = item.solicitacao.num_processo ),
item.evento.data_pedido = item.solicitacao.data_pedido descricao=_(
item.evento.data_recebido_coperi = ( f"{item.tipo_evento} em {item.solicitacao.casa}"
item.solicitacao.data_recebido_coperi ),
) virtual=item.virtual,
item.evento.data_inicio = item.inicio_desejado solicitante=item.solicitacao.senador,
item.evento.data_termino = ( num_processo=item.solicitacao.num_processo,
item.inicio_desejado data_pedido=item.solicitacao.data_pedido,
+ datetime.timedelta(days=item.tipo_evento.duracao) data_recebido_coperi=item.solicitacao.data_recebido_coperi,
data_inicio=item.inicio_desejado,
data_termino=item.inicio_desejado
+ datetime.timedelta(
days=item.tipo_evento.duracao
),
casa_anfitria=item.solicitacao.casa,
observacao=_(
f"Autorizado por {servidor} com a justificativa '{item.justificativa}"
),
status=Evento.STATUS_CONFIRMADO,
contato=item.solicitacao.contato,
telefone=item.solicitacao.telefone_contato,
)
self.message_user(
request,
_(f"Evento {item.evento} criado automaticamente."),
messages.INFO,
)
else:
item.evento.status = Evento.STATUS_CONFIRMADO
item.evento.observacao += _(
f"\nConfirmado por {servidor} com a justificativa: {item.justificativa}"
)
item.evento.data_cancelamento = None
item.evento.motivo_cancelamento = ""
self.message_user(
request,
_(
f"Status do evento {item.evento} alterado para "
f"{item.evento.get_status_display()}"
),
messages.INFO,
)
elif item.status == ItemSolicitado.STATUS_REJEITADO:
item.servidor = servidor
item.data_analise = agora
if item.evento is not None:
item.evento.status = Evento.STATUS_CANCELADO
item.evento.observacao += _(
f"\nCancelado por {servidor} com a justificativa: {item.justificativa}"
)
item.evento.data_cancelamento = timezone.localdate()
item.evento.motivo_cancelamento = _(
f"\nCancelado por {servidor} com a justificativa: {item.justificativa}"
)
self.message_user(
request,
_(
f"Status do evento {item.evento} alterado para "
f"{item.evento.get_status_display()}"
),
messages.INFO,
)
if item.evento:
item.evento.tipo_evento = item.tipo_evento
item.evento.nome = _(
f"{item.tipo_evento} em {item.solicitacao.casa}"[:100]
)
item.evento.descricao = _(
f"{item.tipo_evento} em {item.solicitacao.casa}"
)
item.evento.virtual = item.virtual
item.evento.solicitante = item.solicitacao.senador
item.evento.num_processo = item.solicitacao.num_processo
item.evento.data_pedido = item.solicitacao.data_pedido
item.evento.data_recebido_coperi = (
item.solicitacao.data_recebido_coperi
)
item.evento.data_inicio = item.inicio_desejado
item.evento.data_termino = (
item.inicio_desejado
+ datetime.timedelta(days=item.tipo_evento.duracao)
)
item.evento.casa_anfitria = item.solicitacao.casa
item.evento.contato = item.solicitacao.contato
item.evento.telefone = item.solicitacao.telefone_contato
item.evento.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,
) )
item.evento.casa_anfitria = item.solicitacao.casa
item.evento.contato = item.solicitacao.contato
item.evento.telefone = item.solicitacao.telefone_contato
item.evento.save()
item.save()
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