From 24e3d6b15579ca9a20e802ebf628346d28d519dd Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Sat, 12 Jul 2025 12:58:23 +0330 Subject: [PATCH] closed and active quota lists - incentive plan search --- .../migrations/0041_quota_sale_unit.py | 19 + ...042_alter_incentiveplan_unique_together.py | 17 + apps/product/models.py | 10 +- .../api/v1/serializers/product_serializers.py | 144 ----- .../quota_distribution_serializers.py | 2 +- .../api/v1/serializers/quota_serializers.py | 150 +++++ apps/product/web/api/v1/urls.py | 22 +- .../web/api/v1/viewsets/product_api.py | 518 +--------------- apps/product/web/api/v1/viewsets/quota_api.py | 586 ++++++++++++++++++ 9 files changed, 795 insertions(+), 673 deletions(-) create mode 100644 apps/product/migrations/0041_quota_sale_unit.py create mode 100644 apps/product/migrations/0042_alter_incentiveplan_unique_together.py create mode 100644 apps/product/web/api/v1/serializers/quota_serializers.py create mode 100644 apps/product/web/api/v1/viewsets/quota_api.py diff --git a/apps/product/migrations/0041_quota_sale_unit.py b/apps/product/migrations/0041_quota_sale_unit.py new file mode 100644 index 0000000..6ad8d73 --- /dev/null +++ b/apps/product/migrations/0041_quota_sale_unit.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2025-07-12 06:35 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0040_remove_saleunit_variation_coefficient'), + ] + + operations = [ + migrations.AddField( + model_name='quota', + name='sale_unit', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quotas', to='product.saleunit'), + ), + ] diff --git a/apps/product/migrations/0042_alter_incentiveplan_unique_together.py b/apps/product/migrations/0042_alter_incentiveplan_unique_together.py new file mode 100644 index 0000000..46a7ede --- /dev/null +++ b/apps/product/migrations/0042_alter_incentiveplan_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0 on 2025-07-12 07:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0041_quota_sale_unit'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='incentiveplan', + unique_together=set(), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index de48a4b..a6f692d 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -268,9 +268,6 @@ class IncentivePlan(BaseModel): start_date_limit = models.DateField(null=True, blank=True) end_date_limit = models.DateField(null=True, blank=True) - class Meta: - unique_together = ('name', 'registering_organization') - def __str__(self): return self.name @@ -304,6 +301,12 @@ class Quota(BaseModel): null=True ) sale_type = models.CharField(max_length=50, choices=[("free", "آزاد"), ("gov", "دولتی")]) # noqa + sale_unit = models.ForeignKey( + SaleUnit, + on_delete=models.CASCADE, + related_name='quotas', + null=True + ) month_choices = ArrayField(base_field=models.IntegerField(), null=True) group = models.CharField( max_length=50, @@ -311,7 +314,6 @@ class Quota(BaseModel): ) has_distribution_limit = models.BooleanField(default=False) distribution_mode = ArrayField(base_field=models.IntegerField(), blank=True, null=True) - base_price_factory = models.DecimalField(max_digits=12, decimal_places=2) base_price_cooperative = models.DecimalField(max_digits=12, decimal_places=2) final_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) diff --git a/apps/product/web/api/v1/serializers/product_serializers.py b/apps/product/web/api/v1/serializers/product_serializers.py index 038fbff..07b1a70 100644 --- a/apps/product/web/api/v1/serializers/product_serializers.py +++ b/apps/product/web/api/v1/serializers/product_serializers.py @@ -120,147 +120,3 @@ class IncentivePlanSerializer(serializers.ModelSerializer): # noqa class Meta: model = product_models.IncentivePlan fields = '__all__' - - -class QuotaSerializer(serializers.ModelSerializer): - class Meta: - model = product_models.Quota - fields = '__all__' - depth = 0 - - def to_representation(self, instance): - representation = super().to_representation(instance) - if isinstance(instance, product_models.Quota): - representation['incentive_plan'] = QuotaIncentiveAssignmentSerializer( - instance.incentive_assignments.all(), - many=True - ).data - - representation['attribute_values'] = AttributeValueSerializer( - instance.attribute_values.all(), - many=True - ).data - - representation['brokers'] = QuotaBrokerValueSerializer( - instance.broker_values.all(), - many=True - ).data - - representation['livestock_allocations'] = QuotaLiveStockAllocationSerializer( - instance.livestock_allocations.all(), - many=True - ).data - - representation['livestock_limitations'] = QuotaLiveStockAgeLimitationSerializer( - instance.livestock_age_limitations.all(), - many=True - ).data - - return representation - - def update(self, instance, validated_data): - """ Custom Update """ - - instance.quota_id = validated_data.get('quota_id', instance.quota_id) - instance.quota_code = validated_data.get('quota_code', instance.quota_code) - instance.quota_weight = validated_data.get('quota_weight', instance.quota_weight) - instance.remaining_weight = validated_data.get('remaining_weight', instance.remaining_weight) - instance.quota_distributed = validated_data.get('quota_distributed', instance.quota_distributed) - instance.quota_balance = validated_data.get('quota_balance', instance.quota_balance) - instance.product = validated_data.get('product', instance.product) - instance.sale_type = validated_data.get('sale_type', instance.sale_type) - instance.month_choices = validated_data.get('month_choices', instance.month_choices) - instance.group = validated_data.get('group', instance.group) - instance.has_distribution_limit = validated_data.get('has_distribution_limit', instance.has_distribution_limit) - instance.distribution_mode = validated_data.get('distribution_mode', instance.distribution_mode) - instance.base_price_factory = validated_data.get('base_price_factory', instance.base_price_factory) - instance.base_price_cooperative = validated_data.get('base_price_cooperative', instance.base_price_cooperative) - instance.final_price = validated_data.get('final_price', instance.final_price) - instance.is_closed = validated_data.get('is_closed', instance.is_closed) - instance.closed_at = validated_data.get('closed_at', instance.closed_at) - instance.save() - - instance.assigned_organizations.clear() - instance.assigned_organizations.add( - *(validated_data.get('assigned_organizations', instance.assigned_organizations)) - ) - - return instance - - -class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer): - class Meta: - model = product_models.QuotaIncentiveAssignment - fields = '__all__' - - def update(self, instance, validated_data): - """ Custom Update """ - - instance.quota = validated_data.get('quota', instance.quota) - instance.incentive_plan = validated_data.get('incentive_plan', instance.incentive_plan) - instance.heavy_value = validated_data.get('heavy_value', instance.heavy_value) - instance.light_value = validated_data.get('light_value', instance.light_value) - instance.save() - - return instance - - -class QuotaBrokerValueSerializer(serializers.ModelSerializer): # noqa - class Meta: - model = product_models.QuotaBrokerValue - fields = '__all__' - - def update(self, instance, validated_data): - """ Custom Update """ - - instance.quota = validated_data.get('quota', instance.quota) - instance.broker = validated_data.get('broker', instance.broker) - instance.value = validated_data.get('value', instance.value) - instance.save() - - return instance - - -class QuotaLiveStockAllocationSerializer(serializers.ModelSerializer): - class Meta: - model = product_models.QuotaLivestockAllocation - fields = '__all__' - extra_kwargs = { - 'livestock_group': { - 'required': False - }, - 'livestock_type': { - 'required': False - }, - 'livestock_subtype': { - 'required': False - } - } - - def update(self, instance, validated_data): - """ Custom Update """ - - instance.quota = validated_data.get('quota', instance.quota) - instance.livestock_group = validated_data.get('livestock_group', instance.livestock_group) - instance.livestock_type = validated_data.get('livestock_type', instance.livestock_type) - instance.livestock_subtype = validated_data.get('livestock_subtype', instance.livestock_subtype) - instance.save() - - return instance - - -class QuotaLiveStockAgeLimitationSerializer(serializers.ModelSerializer): - class Meta: - model = product_models.QuotaLiveStockAgeLimitation - fields = '__all__' - - def update(self, instance, validated_data): - """ Custom Update """ - - instance.quota = validated_data.get('quota', instance.quota) - instance.livestock_type = validated_data.get('livestock_type', instance.livestock_type) - instance.livestock_subtype = validated_data.get('livestock_subtype', instance.livestock_subtype) - instance.age_month = validated_data.get('age_month', instance.age_month) - instance.save() - - return instance diff --git a/apps/product/web/api/v1/serializers/quota_distribution_serializers.py b/apps/product/web/api/v1/serializers/quota_distribution_serializers.py index aa8976a..d784803 100644 --- a/apps/product/web/api/v1/serializers/quota_distribution_serializers.py +++ b/apps/product/web/api/v1/serializers/quota_distribution_serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from apps.product import models as product_models -from apps.product.web.api.v1.serializers.product_serializers import QuotaSerializer +from apps.product.web.api.v1.serializers.quota_serializers import QuotaSerializer from django.db import models from apps.product.exceptions import ( QuotaWeightException, diff --git a/apps/product/web/api/v1/serializers/quota_serializers.py b/apps/product/web/api/v1/serializers/quota_serializers.py new file mode 100644 index 0000000..268c44e --- /dev/null +++ b/apps/product/web/api/v1/serializers/quota_serializers.py @@ -0,0 +1,150 @@ +from apps.authentication.api.v1.serializers.serializer import OrganizationSerializer +from apps.authorization.api.v1 import serializers as authorize_serializers +from apps.product.web.api.v1.serializers import product_serializers +from apps.product import models as product_models +from rest_framework import serializers + + +class QuotaSerializer(serializers.ModelSerializer): + class Meta: + model = product_models.Quota + fields = '__all__' + depth = 0 + + def to_representation(self, instance): + representation = super().to_representation(instance) + if isinstance(instance, product_models.Quota): + representation['incentive_plan'] = QuotaIncentiveAssignmentSerializer( + instance.incentive_assignments.all(), + many=True + ).data + + representation['attribute_values'] = product_serializers.AttributeValueSerializer( + instance.attribute_values.all(), + many=True + ).data + + representation['brokers'] = QuotaBrokerValueSerializer( + instance.broker_values.all(), + many=True + ).data + + representation['livestock_allocations'] = QuotaLiveStockAllocationSerializer( + instance.livestock_allocations.all(), + many=True + ).data + + representation['livestock_limitations'] = QuotaLiveStockAgeLimitationSerializer( + instance.livestock_age_limitations.all(), + many=True + ).data + + return representation + + def update(self, instance, validated_data): + """ Custom Update """ + + instance.quota_id = validated_data.get('quota_id', instance.quota_id) + instance.quota_code = validated_data.get('quota_code', instance.quota_code) + instance.quota_weight = validated_data.get('quota_weight', instance.quota_weight) + instance.remaining_weight = validated_data.get('remaining_weight', instance.remaining_weight) + instance.quota_distributed = validated_data.get('quota_distributed', instance.quota_distributed) + instance.quota_balance = validated_data.get('quota_balance', instance.quota_balance) + instance.product = validated_data.get('product', instance.product) + instance.sale_type = validated_data.get('sale_type', instance.sale_type) + instance.sale_unit = validated_data.get('sale_unit', instance.sale_type) + instance.month_choices = validated_data.get('month_choices', instance.month_choices) + instance.group = validated_data.get('group', instance.group) + instance.has_distribution_limit = validated_data.get('has_distribution_limit', instance.has_distribution_limit) + instance.distribution_mode = validated_data.get('distribution_mode', instance.distribution_mode) + instance.base_price_factory = validated_data.get('base_price_factory', instance.base_price_factory) + instance.base_price_cooperative = validated_data.get('base_price_cooperative', instance.base_price_cooperative) + instance.final_price = validated_data.get('final_price', instance.final_price) + instance.is_closed = validated_data.get('is_closed', instance.is_closed) + instance.closed_at = validated_data.get('closed_at', instance.closed_at) + instance.save() + + instance.assigned_organizations.clear() + instance.assigned_organizations.add( + *(validated_data.get('assigned_organizations', instance.assigned_organizations)) + ) + + return instance + + +class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer): + class Meta: + model = product_models.QuotaIncentiveAssignment + fields = '__all__' + + def update(self, instance, validated_data): + """ Custom Update """ + + instance.quota = validated_data.get('quota', instance.quota) + instance.incentive_plan = validated_data.get('incentive_plan', instance.incentive_plan) + instance.heavy_value = validated_data.get('heavy_value', instance.heavy_value) + instance.light_value = validated_data.get('light_value', instance.light_value) + instance.save() + + return instance + + +class QuotaBrokerValueSerializer(serializers.ModelSerializer): # noqa + class Meta: + model = product_models.QuotaBrokerValue + fields = '__all__' + + def update(self, instance, validated_data): + """ Custom Update """ + + instance.quota = validated_data.get('quota', instance.quota) + instance.broker = validated_data.get('broker', instance.broker) + instance.value = validated_data.get('value', instance.value) + instance.save() + + return instance + + +class QuotaLiveStockAllocationSerializer(serializers.ModelSerializer): + class Meta: + model = product_models.QuotaLivestockAllocation + fields = '__all__' + extra_kwargs = { + 'livestock_group': { + 'required': False + }, + 'livestock_type': { + 'required': False + }, + 'livestock_subtype': { + 'required': False + } + } + + def update(self, instance, validated_data): + """ Custom Update """ + + instance.quota = validated_data.get('quota', instance.quota) + instance.livestock_group = validated_data.get('livestock_group', instance.livestock_group) + instance.livestock_type = validated_data.get('livestock_type', instance.livestock_type) + instance.livestock_subtype = validated_data.get('livestock_subtype', instance.livestock_subtype) + instance.save() + + return instance + + +class QuotaLiveStockAgeLimitationSerializer(serializers.ModelSerializer): + class Meta: + model = product_models.QuotaLiveStockAgeLimitation + fields = '__all__' + + def update(self, instance, validated_data): + """ Custom Update """ + + instance.quota = validated_data.get('quota', instance.quota) + instance.livestock_type = validated_data.get('livestock_type', instance.livestock_type) + instance.livestock_subtype = validated_data.get('livestock_subtype', instance.livestock_subtype) + instance.age_month = validated_data.get('age_month', instance.age_month) + instance.save() + + return instance diff --git a/apps/product/web/api/v1/urls.py b/apps/product/web/api/v1/urls.py index f2e5251..d9f2dcc 100644 --- a/apps/product/web/api/v1/urls.py +++ b/apps/product/web/api/v1/urls.py @@ -1,16 +1,20 @@ -from apps.product.web.api.v1.viewsets import product_api as api_views, quota_distribution_api as distribution_apis +from apps.product.web.api.v1.viewsets import ( + product_api, + quota_distribution_api as distribution_apis, + quota_api +) from rest_framework.routers import DefaultRouter from django.urls import path, include router = DefaultRouter() -router.register(r'product', api_views.ProductViewSet, basename='product') -router.register(r'category', api_views.ProductCategoryViewSet, basename='category') -router.register(r'attribute', api_views.AttributeViewSet, basename='attribute') -router.register(r'attribute_value', api_views.AttributeValueViewSet, basename='attribute_value') -router.register(r'broker', api_views.BrokerViewSet, basename='broker') -router.register(r'sale_unit', api_views.SaleUnitViewSet, basename='sale_unit') -router.register(r'incentive_plan', api_views.IncentivePlanViewSet, basename='incentive_plan') -router.register(r'quota', api_views.QuotaViewSet, basename='quota') +router.register(r'product', product_api.ProductViewSet, basename='product') +router.register(r'category', product_api.ProductCategoryViewSet, basename='category') +router.register(r'attribute', product_api.AttributeViewSet, basename='attribute') +router.register(r'attribute_value', product_api.AttributeValueViewSet, basename='attribute_value') +router.register(r'broker', product_api.BrokerViewSet, basename='broker') +router.register(r'sale_unit', product_api.SaleUnitViewSet, basename='sale_unit') +router.register(r'incentive_plan', product_api.IncentivePlanViewSet, basename='incentive_plan') +router.register(r'quota', quota_api.QuotaViewSet, basename='quota') router.register(r'quota_distribution', distribution_apis.QuotaDistributionViewSet, basename='quota_distribution') urlpatterns = [ diff --git a/apps/product/web/api/v1/viewsets/product_api.py b/apps/product/web/api/v1/viewsets/product_api.py index b1e420f..235b0f7 100644 --- a/apps/product/web/api/v1/viewsets/product_api.py +++ b/apps/product/web/api/v1/viewsets/product_api.py @@ -1,15 +1,10 @@ import datetime - from apps.product.web.api.v1.serializers import product_serializers as product_serializers -from apps.product.exceptions import QuotaExpiredTimeException -from apps.product.services import get_products_in_warehouse -from common.helpers import get_organization_by_user from rest_framework.exceptions import APIException from apps.product import models as product_models from rest_framework.response import Response from rest_framework.decorators import action -from common.tools import CustomOperations -from rest_framework import viewsets +from rest_framework import viewsets, filters from rest_framework import status from django.db import transaction from django.db.models import Q @@ -271,6 +266,8 @@ class IncentivePlanViewSet(viewsets.ModelViewSet): # noqa queryset = product_models.IncentivePlan.objects.all() serializer_class = product_serializers.IncentivePlanSerializer + filter_backends = [filters.SearchFilter] + search_fields = ['plan_type', 'name'] @transaction.atomic def create(self, request, *args, **kwargs): @@ -313,7 +310,6 @@ class IncentivePlanViewSet(viewsets.ModelViewSet): # noqa Q(is_time_unlimited=False) | Q(start_date_limit__lte=today, end_date_limit__gte=today) ) - page = self.paginate_queryset(incentive_plans) if page is not None: serializer = self.get_serializer(page, many=True) @@ -352,511 +348,3 @@ class IncentivePlanViewSet(viewsets.ModelViewSet): # noqa return Response(status=status.HTTP_200_OK) except APIException as e: return Response(e, status=status.HTTP_204_NO_CONTENT) - - -class QuotaViewSet(viewsets.ModelViewSet): # noqa - """ apis for product quota """ - - queryset = product_models.Quota.objects.all() - serializer_class = product_serializers.QuotaSerializer - - @transaction.atomic - def create(self, request, *args, **kwargs): - """ custom create quota """ - - # get user relations data like organization - user_relation = request.user.user_relation.all().first() - - # add user relation to data - request.data['registerer_organization'] = user_relation.organization.id - - # create quota - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - quota = serializer.save() - quota.remaining_weight = quota.quota_weight - - # create incentive plan - plans_list = [] - if 'incentive_plan_data' in request.data.keys(): - for plan in request.data['incentive_plan_data']: - plan.update({'quota': quota.id}) - incentive_plan = CustomOperations().custom_create( - request=request, - view=QuotaIncentiveAssignmentViewSet(), - data_key='incentive_plan_data', - data=plan - ) - plans_list.append(incentive_plan) - - # create product price attributes for quota - attributes_value_list = [] - if 'price_attributes_data' in request.data.keys(): - for attr in request.data['price_attributes_data']: - attr.update({'quota': quota.id}) - attributes = CustomOperations().custom_create( - request=request, - view=AttributeValueViewSet(), - data=attr - ) - attributes_value_list.append(attributes) - # create product broker values for quota - broker_data_list = [] - if 'broker_data' in request.data.keys(): - for broker in request.data['broker_data']: - broker.update({'quota': quota.id}) - broker_value = CustomOperations().custom_create( - request=request, - view=QuotaBrokerValueViewSet(), - data=broker - ) - broker_data_list.append(broker_value) - - # create livestock allocations to quota - allocations_list = [] - if 'livestock_allocation_data' in request.data.keys(): - for ls_alloc in request.data['livestock_allocation_data']: - ls_alloc.update({'quota': quota.id}) - allocations = CustomOperations().custom_create( - request=request, - view=QuotaLiveStockAllocationViewSet(), - data=ls_alloc - ) - allocations_list.append(allocations) - # create livestock age limits for quota - livestock_age_limits = [] - if 'livestock_age_limitations' in request.data.keys(): - for age_limit in request.data['livestock_age_limitations']: - age_limit.update({'quota': quota.id}) - age_limit_creation_object = CustomOperations().custom_create( - request=request, - view=QuotaLiveStockAgeLimitation(), - data=age_limit - ) - livestock_age_limits.append(age_limit_creation_object) - - data = { - 'quota': serializer.data, - 'incentive_plan': plans_list, # noqa - 'attribute_values': attributes_value_list, - 'broker_values': broker_data_list, - 'live_stock_allocations': allocations_list, - 'livestock_age_limitations': livestock_age_limits - } - - # call save method to generate id & calculate quota final price - quota.save(calculate_final_price=True) - return Response(data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN) - - @transaction.atomic - def update(self, request, pk=None, *args, **kwargs): - # get user relations data like organization - user_relation = request.user.user_relation.all().first() - - # add user relation to data - request.data['registerer_organization'] = user_relation.organization.id - - # create quota - serializer = self.serializer_class(data=request.data, instance=self.get_object(), partial=True) - if serializer.is_valid(): - quota = serializer.save() - - # create incentive plan - plans_list = [] - if 'incentive_plan_data' in request.data.keys(): - for plan in request.data['incentive_plan_data']: - plan.update({'quota': quota.id}) - incentive_plan = CustomOperations().custom_update( - request=request, - view=QuotaIncentiveAssignmentViewSet(), - data_key='incentive_plan_data', - obj_id=plan['id'], - data=plan - ) - plans_list.append(incentive_plan) - - # create product price attributes for quota - attributes_value_list = [] # noqa - if 'price_attributes_data' in request.data.keys(): - for attr in request.data['price_attributes_data']: - attr.update({'quota': quota.id}) - attributes = CustomOperations().custom_update( - request=request, - view=AttributeValueViewSet(), - obj_id=attr['id'], - data=attr - ) - attributes_value_list.append(attributes) - # create product broker values for quota - broker_data_list = [] - if 'broker_data' in request.data.keys(): - for broker in request.data['broker_data']: - broker.update({'quota': quota.id}) - broker_value = CustomOperations().custom_update( - request=request, - view=QuotaBrokerValueViewSet(), - obj_id=broker['id'], - data=broker - ) - broker_data_list.append(broker_value) - - # create livestock allocations to quota - allocations_list = [] - if 'livestock_allocation_data' in request.data.keys(): - for ls_alloc in request.data['livestock_allocation_data']: - ls_alloc.update({'quota': quota.id}) - allocations = CustomOperations().custom_update( - request=request, - view=QuotaLiveStockAllocationViewSet(), - obj_id=ls_alloc['id'], - data=ls_alloc - ) - allocations_list.append(allocations) - # create livestock age limits for quota - livestock_age_limits = [] - if 'livestock_age_limitations' in request.data.keys(): - for age_limit in request.data['livestock_age_limitations']: - age_limit.update({'quota': quota.id}) - age_limit_creation_object = CustomOperations().custom_update( - request=request, - view=QuotaLiveStockAgeLimitation(), - obj_id=age_limit['id'], - data=age_limit - ) - livestock_age_limits.append(age_limit_creation_object) - - data = { - 'quota': serializer.data, - 'incentive_plan': plans_list, # noqa - 'attribute_values': attributes_value_list, - 'broker_values': broker_data_list, - 'live_stock_allocations': allocations_list, - 'livestock_age_limitations': livestock_age_limits - } - - # call save method to generate id & calculate quota final price - quota.save(calculate_final_price=True) - return Response(data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN) - - @action( - methods=['patch'], - detail=True, - url_path='close', - url_name='close', - name='close' - ) - @transaction.atomic - def close(self, request, pk=None): - """ to close quota """ - quota = self.get_object() - - # check quota expired time - if not quota.is_in_valid_time(): - raise QuotaExpiredTimeException() - - if quota.is_closed: - raise APIException("این سهمیه قبلا بسته شده است", status.HTTP_400_BAD_REQUEST) # noqa - - quota.is_closed = True - quota.closed_at = datetime.now() - quota.save() - - return Response(status.HTTP_200_OK) - - @action( - methods=['get'], - detail=False, - url_name='list_for_assigner', - url_path='list_for_assigner', - name='list_for_assigner' - ) - def quotas_list_for_assigner(self, request): - """ list of quotas for creator """ - - assigner = product_models.UserRelations.objects.filter(user=request.user).first() - serializers = self.serializer_class( - self.queryset.filter(registerer_organization=assigner.organization), - many=True - ).data - return Response(serializers.data, status=status.HTTP_200_OK) - - @action( - methods=['get'], - detail=False, - url_name='list_for_assigned', - url_path='list_for_assigned', - name='list_for_assigned' - ) - def quotas_list_for_assigned(self, request): - """ list of quotas for assigned organizations """ - - assigned = product_models.UserRelations.objects.filter(user=request.user).first() - serializer = self.serializer_class( - self.queryset.filter(assigned_organizations=assigned.organization), - many=True - ) - - return Response(serializer.data, status=status.HTTP_200_OK) - - @action( - methods=['get'], - detail=False, - url_path='quotas_statistics', - url_name='quotas_statistics', - name='quotas_statistics' - ) - @transaction.atomic - def quotas_statistics_by_product(self, request): - """ quota statistics of each product in organization warehouse """ - - product_data = [] - - organization = get_organization_by_user(request.user) - products = get_products_in_warehouse(organization.id) - - for product in products: - product_data.append(product.quota_information()) - - return Response(product_data, status=status.HTTP_200_OK) - - @action( - methods=['get'], - detail=True, - url_path='quotas_information', - url_name='quotas_information', - name='quotas_information' - ) - @transaction.atomic - def quotas_information_by_product(self, request, pk=None): - """ get quotas information of a product """ - - quotas = self.queryset.select_related('product').filter( - product_id=pk, is_closed=False - ) - - try: - quota_serializer = self.serializer_class(quotas, many=True).data - return Response(quota_serializer, status=status.HTTP_200_OK) - except APIException as e: - raise APIException(detail="data error", code=400) - - @action( - methods=['get'], - detail=False, - url_path='quotas_info_by_org', - url_name='quotas_info_by_org', - name='quotas_info_by_org' - ) - def quotas_information_by_organization(self, request): - """ get quotas information of an organization """ - - quotas = self.queryset.filter( - Q(assigned_organizations=get_organization_by_user(request.user)) | - Q(registerer_organization=get_organization_by_user(request.user)) - ) - - serializer = self.serializer_class(quotas, many=True).data - return Response(serializer, status=status.HTTP_200_OK) - - @action( - methods=['get'], - detail=False, - url_path='closed_quotas', - url_name='closed_quotas', - name='closed_quotas' - ) - def closed_quotas(self, request): - """ get list of close quotas for organization """ - - quotas = quotas = self.queryset.filter( - (Q(assigned_organizations=get_organization_by_user(request.user)) | - Q(registerer_organization=get_organization_by_user(request.user))) & - Q(is_closed=True) - ) - - serialize = self.serializer_class(quotas, many=True).data - - return Response(serialize, status=status.HTTP_200_OK) - - @action( - methods=['put'], - detail=True, - url_path='trash', - url_name='trash', - name='trash', - ) - @transaction.atomic - def trash(self, request, pk=None): - """ Sent quota to trash """ - try: - trash(self.queryset, pk) - except APIException as e: - return Response(e, status.HTTP_204_NO_CONTENT) - - @action( - methods=['post'], - detail=True, - url_name='delete', - url_path='delete', - name='delete' - ) - @transaction.atomic - def delete(self, request, pk=None): - """ Full delete of quota object """ - try: - delete(self.queryset, pk) - return Response(status=status.HTTP_200_OK) - except APIException as e: - return Response(e, status=status.HTTP_204_NO_CONTENT) - - -class QuotaIncentiveAssignmentViewSet(viewsets.ModelViewSet): # noqa - """ apis for incentive assignment """ - - queryset = product_models.QuotaIncentiveAssignment.objects.all() - serializer_class = product_serializers.QuotaIncentiveAssignmentSerializer - - @action( - methods=['put'], - detail=True, - url_path='trash', - url_name='trash', - name='trash', - ) - @transaction.atomic - def trash(self, request, pk=None): - """ Sent quota incentive assignment to trash """ - try: - trash(self.queryset, pk) - except APIException as e: - return Response(e, status.HTTP_204_NO_CONTENT) - - @action( - methods=['post'], - detail=True, - url_name='delete', - url_path='delete', - name='delete' - ) - @transaction.atomic - def delete(self, request, pk=None): - """ Full delete of quota incentive assignment object """ - try: - delete(self.queryset, pk) - return Response(status=status.HTTP_200_OK) - except APIException as e: - return Response(e, status=status.HTTP_204_NO_CONTENT) - - -class QuotaBrokerValueViewSet(viewsets.ModelViewSet): # noqa - """ apis for quota broker value """ - - queryset = product_models.QuotaBrokerValue.objects.all() - serializer_class = product_serializers.QuotaBrokerValueSerializer - - @action( - methods=['put'], - detail=True, - url_path='trash', - url_name='trash', - name='trash', - ) - @transaction.atomic - def trash(self, request, pk=None): - """ Sent quota broker value to trash """ - try: - trash(self.queryset, pk) - except APIException as e: - return Response(e, status.HTTP_204_NO_CONTENT) - - @action( - methods=['post'], - detail=True, - url_name='delete', - url_path='delete', - name='delete' - ) - @transaction.atomic - def delete(self, request, pk=None): - """ Full delete of quota broker value object """ - try: - delete(self.queryset, pk) - return Response(status=status.HTTP_200_OK) - except APIException as e: - return Response(e, status=status.HTTP_204_NO_CONTENT) - - -class QuotaLiveStockAllocationViewSet(viewsets.ModelViewSet): - """ apis for quota livestock allocation """ - - queryset = product_models.QuotaLivestockAllocation.objects.all() - serializer_class = product_serializers.QuotaLiveStockAllocationSerializer - - @action( - methods=['put'], - detail=True, - url_path='trash', - url_name='trash', - name='trash', - ) - @transaction.atomic - def trash(self, request, pk=None): - """ Sent quota livestock allocation to trash """ - try: - trash(self.queryset, pk) - except APIException as e: - return Response(e, status.HTTP_204_NO_CONTENT) - - @action( - methods=['post'], - detail=True, - url_name='delete', - url_path='delete', - name='delete' - ) - @transaction.atomic - def delete(self, request, pk=None): - """ Full delete of quota livestock allocation object """ - try: - delete(self.queryset, pk) - return Response(status=status.HTTP_200_OK) - except APIException as e: - return Response(e, status=status.HTTP_204_NO_CONTENT) - - -class QuotaLiveStockAgeLimitation(viewsets.ModelViewSet): - queryset = product_models.QuotaLiveStockAgeLimitation.objects.all() # noqa - serializer_class = product_serializers.QuotaLiveStockAgeLimitationSerializer - - @action( - methods=['put'], - detail=True, - url_path='trash', - url_name='trash', - name='trash', - ) - @transaction.atomic - def trash(self, request, pk=None): - """ Sent quota livestock age limitation to trash """ - try: - trash(self.queryset, pk) - except APIException as e: - return Response(e, status.HTTP_204_NO_CONTENT) - - @action( - methods=['post'], - detail=True, - url_name='delete', - url_path='delete', - name='delete' - ) - @transaction.atomic - def delete(self, request, pk=None): - """ Full delete of quota livestock age limitation object """ - try: - delete(self.queryset, pk) - return Response(status=status.HTTP_200_OK) - except APIException as e: - return Response(e, status=status.HTTP_204_NO_CONTENT) diff --git a/apps/product/web/api/v1/viewsets/quota_api.py b/apps/product/web/api/v1/viewsets/quota_api.py new file mode 100644 index 0000000..0ea328a --- /dev/null +++ b/apps/product/web/api/v1/viewsets/quota_api.py @@ -0,0 +1,586 @@ +from apps.product.web.api.v1.serializers import product_serializers as product_serializers +from apps.product.web.api.v1.serializers import quota_serializers +from apps.product.exceptions import QuotaExpiredTimeException +from apps.product.services import get_products_in_warehouse +from apps.product.web.api.v1.viewsets import product_api +from common.helpers import get_organization_by_user +from rest_framework.exceptions import APIException +from apps.product import models as product_models +from rest_framework.response import Response +from rest_framework.decorators import action +from common.tools import CustomOperations +from rest_framework import viewsets +from rest_framework import status +from django.db import transaction +from django.db.models import Q +from datetime import datetime + + +def trash(queryset, pk): # noqa + """ sent object to trash """ + obj = queryset.get(id=pk) + obj.trash = True + obj.save() + + +def delete(queryset, pk): + """ full delete object """ + obj = queryset.get(id=pk) + obj.delete() + + +class QuotaViewSet(viewsets.ModelViewSet): # noqa + """ apis for product quota """ + + queryset = product_models.Quota.objects.all() + serializer_class = quota_serializers.QuotaSerializer + + @transaction.atomic + def create(self, request, *args, **kwargs): + """ custom create quota """ + + # get user relations data like organization + user_relation = request.user.user_relation.all().first() + + # add user relation to data + request.data['registerer_organization'] = user_relation.organization.id + + # create quota + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + quota = serializer.save() + quota.remaining_weight = quota.quota_weight + + # create incentive plan + plans_list = [] + if 'incentive_plan_data' in request.data.keys(): + for plan in request.data['incentive_plan_data']: + plan.update({'quota': quota.id}) + incentive_plan = CustomOperations().custom_create( + request=request, + view=QuotaIncentiveAssignmentViewSet(), + data_key='incentive_plan_data', + data=plan + ) + plans_list.append(incentive_plan) + + # create product price attributes for quota + attributes_value_list = [] + if 'price_attributes_data' in request.data.keys(): + for attr in request.data['price_attributes_data']: + attr.update({'quota': quota.id}) + attributes = CustomOperations().custom_create( + request=request, + view=product_api.AttributeValueViewSet(), + data=attr + ) + attributes_value_list.append(attributes) + # create product broker values for quota + broker_data_list = [] + if 'broker_data' in request.data.keys(): + for broker in request.data['broker_data']: + broker.update({'quota': quota.id}) + broker_value = CustomOperations().custom_create( + request=request, + view=QuotaBrokerValueViewSet(), + data=broker + ) + broker_data_list.append(broker_value) + + # create livestock allocations to quota + allocations_list = [] + if 'livestock_allocation_data' in request.data.keys(): + for ls_alloc in request.data['livestock_allocation_data']: + ls_alloc.update({'quota': quota.id}) + allocations = CustomOperations().custom_create( + request=request, + view=QuotaLiveStockAllocationViewSet(), + data=ls_alloc + ) + allocations_list.append(allocations) + # create livestock age limits for quota + livestock_age_limits = [] + if 'livestock_age_limitations' in request.data.keys(): + for age_limit in request.data['livestock_age_limitations']: + age_limit.update({'quota': quota.id}) + age_limit_creation_object = CustomOperations().custom_create( + request=request, + view=QuotaLiveStockAgeLimitation(), + data=age_limit + ) + livestock_age_limits.append(age_limit_creation_object) + + data = { + 'quota': serializer.data, + 'incentive_plan': plans_list, # noqa + 'attribute_values': attributes_value_list, + 'broker_values': broker_data_list, + 'live_stock_allocations': allocations_list, + 'livestock_age_limitations': livestock_age_limits + } + + # call save method to generate id & calculate quota final price + quota.save(calculate_final_price=True) + return Response(data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN) + + @transaction.atomic + def update(self, request, pk=None, *args, **kwargs): + # get user relations data like organization + user_relation = request.user.user_relation.all().first() + + # add user relation to data + request.data['registerer_organization'] = user_relation.organization.id + + # create quota + serializer = self.serializer_class(data=request.data, instance=self.get_object(), partial=True) + if serializer.is_valid(): + quota = serializer.save() + + # create incentive plan + plans_list = [] + if 'incentive_plan_data' in request.data.keys(): + for plan in request.data['incentive_plan_data']: + plan.update({'quota': quota.id}) + incentive_plan = CustomOperations().custom_update( + request=request, + view=QuotaIncentiveAssignmentViewSet(), + data_key='incentive_plan_data', + obj_id=plan['id'], + data=plan + ) + plans_list.append(incentive_plan) + + # create product price attributes for quota + attributes_value_list = [] # noqa + if 'price_attributes_data' in request.data.keys(): + for attr in request.data['price_attributes_data']: + attr.update({'quota': quota.id}) + attributes = CustomOperations().custom_update( + request=request, + view=product_api.AttributeValueViewSet(), + obj_id=attr['id'], + data=attr + ) + attributes_value_list.append(attributes) + # create product broker values for quota + broker_data_list = [] + if 'broker_data' in request.data.keys(): + for broker in request.data['broker_data']: + broker.update({'quota': quota.id}) + broker_value = CustomOperations().custom_update( + request=request, + view=QuotaBrokerValueViewSet(), + obj_id=broker['id'], + data=broker + ) + broker_data_list.append(broker_value) + + # create livestock allocations to quota + allocations_list = [] + if 'livestock_allocation_data' in request.data.keys(): + for ls_alloc in request.data['livestock_allocation_data']: + ls_alloc.update({'quota': quota.id}) + allocations = CustomOperations().custom_update( + request=request, + view=QuotaLiveStockAllocationViewSet(), + obj_id=ls_alloc['id'], + data=ls_alloc + ) + allocations_list.append(allocations) + # create livestock age limits for quota + livestock_age_limits = [] + if 'livestock_age_limitations' in request.data.keys(): + for age_limit in request.data['livestock_age_limitations']: + age_limit.update({'quota': quota.id}) + age_limit_creation_object = CustomOperations().custom_update( + request=request, + view=QuotaLiveStockAgeLimitation(), + obj_id=age_limit['id'], + data=age_limit + ) + livestock_age_limits.append(age_limit_creation_object) + + data = { + 'quota': serializer.data, + 'incentive_plan': plans_list, # noqa + 'attribute_values': attributes_value_list, + 'broker_values': broker_data_list, + 'live_stock_allocations': allocations_list, + 'livestock_age_limitations': livestock_age_limits + } + + # call save method to generate id & calculate quota final price + quota.save(calculate_final_price=True) + return Response(data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN) + + @action( + methods=['patch'], + detail=True, + url_path='close', + url_name='close', + name='close' + ) + @transaction.atomic + def close(self, request, pk=None): + """ to close quota """ + quota = self.get_object() + + # check quota expired time + if not quota.is_in_valid_time(): + raise QuotaExpiredTimeException() + + if quota.is_closed: + raise APIException("این سهمیه قبلا بسته شده است", status.HTTP_400_BAD_REQUEST) # noqa + + quota.is_closed = True + quota.closed_at = datetime.now() + quota.save() + + return Response(status.HTTP_200_OK) + + @action( + methods=['get'], + detail=False, + url_path='active_quotas', + url_name='active_quotas', + name='active_quotas' + ) + @transaction.atomic + def active_quotas(self, request): + """ list of organization active quotas """ + + organization = get_organization_by_user(request.user) + + # paginate queryset + page = self.paginate_queryset( + self.queryset.filter( + Q(assigned_organizations=organization) | + Q(registerer_organization=organization), + Q(is_closed=False) + ) + ) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + @action( + methods=['get'], + detail=False, + url_path='closed_quotas', + url_name='closed_quotas', + name='closed_quotas' + ) + @transaction.atomic + def closed_quotas(self, request): + """ list of organization closed quotas """ + + organization = get_organization_by_user(request.user) + # paginate queryset + page = self.paginate_queryset( + self.queryset.filter( + Q(assigned_organizations=organization) | + Q(registerer_organization=organization), + Q(is_closed=True) + ) + ) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + @action( + methods=['get'], + detail=False, + url_name='list_for_assigner', + url_path='list_for_assigner', + name='list_for_assigner' + ) + def quotas_list_for_assigner(self, request): + """ list of quotas for creator """ + + assigner = product_models.UserRelations.objects.filter(user=request.user).first() + serializers = self.serializer_class( + self.queryset.filter(registerer_organization=assigner.organization), + many=True + ).data + return Response(serializers.data, status=status.HTTP_200_OK) + + @action( + methods=['get'], + detail=False, + url_name='list_for_assigned', + url_path='list_for_assigned', + name='list_for_assigned' + ) + def quotas_list_for_assigned(self, request): + """ list of quotas for assigned organizations """ + + assigned = product_models.UserRelations.objects.filter(user=request.user).first() + serializer = self.serializer_class( + self.queryset.filter(assigned_organizations=assigned.organization), + many=True + ) + + return Response(serializer.data, status=status.HTTP_200_OK) + + @action( + methods=['get'], + detail=False, + url_path='quotas_statistics', + url_name='quotas_statistics', + name='quotas_statistics' + ) + @transaction.atomic + def quotas_statistics_by_product(self, request): + """ quota statistics of each product in organization warehouse """ + + product_data = [] + + organization = get_organization_by_user(request.user) + products = get_products_in_warehouse(organization.id) + + for product in products: + product_data.append(product.quota_information()) + + return Response(product_data, status=status.HTTP_200_OK) + + @action( + methods=['get'], + detail=True, + url_path='quotas_information', + url_name='quotas_information', + name='quotas_information' + ) + @transaction.atomic + def quotas_information_by_product(self, request, pk=None): + """ get quotas information of a product """ + + quotas = self.queryset.select_related('product').filter( + product_id=pk, is_closed=False + ) + + try: + quota_serializer = self.serializer_class(quotas, many=True).data + return Response(quota_serializer, status=status.HTTP_200_OK) + except APIException as e: + raise APIException(detail="data error", code=400) + + @action( + methods=['get'], + detail=False, + url_path='quotas_info_by_org', + url_name='quotas_info_by_org', + name='quotas_info_by_org' + ) + def quotas_information_by_organization(self, request): + """ get quotas information of an organization """ + + quotas = self.queryset.filter( + Q(assigned_organizations=get_organization_by_user(request.user)) | + Q(registerer_organization=get_organization_by_user(request.user)) + ) + + serializer = self.serializer_class(quotas, many=True).data + return Response(serializer, status=status.HTTP_200_OK) + + @action( + methods=['get'], + detail=False, + url_path='closed_quotas', + url_name='closed_quotas', + name='closed_quotas' + ) + def closed_quotas(self, request): + """ get list of close quotas for organization """ + + quotas = quotas = self.queryset.filter( + (Q(assigned_organizations=get_organization_by_user(request.user)) | + Q(registerer_organization=get_organization_by_user(request.user))) & + Q(is_closed=True) + ) + + serialize = self.serializer_class(quotas, many=True).data + + return Response(serialize, status=status.HTTP_200_OK) + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent quota to trash """ + try: + trash(self.queryset, pk) + except APIException as e: + return Response(e, status.HTTP_204_NO_CONTENT) + + @action( + methods=['post'], + detail=True, + url_name='delete', + url_path='delete', + name='delete' + ) + @transaction.atomic + def delete(self, request, pk=None): + """ Full delete of quota object """ + try: + delete(self.queryset, pk) + return Response(status=status.HTTP_200_OK) + except APIException as e: + return Response(e, status=status.HTTP_204_NO_CONTENT) + + +class QuotaIncentiveAssignmentViewSet(viewsets.ModelViewSet): # noqa + """ apis for incentive assignment """ + + queryset = product_models.QuotaIncentiveAssignment.objects.all() + serializer_class = quota_serializers.QuotaIncentiveAssignmentSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent quota incentive assignment to trash """ + try: + trash(self.queryset, pk) + except APIException as e: + return Response(e, status.HTTP_204_NO_CONTENT) + + @action( + methods=['post'], + detail=True, + url_name='delete', + url_path='delete', + name='delete' + ) + @transaction.atomic + def delete(self, request, pk=None): + """ Full delete of quota incentive assignment object """ + try: + delete(self.queryset, pk) + return Response(status=status.HTTP_200_OK) + except APIException as e: + return Response(e, status=status.HTTP_204_NO_CONTENT) + + +class QuotaBrokerValueViewSet(viewsets.ModelViewSet): # noqa + """ apis for quota broker value """ + + queryset = product_models.QuotaBrokerValue.objects.all() + serializer_class = quota_serializers.QuotaBrokerValueSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent quota broker value to trash """ + try: + trash(self.queryset, pk) + except APIException as e: + return Response(e, status.HTTP_204_NO_CONTENT) + + @action( + methods=['post'], + detail=True, + url_name='delete', + url_path='delete', + name='delete' + ) + @transaction.atomic + def delete(self, request, pk=None): + """ Full delete of quota broker value object """ + try: + delete(self.queryset, pk) + return Response(status=status.HTTP_200_OK) + except APIException as e: + return Response(e, status=status.HTTP_204_NO_CONTENT) + + +class QuotaLiveStockAllocationViewSet(viewsets.ModelViewSet): + """ apis for quota livestock allocation """ + + queryset = product_models.QuotaLivestockAllocation.objects.all() + serializer_class = quota_serializers.QuotaLiveStockAllocationSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent quota livestock allocation to trash """ + try: + trash(self.queryset, pk) + except APIException as e: + return Response(e, status.HTTP_204_NO_CONTENT) + + @action( + methods=['post'], + detail=True, + url_name='delete', + url_path='delete', + name='delete' + ) + @transaction.atomic + def delete(self, request, pk=None): + """ Full delete of quota livestock allocation object """ + try: + delete(self.queryset, pk) + return Response(status=status.HTTP_200_OK) + except APIException as e: + return Response(e, status=status.HTTP_204_NO_CONTENT) + + +class QuotaLiveStockAgeLimitation(viewsets.ModelViewSet): + queryset = product_models.QuotaLiveStockAgeLimitation.objects.all() # noqa + serializer_class = quota_serializers.QuotaLiveStockAgeLimitationSerializer + + @action( + methods=['put'], + detail=True, + url_path='trash', + url_name='trash', + name='trash', + ) + @transaction.atomic + def trash(self, request, pk=None): + """ Sent quota livestock age limitation to trash """ + try: + trash(self.queryset, pk) + except APIException as e: + return Response(e, status.HTTP_204_NO_CONTENT) + + @action( + methods=['post'], + detail=True, + url_name='delete', + url_path='delete', + name='delete' + ) + @transaction.atomic + def delete(self, request, pk=None): + """ Full delete of quota livestock age limitation object """ + try: + delete(self.queryset, pk) + return Response(status=status.HTTP_200_OK) + except APIException as e: + return Response(e, status=status.HTTP_204_NO_CONTENT)