Compare commits

...

25 Commits

Author SHA1 Message Date
3258991014 fix --> soft delete distribution batch 2026-02-03 11:18:34 +03:30
fb58e6c3aa edit tag distribute from distribution 2026-02-03 08:46:38 +03:30
c5b87e8591 fix --> batch identity bug in create distribution from ditribute 2026-02-02 12:09:31 +03:30
0dd145131f fix --> bug of calculate remaining tags of distribution parent batch 2026-02-02 11:24:37 +03:30
74a870380e fix --> bug of transaction dashboard on free visibility scope 2026-02-02 10:40:54 +03:30
f798b72dbc fix --> bug of transaction dashboard on free visibility scope 2026-02-02 10:36:35 +03:30
93180edc0b revert development transactions dashboard commit 2026-02-02 10:31:33 +03:30
0b08107c14 fix --> bug of transactions dashboard with free visibility scope on queryset 2026-02-02 10:25:05 +03:30
56025d77b1 import --> distribute from distribution and calculate some values in model 2026-02-01 16:56:33 +03:30
e68485c4cc fix --> batch statistics 2026-02-01 11:55:18 +03:30
d643237a77 fix --> siganl of distribute to change some values in batch 2026-02-01 09:16:40 +03:30
8c9f7aca02 import --> distribute from distribution 2026-02-01 09:03:17 +03:30
9ed2a099e7 import --> detail of tag distribution batch 2026-01-28 11:59:10 +03:30
879e004e9b fix --> tag batch main dashboard 2026-01-28 11:34:27 +03:30
10c6eb9e79 fix --> remaining tags of tag batches 2026-01-27 16:00:04 +03:30
36df84da98 fix --> bug of calculated distribution of tag batch 2026-01-27 15:26:25 +03:30
915b0bf5a1 distribution of tags & tag batches 2026-01-27 14:52:50 +03:30
da15cb5b99 import --> filter tag batches by species code 2026-01-26 11:24:11 +03:30
e94d5e4d1b import --> tag batch inner dashboard 2026-01-26 11:03:44 +03:30
eaba79ee91 device validation by serial - DeviceException 2026-01-26 10:27:19 +03:30
e218c550e4 fix-->import distinct to tag batch count in dashboard 2026-01-26 09:56:39 +03:30
c173a1cd85 fix --> tags by batch 2026-01-25 11:41:37 +03:30
08468fe67c import --> tag batch main page dashboard 2026-01-25 11:06:02 +03:30
dd807f04be fix --> structure of species data in main dist batch dashboard 2026-01-25 09:59:37 +03:30
c0e62541c3 fix --> tag distribution dashboard by species 2026-01-24 16:35:54 +03:30
16 changed files with 612 additions and 39 deletions

View File

@@ -2,6 +2,23 @@ from rest_framework import status
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
class DeviceException(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "خطا در اطلاعات پلاک" # noqa
default_code = 'error'
def __init__(self, message=None, status_code=None, code=None):
if status_code is not None:
self.status_code = status_code
detail = {
"message": message,
"status_code": status_code
}
super().__init__(detail)
class DeviceAlreadyAssigned(APIException): class DeviceAlreadyAssigned(APIException):
status_code = status.HTTP_403_FORBIDDEN status_code = status.HTTP_403_FORBIDDEN
default_detail = "این دستگاه قبلا به این کلاینت تخصیص داده شده است" # noqa default_detail = "این دستگاه قبلا به این کلاینت تخصیص داده شده است" # noqa

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0 on 2026-01-26 06:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pos_device', '0080_bankaccountdevicelink'),
]
operations = [
migrations.AlterField(
model_name='device',
name='serial',
field=models.TextField(null=True),
),
]

View File

@@ -36,7 +36,7 @@ class Device(BaseModel):
acceptor = models.CharField(max_length=50, null=True) acceptor = models.CharField(max_length=50, null=True)
terminal = models.CharField(max_length=50, null=True) terminal = models.CharField(max_length=50, null=True)
mac = models.CharField(max_length=50, null=True) mac = models.CharField(max_length=50, null=True)
serial = models.TextField(null=True, unique=True) serial = models.TextField(null=True)
password = models.CharField(max_length=25, null=True) password = models.CharField(max_length=25, null=True)
multi_device = models.BooleanField(default=False) multi_device = models.BooleanField(default=False)
server_in = models.BooleanField(default=False) server_in = models.BooleanField(default=False)

View File

@@ -3,6 +3,7 @@ from rest_framework.serializers import ModelSerializer
from apps.authentication.api.v1.serializers.serializer import BankAccountSerializer from apps.authentication.api.v1.serializers.serializer import BankAccountSerializer
from apps.pos_device import exceptions as pos_exceptions from apps.pos_device import exceptions as pos_exceptions
from apps.pos_device import models as pos_models from apps.pos_device import models as pos_models
from apps.pos_device.exceptions import DeviceException
from apps.pos_device.web.api.v1.serilaizers import client as client_serializer from apps.pos_device.web.api.v1.serilaizers import client as client_serializer
from apps.product.web.api.v1.serializers.quota_distribution_serializers import QuotaDistributionSerializer from apps.product.web.api.v1.serializers.quota_distribution_serializers import QuotaDistributionSerializer
@@ -18,6 +19,19 @@ class DeviceSerializer(ModelSerializer):
model = pos_models.Device model = pos_models.Device
fields = '__all__' fields = '__all__'
def validate(self, attrs):
serial = attrs['serial']
if not self.instance:
if self.Meta.model.objects.filter(serial=serial).exists():
raise DeviceException("دستگاه یا این شماره سریال از قبل ثبت شده است.", status_code=403) # noqa
if self.instance:
if serial != self.instance.serial and self.Meta.model.objects.filter(serial=serial).exists():
raise DeviceException("دستگاهی با این شماره سریال وجود دارد.", status_code=403) # noqa
return attrs
def to_representation(self, instance): def to_representation(self, instance):
""" custom output of serializer """ """ custom output of serializer """
representation = super().to_representation(instance) representation = super().to_representation(instance)

View File

@@ -4,3 +4,6 @@ from django.apps import AppConfig
class TagConfig(AppConfig): class TagConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.tag' name = 'apps.tag'
def ready(self):
import apps.tag.signals.tag_distribution_signals # noqa

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.0 on 2026-01-27 09:18
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tag', '0041_tagbatch_total_remaining_tags'),
]
operations = [
migrations.AddField(
model_name='tagdistribution',
name='parent',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child', to='tag.tagdistribution'),
),
migrations.AddField(
model_name='tagdistribution',
name='remaining_number',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='tagdistribution',
name='total_tag_count',
field=models.IntegerField(default=0),
),
]

View File

@@ -91,6 +91,12 @@ class TagBatch(BaseModel):
class TagDistribution(BaseModel): class TagDistribution(BaseModel):
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='child',
null=True
)
dist_identity = models.CharField(max_length=20, default="0", unique=True, null=True) dist_identity = models.CharField(max_length=20, default="0", unique=True, null=True)
batch = models.ForeignKey( batch = models.ForeignKey(
TagBatch, TagBatch,
@@ -112,7 +118,9 @@ class TagDistribution(BaseModel):
null=True null=True
) )
species_code = models.IntegerField(default=0) species_code = models.IntegerField(default=0)
total_tag_count = models.IntegerField(default=0)
distributed_number = models.IntegerField(default=0) distributed_number = models.IntegerField(default=0)
remaining_number = models.IntegerField(default=0)
is_closed = models.BooleanField(default=False) is_closed = models.BooleanField(default=False)
def __str__(self): def __str__(self):

View File

@@ -0,0 +1,69 @@
from django.db.models import Sum, Q, Count, QuerySet, OuterRef, Subquery, IntegerField
from django.db.models.functions import Coalesce
from apps.authentication.models import Organization
from apps.authentication.services.service import get_all_org_child
from apps.tag.models import TagBatch
class TagBatchService:
"""
services of tag batch
"""
def tag_batch_main_dashboard(self, org: Organization = None, batch: QuerySet[TagBatch] = None):
"""
dashboard data of batch main page
"""
qs = TagBatch.objects.select_related('organization') if not batch else batch
if org.type.key != 'ADM':
child_orgs = get_all_org_child(org) # noqa
child_orgs.append(org)
qs = qs.filter(organization__in=child_orgs)
base_data = qs.aggregate(
batch_count=Count('id', distinct=True),
total_distributed_tags=Coalesce(Sum('total_distributed_tags'), 0),
total_remaining_tags=Coalesce(Sum('total_remaining_tags'), 0),
has_distributed_batches_number=Count(
'id',
distinct=True,
filter=Q(status__in=[
'distributed',
])
)
)
base_data.update(qs.aggregate(tag_count_created_by_batch=Count('tag')))
tag_count_subquery = (
TagBatch.objects
.filter(id=OuterRef('id'))
.annotate(cnt=Count('tag'))
.values('cnt')
)
species_data = (
qs
.annotate(
tag_count=Subquery(tag_count_subquery, output_field=IntegerField())
)
.values('species_code')
.annotate(
batch_count=Count('id', distinct=True),
total_distributed_tags=Coalesce(Sum('total_distributed_tags'), 0),
total_remaining_tags=Coalesce(Sum('total_remaining_tags'), 0),
tag_count_created_by_batch=Coalesce(Sum('tag_count'), 0),
has_distributed_batches_number=Count(
'id',
distinct=True,
filter=Q(status='distributed')
)
)
.order_by('species_code')
)
base_data['batch_data_by_species'] = list(species_data)
return base_data

View File

@@ -4,8 +4,10 @@ from django.db import transaction
from django.db.models import Sum, Q from django.db.models import Sum, Q
from django.db.models.aggregates import Count from django.db.models.aggregates import Count
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from rest_framework.exceptions import PermissionDenied
from apps.authentication.models import Organization from apps.authentication.models import Organization
from apps.livestock.models import LiveStockSpecies
from apps.tag.exceptions import TagException from apps.tag.exceptions import TagException
from apps.tag.models import Tag, TagBatch, TagDistribution, TagDistributionBatch from apps.tag.models import Tag, TagBatch, TagDistribution, TagDistributionBatch
from common.generics import generate_unique_code from common.generics import generate_unique_code
@@ -16,7 +18,7 @@ class TagDistributionService:
service of distribute tags in organizations service of distribute tags in organizations
""" """
def create_distribution(self, org: Organization = None, data: dict = None): def create_distribution_from_batch(self, org: Organization = None, data: dict = None):
""" """
distribute tags with batch distribute tags with batch
""" """
@@ -56,7 +58,8 @@ class TagDistributionService:
assigner_org=org, assigner_org=org,
assigned_org=assigned_org, assigned_org=assigned_org,
species_code=distribution.get('species_code'), species_code=distribution.get('species_code'),
distributed_number=distribution.get('count'), total_tag_count=distribution.get('count'),
remaining_number=distribution.get('count'),
dist_identity=generate_unique_code(f"{random.randint(1000, 9999)}"), dist_identity=generate_unique_code(f"{random.randint(1000, 9999)}"),
) )
@@ -72,6 +75,7 @@ class TagDistributionService:
# create distribution batch # create distribution batch
distributions_batch = TagDistributionBatch.objects.create( distributions_batch = TagDistributionBatch.objects.create(
parent=TagDistributionBatch.objects.get(id=data.get('parent')) if data.get('parent') else None,
assigner_org=org, assigner_org=org,
assigned_org=assigned_org, assigned_org=assigned_org,
total_tag_count=total_counted_tags, total_tag_count=total_counted_tags,
@@ -82,17 +86,20 @@ class TagDistributionService:
return {'tag_distributions': distributions, 'distributions_batch': distributions_batch} return {'tag_distributions': distributions, 'distributions_batch': distributions_batch}
def edit_distribution(self, dist_batch: TagDistributionBatch = None, data: dict = None, org: Organization = None): def edit_distribution_from_batch(
self, dist_batch: TagDistributionBatch = None,
data: dict = None,
org: Organization = None
):
""" """
edit record of distributed tags edit record of distributed tags
""" """
# clear and hard delete of distributions # clear and hard delete of distributions
distributions = dist_batch.distributions.all() dist_batch_distributions = dist_batch.distributions.all()
for dist in distributions: # free distributed tags from reserve for dist in dist_batch_distributions: # free distributed tags from reserve
dist.tag.all().update(status='F') dist.tag.all().update(status='F')
dist_batch.distributions.clear() dist_batch_distributions.delete()
distributions.delete()
# create new distributions and update batch # create new distributions and update batch
total_counted_tags = 0 total_counted_tags = 0
@@ -129,7 +136,8 @@ class TagDistributionService:
assigner_org=org, assigner_org=org,
assigned_org=assigned_org, assigned_org=assigned_org,
species_code=distribution.get('species_code'), species_code=distribution.get('species_code'),
distributed_number=distribution.get('count'), total_tag_count=distribution.get('count'),
remaining_number=distribution.get('count'),
dist_identity=generate_unique_code(f"{random.randint(1000, 9999)}"), dist_identity=generate_unique_code(f"{random.randint(1000, 9999)}"),
) )
@@ -147,10 +155,172 @@ class TagDistributionService:
dist_batch.assigned_org = assigned_org dist_batch.assigned_org = assigned_org
dist_batch.total_tag_count = total_counted_tags dist_batch.total_tag_count = total_counted_tags
dist_batch.distribution_type = distribution_type # noqa dist_batch.distribution_type = distribution_type # noqa
dist_batch.save(update_fields=['assigned_org', 'total_tag_count', 'distribution_type'])
dist_batch.distributions.add(*distributions) dist_batch.distributions.add(*distributions)
return {'tag_distributions': distributions, 'distributions_batch': dist_batch} return {'tag_distributions': distributions, 'distributions_batch': dist_batch}
def create_distribution_from_distribution(
self, org: Organization = None,
tag_batch: TagDistributionBatch = None,
data: dict = None
):
"""
create a distribution from distribution to target organization
"""
with transaction.atomic():
distributions = []
total_counted_tags = 0
assigned_org = Organization.objects.get(id=data['assigned_org'])
parent_batch = TagDistributionBatch.objects.get(
id=data['parent_distribution_batch']
)
if parent_batch.assigned_org != org and org.type.key != 'ADM':
raise PermissionDenied("دسترسی غیرمجاز") # noqa
for dist_data in data['dists']:
species = dist_data['species_code']
count = dist_data['count']
parent_tag_distribution = TagDistribution.objects.get(
id=dist_data['parent_tag_distribution']
)
batch = TagBatch.objects.get(
batch_identity=dist_data.get('batch_identity')
) if dist_data.get('batch_identity') else None
tags = Tag.objects.filter(
distributions__tag_distribution_batch=parent_batch,
species_code=species,
status='R',
)
if tags.count() < count:
raise TagException("پلاک کافی برای این گونه وجود ندارد", 403) # noqa
dist = TagDistribution.objects.create(
parent=parent_tag_distribution,
batch=batch,
assigner_org=org,
assigned_org=assigned_org,
species_code=species,
total_tag_count=count,
remaining_number=count,
dist_identity=generate_unique_code(
f"{random.randint(1000, 9999)}"
),
)
selected_tags = tags.order_by('create_date')[:count]
dist.tag.add(*selected_tags)
distributions.append(dist)
total_counted_tags += count
dist_batch = TagDistributionBatch.objects.create(
parent=parent_batch,
assigner_org=org,
assigned_org=assigned_org,
total_tag_count=total_counted_tags,
distribution_type=parent_batch.distribution_type,
dist_batch_identity=generate_unique_code(
f"{random.randint(1000, 9999)}"
)
)
dist_batch.distributions.add(*distributions)
return {
'tag_distributions': distributions,
'distributions_batch': dist_batch
}
def edit_distribution_from_distribution(
self, org: Organization = None,
tag_batch: TagDistributionBatch = None,
data: dict = None
):
with transaction.atomic():
if tag_batch.assigner_org != org:
raise PermissionDenied("اجازه ویرایش این توزیع را ندارید") # noqa
for dist in tag_batch.distributions.all():
dist.tag.all().update(
status='R',
organization=org
)
old_distributions = tag_batch.distributions.all()
tag_batch.distributions.clear()
old_distributions.delete()
assigned_org = Organization.objects.get(id=data['assigned_org'])
parent_batch = tag_batch.parent
distributions = []
total_counted_tags = 0
for dist_data in data['dists']:
species = dist_data['species_code']
parent_tag_distribution = TagDistribution.objects.get(
id=dist_data['parent_tag_distribution']
)
batch = TagBatch.objects.get(
batch_identity=dist_data.get('batch_identity')
) if dist_data.get('batch_identity') else None
count = dist_data['count']
tags = Tag.objects.filter(
distributions__tag_distribution_batch=parent_batch,
species_code=species,
status='R',
)
if tags.count() < count:
raise TagException(
"پلاک کافی برای این گونه وجود ندارد", # noqa
403
)
dist = TagDistribution.objects.create(
parent=parent_tag_distribution,
batch=batch,
assigner_org=org,
assigned_org=assigned_org,
species_code=species,
total_tag_count=count,
remaining_number=count,
dist_identity=generate_unique_code(
f"{random.randint(1000, 9999)}"
),
)
selected_tags = tags.order_by('create_date')[:count]
dist.tag.add(*selected_tags)
distributions.append(dist)
total_counted_tags += count
# 5⃣ update distribution batch
tag_batch.assigned_org = assigned_org
tag_batch.total_tag_count = total_counted_tags
tag_batch.is_closed = False
tag_batch.save(update_fields=[
'assigned_org',
'total_tag_count',
'is_closed'
])
tag_batch.distributions.add(*distributions)
return {
'tag_distributions': distributions,
'distributions_batch': tag_batch
}
def distribution_batch_main_dashboard(self, org: Organization, is_closed: str = 'false'): def distribution_batch_main_dashboard(self, org: Organization, is_closed: str = 'false'):
""" """
distribution batch main page dashboard detail distribution batch main page dashboard detail
@@ -159,16 +329,18 @@ class TagDistributionService:
is_closed = False if is_closed == 'false' else True is_closed = False if is_closed == 'false' else True
if org.type.key == 'ADM': if org.type.key == 'ADM':
distributions_batch = TagDistributionBatch.objects.prefetch_related( distribution_query = (Q(is_closed=is_closed))
'distributions'
).filter(is_closed=is_closed)
else: else:
distributions_batch = TagDistributionBatch.objects.filter( distribution_query = (
Q(assigner_org=org) | Q(assigner_org=org) |
Q(assigned_org=org), Q(assigned_org=org),
is_closed=is_closed, Q(is_closed=is_closed)
) )
distributions_batch = TagDistributionBatch.objects.prefetch_related(
'distributions'
).filter(distribution_query)
data = distributions_batch.aggregate( data = distributions_batch.aggregate(
count=Count('id'), count=Count('id'),
total_sent_tag_count=Coalesce(Sum('total_tag_count', filter=Q(assigner_org=org)), 0), total_sent_tag_count=Coalesce(Sum('total_tag_count', filter=Q(assigner_org=org)), 0),
@@ -179,11 +351,24 @@ class TagDistributionService:
remaining_tag_count=Sum('remaining_tag_count'), remaining_tag_count=Sum('remaining_tag_count'),
) )
# distributions item list detail
items_list = []
distributions = TagDistribution.objects.filter( distributions = TagDistribution.objects.filter(
Q(assigner_org=org) | distribution_query
Q(assigned_org=org),
is_closed=is_closed,
) )
print(distributions) species = LiveStockSpecies.objects.values('value')
for spec in species:
dist_data = distributions.aggregate(
dist_count=Count('id', filter=Q(species_code=spec.get('value'))),
tag_count=Coalesce(
Sum('distributed_number', filter=Q(species_code=spec.get('value'))), 0
)
)
dist_data.update({'species_code': spec.get('value')}) # add species code to data
items_list.append(dist_data)
data.update({'items': items_list})
return data return data

View File

@@ -62,6 +62,7 @@ class TagService:
species_code=data.get('species_code'), species_code=data.get('species_code'),
serial_from=serial_start_range, serial_from=serial_start_range,
serial_to=serial_end_range, serial_to=serial_end_range,
total_remaining_tags=request_number if request_number > 0 else 1,
status='created', status='created',
) )
@@ -104,6 +105,7 @@ class TagService:
batch.species_code = data.get('species_code') batch.species_code = data.get('species_code')
batch.serial_from = serial_start_range batch.serial_from = serial_start_range
batch.serial_to = serial_end_range batch.serial_to = serial_end_range
batch.total_remaining_tags = request_number
batch.save(update_fields=['request_number', 'species_code', 'serial_from', 'serial_to']) batch.save(update_fields=['request_number', 'species_code', 'serial_from', 'serial_to'])
# recreate tags for batch # recreate tags for batch

View File

View File

@@ -0,0 +1,75 @@
from django.db.models import Sum
from django.db.models.functions import Coalesce
from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save
from django.dispatch import receiver
from apps.tag.models import TagDistribution, TagDistributionBatch, Tag
@receiver(m2m_changed, sender=TagDistribution.tag.through)
def update_batch_on_distribution_change(
sender, instance: TagDistribution, action, **kwargs
):
if action not in ['post_add', 'post_remove', 'post_clear']:
return
if not instance.batch:
return
if instance.parent:
return
batch = instance.batch
distributions = TagDistribution.objects.filter(batch=batch)
distributed_tags = Tag.objects.filter(
distributions__batch=batch,
status__in=['R', 'A'],
).distinct().count()
print("distributed_tags", distributed_tags)
batch.total_distributed_tags = distributed_tags
batch.total_remaining_tags = (
int(batch.request_number) - distributed_tags
)
batch.status = (
'distributed'
if batch.total_remaining_tags == 0
else 'created'
)
batch.save(update_fields=[
'total_distributed_tags',
'total_remaining_tags',
'status'
])
@receiver(post_save, sender=TagDistributionBatch)
def calculate_tag_distribution_detail(sender, instance: TagDistributionBatch, **kwargs):
"""
calculate distribution & remaining distributed tags
"""
if getattr(instance, 'flag', False):
return
tag_dist_batch = instance
parent = tag_dist_batch.parent
if parent:
parent.total_distributed_tag_count = parent.children.aggregate(
total=Coalesce(Sum('total_tag_count'), 0)
)['total']
parent.remaining_tag_count = (
parent.total_tag_count - parent.total_distributed_tag_count
)
parent.parent_flag = True
parent.save(update_fields=['remaining_tag_count', 'total_distributed_tag_count'])
if not getattr(instance, 'parent_flag', False):
tag_dist_batch.remaining_tag_count = tag_dist_batch.total_tag_count
instance.flag = True
tag_dist_batch.save(update_fields=['remaining_tag_count'])

View File

@@ -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_batch_service import TagBatchService
from apps.tag.services.tag_distribution_services import TagDistributionService 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
@@ -34,6 +35,7 @@ class TagViewSet(BaseViewSet, TagService, SoftDeleteMixin, DynamicSearchMixin, v
filter_backends = [SearchFilter] filter_backends = [SearchFilter]
search_fields = [ search_fields = [
'serial', 'serial',
'status',
'tag_code', 'tag_code',
'organization__name', 'organization__name',
'organization__type__key', 'organization__type__key',
@@ -69,7 +71,6 @@ class TagViewSet(BaseViewSet, TagService, SoftDeleteMixin, DynamicSearchMixin, v
org = get_organization_by_user(request.user) # 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] serial_start_range, serial_end_range = request.data.pop('serial_range') # serial_range is like [500, 550]
print(serial_start_range, serial_end_range)
data = request.data.copy() data = request.data.copy()
# create tag & batch # create tag & batch
@@ -127,7 +128,7 @@ class TagViewSet(BaseViewSet, TagService, SoftDeleteMixin, DynamicSearchMixin, v
""" """
get tags by batch id get tags by batch id
""" """
tags = self.queryset.filter(tags__id=pk) tags = self.queryset.filter(batches__id=pk)
page = self.paginate_queryset(tags) page = self.paginate_queryset(tags)
if page is not None: # noqa if page is not None: # noqa
@@ -305,7 +306,7 @@ class AllocatedTagsViewSet(SoftDeleteMixin, viewsets.ModelViewSet):
serializer_class = AllocatedTagsSerializer serializer_class = AllocatedTagsSerializer
class TagBatchViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets.ModelViewSet): class TagBatchViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, TagBatchService, viewsets.ModelViewSet):
queryset = TagBatch.objects.all() queryset = TagBatch.objects.all()
serializer_class = TagBatchSerializer serializer_class = TagBatchSerializer
filter_backends = [SearchFilter] filter_backends = [SearchFilter]
@@ -323,6 +324,11 @@ class TagBatchViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets
queryset = self.get_queryset(visibility_by_org_scope=True).order_by('-create_date') queryset = self.get_queryset(visibility_by_org_scope=True).order_by('-create_date')
params = self.request.query_params # noqa
if params.get('species_code'):
queryset = queryset.filter(species_code=int(params.get('species_code')))
# filter queryset # filter queryset
queryset = self.filter_query(self.filter_queryset(queryset)) queryset = self.filter_query(self.filter_queryset(queryset))
@@ -332,6 +338,44 @@ class TagBatchViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets
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)
@action(
methods=['get'],
detail=False,
url_name='main_dashboard',
url_path='main_dashboard',
name='main_dashboard',
)
def main_dashboard(self, request):
"""
dashboard of tag batches main page
"""
org = get_organization_by_user(request.user)
dashboard_data = self.tag_batch_main_dashboard(org=org)
return Response(dashboard_data, status=status.HTTP_200_OK)
@action(
methods=['get'],
detail=True,
url_name='inner_dashboard',
url_path='inner_dashboard',
name='inner_dashboard',
)
def inner_dashboard(self, request, pk=None):
"""
dashboard of tag batches inner page by id
"""
org = get_organization_by_user(request.user)
dashboard_data = self.tag_batch_main_dashboard(
org=org,
batch=self.queryset.filter(id=self.get_object().id)
)
return Response(dashboard_data, status=status.HTTP_200_OK)
def destroy(self, request, pk=None, *args, **kwargs): def destroy(self, request, pk=None, *args, **kwargs):
""" """
soft delete batch with tag items soft delete batch with tag items
@@ -384,7 +428,7 @@ class TagDistributionViewSet(
org = get_organization_by_user(request.user) org = get_organization_by_user(request.user)
data = request.data.copy() data = request.data.copy()
distribution_data = self.create_distribution( distribution_data = self.create_distribution_from_batch(
org=org, org=org,
data=data data=data
) )
@@ -401,7 +445,57 @@ class TagDistributionViewSet(
data = request.data.copy() data = request.data.copy()
dist_batch = tag_models.TagDistributionBatch.objects.get(id=pk) dist_batch = tag_models.TagDistributionBatch.objects.get(id=pk)
distribution_data = self.edit_distribution(org=org, data=data, dist_batch=dist_batch) distribution_data = self.edit_distribution_from_batch(org=org, data=data, dist_batch=dist_batch)
serializer = self.serializer_class(distribution_data.get('tag_distributions'), many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@action(
methods=['post'],
detail=True,
url_path='distribute_distribution',
url_name='distribute_distribution',
name='distribute_distribution',
)
def create_distribute_from_distribution(self, request, pk=None):
"""
distribute from a tag distribution
"""
data = request.data.copy()
org = get_organization_by_user(request.user)
dist_batch = tag_models.TagDistributionBatch.objects.get(id=pk)
distribution_data = self.create_distribution_from_distribution(
org=org,
tag_batch=dist_batch,
data=data
)
serializer = self.serializer_class(distribution_data.get('tag_distributions'), many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@action(
methods=['put'],
detail=True,
url_path='edit_distribute_distribution',
url_name='edit_distribute_distribution',
name='edit_distribute_distribution',
)
def update_distribute_from_distribution(self, request, pk=None):
"""
update created distribution from distribution
"""
data = request.data.copy()
org = get_organization_by_user(request.user)
dist_batch = tag_models.TagDistributionBatch.objects.get(id=pk)
distribution_data = self.edit_distribution_from_distribution(
org=org,
tag_batch=dist_batch,
data=data
)
serializer = self.serializer_class(distribution_data.get('tag_distributions'), many=True) serializer = self.serializer_class(distribution_data.get('tag_distributions'), many=True)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@@ -464,9 +558,9 @@ class TagDistributionViewSet(
class TagDistributionBatchViewSet( class TagDistributionBatchViewSet(
BaseViewSet, BaseViewSet,
viewsets.ModelViewSet,
SoftDeleteMixin, SoftDeleteMixin,
DynamicSearchMixin, DynamicSearchMixin,
viewsets.ModelViewSet,
TagDistributionService TagDistributionService
): ):
queryset = tag_models.TagDistributionBatch.objects.all() queryset = tag_models.TagDistributionBatch.objects.all()
@@ -495,6 +589,15 @@ class TagDistributionBatchViewSet(
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 retrieve(self, request, pk=None, *args, **kwargs):
"""
detail of distribution batch
"""
distribution_batch = self.get_object()
serializer = self.serializer_class(distribution_batch)
return Response(serializer.data, status=status.HTTP_200_OK)
@action( @action(
methods=['post'], methods=['post'],
detail=True, detail=True,
@@ -509,6 +612,8 @@ class TagDistributionBatchViewSet(
dist_batch.is_closed = True dist_batch.is_closed = True
dist_batch.save() dist_batch.save()
dist_batch.distributions.all().update(is_closed=True) # close distributions of batch dist_batch.distributions.all().update(is_closed=True) # close distributions of batch
for distribute in dist_batch.distributions.all():
distribute.tag.all().update(status='F')
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
@@ -567,3 +672,19 @@ class TagDistributionBatchViewSet(
dashboard_data = self.distribution_batch_main_dashboard(org=org, is_closed=params.get('is_closed')) dashboard_data = self.distribution_batch_main_dashboard(org=org, is_closed=params.get('is_closed'))
return Response(dashboard_data, status=status.HTTP_200_OK) return Response(dashboard_data, status=status.HTTP_200_OK)
def destroy(self, request, pk=None, *args, **kwargs):
"""
delete tag distribution batch and free their tag from distribute
"""
dist_batch = self.get_object()
for distribute in dist_batch.distributions.all():
distribute.tag.all().update(status='F')
distribute.tag.clear()
distribute.soft_delete()
dist_batch.soft_delete()
return Response(status=status.HTTP_200_OK)

View File

@@ -205,6 +205,8 @@ class TagDistributionBatchSerializer(serializers.ModelSerializer):
'batch_identity': dist.batch.batch_identity if dist.batch else None, 'batch_identity': dist.batch.batch_identity if dist.batch else None,
'species_code': dist.species_code, 'species_code': dist.species_code,
'distributed_number': dist.distributed_number, 'distributed_number': dist.distributed_number,
'total_tag_count': dist.total_tag_count,
'remaining_number': dist.remaining_number,
'serial_from': dist.batch.serial_from if dist.batch else None, 'serial_from': dist.batch.serial_from if dist.batch else None,
'serial_to': dist.batch.serial_to if dist.batch else None, 'serial_to': dist.batch.serial_to if dist.batch else None,
} for dist in instance.distributions.all()] } for dist in instance.distributions.all()]

View File

@@ -12,7 +12,14 @@ from apps.warehouse.models import InventoryQuotaSaleTransaction, InventoryQuotaS
class TransactionDashboardService: class TransactionDashboardService:
@staticmethod @staticmethod
def get_dashboard(org: Organization, start_date: str = None, end_date: str = None, status: str = None): def get_dashboard(
org: Organization,
free_visibility_tr_objects=None,
free_visibility_tr_item_objects=None,
start_date: str = None,
end_date: str = None,
status: str = None
):
orgs_child = get_all_org_child(org=org) orgs_child = get_all_org_child(org=org)
orgs_child.append(org) orgs_child.append(org)
@@ -23,13 +30,18 @@ class TransactionDashboardService:
items = InventoryQuotaSaleItem.objects.all().select_related("gov_product", "free_product") items = InventoryQuotaSaleItem.objects.all().select_related("gov_product", "free_product")
else: else:
transactions = InventoryQuotaSaleTransaction.objects.filter( if free_visibility_tr_objects:
seller_organization__in=orgs_child transactions = free_visibility_tr_objects
) items = InventoryQuotaSaleItem.objects.filter(
transaction__in=transactions
items = InventoryQuotaSaleItem.objects.filter( ).select_related("gov_product", "free_product")
transaction__seller_organization__in=orgs_child else:
).select_related("gov_product", "free_product") transactions = InventoryQuotaSaleTransaction.objects.filter(
seller_organization__in=orgs_child
)
items = InventoryQuotaSaleItem.objects.filter(
transaction__seller_organization__in=orgs_child
).select_related("gov_product", "free_product")
# filter queryset (transactions & items) by date # filter queryset (transactions & items) by date
if start_date and end_date: if start_date and end_date:

View File

@@ -280,13 +280,31 @@ class InventoryQuotaSaleTransactionViewSet(
transaction_status = query_param.get('status') if 'status' in query_param.keys() else None transaction_status = query_param.get('status') if 'status' in query_param.keys() else None
org = get_organization_by_user(request.user) org = get_organization_by_user(request.user)
# filer by date & transaction status if org.free_visibility_by_scope:
transaction_dashboard_data = self.get_dashboard(
org, tr_objects = self.get_queryset(visibility_by_org_scope=True)
start_date=start_date,
end_date=end_date, tr_item_view = InventoryQuotaSaleItemViewSet()
status=transaction_status tr_item_view.request = request
) tr_item_view.kwargs = {'pk': None}
tr_item_objects = tr_item_view.get_queryset(visibility_by_org_scope=True)
transaction_dashboard_data = self.get_dashboard(
org,
free_visibility_tr_objects=tr_objects,
free_visibility_tr_item_objects=tr_item_objects,
start_date=start_date,
end_date=end_date,
status=transaction_status,
)
else:
# filer by date & transaction status
transaction_dashboard_data = self.get_dashboard(
org,
start_date=start_date,
end_date=end_date,
status=transaction_status,
)
return Response(transaction_dashboard_data, status=status.HTTP_200_OK) return Response(transaction_dashboard_data, status=status.HTTP_200_OK)