diff --git a/services/car_renting_service.py b/services/car_renting_service.py index c14ae48a..d7d6c675 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -1,3 +1,4 @@ +from zoneinfo import ZoneInfo from fastapi import HTTPException, status from api_schemas.car_booking_schema import CarBookingCreate, CarBookingUpdate from db_models.council_model import Council_DB @@ -107,15 +108,9 @@ def create_new_booking( if data.start_time < datetime.now(UTC): raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking start time cannot be in the past.") - if not manage_permission: - # Unconfirm booking between 17:00 and 08:00 - if data.start_time.hour < 8 or data.start_time.hour >= 17: - booking_confirmed = False - if data.end_time.hour < 8 or data.end_time.hour >= 17: - booking_confirmed = False - # Unconfirm booking on weekends - if data.start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday - booking_confirmed = False + # Unconfirm personal bookings /Vic, BilF 25/26 + if not manage_permission and data.personal: + booking_confirmed = False # Require council_id if not personal booking if not data.personal and data.council_id is None: @@ -172,10 +167,6 @@ def booking_update( else: booking_confirmed = car_booking.confirmed - # Automagically assume the user wants the booking confirmed, they should not have manual control unlike admins - if not manage_permission and booking_confirmed == False: - booking_confirmed = True - # only check for illegal overlap if new times are provided if data.start_time is not None or data.end_time is not None: # Use new values if provided, otherwise use existing values @@ -196,18 +187,24 @@ def booking_update( if booking_overlaps: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking overlaps with another booking.") - if not manage_permission: + # unconfirms personal bookings that now fall outside of school hours + if not manage_permission and (data.personal or data.personal is None and car_booking.personal): + + stockholm_tz = ZoneInfo("Europe/Stockholm") + # Unconfirm booking between 17:00 and 08:00 if data.start_time is not None: - if data.start_time.hour < 8 or data.start_time.hour >= 17: + sthlm_start_time = data.start_time.astimezone(stockholm_tz) + if sthlm_start_time.hour < 8 or sthlm_start_time.hour >= 17: booking_confirmed = False # Unconfirm booking on weekends - if data.start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday + if sthlm_start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday booking_confirmed = False if data.end_time is not None: - if data.end_time.hour < 8 or data.end_time.hour >= 17: + sthlm_end_time = data.end_time.astimezone(stockholm_tz) + if sthlm_end_time.hour < 8 or sthlm_end_time.hour >= 17: booking_confirmed = False - if data.end_time.weekday() >= 5: + if sthlm_end_time.weekday() >= 5: booking_confirmed = False # Remove council_id if personal booking diff --git a/tests/test_car_bookings.py b/tests/test_car_bookings.py index 17da533b..4b41932b 100644 --- a/tests/test_car_bookings.py +++ b/tests/test_car_bookings.py @@ -30,10 +30,10 @@ def patch_booking(client, token, booking_id, **kwargs): return client.patch(f"/car/{booking_id}", json=kwargs, headers=auth_headers(token)) -def test_admin_autoconfirm(client, admin_token, admin_council_id): +def test_admin_autoconfirm_council(client, admin_token, admin_council_id): start = stockholm_dt(2030, 1, 8, 10) # Tuesday end = stockholm_dt(2030, 1, 8, 12) - resp = create_booking(client, admin_token, start, end, "admin booking", council_id=admin_council_id, personal=True) + resp = create_booking(client, admin_token, start, end, "admin booking", council_id=admin_council_id) assert resp.status_code in (200, 201) data = resp.json() assert "confirmed" in data @@ -59,7 +59,36 @@ def test_admin_autoconfirm(client, admin_token, admin_council_id): assert resp4.json()["confirmed"] is True -def test_user_autoconfirm(client, member_token, member_council_id): +def test_admin_autoconfirm_personal(client, admin_token, admin_council_id): + start = stockholm_dt(2030, 1, 8, 10) # Tuesday + end = stockholm_dt(2030, 1, 8, 12) + resp = create_booking(client, admin_token, start, end, "admin booking", personal=True) + assert resp.status_code in (200, 201) + data = resp.json() + assert "confirmed" in data + assert "booking_id" in data + assert data["confirmed"] is True + + start = stockholm_dt(2030, 1, 8, 6) # Before office hours + end = stockholm_dt(2030, 1, 8, 7) + resp2 = create_booking(client, admin_token, start, end, "early booking", personal=True) + assert resp2.status_code in (200, 201) + assert resp2.json()["confirmed"] is True + + start = stockholm_dt(2030, 1, 8, 18) # After office hours + end = stockholm_dt(2030, 1, 8, 19) + resp3 = create_booking(client, admin_token, start, end, "late booking", personal=True) + assert resp3.status_code in (200, 201) + assert resp3.json()["confirmed"] is True + + start = stockholm_dt(2030, 1, 12, 10) # Saturday + end = stockholm_dt(2030, 1, 12, 12) + resp4 = create_booking(client, admin_token, start, end, "weekend booking", personal=True) + assert resp4.status_code in (200, 201) + assert resp4.json()["confirmed"] is True + + +def test_user_autoconfirm_council(client, member_token, member_council_id): # Book inside hours start = stockholm_dt(2030, 1, 8, 10) end = stockholm_dt(2030, 1, 8, 12) @@ -67,28 +96,26 @@ def test_user_autoconfirm(client, member_token, member_council_id): assert resp.status_code in (200, 201) assert resp.json()["confirmed"] is True - -def test_user_autounconfirm_outside_hours(client, member_token, member_council_id): - # Before 08:00 - start = stockholm_dt(2030, 1, 8, 7) - end = stockholm_dt(2030, 1, 8, 9) - resp = create_booking(client, member_token, start, end, "early booking", council_id=member_council_id) + # Saturday + start = stockholm_dt(2030, 1, 12, 10) + end = stockholm_dt(2030, 1, 12, 12) + resp = create_booking(client, member_token, start, end, "personal booking", council_id=member_council_id) assert resp.status_code in (200, 201) - assert resp.json()["confirmed"] is False + assert resp.json()["confirmed"] is True - # After 17:00 - start = stockholm_dt(2030, 1, 8, 18) - end = stockholm_dt(2030, 1, 8, 19) - resp = create_booking(client, member_token, start, end, "late booking", council_id=member_council_id) + +def test_user_autounconfirm_personal(client, member_token, member_council_id): + # Book inside hours + start = stockholm_dt(2030, 1, 8, 10) + end = stockholm_dt(2030, 1, 8, 12) + resp = create_booking(client, member_token, start, end, "user booking", personal=True) assert resp.status_code in (200, 201) assert resp.json()["confirmed"] is False - -def test_user_autounconfirm_weekend(client, member_token, member_council_id): # Saturday start = stockholm_dt(2030, 1, 12, 10) end = stockholm_dt(2030, 1, 12, 12) - resp = create_booking(client, member_token, start, end, "weekend booking", council_id=member_council_id) + resp = create_booking(client, member_token, start, end, "personal booking", personal=True) assert resp.status_code in (200, 201) assert resp.json()["confirmed"] is False @@ -112,17 +139,74 @@ def test_admin_can_confirm_unconfirm(client, admin_token, admin_council_id): assert resp3.json()["confirmed"] is True -def test_user_edit_autounconfirm(client, member_token, member_council_id): +def test_user_edit_autounconfirm_council(client, member_token, member_council_id): # User books inside hours start = stockholm_dt(2030, 1, 10, 10) end = stockholm_dt(2030, 1, 10, 12) resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id) booking_id = resp.json()["booking_id"] + assert resp.json()["confirmed"] is True + + # Try to edit to outside hours, should stay confirmed + new_end = stockholm_dt(2030, 1, 10, 18) + resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat()) + assert resp3.status_code == 200 + assert resp3.json()["confirmed"] is True + + +def test_user_edit_autounconfirm_personal(client, member_token, member_council_id): + # User books inside hours + start = stockholm_dt(2030, 1, 10, 10) + end = stockholm_dt(2030, 1, 10, 12) + resp = create_booking(client, member_token, start, end, "user booking", personal=True) + booking_id = resp.json()["booking_id"] + assert resp.json()["confirmed"] is False + + # Admin confirms + resp2 = patch_booking(client, member_token, booking_id, confirmed=True) + assert resp2.json()["confirmed"] is True + # Try to edit to outside hours new_end = stockholm_dt(2030, 1, 10, 18) - resp2 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat()) - assert resp2.status_code == 200 - assert resp2.json()["confirmed"] is False + resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat()) + assert resp3.status_code == 200 + assert resp3.json()["confirmed"] is False + + +def test_user_edit_keep_confirmed_in_hours(client, member_token, member_council_id): + # User books inside hours + start = stockholm_dt(2030, 1, 10, 10) + end = stockholm_dt(2030, 1, 10, 12) + resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id, personal=True) + booking_id = resp.json()["booking_id"] + + assert resp.json()["confirmed"] is False + + # Admin confirms + resp2 = patch_booking(client, member_token, booking_id, confirmed=True) + assert resp2.json()["confirmed"] is True + + # Try to edit, keep inside hours + new_end = stockholm_dt(2030, 1, 10, 16) + resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat()) + assert resp3.status_code == 200 + assert resp3.json()["confirmed"] is True + + +def test_user_edit_keep_unconfirmed_in_hours(client, member_token, member_council_id): + # User books inside hours + start = stockholm_dt(2030, 1, 10, 10) + end = stockholm_dt(2030, 1, 10, 12) + resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id, personal=True) + booking_id = resp.json()["booking_id"] + + assert resp.json()["confirmed"] is False + + # Try to edit, keep inside hours, should remain unconfirmed + new_end = stockholm_dt(2030, 1, 10, 16) + resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat()) + assert resp3.status_code == 200 + assert resp3.json()["confirmed"] is False def test_overlapping_bookings_not_allowed(client, member_token, member_council_id): @@ -402,4 +486,3 @@ def test_admin_delete_any_booking(client, admin_token, member_token, member_coun # Admin should be able to delete the member's booking resp2 = client.delete(f"/car/{booking_id}", headers=auth_headers(admin_token)) assert resp2.status_code in (200, 204) -