From 18a7955e3a95b2b1b8c41d82d3fe3b926fce7d1c Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Sat, 6 Sep 2025 12:04:41 +0330 Subject: [PATCH] change list of rancher inventory entries to rancher distributions for one item be as sale in pos device --- apps/herd/services/services.py | 13 ++- .../quota_distribution_serializers.py | 52 +++++++++-- apps/product/pos/api/v1/urls.py | 3 +- .../api/v1/viewsets/quota_distribution_api.py | 88 ++++++++++++------- apps/warehouse/services/services.py | 45 +++++++--- 5 files changed, 151 insertions(+), 50 deletions(-) diff --git a/apps/herd/services/services.py b/apps/herd/services/services.py index 0965031..024d891 100644 --- a/apps/herd/services/services.py +++ b/apps/herd/services/services.py @@ -2,7 +2,7 @@ from decimal import Decimal from apps.herd.models import Rancher from apps.livestock.models import LiveStock from apps.warehouse.models import InventoryEntry -from apps.product.models import Quota +from apps.product.models import Quota, QuotaDistribution from django.db.models import Count, Q import typing @@ -26,10 +26,11 @@ def get_rancher_statistics(rancher: Rancher = None) -> typing.Any: return stats -def rancher_quota_weight(rancher, inventory_entry: InventoryEntry): +def rancher_quota_weight(rancher, inventory_entry: InventoryEntry = None, distribution: QuotaDistribution = None): """ :param rancher: Rancher instance :param inventory_entry: InventoryEntry instance + :param distribution: QuotaDistribution instance :return: dict {total, by_type} """ @@ -41,7 +42,13 @@ def rancher_quota_weight(rancher, inventory_entry: InventoryEntry): "اسب": "horse_count" } - quota: Quota = inventory_entry.distribution.quota + if inventory_entry: + quota: Quota = inventory_entry.distribution.quota + elif distribution: + quota: Quota = distribution.quota + else: + quota: Quota = Quota() + # list of quota live stock allocations allocations = list(quota.livestock_allocations.all().select_related('livestock_type')) # list of quota incentive plans 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 20d98e2..1925631 100644 --- a/apps/product/pos/api/v1/serializers/quota_distribution_serializers.py +++ b/apps/product/pos/api/v1/serializers/quota_distribution_serializers.py @@ -1,14 +1,17 @@ -from rest_framework import serializers -from apps.product import models as product_models +from apps.product.services.services import quota_live_stock_allocation_info, quota_incentive_plans_info, \ + quota_attribute_value +from apps.herd.services.services import get_rancher_statistics, rancher_quota_weight +from apps.pos_device.services.services import pos_organizations_sharing_information from rest_framework.exceptions import APIException -from apps.product.web.api.v1.serializers.quota_serializers import QuotaSerializer -from django.db import models +from apps.product import models as product_models +from rest_framework import serializers from apps.product.exceptions import ( QuotaWeightException, QuotaClosedException, QuotaExpiredTimeException, QuotaLimitByOrganizationException ) +from django.db import models class QuotaDistributionSerializer(serializers.ModelSerializer): @@ -77,7 +80,46 @@ class QuotaDistributionSerializer(serializers.ModelSerializer): representation = super().to_representation(instance) if instance.quota: - representation['quota'] = QuotaSerializer(instance.quota).data + representation['quota'] = { + 'quota_identity': instance.quota.quota_id, + 'quota_weight': instance.quota.quota_weight, + 'quota_livestock_allocations': quota_live_stock_allocation_info( + instance.quota + ), + 'quota_incentive_plans': quota_incentive_plans_info(instance.quota) + } + + representation['product'] = { + 'image': instance.quota.product.img, + 'name': instance.quota.product.name, + 'id': instance.quota.product.id, + } + + representation['pricing'] = { # noqa + 'pricing_attributes': quota_attribute_value(instance.quota), + 'sharing': pos_organizations_sharing_information(self.context['device']), + 'base_prices': [ + { + "text": "قیمت درب کارخانه", # noqa + "name": "base_price_factory", + "value": instance.quota.base_price_factory + }, + { + "text": "قیمت درب اتحادیه", # noqa + "name": "base_price_cooperative", + "value": instance.quota.base_price_cooperative + } + ] + } + + if 'rancher' in self.context.keys(): + # rancher herd & live stock statistics + representation['rancher_statistics'] = get_rancher_statistics(self.context['rancher']) + + # rancher live stock statistics by quota distributions + representation['rancher_quota_weight_statistics'] = rancher_quota_weight( + self.context['rancher'], distribution=instance + ) if instance.assigned_organization: representation['assigned_organization'] = { diff --git a/apps/product/pos/api/v1/urls.py b/apps/product/pos/api/v1/urls.py index a73fec2..77912cc 100644 --- a/apps/product/pos/api/v1/urls.py +++ b/apps/product/pos/api/v1/urls.py @@ -1,10 +1,11 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from .viewsets import product_api +from .viewsets import product_api, quota_distribution_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') urlpatterns = [ path('v1/', include(router.urls)) 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 276b72a..1ee82b2 100644 --- a/apps/product/pos/api/v1/viewsets/quota_distribution_api.py +++ b/apps/product/pos/api/v1/viewsets/quota_distribution_api.py @@ -1,12 +1,16 @@ from apps.product.pos.api.v1.serializers import quota_distribution_serializers as distribution_serializers +from apps.pos_device.mixins.pos_device_mixin import POSDeviceMixin from apps.core.mixins.search_mixin import DynamicSearchMixin from apps.core.pagination import CustomPageNumberPagination +from apps.warehouse.services.services import can_buy_from_inventory 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.permissions import AllowAny from rest_framework.response import Response from rest_framework.decorators import action from rest_framework import viewsets, filters +from apps.herd.models import Rancher from rest_framework import status from django.db import transaction from django.db.models import Q @@ -25,12 +29,12 @@ def delete(queryset, pk): obj.delete() -class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin): +class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDeviceMixin): """ quota distribution apis """ queryset = product_models.QuotaDistribution.objects.all() serializer_class = distribution_serializers.QuotaDistributionSerializer - filter_backends = [filters.SearchFilter] + permission_classes = [AllowAny] CustomPageNumberPagination.page_size = 5 search_fields = [ "assigner_organization__name", @@ -53,37 +57,61 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin): def my_distributions(self, request): """ list of my distributions """ - queryset = self.filter_query(self.queryset) # return by search param or all objects - organization = get_organization_by_user(request.user) + organization = self.get_device_organization() + device = self.get_pos_device() - query = self.request.query_params - if query.get('param') == 'assigned': - # paginate queryset - page = self.paginate_queryset( - queryset.filter( - Q(assigned_organization=organization) - ).order_by('-modify_date') + # get distributions with open quota + distributions = self.queryset.filter( + assigned_organization=organization, + quota__is_closed=False, + warehouse_entry__gt=0 + ).order_by('-create_date') + + queryset = self.filter_query(distributions) # return by search param or all objects + + # paginate & response + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True, context={'device': device}) + return self.get_paginated_response(serializer.data) + + @action( + methods=['get'], + detail=False, + url_name='rancher_distributions', + url_path='rancher_distributions', + name='rancher_distributions' + ) + 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 + distributions = self.queryset.filter( + assigned_organization=organization, + quota__is_closed=False, + warehouse_entry__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() ) + ] - elif query.get('param') == 'assigner': - # paginate queryset - page = self.paginate_queryset( - queryset.filter( - Q(assigner_organization=organization) - ).order_by('-modify_date') - ) - - elif query.get('param') == 'all': - # paginate queryset - page = self.paginate_queryset( - queryset.filter( - Q(assigner_organization=organization) | - Q(assigned_organization=organization) - ).order_by('-modify_date') - ) - - if page is not None: # noqa - serializer = self.get_serializer(page, many=True) # noqa + # 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}) + # 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) @action( diff --git a/apps/warehouse/services/services.py b/apps/warehouse/services/services.py index dfbb4d2..7a235fd 100644 --- a/apps/warehouse/services/services.py +++ b/apps/warehouse/services/services.py @@ -1,21 +1,31 @@ -from apps.warehouse.models import InventoryEntry, InventoryQuotaSaleTransaction from apps.herd.services.services import rancher_quota_weight, get_rancher_statistics +from apps.warehouse.models import InventoryEntry, InventoryQuotaSaleTransaction +from apps.product.models import QuotaDistribution from apps.core.models import SystemConfig from django.db.models import Sum -def get_total_sold(inventory_entry, rancher): +def get_total_sold(rancher, inventory_entry: InventoryEntry = None, distribution: QuotaDistribution = None): """ """ - return ( - InventoryQuotaSaleTransaction.objects.filter( - inventory_entry=inventory_entry, - rancher=rancher - ).aggregate(total=Sum('weight'))['total'] or 0 - ) + if inventory_entry: + return ( + InventoryQuotaSaleTransaction.objects.filter( + inventory_entry=inventory_entry, + rancher=rancher + ).aggregate(total=Sum('weight'))['total'] or 0 + ) + + elif distribution: + return ( + InventoryQuotaSaleTransaction.objects.filter( + inventory_entry=inventory_entry, + rancher=rancher + ).aggregate(total=Sum('weight'))['total'] or 0 + ) -def can_buy_from_inventory(rancher, inventory_entry: InventoryEntry): +def can_buy_from_inventory(rancher, inventory_entry: InventoryEntry = None, distribution: QuotaDistribution = None): """ """ if SystemConfig.get("IGNORE_ALL_RANCHER_PURCHASE_LIMITS") == "true": @@ -24,8 +34,21 @@ def can_buy_from_inventory(rancher, inventory_entry: InventoryEntry): if rancher.ignore_purchase_limit: return True - quota_weight = rancher_quota_weight(rancher, inventory_entry) # {total, by_type} - total_allowed = quota_weight['total'] + if inventory_entry: + # check if quota is open and acceptable to sale + if inventory_entry.distribution.quota.is_in_valid_time(): + quota_weight = rancher_quota_weight(rancher, inventory_entry=inventory_entry) # {total, by_type} + else: + return False + + elif distribution: + # check if quota is open and acceptable to sale + if distribution.quota.is_in_valid_time(): + quota_weight = rancher_quota_weight(rancher, distribution=distribution) # {total, by_type} + else: + return False + + total_allowed = quota_weight['total'] # noqa total_sold = get_total_sold(inventory_entry, rancher)