Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d08a43d
✨(backend) add discount relation to order group model
jonathanreveille Feb 10, 2025
cd5302b
✨(backend) add discount to admin serializers order group
jonathanreveille Feb 17, 2025
c02526d
✨(backend) add discount admin viewset
jonathanreveille Feb 17, 2025
443fff4
✨(backend) add discount to client order group serializer
jonathanreveille Feb 17, 2025
95af012
✨(backend) django admin discount view
jonathanreveille Feb 17, 2025
7b022b0
✨(backend) order groups assignation
kernicPanel Feb 18, 2025
3f3a54b
✨(backend) add voucher model
kernicPanel Mar 5, 2025
f6a5343
✨(backend) use discount serializer on ordergroup
kernicPanel Mar 14, 2025
43576a0
✨(backend) discount rate and amount unique
kernicPanel Mar 31, 2025
f9e6c79
✨(frontend) add discount to order group form
kernicPanel Mar 18, 2025
5900fb0
✨(backend) allow empty nb_seats en order group api
kernicPanel Mar 25, 2025
9ea948d
✨(frontend) add all tests runner helper
kernicPanel Mar 25, 2025
6da0ebb
✨(backend) searchable discount list api
kernicPanel Mar 31, 2025
d608734
🩹(backend) use currency setting in Discount model
kernicPanel Apr 11, 2025
676bcd0
✅(backend) fix failing test on order group list
jonathanreveille Apr 14, 2025
fc4e232
✨(backend) batch order model
jonathanreveille Mar 6, 2025
3c51879
✨(backend) add state flows for batch order
jonathanreveille Mar 17, 2025
c950dd6
✨(backend) client endpoints for signature and payment of batch order
jonathanreveille Mar 10, 2025
0d64e87
♻️(backend) refactor generate contract document context
jonathanreveille Mar 10, 2025
b1e9af8
🧑‍💻(backend) debug view for batch order mails
jonathanreveille Mar 14, 2025
1a72f88
✨(backend) assign order with voucher code from batch order
jonathanreveille Apr 2, 2025
1065362
✨(backend) allow empty values in order group api
kernicPanel Apr 11, 2025
b73a725
🐛(frontend) fix mui locale formatting
kernicPanel Apr 11, 2025
481e664
✨(frontend) add dates to order group form
kernicPanel Apr 11, 2025
cf3c04f
✨(backend) cancel orders and delete vouchers of canceled batch order
jonathanreveille Apr 15, 2025
05025b9
✨(backend) batch order admin api viewset and serializers
jonathanreveille Apr 15, 2025
d559ed0
✅(backend) prevent syncing during local testing
kernicPanel May 12, 2025
c26a007
✨(backend) add voucher api
kernicPanel Mar 12, 2025
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
4 changes: 3 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ jobs:
parallelism: 5
resource_class: large
working_directory: ~/joanie/src/frontend/admin
environment:
TZ: "Europe/Paris"
steps:
- checkout:
path: ~/joanie
Expand Down Expand Up @@ -428,7 +430,7 @@ jobs:
command: yarn playwright install --with-deps chromium
- run:
name: Run test command
command: PLAYWRIGHT_JUNIT_OUTPUT_FILE=./reports/playwright-ct/results.xml yarn test-ct --shard=$(($CIRCLE_NODE_INDEX + 1))/${CIRCLE_NODE_TOTAL} --reporter=junit,list
command: PLAYWRIGHT_JUNIT_OUTPUT_FILE=./reports/playwright-ct/results.xml yarn test:ct --shard=$(($CIRCLE_NODE_INDEX + 1))/${CIRCLE_NODE_TOTAL} --reporter=junit,list
- store_test_results:
path: ./reports/playwright-ct/

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ and this project adheres to

## [Unreleased]

### Added

- Add admin api endpoints for `BatchOrder` resources to create, read, list
and cancel
- Claim an order generated from a batch order through voucher code

### Changed

- Add course offer information into course webhook synchronization payload
Expand All @@ -25,6 +31,7 @@ and this project adheres to

### Added

- Add `Discount` relation to `OrderGroup` model
- Add `start` and `end` datetime fields on order group model
- Add Sarbacane newsletter client

Expand Down
8 changes: 7 additions & 1 deletion src/backend/joanie/admin_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,18 @@
)
admin_router.register("users", api_admin.UserViewSet, basename="admin_user")
admin_router.register("orders", api_admin.OrderViewSet, basename="admin_orders")
admin_router.register(
"batch-orders", api_admin.BatchOrderViewSet, basename="admin_batch_orders"
)
admin_router.register(
"enrollments", api_admin.EnrollmentViewSet, basename="admin_enrollments"
)
admin_router.register("teachers", api_admin.TeacherViewSet, basename="admin_teachers")
admin_router.register("skills", api_admin.SkillViewSet, basename="admin_skills")

admin_router.register(
"discounts", api_admin.DiscountViewSet, basename="admin_discounts"
)
admin_router.register("vouchers", api_admin.VoucherViewSet, basename="admin_vouchers")
# Admin API routes nested under a course
admin_course_related_router = DefaultRouter()
admin_course_related_router.register(
Expand Down
1 change: 1 addition & 0 deletions src/backend/joanie/client_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
router.register("course-runs", api_client.CourseRunViewSet, basename="course-runs")
router.register("enrollments", api_client.EnrollmentViewSet, basename="enrollments")
router.register("orders", api_client.OrderViewSet, basename="orders")
router.register("batch-orders", api_client.BatchOrderViewSet, basename="batch-orders")
router.register(
"organizations", api_client.OrganizationViewSet, basename="organizations"
)
Expand Down
16 changes: 15 additions & 1 deletion src/backend/joanie/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,20 +198,24 @@ class OrderGroupAdmin(admin.ModelAdmin):

list_display = (
"course_product_relation",
"position",
"is_active",
"is_enabled",
"nb_available_seats",
"start",
"end",
"discount",
)
search_fields = ("course_product_relation", "start", "end")
fields = (
"course_product_relation",
"position",
"is_enabled",
"is_active",
"nb_seats",
"start",
"end",
"discount",
)
readonly_fields = ("nb_available_seats", "is_enabled")
readonly_update_fields = ("course_product_relation", "nb_seats")
Expand Down Expand Up @@ -501,8 +505,18 @@ def get_related_courses_as_html(obj): # pylint: disable=no-self-use
class DiscountAdmin(admin.ModelAdmin):
"""Admin class for the Discount model"""

list_display = ("rate", "amount")
list_display = ("id", "string_discount_value", "is_used")
search_fields = ["rate", "amount"]
readonly_fields = ("is_used",)

def is_used(self, obj): # pylint: disable=no-self-use
"""Returns a counter of how many times the discount is used in order groups"""
return obj.usage_count

@admin.display(description="Discount")
def string_discount_value(self, obj): # pylint: disable=no-self-use
"""Returns the string representation of the discount value."""
return str(obj)


@admin.register(models.Order)
Expand Down
69 changes: 67 additions & 2 deletions src/backend/joanie/core/api/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import transaction
from django.http import JsonResponse, StreamingHttpResponse
from django.utils import timezone

Expand Down Expand Up @@ -34,6 +35,7 @@
get_generated_certificates,
get_orders,
)
from joanie.core.utils.organization import get_least_active_organization
from joanie.core.utils.payment_schedule import (
get_transaction_references_to_refund,
has_installment_paid,
Expand Down Expand Up @@ -594,9 +596,13 @@ class NestedCourseProductRelationOrderGroupViewSet(
permission_classes = [permissions.IsAdminUser & permissions.DjangoModelPermissions]
serializer_classes = {
"create": serializers.AdminOrderGroupCreateSerializer,
"update": serializers.AdminOrderGroupUpdateSerializer,
"partial_update": serializers.AdminOrderGroupUpdateSerializer,
}
default_serializer_class = serializers.AdminOrderGroupSerializer
queryset = models.OrderGroup.objects.all().select_related("course_product_relation")
queryset = models.OrderGroup.objects.all().select_related(
"course_product_relation", "discount"
)
ordering = "created_on"
lookup_fields = ["course_product_relation", "pk"]
lookup_url_kwargs = ["course_product_relation_id", "pk"]
Expand Down Expand Up @@ -645,7 +651,6 @@ class OrderViewSet(
"contract__definition",
"certificate",
"certificate__certificate_definition",
"order_group",
"credit_card",
)

Expand Down Expand Up @@ -738,6 +743,47 @@ def export(self, request):
)


class BatchOrderViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
"""
Admin Batch Order Viewset
"""

authentication_classes = [SessionAuthenticationWithAuthenticateHeader]
permission_classes = [permissions.IsAdminUser & permissions.DjangoModelPermissions]
serializer_class = serializers.AdminBatchOrderSerializer
queryset = models.BatchOrder.objects.all().select_related(
"contract",
"relation",
"organization",
)
filter_backends = [DjangoFilterBackend, AliasOrderingFilter]

def perform_create(self, serializer):
"""Override `perform_create` to start the flow of the batch order object"""
instance = serializer.save()
instance.init_flow()

def destroy(self, request, *args, **kwargs):
"""Cancel a batch order."""
batch_order = self.get_object()

is_completed = batch_order.state == enums.BATCH_ORDER_STATE_COMPLETED

batch_order.flow.cancel()

if is_completed:
# Delete orders and linked vouchers
batch_order.cancel_orders()

return Response(status=HTTPStatus.NO_CONTENT)


class OrganizationAddressViewSet(
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
Expand Down Expand Up @@ -781,3 +827,22 @@ def destroy(self, request, *args, **kwargs):
) from error

return super().destroy(request, *args, **kwargs)


class DiscountViewSet(viewsets.ModelViewSet):
"""Admin Discount Viewset"""

authentication_classes = [SessionAuthenticationWithAuthenticateHeader]
permission_classes = [permissions.IsAdminUser & permissions.DjangoModelPermissions]
serializer_class = serializers.AdminDiscountSerializer
queryset = models.Discount.objects.all()
filterset_class = filters.DiscountAdminFilterSet


class VoucherViewSet(viewsets.ModelViewSet):
"""Admin Voucher Viewset"""

authentication_classes = [SessionAuthenticationWithAuthenticateHeader]
permission_classes = [permissions.IsAdminUser & permissions.DjangoModelPermissions]
serializer_class = serializers.AdminVoucherSerializer
queryset = models.Voucher.objects.all()
Loading