From 27046f20e94439f142d92564248ccce955458fee Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Sun, 31 Aug 2025 14:51:08 +0330 Subject: [PATCH] optimize load balance for inventory list in pos - add new fields to stake holdes --- apps/herd/services/services.py | 103 ++++++------------ ...ker_stakeholders_broker_amount_and_more.py | 28 +++++ apps/pos_device/models.py | 48 +++----- .../web/api/v1/serilaizers/device.py | 6 - apps/pos_device/web/api/v1/urls.py | 1 - apps/pos_device/web/api/v1/viewsets/device.py | 4 - apps/product/services/services.py | 32 +++++- apps/warehouse/pos/api/v1/serializers.py | 9 +- 8 files changed, 118 insertions(+), 113 deletions(-) create mode 100644 apps/pos_device/migrations/0065_stakeholders_broker_stakeholders_broker_amount_and_more.py diff --git a/apps/herd/services/services.py b/apps/herd/services/services.py index c876006..0965031 100644 --- a/apps/herd/services/services.py +++ b/apps/herd/services/services.py @@ -3,36 +3,27 @@ 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 django.db.models import Count, Q import typing def get_rancher_statistics(rancher: Rancher = None) -> typing.Any: """ get statistics of a rancher """ # noqa - herds = rancher.herd.all() # noqa - herd_count = herds.count() + livestocks = LiveStock.objects.filter(herd__rancher=rancher) # noqa - livestocks = LiveStock.objects.filter(herd__in=herds) # noqa + stats = livestocks.aggregate( + herd_count=Count("herd", distinct=True), + light_count=Count('id', filter=Q(weight_type='L')), + heavy_count=Count('id', filter=Q(weight_type='H')), + sheep_count=Count('id', filter=Q(type__name='گوسفند')), # noqa + goat_count=Count('id', filter=Q(type__name='بز')), + cow_count=Count('id', filter=Q(type__name='گاو')), + camel_count=Count('id', filter=Q(type__name='شتر')), + horse_count=Count('id', filter=Q(type__name='بز')), + ) - light_count = livestocks.filter(weight_type='L').count() - heavy_count = livestocks.filter(weight_type='H').count() - - sheep_count = livestocks.filter(type__name="گوسفند").count() # noqa - goat_count = livestocks.filter(type__name="بز").count() - cow_count = livestocks.filter(type__name="گاو").count() - camel_count = livestocks.filter(type__name="شتر").count() - horse_count = livestocks.filter(type__name="اسب").count() - - return { - "herd_count": herd_count, - "light_count": light_count, - "heavy_count": heavy_count, - "sheep_count": sheep_count, - "goat_count": goat_count, - "cow_count": cow_count, - "camel_count": camel_count, - "horse_count": horse_count, - } + return stats def rancher_quota_weight(rancher, inventory_entry: InventoryEntry): @@ -51,60 +42,38 @@ def rancher_quota_weight(rancher, inventory_entry: InventoryEntry): } quota: Quota = inventory_entry.distribution.quota - allocations = quota.livestock_allocations.all() # list of quota live stock allocations + # list of quota live stock allocations + allocations = list(quota.livestock_allocations.all().select_related('livestock_type')) + # list of quota incentive plans + incentive_plans = list(quota.incentive_assignments.all().select_related('livestock_type')) livestock_counts = get_rancher_statistics(rancher) total_weight = 0 - alloc_details = {} - plan_details = {} - result_list = [] + merged = {} - # list of quota allocations, get allocations weight on any animal type - for alloc in allocations: # noqa - if alloc.livestock_type: - animal_type = alloc.livestock_type.name - per_head = alloc.quantity_kg + for item in allocations + incentive_plans: # noqa + if item.livestock_type: + animal_type = item.livestock_type.name + per_head = item.quantity_kg count = livestock_counts.get(live_stock_meta.get(animal_type), 0) weight = per_head * count - alloc_details[animal_type] = {"weight": weight, "type": alloc.livestock_type.weight_type} total_weight += weight - # list of quota incentive plans, get plans weight on any animal type - incentive_plans = quota.incentive_assignments.all() - for plan in incentive_plans: # noqa - if plan.livestock_type: - animal_type = plan.livestock_type.name - per_head = plan.quantity_kg - count = livestock_counts.get(live_stock_meta.get(animal_type), 0) + if animal_type not in merged: + merged[animal_type] = { + "weight": weight, + "type": item.livestock_type.weight_type + } + else: + merged[animal_type]['weight'] += weight - weight = per_head * count - plan_details[animal_type] = {"weight": weight, "type": plan.livestock_type.weight_type} - total_weight += weight - - # summation of incentive plans & livestock allocations animal types weight - result_details = {"total": total_weight, 'by_type': {}} - all_keys = set(alloc_details.keys()) | set(plan_details.keys()) # get all keys from plan & allocations data - - for key in all_keys: - alloc_weight = alloc_details.get(key, {}).get("weight", 0) # total weight of quota livestock allocations data - plan_weight = plan_details.get(key, {}).get("weight", 0) # total weight of quota incentive plan data - - # get animal type (Heavy, Light) - animal_type = alloc_details.get( - key, {} - ).get("type") or plan_details.get( - key, {} - ).get("type") - - # final result, total weights - result_list.append({ + return { + "total_weight": total_weight, + "by_type": [{ "name": key, - "weight": alloc_weight + plan_weight, - "type": animal_type - }) - - result_details['by_type'] = result_list - - return result_details + "weight": value['weight'], + "type": value['type'] + }for key, value in merged.items()] + } diff --git a/apps/pos_device/migrations/0065_stakeholders_broker_stakeholders_broker_amount_and_more.py b/apps/pos_device/migrations/0065_stakeholders_broker_stakeholders_broker_amount_and_more.py new file mode 100644 index 0000000..92b169c --- /dev/null +++ b/apps/pos_device/migrations/0065_stakeholders_broker_stakeholders_broker_amount_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0 on 2025-08-31 11:17 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0064_brokerstakeholderassignment'), + ('product', '0071_quotaincentiveassignment_livestock_type_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='stakeholders', + name='broker', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pos_stake_holders', to='product.broker'), + ), + migrations.AddField( + model_name='stakeholders', + name='broker_amount', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pos_stake_holders', to='product.quotabrokervalue'), + ), + migrations.DeleteModel( + name='BrokerStakeHolderAssignment', + ), + ] diff --git a/apps/pos_device/models.py b/apps/pos_device/models.py index 4bded58..20342a1 100644 --- a/apps/pos_device/models.py +++ b/apps/pos_device/models.py @@ -1,13 +1,11 @@ -import random -import string - -from apps.authentication.models import Organization -from apps.product.models import Broker -from apps.product.models import Product +from apps.product.models import Product, Broker, QuotaBrokerValue from django.contrib.postgres.fields import ArrayField from apps.authorization.models import UserRelations +from apps.authentication.models import Organization from apps.core.models import BaseModel from django.db import models +import random +import string class ProviderCompany(BaseModel): @@ -258,6 +256,18 @@ class StakeHolders(BaseModel): related_name='pos_stake_holders', null=True ) + broker = models.ForeignKey( + Broker, + on_delete=models.CASCADE, + related_name='pos_stake_holders', + null=True + ) + broker_amount = models.ForeignKey( + QuotaBrokerValue, + on_delete=models.CASCADE, + related_name='pos_stake_holders', + null=True + ) share_percent = models.FloatField(default=0) default = models.BooleanField(default=False) @@ -295,29 +305,3 @@ class POSFreeProducts(BaseModel): def save(self, *args, **kwargs): return super(POSFreeProducts, self).save(*args, **kwargs) - - -class BrokerStakeHolderAssignment(BaseModel): - device = models.ForeignKey( - Device, - on_delete=models.CASCADE, - related_name="stake_brok_assigment", - null=True - ) - stake_holder = models.ForeignKey( - StakeHolders, - on_delete=models.CASCADE, - related_name='stake_brok_assignment', - null=True - ) - broker = models.ForeignKey( - Broker, - on_delete=models.CASCADE, - related_name='stake_brok_assignment', - null=True - ) - - def save(self, *args, **kwargs): - return super(BrokerStakeHolderAssignment, self).save(*args, **kwargs) - - diff --git a/apps/pos_device/web/api/v1/serilaizers/device.py b/apps/pos_device/web/api/v1/serilaizers/device.py index e7ffd7e..f11dcb1 100644 --- a/apps/pos_device/web/api/v1/serilaizers/device.py +++ b/apps/pos_device/web/api/v1/serilaizers/device.py @@ -86,9 +86,3 @@ class StakeHoldersSerializer(ModelSerializer): ).data return representation - - -class BrokerStakeHolderAssignSerializer(ModelSerializer): - class Meta: - model = pos_models.BrokerStakeHolderAssignment - fields = '__all__' diff --git a/apps/pos_device/web/api/v1/urls.py b/apps/pos_device/web/api/v1/urls.py index 9794de6..10717bb 100644 --- a/apps/pos_device/web/api/v1/urls.py +++ b/apps/pos_device/web/api/v1/urls.py @@ -10,7 +10,6 @@ router.register(r'provider', device_views.ProviderCompanyViewSet, basename='prov router.register(r'device', device_views.DeviceViewSet, basename='device') router.register(r'device_assignment', device_views.DeviceAssignmentViewSet, basename='device_assignment') router.register(r'stake_holders', device_views.StakeHoldersViewSet, basename='stake_holders') -router.register(r'broker_stake_assign', device_views.BrokerStakeHolderAssignViewSet, basename='broker_stake_assign') urlpatterns = [ path('v1/pos/', include(router.urls)) diff --git a/apps/pos_device/web/api/v1/viewsets/device.py b/apps/pos_device/web/api/v1/viewsets/device.py index 40604b9..74d9abb 100644 --- a/apps/pos_device/web/api/v1/viewsets/device.py +++ b/apps/pos_device/web/api/v1/viewsets/device.py @@ -325,7 +325,3 @@ class StakeHoldersViewSet(viewsets.ModelViewSet, DynamicSearchMixin, SoftDeleteM serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) - -class BrokerStakeHolderAssignViewSet(viewsets.ModelViewSet, DynamicSearchMixin, SoftDeleteMixin): - queryset = pos_models.BrokerStakeHolderAssignment.objects.all() - serializer_class = device_serializer.BrokerStakeHolderAssignSerializer diff --git a/apps/product/services/services.py b/apps/product/services/services.py index 0b7d22b..1163db0 100644 --- a/apps/product/services/services.py +++ b/apps/product/services/services.py @@ -20,7 +20,7 @@ def get_products_in_warehouse(organization_id): def quota_live_stock_allocation_info(quota: Quota) -> typing.Any: """ information of quota live stock allocations """ - allocations = quota.livestock_allocations.filter(quota=quota) + allocations = quota.livestock_allocations.select_related('livestock_type') if allocations: allocations_list = [{ @@ -35,7 +35,7 @@ def quota_live_stock_allocation_info(quota: Quota) -> typing.Any: def quota_incentive_plans_info(quota: Quota) -> typing.Any: """ information of quota incentive plans """ - incentive_plans = quota.incentive_assignments.all() + incentive_plans = quota.incentive_assignments.select_related("livestock_type", "incentive_plan") if incentive_plans: incentive_plans_list = [{ @@ -48,3 +48,31 @@ def quota_incentive_plans_info(quota: Quota) -> typing.Any: } for plan in incentive_plans] return incentive_plans_list + + +def quota_brokers_value(quota: Quota) -> typing.Any: + """ information of quota brokers with their quota value """ + + brokers = quota.broker_values.select_related("broker") + + if brokers: + broker_values_list = [{ + 'name': broker.broker.name, + 'amount': broker.value + } for broker in brokers] + + return broker_values_list + + +def quota_attribute_value(quota: Quota) -> typing.Any: + """ information of quota pricing attribute values """ + + attributes = quota.attribute_values.select_related("attribute") + + if attributes: + attribute_values_list = [{ + 'name': attr.attribute.name, + 'amount': attr.value + }for attr in attributes] + + return attribute_values_list diff --git a/apps/warehouse/pos/api/v1/serializers.py b/apps/warehouse/pos/api/v1/serializers.py index 17b37c9..5bccf48 100644 --- a/apps/warehouse/pos/api/v1/serializers.py +++ b/apps/warehouse/pos/api/v1/serializers.py @@ -1,7 +1,9 @@ from apps.herd.services.services import get_rancher_statistics, rancher_quota_weight from apps.product.services.services import ( quota_live_stock_allocation_info, - quota_incentive_plans_info + quota_incentive_plans_info, + quota_brokers_value, + quota_attribute_value ) from apps.pos_device.pos.api.v1.serializers.device import DeviceSerializer from apps.herd.pos.api.v1.serializers import RancherSerializer @@ -57,6 +59,11 @@ class InventoryEntrySerializer(serializers.ModelSerializer): 'id': instance.distribution.quota.product.id, } + representation['pricing'] = { + 'brokers_info': quota_brokers_value(instance.distribution.quota), + 'pricing_attributes': quota_attribute_value(instance.distribution.quota) + } + if 'rancher' in self.context.keys(): # rancher herd & live stock statistics representation['rancher_statistics'] = get_rancher_statistics(self.context['rancher'])