Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add application section recurring reservation mass cancel endpoint #1430

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,253 @@
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__cancel_details_not_required(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,
}

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=ReservationStateChoice.REQUIRES_HANDLING,
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
Loading