add - whole system of inventory entry in organization quotas stat

This commit is contained in:
2025-11-19 15:56:15 +03:30
parent 42c01f3eb5
commit 1bf6950ccb
9 changed files with 152 additions and 34 deletions

View File

@@ -474,6 +474,7 @@ class Quota(BaseModel):
"remaining_weight": stat.first().remaining_amount if stat.exists() else 0,
"quota_distributed": stat.first().total_distributed if stat.exists() else 0,
"been_sold": stat.first().sold_amount if stat.exists() else 0,
"inventory_received": stat.first().inventory_received if stat.exists() else 0,
}
def soft_delete(self):

View File

@@ -1,5 +1,6 @@
from apps.product.models import QuotaDistribution, OrganizationQuotaStats
from apps.product.validators.quota_stats_validator import QuotaStatsValidator
from apps.warehouse.models import InventoryEntry
class QuotaStatsService:
@@ -102,3 +103,53 @@ class QuotaStatsService:
assigned_stat.remaining_amount -= distribution.weight
assigned_stat.distributions.remove(distribution)
assigned_stat.save()
@staticmethod
def apply_inventory_entry(entry: InventoryEntry, created):
quota = entry.quota
weight = entry.weight
if not created:
old_weight = entry._old_weight # noqa
diff = weight - old_weight
else:
diff = weight
print("0000", diff)
QuotaStatsService._propagate_inventory(
organization=entry.organization,
quota=quota,
diff=diff
)
@staticmethod
def remove_inventory_entry(entry: InventoryEntry):
quota = entry.quota
weight = entry.weight
QuotaStatsService._propagate_inventory(
organization=entry.organization,
quota=quota,
diff=-weight
)
@staticmethod
def _propagate_inventory(organization, quota, diff):
org = organization
while org:
stat = OrganizationQuotaStats.objects.filter(
organization=org,
quota=quota
).first()
if stat:
print(stat.id)
print(org.id, quota.id)
stat.inventory_received = (stat.inventory_received or 0) + diff
print(stat.remaining_amount)
stat.remaining_amount = stat.remaining_amount - diff
if stat.inventory_received < 0:
stat.inventory_received = 0
stat.save(update_fields=['inventory_received', 'remaining_amount'])
org = org.parent_organization

View File

@@ -33,6 +33,7 @@ class QuotaSerializer(serializers.ModelSerializer):
representation['quota_distributed'] = quota_weight_by_org['quota_distributed']
representation['remaining_weight'] = quota_weight_by_org['remaining_weight']
representation['been_sold'] = quota_weight_by_org['been_sold']
representation['inventory_received'] = quota_weight_by_org['inventory_received']
representation['distributions_number_by_me'] = instance.distributions_assigned.filter(
assigner_organization=org
).count()

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.0 on 2025-11-19 10:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0096_organizationquotastats_inventory_received'),
('warehouse', '0042_inventoryentryallocation'),
]
operations = [
migrations.AddField(
model_name='inventoryentry',
name='quota',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_entry', to='product.quota'),
),
]

View File

@@ -19,6 +19,12 @@ class InventoryEntry(BaseModel):
related_name='inventory_entry',
null=True
)
quota = models.ForeignKey(
product_models.Quota,
on_delete=models.CASCADE,
related_name='inventory_entry',
null=True
)
organization = models.ForeignKey(
product_models.Organization,
on_delete=models.CASCADE,
@@ -38,7 +44,7 @@ class InventoryEntry(BaseModel):
# prefix = "POS"
while True:
number_part = ''.join(random.choices(string.digits, k=6))
code = f"{self.distribution.quota.quota_id}{number_part}"
code = f"{self.quota.quota_id}{number_part}"
if not InventoryEntry.objects.filter(entry_identity=code).exists():
return code

View File

@@ -16,24 +16,21 @@ class WarehouseAllocationService:
with transaction.atomic():
distributions = QuotaDistribution.objects.filter(
assigned_organization=entry.organization,
quota=entry.distribution.quota
quota=entry.quota
).select_related('quota').order_by('-create_date')
if not distributions.exists():
raise WareHouseException("توزیعی برای این انبار وجود ندارد", status.HTTP_403_FORBIDDEN) # noqa
remaining = entry.weight
for dist in distributions:
if remaining <= 0:
break
stat = OrganizationQuotaStats.objects.get(
quota=dist.quota,
organization=dist.assigned_organization
)
capacity = stat.remaining_amount
if capacity <= 0:
continue
@@ -46,6 +43,7 @@ class WarehouseAllocationService:
)
stat.inventory_received += allocate_weight
stat.remaining_amount -= allocate_weight
stat.save()
remaining -= allocate_weight
@@ -55,3 +53,13 @@ class WarehouseAllocationService:
"مقدار وارد شده از انبار بیشتر از مقدار کل سهمیه توزیع داده شده است", # noqa
status.HTTP_400_BAD_REQUEST
)
org = entry.organization.parent_organization
while org:
stat = OrganizationQuotaStats.objects.get(
quota=entry.quota,
organization=org
)
stat.inventory_received += entry.weight
stat.save()
org = org.parent_organization

View File

@@ -1,9 +1,11 @@
from django.db.models import Sum
from django.db.models.signals import post_save, post_delete
from django.db.models.signals import post_save, post_delete, post_init
from django.dispatch import receiver
from apps.product.models import QuotaDistribution
from .models import InventoryEntry, InventoryQuotaSaleItem
from .services.warehouse_allocation_service import WarehouseAllocationService
from ..product.services.quota_stat_service import QuotaStatsService
def calculate_warehouse_entry(quota_distribution):
@@ -50,17 +52,34 @@ def warehouse_sold_and_balance(quota_distribution: QuotaDistribution):
quota_distribution.save(update_fields=['been_sold', 'warehouse_balance', 'free_sale_balance', 'pre_sale_balance'])
@receiver(post_init, sender=InventoryEntry)
def inventory_entry_pre_save(sender, instance: InventoryEntry, **kwargs):
if instance.pk:
instance._is_update = True
instance._old_weight = instance.weight
else:
instance._is_update = False
@receiver(post_save, sender=InventoryEntry)
def update_quota_stat_on_entry_update(sender, instance: InventoryEntry, created, **kwargs):
if instance._is_update: # noqa
QuotaStatsService.apply_inventory_entry(instance, created=False)
else:
WarehouseAllocationService.allocate(entry=instance)
@receiver(post_save, sender=InventoryEntry)
def update_quota_stat_on_entry_soft_delete(sender, instance, **kwargs):
if instance.trash:
pass
QuotaStatsService.remove_inventory_entry(instance)
@receiver(post_save, sender=InventoryEntry)
@receiver(post_delete, sender=InventoryEntry)
def update_distribution_warehouse_entry(sender, instance, **kwargs):
calculate_warehouse_entry(instance.distribution)
warehouse_sold_and_balance(instance.distribution)
# @receiver(post_save, sender=InventoryEntry)
# @receiver(post_delete, sender=InventoryEntry)
# def update_distribution_warehouse_entry(sender, instance, **kwargs):
# calculate_warehouse_entry(instance.distribution)
# warehouse_sold_and_balance(instance.distribution)
@receiver(post_save, sender=InventoryQuotaSaleItem)

View File

@@ -10,7 +10,6 @@ from apps.core.api import BaseViewSet
from apps.core.mixins.search_mixin import DynamicSearchMixin
from apps.core.mixins.soft_delete_mixin import SoftDeleteMixin
from apps.warehouse import models as warehouse_models
from apps.warehouse.services.warehouse_allocation_service import WarehouseAllocationService
from apps.warehouse.web.api.v1 import serializers as warehouse_serializers
from common.generics import base64_to_image_file
from common.helpers import get_organization_by_user
@@ -71,8 +70,6 @@ class InventoryEntryViewSet(BaseViewSet, SoftDeleteMixin, viewsets.ModelViewSet,
inventory_entry = serializer.save()
WarehouseAllocationService.allocate(entry=inventory_entry)
# upload document for confirmation entry
if 'document' in request.data.keys():
self.upload_confirmation_document(request, inventory=inventory_entry.id)

View File

@@ -1,11 +1,9 @@
from django.db import models
from rest_framework import serializers
from rest_framework import serializers, status
from apps.product.exceptions import QuotaExpiredTimeException
from apps.product.models import OrganizationQuotaStats
from apps.warehouse import models as warehouse_models
from apps.warehouse.exceptions import (
InventoryEntryWeightException
)
from apps.warehouse.exceptions import WareHouseException
class InventoryEntrySerializer(serializers.ModelSerializer):
@@ -16,7 +14,7 @@ class InventoryEntrySerializer(serializers.ModelSerializer):
"create_date",
"modify_date",
"organization",
"distribution",
"quota",
"weight",
"balance",
"lading_number",
@@ -30,32 +28,49 @@ class InventoryEntrySerializer(serializers.ModelSerializer):
check if inventory entries weight is not more than
distribution weight & check quota expired time
"""
distribution = attrs['distribution']
quota = attrs['quota']
org = attrs['organization']
# check for quota expired time
if not distribution.quota.is_in_valid_time():
if not 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
# total_entered = distribution.inventory_entry.filter(is_confirmed=True).aggregate(
# total=models.Sum('weight')
# )['total'] or 0
org_quota_stat = OrganizationQuotaStats.objects.get(
organization=org,
quota=quota
)
total_entered_weight = org_quota_stat.inventory_received
remaining_weight_to_enter = org_quota_stat.remaining_amount
# if instance exists, for update check weight with distribution weight
if self.instance:
if self.instance.weight == 0:
if total_entered + attrs['weight'] > distribution.weight:
raise InventoryEntryWeightException()
if total_entered_weight + attrs['weight'] > remaining_weight_to_enter:
raise WareHouseException(
"وزن وارد شده برای ورود به انبار نباید از باقیمانده سهمیه بیشتر باشد", # noqa
status.HTTP_403_FORBIDDEN
)
elif self.instance.weight != 0:
if total_entered - self.instance.weight + attrs['weight'] > distribution.weight:
raise InventoryEntryWeightException()
if total_entered_weight - self.instance.weight + attrs['weight'] > remaining_weight_to_enter:
raise WareHouseException(
"وزن وارد شده برای ورود به انبار نباید از باقیمانده سهمیه بیشتر باشد", # noqa
status.HTTP_403_FORBIDDEN
)
# if instance is not exists for create, check entry weight with distribution
else:
if total_entered + attrs['weight'] > distribution.weight or \
total_entered + attrs['weight'] > distribution.remaining_weight:
raise InventoryEntryWeightException()
if total_entered_weight + attrs['weight'] > remaining_weight_to_enter:
# total_entered + attrs['weight'] > distribution.remaining_weight:
raise WareHouseException(
"وزن وارد شده برای ورود به انبار نباید از باقیمانده سهمیه بیشتر باشد", # noqa
status.HTTP_403_FORBIDDEN
)
return attrs