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.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from django.db import transaction from django.db import transaction
from django.db.models import Sum
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from apps.authentication.models import OrganizationType, Organization from apps.authentication.models import OrganizationType, Organization
@@ -468,7 +469,12 @@ class Quota(BaseModel):
quota=self, quota=self,
organization=org 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): def soft_delete(self):
self.trash = True self.trash = True
@@ -808,6 +814,29 @@ class OrganizationQuotaStats(BaseModel):
distributions = models.ManyToManyField(QuotaDistribution) distributions = models.ManyToManyField(QuotaDistribution)
total_amount = models.PositiveBigIntegerField(default=0) 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): def __str__(self):
return f"organization: {self.organization} - quota: {self.quota}" return f"organization: {self.organization} - quota: {self.quota}"

View File

@@ -243,43 +243,24 @@ 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 set total received distributions for every organization
""" """
if distribution: org_quota_stat, created = OrganizationQuotaStats.objects.get_or_create(
org_quota_stat, created = OrganizationQuotaStats.objects.get_or_create( quota=quota,
quota=quota, organization=quota.registerer_organization,
organization=distribution.assigned_organization, )
) org_quota_stat.total_amount = quota.quota_weight
org_quota_stat.save(update_fields=['total_amount'])
# delete distribution # delete quota
# decrease org stat total amount after remove distribution if quota.trash:
if distribution.trash: org_quota_stat.soft_delete()
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 # prevent from maximum recursion loop
distribution.stat_from_signal = True quota.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
)
# delete quota
if quota.trash:
org_quota_stat.soft_delete()
# prevent from maximum recursion loop
quota.stat_from_signal = True
@receiver([post_save, post_delete], sender=QuotaDistribution) @receiver([post_save, post_delete], sender=QuotaDistribution)
@@ -293,8 +274,6 @@ def update_stats_on_change(sender, instance, **kwargs):
if getattr(instance, 'stat_from_signal', False): if getattr(instance, 'stat_from_signal', False):
return return
organization_quota_stats(instance.quota, instance)
@receiver([post_save, post_delete], sender=Quota) @receiver([post_save, post_delete], sender=Quota)
def update_quota_stats_on_change(sender, instance, **kwargs): def update_quota_stats_on_change(sender, instance, **kwargs):
@@ -304,3 +283,45 @@ def update_quota_stats_on_change(sender, instance, **kwargs):
return return
organization_quota_stats(instance) 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 # change quota weight by organization received weight
if 'org' in self.context.keys(): 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: 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 isinstance(instance, product_models.Quota):
if instance.sale_unit: if instance.sale_unit:

View File

@@ -1,7 +1,8 @@
from django.db.models import Sum from django.db.models import Sum
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from apps.product.models import QuotaDistribution
from apps.product.models import QuotaDistribution, OrganizationQuotaStats
from .models import InventoryEntry, InventoryQuotaSaleItem from .models import InventoryEntry, InventoryQuotaSaleItem
@@ -67,3 +68,17 @@ def update_distribution_warehouse_sold_and_balance(sender, instance: InventoryQu
) )
else: else:
print("quota distribution is null - warehouse app signals") 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()