Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/pr-2-dashboard-occupancy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# PR 2 - Dashboard Occupancy

## Objetivo de negocio
Incorporar un indicador operativo de ocupación para facilitar seguimiento diario del rendimiento comercial y de capacidad del hotel desde el dashboard principal.

## Alcance implementado
- Se agregó un nuevo widget `% ocupación` en Dashboard.
- El cálculo implementado sigue la regla definida para la prueba:
- **reservas confirmadas / total de habitaciones**.
- Se contempló el caso borde sin habitaciones disponibles para evitar errores por división por cero.

## Cambios funcionales y técnicos
1. **Backend (cálculo de métrica)**
- En `DashboardView` se añadió:
- conteo de reservas confirmadas (`state = NEW`),
- conteo total de habitaciones,
- cálculo de porcentaje de ocupación.
- Se expone el valor como `dashboard.occupancy_percentage` para consumo en template.

2. **Frontend (visualización)**
- Se agregó un quinto widget en la grilla de Dashboard para mostrar `% ocupación`.
- Se mantuvo la estructura de visualización existente y se integró el nuevo indicador en la misma jerarquía de información.

3. **Calidad**
- Se añadieron pruebas para validar:
- cálculo correcto en escenario estándar,
- resultado `0` cuando no hay habitaciones.

## Archivos modificados
- `pms/views.py`
- `pms/templates/dashboard.html`
- `pms/tests.py`

## Pruebas ejecutadas
- `python manage.py test pms.tests.DashboardOccupancyTests pms.tests.DashboardOccupancyWithoutRoomsTests`

## Resultado esperado para el producto
- Mayor visibilidad de capacidad utilizada.
- Señal temprana para decisiones de pricing, promociones o gestión de disponibilidad.
- Mejora del valor analítico del Dashboard sin incrementar complejidad operativa para el usuario final.
6 changes: 5 additions & 1 deletion pms/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ <h1 class="dashboard-value">{{dashboard.outcoming_guests}}</h1>
<h5 class="small">Total facturado</h5>
<h1 class="dashboard-value">€ {% if dashboard.invoiced.total__sum == None %}0.00{% endif %} {{dashboard.invoiced.total__sum|floatformat:2}}</h1>
</div>
<div class="card text-white p-3 card-customization" style="background-color: #005f73;">
<h5 class="small">% ocupación</h5>
<h1 class="dashboard-value">{{dashboard.occupancy_percentage|floatformat:2}}%</h1>
</div>
</div>
</div>
{% endblock content%}
{% endblock content%}
51 changes: 50 additions & 1 deletion pms/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,52 @@
from datetime import date, timedelta

from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse

from .models import Booking, Room, Room_type


@override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage")
class DashboardOccupancyTests(TestCase):
@classmethod
def setUpTestData(cls):
room_type = Room_type.objects.create(name="Simple", price=20, max_guests=1)
room_1 = Room.objects.create(room_type=room_type, name="Room 1.1", description="Desc")
room_2 = Room.objects.create(room_type=room_type, name="Room 1.2", description="Desc")
today = date.today()

Booking.objects.create(
state="NEW",
checkin=today,
checkout=today + timedelta(days=1),
room=room_1,
guests=1,
total=20,
code="CONF0001",
)
Booking.objects.create(
state="DEL",
checkin=today,
checkout=today + timedelta(days=1),
room=room_2,
guests=1,
total=20,
code="CANC0001",
)

def test_dashboard_displays_occupancy_percentage_widget(self):
response = self.client.get(reverse("dashboard"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "% ocupación")
self.assertContains(response, "50.00%")
self.assertEqual(response.context["dashboard"]["occupancy_percentage"], 50.0)


# Create your tests here.
@override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage")
class DashboardOccupancyWithoutRoomsTests(TestCase):
def test_dashboard_occupancy_is_zero_when_no_rooms_exist(self):
response = self.client.get(reverse("dashboard"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["dashboard"]["occupancy_percentage"], 0)
self.assertContains(response, "0.00%")
11 changes: 10 additions & 1 deletion pms/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,22 @@ def get(self, request):
.exclude(state="DEL")
.aggregate(Sum('total'))
)
confirmed_bookings = (Booking.objects
.filter(state="NEW")
.values("id")
).count()
total_rooms = Room.objects.values("id").count()
occupancy_percentage = 0
if total_rooms > 0:
occupancy_percentage = (confirmed_bookings / total_rooms) * 100

# preparing context data
dashboard = {
'new_bookings': new_bookings,
'incoming_guests': incoming,
'outcoming_guests': outcoming,
'invoiced': invoiced
'invoiced': invoiced,
'occupancy_percentage': occupancy_percentage,

}

Expand Down