quota, distribution, inventory entry, quota sale transaction, product informations, signals ,....

This commit is contained in:
2025-07-02 15:42:51 +03:30
parent 2f23c5104d
commit 279afba977
45 changed files with 1408 additions and 88 deletions

View File

@@ -6,5 +6,21 @@ class QuotaWeightException(APIException):
""" if quota distributions weight is more """
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "مقدار وارد شده باعث می‌شود مجموع سهمیه‌ها از مقدار کل سهمیه بیشتر شود." # noqa
default_detail = "مقدار وارد شده باعث می‌شود مجموع سهمیه‌ها از مقدار کل سهمیه بیشتر شود." # noqa
default_code = 'error'
class QuotaClosedException(APIException):
""" if quota is closed, operations can not be done """
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "این سهمیه بسته شده است و قابل توزیع نیست" # noqa
default_code = 'error'
class QuotaExpiredTimeException(APIException):
"""if quota allowed time for distribute, sale, etc. is expired"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "زمان مجوز این سهمیه به پایان رسیده است" # noqa
default_code = 'error'

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.0 on 2025-06-28 10:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0028_quota_remaining_weight'),
]
operations = [
migrations.RemoveField(
model_name='quota',
name='assigned_organizations',
),
migrations.RemoveField(
model_name='quota',
name='registerer_organization',
),
migrations.RemoveField(
model_name='quotadistribution',
name='assigned_organization',
),
migrations.RemoveField(
model_name='quotadistribution',
name='assigner_organization',
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.0 on 2025-06-28 11:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0029_remove_quota_assigned_organizations_and_more'),
]
operations = [
migrations.RemoveField(
model_name='broker',
name='organization_relations',
),
]

View File

@@ -0,0 +1,40 @@
# Generated by Django 5.0 on 2025-06-28 11:04
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0022_alter_user_mobile_alter_user_national_code'),
('product', '0030_remove_broker_organization_relations'),
]
operations = [
migrations.AddField(
model_name='broker',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_organization', to='authentication.organization'),
),
migrations.AddField(
model_name='quota',
name='assigned_organizations',
field=models.ManyToManyField(blank=True, related_name='assigned_quotas', to='authentication.organization'),
),
migrations.AddField(
model_name='quota',
name='registerer_organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quotas', to='authentication.organization'),
),
migrations.AddField(
model_name='quotadistribution',
name='assigned_organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='distributions', to='authentication.organization'),
),
migrations.AddField(
model_name='quotadistribution',
name='assigner_organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='distributions_assigner', to='authentication.organization'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.0 on 2025-06-29 11:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0031_broker_organization_quota_assigned_organizations_and_more'),
]
operations = [
migrations.AddField(
model_name='quota',
name='closed_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='quota',
name='is_closed',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.0 on 2025-06-30 11:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0032_quota_closed_at_quota_is_closed'),
]
operations = [
migrations.AlterField(
model_name='quotalivestockagelimitation',
name='livestock_subtype',
field=models.CharField(choices=[('milking', 'شیری'), ('fattening', 'پرواری')], max_length=20, null=True),
),
migrations.AlterField(
model_name='quotalivestockagelimitation',
name='livestock_type',
field=models.CharField(choices=[('light', 'سبک'), ('heavy', 'سنگین')], max_length=20, null=True),
),
migrations.AlterField(
model_name='quotalivestockallocation',
name='livestock_group',
field=models.CharField(choices=[('rural', 'روستایی'), ('industrial', 'صنعتی'), ('nomadic', 'عشایری')], max_length=20, null=True),
),
migrations.AlterField(
model_name='quotalivestockallocation',
name='livestock_subtype',
field=models.CharField(choices=[('milking', 'شیری'), ('fattening', 'پرواری')], max_length=20, null=True),
),
migrations.AlterField(
model_name='quotalivestockallocation',
name='livestock_type',
field=models.CharField(choices=[('light', 'سبک'), ('heavy', 'سنگین')], max_length=20, null=True),
),
migrations.AlterField(
model_name='quotalivestockallocation',
name='quantity_kg',
field=models.DecimalField(decimal_places=2, max_digits=12, null=True),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.0 on 2025-06-30 11:49
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0033_alter_quotalivestockagelimitation_livestock_subtype_and_more'),
]
operations = [
migrations.AlterUniqueTogether(
name='quotalivestockallocation',
unique_together=set(),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.0 on 2025-07-01 05:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0034_alter_quotalivestockallocation_unique_together'),
]
operations = [
migrations.RemoveField(
model_name='quota',
name='quota_balance',
),
]

View File

@@ -0,0 +1,52 @@
# Generated by Django 5.0 on 2025-07-02 05:18
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0023_alter_organization_company_code_and_more'),
('product', '0035_remove_quota_quota_balance'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='HistoricalQuotaDistribution',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('create_date', models.DateTimeField(blank=True, editable=False)),
('modify_date', models.DateTimeField(blank=True, editable=False)),
('creator_info', models.CharField(max_length=100, null=True)),
('modifier_info', models.CharField(max_length=100, null=True)),
('trash', models.BooleanField(default=False)),
('description', models.TextField(max_length=1000, null=True)),
('distribution_id', models.CharField(max_length=20, null=True)),
('weight', models.PositiveBigIntegerField(default=0)),
('warehouse_entry', models.PositiveBigIntegerField(default=0)),
('warehouse_balance', models.PositiveBigIntegerField(default=0)),
('been_sold', models.PositiveBigIntegerField(default=0)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('assigned_organization', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='authentication.organization')),
('assigner_organization', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='authentication.organization')),
('created_by', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
('quota', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='product.quota')),
],
options={
'verbose_name': 'historical quota distribution',
'verbose_name_plural': 'historical quota distributions',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View File

@@ -1,7 +1,12 @@
import datetime
from simple_history.models import HistoricalRecords
from django.db import models
from apps.core.models import BaseModel
from apps.authorization.models import UserRelations
from apps.authentication.models import Organization
from django.contrib.postgres.fields import ArrayField
from datetime import datetime
import jdatetime
class LivestockGroup(models.TextChoices):
@@ -74,6 +79,41 @@ class Product(BaseModel):
next_code = 10
return next_code
def quota_information(self):
"""
quotas information of product
"""
# number of quotas
quotas_count = self.quotas.filter(is_closed=False).count()
# total weight of product that assigned in quota
total_quotas_weight = self.quotas.filter(is_closed=False).aggregate(
total=models.Sum('quota_weight')
)['total'] or 0
# total remaining weight of product quotas
total_remaining_quotas_weight = self.quotas.filter(is_closed=False).aggregate(
total=models.Sum('remaining_weight')
)['total'] or 0
total_distributed_weight = QuotaDistribution.objects.filter(
quota__product_id=self.id,
quota__is_closed=False
).aggregate(total_weight=models.Sum('weight'))['total_weight'] or 0
total_sold = QuotaDistribution.objects.filter(
quota__product_id=self.id,
quota__is_closed=False
).aggregate(total_sold=models.Sum('been_sold'))['total_sold'] or 0
total_warehouse_entry = QuotaDistribution.objects.filter(
quota__product_id=self.id,
quota__is_closed=False
).aggregate(total_entry=models.Sum('warehouse_entry'))['total_entry'] or 0
return {'quotas_count': quotas_count, 'total_quotas_weight': total_quotas_weight}
def __str__(self):
return f'name: {self.name} - type: {self.type}'
@@ -159,8 +199,8 @@ class Broker(BaseModel):
related_name='product_broker',
null=True
)
organization_relations = models.ForeignKey(
UserRelations,
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name='product_organization',
null=True
@@ -174,7 +214,7 @@ class Broker(BaseModel):
required = models.BooleanField(default=False)
def __str__(self):
return f'{self.organization_relations.organization.name} - {self.product.name}'
return f'{self.organization.name} - {self.product.name}'
def save(self, *args, **kwargs):
return super(Broker, self).save(*args, **kwargs)
@@ -245,13 +285,13 @@ class Quota(BaseModel):
""" quota for product with some conditions """
registerer_organization = models.ForeignKey(
UserRelations,
Organization,
on_delete=models.CASCADE,
related_name='quotas',
null=True
)
assigned_organizations = models.ManyToManyField(
UserRelations,
Organization,
related_name='assigned_quotas',
blank=True
)
@@ -260,7 +300,6 @@ class Quota(BaseModel):
quota_weight = models.PositiveIntegerField(default=0)
remaining_weight = models.PositiveBigIntegerField(default=0)
quota_distributed = models.PositiveIntegerField(default=0)
quota_balance = models.PositiveIntegerField(default=0)
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
@@ -279,6 +318,8 @@ class Quota(BaseModel):
base_price_factory = 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)
is_closed = models.BooleanField(default=False)
closed_at = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f"Quota ({self.id}) for {self.product.name}"
@@ -313,6 +354,13 @@ class Quota(BaseModel):
distributed_weight = self.distributions_assigned.aggregate(total=models.Sum("weight"))["total"] or 0
return self.quota_weight - distributed_weight
def is_in_valid_time(self):
""" check if quota allowed time for distribute, sale, etc is expired"""
now = datetime.now()
persian_date = jdatetime.datetime.fromgregorian(datetime=now)
return persian_date.month in self.month_choices
def save(self, calculate_final_price=None, *args, **kwargs):
if not self.quota_id:
self.quota_id = self.generate_quota_id()
@@ -364,7 +412,7 @@ class QuotaBrokerValue(BaseModel):
value = models.DecimalField(max_digits=12, decimal_places=2)
def __str__(self):
return f"Quota ({self.quota.id}) for Broker({self.broker.organization_relations.organization.name})"
return f"Quota ({self.quota.id}) for Broker({self.broker.organization.name})"
def save(self, *args, **kwargs):
return super(QuotaBrokerValue, self).save(*args, **kwargs)
@@ -379,13 +427,16 @@ class QuotaLivestockAllocation(BaseModel):
related_name="livestock_allocations",
null=True
)
livestock_group = models.CharField(max_length=20, choices=LivestockGroup.choices)
livestock_type = models.CharField(max_length=20, choices=LivestockType.choices)
livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices)
quantity_kg = models.DecimalField(max_digits=12, decimal_places=2)
livestock_group = models.CharField(max_length=20, choices=LivestockGroup.choices, null=True)
livestock_type = models.CharField(max_length=20, choices=LivestockType.choices, null=True)
livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices, null=True)
quantity_kg = models.DecimalField(max_digits=12, decimal_places=2, null=True)
"""
@using for set unique values between fields
class Meta:
unique_together = ('quota', 'livestock_group', 'livestock_type', 'livestock_subtype')
"""
def __str__(self):
return f"{self.livestock_group} - {self.livestock_type}/{self.livestock_subtype}: {self.quantity_kg}kg"
@@ -401,8 +452,8 @@ class QuotaLiveStockAgeLimitation(BaseModel):
related_name='livestock_age_limitations',
null=True
)
livestock_type = models.CharField(max_length=20, choices=LivestockType.choices)
livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices)
livestock_type = models.CharField(max_length=20, choices=LivestockType.choices, null=True)
livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices, null=True)
age_month = models.PositiveIntegerField(default=0)
def __str__(self):
@@ -414,11 +465,17 @@ class QuotaLiveStockAgeLimitation(BaseModel):
class QuotaDistribution(BaseModel):
assigner_organization = models.ForeignKey(
UserRelations,
Organization,
on_delete=models.CASCADE,
related_name='distributions_assigner',
null=True
)
assigned_organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name='distributions',
null=True
)
description = models.TextField(max_length=1000, null=True)
distribution_id = models.CharField(max_length=20, null=True)
quota = models.ForeignKey(
@@ -427,19 +484,32 @@ class QuotaDistribution(BaseModel):
related_name='distributions_assigned',
null=True
)
assigned_organization = models.ForeignKey(
UserRelations,
on_delete=models.CASCADE,
related_name='distributions',
null=True
)
weight = models.PositiveBigIntegerField(default=0)
warehouse_entry = models.PositiveBigIntegerField(default=0)
warehouse_balance = models.PositiveBigIntegerField(default=0)
been_sold = models.PositiveBigIntegerField(default=0)
history = HistoricalRecords()
def generate_distribution_id(self):
""" generate special id for quota distribution """
year = jdatetime.datetime.now().year
month = jdatetime.datetime.now().month
day = jdatetime.datetime.now().day
product_id = self.quota.product.product_id
quota_id = self.quota.quota_id
base_code = f"{str(year)[3]}{month}{day}{product_id}{quota_id}"
similar_codes = QuotaDistribution.objects.filter(distribution_id__startswith=base_code).count()
counter = str(similar_codes + 1).zfill(4)
return f"{base_code}{counter}"
def __str__(self):
return f"{self.distribution_id}-{self.assigned_organization.organization.name}"
return f"{self.distribution_id}-"
def save(self, *args, **kwargs):
if not self.distribution_id:
self.distribution_id = self.generate_distribution_id()
return super(QuotaDistribution, self).save(*args, **kwargs)

View File

@@ -10,7 +10,8 @@ def recalculate_remaining_amount(quota):
)['total'] or 0
quota.remaining_weight = quota.quota_weight - total_distributed
quota.save(update_fields=["remaining_weight"])
quota.quota_distributed = total_distributed
quota.save(update_fields=["remaining_weight", "quota_distributed"])
@receiver(post_save, sender=QuotaDistribution)

View File

@@ -1,6 +1,7 @@
import datetime
from apps.product.web.api.v1 import product_serializers as product_serializers
from apps.product.exceptions import QuotaExpiredTimeException
from rest_framework.exceptions import APIException
from apps.product import models as product_models
from rest_framework.response import Response
@@ -10,6 +11,7 @@ from rest_framework import viewsets
from rest_framework import status
from django.db import transaction
from django.db.models import Q
from datetime import datetime
def trash(queryset, pk): # noqa
@@ -65,6 +67,10 @@ class ProductViewSet(viewsets.ModelViewSet):
queryset = product_models.Product.objects.all()
serializer_class = product_serializers.ProductSerializer
def list(self, request, *args, **kwargs):
product = self.queryset.get(id=1)
return Response(product.quota_information(), status.HTTP_200_OK)
@action(
methods=['put'],
detail=True,
@@ -346,24 +352,26 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
user_relation = request.user.user_relation.all().first()
# add user relation to data
request.data['registerer_organization'] = user_relation.id
request.data['registerer_organization'] = user_relation.organization.id
# create quota
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
quota = serializer.save()
quota.remaining_quota_weight = quota.quota_weight
quota.remaining_weight = quota.quota_weight
# create incentive plan
plans_list = []
if 'incentive_plan_data' in request.data.keys():
incentive_plan = CustomOperations().custom_create(
request=request,
view=QuotaIncentiveAssignmentViewSet(),
data_key='incentive_plan_data',
additional_data={'quota': quota.id}
)
else:
incentive_plan = {}
for plan in request.data['incentive_plan_data']:
plan.update({'quota': quota.id})
incentive_plan = CustomOperations().custom_create(
request=request,
view=QuotaIncentiveAssignmentViewSet(),
data_key='incentive_plan_data',
data=plan
)
plans_list.append(incentive_plan)
# create product price attributes for quota
attributes_value_list = []
@@ -376,7 +384,6 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
data=attr
)
attributes_value_list.append(attributes)
# create product broker values for quota
broker_data_list = []
if 'broker_data' in request.data.keys():
@@ -400,7 +407,6 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
data=ls_alloc
)
allocations_list.append(allocations)
# create livestock age limits for quota
livestock_age_limits = []
if 'livestock_age_limitations' in request.data.keys():
@@ -415,19 +421,133 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
data = {
'quota': serializer.data,
'incentive_plan': incentive_plan,
'incentive_plan': plans_list, # noqa
'attribute_values': attributes_value_list,
'broker_values': broker_data_list,
'live_stock_allocations': allocations_list,
'livestock_age_limitations': livestock_age_limits
}
# call save method to generate id & calculate quota final price
quota.save(calculate_final_price=True)
return Response(data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
@transaction.atomic
def update(self, request, *args, **kwargs):
pass
def update(self, request, pk=None, *args, **kwargs):
# get user relations data like organization
user_relation = request.user.user_relation.all().first()
# add user relation to data
request.data['registerer_organization'] = user_relation.organization.id
# create quota
serializer = self.serializer_class(data=request.data, instance=self.get_object(), partial=True)
if serializer.is_valid():
quota = serializer.save()
# create incentive plan
plans_list = []
if 'incentive_plan_data' in request.data.keys():
for plan in request.data['incentive_plan_data']:
plan.update({'quota': quota.id})
incentive_plan = CustomOperations().custom_update(
request=request,
view=QuotaIncentiveAssignmentViewSet(),
data_key='incentive_plan_data',
obj_id=plan['id'],
data=plan
)
plans_list.append(incentive_plan)
# create product price attributes for quota
attributes_value_list = [] # noqa
if 'price_attributes_data' in request.data.keys():
for attr in request.data['price_attributes_data']:
attr.update({'quota': quota.id})
attributes = CustomOperations().custom_update(
request=request,
view=AttributeValueViewSet(),
obj_id=attr['id'],
data=attr
)
attributes_value_list.append(attributes)
# create product broker values for quota
broker_data_list = []
if 'broker_data' in request.data.keys():
for broker in request.data['broker_data']:
broker.update({'quota': quota.id})
broker_value = CustomOperations().custom_update(
request=request,
view=QuotaBrokerValueViewSet(),
obj_id=broker['id'],
data=broker
)
broker_data_list.append(broker_value)
# create livestock allocations to quota
allocations_list = []
if 'livestock_allocation_data' in request.data.keys():
for ls_alloc in request.data['livestock_allocation_data']:
ls_alloc.update({'quota': quota.id})
allocations = CustomOperations().custom_update(
request=request,
view=QuotaLiveStockAllocationViewSet(),
obj_id=ls_alloc['id'],
data=ls_alloc
)
allocations_list.append(allocations)
# create livestock age limits for quota
livestock_age_limits = []
if 'livestock_age_limitations' in request.data.keys():
for age_limit in request.data['livestock_age_limitations']:
age_limit.update({'quota': quota.id})
age_limit_creation_object = CustomOperations().custom_update(
request=request,
view=QuotaLiveStockAgeLimitation(),
obj_id=age_limit['id'],
data=age_limit
)
livestock_age_limits.append(age_limit_creation_object)
data = {
'quota': serializer.data,
'incentive_plan': plans_list, # noqa
'attribute_values': attributes_value_list,
'broker_values': broker_data_list,
'live_stock_allocations': allocations_list,
'livestock_age_limitations': livestock_age_limits
}
# call save method to generate id & calculate quota final price
quota.save(calculate_final_price=True)
return Response(data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
@action(
methods=['patch'],
detail=True,
url_path='close',
url_name='close',
name='close'
)
@transaction.atomic
def close(self, request, pk=None):
""" to close quota """
quota = self.get_object()
# check quota expired time
if not quota.is_in_valid_time():
raise QuotaExpiredTimeException()
if quota.is_closed:
raise APIException("این سهمیه قبلا بسته شده است", status.HTTP_400_BAD_REQUEST) # noqa
quota.is_closed = True
quota.closed_at = datetime.now()
quota.save()
return Response(status.HTTP_200_OK)
@action(
methods=['get'],
@@ -440,7 +560,7 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
""" list of quotas for creator """
assigner = product_models.UserRelations.objects.filter(user=request.user).first()
serializers = self.serializer_class(
self.queryset.filter(registerer_organization=assigner),
self.queryset.filter(registerer_organization=assigner.organization),
many=True
).data
return Response(serializers.data, status=status.HTTP_200_OK)
@@ -456,7 +576,7 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
""" list of quotas for assigned organizations """
assigned = product_models.UserRelations.objects.filter(user=request.user).first()
serializer = self.serializer_class(
self.queryset.filter(assigned_organizations=assigned),
self.queryset.filter(assigned_organizations=assigned.organization),
many=True
)

View File

@@ -1,6 +1,7 @@
from rest_framework import serializers
from apps.product import models as product_models
from apps.authorization.api.v1 import serializers as authorize_serializers
from apps.authentication.api.v1.serializers.serializer import OrganizationSerializer
class ProductCategorySerializer(serializers.ModelSerializer):
@@ -53,16 +54,14 @@ class AttributeValueSerializer(serializers.ModelSerializer):
model = product_models.AttributeValue
fields = '__all__'
def to_representation(self, instance):
""" Custom output """
def update(self, instance, validated_data):
representation = super().to_representation(instance)
if instance.quota:
representation['quota'] = QuotaSerializer(instance.quota).data
if instance.attribute:
representation['attribute'] = AttributeSerializer(instance.attribute).data
instance.quota = validated_data.get('quota', instance.quota)
instance.attribute = validated_data.get('attribute', instance.attribute)
instance.value = validated_data.get('value', instance.value)
instance.save()
return representation
return instance
class BrokerSerializer(serializers.ModelSerializer):
@@ -74,9 +73,9 @@ class BrokerSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
representation = super().to_representation(instance)
if instance.organization_relations:
representation['organization_relations'] = authorize_serializers.UserRelationSerializer(
instance.organization_relations
if instance.organization:
representation['organization'] = OrganizationSerializer(
instance.organization
).data
return representation
@@ -109,6 +108,66 @@ class QuotaSerializer(serializers.ModelSerializer):
class Meta:
model = product_models.Quota
fields = '__all__'
depth = 0
def to_representation(self, instance):
representation = super().to_representation(instance)
if isinstance(instance, product_models.Quota):
representation['incentive_plan'] = QuotaIncentiveAssignmentSerializer(
instance.incentive_assignments.all(),
many=True
).data
representation['attribute_values'] = AttributeValueSerializer(
instance.attribute_values.all(),
many=True
).data
representation['brokers'] = QuotaBrokerValueSerializer(
instance.broker_values.all(),
many=True
).data
representation['livestock_allocations'] = QuotaLiveStockAllocationSerializer(
instance.livestock_allocations.all(),
many=True
).data
representation['livestock_limitations'] = QuotaLiveStockAgeLimitationSerializer(
instance.livestock_age_limitations.all(),
many=True
).data
return representation
def update(self, instance, validated_data):
""" Custom Update """
instance.quota_id = validated_data.get('quota_id', instance.quota_id)
instance.quota_code = validated_data.get('quota_code', instance.quota_code)
instance.quota_weight = validated_data.get('quota_weight', instance.quota_weight)
instance.remaining_weight = validated_data.get('remaining_weight', instance.remaining_weight)
instance.quota_distributed = validated_data.get('quota_distributed', instance.quota_distributed)
instance.quota_balance = validated_data.get('quota_balance', instance.quota_balance)
instance.product = validated_data.get('product', instance.product)
instance.sale_type = validated_data.get('sale_type', instance.sale_type)
instance.month_choices = validated_data.get('month_choices', instance.month_choices)
instance.group = validated_data.get('group', instance.group)
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.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.final_price = validated_data.get('final_price', instance.final_price)
instance.is_closed = validated_data.get('is_closed', instance.is_closed)
instance.closed_at = validated_data.get('closed_at', instance.closed_at)
instance.save()
instance.assigned_organizations.clear()
instance.assigned_organizations.add(
*(validated_data.get('assigned_organizations', instance.assigned_organizations))
)
return instance
class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer):
@@ -116,20 +175,74 @@ class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer):
model = product_models.QuotaIncentiveAssignment
fields = '__all__'
def update(self, instance, validated_data):
""" Custom Update """
instance.quota = validated_data.get('quota', instance.quota)
instance.incentive_plan = validated_data.get('incentive_plan', instance.incentive_plan)
instance.heavy_value = validated_data.get('heavy_value', instance.heavy_value)
instance.light_value = validated_data.get('light_value', instance.light_value)
instance.save()
return instance
class QuotaBrokerValueSerializer(serializers.ModelSerializer): # noqa
class Meta:
model = product_models.QuotaBrokerValue
fields = '__all__'
def update(self, instance, validated_data):
""" Custom Update """
instance.quota = validated_data.get('quota', instance.quota)
instance.broker = validated_data.get('broker', instance.broker)
instance.value = validated_data.get('value', instance.value)
instance.save()
return instance
class QuotaLiveStockAllocationSerializer(serializers.ModelSerializer):
class Meta:
model = product_models.QuotaLivestockAllocation
fields = '__all__'
extra_kwargs = {
'livestock_group': {
'required': False
},
'livestock_type': {
'required': False
},
'livestock_subtype': {
'required': False
}
}
def update(self, instance, validated_data):
""" Custom Update """
instance.quota = validated_data.get('quota', instance.quota)
instance.livestock_group = validated_data.get('livestock_group', instance.livestock_group)
instance.livestock_type = validated_data.get('livestock_type', instance.livestock_type)
instance.livestock_subtype = validated_data.get('livestock_subtype', instance.livestock_subtype)
instance.save()
return instance
class QuotaLiveStockAgeLimitationSerializer(serializers.ModelSerializer):
class Meta:
model = product_models.QuotaLiveStockAgeLimitation
fields = '__all__'
def update(self, instance, validated_data):
""" Custom Update """
instance.quota = validated_data.get('quota', instance.quota)
instance.livestock_type = validated_data.get('livestock_type', instance.livestock_type)
instance.livestock_subtype = validated_data.get('livestock_subtype', instance.livestock_subtype)
instance.age_month = validated_data.get('age_month', instance.age_month)
instance.save()
return instance

View File

@@ -42,7 +42,7 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet):
except APIException as e:
raise APIException("unauthorized", code=status.HTTP_401_UNAUTHORIZED)
request.data.update({'assigner_organization': assigner_user.id})
request.data.update({'assigner_organization': assigner_user.organization.id})
serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
@@ -77,6 +77,14 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet):
@transaction.atomic
def trash(self, request, pk=None):
""" Sent quota distribution to trash """
quota_distribution = self.get_object()
# check if distribution has inventory entry
if quota_distribution.inventory_entry.exists():
raise APIException(
"امکان حذف این توزیع وجود ندارد. ورود به انبار برای آن ثبت شده است", # noqa
status.HTTP_400_BAD_REQUEST
)
try:
trash(self.queryset, pk)
except APIException as e:
@@ -92,6 +100,14 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet):
@transaction.atomic
def delete(self, request, pk=None):
""" Full delete of quota distribution object """
quota_distribution = self.get_object()
# check if distribution has inventory entry
if quota_distribution.inventory_entry.exists():
raise APIException(
"امکان حذف این توزیع وجود ندارد. ورود به انبار برای آن ثبت شده است", # noqa
status.HTTP_400_BAD_REQUEST
)
try:
delete(self.queryset, pk)
return Response(status=status.HTTP_200_OK)

View File

@@ -2,7 +2,11 @@ from rest_framework import serializers
from apps.product import models as product_models
from apps.product.web.api.v1.product_serializers import QuotaSerializer
from django.db import models
from apps.product.exceptions import QuotaWeightException
from apps.product.exceptions import (
QuotaWeightException,
QuotaClosedException,
QuotaExpiredTimeException
)
from rest_framework import status
@@ -17,13 +21,25 @@ class QuotaDistributionSerializer(serializers.ModelSerializer):
}
def validate(self, data):
""" to validate if distribution weight
more than quota weight raise exception """
"""
to validate if distribution weight
more than quota weight raise exception
or if quota is closed raise exception
"""
quota = data['quota']
amount = data['weight']
instance_id = self.instance.id if self.instance else None
# check quota expired time
if not quota.is_in_valid_time():
raise QuotaExpiredTimeException()
# check if quota is closed
if quota.is_closed:
raise QuotaClosedException()
# total quota distributions weight
total = product_models.QuotaDistribution.objects.filter(
quota=quota
).exclude(id=instance_id).aggregate(