add - whole system of inventory entry in organization quotas stat
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
20
apps/warehouse/migrations/0043_inventoryentry_quota.py
Normal file
20
apps/warehouse/migrations/0043_inventoryentry_quota.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user