add - new changes on OrganizationQuotaStat & calculative signals on quota/distribution

This commit is contained in:
2025-11-16 10:54:56 +03:30
parent 3169298f91
commit 2ba59174d3
5 changed files with 141 additions and 38 deletions

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.0 on 2025-11-16 07:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0093_organizationquotastats'),
]
operations = [
migrations.AddField(
model_name='organizationquotastats',
name='remaining_amount',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='organizationquotastats',
name='sold_amount',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='organizationquotastats',
name='total_distributed',
field=models.PositiveBigIntegerField(default=0),
),
]

View File

@@ -4,6 +4,7 @@ import jdatetime
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.db import transaction
from django.db.models import Sum
from simple_history.models import HistoricalRecords
from apps.authentication.models import OrganizationType, Organization
@@ -468,7 +469,12 @@ class Quota(BaseModel):
quota=self,
organization=org
)
return stat.first().total_amount if stat.exists() else None
return {
"quota_weight": stat.first().total_amount if stat.exists() else 0,
"remaining_weight": stat.first().remaining_amount if stat.exists() else 0,
"quota_distributed": stat.first().total_distributed if stat.exists() else 0,
"been_sold": stat.first().sold_amount if stat.exists() else 0,
}
def soft_delete(self):
self.trash = True
@@ -808,6 +814,29 @@ class OrganizationQuotaStats(BaseModel):
distributions = models.ManyToManyField(QuotaDistribution)
total_amount = models.PositiveBigIntegerField(default=0)
total_distributed = models.PositiveBigIntegerField(default=0)
sold_amount = models.PositiveBigIntegerField(default=0)
remaining_amount = models.PositiveBigIntegerField(default=0) # total - sold
def update_amount(self):
""" calculate total/sold/remaining """
from apps.warehouse.models import InventoryQuotaSaleItem
# calculate total amount of distribution
self.total_distributed = self.distributions.filter().aggregate(
total=Sum('weight')
)['total'] or 0
self.sold_amount = InventoryQuotaSaleItem.objects.filter(
quota_distribution__in=self.distributions.all(),
transaction__transaction_status='success'
).aggregate(
total=Sum('weight')
)['total'] or 0
self.remaining_amount = self.total_amount - self.sold_amount
self.save()
def __str__(self):
return f"organization: {self.organization} - quota: {self.quota}"

View File

@@ -243,36 +243,17 @@ def update_quota_stats(instance: Quota):
])
def organization_quota_stats(quota: Quota, distribution: QuotaDistribution = None):
@receiver(post_save, sender=Quota)
def organization_quota_stats(sender, quota: Quota, created: bool, **kwargs):
"""
set total received distributions for every organization
"""
if distribution:
org_quota_stat, created = OrganizationQuotaStats.objects.get_or_create(
quota=quota,
organization=distribution.assigned_organization,
)
# delete distribution
# decrease org stat total amount after remove distribution
if distribution.trash:
org_quota_stat.total_amount -= distribution.weight
org_quota_stat.save(update_fields=["total_amount"])
else:
# if stat was created before or total amount is 0
if not created or org_quota_stat.total_amount == 0:
org_quota_stat.total_amount += distribution.weight
org_quota_stat.save(update_fields=["total_amount"])
org_quota_stat.distributions.add(distribution)
# prevent from maximum recursion loop
distribution.stat_from_signal = True
else:
org_quota_stat, created = OrganizationQuotaStats.objects.get_or_create(
quota=quota,
organization=quota.registerer_organization,
total_amount=quota.quota_weight
)
org_quota_stat.total_amount = quota.quota_weight
org_quota_stat.save(update_fields=['total_amount'])
# delete quota
if quota.trash:
@@ -293,8 +274,6 @@ def update_stats_on_change(sender, instance, **kwargs):
if getattr(instance, 'stat_from_signal', False):
return
organization_quota_stats(instance.quota, instance)
@receiver([post_save, post_delete], sender=Quota)
def update_quota_stats_on_change(sender, instance, **kwargs):
@@ -304,3 +283,45 @@ def update_quota_stats_on_change(sender, instance, **kwargs):
return
organization_quota_stats(instance)
@receiver(post_save, sender=QuotaDistribution)
def update_quota_stats_on_distribution(sender, instance: QuotaDistribution, created, **kwargs):
if getattr(instance, 'one_time_loop_flag', False):
return
if instance.trash:
return
org = instance.assigned_organization
quota = instance.quota
stats, _ = OrganizationQuotaStats.objects.get_or_create(
quota=quota,
organization=org,
)
stats.distributions.add(instance)
stats.update_amount()
instance.one_time_loop_flag = True
@receiver(post_save, sender=QuotaDistribution)
def handle_quota_stats_soft_delete_on_distribution(sender, instance: QuotaDistribution, created, **kwargs):
if getattr(instance, 'one_time_loop_flag', False):
return
if instance.trash:
org = instance.assigned_organization
quota = instance.quota
stats_qs = OrganizationQuotaStats.objects.filter(
quota=quota,
organization=org,
)
if stats_qs:
for stats in stats_qs:
stats.distributions.remove(instance)
stats.update_amount()
instance.one_time_loop_flag = True

View File

@@ -15,9 +15,19 @@ class QuotaSerializer(serializers.ModelSerializer):
# change quota weight by organization received weight
if 'org' in self.context.keys():
quota_weight_by_org = instance.quota_amount_by_org(self.context['org'])
org = self.context['org']
quota_weight_by_org = instance.quota_amount_by_org(org)
if quota_weight_by_org:
representation['quota_weight'] = quota_weight_by_org
representation['quota_weight'] = quota_weight_by_org['quota_weight']
representation['quota_distributed'] = quota_weight_by_org['quota_distributed']
representation['remaining_weight'] = quota_weight_by_org['remaining_weight']
representation['been_sold'] = quota_weight_by_org['been_sold']
representation['distributions'] = [{
"id": dist.id,
"organization": org.name,
"organization_id": org.id,
"weight": dist.weight,
} for dist in instance.distributions_assigned.all()]
if isinstance(instance, product_models.Quota):
if instance.sale_unit:

View File

@@ -1,7 +1,8 @@
from django.db.models import Sum
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from apps.product.models import QuotaDistribution
from apps.product.models import QuotaDistribution, OrganizationQuotaStats
from .models import InventoryEntry, InventoryQuotaSaleItem
@@ -67,3 +68,17 @@ def update_distribution_warehouse_sold_and_balance(sender, instance: InventoryQu
)
else:
print("quota distribution is null - warehouse app signals")
@receiver(post_save, sender=InventoryQuotaSaleItem)
def update_quota_stats_on_sale(sender, instance: InventoryQuotaSaleItem, created, **kwargs):
if instance.transaction.transaction_status == 'success':
return
stats = OrganizationQuotaStats.objects.filter(
organization=instance.quota_distribution.assigned_organization,
quota=instance.quota_distribution.quota
).first()
if stats:
stats.update_amounts()