add - WarehouseAllocationService
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-11-18 12:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0095_organizationquotastats_stat_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='organizationquotastats',
|
||||||
|
name='inventory_received',
|
||||||
|
field=models.PositiveBigIntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -103,7 +103,7 @@ class QuotaDistributionSerializer(serializers.ModelSerializer):
|
|||||||
'id': instance.assigned_organization.id,
|
'id': instance.assigned_organization.id,
|
||||||
# if distributor is 0 , organization has not any distribute
|
# if distributor is 0 , organization has not any distribute
|
||||||
'is_distributor': instance.quota.distributions_assigned.filter(
|
'is_distributor': instance.quota.distributions_assigned.filter(
|
||||||
assigner_organization=instance.assigner_organization
|
assigner_organization=instance.assigned_organization
|
||||||
).count()
|
).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
from rest_framework.exceptions import APIException
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
|
|
||||||
|
class WareHouseException(APIException):
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
default_detail = "مقدار ورد شده از انبار بیشتر از موجودی انبار میباشد" # noqa
|
||||||
|
default_code = 'error'
|
||||||
|
|
||||||
|
def __init__(self, message=None, code=None, status_code=None):
|
||||||
|
if status_code is not None:
|
||||||
|
self.status_code = status_code
|
||||||
|
|
||||||
|
detail = {
|
||||||
|
"message": message,
|
||||||
|
"status_code": status_code
|
||||||
|
}
|
||||||
|
|
||||||
|
super().__init__(detail)
|
||||||
|
|
||||||
|
|
||||||
class InventoryEntryWeightException(APIException):
|
class InventoryEntryWeightException(APIException):
|
||||||
|
|||||||
36
apps/warehouse/migrations/0042_inventoryentryallocation.py
Normal file
36
apps/warehouse/migrations/0042_inventoryentryallocation.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-11-18 13:20
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0096_organizationquotastats_inventory_received'),
|
||||||
|
('warehouse', '0041_alter_inventoryquotasaletransaction_payer_cart_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='InventoryEntryAllocation',
|
||||||
|
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)),
|
||||||
|
('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.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='product.quotadistribution')),
|
||||||
|
('inventory_entry', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', 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)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -59,6 +59,25 @@ class InventoryEntry(BaseModel):
|
|||||||
super(InventoryEntry, self).save(*args, **kwargs)
|
super(InventoryEntry, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryEntryAllocation(BaseModel):
|
||||||
|
inventory_entry = models.ForeignKey(
|
||||||
|
InventoryEntry,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='allocations',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
distribution = models.ForeignKey(
|
||||||
|
product_models.QuotaDistribution,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='allocations',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
weight = models.PositiveBigIntegerField(default=0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.weight} -> Distribution {self.distribution.id}"
|
||||||
|
|
||||||
|
|
||||||
class InventoryQuotaSaleTransaction(BaseModel):
|
class InventoryQuotaSaleTransaction(BaseModel):
|
||||||
rancher = models.ForeignKey(
|
rancher = models.ForeignKey(
|
||||||
Rancher,
|
Rancher,
|
||||||
|
|||||||
57
apps/warehouse/services/warehouse_allocation_service.py
Normal file
57
apps/warehouse/services/warehouse_allocation_service.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from django.db import transaction
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from apps.product.models import QuotaDistribution, OrganizationQuotaStats
|
||||||
|
from apps.warehouse.exceptions import WareHouseException
|
||||||
|
from apps.warehouse.models import InventoryEntry, InventoryEntryAllocation
|
||||||
|
|
||||||
|
|
||||||
|
class WarehouseAllocationService:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def allocate(entry: InventoryEntry):
|
||||||
|
"""
|
||||||
|
Auto allocate entry.total_weight between multiple distributions
|
||||||
|
"""
|
||||||
|
with transaction.atomic():
|
||||||
|
distributions = QuotaDistribution.objects.filter(
|
||||||
|
assigned_organization=entry.organization,
|
||||||
|
quota=entry.distribution.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
|
||||||
|
|
||||||
|
allocate_weight = min(remaining, capacity)
|
||||||
|
|
||||||
|
InventoryEntryAllocation.objects.create(
|
||||||
|
distribution=dist,
|
||||||
|
entry=entry,
|
||||||
|
weight=allocate_weight
|
||||||
|
)
|
||||||
|
|
||||||
|
stat.inventory_received += allocate_weight
|
||||||
|
stat.save()
|
||||||
|
|
||||||
|
remaining -= allocate_weight
|
||||||
|
|
||||||
|
if remaining > 0:
|
||||||
|
raise WareHouseException(
|
||||||
|
"مقدار ورد شده از انبار بیشتر از مقدار کل سهمیه توزیع داده شده است",
|
||||||
|
status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
@@ -10,6 +10,7 @@ from apps.core.api import BaseViewSet
|
|||||||
from apps.core.mixins.search_mixin import DynamicSearchMixin
|
from apps.core.mixins.search_mixin import DynamicSearchMixin
|
||||||
from apps.core.mixins.soft_delete_mixin import SoftDeleteMixin
|
from apps.core.mixins.soft_delete_mixin import SoftDeleteMixin
|
||||||
from apps.warehouse import models as warehouse_models
|
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 apps.warehouse.web.api.v1 import serializers as warehouse_serializers
|
||||||
from common.generics import base64_to_image_file
|
from common.generics import base64_to_image_file
|
||||||
from common.helpers import get_organization_by_user
|
from common.helpers import get_organization_by_user
|
||||||
@@ -70,6 +71,8 @@ class InventoryEntryViewSet(BaseViewSet, SoftDeleteMixin, viewsets.ModelViewSet,
|
|||||||
|
|
||||||
inventory_entry = serializer.save()
|
inventory_entry = serializer.save()
|
||||||
|
|
||||||
|
WarehouseAllocationService.allocate(entry=inventory_entry)
|
||||||
|
|
||||||
# upload document for confirmation entry
|
# upload document for confirmation entry
|
||||||
if 'document' in request.data.keys():
|
if 'document' in request.data.keys():
|
||||||
self.upload_confirmation_document(request, inventory=inventory_entry.id)
|
self.upload_confirmation_document(request, inventory=inventory_entry.id)
|
||||||
|
|||||||
Reference in New Issue
Block a user