import - add tag/tag_batch --> edit tag_batch --> list of tag_batch

This commit is contained in:
2026-01-07 14:21:27 +03:30
parent 89c794bf81
commit 1bf107d81d
7 changed files with 201 additions and 44 deletions

View File

@@ -10,6 +10,7 @@ VISIBILITY_MAP = {
'device': 'assignment__client__organization', 'device': 'assignment__client__organization',
'rancher': 'organization', 'rancher': 'organization',
'rancherorganizationlink': 'organization', # noqa 'rancherorganizationlink': 'organization', # noqa
'tagbatch': 'organization', # noqa
# 'deviceactivationcode': 'organization', # 'deviceactivationcode': 'organization',
# 'deviceversion': 'organization', # 'deviceversion': 'organization',

View File

@@ -0,0 +1,16 @@
# Generated by Django 5.0 on 2026-01-07 07:13
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('authentication', '0060_organization_ownership_code'),
('tag', '0027_alter_tag_country_code_alter_tag_ownership_code_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
]

View File

@@ -29,11 +29,11 @@ class Tag(BaseModel):
) )
status = models.CharField(max_length=20, default="F") status = models.CharField(max_length=20, default="F")
class Meta: # class Meta:
indexes = [ # indexes = [
models.Index(fields=['ownership_code', 'species_code']), # models.Index(fields=['ownership_code', 'species_code']),
models.Index(fields=['status']), # models.Index(fields=['status']),
] # ]
def __str__(self): def __str__(self):
return f'{self.id}-{self.tag_code}' return f'{self.id}-{self.tag_code}'
@@ -65,12 +65,16 @@ class TagBatch(BaseModel):
null=True null=True
) )
tag = models.ManyToManyField(Tag, related_name='tags')
species_code = models.IntegerField(default=0)
serial_from = models.PositiveBigIntegerField(default=0)
serial_to = models.PositiveBigIntegerField(default=0)
status = models.CharField( status = models.CharField(
max_length=20, max_length=20,
choices=[ choices=[
('created', 'CREATED'), ('created', 'CREATED'),
('distributed', 'DISTRIBUTED'), ('distributed', 'DISTRIBUTED'),
('created', 'CREATED'),
], ],
null=True, null=True,
default='created' default='created'
@@ -84,23 +88,6 @@ class TagBatch(BaseModel):
return super(TagBatch, self).save(*args, **kwargs) return super(TagBatch, self).save(*args, **kwargs)
class TagBatchItem(BaseModel):
batch = models.ForeignKey(
TagBatch,
on_delete=models.CASCADE,
related_name='items',
null=True
)
tag = models.ManyToManyField(Tag, related_name='tags')
species_code = models.IntegerField(default=0)
serial_from = models.PositiveBigIntegerField(default=0)
serial_to = models.PositiveBigIntegerField(default=0)
count = models.IntegerField(default=0)
def __str__(self):
return f"id:{self.id}-batch:{self.batch.id}-code:{self.species_code}"
class TagAssignment(BaseModel): class TagAssignment(BaseModel):
organization = models.ForeignKey( organization = models.ForeignKey(
auth_models.Organization, auth_models.Organization,

View File

@@ -1,8 +1,10 @@
from django.db.models import Q from django.db.models import Q
from django.db.models.aggregates import Count from django.db.models.aggregates import Count
from apps.authentication.models import Organization
from apps.livestock.web.api.v1.serializers import LiveStockSerializer from apps.livestock.web.api.v1.serializers import LiveStockSerializer
from apps.tag.models import Tag from apps.tag.exceptions import TagException
from apps.tag.models import Tag, TagBatch
class TagService: class TagService:
@@ -10,6 +12,95 @@ class TagService:
Different Services of Livestock Tags Different Services of Livestock Tags
""" """
def create_tag(
self,
serial_start_range: int = None,
serial_end_range: int = None,
org: Organization = None,
data: dict = None
):
"""
create livestock tag with batch / batch item
"""
# create tag batch
request_number = serial_end_range - serial_start_range
batch = TagBatch.objects.create(
organization=org,
request_number=request_number if request_number > 0 else 1,
species_code=data.get('species_code'),
serial_from=serial_start_range,
serial_to=serial_end_range,
status='created',
)
tag_list = []
while serial_start_range <= serial_end_range:
data.update({
'serial': str(serial_start_range),
'ownership_code': org.ownership_code,
'organization': org,
})
tag_list.append(Tag(**data))
if Tag.objects.filter(serial=serial_start_range, species_code=data.get('species_code')).exists():
raise TagException(f' پلاک با مشخصات مورد نظر {serial_start_range} وجود دارد ', status_code=403) # noqa
serial_start_range += 1
created_tags = Tag.objects.bulk_create(tag_list)
batch.tag.add(*created_tags)
return created_tags
def update_batch_tag(
self,
serial_start_range: int = None,
serial_end_range: int = None,
org: Organization = None,
data: dict = None,
batch_id: int = None,
):
"""
update livestock tag with batch / batch item
"""
# update tag batch
request_number = serial_end_range - serial_start_range
batch = TagBatch.objects.get(id=batch_id)
batch.request_number = request_number
batch.species_code = data.get('species_code')
batch.serial_from = serial_start_range
batch.serial_to = serial_end_range
batch.save(update_fields=['request_number', 'species_code', 'serial_from', 'serial_to'])
# recreate tags for batch
tag_list = []
while serial_start_range <= serial_end_range:
data.update({
'serial': str(serial_start_range),
'ownership_code': org.ownership_code,
'organization': org,
})
tag_list.append(Tag(**data))
if Tag.objects.filter(
serial=serial_start_range,
species_code=data.get('species_code')
).exists() and not batch.tag.filter(
serial=serial_start_range
).exists():
raise TagException(f' پلاک با مشخصات مورد نظر {serial_start_range} وجود دارد ', status_code=403) # noqa
serial_start_range += 1
created_tags = Tag.objects.bulk_create(tag_list)
# hard delete of created tags for batch
batch.tag.all().delete()
batch.tag.add(*created_tags)
return created_tags
def tag_detail(self, by_id: int = None, by_queryset: bool = False): def tag_detail(self, by_id: int = None, by_queryset: bool = False):
""" """
get detail of a tag like: livestock, rancher, herd, etc get detail of a tag like: livestock, rancher, herd, etc

View File

@@ -1,6 +1,5 @@
import typing import typing
from django.db import IntegrityError
from django.db import transaction from django.db import transaction
from rest_framework import status from rest_framework import status
from rest_framework import viewsets from rest_framework import viewsets
@@ -16,13 +15,14 @@ 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.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.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
from .serializers import ( from .serializers import (
TagSerializer, TagSerializer,
TagAssignmentSerializer, TagAssignmentSerializer,
AllocatedTagsSerializer AllocatedTagsSerializer, TagBatchSerializer
) )
@@ -67,24 +67,38 @@ class TagViewSet(BaseViewSet, TagService, SoftDeleteMixin, DynamicSearchMixin, v
""" Create tag for livestocks """ # noqa """ Create tag for livestocks """ # noqa
org = get_organization_by_user(request.user) # noqa org = get_organization_by_user(request.user) # noqa
tag_objects = [] serial_start_range, serial_end_range = request.data.pop('serial_range') # serial_range is like [500, 550]
serial_start_range, serial_end_range = request.data['serial_range'] # serial_range is like [500, 550] print(serial_start_range, serial_end_range)
while serial_start_range <= serial_end_range: data = request.data.copy()
try:
request.data.update({
'serial': str(serial_start_range),
'ownership_code': org.ownership_code,
'organization': org,
})
serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
tag_objects.append(serializer.save())
except IntegrityError as e: # if tag exists before
if 'unique constraint' in e.args[0]:
return Response("tag exists", status.HTTP_406_NOT_ACCEPTABLE)
serial_start_range += 1
serializer = self.serializer_class(tag_objects, many=True) # create tag & batch
created_tags = self.create_tag(
serial_start_range=serial_start_range,
serial_end_range=serial_end_range,
data=data,
org=org
)
serializer = self.serializer_class(created_tags, many=True)
return Response(serializer.data, status.HTTP_201_CREATED)
def update(self, request, pk=None, *args, **kwargs):
""" update tag for livestocks """ # noqa
org = get_organization_by_user(request.user) # noqa
serial_start_range, serial_end_range = request.data.pop('serial_range') # serial_range is like [500, 550]
data = request.data.copy()
# create tag & batch
created_tags = self.update_batch_tag(
serial_start_range=serial_start_range,
serial_end_range=serial_end_range,
data=data,
org=org,
batch_id=int(pk)
)
serializer = self.serializer_class(created_tags, many=True)
return Response(serializer.data, status.HTTP_201_CREATED) return Response(serializer.data, status.HTTP_201_CREATED)
@action( @action(
@@ -269,3 +283,31 @@ class TagAssignmentViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, vie
class AllocatedTagsViewSet(SoftDeleteMixin, viewsets.ModelViewSet): class AllocatedTagsViewSet(SoftDeleteMixin, viewsets.ModelViewSet):
queryset = tag_models.AllocatedTags.objects.all() queryset = tag_models.AllocatedTags.objects.all()
serializer_class = AllocatedTagsSerializer serializer_class = AllocatedTagsSerializer
class TagBatchViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets.ModelViewSet):
queryset = TagBatch.objects.all()
serializer_class = TagBatchSerializer
filter_backends = [SearchFilter]
search_fields = [
"organization__name"
"request_number"
"tag__tag_code"
"species_code"
]
def list(self, request, *args, **kwargs):
"""
list of tag batches
"""
queryset = self.get_queryset(visibility_by_org_scope=True).order_by('-create_date')
# filter queryset
queryset = self.filter_query(self.filter_queryset(queryset))
page = self.paginate_queryset(queryset)
if page is not None: # noqa
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
return Response(self.serializer_class(queryset).data)

View File

@@ -3,6 +3,7 @@ from rest_framework import serializers
from apps.authentication.api.v1.serializers import serializer as auth_serializers from apps.authentication.api.v1.serializers import serializer as auth_serializers
from apps.tag import models as tag_models from apps.tag import models as tag_models
from apps.tag.exceptions import TagException from apps.tag.exceptions import TagException
from apps.tag.models import TagBatch
class TagSerializer(serializers.ModelSerializer): class TagSerializer(serializers.ModelSerializer):
@@ -125,3 +126,20 @@ class AllocatedTagsSerializer(serializers.ModelSerializer):
representation['status'] = instance.status representation['status'] = instance.status
return representation return representation
class TagBatchSerializer(serializers.ModelSerializer):
class Meta:
model = TagBatch
fields = '__all__'
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['tag'] = [{
'tag_code': tag.tag_code,
'species_code': tag.species_code,
'status': tag.status
} for tag in instance.tag.all()]
return representation

View File

@@ -1,15 +1,17 @@
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .api import ( from .api import (
TagViewSet, TagViewSet,
TagAssignmentViewSet, TagAssignmentViewSet,
AllocatedTagsViewSet AllocatedTagsViewSet, TagBatchViewSet
) )
router = DefaultRouter() router = DefaultRouter()
router.register(r'tag', TagViewSet, basename='tag') 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')
urlpatterns = [ urlpatterns = [
path('v1/', include(router.urls)) path('v1/', include(router.urls))