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

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