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

@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
import os.path import os.path
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
import sentry_sdk
from django.conf import settings from django.conf import settings
@@ -27,6 +28,22 @@ SECRET_KEY = 'django-insecure-@0apn-lk85pfw=z00x2ib$w9#rwz8%2v4i_n^^9jz-m9b+y55*
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
sentry_sdk.init(
dsn="https://e8d8ff4f1bf729370af00b7775be441c@o4509597964697600.ingest.us.sentry.io/4509597966073856",
# Add data like request headers and IP for users,
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
send_default_pii=True,
# Set traces_sample_rate to 1.0 to capture 100%
# of transactions for tracing.
traces_sample_rate=1.0,
# Set profile_session_sample_rate to 1.0 to profile 100%
# of profile sessions.
profile_session_sample_rate=1.0,
# Set profile_lifecycle to "trace" to automatically
# run the profiler on when there is an active transaction
profile_lifecycle="trace",
)
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
'localhost', 'localhost',
'127.0.0.1', '127.0.0.1',
@@ -131,16 +148,18 @@ AUTH_USER_MODEL = 'authentication.User'
SWAGGER_SETTINGS = { SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': { 'SECURITY_DEFINITIONS': {
'Bearer': { "bearer": {
'type': 'apiKey', "type": "apiKey",
'name': 'Authorization', "name": "Authorization",
'in': 'header' "in": "header",
"description": 'JWT Authorization header using the Bearer scheme. Example: "Authorization: Bearer <token>"',
}, },
'basic': { # <<-- is for djagno authentication 'basic': { # <<-- is for djagno authentication
'type': 'basic' 'type': 'basic'
}, },
}, },
'USE_SESSION_AUTH': True, 'USE_SESSION_AUTH': False,
"DEFAULT_AUTO_SCHEMA_CLASS": "drf_yasg.inspectors.SwaggerAutoSchema"
} }
LOGIN_URL = 'rest_framework:login' LOGIN_URL = 'rest_framework:login'
@@ -159,7 +178,7 @@ REST_FRAMEWORK = {
'django_filters.rest_framework.DjangoFilterBackend', 'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter', 'rest_framework.filters.SearchFilter',
], ],
'EXCEPTION_HANDLER': 'apps.core.error_handler.custom_exception_handler', # 'EXCEPTION_HANDLER': 'apps.core.error_handler.custom_exception_handler',
"DEFAULT_PAGINATION_CLASS": 'apps.core.pagination.CustomPageNumberPagination', "DEFAULT_PAGINATION_CLASS": 'apps.core.pagination.CustomPageNumberPagination',
"PAGE_SIZE": 20, "PAGE_SIZE": 20,
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'

View File

@@ -38,5 +38,6 @@ urlpatterns = [
path('tag/', include('apps.tag.urls')), path('tag/', include('apps.tag.urls')),
path('search/', include('apps.search.urls')), path('search/', include('apps.search.urls')),
path('product/', include('apps.product.urls')), path('product/', include('apps.product.urls')),
path('warehouse/', include('apps.warehouse.urls')),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
] ]

View File

@@ -98,9 +98,9 @@ class UserViewSet(ModelViewSet):
Customizing update user & bank account info with Customizing update user & bank account info with
permission levels permission levels
""" """
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data, instance=self.get_object(), partial=True)
if serializer.is_valid(): if serializer.is_valid():
user = serializer.update(self.queryset.get(id=pk), validated_data=request.data) user = serializer.save()
if 'organization' in request.data.keys(): # noqa if 'organization' in request.data.keys(): # noqa
organization = CustomOperations().custom_update( # update organization for user organization = CustomOperations().custom_update( # update organization for user

View File

@@ -138,8 +138,8 @@ class UserSerializer(serializers.ModelSerializer):
instance.ownership = validated_data.get('ownership') instance.ownership = validated_data.get('ownership')
instance.address = validated_data.get('address') instance.address = validated_data.get('address')
instance.photo = validated_data.get('photo') instance.photo = validated_data.get('photo')
instance.province = Province.objects.get(id=validated_data.get('province')) instance.province = validated_data.get('province', instance.province)
instance.city = City.objects.get(id=validated_data.get('city')) instance.city = validated_data.get('province', instance.province)
instance.otp_status = validated_data.get('otp_status') instance.otp_status = validated_data.get('otp_status')
instance.save() instance.save()
@@ -213,14 +213,10 @@ class OrganizationSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data): def update(self, instance, validated_data):
""" update user organization information """ # noqa """ update user organization information """ # noqa
instance.name = validated_data.get('name', instance.name) instance.name = validated_data.get('name', instance.name)
if validated_data.get('type'): instance.type = validated_data.get('type', instance.type)
instance.type = validated_data['type'] instance.province = validated_data.get('province', instance.province)
if validated_data.get('province'): instance.city = validated_data.get('city', instance.city)
instance.province = validated_data['province'] instance.parent_organization = validated_data.get('parent_organization', instance.parent_organization)
if validated_data.get('city'):
instance.city = validated_data['city']
if validated_data.get('parent_organization'):
instance.parent_organization = validated_data['parent_organization']
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

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.0 on 2025-06-28 11:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0022_alter_user_mobile_alter_user_national_code'),
]
operations = [
migrations.AlterField(
model_name='organization',
name='company_code',
field=models.CharField(default='', max_length=30),
),
migrations.AlterField(
model_name='organization',
name='field_of_activity',
field=models.CharField(choices=[('CO', 'Country'), ('PR', 'Province'), ('CI', 'City')], default='', max_length=2),
),
]

View File

@@ -105,8 +105,8 @@ class Organization(BaseModel):
('PR', 'Province'), ('PR', 'Province'),
('CI', 'City') ('CI', 'City')
) )
field_of_activity = models.CharField(max_length=2, choices=activity_fields, default='EM') field_of_activity = models.CharField(max_length=2, choices=activity_fields, default="")
company_code = models.CharField(max_length=30, default="empty") company_code = models.CharField(max_length=30, default="")
province = models.ForeignKey( province = models.ForeignKey(
Province, Province,
on_delete=models.CASCADE, on_delete=models.CASCADE,

View File

@@ -146,10 +146,8 @@ class UserRelationSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data): def update(self, instance, validated_data):
""" update user relation object """ """ update user relation object """
if validated_data.get('role'): instance.role = validated_data.get('role', instance.role)
instance.role = Role.objects.get(id=validated_data.get("role")) instance.organization = validated_data.get('organization', instance.organization)
if validated_data.get('organization'):
instance.organization = Organization.objects.get(id=validated_data.get('organization'))
instance.save() instance.save()
instance.permissions.clear() instance.permissions.clear()
instance.permissions.add(*(validated_data.get('permissions', instance.permissions))) instance.permissions.add(*(validated_data.get('permissions', instance.permissions)))

View File

@@ -32,8 +32,8 @@ class BaseModel(models.Model):
self.modified_by = user self.modified_by = user
if not self.creator_info: if not self.creator_info:
self.created_by = user self.created_by = user
self.creator_info = user.first_name + ' ' + user.last_name + '-' + user.national_code self.creator_info = user.first_name + ' ' + user.last_name
self.modifier_info = user.first_name + ' ' + user.last_name + '-' + user.national_code self.modifier_info = user.first_name + ' ' + user.last_name
super(BaseModel, self).save(*args, **kwargs) super(BaseModel, self).save(*args, **kwargs)

View File

@@ -12,5 +12,5 @@ schema_view = get_schema_view(
license=openapi.License(name="BSD License"), license=openapi.License(name="BSD License"),
), ),
public=True, public=True,
permission_classes=[permissions.BasePermission, permissions.IsAuthenticated] # permission_classes=[permissions.BasePermission, permissions.IsAuthenticated]
) )

View File

@@ -6,5 +6,21 @@ class QuotaWeightException(APIException):
""" if quota distributions weight is more """ """ if quota distributions weight is more """
status_code = status.HTTP_400_BAD_REQUEST 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' 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 django.db import models
from apps.core.models import BaseModel from apps.core.models import BaseModel
from apps.authorization.models import UserRelations from apps.authorization.models import UserRelations
from apps.authentication.models import Organization
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from datetime import datetime
import jdatetime
class LivestockGroup(models.TextChoices): class LivestockGroup(models.TextChoices):
@@ -74,6 +79,41 @@ class Product(BaseModel):
next_code = 10 next_code = 10
return next_code 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): def __str__(self):
return f'name: {self.name} - type: {self.type}' return f'name: {self.name} - type: {self.type}'
@@ -159,8 +199,8 @@ class Broker(BaseModel):
related_name='product_broker', related_name='product_broker',
null=True null=True
) )
organization_relations = models.ForeignKey( organization = models.ForeignKey(
UserRelations, Organization,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='product_organization', related_name='product_organization',
null=True null=True
@@ -174,7 +214,7 @@ class Broker(BaseModel):
required = models.BooleanField(default=False) required = models.BooleanField(default=False)
def __str__(self): 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): def save(self, *args, **kwargs):
return super(Broker, self).save(*args, **kwargs) return super(Broker, self).save(*args, **kwargs)
@@ -245,13 +285,13 @@ class Quota(BaseModel):
""" quota for product with some conditions """ """ quota for product with some conditions """
registerer_organization = models.ForeignKey( registerer_organization = models.ForeignKey(
UserRelations, Organization,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='quotas', related_name='quotas',
null=True null=True
) )
assigned_organizations = models.ManyToManyField( assigned_organizations = models.ManyToManyField(
UserRelations, Organization,
related_name='assigned_quotas', related_name='assigned_quotas',
blank=True blank=True
) )
@@ -260,7 +300,6 @@ class Quota(BaseModel):
quota_weight = models.PositiveIntegerField(default=0) quota_weight = models.PositiveIntegerField(default=0)
remaining_weight = models.PositiveBigIntegerField(default=0) remaining_weight = models.PositiveBigIntegerField(default=0)
quota_distributed = models.PositiveIntegerField(default=0) quota_distributed = models.PositiveIntegerField(default=0)
quota_balance = models.PositiveIntegerField(default=0)
product = models.ForeignKey( product = models.ForeignKey(
Product, Product,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -279,6 +318,8 @@ class Quota(BaseModel):
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)
is_closed = models.BooleanField(default=False)
closed_at = models.DateTimeField(null=True, blank=True)
def __str__(self): def __str__(self):
return f"Quota ({self.id}) for {self.product.name}" 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 distributed_weight = self.distributions_assigned.aggregate(total=models.Sum("weight"))["total"] or 0
return self.quota_weight - distributed_weight 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): def save(self, calculate_final_price=None, *args, **kwargs):
if not self.quota_id: if not self.quota_id:
self.quota_id = self.generate_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) value = models.DecimalField(max_digits=12, decimal_places=2)
def __str__(self): 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): def save(self, *args, **kwargs):
return super(QuotaBrokerValue, self).save(*args, **kwargs) return super(QuotaBrokerValue, self).save(*args, **kwargs)
@@ -379,13 +427,16 @@ class QuotaLivestockAllocation(BaseModel):
related_name="livestock_allocations", related_name="livestock_allocations",
null=True null=True
) )
livestock_group = models.CharField(max_length=20, choices=LivestockGroup.choices) livestock_group = models.CharField(max_length=20, choices=LivestockGroup.choices, null=True)
livestock_type = models.CharField(max_length=20, choices=LivestockType.choices) livestock_type = models.CharField(max_length=20, choices=LivestockType.choices, null=True)
livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices) livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices, null=True)
quantity_kg = models.DecimalField(max_digits=12, decimal_places=2) quantity_kg = models.DecimalField(max_digits=12, decimal_places=2, null=True)
"""
@using for set unique values between fields
class Meta: class Meta:
unique_together = ('quota', 'livestock_group', 'livestock_type', 'livestock_subtype') unique_together = ('quota', 'livestock_group', 'livestock_type', 'livestock_subtype')
"""
def __str__(self): def __str__(self):
return f"{self.livestock_group} - {self.livestock_type}/{self.livestock_subtype}: {self.quantity_kg}kg" 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', related_name='livestock_age_limitations',
null=True null=True
) )
livestock_type = models.CharField(max_length=20, choices=LivestockType.choices) livestock_type = models.CharField(max_length=20, choices=LivestockType.choices, null=True)
livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices) livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices, null=True)
age_month = models.PositiveIntegerField(default=0) age_month = models.PositiveIntegerField(default=0)
def __str__(self): def __str__(self):
@@ -414,11 +465,17 @@ class QuotaLiveStockAgeLimitation(BaseModel):
class QuotaDistribution(BaseModel): class QuotaDistribution(BaseModel):
assigner_organization = models.ForeignKey( assigner_organization = models.ForeignKey(
UserRelations, Organization,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='distributions_assigner', related_name='distributions_assigner',
null=True null=True
) )
assigned_organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name='distributions',
null=True
)
description = models.TextField(max_length=1000, null=True) description = models.TextField(max_length=1000, null=True)
distribution_id = models.CharField(max_length=20, null=True) distribution_id = models.CharField(max_length=20, null=True)
quota = models.ForeignKey( quota = models.ForeignKey(
@@ -427,19 +484,32 @@ class QuotaDistribution(BaseModel):
related_name='distributions_assigned', related_name='distributions_assigned',
null=True null=True
) )
assigned_organization = models.ForeignKey(
UserRelations,
on_delete=models.CASCADE,
related_name='distributions',
null=True
)
weight = models.PositiveBigIntegerField(default=0) weight = models.PositiveBigIntegerField(default=0)
warehouse_entry = models.PositiveBigIntegerField(default=0) warehouse_entry = models.PositiveBigIntegerField(default=0)
warehouse_balance = models.PositiveBigIntegerField(default=0) warehouse_balance = models.PositiveBigIntegerField(default=0)
been_sold = 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): def __str__(self):
return f"{self.distribution_id}-{self.assigned_organization.organization.name}" return f"{self.distribution_id}-"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.distribution_id:
self.distribution_id = self.generate_distribution_id()
return super(QuotaDistribution, self).save(*args, **kwargs) return super(QuotaDistribution, self).save(*args, **kwargs)

View File

@@ -10,7 +10,8 @@ def recalculate_remaining_amount(quota):
)['total'] or 0 )['total'] or 0
quota.remaining_weight = quota.quota_weight - total_distributed 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) @receiver(post_save, sender=QuotaDistribution)

View File

@@ -1,6 +1,7 @@
import datetime import datetime
from apps.product.web.api.v1 import product_serializers as product_serializers 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 rest_framework.exceptions import APIException
from apps.product import models as product_models from apps.product import models as product_models
from rest_framework.response import Response from rest_framework.response import Response
@@ -10,6 +11,7 @@ from rest_framework import viewsets
from rest_framework import status from rest_framework import status
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from datetime import datetime
def trash(queryset, pk): # noqa def trash(queryset, pk): # noqa
@@ -65,6 +67,10 @@ class ProductViewSet(viewsets.ModelViewSet):
queryset = product_models.Product.objects.all() queryset = product_models.Product.objects.all()
serializer_class = product_serializers.ProductSerializer 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( @action(
methods=['put'], methods=['put'],
detail=True, detail=True,
@@ -346,24 +352,26 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
user_relation = request.user.user_relation.all().first() user_relation = request.user.user_relation.all().first()
# add user relation to data # add user relation to data
request.data['registerer_organization'] = user_relation.id request.data['registerer_organization'] = user_relation.organization.id
# create quota # create quota
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
quota = serializer.save() quota = serializer.save()
quota.remaining_quota_weight = quota.quota_weight quota.remaining_weight = quota.quota_weight
# create incentive plan # create incentive plan
plans_list = []
if 'incentive_plan_data' in request.data.keys(): if 'incentive_plan_data' in request.data.keys():
incentive_plan = CustomOperations().custom_create( for plan in request.data['incentive_plan_data']:
request=request, plan.update({'quota': quota.id})
view=QuotaIncentiveAssignmentViewSet(), incentive_plan = CustomOperations().custom_create(
data_key='incentive_plan_data', request=request,
additional_data={'quota': quota.id} view=QuotaIncentiveAssignmentViewSet(),
) data_key='incentive_plan_data',
else: data=plan
incentive_plan = {} )
plans_list.append(incentive_plan)
# create product price attributes for quota # create product price attributes for quota
attributes_value_list = [] attributes_value_list = []
@@ -376,7 +384,6 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
data=attr data=attr
) )
attributes_value_list.append(attributes) attributes_value_list.append(attributes)
# create product broker values for quota # create product broker values for quota
broker_data_list = [] broker_data_list = []
if 'broker_data' in request.data.keys(): if 'broker_data' in request.data.keys():
@@ -400,7 +407,6 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
data=ls_alloc data=ls_alloc
) )
allocations_list.append(allocations) allocations_list.append(allocations)
# create livestock age limits for quota # create livestock age limits for quota
livestock_age_limits = [] livestock_age_limits = []
if 'livestock_age_limitations' in request.data.keys(): if 'livestock_age_limitations' in request.data.keys():
@@ -415,19 +421,133 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
data = { data = {
'quota': serializer.data, 'quota': serializer.data,
'incentive_plan': incentive_plan, 'incentive_plan': plans_list, # noqa
'attribute_values': attributes_value_list, 'attribute_values': attributes_value_list,
'broker_values': broker_data_list, 'broker_values': broker_data_list,
'live_stock_allocations': allocations_list, 'live_stock_allocations': allocations_list,
'livestock_age_limitations': livestock_age_limits 'livestock_age_limitations': livestock_age_limits
} }
# call save method to generate id & calculate quota final price # call save method to generate id & calculate quota final price
quota.save(calculate_final_price=True) quota.save(calculate_final_price=True)
return Response(data, status=status.HTTP_201_CREATED) return Response(data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN)
@transaction.atomic @transaction.atomic
def update(self, request, *args, **kwargs): def update(self, request, pk=None, *args, **kwargs):
pass # 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( @action(
methods=['get'], methods=['get'],
@@ -440,7 +560,7 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
""" list of quotas for creator """ """ list of quotas for creator """
assigner = product_models.UserRelations.objects.filter(user=request.user).first() assigner = product_models.UserRelations.objects.filter(user=request.user).first()
serializers = self.serializer_class( serializers = self.serializer_class(
self.queryset.filter(registerer_organization=assigner), self.queryset.filter(registerer_organization=assigner.organization),
many=True many=True
).data ).data
return Response(serializers.data, status=status.HTTP_200_OK) return Response(serializers.data, status=status.HTTP_200_OK)
@@ -456,7 +576,7 @@ class QuotaViewSet(viewsets.ModelViewSet): # noqa
""" list of quotas for assigned organizations """ """ list of quotas for assigned organizations """
assigned = product_models.UserRelations.objects.filter(user=request.user).first() assigned = product_models.UserRelations.objects.filter(user=request.user).first()
serializer = self.serializer_class( serializer = self.serializer_class(
self.queryset.filter(assigned_organizations=assigned), self.queryset.filter(assigned_organizations=assigned.organization),
many=True many=True
) )

View File

@@ -1,6 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from apps.product import models as product_models from apps.product import models as product_models
from apps.authorization.api.v1 import serializers as authorize_serializers from apps.authorization.api.v1 import serializers as authorize_serializers
from apps.authentication.api.v1.serializers.serializer import OrganizationSerializer
class ProductCategorySerializer(serializers.ModelSerializer): class ProductCategorySerializer(serializers.ModelSerializer):
@@ -53,16 +54,14 @@ class AttributeValueSerializer(serializers.ModelSerializer):
model = product_models.AttributeValue model = product_models.AttributeValue
fields = '__all__' fields = '__all__'
def to_representation(self, instance): def update(self, instance, validated_data):
""" Custom output """
representation = super().to_representation(instance) instance.quota = validated_data.get('quota', instance.quota)
if instance.quota: instance.attribute = validated_data.get('attribute', instance.attribute)
representation['quota'] = QuotaSerializer(instance.quota).data instance.value = validated_data.get('value', instance.value)
if instance.attribute: instance.save()
representation['attribute'] = AttributeSerializer(instance.attribute).data
return representation return instance
class BrokerSerializer(serializers.ModelSerializer): class BrokerSerializer(serializers.ModelSerializer):
@@ -74,9 +73,9 @@ class BrokerSerializer(serializers.ModelSerializer):
def to_representation(self, instance): def to_representation(self, instance):
representation = super().to_representation(instance) representation = super().to_representation(instance)
if instance.organization_relations: if instance.organization:
representation['organization_relations'] = authorize_serializers.UserRelationSerializer( representation['organization'] = OrganizationSerializer(
instance.organization_relations instance.organization
).data ).data
return representation return representation
@@ -109,6 +108,66 @@ class QuotaSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = product_models.Quota model = product_models.Quota
fields = '__all__' 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): class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer):
@@ -116,20 +175,74 @@ class QuotaIncentiveAssignmentSerializer(serializers.ModelSerializer):
model = product_models.QuotaIncentiveAssignment model = product_models.QuotaIncentiveAssignment
fields = '__all__' 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 QuotaBrokerValueSerializer(serializers.ModelSerializer): # noqa
class Meta: class Meta:
model = product_models.QuotaBrokerValue model = product_models.QuotaBrokerValue
fields = '__all__' 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 QuotaLiveStockAllocationSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = product_models.QuotaLivestockAllocation model = product_models.QuotaLivestockAllocation
fields = '__all__' 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 QuotaLiveStockAgeLimitationSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = product_models.QuotaLiveStockAgeLimitation model = product_models.QuotaLiveStockAgeLimitation
fields = '__all__' 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: except APIException as e:
raise APIException("unauthorized", code=status.HTTP_401_UNAUTHORIZED) 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) serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True): if serializer.is_valid(raise_exception=True):
@@ -77,6 +77,14 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet):
@transaction.atomic @transaction.atomic
def trash(self, request, pk=None): def trash(self, request, pk=None):
""" Sent quota distribution to trash """ """ 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: try:
trash(self.queryset, pk) trash(self.queryset, pk)
except APIException as e: except APIException as e:
@@ -92,6 +100,14 @@ class QuotaDistributionViewSet(viewsets.ModelViewSet):
@transaction.atomic @transaction.atomic
def delete(self, request, pk=None): def delete(self, request, pk=None):
""" Full delete of quota distribution object """ """ 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: try:
delete(self.queryset, pk) delete(self.queryset, pk)
return Response(status=status.HTTP_200_OK) 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 import models as product_models
from apps.product.web.api.v1.product_serializers import QuotaSerializer from apps.product.web.api.v1.product_serializers import QuotaSerializer
from django.db import models from django.db import models
from apps.product.exceptions import QuotaWeightException from apps.product.exceptions import (
QuotaWeightException,
QuotaClosedException,
QuotaExpiredTimeException
)
from rest_framework import status from rest_framework import status
@@ -17,13 +21,25 @@ class QuotaDistributionSerializer(serializers.ModelSerializer):
} }
def validate(self, data): 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'] quota = data['quota']
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
# 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( total = product_models.QuotaDistribution.objects.filter(
quota=quota quota=quota
).exclude(id=instance_id).aggregate( ).exclude(id=instance_id).aggregate(

View File

@@ -4,3 +4,6 @@ from django.apps import AppConfig
class WarehouseConfig(AppConfig): class WarehouseConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.warehouse' name = 'apps.warehouse'
def ready(self):
import apps.warehouse.signals

View File

@@ -0,0 +1,14 @@
from rest_framework.exceptions import APIException
from rest_framework import status
class InventoryEntryWeightException(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "مقدار وارد شده برای ورودی به انبار از مقدار کل سهمیه توزیع داده شده بیشتر است" # noqa
default_code = 'error'
class TotalInventorySaleException(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "مقدار وارد شده برای فروش از انبار از موجودی انبار بیشتر میباشد" # noqa
default_code = 'error'

View File

@@ -0,0 +1,59 @@
# Generated by Django 5.0 on 2025-06-28 10:53
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('authorization', '0019_page_is_active_permissions_is_active'),
('product', '0029_remove_quota_assigned_organizations_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='WareHouse',
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)),
('name', models.CharField(max_length=250, null=True)),
('address', models.TextField(blank=True, null=True)),
('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)),
('user_relation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='warehouse', to='authorization.userrelations')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='InventoryEntry',
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)),
('document', models.CharField(max_length=250, null=True)),
('is_confirmed', models.BooleanField(default=False)),
('notes', models.TextField(blank=True, null=True)),
('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)),
('distribution', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_entry', 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)),
('warehouse', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory', to='warehouse.warehouse')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.0 on 2025-06-28 12:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='warehouse',
name='address',
),
migrations.RemoveField(
model_name='warehouse',
name='name',
),
migrations.RemoveField(
model_name='warehouse',
name='user_relation',
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.0 on 2025-06-28 12:05
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0023_alter_organization_company_code_and_more'),
('warehouse', '0002_remove_warehouse_address_remove_warehouse_name_and_more'),
]
operations = [
migrations.RemoveField(
model_name='inventoryentry',
name='distribution',
),
migrations.AddField(
model_name='warehouse',
name='organization',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='warehouse', to='authentication.organization'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.0 on 2025-06-28 12:06
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0031_broker_organization_quota_assigned_organizations_and_more'),
('warehouse', '0003_remove_inventoryentry_distribution_and_more'),
]
operations = [
migrations.AddField(
model_name='inventoryentry',
name='distribution',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_entry', to='product.quotadistribution'),
),
]

View File

@@ -0,0 +1,48 @@
# Generated by Django 5.0 on 2025-06-29 05:44
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0023_alter_organization_company_code_and_more'),
('product', '0031_broker_organization_quota_assigned_organizations_and_more'),
('warehouse', '0004_inventoryentry_distribution'),
]
operations = [
migrations.RemoveField(
model_name='inventoryentry',
name='warehouse',
),
migrations.AddField(
model_name='inventoryentry',
name='delivery_address',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='inventoryentry',
name='lading_number',
field=models.CharField(max_length=50, null=True),
),
migrations.AddField(
model_name='inventoryentry',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory', to='authentication.organization'),
),
migrations.AddField(
model_name='inventoryentry',
name='weight',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AlterField(
model_name='inventoryentry',
name='distribution',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_entry', to='product.quotadistribution'),
),
migrations.DeleteModel(
name='WareHouse',
),
]

View File

@@ -0,0 +1,68 @@
# Generated by Django 5.0 on 2025-06-29 11:18
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0023_alter_organization_company_code_and_more'),
('authorization', '0019_page_is_active_permissions_is_active'),
('product', '0032_quota_closed_at_quota_is_closed'),
('warehouse', '0005_remove_inventoryentry_warehouse_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='InventoryQuotaSale',
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)),
('weight', models.PositiveBigIntegerField(default=0)),
('herd_owners_number', models.PositiveBigIntegerField(default=0)),
('transactions_number', models.PositiveBigIntegerField(default=0)),
('sale_status', models.BooleanField(default=False)),
('is_active', models.BooleanField(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)),
('inventory_entry', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_sales', to='warehouse.inventoryentry')),
('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_distribution', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_sales', to='product.quotadistribution')),
('seller_organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_sales', to='authentication.organization')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='InventorySaleTransaction',
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)),
('transaction_id', models.CharField(max_length=50, null=True)),
('weight', models.DecimalField(decimal_places=2, max_digits=12)),
('delivery_address', models.TextField(blank=True, null=True)),
('total_price', models.PositiveBigIntegerField(default=0)),
('description', models.TextField(blank=True, null=True)),
('buyer_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='buyer_sale_transactions', to='authorization.userrelations')),
('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.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_transactions', to='product.product')),
('quota_sale', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_transactions', to='warehouse.inventoryquotasale')),
('seller_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='seller_sale_transactions', to='authorization.userrelations')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.0 on 2025-06-29 12:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0006_inventoryquotasale_inventorysaletransaction'),
]
operations = [
migrations.RemoveField(
model_name='inventorysaletransaction',
name='buyer_user',
),
migrations.RemoveField(
model_name='inventorysaletransaction',
name='seller_user',
),
]

View File

@@ -0,0 +1,26 @@
# Generated by Django 5.0 on 2025-06-29 12:22
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0007_remove_inventorysaletransaction_buyer_user_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='inventorysaletransaction',
name='buyer_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='buyer_sale_transactions', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='inventorysaletransaction',
name='seller_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='seller_sale_transactions', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.0 on 2025-06-29 12:46
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0023_alter_organization_company_code_and_more'),
('warehouse', '0008_inventorysaletransaction_buyer_user_and_more'),
]
operations = [
migrations.AddField(
model_name='inventorysaletransaction',
name='buyer_organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_transactions', to='authentication.organization'),
),
]

View File

@@ -0,0 +1,47 @@
# Generated by Django 5.0 on 2025-07-01 07:05
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0009_inventorysaletransaction_buyer_organization'),
]
operations = [
migrations.RemoveField(
model_name='inventorysaletransaction',
name='quota_sale',
),
migrations.RemoveField(
model_name='inventorysaletransaction',
name='buyer_organization',
),
migrations.RemoveField(
model_name='inventorysaletransaction',
name='buyer_user',
),
migrations.RemoveField(
model_name='inventorysaletransaction',
name='created_by',
),
migrations.RemoveField(
model_name='inventorysaletransaction',
name='modified_by',
),
migrations.RemoveField(
model_name='inventorysaletransaction',
name='product',
),
migrations.RemoveField(
model_name='inventorysaletransaction',
name='seller_user',
),
migrations.DeleteModel(
name='InventoryQuotaSale',
),
migrations.DeleteModel(
name='InventorySaleTransaction',
),
]

View File

@@ -0,0 +1,49 @@
# Generated by Django 5.0 on 2025-07-01 07:57
import django.db.models.deletion
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'),
('warehouse', '0010_remove_inventorysaletransaction_quota_sale_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='InventoryQuotaSaleTransaction',
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)),
('transaction_id', models.CharField(max_length=50, null=True)),
('weight', models.DecimalField(decimal_places=2, max_digits=12, null=True)),
('delivery_address', models.TextField(blank=True, null=True)),
('transaction_price', models.PositiveBigIntegerField(default=0)),
('description', models.TextField(blank=True, null=True)),
('herd_owners_number', models.PositiveBigIntegerField(default=0)),
('transactions_number', models.PositiveBigIntegerField(default=0)),
('sale_status', models.BooleanField(default=False)),
('is_active', models.BooleanField(default=0)),
('buyer_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='buyer_sale_transactions', to=settings.AUTH_USER_MODEL)),
('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)),
('inventory_entry', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_sales', to='warehouse.inventoryentry')),
('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.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_transactions', to='product.product')),
('quota_distribution', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_sales', to='product.quotadistribution')),
('seller_organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_sales', to='authentication.organization')),
('seller_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='seller_sale_transactions', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,103 @@
from apps.product import models as product_models
from apps.authentication.models import User
from apps.core.models import BaseModel
from django.db import models
class InventoryEntry(BaseModel):
distribution = models.ForeignKey(
product_models.QuotaDistribution,
on_delete=models.CASCADE,
related_name='inventory_entry',
null=True
)
organization = models.ForeignKey(
product_models.Organization,
on_delete=models.CASCADE,
related_name="inventory",
null=True
)
weight = models.PositiveBigIntegerField(default=0)
balance = models
lading_number = models.CharField(max_length=50, null=True)
delivery_address = models.TextField(blank=True, null=True)
document = models.CharField(max_length=250, null=True)
is_confirmed = models.BooleanField(default=False)
notes = models.TextField(blank=True, null=True)
@property
def total_sold(self):
return self.inventory_sales.aggregate(total=models.Sum('weight'))['total'] or 0
@property
def remaining_weight(self):
return self.weight - self.total_sold
def __str__(self):
return f"distribution: {self.distribution.distribution_id}-{self.organization.name}"
def save(self, *args, **kwargs):
super(InventoryEntry, self).save(*args, **kwargs)
class InventoryQuotaSaleTransaction(BaseModel):
transaction_id = models.CharField(max_length=50, null=True)
seller_organization = models.ForeignKey(
product_models.Organization,
on_delete=models.CASCADE,
related_name='inventory_sales',
null=True
)
quota_distribution = models.ForeignKey(
product_models.QuotaDistribution,
on_delete=models.CASCADE,
related_name='inventory_sales',
null=True
)
inventory_entry = models.ForeignKey(
InventoryEntry,
on_delete=models.CASCADE,
related_name='inventory_sales',
null=True
)
buyer_user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='buyer_sale_transactions',
null=True
)
seller_user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='seller_sale_transactions',
null=True
)
weight = models.DecimalField(max_digits=12, decimal_places=2, null=True)
delivery_address = models.TextField(blank=True, null=True)
product = models.ForeignKey(
product_models.Product,
on_delete=models.CASCADE,
related_name='sale_transactions',
null=True
)
transaction_price = models.PositiveBigIntegerField(default=0)
description = models.TextField(blank=True, null=True)
herd_owners_number = models.PositiveBigIntegerField(default=0)
transactions_number = models.PositiveBigIntegerField(default=0)
sale_status = models.BooleanField(default=False)
is_active = models.BooleanField(default=0)
def buyers_count(self):
""" number of buyers from specific inventory """
unique_buyers_count = self.objects.filter(
inventory_entry=self.inventory_entry
).values('buyer_user').distinct().count()
return unique_buyers_count
def __str__(self):
return f"Inventory Sale: {self.transaction_id}-{self.quota_distribution.distribution_id}"
def save(self, *args, **kwargs):
super(InventoryQuotaSaleTransaction, self).save(*args, **kwargs)

36
apps/warehouse/signals.py Normal file
View File

@@ -0,0 +1,36 @@
from django.db.models import Sum
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from apps.product.models import QuotaDistribution
from .models import InventoryEntry, InventoryQuotaSaleTransaction
def calculate_warehouse_entry(quota_distribution):
total_entry = quota_distribution.inventory_entry.aggregate(
total=Sum('weight')
)['total'] or 0
quota_distribution.warehouse_entry = total_entry
quota_distribution.save(update_fields=['warehouse_entry'])
def warehouse_sold_and_balance(quota_distribution):
total_sold = quota_distribution.inventory_sales.aggregate(
total=Sum('weight')
)['total'] or 0
quota_distribution.been_sold = total_sold
quota_distribution.warehouse_balance = quota_distribution.warehouse_entry - total_sold
quota_distribution.save(update_fields=['been_sold', 'warehouse_balance'])
@receiver(post_save, sender=InventoryEntry)
@receiver(post_delete, sender=InventoryEntry)
def update_distribution_warehouse_entry(sender, instance, **kwargs):
calculate_warehouse_entry(instance.distribution)
@receiver(post_save, sender=InventoryQuotaSaleTransaction)
@receiver(post_delete, sender=InventoryQuotaSaleTransaction)
def update_distribution_warehouse_sold_and_balance(sender, instance, **kwargs):
warehouse_sold_and_balance(instance.quota_distribution)

View File

@@ -1 +1,7 @@
# Your urls go here # Your urls go here
from django.urls import path, include
urlpatterns = [
path('web/api/', include('apps.warehouse.web.api.v1.urls'))
]

View File

@@ -0,0 +1,17 @@
from apps.warehouse.web.api.v1 import serializers as warehouse_serializers
from apps.warehouse import models as warehouse_models
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import viewsets
from django.db import transaction
from rest_framework import status
class InventoryEntryViewSet(viewsets.ModelViewSet):
queryset = warehouse_models.InventoryEntry.objects.all()
serializer_class = warehouse_serializers.InventoryEntrySerializer
class InventoryQuotaSaleTransactionViewSet(viewsets.ModelViewSet):
queryset = warehouse_models.InventoryQuotaSaleTransaction.objects.all()
serializer_class = warehouse_serializers.InventoryQuotaSaleTransactionSerializer

View File

@@ -0,0 +1,90 @@
from apps.warehouse.exceptions import (
InventoryEntryWeightException,
TotalInventorySaleException
)
from apps.product.exceptions import QuotaExpiredTimeException
from apps.warehouse import models as warehouse_models
from apps.authorization.models import UserRelations
from rest_framework import serializers
from django.db import models
class InventoryEntrySerializer(serializers.ModelSerializer):
class Meta:
model = warehouse_models.InventoryEntry
fields = '__all__'
def create(self, validated_data):
""" Custom create & set organization """
distribution = validated_data['distribution']
organization = distribution.assigned_organization
return warehouse_models.InventoryEntry.objects.create(
organization=organization,
**validated_data
)
def validate(self, attrs):
"""
check if inventory entries weight is not more than
distribution weight & check quota expired time
"""
distribution = attrs['distribution']
# check for quota expired time
if not distribution.quota.is_in_valid_time():
raise QuotaExpiredTimeException()
# total inventory entries weight
total_entered = distribution.inventory_entry.filter(is_confirmed=True).aggregate(
total=models.Sum('weight')
)['total'] or 0
if total_entered + attrs['weight'] > distribution.weight:
raise InventoryEntryWeightException()
return attrs
class InventoryQuotaSaleTransactionSerializer(serializers.ModelSerializer):
class Meta:
model = warehouse_models.InventoryQuotaSaleTransaction
fields = '__all__'
depth = 0
def validate(self, attrs):
"""
validate total inventory sale should be fewer than
inventory entry from distribution
"""
inventory_entry = attrs['inventory_entry']
distribution = attrs['quota_distribution']
total_sale_weight = inventory_entry.inventory_sales.aggregate(
total=models.Sum('weight')
)['total'] or 0
if total_sale_weight + attrs['weight'] > distribution.warehouse_balance:
raise TotalInventorySaleException()
return attrs
def create(self, validated_data):
""" Custom create & set some parameters like seller & buyer """
distribution = validated_data['quota_distribution']
seller_organization = distribution.assigned_organization
user = self.context['request'].user
buyer_user = user
seller_user = validated_data['inventory_entry'].created_by
return warehouse_models.InventoryQuotaSaleTransaction.objects.create(
seller_organization=seller_organization,
seller_user=seller_user,
buyer_user=buyer_user,
**validated_data
)

View File

@@ -0,0 +1,16 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.warehouse.web.api.v1 import api
router = DefaultRouter()
router.register(r'inventory_entry', api.InventoryEntryViewSet, basename='inventory_entry')
router.register(
r'inventory_sale_transaction',
api.InventoryQuotaSaleTransactionViewSet,
basename='inventory_sale_transaction'
)
urlpatterns = [
path('v1/', include(router.urls)),
]

View File

@@ -35,7 +35,6 @@ class CustomOperations:
serializer = view.serializer_class(data=data) # noqa serializer = view.serializer_class(data=data) # noqa
serializer.is_valid(raise_exception=True) # noqa serializer.is_valid(raise_exception=True) # noqa
view.perform_create(serializer) # noqa view.perform_create(serializer) # noqa
headers = view.get_success_headers(serializer.data) # noqa headers = view.get_success_headers(serializer.data) # noqa
return serializer.data return serializer.data
@@ -50,16 +49,17 @@ class CustomOperations:
additional_data: dict = None additional_data: dict = None
) -> typing.Any: ) -> typing.Any:
view_data = request.data # included needed data for view set # noqa view_data = request.data # included needed data for view set # noqa
queryset = view.queryset.get(id=obj_id)
if user: if user:
view_data[data_key].update({'user': user.id}) # noqa view_data[data_key].update({'user': user.id}) # noqa
if additional_data: if additional_data:
view_data[data_key].update(additional_data) view_data[data_key].update(additional_data)
if data_key: if data_key:
serializer = view.serializer_class(data=view_data[data_key]) # noqa serializer = view.serializer_class(data=view_data[data_key], instance=queryset, partial=True) # noqa
if data: if data:
serializer = view.serializer_class(data=data) # noqa serializer = view.serializer_class(data=data, instance=queryset, partial=True) # noqa
serializer.is_valid(raise_exception=True) # noqa serializer.is_valid(raise_exception=True) # noqa
serializer.update(view.queryset.get(id=obj_id), view_data[data_key]) # noqa object_data = data if data else view_data[data_key]
# view.perform_update(serializer) # noqa serializer.save() # noqa
headers = view.get_success_headers(serializer.data) # noqa headers = view.get_success_headers(serializer.data) # noqa
return serializer.data return serializer.data

View File

@@ -74,4 +74,5 @@ django_elasticsearch_dsl_drf
django-crum django-crum
django-rest-swagger django-rest-swagger
drf-yasg drf-yasg
tinydb tinydb
django-simple-history