diff --git a/docs/pr-2-dashboard-occupancy.md b/docs/pr-2-dashboard-occupancy.md new file mode 100644 index 000000000..9a40bb013 --- /dev/null +++ b/docs/pr-2-dashboard-occupancy.md @@ -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. diff --git a/pms/templates/dashboard.html b/pms/templates/dashboard.html index 10f0285cc..7acf779bc 100644 --- a/pms/templates/dashboard.html +++ b/pms/templates/dashboard.html @@ -22,6 +22,10 @@

{{dashboard.outcoming_guests}}

Total facturado

€ {% if dashboard.invoiced.total__sum == None %}0.00{% endif %} {{dashboard.invoiced.total__sum|floatformat:2}}

+
+
% ocupación
+

{{dashboard.occupancy_percentage|floatformat:2}}%

+
-{% endblock content%} \ No newline at end of file +{% endblock content%} diff --git a/pms/tests.py b/pms/tests.py index 7ce503c2d..00b4c9fe9 100644 --- a/pms/tests.py +++ b/pms/tests.py @@ -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%") diff --git a/pms/views.py b/pms/views.py index f38563933..e5d01ceb8 100644 --- a/pms/views.py +++ b/pms/views.py @@ -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, }