from django.db import transaction from django.db.models import Sum from rest_framework.exceptions import APIException from apps.product import models as product_models class QuotaDistributionService: """ Service layer to manage multiple quota distributions as one unified entity. Used for organization-based views and POS operations. """ def __init__(self, quota, organization): self.quota = quota self.organization = organization self.distribution = product_models.QuotaDistribution.objects.filter( quota=quota, assigned_organization=organization, warehouse_entry__gt=0 ).first() @property def total_weight(): return self.distribution.aggregate( total=Sum('weight') )['total'] or 0 @property def remaining_weight(): return self.distribution.aggregate( total=Sum('remaining_weight') )['total'] or 0 def to_representation(self): # noqa return { 'quota_id': self.quota.id, 'quota_identity': self.quota.quota_id, 'product': self.quota.product.name, 'total_weight': self.total_weight, 'remaining_weight': self.remaining_weight, 'distribution_ids': self.distribution_ids, } @transaction.atomic def consume(self, amount): # noqa """ Consume 'amount' of weight across all distributions in order. Automatically splits usage if needed. """ if amount > self.remaining_weight: raise APIException('Not enough weight to consume') remaining_to_consume = amount for dist in self.distribution.select_for_update(): if remaining_to_consume <= 0: break available = dist.remaining_weight if available <= 0: continue consume_amount = min(available, remaining_to_consume) dist.remaining_weight -= consume_amount dist.save(update_fields=["remaining_weight"]) remaining_to_consume -= consume_amount if remaining_to_consume > 0: raise APIException('Not enough weight to consume') return True