""" API views для промокодов. """ from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated, AllowAny from .services import PromoCodeService from .models import PromoCode from rest_framework import serializers class PromoCodeValidateSerializer(serializers.Serializer): """Сериализатор валидации промокода.""" code = serializers.CharField(max_length=50, required=True) plan_id = serializers.IntegerField(required=False) student_count = serializers.IntegerField(required=False, default=0, min_value=0) duration_days = serializers.IntegerField(required=False, default=30, min_value=1) @api_view(['POST']) @permission_classes([AllowAny]) def validate_promo_code(request): """ Валидация промокода и расчет скидки. POST /api/subscriptions/promo-codes/validate/ Body: { "code": "PROMO2024", "plan_id": 1, "student_count": 5, "duration_days": 30 } """ serializer = PromoCodeValidateSerializer(data=request.data) serializer.is_valid(raise_exception=True) code = serializer.validated_data['code'] plan_id = serializer.validated_data.get('plan_id') student_count = serializer.validated_data.get('student_count', 0) duration_days = serializer.validated_data.get('duration_days', 30) # Валидация промокода user = request.user if request.user.is_authenticated else None promo_result = PromoCodeService.validate_promo_code(code, user) if not promo_result['valid']: return Response( { 'valid': False, 'error': promo_result['error'] }, status=status.HTTP_400_BAD_REQUEST ) promo_code = promo_result['promo_code'] # Если указан план, рассчитываем скидку if plan_id: from .models import SubscriptionPlan try: plan = SubscriptionPlan.objects.select_related().get(id=plan_id, is_active=True) price_data = plan.calculate_price( student_count=student_count, duration_days=duration_days, promo_code=promo_code ) return Response({ 'valid': True, 'promo_code': { 'code': promo_code.code, 'discount_type': promo_code.discount_type, 'discount_value': float(promo_code.discount_value), }, 'price': { 'original_amount': float(price_data['original_amount']), 'discount_amount': float(price_data['discount_amount']), 'final_amount': float(price_data['final_amount']), } }) except SubscriptionPlan.DoesNotExist: return Response( { 'valid': True, 'promo_code': { 'code': promo_code.code, 'discount_type': promo_code.discount_type, 'discount_value': float(promo_code.discount_value), }, 'price': None } ) # Если план не указан, просто возвращаем информацию о промокоде return Response({ 'valid': True, 'promo_code': { 'code': promo_code.code, 'discount_type': promo_code.discount_type, 'discount_value': float(promo_code.discount_value), } }) @api_view(['GET']) @permission_classes([IsAuthenticated]) def calculate_price(request): """ Расчет цены подписки с учетом промокода. GET /api/subscriptions/calculate-price/?plan_id=1&student_count=5&duration_days=30&promo_code=PROMO2024 """ from .models import SubscriptionPlan from .services import SubscriptionService plan_id = request.query_params.get('plan_id') student_count = int(request.query_params.get('student_count', 0)) duration_days = int(request.query_params.get('duration_days', 30)) promo_code_str = request.query_params.get('promo_code') remaining_days = request.query_params.get('remaining_days') is_renewal = request.query_params.get('is_renewal') == 'true' is_add_students = request.query_params.get('is_add_students') == 'true' current_student_count = request.query_params.get('current_student_count') if not plan_id: return Response( {'error': 'Необходимо указать plan_id'}, status=status.HTTP_400_BAD_REQUEST ) try: plan = SubscriptionPlan.objects.select_related().get(id=plan_id, is_active=True) except SubscriptionPlan.DoesNotExist: return Response( {'error': 'Тарифный план не найден'}, status=status.HTTP_404_NOT_FOUND ) # Проверяем доступность периода if not plan.is_duration_available(duration_days): available = plan.get_available_durations() return Response( {'error': f'Период {duration_days} дней недоступен для этого тарифа. Доступные периоды: {", ".join(map(str, available))}'}, status=status.HTTP_400_BAD_REQUEST ) # Валидация промокода если указан promo_code = None if promo_code_str: promo_result = PromoCodeService.validate_promo_code(promo_code_str, request.user) if promo_result['valid']: promo_code = promo_result['promo_code'] else: return Response( {'error': promo_result['error']}, status=status.HTTP_400_BAD_REQUEST ) # Если это продление и есть оставшиеся дни, учитываем их remaining_days_int = None if (is_renewal or is_add_students) and remaining_days: try: remaining_days_int = int(remaining_days) except (ValueError, TypeError): pass # Получаем текущее количество учеников для расчета доплаты current_student_count_int = 0 if current_student_count: try: current_student_count_int = int(current_student_count) except (ValueError, TypeError): pass elif is_renewal and request.user.is_authenticated: # Если не передано, пытаемся получить из активной подписки from .models import Subscription try: subscription = Subscription.objects.filter( user=request.user, plan=plan, status__in=['active', 'trial'] ).select_related('plan').order_by('-end_date').first() if subscription: current_student_count_int = subscription.student_count or 0 except: pass # Рассчитываем цену price_data = SubscriptionService.calculate_subscription_price( plan=plan, student_count=student_count, duration_days=duration_days, promo_code=promo_code, remaining_days=remaining_days_int, is_add_students=is_add_students, current_student_count=current_student_count_int ) response_data = { 'original_amount': float(price_data['original_amount']), 'discount_amount': float(price_data['discount_amount']), 'final_amount': float(price_data['final_amount']), } # Добавляем информацию о доплате за новых учеников if 'extra_students_payment' in price_data: response_data['extra_students_payment'] = price_data['extra_students_payment'] response_data['extra_students_count'] = price_data.get('extra_students_count', 0) response_data['remaining_days_payment'] = price_data.get('remaining_days_payment', 0) # Добавляем информацию о скидке за длительность if 'duration_discount' in price_data: response_data['duration_discount'] = price_data['duration_discount'] # Добавляем дату окончания подписки if 'end_date' in price_data: response_data['end_date'] = price_data['end_date'] if 'end_date_display' in price_data: response_data['end_date_display'] = price_data['end_date_display'] if promo_code: response_data['promo_code'] = { 'code': promo_code.code, 'discount_type': promo_code.discount_type, 'discount_value': float(promo_code.discount_value), } return Response(response_data)