change list of rancher inventory entries to rancher distributions for one item be as sale in pos device

This commit is contained in:
2025-09-06 12:04:41 +03:30
parent 272e9ed2c8
commit 18a7955e3a
5 changed files with 151 additions and 50 deletions

View File

@@ -2,7 +2,7 @@ from decimal import Decimal
from apps.herd.models import Rancher from apps.herd.models import Rancher
from apps.livestock.models import LiveStock from apps.livestock.models import LiveStock
from apps.warehouse.models import InventoryEntry 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 from django.db.models import Count, Q
import typing import typing
@@ -26,10 +26,11 @@ def get_rancher_statistics(rancher: Rancher = None) -> typing.Any:
return stats 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 rancher: Rancher instance
:param inventory_entry: InventoryEntry instance :param inventory_entry: InventoryEntry instance
:param distribution: QuotaDistribution instance
:return: dict {total, by_type} :return: dict {total, by_type}
""" """
@@ -41,7 +42,13 @@ def rancher_quota_weight(rancher, inventory_entry: InventoryEntry):
"اسب": "horse_count" "اسب": "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 # list of quota live stock allocations
allocations = list(quota.livestock_allocations.all().select_related('livestock_type')) allocations = list(quota.livestock_allocations.all().select_related('livestock_type'))
# list of quota incentive plans # list of quota incentive plans

View File

@@ -1,14 +1,17 @@
from rest_framework import serializers from apps.product.services.services import quota_live_stock_allocation_info, quota_incentive_plans_info, \
from apps.product import models as product_models 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 rest_framework.exceptions import APIException
from apps.product.web.api.v1.serializers.quota_serializers import QuotaSerializer from apps.product import models as product_models
from django.db import models from rest_framework import serializers
from apps.product.exceptions import ( from apps.product.exceptions import (
QuotaWeightException, QuotaWeightException,
QuotaClosedException, QuotaClosedException,
QuotaExpiredTimeException, QuotaExpiredTimeException,
QuotaLimitByOrganizationException QuotaLimitByOrganizationException
) )
from django.db import models
class QuotaDistributionSerializer(serializers.ModelSerializer): class QuotaDistributionSerializer(serializers.ModelSerializer):
@@ -77,7 +80,46 @@ class QuotaDistributionSerializer(serializers.ModelSerializer):
representation = super().to_representation(instance) representation = super().to_representation(instance)
if instance.quota: 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: if instance.assigned_organization:
representation['assigned_organization'] = { representation['assigned_organization'] = {

View File

@@ -1,10 +1,11 @@
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .viewsets import product_api from .viewsets import product_api, quota_distribution_api
router = DefaultRouter() router = DefaultRouter()
router.register(r'product', product_api.ProductViewSet, basename='product') 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'pos_free_products', product_api.POSFreeProductsViewSet, basename='pos_free_products')
router.register(r'distributions', quota_distribution_api.QuotaDistributionViewSet, basename='distributions')
urlpatterns = [ urlpatterns = [
path('v1/', include(router.urls)) path('v1/', include(router.urls))

View File

@@ -1,12 +1,16 @@
from apps.product.pos.api.v1.serializers import quota_distribution_serializers as distribution_serializers 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.mixins.search_mixin import DynamicSearchMixin
from apps.core.pagination import CustomPageNumberPagination 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 common.helpers import get_organization_by_user
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from apps.product import models as product_models from apps.product import models as product_models
from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework import viewsets, filters from rest_framework import viewsets, filters
from apps.herd.models import Rancher
from rest_framework import status from rest_framework import status
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
@@ -25,12 +29,12 @@ def delete(queryset, pk):
obj.delete() obj.delete()
class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin): class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDeviceMixin):
""" quota distribution apis """ """ quota distribution apis """
queryset = product_models.QuotaDistribution.objects.all() queryset = product_models.QuotaDistribution.objects.all()
serializer_class = distribution_serializers.QuotaDistributionSerializer serializer_class = distribution_serializers.QuotaDistributionSerializer
filter_backends = [filters.SearchFilter] permission_classes = [AllowAny]
CustomPageNumberPagination.page_size = 5 CustomPageNumberPagination.page_size = 5
search_fields = [ search_fields = [
"assigner_organization__name", "assigner_organization__name",
@@ -53,37 +57,61 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet, DynamicSearchMixin):
def my_distributions(self, request): def my_distributions(self, request):
""" list of my distributions """ """ list of my distributions """
queryset = self.filter_query(self.queryset) # return by search param or all objects organization = self.get_device_organization()
organization = get_organization_by_user(request.user) device = self.get_pos_device()
query = self.request.query_params # get distributions with open quota
if query.get('param') == 'assigned': distributions = self.queryset.filter(
# paginate queryset assigned_organization=organization,
page = self.paginate_queryset( quota__is_closed=False,
queryset.filter( warehouse_entry__gt=0
Q(assigned_organization=organization) ).order_by('-create_date')
).order_by('-modify_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 & response
# paginate queryset page = self.paginate_queryset(available_distributions) # noqa
page = self.paginate_queryset( if page is not None:
queryset.filter( serializer = self.get_serializer(page, many=True, context={'rancher': rancher.first(), 'device': device})
Q(assigner_organization=organization) # set custom message for paginator
).order_by('-modify_date') if not rancher:
) self.paginator.set_message("دامدار با کد ملی مد نظر یافت نشد") # noqa
elif not available_distributions:
elif query.get('param') == 'all': self.paginator.set_message("دامدار با کد ملی مد نظر سهمیه ایی ندارد") # noqa
# 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
return self.get_paginated_response(serializer.data) return self.get_paginated_response(serializer.data)
@action( @action(

View File

@@ -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.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 apps.core.models import SystemConfig
from django.db.models import Sum 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 ( if inventory_entry:
InventoryQuotaSaleTransaction.objects.filter( return (
inventory_entry=inventory_entry, InventoryQuotaSaleTransaction.objects.filter(
rancher=rancher inventory_entry=inventory_entry,
).aggregate(total=Sum('weight'))['total'] or 0 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": 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: if rancher.ignore_purchase_limit:
return True return True
quota_weight = rancher_quota_weight(rancher, inventory_entry) # {total, by_type} if inventory_entry:
total_allowed = quota_weight['total'] # 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) total_sold = get_total_sold(inventory_entry, rancher)