import --> create tag distribution with/without batch
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
# Generated by Django 5.0 on 2026-01-20 07:31
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0060_organization_ownership_code'),
|
||||||
|
('tag', '0036_tagdistribution_assigner_org_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tagdistribution',
|
||||||
|
name='dist_identity',
|
||||||
|
field=models.CharField(default='0', max_length=20, null=True, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tagdistribution',
|
||||||
|
name='is_closed',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tagbatch',
|
||||||
|
name='tag',
|
||||||
|
field=models.ManyToManyField(related_name='batches', to='tag.tag'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TagDistributionBatch',
|
||||||
|
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)),
|
||||||
|
('dist_batch_identity', models.CharField(default='0', max_length=20, null=True, unique=True)),
|
||||||
|
('total_tag_count', models.IntegerField(default=0)),
|
||||||
|
('is_closed', models.BooleanField(default=False)),
|
||||||
|
('assigned_org', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='to_tag_distribution_batch', to='authentication.organization')),
|
||||||
|
('assigner_org', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='from_tag_distribution_batch', to='authentication.organization')),
|
||||||
|
('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)),
|
||||||
|
('distributions', models.ManyToManyField(related_name='tag_distribution_batch', to='tag.tagdistribution')),
|
||||||
|
('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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -65,7 +65,7 @@ class TagBatch(BaseModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
tag = models.ManyToManyField(Tag, related_name='tags')
|
tag = models.ManyToManyField(Tag, related_name='batches')
|
||||||
species_code = models.IntegerField(default=0)
|
species_code = models.IntegerField(default=0)
|
||||||
serial_from = models.PositiveBigIntegerField(default=0)
|
serial_from = models.PositiveBigIntegerField(default=0)
|
||||||
serial_to = models.PositiveBigIntegerField(default=0)
|
serial_to = models.PositiveBigIntegerField(default=0)
|
||||||
@@ -89,6 +89,7 @@ class TagBatch(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TagDistribution(BaseModel):
|
class TagDistribution(BaseModel):
|
||||||
|
dist_identity = models.CharField(max_length=20, default="0", unique=True, null=True)
|
||||||
batch = models.ForeignKey(
|
batch = models.ForeignKey(
|
||||||
TagBatch,
|
TagBatch,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@@ -110,6 +111,7 @@ class TagDistribution(BaseModel):
|
|||||||
)
|
)
|
||||||
species_code = models.IntegerField(default=0)
|
species_code = models.IntegerField(default=0)
|
||||||
distributed_number = models.IntegerField(default=0)
|
distributed_number = models.IntegerField(default=0)
|
||||||
|
is_closed = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.id}-{self.distributed_number}-{self.assigned_org.name}'
|
return f'{self.id}-{self.distributed_number}-{self.assigned_org.name}'
|
||||||
@@ -118,6 +120,31 @@ class TagDistribution(BaseModel):
|
|||||||
return super(TagDistribution, self).save(*args, **kwargs)
|
return super(TagDistribution, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TagDistributionBatch(BaseModel):
|
||||||
|
dist_batch_identity = models.CharField(max_length=20, default="0", unique=True, null=True)
|
||||||
|
assigner_org = models.ForeignKey(
|
||||||
|
Organization,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='from_tag_distribution_batch',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
assigned_org = models.ForeignKey(
|
||||||
|
Organization,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="to_tag_distribution_batch",
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
distributions = models.ManyToManyField(TagDistribution, related_name='tag_distribution_batch')
|
||||||
|
total_tag_count = models.IntegerField(default=0)
|
||||||
|
is_closed = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.id}'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
return super(TagDistributionBatch, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TagAssignment(BaseModel):
|
class TagAssignment(BaseModel):
|
||||||
organization = models.ForeignKey(
|
organization = models.ForeignKey(
|
||||||
auth_models.Organization,
|
auth_models.Organization,
|
||||||
|
|||||||
@@ -1,6 +1,83 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from apps.authentication.models import Organization
|
||||||
|
from apps.tag.exceptions import TagException
|
||||||
|
from apps.tag.models import Tag, TagBatch, TagDistribution, TagDistributionBatch
|
||||||
|
from common.generics import generate_unique_code
|
||||||
|
|
||||||
|
|
||||||
class TagDistributionService:
|
class TagDistributionService:
|
||||||
"""
|
"""
|
||||||
service of distribute tags in organizations
|
service of distribute tags in organizations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def create_distribution(self, org: Organization = None, data: dict = None):
|
||||||
|
"""
|
||||||
|
distribute tags with batch
|
||||||
|
"""
|
||||||
|
with transaction.atomic():
|
||||||
|
distributions = []
|
||||||
|
total_counted_tags = 0
|
||||||
|
assigned_org = Organization.objects.get(id=data.get('assigned_org'))
|
||||||
|
|
||||||
|
for distribution in data.get('dists'):
|
||||||
|
batch_identity = distribution.get('batch_identity', None)
|
||||||
|
# if batch identity exists distribute tags of batch
|
||||||
|
if batch_identity:
|
||||||
|
batch = TagBatch.objects.get(batch_identity=batch_identity)
|
||||||
|
tags = Tag.objects.filter(
|
||||||
|
batches__batch_identity=batch_identity,
|
||||||
|
species_code=distribution.get('species_code'),
|
||||||
|
status='F'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
batch = None
|
||||||
|
# get tags without batch and only with species code
|
||||||
|
tags = Tag.objects.filter(
|
||||||
|
species_code=distribution.get('species_code'),
|
||||||
|
status='F'
|
||||||
|
)
|
||||||
|
|
||||||
|
if tags.count() < distribution.get('count'):
|
||||||
|
raise TagException(
|
||||||
|
"تعداد وارد شده از تعداد موجودی این گونه بیشتر میباشد.", # noqa
|
||||||
|
403
|
||||||
|
)
|
||||||
|
|
||||||
|
dist = TagDistribution.objects.create(
|
||||||
|
batch=batch,
|
||||||
|
assigner_org=org,
|
||||||
|
assigned_org=assigned_org,
|
||||||
|
species_code=distribution.get('species_code'),
|
||||||
|
distributed_number=distribution.get('count'),
|
||||||
|
dist_identity=generate_unique_code(f"{random.randint(1000, 9999)}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# get counted tag ids and filter by them to update status To Reserve
|
||||||
|
counted_tags_obj = tags.order_by('create_date')[:int(distribution.get('count'))]
|
||||||
|
counted_tag_ids = [tag.id for tag in counted_tags_obj]
|
||||||
|
tags.filter(id__in=counted_tag_ids).update(status='R')
|
||||||
|
|
||||||
|
dist.tag.add(*counted_tags_obj)
|
||||||
|
distributions.append(dist)
|
||||||
|
|
||||||
|
total_counted_tags += distribution.get('count')
|
||||||
|
|
||||||
|
# create distribution batch
|
||||||
|
distributions_batch = TagDistributionBatch.objects.create(
|
||||||
|
assigner_org=org,
|
||||||
|
assigned_org=assigned_org,
|
||||||
|
total_tag_count=total_counted_tags,
|
||||||
|
dist_batch_identity=generate_unique_code(f"{random.randint(1000, 9999)}"),
|
||||||
|
)
|
||||||
|
distributions_batch.distributions.add(*distributions)
|
||||||
|
|
||||||
|
return {'tag_distributions': distributions, 'distributions_batch': distributions_batch}
|
||||||
|
|
||||||
|
def edit_distribution(self):
|
||||||
|
"""
|
||||||
|
edit record of distributed tags
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from apps.core.mixins.soft_delete_mixin import SoftDeleteMixin
|
|||||||
from apps.tag import exceptions as tag_exceptions
|
from apps.tag import exceptions as tag_exceptions
|
||||||
from apps.tag import models as tag_models
|
from apps.tag import models as tag_models
|
||||||
from apps.tag.models import TagBatch
|
from apps.tag.models import TagBatch
|
||||||
|
from apps.tag.services.tag_distribution_services import TagDistributionService
|
||||||
from apps.tag.services.tag_services import TagService
|
from apps.tag.services.tag_services import TagService
|
||||||
from common.helpers import get_organization_by_user
|
from common.helpers import get_organization_by_user
|
||||||
from common.liara_tools import upload_to_liara
|
from common.liara_tools import upload_to_liara
|
||||||
@@ -344,7 +345,13 @@ class TagBatchViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets
|
|||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class TagDistributionViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets.ModelViewSet):
|
class TagDistributionViewSet(
|
||||||
|
BaseViewSet,
|
||||||
|
SoftDeleteMixin,
|
||||||
|
DynamicSearchMixin,
|
||||||
|
viewsets.ModelViewSet,
|
||||||
|
TagDistributionService
|
||||||
|
):
|
||||||
queryset = tag_models.TagDistribution.objects.all()
|
queryset = tag_models.TagDistribution.objects.all()
|
||||||
serializer_class = TagDistributionSerializer
|
serializer_class = TagDistributionSerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [SearchFilter]
|
||||||
@@ -369,3 +376,18 @@ class TagDistributionViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, v
|
|||||||
serializer = self.get_serializer(page, many=True)
|
serializer = self.get_serializer(page, many=True)
|
||||||
return self.get_paginated_response(serializer.data)
|
return self.get_paginated_response(serializer.data)
|
||||||
return Response(self.serializer_class(queryset).data)
|
return Response(self.serializer_class(queryset).data)
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
create tag distributions with batch or without batch in random
|
||||||
|
"""
|
||||||
|
org = get_organization_by_user(request.user)
|
||||||
|
data = request.data.copy()
|
||||||
|
|
||||||
|
distribution_data = self.create_distribution(
|
||||||
|
org=org,
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
serializer = self.serializer_class(distribution_data.get('tag_distributions'), many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from rest_framework.routers import DefaultRouter
|
|||||||
from .api import (
|
from .api import (
|
||||||
TagViewSet,
|
TagViewSet,
|
||||||
TagAssignmentViewSet,
|
TagAssignmentViewSet,
|
||||||
AllocatedTagsViewSet, TagBatchViewSet
|
AllocatedTagsViewSet, TagBatchViewSet, TagDistributionViewSet
|
||||||
)
|
)
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
@@ -12,6 +12,7 @@ router.register(r'tag', TagViewSet, basename='tag')
|
|||||||
router.register(r'tag_assignment', TagAssignmentViewSet, basename='tag_assignment')
|
router.register(r'tag_assignment', TagAssignmentViewSet, basename='tag_assignment')
|
||||||
router.register(r'allocated_tag', AllocatedTagsViewSet, basename='allocated_tag')
|
router.register(r'allocated_tag', AllocatedTagsViewSet, basename='allocated_tag')
|
||||||
router.register(r'tag_batch', TagBatchViewSet, basename='tag_batch')
|
router.register(r'tag_batch', TagBatchViewSet, basename='tag_batch')
|
||||||
|
router.register(r'tag_distribution', TagDistributionViewSet, basename='tag_distribution')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('v1/', include(router.urls))
|
path('v1/', include(router.urls))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import base64
|
import base64
|
||||||
|
import random
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
@@ -56,3 +57,10 @@ def parse_birthdate(jalali_str):
|
|||||||
gregorian_dt,
|
gregorian_dt,
|
||||||
timezone.get_current_timezone()
|
timezone.get_current_timezone()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_unique_code(prefix: str):
|
||||||
|
now = timezone.now()
|
||||||
|
date_part = now.strftime("%Y%m%d")
|
||||||
|
rand_part = random.randint(100000, 999999)
|
||||||
|
return f"{prefix}{date_part}{rand_part}"
|
||||||
|
|||||||
Reference in New Issue
Block a user