From d9a9cfde3505cff0e9305c875a9d1b29910768a8 Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Sun, 3 Aug 2025 10:36:00 +0330 Subject: [PATCH] fix product stat signal --- .idea/Rasaddam_Backend.iml | 2 +- .idea/misc.xml | 2 +- ...0_alter_deviceactivationcode_expires_at.py | 19 +++++ ...1_alter_deviceactivationcode_expires_at.py | 19 +++++ ...2_alter_deviceactivationcode_expires_at.py | 19 +++++ ...3_alter_deviceactivationcode_expires_at.py | 19 +++++ ...ductstats_active_quotas_weight_and_more.py | 61 +++++++++++++++ ...ductstats_active_quotas_weight_and_more.py | 78 +++++++++++++++++++ ..._productstats_given_distribution_number.py | 18 +++++ apps/product/models.py | 8 +- apps/product/signals.py | 71 ++++++++++------- .../web/api/v1/viewsets/product_api.py | 2 +- 12 files changed, 285 insertions(+), 33 deletions(-) create mode 100644 apps/pos_device/migrations/0020_alter_deviceactivationcode_expires_at.py create mode 100644 apps/pos_device/migrations/0021_alter_deviceactivationcode_expires_at.py create mode 100644 apps/pos_device/migrations/0022_alter_deviceactivationcode_expires_at.py create mode 100644 apps/pos_device/migrations/0023_alter_deviceactivationcode_expires_at.py create mode 100644 apps/product/migrations/0063_remove_productstats_active_quotas_weight_and_more.py create mode 100644 apps/product/migrations/0064_productstats_active_quotas_weight_and_more.py create mode 100644 apps/product/migrations/0065_productstats_given_distribution_number.py diff --git a/.idea/Rasaddam_Backend.iml b/.idea/Rasaddam_Backend.iml index c5d6090..168bde0 100644 --- a/.idea/Rasaddam_Backend.iml +++ b/.idea/Rasaddam_Backend.iml @@ -14,7 +14,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 29f5506..296aa57 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/apps/pos_device/migrations/0020_alter_deviceactivationcode_expires_at.py b/apps/pos_device/migrations/0020_alter_deviceactivationcode_expires_at.py new file mode 100644 index 0000000..8157248 --- /dev/null +++ b/apps/pos_device/migrations/0020_alter_deviceactivationcode_expires_at.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2025-08-03 05:39 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0019_alter_deviceactivationcode_expires_at'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(default=datetime.datetime(2025, 8, 3, 9, 9, 21, 204835)), + ), + ] diff --git a/apps/pos_device/migrations/0021_alter_deviceactivationcode_expires_at.py b/apps/pos_device/migrations/0021_alter_deviceactivationcode_expires_at.py new file mode 100644 index 0000000..3aeb856 --- /dev/null +++ b/apps/pos_device/migrations/0021_alter_deviceactivationcode_expires_at.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2025-08-03 05:40 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0020_alter_deviceactivationcode_expires_at'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(default=datetime.datetime(2025, 8, 3, 9, 10, 13, 72714)), + ), + ] diff --git a/apps/pos_device/migrations/0022_alter_deviceactivationcode_expires_at.py b/apps/pos_device/migrations/0022_alter_deviceactivationcode_expires_at.py new file mode 100644 index 0000000..27cbcb0 --- /dev/null +++ b/apps/pos_device/migrations/0022_alter_deviceactivationcode_expires_at.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2025-08-03 06:43 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0021_alter_deviceactivationcode_expires_at'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(default=datetime.datetime(2025, 8, 3, 10, 13, 54, 66149)), + ), + ] diff --git a/apps/pos_device/migrations/0023_alter_deviceactivationcode_expires_at.py b/apps/pos_device/migrations/0023_alter_deviceactivationcode_expires_at.py new file mode 100644 index 0000000..9f43133 --- /dev/null +++ b/apps/pos_device/migrations/0023_alter_deviceactivationcode_expires_at.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2025-08-03 07:05 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0022_alter_deviceactivationcode_expires_at'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(default=datetime.datetime(2025, 8, 3, 10, 35, 32, 916008)), + ), + ] diff --git a/apps/product/migrations/0063_remove_productstats_active_quotas_weight_and_more.py b/apps/product/migrations/0063_remove_productstats_active_quotas_weight_and_more.py new file mode 100644 index 0000000..9628ac6 --- /dev/null +++ b/apps/product/migrations/0063_remove_productstats_active_quotas_weight_and_more.py @@ -0,0 +1,61 @@ +# Generated by Django 5.0 on 2025-08-03 05:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0062_productstats_sale_unit_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='productstats', + name='active_quotas_weight', + ), + migrations.RemoveField( + model_name='productstats', + name='closed_quotas_weight', + ), + migrations.RemoveField( + model_name='productstats', + name='quotas_number', + ), + migrations.RemoveField( + model_name='productstats', + name='sale_unit', + ), + migrations.RemoveField( + model_name='productstats', + name='total_distributed_weight', + ), + migrations.RemoveField( + model_name='productstats', + name='total_distribution_weight', + ), + migrations.RemoveField( + model_name='productstats', + name='total_quota_weight', + ), + migrations.RemoveField( + model_name='productstats', + name='total_remaining', + ), + migrations.RemoveField( + model_name='productstats', + name='total_remaining_distribution_weight', + ), + migrations.RemoveField( + model_name='productstats', + name='total_sold', + ), + migrations.RemoveField( + model_name='productstats', + name='total_transactions', + ), + migrations.RemoveField( + model_name='productstats', + name='total_warehouse_entry', + ), + ] diff --git a/apps/product/migrations/0064_productstats_active_quotas_weight_and_more.py b/apps/product/migrations/0064_productstats_active_quotas_weight_and_more.py new file mode 100644 index 0000000..3ade507 --- /dev/null +++ b/apps/product/migrations/0064_productstats_active_quotas_weight_and_more.py @@ -0,0 +1,78 @@ +# Generated by Django 5.0 on 2025-08-03 05:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0063_remove_productstats_active_quotas_weight_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='productstats', + name='active_quotas_weight', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='closed_quotas_weight', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='given_distribution_weight', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='quotas_number', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='received_distribution_number', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='received_distribution_weight', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='sale_unit', + field=models.CharField(max_length=25, null=True), + ), + migrations.AddField( + model_name='productstats', + name='total_quota_remaining', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='total_quota_weight', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='total_remaining_distribution_weight', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='total_sold', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='total_transactions', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.AddField( + model_name='productstats', + name='total_warehouse_entry', + field=models.PositiveBigIntegerField(default=0), + ), + ] diff --git a/apps/product/migrations/0065_productstats_given_distribution_number.py b/apps/product/migrations/0065_productstats_given_distribution_number.py new file mode 100644 index 0000000..0d2bf61 --- /dev/null +++ b/apps/product/migrations/0065_productstats_given_distribution_number.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-08-03 06:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0064_productstats_active_quotas_weight_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='productstats', + name='given_distribution_number', + field=models.PositiveBigIntegerField(default=0), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index 3f9b620..af30931 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -153,10 +153,12 @@ class ProductStats(BaseModel): active_quotas_weight = models.PositiveBigIntegerField(default=0) closed_quotas_weight = models.PositiveBigIntegerField(default=0) total_quota_weight = models.PositiveBigIntegerField(default=0) - total_remaining = models.PositiveBigIntegerField(default=0) + total_quota_remaining = models.PositiveBigIntegerField(default=0) total_remaining_distribution_weight = models.PositiveBigIntegerField(default=0) - total_distributed_weight = models.PositiveBigIntegerField(default=0) - total_distribution_weight = models.PositiveBigIntegerField(default=0) + received_distribution_weight = models.PositiveBigIntegerField(default=0) + given_distribution_weight = models.PositiveBigIntegerField(default=0) + received_distribution_number = models.PositiveBigIntegerField(default=0) + given_distribution_number = models.PositiveBigIntegerField(default=0) total_warehouse_entry = models.PositiveBigIntegerField(default=0) total_sold = models.PositiveBigIntegerField(default=0) total_transactions = models.PositiveBigIntegerField(default=0) diff --git a/apps/product/signals.py b/apps/product/signals.py index 3367a1d..03b5d31 100644 --- a/apps/product/signals.py +++ b/apps/product/signals.py @@ -1,6 +1,7 @@ from django.db.models import Sum, Q from django.db.models.signals import post_save, post_delete from common.helpers import get_organization_by_user +from rest_framework.exceptions import APIException from django.dispatch import receiver from .models import ( QuotaDistribution, @@ -83,18 +84,20 @@ def update_product_stats(instance: Product, distribution: QuotaDistribution = No ) # number of quotas - quotas_count = instance.quotas.filter(is_closed=False).count() # noqa + quotas_count = instance.quotas.filter(is_closed=False, registerer_organization=organization) # noqa + + total_quotas_weight = quotas_count.aggregate( # noqa + total=models.Sum('quota_weight') + )['total'] or 0 + + quotas_count = quotas_count.count() # total weight of product that assigned in quota - active_quotas_weight = instance.quotas.filter(is_closed=False).aggregate( + active_quotas_weight = instance.quotas.filter(is_closed=False, registerer_organization=organization).aggregate( total=models.Sum('quota_weight') )['total'] or 0 - closed_quotas_weight = instance.quotas.filter(is_closed=True).aggregate( # noqa - total=models.Sum('quota_weight') - )['total'] or 0 - - total_quotas_weight = instance.quotas.all().aggregate( # noqa + closed_quotas_weight = instance.quotas.filter(is_closed=True, registerer_organization=organization).aggregate( # noqa total=models.Sum('quota_weight') )['total'] or 0 @@ -103,34 +106,43 @@ def update_product_stats(instance: Product, distribution: QuotaDistribution = No total=models.Sum('remaining_weight') )['total'] or 0 - total_distribution_weight = QuotaDistribution.objects.filter( + received_distribution_weight = QuotaDistribution.objects.filter( quota__product_id=instance.id, quota__is_closed=False, quota__sale_unit=distribution.quota.sale_unit, - assigned_organization=distribution.assigned_organization - ).aggregate(total_weight=models.Sum('weight'))['total_weight'] or 0 + assigned_organization=organization + ) + + received_distribution_number = received_distribution_weight.count() + + received_distribution_weight = received_distribution_weight.aggregate( + total_weight=models.Sum('weight') + )['total_weight'] or 0 # product total distributed weight from quota - total_distributed_weight = QuotaDistribution.objects.filter( + given_distribution_weight = QuotaDistribution.objects.filter( quota__product_id=instance.id, quota__is_closed=False, quota__sale_unit=distribution.quota.sale_unit, - assigner_organization=distribution.assigner_organization - ).aggregate(total_weight=models.Sum('weight'))['total_weight'] or 0 + assigner_organization=organization + ) - total_remaining_distribution_weight = QuotaDistribution.objects.filter( - quota__product_id=instance.id, - quota__is_closed=False, - quota__sale_unit=distribution.quota.sale_unit, - assigner_organization=distribution.assigner_organization - ).aggregate(total_weight=models.Sum('remaining_weight'))['total_weight'] or 0 + given_distribution_number = given_distribution_weight.count() + given_distribution_weight = given_distribution_weight.aggregate( + total_weight=models.Sum('weight') + )['total_weight'] or 0 + + if received_distribution_weight > 0: + distribution_weight_balance = received_distribution_weight - given_distribution_weight + else: + distribution_weight_balance = given_distribution_weight # total sold of product from quota total_sold = QuotaDistribution.objects.filter( quota__product_id=instance.id, quota__is_closed=False, quota__sale_unit=distribution.quota.sale_unit, - assigned_organization=distribution.assigned_organization + assigned_organization=organization ).aggregate(total_sold=models.Sum('been_sold'))['total_sold'] or 0 # total entry from product to inventory @@ -138,17 +150,19 @@ def update_product_stats(instance: Product, distribution: QuotaDistribution = No quota__product_id=instance.id, quota__is_closed=False, quota__sale_unit=distribution.quota.sale_unit, - assigned_organization=distribution.assigned_organization + assigned_organization=organization ).aggregate(total_entry=models.Sum('warehouse_entry'))['total_entry'] or 0 stat.quotas_number = quotas_count stat.active_quotas_weight = active_quotas_weight stat.closed_quotas_weight = closed_quotas_weight stat.total_quota_weight = total_quotas_weight - stat.total_remaining = total_remaining_quotas_weight - stat.total_remaining_distribution_weight = total_remaining_distribution_weight - stat.total_distributed_weight = total_distributed_weight - stat.total_distribution_weight = total_distribution_weight + stat.total_quota_remaining = total_remaining_quotas_weight + stat.total_remaining_distribution_weight = distribution_weight_balance + stat.received_distribution_weight = received_distribution_weight + stat.given_distribution_weight = given_distribution_weight + stat.received_distribution_number = received_distribution_number + stat.given_distribution_number = given_distribution_number stat.total_warehouse_entry = total_warehouse_entry stat.total_sold = total_sold stat.save(update_fields=[ @@ -156,8 +170,11 @@ def update_product_stats(instance: Product, distribution: QuotaDistribution = No "active_quotas_weight", "closed_quotas_weight", "total_quota_weight", - "total_remaining", - "total_distributed_weight", + "total_quota_remaining", + "total_remaining_distribution_weight", + "received_distribution_weight", + "given_distribution_weight", + "received_distribution_number", "total_warehouse_entry", "total_sold", ]) diff --git a/apps/product/web/api/v1/viewsets/product_api.py b/apps/product/web/api/v1/viewsets/product_api.py index dc0eaf0..adecbb5 100644 --- a/apps/product/web/api/v1/viewsets/product_api.py +++ b/apps/product/web/api/v1/viewsets/product_api.py @@ -134,7 +134,7 @@ class ProductStatsViewSet(viewsets.ModelViewSet): try: organization = get_organization_by_user(request.user) - product_stats = self.queryset.filter(organization=organization) + product_stats = self.queryset.filter(organization=organization).order_by('-modify_date') page = self.paginate_queryset(product_stats) # noqa if page is not None: