From e3450addf54c836e68611011a7472f8beb77a8de Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 11:41:20 +0100 Subject: [PATCH 01/10] unconfirm personal bookings --- services/car_renting_service.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index c14ae48a..a993e49f 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -107,15 +107,19 @@ 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 + # 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: From 73e4ed166e9d5f1ac4e4ad301af1107b08856b16 Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 11:43:14 +0100 Subject: [PATCH 02/10] dont unconfirm council bookings on update --- services/car_renting_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index a993e49f..5b4c78f1 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -200,7 +200,7 @@ def booking_update( if booking_overlaps: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking overlaps with another booking.") - if not manage_permission: + if not manage_permission and data.personal: # 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: From e0ab2d4969b6359028e6a731cb281f2d53350d7a Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 11:48:28 +0100 Subject: [PATCH 03/10] fix previous commit to also work when booking.personal is None --- services/car_renting_service.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index 5b4c78f1..1e84ca0e 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -181,6 +181,7 @@ def booking_update( booking_confirmed = True # only check for illegal overlap if new times are provided + # also unconfirms bookings that now fall outside of school hours if data.start_time is not None or data.end_time is not None: # Use new values if provided, otherwise use existing values check_start_time = data.start_time if data.start_time is not None else car_booking.start_time @@ -200,7 +201,7 @@ def booking_update( if booking_overlaps: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking overlaps with another booking.") - if not manage_permission and data.personal: + if not manage_permission and (data.personal or data.personal is None and car_booking.personal): # 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: @@ -226,7 +227,7 @@ def booking_update( # Disallow regular users from booking cars for other councils if not manage_permission and data.council_id is not None: - if data.council_id not in [post.council_id for post in current_user.posts]: + if data.council_id not in [post.council_id for post in current_user.posts]:S raise HTTPException( status.HTTP_403_FORBIDDEN, detail="You do not have permission to book cars for this council." ) From 6e64f310aa7a4485a4b82cbaa4d263a41e05cc47 Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 11:48:53 +0100 Subject: [PATCH 04/10] only unconfirm bookins on update if times have changed --- services/car_renting_service.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index 1e84ca0e..1e96e3cd 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -201,19 +201,19 @@ def booking_update( if booking_overlaps: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking overlaps with another booking.") - if not manage_permission and (data.personal or data.personal is None and car_booking.personal): - # 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: - booking_confirmed = False - # Unconfirm booking on weekends - if data.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: - booking_confirmed = False - if data.end_time.weekday() >= 5: - booking_confirmed = False + if not manage_permission and (data.personal or data.personal is None and car_booking.personal): + # 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: + booking_confirmed = False + # Unconfirm booking on weekends + if data.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: + booking_confirmed = False + if data.end_time.weekday() >= 5: + booking_confirmed = False # Remove council_id if personal booking if data.personal and data.council_id is not None: From aecc74d2f4193b3be241af1ebe72676dd4fe7e27 Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 11:53:30 +0100 Subject: [PATCH 05/10] Dont automatically confirm bookings on update --- services/car_renting_service.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index 1e96e3cd..228ebbf5 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -176,10 +176,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 # also unconfirms bookings that now fall outside of school hours if data.start_time is not None or data.end_time is not None: From 3729fd64d639f07507c42c63cb9d2858da34c51f Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:12:49 +0100 Subject: [PATCH 06/10] fix time zones when unconfirming bookings outside of school hours --- services/car_renting_service.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index 228ebbf5..9061a765 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -107,16 +107,6 @@ 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 @@ -198,17 +188,22 @@ def booking_update( raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking overlaps with another booking.") 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 From 261220ef2c51ee6e6865b4758de3a21eb9c2eab3 Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:19:26 +0100 Subject: [PATCH 07/10] refactor: style and fixed confusion created by 6e64f31 --- services/car_renting_service.py | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index 9061a765..b70c787a 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -167,7 +167,6 @@ def booking_update( booking_confirmed = car_booking.confirmed # only check for illegal overlap if new times are provided - # also unconfirms bookings that now fall outside of school hours if data.start_time is not None or data.end_time is not None: # Use new values if provided, otherwise use existing values check_start_time = data.start_time if data.start_time is not None else car_booking.start_time @@ -187,24 +186,25 @@ def booking_update( if booking_overlaps: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking overlaps with another booking.") - 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: - 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 sthlm_start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday - booking_confirmed = False - if data.end_time is not None: - 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 sthlm_end_time.weekday() >= 5: - booking_confirmed = False + # 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: + 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 sthlm_start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday + booking_confirmed = False + if data.end_time is not None: + 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 sthlm_end_time.weekday() >= 5: + booking_confirmed = False # Remove council_id if personal booking if data.personal and data.council_id is not None: From 83896658a8c364ee0ea104c85a2e0df69eb4a637 Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:21:49 +0100 Subject: [PATCH 08/10] fixed syntax error :S --- services/car_renting_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index b70c787a..79086e35 100644 --- a/services/car_renting_service.py +++ b/services/car_renting_service.py @@ -218,7 +218,7 @@ def booking_update( # Disallow regular users from booking cars for other councils if not manage_permission and data.council_id is not None: - if data.council_id not in [post.council_id for post in current_user.posts]:S + if data.council_id not in [post.council_id for post in current_user.posts]: raise HTTPException( status.HTTP_403_FORBIDDEN, detail="You do not have permission to book cars for this council." ) From 7879d8f015e0237f4f8277d9bd2b390395f842fa Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Sun, 21 Dec 2025 19:39:40 +0100 Subject: [PATCH 09/10] add zoneinfo import --- services/car_renting_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/car_renting_service.py b/services/car_renting_service.py index 79086e35..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 From 504e27f52a2d257ad70903a40b0a4fc81433054c Mon Sep 17 00:00:00 2001 From: Armadillan <74256936+Armadillan@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:59:25 +0100 Subject: [PATCH 10/10] changed and added tests per new spec --- tests/test_car_bookings.py | 127 ++++++++++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 22 deletions(-) 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) -