quota stat by organization - v1
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
# Django secrets
|
||||
SECRET_KEY=django-insecure-@0apn-lk85pfw=z00x2ib$w9#rwz8%2v4i_n^^9jz-m9b+y55*
|
||||
DEBUG=0
|
||||
DEBUG=1
|
||||
ALLOWED_HOSTS=localhost,127.0.0.1,https://rasadyar.net/,https://localhost:9200,https://api.rasadyaar.net,https://api.dam.rasadyaar.net',https://dam.rasadyar.net',http://localhost:3000',http://192.168.88.130:3000',https://rasaddam-front.liara.run',ns0ck4ksk0koks8ksw0ss08g.31.7.78.133.sslip.io' # noqa
|
||||
ENV_NAME=DEV
|
||||
|
||||
# Database secrets
|
||||
DB_HOST=31.7.78.133
|
||||
DB_PORT=14352
|
||||
DB_NAME=Production
|
||||
DB_NAME=Development
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=pfLIVXupbDetvFMt2gUvxLXUL9b4HIOHaPcKXsBEZ1i8zl0iLUjmhUfXlGfJKcTV
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from apps.authentication.services.visibility_services import apply_visibility_fi
|
||||
from apps.authorization.services.role_child import get_all_role_child
|
||||
from apps.core.models import MobileTest, SystemConfig
|
||||
from apps.core.serializers import MobileTestSerializer, SystemConfigSerializer
|
||||
from apps.core.services.visibility_service import apply_visibility_filter_by_org_type
|
||||
|
||||
|
||||
class BaseViewSet(RegionFilterMixin, viewsets.ModelViewSet):
|
||||
@@ -29,6 +30,11 @@ class BaseViewSet(RegionFilterMixin, viewsets.ModelViewSet):
|
||||
queryset = apply_visibility_filter(queryset, org)
|
||||
return queryset
|
||||
|
||||
if visibility_by_org_scope:
|
||||
""" if organization has free visibility by org type, apply visibility filter """
|
||||
queryset = apply_visibility_filter_by_org_type(queryset, org)
|
||||
return queryset
|
||||
|
||||
if user_relation.exists():
|
||||
user_relation = user_relation.first()
|
||||
if not user_relation.role.type.key == 'ADM':
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0 on 2025-11-15 08:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('herd', '0018_rancher_dhi_state'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rancher',
|
||||
name='union_code',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rancher',
|
||||
name='union_name',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
]
|
||||
37
apps/product/migrations/0093_organizationquotastats.py
Normal file
37
apps/product/migrations/0093_organizationquotastats.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.0 on 2025-11-15 08:46
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0049_alter_bankaccountinformation_account_and_more'),
|
||||
('product', '0092_remove_historicalquotadistribution_free_sale_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrganizationQuotaStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||
('modify_date', models.DateTimeField(auto_now=True)),
|
||||
('creator_info', models.CharField(max_length=100, null=True)),
|
||||
('modifier_info', models.CharField(max_length=100, null=True)),
|
||||
('trash', models.BooleanField(default=False)),
|
||||
('total_amount', models.PositiveBigIntegerField(default=0)),
|
||||
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
|
||||
('distributions', models.ManyToManyField(to='product.quotadistribution')),
|
||||
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='org_quota_stats', to='authentication.organization')),
|
||||
('quota', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='org_quota_stats', to='product.quota')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -463,6 +463,13 @@ class Quota(BaseModel):
|
||||
|
||||
return persian_date.month in self.sale_license
|
||||
|
||||
def quota_amount_by_org(self, org: Organization):
|
||||
stat = OrganizationQuotaStats.objects.filter(
|
||||
quota=self,
|
||||
organization=org
|
||||
)
|
||||
return stat.first().total_amount if stat.exists() else None
|
||||
|
||||
def soft_delete(self):
|
||||
self.trash = True
|
||||
self.save(update_fields=['trash'])
|
||||
@@ -783,3 +790,27 @@ class QuotaDistribution(BaseModel):
|
||||
if not self.distribution_id:
|
||||
self.distribution_id = self.generate_distribution_id()
|
||||
return super(QuotaDistribution, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class OrganizationQuotaStats(BaseModel):
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='org_quota_stats',
|
||||
null=True
|
||||
)
|
||||
quota = models.ForeignKey(
|
||||
Quota,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='org_quota_stats',
|
||||
null=True
|
||||
)
|
||||
|
||||
distributions = models.ManyToManyField(QuotaDistribution)
|
||||
total_amount = models.PositiveBigIntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"organization: {self.organization} - quota: {self.quota}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return super(OrganizationQuotaStats, self).save(*args, **kwargs)
|
||||
|
||||
@@ -12,6 +12,7 @@ from apps.warehouse.models import (
|
||||
from common.helpers import get_organization_by_user
|
||||
from .models import (
|
||||
QuotaDistribution,
|
||||
OrganizationQuotaStats,
|
||||
Quota,
|
||||
Product,
|
||||
ProductStats,
|
||||
@@ -242,15 +243,64 @@ def update_quota_stats(instance: Quota):
|
||||
])
|
||||
|
||||
|
||||
def organization_quota_stats(quota: Quota, distribution: QuotaDistribution = None):
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
# 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=InventoryQuotaSaleTransaction)
|
||||
def update_stats_on_change(sender, instance, **kwargs):
|
||||
if sender == QuotaDistribution:
|
||||
update_product_stats(instance.quota.product, instance)
|
||||
update_quota_stats(instance.quota)
|
||||
# elif sender == InventoryQuotaSaleTransaction:
|
||||
# if instance.quota_distribution:
|
||||
# update_product_stats(instance.quota_distribution.quota.product, instance.quota_distribution)
|
||||
# update_quota_stats(instance.quota_distribution.quota)
|
||||
# else:
|
||||
# print("quota distribution is null - product app signal")
|
||||
|
||||
# if _from_signal=True prevent from maximum recursion loop
|
||||
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):
|
||||
if sender == Quota:
|
||||
# if _from_signal=True prevent from maximum recursion loop
|
||||
if getattr(instance, 'stat_from_signal', False):
|
||||
return
|
||||
|
||||
organization_quota_stats(instance)
|
||||
|
||||
@@ -87,9 +87,6 @@ class QuotaDistributionSerializer(serializers.ModelSerializer):
|
||||
if not self.instance.parent_distribution:
|
||||
if amount + total > self.instance.quota.quota_weight:
|
||||
raise APIException("وزن وارد شده بیشتر از وزن باقیمانده است", code=403) # noqa
|
||||
# else:
|
||||
# if amount > self.instance.parent_distribution.remaining_weight:
|
||||
# raise QuotaWeightException()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -10,8 +10,15 @@ class QuotaSerializer(serializers.ModelSerializer):
|
||||
model = product_models.Quota
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, instance):
|
||||
def to_representation(self, instance: product_models.Quota):
|
||||
representation = super().to_representation(instance)
|
||||
|
||||
# change quota weight by organization received weight
|
||||
if self.context['org']:
|
||||
quota_weight_by_org = instance.quota_amount_by_org(self.context['org'])
|
||||
if quota_weight_by_org:
|
||||
representation['quota_weight'] = quota_weight_by_org
|
||||
|
||||
if isinstance(instance, product_models.Quota):
|
||||
if instance.sale_unit:
|
||||
representation['sale_unit'] = product_serializers.SaleUnitSerializer(
|
||||
|
||||
@@ -355,7 +355,7 @@ class QuotaViewSet(BaseViewSet, SoftDeleteMixin, viewsets.ModelViewSet, DynamicS
|
||||
queryset.order_by('-modify_date')
|
||||
)
|
||||
if page is not None: # noqa
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
serializer = self.get_serializer(page, many=True, context={'org': get_organization_by_user(request.user)})
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
@action(
|
||||
|
||||
@@ -65,6 +65,10 @@ class QuotaDistributionViewSet(BaseViewSet, SoftDeleteMixin, viewsets.ModelViewS
|
||||
if serializer.is_valid():
|
||||
distribution = serializer.save()
|
||||
|
||||
# add this organization to quota assigned_organizations
|
||||
# this org is received a distribution from quota
|
||||
distribution.quota.assigned_organizations.add(distribution.assigned_organization)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user