From d0db6c96938ea721d25f09895a7996ae9876df41 Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Wed, 24 Sep 2025 11:00:48 +0330 Subject: [PATCH] create rancher incentive plan structure - add pos device main sheba --- apps/pos_device/services/services.py | 3 +- apps/product/exceptions.py | 10 +++++++ .../quota_distribution_serializers.py | 16 ++++++---- .../api/v1/viewsets/quota_distribution_api.py | 6 +++- apps/product/services/services.py | 30 +++++++++++++------ .../api/v1/serializers/product_serializers.py | 20 ++++++++++++- .../web/api/v1/viewsets/product_api.py | 12 ++++++++ 7 files changed, 80 insertions(+), 17 deletions(-) diff --git a/apps/pos_device/services/services.py b/apps/pos_device/services/services.py index 3cbff03..6788826 100644 --- a/apps/pos_device/services/services.py +++ b/apps/pos_device/services/services.py @@ -27,7 +27,8 @@ def pos_organizations_sharing_information( ).first().value if quota and item.broker else ( item.holders_share_amount.filter(quota_distribution=distribution).first().share_amount if item.holders_share_amount.filter(quota_distribution=distribution).exists() else None - ) + ), + "default_account": item.default } for item in stake_holders] return sharing_information_list diff --git a/apps/product/exceptions.py b/apps/product/exceptions.py index b898a50..607e687 100644 --- a/apps/product/exceptions.py +++ b/apps/product/exceptions.py @@ -53,3 +53,13 @@ class FreePOSProductUniqueCheck(APIException): status_code = status.HTTP_403_FORBIDDEN default_detail = "محصول مورد نظر برای این دستگاه از قبل ثبت شده است" # noqa default_code = 'error' + + +class RancherIncentivePlanExists(APIException): + """ + raise exception if rancher with same info + """ + + status_code = status.HTTP_403_FORBIDDEN + default_detail = "این دامدار برای این طرح با این نوع گونه دام قبلا اضافه شده است" # noqa + default_code = 'error' diff --git a/apps/product/pos/api/v1/serializers/quota_distribution_serializers.py b/apps/product/pos/api/v1/serializers/quota_distribution_serializers.py index dd95198..d8a3ce0 100644 --- a/apps/product/pos/api/v1/serializers/quota_distribution_serializers.py +++ b/apps/product/pos/api/v1/serializers/quota_distribution_serializers.py @@ -80,6 +80,11 @@ class QuotaDistributionSerializer(serializers.ModelSerializer): """ Custom output of serializer """ representation = super().to_representation(instance) + + organization = self.context['organization'] + rancher = self.context['rancher'] + device = self.context['device'] + if instance.quota: representation['quota'] = { 'quota_identity': instance.quota.quota_id, @@ -87,7 +92,7 @@ class QuotaDistributionSerializer(serializers.ModelSerializer): 'quota_livestock_allocations': quota_live_stock_allocation_info( instance.quota ), - 'quota_incentive_plans': quota_incentive_plans_info(instance.quota), + 'quota_incentive_plans': quota_incentive_plans_info(instance.quota, rancher), 'quota_sale_license': instance.quota.sale_license, 'has_sale_license': instance.quota.is_in_valid_time() } @@ -101,13 +106,14 @@ class QuotaDistributionSerializer(serializers.ModelSerializer): 'name': instance.quota.product.name, 'id': instance.quota.product.id, 'free_sale_for_all': sale_limitation if sale_limitation else False, - 'free_sale_for_this_rancher': self.context['rancher'].ignore_purchase_limit + 'free_sale_for_this_rancher': rancher.ignore_purchase_limit } representation['pricing'] = { # noqa + 'main_account_sheba': organization.bank_information.first().sheba, 'pricing_attributes': quota_attribute_value(instance.quota), 'sharing': pos_organizations_sharing_information( - self.context['device'], + device, instance.quota, distribution=instance ), @@ -127,12 +133,12 @@ class QuotaDistributionSerializer(serializers.ModelSerializer): if 'rancher' in self.context.keys(): # rancher herd & live stock statistics - livestock_counts_list, livestock_counts_dict = get_rancher_statistics(self.context['rancher']) + livestock_counts_list, livestock_counts_dict = get_rancher_statistics(rancher) representation['rancher_statistics'] = livestock_counts_list # rancher live stock statistics by quota distributions representation['rancher_quota_weight_statistics'] = rancher_quota_weight( - self.context['rancher'], distribution=instance + rancher, distribution=instance ) if instance.assigned_organization: diff --git a/apps/product/pos/api/v1/viewsets/quota_distribution_api.py b/apps/product/pos/api/v1/viewsets/quota_distribution_api.py index 1ee82b2..fcd8f98 100644 --- a/apps/product/pos/api/v1/viewsets/quota_distribution_api.py +++ b/apps/product/pos/api/v1/viewsets/quota_distribution_api.py @@ -106,7 +106,11 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDev # paginate & response page = self.paginate_queryset(available_distributions) # noqa if page is not None: - serializer = self.get_serializer(page, many=True, context={'rancher': rancher.first(), 'device': device}) + serializer = self.get_serializer(page, many=True, context={ + 'rancher': rancher.first(), + 'device': device, + 'organization': organization + }) # set custom message for paginator if not rancher: self.paginator.set_message("دامدار با کد ملی مد نظر یافت نشد") # noqa diff --git a/apps/product/services/services.py b/apps/product/services/services.py index 4e31981..afef698 100644 --- a/apps/product/services/services.py +++ b/apps/product/services/services.py @@ -1,5 +1,6 @@ from apps.product.models import Quota, QuotaLivestockAllocation from apps.warehouse.models import InventoryEntry +from apps.herd.models import Rancher import typing @@ -32,20 +33,31 @@ def quota_live_stock_allocation_info(quota: Quota) -> typing.Any: return allocations_list -def quota_incentive_plans_info(quota: Quota) -> typing.Any: +def quota_incentive_plans_info(quota: Quota, rancher: Rancher) -> typing.Any: """ information of quota incentive plans """ incentive_plans = quota.incentive_assignments.select_related("livestock_type", "incentive_plan") if incentive_plans: - incentive_plans_list = [{ - 'name': plan.incentive_plan.name, - 'heavy_value': plan.heavy_value, - 'light_value': plan.light_value, - 'livestock_type': plan.livestock_type.name if plan.livestock_type else "", - 'livestock_weight_type': plan.livestock_type.weight_type if plan.livestock_type else "", - 'quantity_kg': plan.quantity_kg - } for plan in incentive_plans] + incentive_plans_list = [] + for plan in incentive_plans: + rancher_plan = plan.incentive_plan.rancher_plans.select_related( + 'rancher', + 'livestock_type' + ).filter(rancher=rancher, livestock_type=plan.livestock_type) + + incentive_plans_data = { + 'name': plan.incentive_plan.name, + 'heavy_value': plan.heavy_value, + 'light_value': plan.light_value, + 'livestock_type': plan.livestock_type.name if plan.livestock_type else "", + 'livestock_weight_type': plan.livestock_type.weight_type if plan.livestock_type else "", + 'quantity_kg': plan.quantity_kg, + 'rancher_plan_statistic': { + f'{rancher_plan.first().livestock_type.en_name}': rancher_plan.first().allowed_quantity + } if rancher_plan else {} + } + incentive_plans_list.append(incentive_plans_data) return incentive_plans_list diff --git a/apps/product/web/api/v1/serializers/product_serializers.py b/apps/product/web/api/v1/serializers/product_serializers.py index 634b958..e17610a 100644 --- a/apps/product/web/api/v1/serializers/product_serializers.py +++ b/apps/product/web/api/v1/serializers/product_serializers.py @@ -2,9 +2,10 @@ from rest_framework import serializers from apps.product import models as product_models from apps.authorization.api.v1 import serializers as authorize_serializers from apps.authentication.api.v1.serializers.serializer import OrganizationSerializer, OrganizationTypeSerializer +from apps.product import exceptions -class ProductCategorySerializer(serializers.ModelSerializer): +class ProductCategorySerializer(serializers.ModelSerializer): # noqa class Meta: model = product_models.ProductCategory fields = '__all__' @@ -161,3 +162,20 @@ class IncentivePlanRancherSerializer(serializers.ModelSerializer): class Meta: model = product_models.IncentivePlanRancher fields = '__all__' + + def validate(self, attrs): + """ validate incentive plans of a rancher """ + + rancher_plans_data = self.context['request'].data['data'] + + # check if rancher with same plan & livestock exists + if not self.instance: + for plan in rancher_plans_data: + if product_models.IncentivePlanRancher.objects.filter( + plan_id=plan['plan'], + rancher_id=plan['rancher'], + livestock_type_id=plan['livestock_type'], + ).exists(): + raise exceptions.RancherIncentivePlanExists() + + return attrs diff --git a/apps/product/web/api/v1/viewsets/product_api.py b/apps/product/web/api/v1/viewsets/product_api.py index 811f574..e8577ed 100644 --- a/apps/product/web/api/v1/viewsets/product_api.py +++ b/apps/product/web/api/v1/viewsets/product_api.py @@ -446,3 +446,15 @@ class IncentivePlanViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearch class IncentivePlanRancherViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): queryset = product_models.IncentivePlanRancher.objects.all() serializer_class = product_serializers.IncentivePlanRancherSerializer + search_fields = [] + + @transaction.atomic + def create(self, request, *args, **kwargs): + """ create rancher incentive plans by livestock type count """ + + serializer = self.serializer_class( + data=request.data['data'], many=True, context={'request': request} + ) + if serializer.is_valid(raise_exception=True): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED)