diff --git a/pms/forms.py b/pms/forms.py
index f1bc68d08..e0c876a66 100644
--- a/pms/forms.py
+++ b/pms/forms.py
@@ -43,6 +43,11 @@ class Meta:
}
+class EditBookingDatesForm(forms.Form):
+ checkin = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}), label="Fecha de entrada")
+ checkout = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}), label="Fecha de salida")
+
+
class BookingFormExcluded(ModelForm):
class Meta:
model = Booking
diff --git a/pms/templates/edit_booking_dates.html b/pms/templates/edit_booking_dates.html
new file mode 100644
index 000000000..50c4a3619
--- /dev/null
+++ b/pms/templates/edit_booking_dates.html
@@ -0,0 +1,26 @@
+{% extends "main.html"%}
+
+{% block content %}
+
Editar fechas de reserva
+Reserva: {{booking.code}} - {{booking.room.name}}
+
+{% endblock content%}
diff --git a/pms/templates/home.html b/pms/templates/home.html
index 1e61b8024..1be2c0518 100644
--- a/pms/templates/home.html
+++ b/pms/templates/home.html
@@ -68,7 +68,9 @@ Reservas Realizadas
Editar datos de contacto
-
+ {% if booking.state != "DEL" %}
+
Editar fechas
+ {% endif %}
diff --git a/pms/tests.py b/pms/tests.py
index 7ce503c2d..a0fcaf235 100644
--- a/pms/tests.py
+++ b/pms/tests.py
@@ -1,3 +1,89 @@
-from django.test import TestCase
+from datetime import date
-# Create your tests here.
+from django.test import TestCase, Client
+from django.urls import reverse
+
+from .models import Room, Room_type, Booking, Customer
+
+
+class EditBookingDatesViewTest(TestCase):
+ def setUp(self):
+ self.client = Client()
+ self.room_type = Room_type.objects.create(name="Individual", price=20, max_guests=1)
+ self.room = Room.objects.create(name="Room 1", room_type=self.room_type, description="")
+ self.customer = Customer.objects.create(name="Test", email="test@test.com", phone="123")
+ self.booking = Booking.objects.create(
+ checkin=date(2025, 6, 1), checkout=date(2025, 6, 5),
+ room=self.room, customer=self.customer,
+ guests=1, total=80, code="AAAA1111", state="NEW"
+ )
+
+ def test_get_edit_dates_form(self):
+ response = self.client.get(reverse("edit_booking_dates", args=[self.booking.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Editar fechas de reserva")
+
+ def test_edit_dates_success(self):
+ response = self.client.post(
+ reverse("edit_booking_dates", args=[self.booking.id]),
+ {"checkin": "2025-07-01", "checkout": "2025-07-03"}
+ )
+ self.assertRedirects(response, "/")
+ self.booking.refresh_from_db()
+ self.assertEqual(self.booking.checkin, date(2025, 7, 1))
+ self.assertEqual(self.booking.checkout, date(2025, 7, 3))
+ # total recalculated: 2 days * 20 = 40
+ self.assertEqual(self.booking.total, 40)
+
+ def test_edit_dates_conflict_shows_error(self):
+ # Another booking on the same room overlapping the new dates
+ Booking.objects.create(
+ checkin=date(2025, 7, 1), checkout=date(2025, 7, 5),
+ room=self.room, customer=self.customer,
+ guests=1, total=80, code="BBBB2222", state="NEW"
+ )
+ response = self.client.post(
+ reverse("edit_booking_dates", args=[self.booking.id]),
+ {"checkin": "2025-07-02", "checkout": "2025-07-04"}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "No hay disponibilidad para las fechas seleccionadas")
+ # Booking unchanged
+ self.booking.refresh_from_db()
+ self.assertEqual(self.booking.checkin, date(2025, 6, 1))
+
+ def test_edit_dates_no_conflict_with_itself(self):
+ # Shifting the same booking's dates slightly should not conflict with itself
+ response = self.client.post(
+ reverse("edit_booking_dates", args=[self.booking.id]),
+ {"checkin": "2025-06-02", "checkout": "2025-06-06"}
+ )
+ self.assertRedirects(response, "/")
+ self.booking.refresh_from_db()
+ self.assertEqual(self.booking.checkin, date(2025, 6, 2))
+
+ def test_edit_dates_ignores_deleted_bookings(self):
+ # A deleted booking on same room/dates should not block
+ Booking.objects.create(
+ checkin=date(2025, 7, 1), checkout=date(2025, 7, 5),
+ room=self.room, customer=self.customer,
+ guests=1, total=80, code="CCCC3333", state="DEL"
+ )
+ response = self.client.post(
+ reverse("edit_booking_dates", args=[self.booking.id]),
+ {"checkin": "2025-07-02", "checkout": "2025-07-04"}
+ )
+ self.assertRedirects(response, "/")
+
+ def test_edit_dates_allows_adjacent_bookings(self):
+ # Booking on same room ending on the day the new one starts (same-day checkout/checkin)
+ Booking.objects.create(
+ checkin=date(2025, 7, 1), checkout=date(2025, 7, 5),
+ room=self.room, customer=self.customer,
+ guests=1, total=80, code="DDDD4444", state="NEW"
+ )
+ response = self.client.post(
+ reverse("edit_booking_dates", args=[self.booking.id]),
+ {"checkin": "2025-07-05", "checkout": "2025-07-08"}
+ )
+ self.assertRedirects(response, "/")
diff --git a/pms/urls.py b/pms/urls.py
index c18714abf..2acbd0a22 100644
--- a/pms/urls.py
+++ b/pms/urls.py
@@ -8,6 +8,7 @@
path("search/booking/", views.BookingSearchView.as_view(), name="booking_search"),
path("booking//", views.BookingView.as_view(), name="booking"),
path("booking//edit", views.EditBookingView.as_view(), name="edit_booking"),
+ path("booking//edit-dates", views.EditBookingDatesView.as_view(), name="edit_booking_dates"),
path("booking//delete", views.DeleteBookingView.as_view(), name="delete_booking"),
path("rooms/", views.RoomsView.as_view(), name="rooms"),
path("room//", views.RoomDetailsView.as_view(), name="room_details"),
diff --git a/pms/views.py b/pms/views.py
index f38563933..693641e7f 100644
--- a/pms/views.py
+++ b/pms/views.py
@@ -6,6 +6,7 @@
from .form_dates import Ymd
from .forms import *
+from .forms import EditBookingDatesForm
from .models import Room
from .reservation_code import generate
@@ -174,6 +175,43 @@ def post(self, request, pk):
return redirect("/")
+class EditBookingDatesView(View):
+ def get(self, request, pk):
+ booking = Booking.objects.get(id=pk)
+ form = EditBookingDatesForm(initial={
+ 'checkin': booking.checkin,
+ 'checkout': booking.checkout,
+ })
+ context = {'form': form, 'booking': booking}
+ 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 = EditBookingDatesForm(request.POST)
+ if form.is_valid():
+ checkin = form.cleaned_data['checkin']
+ checkout = form.cleaned_data['checkout']
+ # Check room availability excluding the current booking
+ conflicting = Booking.objects.filter(
+ room=booking.room,
+ state="NEW",
+ checkin__lt=checkout,
+ checkout__gt=checkin,
+ ).exclude(id=booking.id).exists()
+ if conflicting:
+ form.add_error(None, "No hay disponibilidad para las fechas seleccionadas")
+ else:
+ booking.checkin = checkin
+ booking.checkout = checkout
+ total_days = (checkout - checkin).days
+ booking.total = total_days * booking.room.room_type.price
+ booking.save()
+ return redirect("/")
+ context = {'form': form, 'booking': booking}
+ return render(request, "edit_booking_dates.html", context)
+
+
class DashboardView(View):
def get(self, request):
from datetime import date, time, datetime