Browse Source

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

pull/169/head
Sesóstris Vieira 1 year ago
parent
commit
15971eb411
  1. 66
      sigi/apps/espacos/admin.py
  2. 9
      sigi/apps/espacos/forms.py
  3. 22
      sigi/apps/espacos/migrations/0004_alter_reserva_status.py
  4. 30
      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

66
sigi/apps/espacos/admin.py

@ -1,4 +1,6 @@
from typing import Any
from django.contrib import admin, messages
from django.http.request import HttpRequest
from django.urls import path, reverse
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _, ngettext
@ -10,6 +12,7 @@ from sigi.apps.espacos.models import (
Reserva,
RecursoSolicitado,
)
from sigi.apps.espacos.forms import ReservaAdminForm
from sigi.apps.utils.mixins import CartExportMixin, LabeledResourse
@ -65,6 +68,7 @@ class RecursoAdmin(admin.ModelAdmin):
@admin.register(Reserva)
class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
form = ReservaAdminForm
resource_classes = [ReservaResource]
list_display = [
"get_status",
@ -87,7 +91,6 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
"num_processo",
]
date_hierarchy = "inicio"
actions = ["cancelar_action", "reativar_action"]
fieldsets = [
(None, {"fields": ("status",)}),
(
@ -95,6 +98,7 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
{
"fields": (
"espaco",
"evento",
"proposito",
"num_processo",
"virtual",
@ -119,20 +123,24 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
),
]
autocomplete_fields = ["espaco"]
readonly_fields = ("status",)
readonly_fields = ("evento",)
inlines = [RecursoSolicitadoInline]
def get_urls(self):
urls = super().get_urls()
model_info = self.get_model_info()
my_urls = [
path(
"<path:object_id>/cancel/",
self.admin_site.admin_view(self.cancelar_reserva),
name="%s_%s_cancel" % model_info,
def get_readonly_fields(self, request, obj=None):
if obj and hasattr(obj, "evento"):
if not hasattr(self, "_readonly_evento_alerted"):
self.message_user(
request,
_(
f"Esta reserva está vinculada ao evento '{obj.evento}'. "
"Apenas os recursos solicitados podem ser editados. "
"Os demais campos devem ser alterados no evento."
),
level=messages.ERROR,
)
]
return my_urls + urls
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)
def get_status(self, obj):
@ -147,37 +155,3 @@ class ReservaAdmin(CartExportMixin, admin.ModelAdmin):
if obj.pk is None:
return ""
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 django.forms.widgets import CheckboxSelectMultiple
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):

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",
),
),
]

30
sigi/apps/espacos/models.py

@ -1,6 +1,8 @@
import re
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
@ -53,7 +55,6 @@ class Reserva(models.Model):
max_length=1,
choices=STATUS_CHOICES,
default=STATUS_ATIVO,
editable=False,
)
espaco = models.ForeignKey(
Espaco, verbose_name=_("espaço"), on_delete=models.PROTECT
@ -126,23 +127,36 @@ class Reserva(models.Model):
raise ValidationError(
_("Data de início deve ser anterior à data de término")
)
if (
Reserva.objects.exclude(id=self.pk)
.filter(
reservas_conflitantes = Reserva.objects.exclude(id=self.pk).filter(
espaco=self.espaco,
inicio__lte=self.termino,
termino__gte=self.inicio,
status=Reserva.STATUS_ATIVO,
)
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(
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()
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
def get_sigad_url(self):
m = re.match(
"(?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",
"carga_horaria",
"casa_anfitria",
"espaco",
"contato",
"telefone",
"observacao",
@ -1134,6 +1135,40 @@ class EventoAdmin(AsciifyQParameter, CartExportReportMixin, admin.ModelAdmin):
]
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):
if request.method == "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.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 material.admin.widgets import MaterialAdminTextareaWidget
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.parlamentares.models import Parlamentar
class EventoAdminForm(forms.ModelForm):
espaco = forms.ModelChoiceField(
label=_("Reservar espaço"),
required=False,
queryset=Espaco.objects.all(),
)
class Meta:
model = Evento
fields = (
@ -23,6 +35,7 @@ class EventoAdminForm(forms.ModelForm):
"data_termino",
"carga_horaria",
"casa_anfitria",
"espaco",
"observacao",
"local",
"publico_alvo",
@ -42,8 +55,13 @@ class EventoAdminForm(forms.ModelForm):
"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):
cleaned_data = super(EventoAdminForm, self).clean()
cleaned_data = super().clean()
data_inicio = cleaned_data.get("data_inicio")
data_termino = cleaned_data.get("data_termino")
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):
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",
)
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):
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 sigi.apps.casas.models import Orgao, Servidor
from sigi.apps.contatos.models import Municipio
from sigi.apps.espacos.models import Reserva
from sigi.apps.servidores.models import Servidor
@ -374,6 +375,9 @@ class Evento(models.Model):
blank=True,
null=True,
)
reserva = models.OneToOneField(
Reserva, blank=True, null=True, on_delete=models.PROTECT
)
local = models.TextField(_("Local do evento"), blank=True)
observacao = models.TextField(_("Observações e anotações"), blank=True)
publico_alvo = models.TextField(_("Público alvo"), blank=True)
@ -606,6 +610,39 @@ class Evento(models.Model):
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):
# Força que a casa anfitriã de todas as visitas seja Senado
# Gertik #165751
@ -622,15 +659,6 @@ class Evento(models.Model):
if self.status != Evento.STATUS_CANCELADO:
self.data_cancelamento = None
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 (
self.turma == ""
and self.data_inicio
@ -653,7 +681,15 @@ class Evento(models.Model):
self.turma = f"{proximo:02}/{ano:04}"
# É 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)
for reserva in reservas_remover:
reserva.delete()
if self.total_participantes == 0 and self.moodle_courseid is None:
# Só calcula total_participantes se não tem curso relacionado

2
sigi/menu_conf.yaml

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

Loading…
Cancel
Save