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}}
+
+
+ {% csrf_token %} + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} + {% for field in form %} +
+
{{field.label_tag}}
+
{{field}}
+
+ {% endfor %} + Volver + +
+
+{% 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