Skip to content

Commit 4c45ca4

Browse files
Merge pull request #396 from MySecondLanguage/test-case-written-for-order-fullfillment
Test case written for order fullfillment
2 parents ae927fa + 66ac108 commit 4c45ca4

File tree

10 files changed

+529
-7
lines changed

10 files changed

+529
-7
lines changed

nxtbn/core/enum_perms.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class PermissionsEnum(models.TextChoices):
99
CAN_DELIVER_ORDER = "can_deliver_order"
1010
CAN_UPDATE_ORDER_PYMENT_TERM = "can_update_order_payment_term"
1111
CAN_UPDATE_ORDER_PAYMENT_METHOD = "can_update_order_payment_method"
12+
CAN_FULLFILL_ORDER = "can_fullfill_order" # Directly fullfill order, will not check lifecycle
13+
1214

1315
CAN_INITIATE_PAYMENT_REFUND = "CAN_INITIATE_PAYMENT_REFUND"
1416

nxtbn/order/api/dashboard/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ def validate(self, attrs):
213213
raise serializers.ValidationError(_("Order is already shipped or delivered."))
214214
if current_status == OrderStatus.RETURNED:
215215
raise serializers.ValidationError(_("Returned orders cannot be re-shipped."))
216+
if current_status != OrderStatus.PACKED:
217+
raise serializers.ValidationError(_("Order must be packed before it can be shipped."))
216218

217219
if new_status == OrderStatus.DELIVERED:
218220
if current_status == OrderStatus.CANCELLED:

nxtbn/order/api/dashboard/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
path('create-customer/', order_views.CreateCustomAPIView.as_view(), name='create-customer'),
1111
path('orders/<uuid:alias>/', order_views.OrderDetailView.as_view(), name='order-detail'),
1212
path('orders/status/update/<uuid:alias>/', order_views.OrderStatusUpdateAPIView.as_view(), name='order-status-update'),
13+
path('orders/fulfill/<uuid:alias>/', order_views.OrderMarkAsFullfiledAPIView.as_view(), name='order-fulfillment-status-update'),
1314
path('orders/payment-term/update/<uuid:alias>/', order_views.OrderPaymentTermUpdateAPIView.as_view(), name='order-payment-term-update'),
1415
path('orders/payment-method/update/<uuid:alias>/', order_views.OrderPaymentMethodUpdateAPIView.as_view(), name='order-payment-method-update'),
1516
path('orders/return-request/', order_views.ReturnRequestAPIView.as_view(), name='return-request'),

nxtbn/order/api/dashboard/views.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from rest_framework.views import APIView
1111
from django.db.models import Sum, Count, F
12+
from django.db import transaction
1213
from django.db.models.functions import TruncMonth, TruncDay, TruncWeek, TruncHour
1314

1415
from django.utils import timezone
@@ -40,6 +41,9 @@
4041

4142
from calendar import monthrange, day_name
4243
from nxtbn.warehouse.utils import adjust_stocks_returned_items
44+
from nxtbn.warehouse.utils import deduct_reservation_on_packed_for_dispatch, release_stock
45+
from nxtbn.order import OrderStockReservationStatus, ReturnReceiveStatus
46+
4347

4448

4549

@@ -429,8 +433,6 @@ def check_permissions(self, request):
429433
status = request.data.get('status')
430434
user = request.user
431435

432-
print(status, 'status')
433-
434436
permission_map = {
435437
OrderStatus.CANCELLED: PermissionsEnum.CAN_CANCEL_ORDER,
436438
OrderStatus.SHIPPED: PermissionsEnum.CAN_SHIP_ORDER,
@@ -440,14 +442,42 @@ def check_permissions(self, request):
440442
}
441443

442444
required_permission = permission_map.get(status)
443-
print(required_permission, 'required_permission')
444445
if required_permission and not has_required_perm(user, required_permission, Order):
445446
self.permission_denied(
446447
request,
447448
message=_("You do not have permission to perform this action."),
448449
code='permission_denied'
449450
)
450451

452+
class OrderMarkAsFullfiledAPIView(generics.GenericAPIView):
453+
queryset = Order.objects.all()
454+
serializer_class = OrderStatusUpdateSerializer
455+
permission_classes = (GranularPermission, )
456+
required_perm = PermissionsEnum.CAN_FULLFILL_ORDER
457+
458+
def patch(self, request, *args, **kwargs):
459+
with transaction.atomic():
460+
instance = Order.objects.get(alias=kwargs['alias'])
461+
462+
if instance.status == OrderStatus.DELIVERED:
463+
raise ValidationError("Order is already marked as fulfilled.")
464+
465+
if instance.status == OrderStatus.CANCELLED:
466+
raise ValidationError("Cannot mark a cancelled order as fulfilled.")
467+
468+
if instance.status == OrderStatus.RETURNED:
469+
raise ValidationError("Cannot mark a returned order as fulfilled.")
470+
471+
# Now check stock and reservation
472+
if instance.reservation_status == OrderStockReservationStatus.DISPATCHED: # do nothing if already dispatched
473+
pass
474+
else:
475+
deduct_reservation_on_packed_for_dispatch(instance)
476+
477+
instance.status = OrderStatus.DELIVERED
478+
instance.save()
479+
return Response({"message": "Order marked as fulfilled."}, status=status.HTTP_200_OK)
480+
451481
class OrderPaymentTermUpdateAPIView(generics.UpdateAPIView):
452482
model = Order
453483
permission_classes = (GranularPermission, )
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Generated by Django 4.2.11 on 2025-02-20 14:31
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("order", "0038_alter_order_reservation_status"),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name="order",
15+
options={
16+
"ordering": ("-created_at",),
17+
"permissions": [
18+
("can_approve_order", "Can approve order"),
19+
("can_cancel_order", "Can cancel order"),
20+
("can_ship_order", "Can ship order"),
21+
("can_pack_order", "Can process order"),
22+
("can_deliver_order", "Can deliver order"),
23+
("can_update_order_payment_term", "Can update order payment term"),
24+
(
25+
"can_update_order_payment_method",
26+
"Can update order payment method",
27+
),
28+
("can_fullfill_order", "Can fullfill order"),
29+
],
30+
},
31+
),
32+
]

nxtbn/order/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ class Meta:
258258
(PermissionsEnum.CAN_DELIVER_ORDER, 'Can deliver order'),
259259
(PermissionsEnum.CAN_UPDATE_ORDER_PYMENT_TERM, 'Can update order payment term'),
260260
(PermissionsEnum.CAN_UPDATE_ORDER_PAYMENT_METHOD, 'Can update order payment method'),
261+
(PermissionsEnum.CAN_FULLFILL_ORDER, 'Can fullfill order'), # Directly fullfill order, will not check order lifecycle
261262
]
262263

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

nxtbn/order/proccesor/views.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def get_shipping_rate_instance(shipping_method_id, address, total_weight):
7272
region__isnull=True,
7373
city__isnull=True
7474
).first()
75-
print(rate, 'rate for country')
7675
return rate
7776

7877
# Global
@@ -84,7 +83,6 @@ def get_shipping_rate_instance(shipping_method_id, address, total_weight):
8483
region__isnull=True,
8584
city__isnull=True
8685
).first()
87-
print(rate, 'rate for global')
8886
return rate
8987
else:
9088
raise serializers.ValidationError({"details": "We don't ship to this location."})

nxtbn/order/tests/test_order_reservation_cancel_shipped_merge_return_sc.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ def test_order_back_order_allowed(self):
251251
# Now Ship the successfull order
252252
order_status_update_url = reverse('order-status-update', args=[order_out_of_stock_response_with_stock_tracking_bo.data['order_alias']])
253253
approve = self.auth_client.patch(order_status_update_url, {"status": OrderStatus.APPROVED}, format='json')
254-
processing = self.auth_client.patch(order_status_update_url, {"status": OrderStatus.PACKED}, format='json')
254+
packed = self.auth_client.patch(order_status_update_url, {"status": OrderStatus.PACKED}, format='json')
255255
shipped = self.auth_client.patch(order_status_update_url, {"status": OrderStatus.SHIPPED}, format='json')
256256

257257
self.assertEqual(approve.status_code, status.HTTP_200_OK)
258-
self.assertEqual(processing.status_code, status.HTTP_200_OK)
258+
self.assertEqual(packed.status_code, status.HTTP_400_BAD_REQUEST) # as order is not reserved, mean is not in stock, so packing should not be allowed
259259

260260
self.assertEqual(shipped.status_code, status.HTTP_400_BAD_REQUEST) # as order is not reserved, it should not be shipped
261261

@@ -288,6 +288,10 @@ def test_order_back_order_allowed(self):
288288
self.assertEqual(remained_stock_without_bo, 15)
289289
self.assertEqual(reserved_stock_without_bo, 8)
290290

291+
# now pack
292+
packed = self.auth_client.patch(order_status_update_url, {"status": OrderStatus.PACKED}, format='json')
293+
self.assertEqual(packed.status_code, status.HTTP_200_OK) # as order is reserved, it should be packed
294+
291295
# as enough stock is available and order is reserved, now ship the order
292296
shipped = self.auth_client.put(order_status_update_url, {"status": OrderStatus.SHIPPED}, format='json')
293297
self.assertEqual(shipped.status_code, status.HTTP_200_OK)

0 commit comments

Comments
 (0)