Browse Source

Evento cria reserva de espaço automaticamente. Gertiq #167321

pull/169/head
Sesóstris Vieira 1 year ago
parent
commit
15971eb411
  1. 68
      sigi/apps/espacos/admin.py
  2. 9
      sigi/apps/espacos/forms.py
  3. 22
      sigi/apps/espacos/migrations/0004_alter_reserva_status.py
  4. 38
      sigi/apps/espacos/models.py
  5. 15
      sigi/apps/espacos/templates/admin/espacos/reserva/change_form.html
  6. 35
      sigi/apps/eventos/admin.py
  7. 38
      sigi/apps/eventos/forms.py
  8. 27
      sigi/apps/eventos/migrations/0055_evento_reserva.py
  9. 54
      sigi/apps/eventos/models.py
  10. 2
      sigi/menu_conf.yaml

68
sigi/apps/espacos/admin.py

@ -1,4 +1,6 @@
from typing import Any
from django.contrib import admin, messages from django.contrib import admin, messages
from django.http.request import HttpRequest
from django.urls import path, reverse from django.urls import path, reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _, ngettext from django.utils.translation import gettext as _, ngettext
@ -10,6 +12,7 @@ from sigi.apps.espacos.models import (
Reserva, Reserva,
RecursoSolicitado, RecursoSolicitado,
) )
from sigi.apps.espacos.forms import ReservaAdminForm
from sigi.apps.utils.mixins import CartExportMixin, LabeledResourse from sigi.apps.utils.mixins import CartExportMixin, LabeledResourse
@ -65,6 +68,7 @@ class RecursoAdmin(admin.ModelAdmin):
@admin.register(Reserva) @admin.register(Reserva)
class ReservaAdmin(CartExportMixin, admin.ModelAdmin): class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
form = ReservaAdminForm
resource_classes = [ReservaResource] resource_classes = [ReservaResource]
list_display = [ list_display = [
"get_status", "get_status",
@ -87,7 +91,6 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
"num_processo", "num_processo",
] ]
date_hierarchy = "inicio" date_hierarchy = "inicio"
actions = ["cancelar_action", "reativar_action"]
fieldsets = [ fieldsets = [
(None, {"fields": ("status",)}), (None, {"fields": ("status",)}),
( (
@ -95,6 +98,7 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
{ {
"fields": ( "fields": (
"espaco", "espaco",
"evento",
"proposito", "proposito",
"num_processo", "num_processo",
"virtual", "virtual",
@ -119,20 +123,24 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
), ),
] ]
autocomplete_fields = ["espaco"] autocomplete_fields = ["espaco"]
readonly_fields = ("status",) readonly_fields = ("evento",)
inlines = [RecursoSolicitadoInline] inlines = [RecursoSolicitadoInline]
def get_urls(self): def get_readonly_fields(self, request, obj=None):
urls = super().get_urls() if obj and hasattr(obj, "evento"):
model_info = self.get_model_info() if not hasattr(self, "_readonly_evento_alerted"):
my_urls = [ self.message_user(
path( request,
"<path:object_id>/cancel/", _(
self.admin_site.admin_view(self.cancelar_reserva), f"Esta reserva está vinculada ao evento '{obj.evento}'. "
name="%s_%s_cancel" % model_info, "Apenas os recursos solicitados podem ser editados. "
) "Os demais campos devem ser alterados no evento."
] ),
return my_urls + urls level=messages.ERROR,
)
self._readonly_evento_alerted = True
return self.get_fields(request)
return super().get_readonly_fields(request, obj)
@admin.display(description=_("Status"), ordering="status", boolean=True) @admin.display(description=_("Status"), ordering="status", boolean=True)
def get_status(self, obj): def get_status(self, obj):
@ -147,37 +155,3 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
if obj.pk is None: if obj.pk is None:
return "" return ""
return mark_safe(obj.get_sigad_url()) return mark_safe(obj.get_sigad_url())
def cancelar_reserva(self, request, object_id):
reserva = get_object_or_404(Reserva, id=object_id)
reserva.status = Reserva.STATUS_CANCELADO
reserva.save()
return redirect(
reverse(
"admin:%s_%s_change" % self.get_model_info(), args=[object_id]
)
+ "?"
+ self.get_preserved_filters(request)
)
@admin.action(description=_("Cancelar as reservas selecionadas"))
def cancelar_action(self, request, queryset):
count = queryset.update(status=Reserva.STATUS_CANCELADO)
self.message_user(
request,
ngettext(
"Uma reserva cancelada", f"{count} reservas canceladas", count
),
messages.SUCCESS,
)
@admin.action(description=_("Reativar as reservas selecionadas"))
def reativar_action(self, request, queryset):
count = queryset.update(status=Reserva.STATUS_ATIVO)
self.message_user(
request,
ngettext(
"Uma reserva reativada", f"{count} reservas reativadas", count
),
messages.SUCCESS,
)

9
sigi/apps/espacos/forms.py

@ -4,7 +4,14 @@ from django import forms
from material.admin.widgets import MaterialAdminDateWidget from material.admin.widgets import MaterialAdminDateWidget
from django.forms.widgets import CheckboxSelectMultiple from django.forms.widgets import CheckboxSelectMultiple
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from sigi.apps.espacos.models import Espaco from sigi.apps.espacos.models import Espaco, Reserva
class ReservaAdminForm(forms.ModelForm):
class Meta:
model = Reserva
widgets = {"status": forms.RadioSelect}
fields = "__all__"
class UsoEspacoReportForm(forms.Form): class UsoEspacoReportForm(forms.Form):

22
sigi/apps/espacos/migrations/0004_alter_reserva_status.py

@ -0,0 +1,22 @@
# Generated by Django 4.2.4 on 2023-12-05 13:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("espacos", "0003_reserva_data_pedido_reserva_num_processo_and_more"),
]
operations = [
migrations.AlterField(
model_name="reserva",
name="status",
field=models.CharField(
choices=[("A", "Ativo"), ("C", "Cancelado")],
default="A",
max_length=1,
verbose_name="status",
),
),
]

38
sigi/apps/espacos/models.py

@ -1,6 +1,8 @@
import re import re
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -53,7 +55,6 @@ class Reserva(models.Model):
max_length=1, max_length=1,
choices=STATUS_CHOICES, choices=STATUS_CHOICES,
default=STATUS_ATIVO, default=STATUS_ATIVO,
editable=False,
) )
espaco = models.ForeignKey( espaco = models.ForeignKey(
Espaco, verbose_name=_("espaço"), on_delete=models.PROTECT Espaco, verbose_name=_("espaço"), on_delete=models.PROTECT
@ -126,23 +127,36 @@ class Reserva(models.Model):
raise ValidationError( raise ValidationError(
_("Data de início deve ser anterior à data de término") _("Data de início deve ser anterior à data de término")
) )
if ( reservas_conflitantes = Reserva.objects.exclude(id=self.pk).filter(
Reserva.objects.exclude(id=self.pk) espaco=self.espaco,
.filter( inicio__lte=self.termino,
espaco=self.espaco, termino__gte=self.inicio,
inicio__lte=self.termino, status=Reserva.STATUS_ATIVO,
termino__gte=self.inicio, )
if reservas_conflitantes.exists():
link_list = ", ".join(
[
f"<a href='"
f"{reverse('admin:espacos_reserva_change', args=[reserva.pk])}'>"
f"{ reserva }</a>"
for reserva in reservas_conflitantes
]
) )
.exists()
):
raise ValidationError( raise ValidationError(
_( mark_safe(
"Já existe um evento neste mesmo espaço que conflita com " _(
"as datas solicitadas" "Existe(m) reserva(s) que conflita(m) com essas datas: "
f"{ link_list }"
)
) )
) )
return super().clean() return super().clean()
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
def get_sigad_url(self): def get_sigad_url(self):
m = re.match( m = re.match(
"(?P<orgao>00100|00200)\.(?P<sequencial>\d{6})/(?P<ano>" "(?P<orgao>00100|00200)\.(?P<sequencial>\d{6})/(?P<ano>"

15
sigi/apps/espacos/templates/admin/espacos/reserva/change_form.html

@ -1,15 +0,0 @@
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
{% if object_id %}
<li>
{% url opts|admin_urlname:'cancel' object_id|admin_urlquote as tool_url %}
<a href="{% add_preserved_filters tool_url %}">
<i class="left material-icons" aria-hidden="true">cancel</i>
{% trans "Cancelar reserva" %}
</a>
</li>
{% endif %}
{{ block.super }}
{% endblock %}

35
sigi/apps/eventos/admin.py

@ -873,6 +873,7 @@ class EventoAdmin(AsciifyQParameter, CartExportReportMixin, admin.ModelAdmin):
"data_termino", "data_termino",
"carga_horaria", "carga_horaria",
"casa_anfitria", "casa_anfitria",
"espaco",
"contato", "contato",
"telefone", "telefone",
"observacao", "observacao",
@ -1134,6 +1135,40 @@ class EventoAdmin(AsciifyQParameter, CartExportReportMixin, admin.ModelAdmin):
] ]
return my_urls + urls return my_urls + urls
def save_model(self, request, obj, form, change):
if change:
reserva_original = self.get_object(request, obj.pk).reserva
super().save_model(request, obj, form, change)
if change:
if reserva_original is None and obj.reserva is not None:
self.message_user(
request,
_(
f"Reserva do espaço '{obj.reserva.espaco}' criada para "
"este evento.",
),
level=messages.SUCCESS,
)
if reserva_original is not None:
if obj.reserva is None:
self.message_user(
request,
_(
f"Reserva do espaço '{reserva_original.espaco}' "
"excluída.",
),
level=messages.SUCCESS,
)
else:
self.message_user(
request,
_(
f"Reserva do espaço '{obj.reserva.espaco}' "
"atualizada.",
),
level=messages.SUCCESS,
)
def declaracao_report(self, request, object_id): def declaracao_report(self, request, object_id):
if request.method == "POST": if request.method == "POST":
form = SelecionaModeloForm(request.POST) form = SelecionaModeloForm(request.POST)

38
sigi/apps/eventos/forms.py

@ -1,12 +1,24 @@
from collections.abc import Mapping
from typing import Any
from django import forms from django import forms
from django.core.files.base import File
from django.db.models.base import Model
from django.forms.utils import ErrorList
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from material.admin.widgets import MaterialAdminTextareaWidget from material.admin.widgets import MaterialAdminTextareaWidget
from sigi.apps.casas.models import Funcionario, Orgao from sigi.apps.casas.models import Funcionario, Orgao
from sigi.apps.espacos.models import Espaco, Reserva
from sigi.apps.eventos.models import Convite, ModeloDeclaracao, Evento from sigi.apps.eventos.models import Convite, ModeloDeclaracao, Evento
from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.parlamentares.models import Parlamentar
class EventoAdminForm(forms.ModelForm): class EventoAdminForm(forms.ModelForm):
espaco = forms.ModelChoiceField(
label=_("Reservar espaço"),
required=False,
queryset=Espaco.objects.all(),
)
class Meta: class Meta:
model = Evento model = Evento
fields = ( fields = (
@ -23,6 +35,7 @@ class EventoAdminForm(forms.ModelForm):
"data_termino", "data_termino",
"carga_horaria", "carga_horaria",
"casa_anfitria", "casa_anfitria",
"espaco",
"observacao", "observacao",
"local", "local",
"publico_alvo", "publico_alvo",
@ -42,8 +55,13 @@ class EventoAdminForm(forms.ModelForm):
"motivo_cancelamento", "motivo_cancelamento",
) )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.reserva:
self.initial["espaco"] = self.instance.reserva.espaco
def clean(self): def clean(self):
cleaned_data = super(EventoAdminForm, self).clean() cleaned_data = super().clean()
data_inicio = cleaned_data.get("data_inicio") data_inicio = cleaned_data.get("data_inicio")
data_termino = cleaned_data.get("data_termino") data_termino = cleaned_data.get("data_termino")
publicar = cleaned_data.get("publicar") publicar = cleaned_data.get("publicar")
@ -57,11 +75,27 @@ class EventoAdminForm(forms.ModelForm):
if publicar and (data_inicio is None or data_termino is None): if publicar and (data_inicio is None or data_termino is None):
raise forms.ValidationError( raise forms.ValidationError(
_( _(
"Para publicar no site é preciso ter data início e data término" "Para publicar no site é preciso ter data início e "
"data término"
), ),
code="cannot_publish", code="cannot_publish",
) )
espaco = cleaned_data["espaco"]
if (self.instance.reserva is None) and (espaco is None):
return
if self.instance.reserva is None:
self.instance.reserva = Reserva(espaco=espaco)
elif espaco is None:
self.instance.reserva = None
else:
self.instance.reserva.espaco = espaco
if self.instance.reserva:
self.instance.update_reserva()
return cleaned_data
class SelecionaModeloForm(forms.Form): class SelecionaModeloForm(forms.Form):
modelo = forms.ModelChoiceField( modelo = forms.ModelChoiceField(

27
sigi/apps/eventos/migrations/0055_evento_reserva.py

@ -0,0 +1,27 @@
# Generated by Django 4.2.4 on 2023-12-01 12:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("espacos", "0003_reserva_data_pedido_reserva_num_processo_and_more"),
(
"eventos",
"0054_equipe_emissao_passagens_equipe_qtde_diarias_and_more",
),
]
operations = [
migrations.AddField(
model_name="evento",
name="reserva",
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="espacos.reserva",
),
),
]

54
sigi/apps/eventos/models.py

@ -15,6 +15,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from sigi.apps.casas.models import Orgao, Servidor from sigi.apps.casas.models import Orgao, Servidor
from sigi.apps.contatos.models import Municipio from sigi.apps.contatos.models import Municipio
from sigi.apps.espacos.models import Reserva
from sigi.apps.servidores.models import Servidor from sigi.apps.servidores.models import Servidor
@ -374,6 +375,9 @@ class Evento(models.Model):
blank=True, blank=True,
null=True, null=True,
) )
reserva = models.OneToOneField(
Reserva, blank=True, null=True, on_delete=models.PROTECT
)
local = models.TextField(_("Local do evento"), blank=True) local = models.TextField(_("Local do evento"), blank=True)
observacao = models.TextField(_("Observações e anotações"), blank=True) observacao = models.TextField(_("Observações e anotações"), blank=True)
publico_alvo = models.TextField(_("Público alvo"), blank=True) publico_alvo = models.TextField(_("Público alvo"), blank=True)
@ -606,6 +610,39 @@ class Evento(models.Model):
self.save() self.save()
def clean(self):
super().clean()
if (
self.data_inicio
and self.data_termino
and self.data_inicio > self.data_termino
):
raise ValidationError(
_("Data de término deve ser posterior à data de início")
)
if self.reserva:
self.update_reserva()
self.reserva.clean()
def update_reserva(self):
# Prepara e valida a reserva de espaço para ser salva
# Gertiq #167321
if self.reserva is not None:
self.reserva.proposito = self.nome
self.reserva.virtual = self.virtual
self.reserva.data_pedido = self.data_pedido
self.reserva.inicio = self.data_inicio
self.reserva.termino = self.data_termino
self.reserva.num_processo = self.num_processo
self.reserva.informacoes = self.observacao
self.reserva.solicitante = self.solicitante
self.reserva.contato = self.contato
self.reserva.telefone_contato = self.telefone
if self.status in (self.STATUS_CANCELADO, self.STATUS_SOBRESTADO):
self.reserva.status = Reserva.STATUS_CANCELADO
else:
self.reserva.status = Reserva.STATUS_ATIVO
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Força que a casa anfitriã de todas as visitas seja Senado # Força que a casa anfitriã de todas as visitas seja Senado
# Gertik #165751 # Gertik #165751
@ -622,15 +659,6 @@ class Evento(models.Model):
if self.status != Evento.STATUS_CANCELADO: if self.status != Evento.STATUS_CANCELADO:
self.data_cancelamento = None self.data_cancelamento = None
self.motivo_cancelamento = "" self.motivo_cancelamento = ""
if (
self.data_inicio
and self.data_termino
and self.data_inicio > self.data_termino
):
raise ValidationError(
_("Data de término deve ser posterior à data de início")
)
if ( if (
self.turma == "" self.turma == ""
and self.data_inicio and self.data_inicio
@ -653,7 +681,15 @@ class Evento(models.Model):
self.turma = f"{proximo:02}/{ano:04}" self.turma = f"{proximo:02}/{ano:04}"
# É preciso salvar para poder usar o relacionamento com convites # É preciso salvar para poder usar o relacionamento com convites
if self.reserva is None:
reservas_remover = list(Reserva.objects.filter(evento=self))
else:
self.update_reserva()
self.reserva.save()
reservas_remover = Reserva.objects.none()
super().save(*args, **kwargs) super().save(*args, **kwargs)
for reserva in reservas_remover:
reserva.delete()
if self.total_participantes == 0 and self.moodle_courseid is None: if self.total_participantes == 0 and self.moodle_courseid is None:
# Só calcula total_participantes se não tem curso relacionado # Só calcula total_participantes se não tem curso relacionado

2
sigi/menu_conf.yaml

@ -76,7 +76,7 @@ main_menu:
children: children:
- title: Reservas - title: Reservas
view_name: admin:espacos_reserva_changelist view_name: admin:espacos_reserva_changelist
querystr: status=A querystr: status__exact=A
- title: Agenda de reservas - title: Agenda de reservas
view_name: espacos_agenda view_name: espacos_agenda
- title: Uso dos espaços - title: Uso dos espaços

Loading…
Cancel
Save