Skip to content

Commit

Permalink
Add application section recurring reservation mass cancel endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
matti-lamppu committed Nov 14, 2024
1 parent 8f4d410 commit 8cee54c
Show file tree
Hide file tree
Showing 9 changed files with 460 additions and 4 deletions.
11 changes: 10 additions & 1 deletion tests/test_graphql_api/test_recurring_reservation/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@
CREATE_SERIES_MUTATION = build_mutation("createReservationSeries", "ReservationSeriesCreateMutation")
UPDATE_SERIES_MUTATION = build_mutation("updateReservationSeries", "ReservationSeriesUpdateMutation")
RESCHEDULE_SERIES_MUTATION = build_mutation("rescheduleReservationSeries", "ReservationSeriesRescheduleMutation")
DENY_SERIES_MUTATION = build_mutation("denyReservationSeries", "ReservationSeriesDenyMutation", fields="denied future")
DENY_SERIES_MUTATION = build_mutation(
"denyReservationSeries",
"ReservationSeriesDenyMutation",
fields="future denied",
)
CANCEL_SECTION_SERIES_MUTATION = build_mutation(
"cancelAllApplicationSectionReservations",
"ApplicationSectionReservationCancellationMutation",
fields="future cancelled",
)


def get_minimal_series_data(reservation_unit: ReservationUnit, user: User, **overrides: Any) -> dict[str, Any]:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import datetime

import pytest
from freezegun import freeze_time

from tests.factories import (
AllocatedTimeSlotFactory,
ApplicationRoundFactory,
ReservationCancelReasonFactory,
UserFactory,
)
from tilavarauspalvelu.enums import ReservationStateChoice, ReservationTypeChoice
from utils.date_utils import local_date, local_datetime

from .helpers import CANCEL_SECTION_SERIES_MUTATION, create_reservation_series

# Applied to all tests
pytestmark = [
pytest.mark.django_db,
]


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__cancel_whole_remaining(graphql):
reason = ReservationCancelReasonFactory.create()
user = UserFactory.create()

reservation_series = create_reservation_series(
user=user,
reservations__type=ReservationTypeChoice.SEASONAL,
reservations__price=0,
reservation_unit__cancellation_rule__can_be_cancelled_time_before=datetime.timedelta(),
)

application_round = ApplicationRoundFactory.create_in_status_results_sent()
allocation = AllocatedTimeSlotFactory.create(
reservation_unit_option__application_section__application__user=user,
reservation_unit_option__application_section__application__application_round=application_round,
)
section = allocation.reservation_unit_option.application_section

reservation_series.allocated_time_slot = allocation
reservation_series.save()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.force_login(user)
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.has_errors is False, response.errors

assert response.first_query_object == {"cancelled": 5, "future": 5}
assert reservation_series.reservations.count() == 9


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__not_seasonal_type(graphql):
reason = ReservationCancelReasonFactory.create()
user = UserFactory.create()

reservation_series = create_reservation_series(
user=user,
reservations__type=ReservationTypeChoice.NORMAL,
reservations__price=0,
reservation_unit__cancellation_rule__can_be_cancelled_time_before=datetime.timedelta(),
)

application_round = ApplicationRoundFactory.create_in_status_results_sent()
allocation = AllocatedTimeSlotFactory.create(
reservation_unit_option__application_section__application__user=user,
reservation_unit_option__application_section__application__application_round=application_round,
)
section = allocation.reservation_unit_option.application_section

reservation_series.allocated_time_slot = allocation
reservation_series.save()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.force_login(user)
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.has_errors is False, response.errors

assert response.first_query_object == {"cancelled": 0, "future": 5}
assert reservation_series.reservations.count() == 9


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__paid(graphql):
reason = ReservationCancelReasonFactory.create()
user = UserFactory.create()

reservation_series = create_reservation_series(
user=user,
reservations__type=ReservationTypeChoice.SEASONAL,
reservations__price=10,
reservation_unit__cancellation_rule__can_be_cancelled_time_before=datetime.timedelta(),
)

application_round = ApplicationRoundFactory.create_in_status_results_sent()
allocation = AllocatedTimeSlotFactory.create(
reservation_unit_option__application_section__application__user=user,
reservation_unit_option__application_section__application__application_round=application_round,
)
section = allocation.reservation_unit_option.application_section

reservation_series.allocated_time_slot = allocation
reservation_series.save()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.force_login(user)
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.has_errors is False, response.errors

assert response.first_query_object == {"cancelled": 0, "future": 5}
assert reservation_series.reservations.count() == 9


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__not_confirmed_state(graphql):
reason = ReservationCancelReasonFactory.create()
user = UserFactory.create()

reservation_series = create_reservation_series(
user=user,
reservations__type=ReservationTypeChoice.SEASONAL,
reservations__state=ReservationTypeChoice.BEHALF,
reservations__price=0,
reservation_unit__cancellation_rule__can_be_cancelled_time_before=datetime.timedelta(),
)

application_round = ApplicationRoundFactory.create_in_status_results_sent()
allocation = AllocatedTimeSlotFactory.create(
reservation_unit_option__application_section__application__user=user,
reservation_unit_option__application_section__application__application_round=application_round,
)
section = allocation.reservation_unit_option.application_section

reservation_series.allocated_time_slot = allocation
reservation_series.save()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.force_login(user)
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.has_errors is False, response.errors

assert response.first_query_object == {"cancelled": 0, "future": 5}
assert reservation_series.reservations.count() == 9


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__cancellation_rule(graphql):
reason = ReservationCancelReasonFactory.create()
user = UserFactory.create()

reservation_series = create_reservation_series(
user=user,
reservations__type=ReservationTypeChoice.SEASONAL,
reservations__price=0,
reservation_unit__cancellation_rule__can_be_cancelled_time_before=datetime.timedelta(days=1),
)

application_round = ApplicationRoundFactory.create_in_status_results_sent()
allocation = AllocatedTimeSlotFactory.create(
reservation_unit_option__application_section__application__user=user,
reservation_unit_option__application_section__application__application_round=application_round,
)
section = allocation.reservation_unit_option.application_section

reservation_series.allocated_time_slot = allocation
reservation_series.save()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.force_login(user)
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.has_errors is False, response.errors

# First future reservation is not cancelled since it's too soon according to the cancellation rule.
assert response.first_query_object == {"cancelled": 4, "future": 5}
assert reservation_series.reservations.count() == 9

future_reservations = reservation_series.reservations.filter(begin__date__gte=local_date()).iterator()

reservation_1 = next(future_reservations)
assert reservation_1.begin.date() == datetime.date(2024, 1, 1)
assert reservation_1.state == ReservationStateChoice.CONFIRMED

reservation_2 = next(future_reservations)
assert reservation_2.begin.date() == datetime.date(2024, 1, 8)
assert reservation_2.state == ReservationStateChoice.CANCELLED
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import datetime

import pytest
from freezegun import freeze_time

from tests.factories import (
AllocatedTimeSlotFactory,
ApplicationRoundFactory,
ReservationCancelReasonFactory,
UserFactory,
)
from tilavarauspalvelu.enums import ReservationTypeChoice, UserRoleChoice
from tilavarauspalvelu.models import ApplicationSection, ReservationCancelReason, User
from utils.date_utils import local_datetime

from .helpers import CANCEL_SECTION_SERIES_MUTATION, create_reservation_series

# Applied to all tests
pytestmark = [
pytest.mark.django_db,
]


def create_data_for_cancellation() -> tuple[ReservationCancelReason, ApplicationSection, User]:
reason = ReservationCancelReasonFactory.create()
user = UserFactory.create()

reservation_series = create_reservation_series(
user=user,
reservations__type=ReservationTypeChoice.SEASONAL,
reservations__price=0,
reservation_unit__cancellation_rule__can_be_cancelled_time_before=datetime.timedelta(),
)

application_round = ApplicationRoundFactory.create_in_status_results_sent()
allocation = AllocatedTimeSlotFactory.create(
reservation_unit_option__application_section__application__user=user,
reservation_unit_option__application_section__application__application_round=application_round,
)
section = allocation.reservation_unit_option.application_section

reservation_series.allocated_time_slot = allocation
reservation_series.save()
return reason, section, user


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__applicant(graphql):
reason, section, user = create_data_for_cancellation()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.force_login(user)
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.has_errors is False, response.errors


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__superuser(graphql):
reason, section, _ = create_data_for_cancellation()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.login_with_superuser()
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.error_message() == "No permission to update."


@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__general_admin(graphql):
reason, section, _ = create_data_for_cancellation()

data = {
"pk": section.pk,
"cancelReason": reason.pk,
"cancelDetails": "Cancellation details",
}

graphql.login_user_with_role(UserRoleChoice.ADMIN)
response = graphql(CANCEL_SECTION_SERIES_MUTATION, input_data=data)

assert response.error_message() == "No permission to update."
2 changes: 2 additions & 0 deletions tilavarauspalvelu/api/graphql/extensions/error_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,7 @@
APPLICATION_ROUND_NOT_IN_ALLOCATION = "APPLICATION_ROUND_NOT_IN_ALLOCATION"
APPLICATION_ROUND_NOT_HANDLED = "APPLICATION_ROUND_NOT_HANDLED"
APPLICATION_ROUND_HAS_UNHANDLED_APPLICATIONS = "APPLICATION_ROUND_HAS_UNHANDLED_APPLICATIONS"
APPLICATION_ROUND_NOT_IN_RESULTS_SENT_STATE = "APPLICATION_ROUND_NOT_IN_RESULTS_SENT_STATE"

CANCEL_REASON_DOES_NOT_EXIST = "CANCEL_REASON_DOES_NOT_EXIST"
DENY_REASON_DOES_NOT_EXIST = "DENY_REASON_DOES_NOT_EXIST"
2 changes: 2 additions & 0 deletions tilavarauspalvelu/api/graphql/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .types.application_section.mutations import (
ApplicationSectionCreateMutation,
ApplicationSectionDeleteMutation,
ApplicationSectionReservationCancellationMutation,
ApplicationSectionUpdateMutation,
RejectAllSectionOptionsMutation,
RestoreAllSectionOptionsMutation,
Expand Down Expand Up @@ -85,6 +86,7 @@
"ApplicationCreateMutation",
"ApplicationSectionCreateMutation",
"ApplicationSectionDeleteMutation",
"ApplicationSectionReservationCancellationMutation",
"ApplicationSectionUpdateMutation",
"ApplicationSendMutation",
"ApplicationUpdateMutation",
Expand Down
2 changes: 2 additions & 0 deletions tilavarauspalvelu/api/graphql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
ApplicationCreateMutation,
ApplicationSectionCreateMutation,
ApplicationSectionDeleteMutation,
ApplicationSectionReservationCancellationMutation,
ApplicationSectionUpdateMutation,
ApplicationSendMutation,
ApplicationUpdateMutation,
Expand Down Expand Up @@ -306,6 +307,7 @@ class Mutation(graphene.ObjectType):
restore_all_section_options = RestoreAllSectionOptionsMutation.Field()
reject_all_application_options = RejectAllApplicationOptionsMutation.Field()
restore_all_application_options = RestoreAllApplicationOptionsMutation.Field()
cancel_all_application_section_reservations = ApplicationSectionReservationCancellationMutation.Field()
set_application_round_handled = SetApplicationRoundHandledMutation.Field()
set_application_round_results_sent = SetApplicationRoundResultsSentMutation.Field()
#
Expand Down
Loading

0 comments on commit 8cee54c

Please sign in to comment.