diff --git a/apps/payment/models.py b/apps/payment/models.py index 7070b5eb..b8afc76b 100644 --- a/apps/payment/models.py +++ b/apps/payment/models.py @@ -3,12 +3,13 @@ from decimal import Decimal import arrow import short_url +from django.db.models.functions import Coalesce from paymentwall import Pingback from polymorphic.models import PolymorphicModel from polymorphic.managers import PolymorphicManager -from django.db.models import Func, F +from django.db.models import Func, F, Q, Max from django.db import models from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField, JSONField @@ -423,6 +424,38 @@ class UserGiftCertificate(models.Model): return short_url.encode_url(self.id) if self.id else None +class DiscountManager(models.Manager): + + def get_actual_discounts(self, user=None): + now_dt = now() + qs = self.filter(start__lte=now_dt, end__gte=now_dt, is_active=True, promo_id__isnull=True) + if user: + qs = qs.filter(Q(client=user) | Q(client__isnull=True)) + else: + qs = qs.filter(client__isnull=True) + return qs + + def get_courses_discounts(self, user=None, course=None): + actual_discounts = self.get_actual_discounts(user=user) + all_courses_max_discount = actual_discounts.filter(product=Discount.PRODUCT_ALL_COURSES)\ + .aggregate(max_discount=Max('value')) + if course: + actual_discounts = actual_discounts.filter(course=course) + courses_max_discounts = actual_discounts.filter(product=Discount.PRODUCT_ONE_COURSE).values('course')\ + .annotate(max_discount=Max('value')) + return all_courses_max_discount, courses_max_discounts + + def get_packages_discounts(self, user=None, package=None): + actual_discounts = self.get_actual_discounts(user=user) + all_packages_max_discount = actual_discounts.filter(product=Discount.PRODUCT_ALL_PACKAGES) \ + .aggregate(max_discount=Max('value')) + if package: + actual_discounts = actual_discounts.filter(package=package) + packages_max_discounts = actual_discounts.filter(product=Discount.PRODUCT_ONE_PACKAGE).values('package') \ + .annotate(max_discount=Max('value')) + return all_packages_max_discount, packages_max_discounts + + class Discount(models.Model): PRODUCT_ALL_COURSES = 0 PRODUCT_ALL_PACKAGES = 1 @@ -467,6 +500,8 @@ class Discount(models.Model): author = models.ForeignKey(User, verbose_name='Автор', null=True, blank=True, related_name='author_discounts', on_delete=models.CASCADE) + objects = DiscountManager() + def __str__(self): return self.name diff --git a/apps/payment/tests/tests_models.py b/apps/payment/tests/tests_models.py index 7ce503c2..e746a2e8 100644 --- a/apps/payment/tests/tests_models.py +++ b/apps/payment/tests/tests_models.py @@ -1,3 +1,336 @@ +from datetime import timedelta + from django.test import TestCase +from django.utils.timezone import now + +from apps.payment.models import Discount +from project.tests.factories import CourseFactory, DiscountFactory, UserFactory, PackageFactory + + +class DiscountForCoursesModelTestCase(TestCase): + + def setUp(self): + self.user = UserFactory.create() + + def test_discounts_for_courses_without_user(self): + course_1 = CourseFactory.create() + course_2 = CourseFactory.create() + course_3 = CourseFactory.create() + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course_1, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course_2, + product=Discount.PRODUCT_ONE_COURSE) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts() + self.assertEqual(all_curses_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_courses[0], {'course': course_1.pk, 'max_discount': 30}) + self.assertEqual(discounts_for_courses[1], {'course': course_2.pk, 'max_discount': 50}) + + def test_greater_value_discount_without_user(self): + course = CourseFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course, + product=Discount.PRODUCT_ONE_COURSE) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts() + self.assertEqual(all_curses_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_courses[0], {'course': course.pk, 'max_discount': 50}) + + def test_exclude_personal_discounts_without_user(self): + user_1 = UserFactory.create() + course = CourseFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course, + product=Discount.PRODUCT_ONE_COURSE, client=user_1) + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts() + self.assertEqual(all_curses_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_courses[0], {'course': course.pk, 'max_discount': 30}) + + def test_discount_for_one_course_and_all_courses(self): + course = CourseFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_COURSES) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts() + self.assertEqual(all_curses_discounts, {'max_discount': 10}) + self.assertEqual(discounts_for_courses[0], {'course': course.pk, 'max_discount': 50}) + + def test_personal_discounts(self): + course = CourseFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course, + product=Discount.PRODUCT_ONE_COURSE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_COURSES) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts(user=self.user) + self.assertEqual(all_curses_discounts, {'max_discount': 10}) + self.assertEqual(discounts_for_courses[0], {'course': course.pk, 'max_discount': 50}) + + def test_personal_discounts_multiple_users(self): + user_1 = UserFactory() + course = CourseFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course, + product=Discount.PRODUCT_ONE_COURSE, client=user_1) + + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course, + product=Discount.PRODUCT_ONE_COURSE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_COURSES, + client=self.user) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts(user=self.user) + self.assertEqual(all_curses_discounts, {'max_discount': 10}) + self.assertEqual(discounts_for_courses[0], {'course': course.pk, 'max_discount': 50}) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts(user=user_1) + self.assertEqual(all_curses_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_courses[0], {'course': course.pk, 'max_discount': 30}) + + def test_discount_for_one_course_without_user(self): + course_1 = CourseFactory.create() + course_2 = CourseFactory.create() + course_3 = CourseFactory.create() + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course_1, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course_2, + product=Discount.PRODUCT_ONE_COURSE) + DiscountFactory.create(end=now() + timedelta(days=5), value=60, course=course_3, + product=Discount.PRODUCT_ONE_COURSE) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts(user=None, course=course_1) + self.assertEqual(all_curses_discounts, {'max_discount': None}) + self.assertEqual(len(discounts_for_courses), 1) + self.assertEqual(discounts_for_courses[0], {'course': course_1.pk, 'max_discount': 30}) + + def test_discount_for_one_course_with_multiple_users(self): + user_1 = UserFactory.create() + course_1 = CourseFactory.create() + course_2 = CourseFactory.create() + + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_COURSES) + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course_1, + product=Discount.PRODUCT_ONE_COURSE, client=user_1) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course_2, + product=Discount.PRODUCT_ONE_COURSE, client=user_1) + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, course=course_1, + product=Discount.PRODUCT_ONE_COURSE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, course=course_2, + product=Discount.PRODUCT_ONE_COURSE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, product=Discount.PRODUCT_ALL_COURSES, + client=self.user) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts(user=user_1, + course=course_1) + self.assertEqual(all_curses_discounts, {'max_discount': 10}) + self.assertEqual(len(discounts_for_courses), 1) + self.assertEqual(discounts_for_courses[0], {'course': course_1.pk, 'max_discount': 30}) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts(user=self.user, + course=course_1) + self.assertEqual(all_curses_discounts, {'max_discount': 50}) + self.assertEqual(len(discounts_for_courses), 1) + self.assertEqual(discounts_for_courses[0], {'course': course_1.pk, 'max_discount': 30}) + + def test_not_active_discount(self): + DiscountFactory.create(end=now() + timedelta(days=5), value=60, product=Discount.PRODUCT_ALL_COURSES, + is_active=False) + DiscountFactory.create(end=now() + timedelta(days=5), value=40, product=Discount.PRODUCT_ALL_COURSES) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts() + + self.assertEqual(all_curses_discounts, {'max_discount': 40}) + self.assertEqual(len(discounts_for_courses), 0) + + def test_old_discount(self): + DiscountFactory.create(start=now() - timedelta(days=2), end=now() - timedelta(days=1), value=60, + product=Discount.PRODUCT_ALL_COURSES) + DiscountFactory.create(end=now() + timedelta(days=5), value=40, product=Discount.PRODUCT_ALL_COURSES) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts() + + self.assertEqual(all_curses_discounts, {'max_discount': 40}) + self.assertEqual(len(discounts_for_courses), 0) + + def test_discount_from_the_future(self): + DiscountFactory.create(start=now() + timedelta(days=2), end=now() + timedelta(days=3), value=60, + product=Discount.PRODUCT_ALL_COURSES) + DiscountFactory.create(end=now() + timedelta(days=5), value=40, product=Discount.PRODUCT_ALL_COURSES) + + all_curses_discounts, discounts_for_courses = Discount.objects.get_courses_discounts() + + self.assertEqual(all_curses_discounts, {'max_discount': 40}) + self.assertEqual(len(discounts_for_courses), 0) + + +class DiscountForPackagesModelTestCase(TestCase): + + def setUp(self): + self.user = UserFactory.create() + + def test_discounts_for_packages_without_user(self): + package_1 = PackageFactory.create() + package_2 = PackageFactory.create() + package_3 = PackageFactory.create() + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package_1, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package_2, + product=Discount.PRODUCT_ONE_PACKAGE) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts() + self.assertEqual(all_packages_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_packages[0], {'package': package_1.pk, 'max_discount': 30}) + self.assertEqual(discounts_for_packages[1], {'package': package_2.pk, 'max_discount': 50}) + + def test_greater_value_discount_without_user(self): + package = PackageFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package, + product=Discount.PRODUCT_ONE_PACKAGE) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts() + self.assertEqual(all_packages_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_packages[0], {'package': package.pk, 'max_discount': 50}) + + def test_exclude_personal_discounts_without_user(self): + user_1 = UserFactory.create() + package = PackageFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package, + product=Discount.PRODUCT_ONE_PACKAGE, client=user_1) + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts() + self.assertEqual(all_packages_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_packages[0], {'package': package.pk, 'max_discount': 30}) + + def test_discount_for_one_package_and_all_packages(self): + package = PackageFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_PACKAGES) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts() + self.assertEqual(all_packages_discounts, {'max_discount': 10}) + self.assertEqual(discounts_for_packages[0], {'package': package.pk, 'max_discount': 50}) + + def test_personal_discounts(self): + package = PackageFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package, + product=Discount.PRODUCT_ONE_PACKAGE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_PACKAGES) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts(user=self.user) + self.assertEqual(all_packages_discounts, {'max_discount': 10}) + self.assertEqual(discounts_for_packages[0], {'package': package.pk, 'max_discount': 50}) + + def test_personal_discounts_multiple_users(self): + user_1 = UserFactory() + package = PackageFactory.create() + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package, + product=Discount.PRODUCT_ONE_PACKAGE, client=user_1) + + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package, + product=Discount.PRODUCT_ONE_PACKAGE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_PACKAGES, + client=self.user) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts(user=self.user) + self.assertEqual(all_packages_discounts, {'max_discount': 10}) + self.assertEqual(discounts_for_packages[0], {'package': package.pk, 'max_discount': 50}) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts(user=user_1) + self.assertEqual(all_packages_discounts, {'max_discount': None}) + self.assertEqual(discounts_for_packages[0], {'package': package.pk, 'max_discount': 30}) + + def test_discount_for_one_package_without_user(self): + package_1 = PackageFactory.create() + package_2 = PackageFactory.create() + package_3 = PackageFactory.create() + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package_1, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package_2, + product=Discount.PRODUCT_ONE_PACKAGE) + DiscountFactory.create(end=now() + timedelta(days=5), value=60, package=package_3, + product=Discount.PRODUCT_ONE_PACKAGE) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts(user=None, + package=package_1) + self.assertEqual(all_packages_discounts, {'max_discount': None}) + self.assertEqual(len(discounts_for_packages), 1) + self.assertEqual(discounts_for_packages[0], {'package': package_1.pk, 'max_discount': 30}) + + def test_discount_for_one_package_with_multiple_users(self): + user_1 = UserFactory.create() + package_1 = PackageFactory.create() + package_2 = PackageFactory.create() + + DiscountFactory.create(end=now() + timedelta(days=5), value=10, product=Discount.PRODUCT_ALL_PACKAGES) + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package_1, + product=Discount.PRODUCT_ONE_PACKAGE, client=user_1) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package_2, + product=Discount.PRODUCT_ONE_PACKAGE, client=user_1) + + DiscountFactory.create(end=now() + timedelta(days=5), value=30, package=package_1, + product=Discount.PRODUCT_ONE_PACKAGE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, package=package_2, + product=Discount.PRODUCT_ONE_PACKAGE, client=self.user) + DiscountFactory.create(end=now() + timedelta(days=5), value=50, product=Discount.PRODUCT_ALL_PACKAGES, + client=self.user) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts(user=user_1, + package=package_1) + self.assertEqual(all_packages_discounts, {'max_discount': 10}) + self.assertEqual(len(discounts_for_packages), 1) + self.assertEqual(discounts_for_packages[0], {'package': package_1.pk, 'max_discount': 30}) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts(user=self.user, + package=package_1) + self.assertEqual(all_packages_discounts, {'max_discount': 50}) + self.assertEqual(len(discounts_for_packages), 1) + self.assertEqual(discounts_for_packages[0], {'package': package_1.pk, 'max_discount': 30}) + + def test_not_active_discount(self): + DiscountFactory.create(end=now() + timedelta(days=5), value=60, product=Discount.PRODUCT_ALL_PACKAGES, + is_active=False) + DiscountFactory.create(end=now() + timedelta(days=5), value=40, product=Discount.PRODUCT_ALL_PACKAGES) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts() + + self.assertEqual(all_packages_discounts, {'max_discount': 40}) + self.assertEqual(len(discounts_for_packages), 0) + + def test_old_discount(self): + DiscountFactory.create(start=now() - timedelta(days=2), end=now() - timedelta(days=1), value=60, + product=Discount.PRODUCT_ALL_PACKAGES) + DiscountFactory.create(end=now() + timedelta(days=5), value=40, product=Discount.PRODUCT_ALL_PACKAGES) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts() + + self.assertEqual(all_packages_discounts, {'max_discount': 40}) + self.assertEqual(len(discounts_for_packages), 0) + + def test_discount_from_the_future(self): + DiscountFactory.create(start=now() + timedelta(days=2), end=now() + timedelta(days=3), value=60, + product=Discount.PRODUCT_ALL_PACKAGES) + DiscountFactory.create(end=now() + timedelta(days=5), value=40, product=Discount.PRODUCT_ALL_PACKAGES) + + all_packages_discounts, discounts_for_packages = Discount.objects.get_packages_discounts() -# Create your tests here. + self.assertEqual(all_packages_discounts, {'max_discount': 40}) + self.assertEqual(len(discounts_for_packages), 0) diff --git a/project/tests/factories.py b/project/tests/factories.py index a56fd68b..3a0060c2 100644 --- a/project/tests/factories.py +++ b/project/tests/factories.py @@ -9,6 +9,7 @@ from django.utils.text import slugify from apps.course.models import * from apps.content.models import * +from apps.payment.models import Discount from apps.user.models import * @@ -94,3 +95,21 @@ class CourseFactory(DjangoModelFactory): cover = None #factory.SubFactory(ImageObjectFactory) gallery = None status = factory.Iterator([s[0] for s in Course.STATUS_CHOICES]) + + +class DiscountFactory(DjangoModelFactory): + author = factory.SubFactory(UserFactory, role=User.AUTHOR_ROLE) + name = factory.Faker('sentence', nb_words=3) + + class Meta: + model = Discount + + +class PackageFactory(DjangoModelFactory): + description = factory.Faker('sentence', nb_words=5) + options = factory.Faker('sentence', nb_words=10) + price = factory.fuzzy.FuzzyDecimal(low=0, high=9999999.50) + duration = factory.fuzzy.FuzzyInteger(low=0, high=9999) + + class Meta: + model = Package