organization limit for quota - get org childs
This commit is contained in:
@@ -78,7 +78,7 @@ INSTALLED_APPS = [
|
|||||||
'apps.core.apps.CoreConfig',
|
'apps.core.apps.CoreConfig',
|
||||||
'apps.herd.apps.HerdAppConfig',
|
'apps.herd.apps.HerdAppConfig',
|
||||||
'apps.livestock.apps.LivestockConfig',
|
'apps.livestock.apps.LivestockConfig',
|
||||||
'apps.pos_machine.apps.PosMachineConfig',
|
'apps.pos_device.apps.PosDeviceConfig',
|
||||||
'apps.tag.apps.TagConfig',
|
'apps.tag.apps.TagConfig',
|
||||||
'apps.warehouse.apps.WarehouseConfig',
|
'apps.warehouse.apps.WarehouseConfig',
|
||||||
'apps.search.apps.SearchConfig',
|
'apps.search.apps.SearchConfig',
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import typing
|
|
||||||
from rest_framework.permissions import AllowAny
|
|
||||||
from apps.authentication.api.v1.serializers.jwt import CustomizedTokenObtainPairSerializer
|
from apps.authentication.api.v1.serializers.jwt import CustomizedTokenObtainPairSerializer
|
||||||
from rest_framework.decorators import action, permission_classes
|
from rest_framework.decorators import action, permission_classes
|
||||||
from apps.authentication import permissions as auth_permissions
|
from apps.authentication import permissions as auth_permissions
|
||||||
@@ -16,7 +14,9 @@ from apps.core.pagination import CustomPageNumberPagination
|
|||||||
from apps.authorization.api.v1 import api as authorize_view
|
from apps.authorization.api.v1 import api as authorize_view
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from apps.authentication.tools import get_token_jti
|
from apps.authentication.tools import get_token_jti
|
||||||
|
from common.helpers import get_organization_by_user
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
from apps.authentication.models import (
|
from apps.authentication.models import (
|
||||||
User,
|
User,
|
||||||
City,
|
City,
|
||||||
@@ -35,6 +35,7 @@ from rest_framework import status
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from common.sms import send_sms
|
from common.sms import send_sms
|
||||||
import random
|
import random
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class CustomizedTokenObtainPairView(TokenObtainPairView):
|
class CustomizedTokenObtainPairView(TokenObtainPairView):
|
||||||
@@ -197,6 +198,14 @@ class OrganizationViewSet(ModelViewSet):
|
|||||||
queryset = Organization.objects.all()
|
queryset = Organization.objects.all()
|
||||||
serializer_class = OrganizationSerializer
|
serializer_class = OrganizationSerializer
|
||||||
|
|
||||||
|
def get_all_org_child(self, org):
|
||||||
|
descendants = []
|
||||||
|
children = org.parents.all()
|
||||||
|
for child in children:
|
||||||
|
descendants.append(child)
|
||||||
|
descendants.extend(self.get_all_org_child(child))
|
||||||
|
return descendants
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -274,6 +283,24 @@ class OrganizationViewSet(ModelViewSet):
|
|||||||
serializer = self.serializer_class(queryset, many=True)
|
serializer = self.serializer_class(queryset, many=True)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['get'],
|
||||||
|
detail=False,
|
||||||
|
url_path='child_organizations',
|
||||||
|
url_name='child_organizations',
|
||||||
|
name='child_organizations'
|
||||||
|
)
|
||||||
|
@transaction.atomic
|
||||||
|
def get_child_organizations(self, request):
|
||||||
|
organization = get_organization_by_user(request.user)
|
||||||
|
child_organizations = self.get_all_org_child(organization)
|
||||||
|
|
||||||
|
page = self.paginate_queryset(child_organizations) # paginate queryset
|
||||||
|
|
||||||
|
if page is not None:
|
||||||
|
serializer = self.serializer_class(page, many=True)
|
||||||
|
return self.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class BankAccountViewSet(ModelViewSet):
|
class BankAccountViewSet(ModelViewSet):
|
||||||
""" Crud operations for bank account model """ #
|
""" Crud operations for bank account model """ #
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from apps.authentication.models import (
|
|||||||
Province,
|
Province,
|
||||||
Organization,
|
Organization,
|
||||||
OrganizationType,
|
OrganizationType,
|
||||||
|
OrganizationStats,
|
||||||
BankAccountInformation
|
BankAccountInformation
|
||||||
)
|
)
|
||||||
from apps.authorization import models as authorize_models
|
from apps.authorization import models as authorize_models
|
||||||
@@ -220,3 +221,9 @@ class OrganizationSerializer(serializers.ModelSerializer):
|
|||||||
instance.national_unique_id = validated_data.get('national_unique_id', instance.national_unique_id)
|
instance.national_unique_id = validated_data.get('national_unique_id', instance.national_unique_id)
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationStatsSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = OrganizationStats
|
||||||
|
fields = '__all__'
|
||||||
|
|||||||
@@ -4,3 +4,6 @@ from django.apps import AppConfig
|
|||||||
class AuthenticationConfig(AppConfig):
|
class AuthenticationConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'apps.authentication'
|
name = 'apps.authentication'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import apps.authentication.signals
|
||||||
|
|||||||
37
apps/authentication/migrations/0026_organizationstats.py
Normal file
37
apps/authentication/migrations/0026_organizationstats.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-07-16 07:41
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0025_alter_organizationtype_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrganizationStats',
|
||||||
|
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_quota_received', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_distributed', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_inventory_in', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_sold', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_buyers', 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)),
|
||||||
|
('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.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='authentication.organization')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -135,6 +135,26 @@ class Organization(BaseModel):
|
|||||||
super(Organization, self).save(*args, **kwargs)
|
super(Organization, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationStats(BaseModel):
|
||||||
|
organization = models.OneToOneField(
|
||||||
|
Organization,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='stats',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
total_quota_received = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_distributed = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_inventory_in = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_sold = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_buyers = models.PositiveBigIntegerField(default=0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Organization: {self.organization.name}'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
return super(OrganizationStats, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class BankAccountInformation(BaseModel):
|
class BankAccountInformation(BaseModel):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
|
|||||||
11
apps/authentication/signals.py
Normal file
11
apps/authentication/signals.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.db.models import Sum
|
||||||
|
from apps.product.models import QuotaDistribution
|
||||||
|
from apps.warehouse.models import InventoryQuotaSaleTransaction
|
||||||
|
from django.db.models.signals import post_save, post_delete
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
|
||||||
|
@receiver([post_save, post_delete], sender=QuotaDistribution)
|
||||||
|
@receiver([post_save, post_delete], sender=InventoryQuotaSaleTransaction)
|
||||||
|
def update_organization_stats(sender, instance, **kwargs):
|
||||||
|
pass
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class PosMachineConfig(AppConfig):
|
class PosDeviceConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'apps.pos_machine'
|
name = 'apps.pos_device'
|
||||||
|
|
||||||
65
apps/pos_device/migrations/0001_initial.py
Normal file
65
apps/pos_device/migrations/0001_initial.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-07-16 04:54
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Device',
|
||||||
|
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)),
|
||||||
|
('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)),
|
||||||
|
('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)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaymentCompany',
|
||||||
|
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)),
|
||||||
|
('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)),
|
||||||
|
('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)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Sessions',
|
||||||
|
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)),
|
||||||
|
('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)),
|
||||||
|
('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)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/pos_device/models.py
Normal file
14
apps/pos_device/models.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django.db import models
|
||||||
|
from apps.core.models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentCompany(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Device(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Sessions(BaseModel):
|
||||||
|
pass
|
||||||
5
apps/pos_device/urls.py
Normal file
5
apps/pos_device/urls.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(''))
|
||||||
|
]
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Your urls go here
|
|
||||||
@@ -24,3 +24,14 @@ class QuotaExpiredTimeException(APIException):
|
|||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
default_detail = "زمان مجوز این سهمیه به پایان رسیده است" # noqa
|
default_detail = "زمان مجوز این سهمیه به پایان رسیده است" # noqa
|
||||||
default_code = 'error'
|
default_code = 'error'
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaLimitByOrganizationException(APIException):
|
||||||
|
"""
|
||||||
|
if limitation of quota by organization is true,
|
||||||
|
distribution should be done in organizations limits
|
||||||
|
"""
|
||||||
|
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
default_detail = "سازمان انتخاب شده در بین سامان های انتخاب شده برای توزیع سهمیه وجود ندارد" # noqa
|
||||||
|
default_code = 'error'
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-07-16 07:41
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0026_organizationstats'),
|
||||||
|
('product', '0047_quotalivestockagelimitation_livestock_type'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='quota',
|
||||||
|
name='has_organization_limit',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='quota',
|
||||||
|
name='limit_by_organizations',
|
||||||
|
field=models.ManyToManyField(null=True, related_name='quota_limits', to='authentication.organization'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProductStats',
|
||||||
|
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_quota', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_remaining', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_sold', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_transactions', 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)),
|
||||||
|
('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)),
|
||||||
|
('product', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='product.product')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='QuotaStats',
|
||||||
|
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_distributed', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('remaining', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_inventory', models.PositiveBigIntegerField(default=0)),
|
||||||
|
('total_sale', 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)),
|
||||||
|
('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)),
|
||||||
|
('quota', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='product.quota')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-07-16 07:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0026_organizationstats'),
|
||||||
|
('product', '0048_quota_has_organization_limit_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='quota',
|
||||||
|
name='limit_by_organizations',
|
||||||
|
field=models.ManyToManyField(related_name='quota_limits', to='authentication.organization'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -134,6 +134,25 @@ class Product(BaseModel):
|
|||||||
super(Product, self).save(*args, **kwargs)
|
super(Product, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductStats(BaseModel):
|
||||||
|
product = models.OneToOneField(
|
||||||
|
Product,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='stats',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
total_quota = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_remaining = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_sold = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_transactions = models.PositiveBigIntegerField(default=0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Product: {self.product.name}-{self.product.id} stats'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
return super(ProductStats).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Attribute(BaseModel):
|
class Attribute(BaseModel):
|
||||||
"""
|
"""
|
||||||
every reference product have multiple attributes
|
every reference product have multiple attributes
|
||||||
@@ -316,6 +335,8 @@ class Quota(BaseModel):
|
|||||||
)
|
)
|
||||||
has_distribution_limit = models.BooleanField(default=False)
|
has_distribution_limit = models.BooleanField(default=False)
|
||||||
distribution_mode = ArrayField(base_field=models.IntegerField(), blank=True, null=True)
|
distribution_mode = ArrayField(base_field=models.IntegerField(), blank=True, null=True)
|
||||||
|
has_organization_limit = models.BooleanField(default=False)
|
||||||
|
limit_by_organizations = models.ManyToManyField(Organization, related_name='quota_limits')
|
||||||
base_price_factory = models.DecimalField(max_digits=12, decimal_places=2)
|
base_price_factory = models.DecimalField(max_digits=12, decimal_places=2)
|
||||||
base_price_cooperative = models.DecimalField(max_digits=12, decimal_places=2)
|
base_price_cooperative = models.DecimalField(max_digits=12, decimal_places=2)
|
||||||
final_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
|
final_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
|
||||||
@@ -371,6 +392,25 @@ class Quota(BaseModel):
|
|||||||
return super(Quota, self).save(*args, **kwargs)
|
return super(Quota, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaStats(BaseModel):
|
||||||
|
quota = models.OneToOneField(
|
||||||
|
Quota,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='stats',
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
total_distributed = models.PositiveBigIntegerField(default=0)
|
||||||
|
remaining = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_inventory = models.PositiveBigIntegerField(default=0)
|
||||||
|
total_sale = models.PositiveBigIntegerField(default=0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Quota: {self.quota.quota_id} stats'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
return super(QuotaStats, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class QuotaIncentiveAssignment(BaseModel):
|
class QuotaIncentiveAssignment(BaseModel):
|
||||||
""" assign incentive plan to quota """
|
""" assign incentive plan to quota """
|
||||||
|
|
||||||
|
|||||||
@@ -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 .models import QuotaDistribution, Quota
|
from .models import QuotaDistribution, Quota, Product
|
||||||
|
from apps.warehouse.models import InventoryQuotaSaleTransaction
|
||||||
|
|
||||||
|
|
||||||
def recalculate_remaining_amount(quota):
|
def recalculate_remaining_amount(quota):
|
||||||
@@ -18,3 +19,18 @@ def recalculate_remaining_amount(quota):
|
|||||||
@receiver(post_delete, sender=QuotaDistribution)
|
@receiver(post_delete, sender=QuotaDistribution)
|
||||||
def update_quota_remaining(sender, instance, **kwargs):
|
def update_quota_remaining(sender, instance, **kwargs):
|
||||||
recalculate_remaining_amount(instance.quota)
|
recalculate_remaining_amount(instance.quota)
|
||||||
|
|
||||||
|
|
||||||
|
def update_product_stats(instance: Product):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def update_quota_stats(instance: Quota):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver([post_save, post_delete], sender=QuotaDistribution)
|
||||||
|
@receiver([post_save, post_delete], sender=InventoryQuotaSaleTransaction)
|
||||||
|
def update_stats_on_change(sender, instance, **kwargs):
|
||||||
|
update_product_stats(instance)
|
||||||
|
update_quota_stats(instance)
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ class ProductSerializer(serializers.ModelSerializer):
|
|||||||
return representation
|
return representation
|
||||||
|
|
||||||
|
|
||||||
|
class ProductStatsSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = product_models.ProductStats
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class AttributeSerializer(serializers.ModelSerializer):
|
class AttributeSerializer(serializers.ModelSerializer):
|
||||||
""" serialize attributes of reference product """
|
""" serialize attributes of reference product """
|
||||||
|
|
||||||
@@ -61,7 +67,6 @@ class AttributeValueSerializer(serializers.ModelSerializer):
|
|||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
|
||||||
instance.quota = validated_data.get('quota', instance.quota)
|
instance.quota = validated_data.get('quota', instance.quota)
|
||||||
instance.attribute = validated_data.get('attribute', instance.attribute)
|
instance.attribute = validated_data.get('attribute', instance.attribute)
|
||||||
instance.value = validated_data.get('value', instance.value)
|
instance.value = validated_data.get('value', instance.value)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from django.db import models
|
|||||||
from apps.product.exceptions import (
|
from apps.product.exceptions import (
|
||||||
QuotaWeightException,
|
QuotaWeightException,
|
||||||
QuotaClosedException,
|
QuotaClosedException,
|
||||||
QuotaExpiredTimeException
|
QuotaExpiredTimeException,
|
||||||
|
QuotaLimitByOrganizationException
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -21,12 +22,15 @@ class QuotaDistributionSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
"""
|
"""
|
||||||
to validate if distribution weight
|
@ to validate if distribution weight
|
||||||
more than quota weight raise exception
|
more than quota weight raise exception
|
||||||
or if quota is closed raise exception
|
@ if quota is closed raise exception
|
||||||
|
@ if quota has organization limit, before distribution
|
||||||
|
check assigned organization
|
||||||
"""
|
"""
|
||||||
|
|
||||||
quota = data['quota']
|
quota = data['quota']
|
||||||
|
assigned_organization = data['assigned_organization']
|
||||||
amount = data['weight']
|
amount = data['weight']
|
||||||
instance_id = self.instance.id if self.instance else None
|
instance_id = self.instance.id if self.instance else None
|
||||||
|
|
||||||
@@ -38,6 +42,9 @@ class QuotaDistributionSerializer(serializers.ModelSerializer):
|
|||||||
if quota.is_closed:
|
if quota.is_closed:
|
||||||
raise QuotaClosedException()
|
raise QuotaClosedException()
|
||||||
|
|
||||||
|
if assigned_organization not in quota.limit_by_organizations.all():
|
||||||
|
raise QuotaLimitByOrganizationException()
|
||||||
|
|
||||||
# total quota distributions weight
|
# total quota distributions weight
|
||||||
total = product_models.QuotaDistribution.objects.filter(
|
total = product_models.QuotaDistribution.objects.filter(
|
||||||
quota=quota
|
quota=quota
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ class QuotaSerializer(serializers.ModelSerializer):
|
|||||||
instance.group = validated_data.get('group', instance.group)
|
instance.group = validated_data.get('group', instance.group)
|
||||||
instance.has_distribution_limit = validated_data.get('has_distribution_limit', instance.has_distribution_limit)
|
instance.has_distribution_limit = validated_data.get('has_distribution_limit', instance.has_distribution_limit)
|
||||||
instance.distribution_mode = validated_data.get('distribution_mode', instance.distribution_mode)
|
instance.distribution_mode = validated_data.get('distribution_mode', instance.distribution_mode)
|
||||||
|
instance.has_organization_limit = validated_data.get('has_organization_limit', instance.has_organization_limit)
|
||||||
|
instance.limit_by_organizations = validated_data.get('limit_by_organizations', instance.limit_by_organizations)
|
||||||
instance.base_price_factory = validated_data.get('base_price_factory', instance.base_price_factory)
|
instance.base_price_factory = validated_data.get('base_price_factory', instance.base_price_factory)
|
||||||
instance.base_price_cooperative = validated_data.get('base_price_cooperative', instance.base_price_cooperative)
|
instance.base_price_cooperative = validated_data.get('base_price_cooperative', instance.base_price_cooperative)
|
||||||
instance.final_price = validated_data.get('final_price', instance.final_price)
|
instance.final_price = validated_data.get('final_price', instance.final_price)
|
||||||
@@ -74,6 +76,12 @@ class QuotaSerializer(serializers.ModelSerializer):
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaStatsSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = product_models.QuotaStats
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer):
|
class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = product_models.QuotaIncentiveAssignment
|
model = product_models.QuotaIncentiveAssignment
|
||||||
|
|||||||
Reference in New Issue
Block a user