quota, distribution, inventory entry, quota sale transaction, product informations, signals ,....
This commit is contained in:
@@ -4,3 +4,6 @@ from django.apps import AppConfig
|
||||
class WarehouseConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.warehouse'
|
||||
|
||||
def ready(self):
|
||||
import apps.warehouse.signals
|
||||
|
||||
14
apps/warehouse/exceptions.py
Normal file
14
apps/warehouse/exceptions.py
Normal 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'
|
||||
59
apps/warehouse/migrations/0001_initial.py
Normal file
59
apps/warehouse/migrations/0001_initial.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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
36
apps/warehouse/signals.py
Normal 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)
|
||||
@@ -1 +1,7 @@
|
||||
# Your urls go here
|
||||
from django.urls import path, include
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('web/api/', include('apps.warehouse.web.api.v1.urls'))
|
||||
]
|
||||
|
||||
17
apps/warehouse/web/api/v1/api.py
Normal file
17
apps/warehouse/web/api/v1/api.py
Normal 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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user