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

class EditBookingDateForm(ModelForm):
class Meta:
model = Booking
fields = ['checkin', 'checkout']
widgets = {
'checkin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'checkout': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
}

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

if checkin and checkout:
# Basic validation
if checkin >= checkout:
raise forms.ValidationError("La fecha de salida debe ser posterior a la de entrada.")

# Check availability
overlapping_bookings = Booking.objects.filter(
room=self.instance.room,
checkin__lt=checkout,
checkout__gt=checkin
).exclude(
id=self.instance.id # exclude same reservation
).exclude(
state='DEL'
)

if overlapping_bookings.exists():

raise forms.ValidationError("No hay disponibilidad para las fechas seleccionadas")

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

{% block content %}
<div class="container mt-4">
<h2>Editar Fechas de la Reserva: {{ booking.code }}</h2>
<hr>

<div class="row">
<div class="col-md-6">
<div class="card card-body">
<p><strong>Habitación:</strong> {{ booking.room.name }}</p>
<p><strong>Fechas actuales:</strong> {{ booking.checkin }} a {{ booking.checkout }}</p>

<form method="POST" action="">
{% csrf_token %}

{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}

<div class="mb-3">
<label class="form-label">Fecha de Entrada (Check-in)</label>
{{ form.checkin }}
{{ form.checkin.errors }}
</div>

<div class="mb-3">
<label class="form-label">Fecha de Salida (Check-out)</label>
{{ form.checkout }}
{{ form.checkout.errors }}
</div>

<a href="{% url 'home' %}" class="btn btn-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Guardar</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
7 changes: 4 additions & 3 deletions pms/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,16 @@ <h3>Reservas Realizadas</h3>
<div class="row">
<div class="col">

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

</div>
<div class="col">
<div class="col d-flex flex-column">

{% if booking.state != "DEL" %}
<a href="{% url 'delete_booking' pk=booking.id%} " >Cancelar reserva</a>
<a href="{% url 'edit_booking_date' pk=booking.id%} ">Editar Fecha de la Reserva</a>
<a class="text-danger" href="{% url 'delete_booking' pk=booking.id%} " >Cancelar reserva</a>
{% endif %}
</div>

Expand Down
87 changes: 86 additions & 1 deletion pms/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,88 @@
from django.test import TestCase
from django.utils import timezone
from datetime import timedelta
from .models import Room, Room_type, Customer, Booking
from .forms import EditBookingDateForm

# Create your tests here.
class EditBookingDateFormUnitTests(TestCase):
def setUp(self):
# Date today
self.today = timezone.now().date()

# Setup data
self.room_type = Room_type.objects.create(name="Doble", price=50.0, max_guests=2)
self.room = Room.objects.create(name="101", room_type=self.room_type)
self.customer = Customer.objects.create(name="Test", email="test@test.com", phone="123")

# Reservation A: stays today & tomorrow
self.booking_a = Booking.objects.create(
room=self.room, customer=self.customer,
checkin=self.today, checkout=self.today + timedelta(days=2),
guests=1, total=100.0, code="BKA", state="NEW"
)

# Reservation B stays from day 5 to day 10
self.booking_b = Booking.objects.create(
room=self.room, customer=self.customer,
checkin=self.today + timedelta(days=5), checkout=self.today + timedelta(days=10),
guests=1, total=250.0, code="BKB", state="NEW"
)

def test_form_valid_dates(self):
"""
Case: book in week available
expect: successfully changed
"""
data = {
'checkin': self.today + timedelta(days=20),
'checkout': self.today + timedelta(days=25)
}

form = EditBookingDateForm(data=data, instance=self.booking_a)

self.assertTrue(form.is_valid())

def test_form_invalid_checkout_before_checkin(self):
"""
Case: try to checkout before of checkin
expect: invalid form & feedback error message
"""
error_text = "La fecha de salida debe ser posterior a la de entrada."
data = {
'checkin': self.today + timedelta(days=5),
'checkout': self.today + timedelta(days=3) # Checkout before of checkin
}
form = EditBookingDateForm(data=data, instance=self.booking_a)


self.assertFalse(form.is_valid())
# Comprobamos que el error es exactamente el que programamos
self.assertIn(error_text, form.errors['__all__'])

def test_form_overlap_with_other_booking(self):
"""
CASE: overlapping with reservation B
Expect: invalid form & feedback error message
"""
error_text = "No hay disponibilidad para las fechas seleccionadas"
data = {
'checkin': self.today + timedelta(days=6), # Entra en medio de la Reserva B
'checkout': self.today + timedelta(days=8)
}
form = EditBookingDateForm(data=data, instance=self.booking_a)

self.assertFalse(form.is_valid())
self.assertIn(error_text, form.errors['__all__'])

def test_form_self_exclusion(self):
"""
Case: Submitting the form with the booking's currently saved dates
Expect : valid form
"""
data = {
'checkin': self.booking_a.checkin,
'checkout': self.booking_a.checkout
}
form = EditBookingDateForm(data=data, instance=self.booking_a)

self.assertTrue(form.is_valid())
3 changes: 2 additions & 1 deletion pms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
path("search/room/", views.RoomSearchView.as_view(), name="search"),
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/contact/", views.EditBookingView.as_view(), name="edit_booking_contact"),
path("booking/<str:pk>/edit/date/", views.EditBookingDateView.as_view(), name="edit_booking_date"),
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
31 changes: 30 additions & 1 deletion pms/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db.models import F, Q, Count, Sum
from django.shortcuts import render, redirect
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import ensure_csrf_cookie
Expand Down Expand Up @@ -244,3 +244,32 @@ def get(self, request):
'rooms': rooms
}
return render(request, "rooms.html", context)


class EditBookingDateView(View):
# renders the booking date edition form
def get(self, request, pk):
booking = get_object_or_404(Booking, pk=pk)
form = EditBookingDateForm(instance=booking)

context = {
'form': form,
'booking': booking
}
return render(request, 'edit_booking_date.html', context)

# processes the form and updates the dates
def post(self, request, pk):
booking = get_object_or_404(Booking, pk=pk)
form = EditBookingDateForm(request.POST, instance=booking)

if form.is_valid():
form.save()
return redirect('/')

# re-render if form data is invalid
context = {
'form': form,
'booking': booking
}
return render(request, 'edit_booking_date.html', context)