Skip to content

Commit b3bd570

Browse files
Merge pull request #15 from mohamad-liyaghi/transactions-logic
Transactions logic
2 parents fe7e13c + c24e725 commit b3bd570

File tree

8 files changed

+69
-23
lines changed

8 files changed

+69
-23
lines changed

apps/orders/models.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from django.conf import settings
55
from django.db.models import QuerySet
66
from uuid import uuid4
7-
from orders.exceptions import EmptyCartException, InsufficientBalanceException
7+
from orders.exceptions import EmptyCartException
88
from orders.enums import OrderStatus
9+
from transactions.models import Transaction
910
from restaurants.models import Restaurant
1011
from products.models import Product
1112

@@ -25,10 +26,11 @@ def __str__(self):
2526

2627
def save(self, *args, **kwargs):
2728
if self.status == OrderStatus.PROCESSING and not self.is_removed_from_balance:
28-
if self.user.balance < self.total_price:
29-
raise InsufficientBalanceException
30-
self.user.balance -= self.total_price
31-
self.user.save()
29+
Transaction.transfer(
30+
sender=self.user,
31+
receiver=self.restaurant.owner,
32+
amount=self.total_price,
33+
)
3234
return super().save(*args, **kwargs)
3335

3436
@classmethod
@@ -64,6 +66,11 @@ def create_order(cls, user: settings.AUTH_USER_MODEL, cart: dict) -> QuerySet:
6466

6567
with transaction.atomic():
6668
OrderItem.objects.bulk_create(order_items)
69+
70+
for item in order_items:
71+
item.product.quantity -= item.quantity
72+
item.product.save()
73+
6774
for order in orders.values():
6875
order.save()
6976

apps/orders/views/customer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from rest_framework import status
77
from drf_spectacular.utils import extend_schema_view, OpenApiResponse, extend_schema
88
from orders.enums import OrderStatus
9-
from orders.exceptions import InsufficientBalanceException
9+
from transactions.exceptions import InsufficientBalanceError
1010
from orders.models import Order
1111
from orders.serializers import OrderSerializer
1212

@@ -80,7 +80,7 @@ def post(self, request, *args, **kwargs):
8080
order.save()
8181
return Response({"message": "Order is being processed"}, status=status.HTTP_200_OK)
8282

83-
except InsufficientBalanceException:
83+
except InsufficientBalanceError:
8484
return Response({"error": "Insufficient balance"}, status=status.HTTP_400_BAD_REQUEST)
8585

8686

apps/products/views.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def get_queryset(self):
3939
restaurant = self._get_restaurant()
4040
return Product.objects.select_related("restaurant", "restaurant__owner").filter(
4141
restaurant=restaurant, is_deleted=False
42-
) # TODO: make this single query
42+
)
4343

4444
def _get_restaurant(self, for_creation=False):
4545
if not for_creation:

apps/transactions/exceptions.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class InsufficientBalanceError(Exception):
2+
pass

apps/transactions/models.py

+42-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from decimal import Decimal
44
from uuid import uuid4
55
from transactions.enums import TransactionType, TransactionStatus
6+
from transactions.exceptions import InsufficientBalanceError
67
from transactions.tasks import do_withdraw
78

89

@@ -21,18 +22,46 @@ def __str__(self):
2122

2223
def save(self, *args, **kwargs):
2324
if self.status == TransactionStatus.SUCCESS and not self.is_processed:
24-
if self.type == TransactionType.DEPOSIT:
25-
self._handle_deposit()
26-
elif self.type == TransactionType.WITHDRAWAL:
27-
self._handle_withdrawal()
28-
self.is_processed = True
29-
return super().save(*args, **kwargs)
30-
31-
def _handle_deposit(self):
32-
self.user.balance += Decimal(self.amount)
33-
self.user.save()
25+
self._process_transaction()
26+
super().save(*args, **kwargs)
27+
28+
def _process_transaction(self):
29+
amount = Decimal(self.amount)
30+
31+
if self.type == TransactionType.DEPOSIT:
32+
self._adjust_balance(amount)
33+
elif self.type in {TransactionType.WITHDRAWAL, TransactionType.COST}:
34+
self._adjust_balance(-amount)
35+
elif self.type == TransactionType.CHARGE:
36+
self._adjust_balance(amount)
37+
38+
self.is_processed = True
3439

35-
def _handle_withdrawal(self):
36-
self.user.balance -= Decimal(self.amount)
40+
if self.type == TransactionType.WITHDRAWAL:
41+
do_withdraw.delay(self.user.id, self.id)
42+
43+
def _adjust_balance(self, amount):
44+
if amount < 0 and self.user.balance < abs(amount):
45+
raise InsufficientBalanceError
46+
self.user.balance += amount
3747
self.user.save()
38-
do_withdraw.delay(self.user.id, self.id)
48+
49+
@classmethod
50+
def transfer(cls, sender, receiver, amount) -> tuple:
51+
"""Transfer money from sender to receiver."""
52+
if sender.balance < amount:
53+
raise InsufficientBalanceError
54+
55+
sender_transaction = cls.objects.create(
56+
user=sender,
57+
amount=amount,
58+
type=TransactionType.COST,
59+
status=TransactionStatus.SUCCESS,
60+
)
61+
receiver_transaction = cls.objects.create(
62+
user=receiver,
63+
amount=amount,
64+
type=TransactionType.CHARGE,
65+
status=TransactionStatus.SUCCESS,
66+
)
67+
return sender_transaction, receiver_transaction

apps/transactions/tests/test_models.py

+10
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,13 @@ def test_user_balance_decreased_after_successful_withdrawal(self, user):
2525
status=TransactionStatus.SUCCESS,
2626
)
2727
assert user.balance == user_balance - Decimal(successful_withdrawal.amount)
28+
29+
def test_transfer_money_between_users(self, user, another_user):
30+
user_balance = user.balance
31+
another_user_balance = another_user.balance
32+
amount = Decimal(100.00)
33+
Transaction.transfer(user, another_user, amount)
34+
user.refresh_from_db()
35+
another_user.refresh_from_db()
36+
assert user.balance == user_balance - amount
37+
assert another_user.balance == another_user_balance + amount

apps/users/tests/test_views/test_profile/test_retrieve.py

-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,3 @@ def test_retrieve_authorized_succeeds(self, user):
2222
assert response.data["email"] == user.email
2323
assert response.data["first_name"] == user.first_name
2424
assert response.data["last_name"] == user.last_name
25-
assert Decimal(response.data["balance"]) == Decimal(user.balance)

docker/Dockerfile

-1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,3 @@ COPY . /src/
2525
WORKDIR /src
2626

2727
EXPOSE 8000
28-
# TODO: This Dockerfile is shit, clean it up

0 commit comments

Comments
 (0)