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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:latest
FROM python:3.10-slim

ENV PYTHONUNBUFFERED 1
ENV DJANGO_SETTINGS_MODULE="chapp.settings"
Expand Down
Binary file modified db.sqlite3
Binary file not shown.
Binary file modified pms/form_dates/__pycache__/Ymd.cpython-310.pyc
Binary file not shown.
Binary file modified pms/migrations/__pycache__/0001_initial.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified pms/migrations/__pycache__/0008_alter_book_code.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified pms/migrations/__pycache__/0011_alter_book_code.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified pms/migrations/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file modified pms/reservation_code/__pycache__/generate.cpython-310.pyc
Binary file not shown.
5 changes: 5 additions & 0 deletions pms/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ <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: #6f42c1;">
<h5 class="small">% ocupación</h5>
<h1 class="dashboard-value">{{dashboard.occupancy_pct|floatformat:2}}%</h1>
</div>
</div>
</div>
{% endblock content%}
19 changes: 19 additions & 0 deletions pms/templates/rooms.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

{% block content %}
<h1>Habitaciones del hotel</h1>

<form method="GET" class="row g-2 align-items-end">
<div class="col-sm-6 col-md-4">
<label for="room-name-filter" class="form-label">Filtrar por nombre</label>
<input
id="room-name-filter"
class="form-control"
type="search"
name="name"
value="{{ name|default:'' }}"
placeholder="Ej: Room 1"
>
</div>
<div class="col-auto">
<button class="btn btn-primary" type="submit">Filtrar</button>
<a class="btn btn-outline-secondary" href="{% url 'rooms' %}">Limpiar</a>
</div>
</form>

{% for room in rooms%}
<div class="row card mt-3 mb-3 hover-card bg-tr-250">
<div class="col p-3">
Expand Down
147 changes: 145 additions & 2 deletions pms/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,146 @@
from django.test import TestCase
from datetime import date, timedelta

# Create your tests here.
from django.test import TestCase, override_settings
from django.urls import reverse

from .models import Booking, Customer, Room, Room_type


@override_settings(
STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage"
)
class RoomsFilterTests(TestCase):
def setUp(self):
self.room_type = Room_type.objects.create(
name="TestType",
price=10.0,
max_guests=2,
)
Room.objects.create(
room_type=self.room_type,
name="Room 1.1",
description="Test room 1.1",
)
Room.objects.create(
room_type=self.room_type,
name="Room 1.2",
description="Test room 1.2",
)
Room.objects.create(
room_type=self.room_type,
name="Room 2.1",
description="Test room 2.1",
)

def test_rooms_without_filter_lists_all_rooms(self):
url = reverse("rooms")
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, "Room 1.1")
self.assertContains(resp, "Room 1.2")
self.assertContains(resp, "Room 2.1")

def test_rooms_filter_partial_name(self):
url = reverse("rooms")
resp = self.client.get(url, {"name": "Room 1"})
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, "Room 1.1")
self.assertContains(resp, "Room 1.2")
self.assertNotContains(resp, "Room 2.1")

def test_rooms_filter_is_case_insensitive(self):
url = reverse("rooms")
resp = self.client.get(url, {"name": "room 1"})
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, "Room 1.1")
self.assertContains(resp, "Room 1.2")
self.assertNotContains(resp, "Room 2.1")

def test_rooms_filter_empty_or_whitespace_behaves_like_no_filter(self):
url = reverse("rooms")
resp = self.client.get(url, {"name": " "})
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, "Room 1.1")
self.assertContains(resp, "Room 1.2")
self.assertContains(resp, "Room 2.1")


@override_settings(
STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage"
)
class DashboardOccupancyTests(TestCase):
def setUp(self):
self.room_type = Room_type.objects.create(
name="TestType",
price=10.0,
max_guests=2,
)
self.rooms = [
Room.objects.create(
room_type=self.room_type,
name=f"Room {i}",
description=f"Test room {i}",
)
for i in range(1, 5)
]
self.customer = Customer.objects.create(
name="Test Customer",
email="test@example.com",
phone="123",
)

def test_dashboard_occupancy_pct_counts_only_new_bookings_occupying_today(self):
today = date.today()

# Counts: state NEW and checkin <= today < checkout
Booking.objects.create(
state="NEW",
checkin=today - timedelta(days=1),
checkout=today + timedelta(days=1),
room=self.rooms[0],
guests=1,
customer=self.customer,
total=10.0,
code="ABCDEFG1",
)

# Does not count: outside of today range
Booking.objects.create(
state="NEW",
checkin=today + timedelta(days=5),
checkout=today + timedelta(days=6),
room=self.rooms[1],
guests=1,
customer=self.customer,
total=10.0,
code="ABCDEFG2",
)

# Does not count: cancelled
Booking.objects.create(
state="DEL",
checkin=today - timedelta(days=1),
checkout=today + timedelta(days=1),
room=self.rooms[2],
guests=1,
customer=self.customer,
total=10.0,
code="ABCDEFG3",
)

url = reverse("dashboard")
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)

occupancy_pct = resp.context["dashboard"]["occupancy_pct"]
self.assertAlmostEqual(occupancy_pct, 25.0, places=2)

def test_dashboard_occupancy_pct_is_zero_when_no_rooms_exist(self):
Room.objects.all().delete()

url = reverse("dashboard")
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)

occupancy_pct = resp.context["dashboard"]["occupancy_pct"]
self.assertEqual(occupancy_pct, 0)
21 changes: 18 additions & 3 deletions pms/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,22 @@ def get(self, request):
.aggregate(Sum('total'))
)

total_rooms = Room.objects.count()
occupied_today = (Booking.objects
.filter(state="NEW", checkin__lte=today, checkout__gt=today)
.values("id")
).count()
occupancy_pct = 0
if total_rooms:
occupancy_pct = occupied_today / total_rooms * 100

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

}

Expand All @@ -239,8 +249,13 @@ def get(self, request, pk):
class RoomsView(View):
def get(self, request):
# renders a list of rooms
rooms = Room.objects.all().values("name", "room_type__name", "id")
name = (request.GET.get("name") or "").strip()
rooms_qs = Room.objects.all()
if name:
rooms_qs = rooms_qs.filter(name__icontains=name)
rooms = rooms_qs.order_by("name").values("name", "room_type__name", "id")
context = {
'rooms': rooms
'rooms': rooms,
'name': name,
}
return render(request, "rooms.html", context)
85 changes: 85 additions & 0 deletions requermientos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
**Prueba de selección**

*Buscador de reservas*

El proyecto consiste en una aplicación Django de un motor de reservas para un hotel que ya ha sido desarrollada y alojada en Github.

**Enlace al repositorio**: https://github.com/oscarchapp/testbookingengine

En el *README* del repositorio se encuentra la información para desplegar la aplicación en local. También se incluye un backup de SQLite con la configuración inicial.

**Descripción del proyecto actual**

La pantalla principal de la aplicación es un listado de reservas.

Una reserva consiste de:

● fecha entrada

● fecha salida

● tipo de habitación

● nº huéspedes

● datos de contacto (nombre, email, teléfono)

● precio total de la reserva

● localizador

● nº de habitación (opcional)

La funcionalidad principal es la de crear una nueva reserva. En la parte superior hay un botón de “Nueva reserva” que lleva a una pantalla que permite introducir 2 fechas (entrada y salida) y el número de huéspedes. Al buscar entre 2 fechas se muestra la siguiente información: ● Tipo de habitación

● Nº de habitaciones disponibles para este rango de fechas

● Precio total de la estancia

● Un botón para seleccionar esa habitación

Tras seleccionar la habitación deseada, se muestra un formulario donde se debe introducir los datos de contacto para la reserva (nombre, email, teléfono). Al finalizar el formulario se creará la reserva (con un localizador alfanumérico único) y se mostrará la pantalla con el listado de reservas.
Algunos detalles a tener en cuenta:

● Hay 4 tipos de habitaciones: 10 individuales, 5 dobles, 4 triples, 6 cuádruples. ● El precio diario de cada tipo de habitación es: individual=20€/día, doble=30€/día, triple=40€/día, cuádruple=50€/día

● En una habitación individual sólo cabe 1 persona (huésped). En una doble caben 1 o 2 personas. En una Triple caben 1, 2, o 3 personas. En una cuádruple caben 1, 2, 3 o 4 personas. Por lo tanto si el usuario hace una búsqueda para 3 personas, solo deberá mostrar habitaciones triples y cuádruples (siempre y cuando estén disponibles para esas fechas) .

● A medida que se vayan creando reservas, debe descontarse del número de habitaciones disponibles de ese tipo para ese rango de fechas. Solo se podrán crear reservas desde la fecha actual hasta el 31/12/2026.

**Requisitos de la prueba:**

A continuación, se describen algunos ejercicios sobre cambios de funcionalidad en la aplicación existente. Se requiere que el candidato cree pull-request por cada uno y se valorará la calidad del código, buenas prácticas y la inclusión de tests.

Cualquier cambio añadido sobre lo aquí explicado que suponga una mejora en la aplicación será bienvenida y tenida en cuenta.

1\. **Filtrar panel de habitaciones**:

En la sección de Habitaciones, actualmente se muestra una lista completa de todas las habitaciones existentes, con su nombre (ej Room 1.1).

Se debe añadir un formulario en la parte superior para poder filtrar los resultados de la lista, permitiendo buscar únicamente por el nombre de la habitación. Dicho filtro debe buscar habitaciones cuyo campo “name” contenga el texto introducido en el formulario. Por ejemplo, si se introduce “Room 1”, se mostrarán las habitaciones “Room 1.1”, “Room 1.2”, etc, pero no otras como “Room 2.1”.

*Nota*: se puede realizar de cualquier forma, bien por un formulario HTML que envíe un POST a la view, con Javascript y AJAX o cualquier otra que el candidato considere mejor. El diseño de la interfaz no es importante, puede ser un formulario básico sin estilo.

**2\. Añadir porcentaje de ocupación**

En la sección de Dashboard, se muestran varios “widgets” con datos de las reservas actuales.
Se requiere añadir un nuevo widget llamado “% ocupación”, cuyo cálculo es el siguiente: *Cantidad total de reservas en estado confirmada / número de habitaciones existentes.*

Nota: Adaptar el diseño del Dashboard para mostrar este nuevo valor en la interfaz, alineado al resto de elementos o de la forma que considere mejor el candidato.

**3\. Edición de reservas**

En la pantalla principal se muestran todas las reservas realizadas y se permite actualmente poder editar los datos de contacto.

Se requiere añadir una nueva funcionalidad para editar las fechas de la reserva, añadiendo un enlace a cada reserva similar al de “Editar datos de contacto”. Dicho enlace debe llevar una nueva página con un formulario similar al de crear nueva reserva, pero solo con los campos de fecha de entrada y salida y un botón de “Guardar”.

Además de editar los campos de la reserva, se debe realizar la misma validación que al crear una nueva reserva, es decir, comprobar que la habitación actual está disponible en las nuevas fechas seleccionadas. Si existen otras reservas ocupando en las mismas fechas, se debe mostrar un error: “No hay disponibilidad para las fechas seleccionadas”.

**Instrucciones para la prueba técnica**

En el repositorio mencionado encontrarás el punto de partida para la prueba donde debes crear los pull requests.

Tu objetivo es completar las 3 tareas que indicamos en este documento. No hay una única forma correcta de abordarla. Nos interesa ver cómo trabajas, cómo estructuras tus cambios y cómo los presentas al equipo.

Piensa en esta prueba como una pequeña simulación del día a día en el trabajo. Cuando hayas terminado, recuerda indicarnos tu nombre de usuario en Github.
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
asgiref==3.5.0
Django==4.0.2
gunicorn==20.1.0
sqlparse==0.4.2
whitenoise
whitenoise==6.0.0