Skip to content
Open
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
38 changes: 38 additions & 0 deletions pms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,41 @@ class Meta:
'total': forms.HiddenInput(),
'state': forms.HiddenInput(),
}


class BookingDatesForm(forms.Form):
checkin = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))
checkout = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))

def __init__(self, *args, booking=None, **kwargs):
super().__init__(*args, **kwargs)
self.booking = booking
if self.booking is None:
raise ValueError("BookingDatesForm requires a booking instance")

def clean(self):
cleaned_data = super().clean()
checkin = cleaned_data.get("checkin")
checkout = cleaned_data.get("checkout")

if not checkin or not checkout:
return cleaned_data

if checkout <= checkin:
raise forms.ValidationError(
"La fecha de salida debe ser posterior a la de entrada"
)

conflicts = Booking.objects.filter(
room=self.booking.room,
state=Booking.NEW,
checkin__lte=checkout,
checkout__gte=checkin,
).exclude(pk=self.booking.pk)

if conflicts.exists():
raise forms.ValidationError(
"No hay disponibilidad para las fechas seleccionadas"
)

return cleaned_data
24 changes: 24 additions & 0 deletions pms/templates/edit_booking_dates.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends "main.html" %}

{% block content %}
<h1>Editar fechas de reserva</h1>

<form method="post">
{% csrf_token %}
{{ form.non_field_errors }}

<div>
{{ form.checkin.label_tag }}
{{ form.checkin }}
{{ form.checkin.errors }}
</div>

<div>
{{ form.checkout.label_tag }}
{{ form.checkout }}
{{ form.checkout.errors }}
</div>

<button type="submit">Guardar</button>
</form>
{% endblock content %}
6 changes: 3 additions & 3 deletions pms/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@ <h3>Reservas Realizadas</h3>
</div>
<div class="row">
<div class="col">

<a href="{% url 'edit_booking' pk=booking.id%} " >Editar datos de contacto</a>
<a href="{% url 'edit_booking' pk=booking.id%}">Editar datos de contacto</a><br>
<a href="{% url 'edit_booking_dates' pk=booking.id%}">Editar fechas</a>
</div>
<div class="col">

</div>
<div class="col">

{% if booking.state != "DEL" %}
<a href="{% url 'delete_booking' pk=booking.id%} " >Cancelar reserva</a>
<a href="{% url 'delete_booking' pk=booking.id%}">Cancelar reserva</a>
{% endif %}
</div>

Expand Down
150 changes: 148 additions & 2 deletions pms/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,149 @@
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 EditBookingDatesViewTests(TestCase):
def setUp(self):
self.today = date.today()

self.room_type = Room_type.objects.create(
name="Doble",
price=30,
max_guests=2,
)
self.room = Room.objects.create(
room_type=self.room_type,
name="Room 1.1",
description="Test room",
)
self.customer = Customer.objects.create(
name="Test User",
email="test@example.com",
phone="123456789",
)
self.booking = Booking.objects.create(
room=self.room,
customer=self.customer,
checkin=self.today,
checkout=self.today + timedelta(days=2),
guests=2,
total=60,
code="ABC12345",
state=Booking.NEW,
)
self.url = reverse("edit_booking_dates", args=[self.booking.id])

def test_edit_dates_page_renders_with_initial_data(self):
response = self.client.get(self.url)

self.assertEqual(response.status_code, 200)
self.assertContains(response, str(self.booking.checkin))
self.assertContains(response, str(self.booking.checkout))

def test_valid_date_update_updates_booking_dates(self):
new_checkin = self.today + timedelta(days=5)
new_checkout = new_checkin + timedelta(days=2)

response = self.client.post(self.url, {
"checkin": new_checkin,
"checkout": new_checkout,
})

self.assertEqual(response.status_code, 302)
self.booking.refresh_from_db()
self.assertEqual(self.booking.checkin, new_checkin)
self.assertEqual(self.booking.checkout, new_checkout)

def test_valid_date_update_recalculates_total(self):
new_checkin = self.today + timedelta(days=10)
new_checkout = new_checkin + timedelta(days=3)

response = self.client.post(self.url, {
"checkin": new_checkin,
"checkout": new_checkout,
})

self.assertEqual(response.status_code, 302)
self.booking.refresh_from_db()
self.assertEqual(self.booking.total, 3 * self.room_type.price)

def test_booking_does_not_conflict_with_itself(self):
response = self.client.post(self.url, {
"checkin": self.booking.checkin,
"checkout": self.booking.checkout,
})

self.assertEqual(response.status_code, 302)

def test_overlapping_new_booking_blocks_date_update(self):
Booking.objects.create(
room=self.room,
customer=self.customer,
checkin=self.today + timedelta(days=3),
checkout=self.today + timedelta(days=6),
guests=2,
total=90,
code="XYZ12345",
state=Booking.NEW,
)

response = self.client.post(self.url, {
"checkin": self.today + timedelta(days=4),
"checkout": self.today + timedelta(days=7),
})

self.assertEqual(response.status_code, 200)
self.assertContains(response, "No hay disponibilidad para las fechas seleccionadas")
self.booking.refresh_from_db()
self.assertEqual(self.booking.checkin, self.today)
self.assertEqual(self.booking.checkout, self.today + timedelta(days=2))

def test_deleted_booking_does_not_block_date_update(self):
new_checkin = self.today + timedelta(days=4)
new_checkout = self.today + timedelta(days=7)

Booking.objects.create(
room=self.room,
customer=self.customer,
checkin=self.today + timedelta(days=3),
checkout=self.today + timedelta(days=6),
guests=2,
total=90,
code="DEL12345",
state=Booking.DELETED,
)

response = self.client.post(self.url, {
"checkin": new_checkin,
"checkout": new_checkout,
})

self.assertEqual(response.status_code, 302)
self.booking.refresh_from_db()
self.assertEqual(self.booking.checkin, new_checkin)
self.assertEqual(self.booking.checkout, new_checkout)

def test_adjacent_dates_follow_same_overlap_rule_as_create(self):
Booking.objects.create(
room=self.room,
customer=self.customer,
checkin=self.today + timedelta(days=3),
checkout=self.today + timedelta(days=6),
guests=2,
total=90,
code="ADJ12345",
state=Booking.NEW,
)

response = self.client.post(self.url, {
"checkin": self.today + timedelta(days=6),
"checkout": self.today + timedelta(days=8),
})

self.assertEqual(response.status_code, 200)
self.assertContains(response, "No hay disponibilidad para las fechas seleccionadas")
1 change: 1 addition & 0 deletions pms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
path("search/booking/", views.BookingSearchView.as_view(), name="booking_search"),
path("booking/<str:pk>/", views.BookingView.as_view(), name="booking"),
path("booking/<str:pk>/edit", views.EditBookingView.as_view(), name="edit_booking"),
path("booking/<str:pk>/edit-dates", views.EditBookingDatesView.as_view(), name="edit_booking_dates"),
path("booking/<str:pk>/delete", views.DeleteBookingView.as_view(), name="delete_booking"),
path("rooms/", views.RoomsView.as_view(), name="rooms"),
path("room/<str:pk>/", views.RoomDetailsView.as_view(), name="room_details"),
Expand Down
40 changes: 40 additions & 0 deletions pms/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,46 @@ def post(self, request, pk):
return redirect("/")


class EditBookingDatesView(View):
def get(self, request, pk):
booking = Booking.objects.get(id=pk)
form = BookingDatesForm(
booking=booking,
initial={
"checkin": booking.checkin,
"checkout": booking.checkout,
},
)
context = {
"booking": booking,
"form": form,
}
return render(request, "edit_booking_dates.html", context)

@method_decorator(ensure_csrf_cookie)
def post(self, request, pk):
booking = Booking.objects.get(id=pk)
form = BookingDatesForm(request.POST, booking=booking)

if form.is_valid():
checkin = form.cleaned_data["checkin"]
checkout = form.cleaned_data["checkout"]
total_days = (checkout - checkin).days

booking.checkin = checkin
booking.checkout = checkout
booking.total = total_days * booking.room.room_type.price
booking.save()

return redirect("/")

context = {
"booking": booking,
"form": form,
}
return render(request, "edit_booking_dates.html", context)


class DashboardView(View):
def get(self, request):
from datetime import date, time, datetime
Expand Down