diff --git a/apps/product/services/quota_stat_service.py b/apps/product/services/quota_stat_service.py index 3b131c3..9c1582a 100644 --- a/apps/product/services/quota_stat_service.py +++ b/apps/product/services/quota_stat_service.py @@ -1,15 +1,23 @@ from apps.product.models import QuotaDistribution, OrganizationQuotaStats +from apps.product.validators.quota_stats_validator import QuotaStatsValidator class QuotaStatsService: @staticmethod def apply_distribution(distribution: QuotaDistribution): quota = distribution.quota - + print("ssss") # origin org assigner = distribution.assigner_organization # destination org assigned = distribution.assigned_organization + + QuotaStatsValidator.validate_assigner_has_enough( + assigner_org=assigner, + quota=quota, + amount=distribution.weight + ) + # ================ origin ================ assigner_stat, created = OrganizationQuotaStats.objects.get_or_create( organization=assigner, @@ -46,6 +54,14 @@ class QuotaStatsService: assigner = distribution.assigner_organization assigned = distribution.assigned_organization + QuotaStatsValidator.validate_distribution_update( + assigner_org=assigner, + assigned_org=assigned, + quota=quota, + old_amount=old_weight, + new_amount=distribution.weight + ) + assigner_stat = OrganizationQuotaStats.objects.get( organization=assigner, quota=quota @@ -57,13 +73,9 @@ class QuotaStatsService: if assigner_stat.stat_type == 'distribution': # if diff > 0 it is added to destination assigner_stat.remaining_amount -= diff - print("distributed : ", assigner_stat.total_distributed) assigner_stat.total_distributed += diff - print(assigner_stat.total_distributed, diff) assigner_stat.save() - print(assigned_stat.total_amount) assigned_stat.total_amount += diff - print(assigned_stat.total_amount) assigned_stat.remaining_amount += diff assigned_stat.save() diff --git a/apps/product/signals.py b/apps/product/signals.py index aab2457..6b4fa50 100644 --- a/apps/product/signals.py +++ b/apps/product/signals.py @@ -5,6 +5,7 @@ from django.db.models import Sum, Q from django.db.models.signals import post_save, post_delete, post_init from django.dispatch import receiver +from apps.product.validators.quota_stats_validator import QuotaStatsValidator from apps.warehouse.models import ( InventoryQuotaSaleTransaction, InventoryEntry @@ -80,7 +81,11 @@ def update_product_stats(instance: Product, distribution: QuotaDistribution = No user = get_current_user() # get user object if not isinstance(user, AnonymousUser): organization = get_organization_by_user(user) - + QuotaStatsValidator.validate_assigner_has_enough( + organization, + distribution.quota, + distribution.weight + ) if ProductStats.objects.filter( organization=organization, product=instance, diff --git a/apps/product/validators/__init__.py b/apps/product/validators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/product/validators/quota_stats_validator.py b/apps/product/validators/quota_stats_validator.py new file mode 100644 index 0000000..de05dce --- /dev/null +++ b/apps/product/validators/quota_stats_validator.py @@ -0,0 +1,60 @@ +from django.apps import apps +from django.core.exceptions import ValidationError +from rest_framework.exceptions import APIException + + +def get_model(app_label, model_name): + return apps.get_model(app_label, model_name) + + +class QuotaStatsValidator: + """ + Validator static methods for distribution/sale/quota edits. + """ + + @staticmethod + def _get_stat(quota, organization): + organization_quota_stats = get_model("product", "OrganizationQuotaStats") + return organization_quota_stats.objects.filter(quota=quota, organization=organization).first() + + @staticmethod + def validate_assigner_has_enough(assigner_org, quota, amount, allow_zero=False): + """ + if organization has enough remaining weight + """ + stat = QuotaStatsValidator._get_stat(quota, assigner_org) + if not stat: + raise APIException(f"Organization {assigner_org} has no quota record for quota {quota}.") + + remaining = getattr(stat, "remaining_amount", None) + if remaining is None: + # fallback to quota.remaining_weight subtraction from totals + raise APIException("remaining_amount field missing in OrganizationQuotaStats.") + + if amount < 0: + raise APIException("Amount must be non-negative.") + + if remaining < amount and not allow_zero: + raise APIException( + f"Assigning {amount} exceeds remaining amount {remaining} for organization {assigner_org}." + ) + + @staticmethod + def validate_distribution_create(assigner_org, assigned_org, quota, amount, parent_distribution=None): + # rule: assigner must have enough remaining weight + QuotaStatsValidator.validate_assigner_has_enough(assigner_org, quota, amount) + + @staticmethod + def validate_distribution_update(assigner_org, assigned_org, quota, old_amount, new_amount, + parent_distribution=None): + # if diff is positive we must check + diff = new_amount - (old_amount or 0) + if diff > 0: + QuotaStatsValidator.validate_assigner_has_enough(assigner_org, quota, diff) + + @staticmethod + def validate_distribution_delete(distribution): + # اگر already sold (been_sold > 0) نباید حذف شود + been_sold = getattr(distribution, "been_sold", None) + if been_sold and been_sold > 0: + raise ValidationError("Cannot delete distribution: it has sold items.") diff --git a/apps/product/web/api/v1/serializers/quota_serializers.py b/apps/product/web/api/v1/serializers/quota_serializers.py index ad3f4bb..27c9bc3 100644 --- a/apps/product/web/api/v1/serializers/quota_serializers.py +++ b/apps/product/web/api/v1/serializers/quota_serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from rest_framework.exceptions import APIException from apps.livestock.web.api.v1.serializers import LiveStockTypeSerializer from apps.product import models as product_models @@ -10,6 +11,13 @@ class QuotaSerializer(serializers.ModelSerializer): model = product_models.Quota fields = '__all__' + def validate(self, attrs): + weight = attrs['quota_weight'] + if self.instance: + if self.instance.quota_distributed < weight: + raise APIException("Quota weight cannot be less than distributed weight.") + return attrs + def to_representation(self, instance: product_models.Quota): representation = super().to_representation(instance)