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

refactored event serializer: update to free event only if there are n… #722

Merged
merged 34 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
219c4ad
refactored event serializer: update to free event only if there are n…
MadsNyl Oct 16, 2023
bbe2325
first version finished
MadsNyl Oct 17, 2023
b2d63b1
format
MadsNyl Oct 17, 2023
e434f18
fixed task bug
MadsNyl Oct 18, 2023
b704969
Merge branch 'dev' into refactor(payment)/order-creation
MadsNyl Oct 18, 2023
44d13b5
removed payment tests
MadsNyl Oct 18, 2023
15473e9
Merge branch 'refactor(payment)/order-creation' of https://github.com…
MadsNyl Oct 18, 2023
1be4eba
format
MadsNyl Oct 18, 2023
c320dca
Trigger Build
MadsNyl Oct 18, 2023
d791fcb
removed order test that uses vipps api
MadsNyl Oct 18, 2023
5183a6f
fixed celery task to delete registration if there are no orders
MadsNyl Oct 18, 2023
adc8c21
when refunded, order status is updated to CANCEl in db
MadsNyl Oct 20, 2023
209eb99
added exceptions for refund
MadsNyl Oct 20, 2023
f3bcfa8
format
MadsNyl Oct 20, 2023
ff40b55
fixed bug with celery task when having several orders
MadsNyl Oct 20, 2023
77a005d
added search and filter in admin panel
MadsNyl Oct 23, 2023
9990ce2
added 10 minutes to countdown task, so an user always will have the c…
MadsNyl Oct 24, 2023
a779845
refactored check payment function
MadsNyl Oct 27, 2023
4855f85
altered order payment check
MadsNyl Oct 28, 2023
befe572
Merge branch 'dev' into refactor(payment)/order-creation
MadsNyl Dec 17, 2023
f2347c8
made test for vipps callback and checking if order status is successfull
MadsNyl Dec 17, 2023
1928726
added check for status when fetching a payment order for a registrati…
MadsNyl Dec 22, 2023
da2f96c
made tests for updating and creating paid events
MadsNyl Dec 23, 2023
c040622
Refactor event integration tests to use boolean truthiness
MadsNyl Dec 23, 2023
e907823
Refactor payment serializers and views
MadsNyl Dec 29, 2023
a67405d
format
MadsNyl Dec 29, 2023
1d6f408
format
MadsNyl Jan 2, 2024
eb560db
Merge branch 'dev' into refactor(payment)/order-creation
MadsNyl Jan 2, 2024
1652cc8
format
MadsNyl Jan 2, 2024
e3833cd
Merge branch 'refactor(payment)/order-creation' of https://github.com…
MadsNyl Jan 2, 2024
fc6dc82
fixed 500 error response and added vipps msn to env in docker
MadsNyl Jan 2, 2024
3ffd6a8
Add payment expiration check and handling
MadsNyl Jan 3, 2024
b5c8802
fixed bug with expired payment time
MadsNyl Jan 9, 2024
8935722
Merge branch 'dev' into refactor(payment)/order-creation
MadsNyl Jan 9, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
run: |
touch .env
echo "AZURE_STORAGE_CONNECTION_STRING=${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" >> .env
echo "VIPPS_MERCHANT_SERIAL_NUMBER=${{ secrets.VIPPS_MERCHANT_SERIAL_NUMBER }}" >> .env

- name: Build the Stack
run: docker-compose build
Expand Down
11 changes: 10 additions & 1 deletion app/content/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

class APIPaidEventCantBeChangedToFreeEventException(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "Arrangementet er et betalt arrangement, og kan ikke endres til et gratis arrangement"
default_detail = "Arrangementet er et betalt arrangement med påmeldte deltagere, og kan ikke endres til et gratis arrangement"


class APIEventCantBeChangedToPaidEventException(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "Arrangementet er et gratis arrangement med påmeldte deltagere, og kan ikke endres til et betalt arrangement"


class APIUserAlreadyAttendedEvent(APIException):
Expand Down Expand Up @@ -48,3 +53,7 @@ class UnansweredFormError(ValueError):

class EventIsFullError(ValueError):
pass


class RefundFailedError(ValueError):
pass
18 changes: 18 additions & 0 deletions app/content/migrations/0054_registration_payment_expiredate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2023-10-18 08:55

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("content", "0053_event_contact_person"),
]

operations = [
migrations.AddField(
model_name="registration",
name="payment_expiredate",
field=models.DateTimeField(default=None, null=True),
),
]
13 changes: 13 additions & 0 deletions app/content/migrations/0058_merge_20231217_2155.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Generated by Django 4.2.5 on 2023-12-17 20:55

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("content", "0054_registration_payment_expiredate"),
("content", "0057_event_emojis_allowed_news_emojis_allowed"),
]

operations = []
5 changes: 5 additions & 0 deletions app/content/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ def list_count(self):
"""Number of users registered to attend the event"""
return self.get_participants().count()

@property
def has_participants(self):
"""Returns if the event has users registered to attend the event"""
return self.list_count > 0

@property
def waiting_list_count(self):
"""Number of users on the waiting list"""
Expand Down
62 changes: 62 additions & 0 deletions app/content/models/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from django.db import models
from django.db.models import Q

from sentry_sdk import capture_exception

from app.common.enums import StrikeEnum
from app.common.permissions import BasePermissionModel
from app.communication.enums import UserNotificationSettingType
Expand All @@ -17,7 +19,9 @@
from app.content.models.event import Event
from app.content.models.strike import create_strike
from app.content.models.user import User
from app.content.util.registration_utils import get_payment_expiredate
from app.forms.enums import EventFormType
from app.payment.util.order_utils import check_if_order_is_paid, has_paid_order
from app.util import now
from app.util.models import BaseModel
from app.util.utils import datetime_format
Expand All @@ -36,6 +40,7 @@ class Registration(BaseModel, BasePermissionModel):
is_on_wait = models.BooleanField(default=False, verbose_name="waiting list")
has_attended = models.BooleanField(default=False)
allow_photo = models.BooleanField(default=True)
payment_expiredate = models.DateTimeField(null=True, default=None)
created_by_admin = models.BooleanField(default=False)

class Meta:
Expand Down Expand Up @@ -86,7 +91,27 @@ def delete_submission_if_exists(self):
)[:1]
Submission.objects.filter(form=event_form, user=self.user).delete()

def refund_payment_if_exist(self):
from app.content.util.event_utils import refund_vipps_order

if not self.event.is_paid_event:
return

orders = self.event.orders.filter(user=self.user)

if has_paid_order(orders):
for order in orders:
if check_if_order_is_paid(order):
refund_vipps_order(
order_id=order.order_id,
event=self.event,
transaction_text=f"Refund for {self.event.title} - {self.user.first_name} {self.user.last_name}",
)
self.send_notification_and_mail_for_refund(order)

def delete(self, *args, **kwargs):
from app.content.util.event_utils import start_payment_countdown

moved_registration = None
if not self.is_on_wait:
if self.event.is_past_sign_off_deadline:
Expand All @@ -99,9 +124,26 @@ def delete(self, *args, **kwargs):
moved_registration = self.move_from_waiting_list_to_queue()

self.delete_submission_if_exists()

# TODO: Add this for refund
# self.refund_payment_if_exist()

registration = super().delete(*args, **kwargs)
if moved_registration:
moved_registration.save()

if (
moved_registration.event.is_paid_event
and not moved_registration.is_on_wait
):
try:
start_payment_countdown(
moved_registration.event, moved_registration
)
except Exception as countdown_error:
capture_exception(countdown_error)
moved_registration.delete()

return registration

def admin_unregister(self, *args, **kwargs):
Expand All @@ -115,6 +157,7 @@ def admin_unregister(self, *args, **kwargs):
moved_registration.save()

def save(self, *args, **kwargs):

if not self.registration_id:
self.create()

Expand Down Expand Up @@ -208,6 +251,19 @@ def send_notification_and_mail(self):
self.event.pk
).send()

def send_notification_and_mail_for_refund(self, order):
Notify(
[self.user],
f'Du har blitt meldt av "{self.event.title}" og vil bli refundert',
UserNotificationSettingType.UNREGISTRATION,
).add_paragraph(f"Hei, {self.user.first_name}!").add_paragraph(
f"Du har blitt meldt av {self.event.title} og vil bli refundert."
).add_paragraph(
"Du vil få pengene tilbake på kontoen din innen 2 til 3 virkedager. I enkelte tilfeller, avhengig av bank, tar det inntil 10 virkedager."
).add_paragraph(
f"Hvis det skulle oppstå noen problemer så kontakt oss på hs@tihlde.org. Ditt ordrenummer er {order.order_id}."
).send()

def should_swap_with_non_prioritized_user(self):
return (
self.is_on_wait
Expand Down Expand Up @@ -279,6 +335,12 @@ def move_from_waiting_list_to_queue(self):
registrations_in_waiting_list[0],
)
registration_move_to_queue.is_on_wait = False

if self.event.is_paid_event:
registration_move_to_queue.payment_expiredate = get_payment_expiredate(
self.event
)

return registration_move_to_queue

def move_from_queue_to_waiting_list(self):
Expand Down
52 changes: 38 additions & 14 deletions app/content/serializers/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

from app.common.enums import GroupType
from app.common.serializers import BaseModelSerializer
from app.content.exceptions import APIPaidEventCantBeChangedToFreeEventException
from app.content.exceptions import (
APIEventCantBeChangedToPaidEventException,
APIPaidEventCantBeChangedToFreeEventException,
)
from app.content.models import Event, PriorityPool
from app.content.serializers.priority_pool import (
PriorityPoolCreateSerializer,
Expand Down Expand Up @@ -176,36 +179,57 @@ def update(self, instance, validated_data):
priority_pools_data = validated_data.pop("priority_pools", None)
paid_information_data = validated_data.pop("paid_information", None)
limit = validated_data.get("limit")
limit_difference = 0
if limit:
limit_difference = limit - instance.limit
instance_limit = instance.limit

event = super().update(instance, validated_data)

self.update_queue(event, limit, instance_limit)

self.update_from_free_to_paid(event, paid_information_data)

self.update_from_paid_to_free(event, paid_information_data)

if len(paid_information_data):
self.update_paid_information(event, paid_information_data)

if priority_pools_data:
self.update_priority_pools(event, priority_pools_data)

event.save()
return event

def update_queue(self, event, limit, instance_limit):
if not limit:
return

limit_difference = limit - instance_limit

if limit_difference > 0 and event.waiting_list_count > 0:
event.move_users_from_waiting_list_to_queue(limit_difference)

if limit_difference < 0:
event.move_users_from_queue_to_waiting_list(abs(limit_difference))

def update_from_paid_to_free(self, event, paid_information_data):
if paid_information_data and not event.is_paid_event:
if event.has_participants:
raise APIEventCantBeChangedToPaidEventException()

PaidEvent.objects.create(
event=event,
price=paid_information_data["price"],
paytime=paid_information_data["paytime"],
)

if event.is_paid_event and not len(paid_information_data):
raise APIPaidEventCantBeChangedToFreeEventException()

if len(paid_information_data):
self.update_paid_information(event, paid_information_data)

if priority_pools_data:
self.update_priority_pools(event, priority_pools_data)
def update_from_free_to_paid(self, event, paid_information_data):
if event.is_paid_event:
if not len(paid_information_data) and event.has_participants:
raise APIPaidEventCantBeChangedToFreeEventException()

event.save()
return event
paid_event = PaidEvent.objects.filter(event=event)
if paid_event:
paid_event.first().delete()
event.paid_information = None

def update_priority_pools(self, event, priority_pools_data):
event.priority_pools.all().delete()
Expand Down
36 changes: 20 additions & 16 deletions app/content/serializers/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
DefaultUserSerializer,
UserListSerializer,
)
from app.content.util.registration_utils import get_payment_expiredate
from app.forms.enums import EventFormType
from app.forms.serializers.submission import SubmissionInRegistrationSerializer
from app.payment.enums import OrderStatus
from app.payment.serializers.order import OrderSerializer
from app.payment.util.order_utils import has_paid_order
from app.payment.util.payment_utils import get_payment_order_status


class RegistrationSerializer(BaseModelSerializer):
user_info = UserListSerializer(source="user", read_only=True)
survey_submission = serializers.SerializerMethodField()
has_unanswered_evaluation = serializers.SerializerMethodField()
order = serializers.SerializerMethodField(required=False)
has_paid_order = serializers.SerializerMethodField(required=False)
wait_queue_number = serializers.SerializerMethodField(required=False)

Expand All @@ -31,7 +32,7 @@ class Meta:
"created_at",
"survey_submission",
"has_unanswered_evaluation",
"order",
"payment_expiredate",
"has_paid_order",
"wait_queue_number",
"created_by_admin",
Expand All @@ -44,20 +45,23 @@ def get_survey_submission(self, obj):
def get_has_unanswered_evaluation(self, obj):
return obj.user.has_unanswered_evaluations_for(obj.event)

def get_order(self, obj):
order = obj.event.orders.filter(user=obj.user).first()
if order:
return OrderSerializer(order).data
return None

def get_has_paid_order(self, obj):
for order in obj.event.orders.filter(user=obj.user):
if (
order.status == OrderStatus.CAPTURE
or order.status == OrderStatus.RESERVE
or order.status == OrderStatus.SALE
):
return True
orders = obj.event.orders.filter(user=obj.user)

if orders and (order := orders.first()).status == OrderStatus.INITIATE:
order_status = get_payment_order_status(order.order_id)
order.status = order_status
order.save()

return has_paid_order(orders)

def create(self, validated_data):
event = validated_data["event"]

if event.is_paid_event and not event.is_full:
validated_data["payment_expiredate"] = get_payment_expiredate(event)

return super().create(validated_data)

def get_wait_queue_number(self, obj):
if obj.is_on_wait:
Expand Down
39 changes: 39 additions & 0 deletions app/content/tests/test_event_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from datetime import timedelta

import pytest

from app.content.factories import EventFactory, RegistrationFactory
from app.content.util.event_utils import get_countdown_time
from app.payment.factories import PaidEventFactory


@pytest.fixture()
def paid_event():
return PaidEventFactory()


@pytest.fixture()
def event():
return EventFactory()


@pytest.fixture()
def registration(paid_event):
return RegistrationFactory(event=paid_event)


@pytest.mark.django_db
def test_that_paytime_countdown_adds_ten_minutes(paid_event):
"""
Should return the countdown time of the event + 10 minutes.
"""

paytime = paid_event.paytime
paytime_in_seconds = (paytime.hour * 60 + paytime.minute) * 60 + paytime.second

countdown_time = get_countdown_time(paid_event.event)

ten_minutes = timedelta(minutes=10)
ten_minutes_in_seconds = ten_minutes.seconds

assert countdown_time - paytime_in_seconds == ten_minutes_in_seconds
Loading
Loading