From 552813edd59726452f9d975aa1bca71f67166e01 Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Tue, 25 Nov 2025 13:46:55 +0330 Subject: [PATCH] fix - edit quota -> distributed > weight * --- .../api/v1/serializers/quota_serializers.py | 13 +- apps/product/pos/api/v1/urls.py | 5 +- apps/product/pos/api/v1/viewsets/quota_api.py | 241 ++++++------------ .../api/v1/viewsets/quota_distribution_api.py | 2 +- .../api/v1/serializers/quota_serializers.py | 2 +- 5 files changed, 90 insertions(+), 173 deletions(-) diff --git a/apps/product/pos/api/v1/serializers/quota_serializers.py b/apps/product/pos/api/v1/serializers/quota_serializers.py index 92b8922..9c17407 100644 --- a/apps/product/pos/api/v1/serializers/quota_serializers.py +++ b/apps/product/pos/api/v1/serializers/quota_serializers.py @@ -1,9 +1,8 @@ -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 rest_framework import serializers + from apps.livestock.web.api.v1.serializers import LiveStockTypeSerializer from apps.product import models as product_models -from rest_framework import serializers +from apps.product.web.api.v1.serializers import product_serializers class QuotaSerializer(serializers.ModelSerializer): @@ -235,3 +234,9 @@ class QuotaLiveStockAgeLimitationSerializer(serializers.ModelSerializer): instance.save() return instance + + +class OrganizationQuotaStatsSerializer(serializers.ModelSerializer): + class Meta: + model = product_models.OrganizationQuotaStats + fields = '__all__' diff --git a/apps/product/pos/api/v1/urls.py b/apps/product/pos/api/v1/urls.py index 77912cc..fb64143 100644 --- a/apps/product/pos/api/v1/urls.py +++ b/apps/product/pos/api/v1/urls.py @@ -1,12 +1,15 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from .viewsets import product_api, quota_distribution_api + +from .viewsets import product_api, quota_distribution_api, quota_api router = DefaultRouter() router.register(r'product', product_api.ProductViewSet, basename='product') router.register(r'pos_free_products', product_api.POSFreeProductsViewSet, basename='pos_free_products') router.register(r'distributions', quota_distribution_api.QuotaDistributionViewSet, basename='distributions') +router.register(r'quotas_stat', quota_api.OrganizationQuotaStatsViewSet, basename='quotas_stat') + urlpatterns = [ path('v1/', include(router.urls)) ] diff --git a/apps/product/pos/api/v1/viewsets/quota_api.py b/apps/product/pos/api/v1/viewsets/quota_api.py index 191c147..291c462 100644 --- a/apps/product/pos/api/v1/viewsets/quota_api.py +++ b/apps/product/pos/api/v1/viewsets/quota_api.py @@ -1,20 +1,18 @@ -from apps.product.pos.api.v1.serializers import quota_distribution_serializers -from apps.product.web.api.v1.serializers import quota_serializers -from apps.product.exceptions import QuotaExpiredTimeException -from apps.core.mixins.search_mixin import DynamicSearchMixin -from apps.core.pagination import CustomPageNumberPagination -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 rest_framework import viewsets, filters -from common.tools import CustomOperations -from rest_framework import status from django.db import transaction from django.db.models import Q -from datetime import datetime +from rest_framework import status +from rest_framework import viewsets, filters +from rest_framework.decorators import action +from rest_framework.exceptions import APIException +from rest_framework.response import Response + +from apps.core.mixins.search_mixin import DynamicSearchMixin +from apps.core.pagination import CustomPageNumberPagination +from apps.herd.models import Rancher +from apps.pos_device.mixins.pos_device_mixin import POSDeviceMixin +from apps.product import models as product_models +from apps.product.models import OrganizationQuotaStats +from apps.product.pos.api.v1.serializers import quota_serializers def trash(queryset, pk): # noqa @@ -30,7 +28,7 @@ def delete(queryset, pk): obj.delete() -class QuotaViewSet(viewsets.ModelViewSet, DynamicSearchMixin): # noqa +class QuotaViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDeviceMixin): # noqa """ apis for product quota """ queryset = product_models.Quota.objects.all() @@ -46,156 +44,6 @@ class QuotaViewSet(viewsets.ModelViewSet, DynamicSearchMixin): # noqa "group", ] - @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 """ - - queryset = self.filter_query(self.queryset) # return by search param or all objects - - organization = get_organization_by_user(request.user) - - # paginate queryset - page = self.paginate_queryset( - queryset.filter( - Q(registerer_organization=organization), - Q(is_closed=False) - ).order_by('-modify_date') - ) - 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 """ - - queryset = self.filter_query(self.queryset) # return by search param or all objects - - organization = get_organization_by_user(request.user) - - # paginate queryset - page = self.paginate_queryset( - queryset.filter( - Q(registerer_organization=organization), - Q(is_closed=True) - ).order_by('-modify_date') - ) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - - @action( - methods=['get'], - detail=True, - url_path='distributions_by_quota', - url_name='distributions_by_quota', - name='distributions_by_quota' - ) - def get_distributions_by_quota(self, request, pk=None): - """ list of distributions by quota """ - - try: - quota = self.get_object() - queryset = self.filter_query( - quota.distributions_assigned.all().order_by('-modify_date') - ) # return by search param or all objects - - # paginate queryset - page = self.paginate_queryset( - queryset - ) - if page is not None: - serializer = quota_distribution_serializers.QuotaDistributionSerializer( - page, many=True - ) - return self.get_paginated_response(serializer.data) - except Exception as e: - raise APIException("none object", code=403) - - @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=['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 """ @@ -345,3 +193,64 @@ class QuotaLiveStockAgeLimitation(viewsets.ModelViewSet): return Response(status=status.HTTP_200_OK) except APIException as e: return Response(e, status=status.HTTP_204_NO_CONTENT) + + +class OrganizationQuotaStatsViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDeviceMixin): + queryset = OrganizationQuotaStats.objects.all() + serializer_class = quota_serializers.OrganizationQuotaStatsSerializer + filter_backends = [filters.SearchFilter] + search_fields = [ + "quota__registerer_organization__name", + "quota__quota_id", + "quota__product__name", + "quota__sale_type", + "quota__sale_unit__unit", + "quota__group", + ] + + @action( + methods=['get'], + detail=False, + url_name='rancher_quotas', + url_path='rancher_quotas', + name='rancher_quotas' + ) + def rancher_distributions(self, request): + """ list of quota distributions for rancher """ + + organization = self.get_device_organization() + device = self.get_pos_device() + rancher = Rancher.objects.filter(national_code=request.GET['national_code']) + + # get distributions with open quota + quotas = self.queryset.filter( + Q(organization=organization), + Q(quota__is_closed=False), + ( + Q(quota__pre_sale=True) | Q(quota__free_sale=True) | Q(inventory_received__gt=0) + ) + ).order_by('-create_date') + + # check quota distributions for rancher + # available_distributions = [ + # distribution for distribution in distributions if ( + # can_buy_from_inventory(rancher.first(), distribution=distribution) & rancher.exists() + # ) + # ] + + available_distributions = quotas + + # paginate & response + page = self.paginate_queryset(available_distributions) # noqa + if page is not None: # noqa + 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 + elif not available_distributions: + self.paginator.set_message("دامدار با کد ملی مد نظر سهمیه ایی ندارد") # noqa + return self.get_paginated_response(serializer.data) 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 f80bf40..31fbb75 100644 --- a/apps/product/pos/api/v1/viewsets/quota_distribution_api.py +++ b/apps/product/pos/api/v1/viewsets/quota_distribution_api.py @@ -107,7 +107,7 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDev # paginate & response page = self.paginate_queryset(available_distributions) # noqa - if page is not None: + if page is not None: # noqa serializer = self.get_serializer(page, many=True, context={ 'rancher': rancher.first(), 'device': device, diff --git a/apps/product/web/api/v1/serializers/quota_serializers.py b/apps/product/web/api/v1/serializers/quota_serializers.py index 649dbec..8e2b814 100644 --- a/apps/product/web/api/v1/serializers/quota_serializers.py +++ b/apps/product/web/api/v1/serializers/quota_serializers.py @@ -14,7 +14,7 @@ class QuotaSerializer(serializers.ModelSerializer): def validate(self, attrs): weight = attrs['quota_weight'] if self.instance: - if self.instance.quota_distributed < weight: + if self.instance.quota_distributed > weight: raise QuotaException( "وزن سهمیه نمیتواند کمتر از وزن توزیع شده باشد", # noqa status.HTTP_403_FORBIDDEN