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.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

View File

@@ -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'] = {

View File

@@ -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))

View File

@@ -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(

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.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)