diff --git a/apps/tag/migrations/0044_tagdistributionbatch_exit_doc_status_and_more.py b/apps/tag/migrations/0044_tagdistributionbatch_exit_doc_status_and_more.py
new file mode 100644
index 0000000..2101630
--- /dev/null
+++ b/apps/tag/migrations/0044_tagdistributionbatch_exit_doc_status_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.0 on 2026-02-08 07:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tag', '0043_tagdistributionbatch_owner_org_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tagdistributionbatch',
+ name='exit_doc_status',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='tagdistributionbatch',
+ name='warehouse_exit_doc',
+ field=models.CharField(max_length=350, null=True),
+ ),
+ ]
diff --git a/apps/tag/models.py b/apps/tag/models.py
index f1bcee9..b621fa2 100644
--- a/apps/tag/models.py
+++ b/apps/tag/models.py
@@ -170,6 +170,8 @@ class TagDistributionBatch(BaseModel):
total_distributed_tag_count = models.PositiveBigIntegerField(default=0)
remaining_tag_count = models.PositiveBigIntegerField(default=0)
top_root_distribution = models.BooleanField(default=False)
+ warehouse_exit_doc = models.CharField(max_length=350, null=True)
+ exit_doc_status = models.BooleanField(default=False)
is_closed = models.BooleanField(default=False)
def __str__(self):
diff --git a/apps/tag/templates/pdf/tag_distribution.html b/apps/tag/templates/pdf/tag_distribution.html
new file mode 100644
index 0000000..4f11fee
--- /dev/null
+++ b/apps/tag/templates/pdf/tag_distribution.html
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+سند توزیع پلاک دام
+
+
+
+
+
+
+ | کد گونه |
+ از سریال |
+ تا سریال |
+ تعداد کل |
+ باقیمانده |
+
+
+
+ {% for dist in batch.distributions.all %}
+
+ | {{ dist.species_code }} |
+ {{ dist.serial_from }} |
+ {{ dist.serial_to }} |
+ {{ dist.total_tag_count }} |
+ {{ dist.remaining_number }} |
+
+ {% endfor %}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/tag/web/api/v1/api.py b/apps/tag/web/api/v1/api.py
index 4767db4..55460ea 100644
--- a/apps/tag/web/api/v1/api.py
+++ b/apps/tag/web/api/v1/api.py
@@ -1,12 +1,15 @@
import typing
from django.db import transaction
+from django.http import HttpResponse
+from django.template.loader import render_to_string
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import APIException
from rest_framework.filters import SearchFilter
from rest_framework.response import Response
+from weasyprint import HTML
from apps.authentication.api.v1.api import GeneralOTPViewSet
from apps.authorization import models as authorize_models
@@ -21,6 +24,7 @@ from apps.tag.services.tag_distribution_services import TagDistributionService
from apps.tag.services.tag_services import TagService
from common.helpers import get_organization_by_user
from common.liara_tools import upload_to_liara
+from common.storage import upload_to_storage
from .serializers import (
TagSerializer,
TagAssignmentSerializer,
@@ -708,6 +712,79 @@ class TagDistributionBatchViewSet(
return Response(dashboard_data, status=status.HTTP_200_OK)
+ @action(
+ methods=['get'],
+ detail=True,
+ url_path='distribution_pdf_view',
+ url_name='distribution_pdf_view',
+ name='distribution_pdf_view',
+ )
+ def distribution_pdf_view(self, request, pk=None):
+ batch = tag_models.TagDistributionBatch.objects.select_related(
+ 'assigner_org', 'assigned_org'
+ ).prefetch_related('distributions').get(id=pk)
+
+ html_string = render_to_string(
+ 'pdf/tag_distribution.html', # noqa
+ {'batch': batch}
+ )
+
+ html = HTML(
+ string=html_string,
+ base_url=request.build_absolute_uri('/')
+ )
+
+ pdf = html.write_pdf()
+
+ response = HttpResponse(pdf, content_type='application/pdf')
+ response['Content-Disposition'] = (
+ f'inline; filename="distribution_{batch.dist_batch_identity}.pdf"'
+ )
+
+ return response
+
+ @action(
+ methods=['post', ],
+ detail=True,
+ url_name='assign_document',
+ url_path='assign_document',
+ name='assign_document'
+ )
+ @transaction.atomic
+ def assign_document(self, request, pk=None):
+ """ set document for tag assignment """
+
+ # get tag assignment object & set document url
+ dist_batch = self.queryset.get(id=pk)
+
+ # upload document file to liara storage
+ document = request.FILES.get('dist_exit_document')
+ document_url = upload_to_storage(
+ document,
+ f'distribution_batch_document_{dist_batch.dist_batch_identity}.{str(document).split(".")[1]}'
+ )
+ dist_batch.warehouse_exit_doc = document_url
+ dist_batch.save(update_fields=['warehouse_exit_doc'])
+ serializer = self.serializer_class(dist_batch)
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ @action(
+ methods=['post'],
+ url_path='accept_exit_doc',
+ url_name='accept_exit_doc',
+ name='accept_exit_doc',
+ )
+ def accept_exit_doc(self, request, pk=None):
+ """
+ accept exit document from warehouse on distribution batch
+ """
+
+ dist_batch = self.get_object()
+ dist_batch.exit_doc_status = True
+ dist_batch.save(update_fields=['exit_doc_status'])
+
+ return Response(status=status.HTTP_200_OK)
+
def destroy(self, request, pk=None, *args, **kwargs):
"""
delete tag distribution batch and free their tag from distribute
diff --git a/common/storage.py b/common/storage.py
index a9d5b5f..389f1a6 100644
--- a/common/storage.py
+++ b/common/storage.py
@@ -1,4 +1,31 @@
+import boto3
+from botocore.exceptions import NoCredentialsError
+
STORAGE_ENDPOINT = 'https://s3.rasadyar.com/rasaddam'
STORAGE_BUCKET_NAME = 'ticket-rasadyar'
STORAGE_ACCESS_KEY = "zG3ewsbYsTqCmuws"
STORAGE_SECRET_KEY = 'RInUMB78zlQZp6CNf8+sRoSh2cNDHcGQhXrLnTJ1AuI='
+
+
+def upload_to_storage(file_obj, file_name):
+ try:
+ s3 = boto3.client(
+ 's3',
+ endpoint_url=STORAGE_ENDPOINT,
+ aws_access_key_id=STORAGE_ACCESS_KEY,
+ aws_secret_access_key=STORAGE_SECRET_KEY
+ )
+
+ s3.upload_fileobj(
+ file_obj,
+ STORAGE_BUCKET_NAME,
+ file_name,
+ ExtraArgs={'ACL': 'public-read'} # دسترسی عمومی
+ )
+
+ return f"{STORAGE_ENDPOINT}/{STORAGE_ENDPOINT}/{file_name}"
+
+ except NoCredentialsError:
+ raise Exception("اعتبارنامههای AWS معتبر نیستند")
+ except Exception as e:
+ raise Exception(f"خطا در آپلود فایل: {e}")
diff --git a/requirements.txt b/requirements.txt
index 791975f..7e99b52 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -84,3 +84,4 @@ channels_redis
daphne
django-jazzmin
python-dotenv
+weasyprint
\ No newline at end of file