import - sync_livestock/herd_rancher_sync/purchase_policy & service_area in organization
This commit is contained in:
@@ -261,11 +261,15 @@ class OrganizationSerializer(serializers.ModelSerializer):
|
||||
'company_code',
|
||||
'field_of_activity',
|
||||
'free_visibility_by_scope',
|
||||
'service_area'
|
||||
'service_area', # noqa
|
||||
'purchase_policy'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'service_area': {
|
||||
'service_area': { # noqa
|
||||
'required': False
|
||||
},
|
||||
'purchase_policy': {
|
||||
'purchase_policy': False
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,6 +374,7 @@ class OrganizationSerializer(serializers.ModelSerializer):
|
||||
instance.address = validated_data.get('address', instance.address)
|
||||
instance.parent_organization = validated_data.get('parent_organization', instance.parent_organization)
|
||||
instance.national_unique_id = validated_data.get('national_unique_id', instance.national_unique_id)
|
||||
instance.purchase_policy = validated_data.get('purchase_policy', instance.purchase_policy)
|
||||
instance.free_visibility_by_scope = validated_data.get(
|
||||
'free_visibility_by_scope',
|
||||
instance.free_visibility_by_scope
|
||||
|
||||
@@ -147,6 +147,16 @@ class Organization(BaseModel):
|
||||
has_pos = models.BooleanField(default=False)
|
||||
additional_data = models.JSONField(default=dict)
|
||||
service_area = models.ManyToManyField(City, related_name='service_area')
|
||||
PURCHASE_POLICIES = (
|
||||
('INTERNAL_ONLY', 'Internal Only'),
|
||||
('CROSS_COOP', 'Cross Cooperative Allowed'),
|
||||
)
|
||||
purchase_policy = models.CharField(
|
||||
max_length=20,
|
||||
choices=PURCHASE_POLICIES,
|
||||
default='INTERNAL_ONLY',
|
||||
help_text='defines where ranchers can purchase from'
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}-{self.type}'
|
||||
|
||||
@@ -12,6 +12,11 @@ class DuplicateRancherException(APIException):
|
||||
default_detail = "اطلاعات دامدار استعلام شده دارای مشکل میباشد با پشتیبانی تماس بگیرید" # noqa
|
||||
|
||||
|
||||
class RancherOrganizationLinkException(APIException):
|
||||
status_code = status.HTTP_403_FORBIDDEN
|
||||
default_detail = "این دامدار دسترسی خرید از سازمان (تعاونی) مرتبط با این دستگاه را ندارد" # noqa
|
||||
|
||||
|
||||
class HerdCapacityException(APIException):
|
||||
status_code = status.HTTP_403_FORBIDDEN
|
||||
default_detail = "مقدار حجم سبک و سنگین وارد شده از ظرفیت گله بیشتر میباشد" # noqa
|
||||
|
||||
39
apps/herd/management/commands/link_ranchers_parallel.py
Normal file
39
apps/herd/management/commands/link_ranchers_parallel.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from multiprocessing import cpu_count, Pool
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection
|
||||
|
||||
from apps.herd.models import Rancher
|
||||
from .link_ranchers_parallel_worker import process_city
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Parallel link ranchers to cooperative by city"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--worker',
|
||||
type=int,
|
||||
default=cpu_count() // 2
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
workers = options['workers']
|
||||
|
||||
city_ids = (
|
||||
Rancher.objects.filter(city__isnull=False)
|
||||
.values_list('city_id', flat=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
f"Starting parallel sync for {len(city_ids)} cities "
|
||||
f"with {workers} workers"
|
||||
)
|
||||
|
||||
connection.close()
|
||||
|
||||
with Pool(processes=workers) as pool:
|
||||
pool.map(process_city, city_ids)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("DONE ✅"))
|
||||
@@ -0,0 +1,55 @@
|
||||
from django.db import connection, transaction
|
||||
|
||||
from apps.herd.models import Organization, Rancher, RancherOrganizationLink
|
||||
|
||||
BATCH_SIZE = 2000
|
||||
|
||||
|
||||
def process_city(city_id):
|
||||
connection.close()
|
||||
|
||||
orgs = Organization.objects.filter(
|
||||
city_id=city_id,
|
||||
type__key='CO'
|
||||
)
|
||||
|
||||
if not orgs.exists() or orgs.count() > 1:
|
||||
return
|
||||
|
||||
coop = orgs.first()
|
||||
|
||||
ranchers = (
|
||||
Rancher.objects.filter(city_id=city_id)
|
||||
.only('id')
|
||||
)
|
||||
|
||||
buffer = []
|
||||
|
||||
for rancher in ranchers.iterator(chunk_size=BATCH_SIZE):
|
||||
|
||||
if RancherOrganizationLink.objects.filter(
|
||||
rancher_id=rancher.id
|
||||
).exists():
|
||||
continue
|
||||
|
||||
buffer.append(
|
||||
RancherOrganizationLink(
|
||||
rancher_id=rancher.id,
|
||||
organization_id=coop.id
|
||||
)
|
||||
)
|
||||
|
||||
if len(buffer) >= BATCH_SIZE:
|
||||
bulk_insert(buffer)
|
||||
buffer.clear()
|
||||
|
||||
if buffer:
|
||||
bulk_insert(buffer)
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def bulk_insert(objs):
|
||||
RancherOrganizationLink.objects.bulk_create(
|
||||
objs,
|
||||
ignore_conflicts=True
|
||||
)
|
||||
@@ -11,6 +11,7 @@ from apps.herd.exception import DuplicateRancherException
|
||||
from apps.herd.models import Herd, Rancher
|
||||
from apps.herd.pos.api.v1.serializers import HerdSerializer, RancherSerializer
|
||||
from apps.livestock.web.api.v1.serializers import LiveStockSerializer
|
||||
from apps.pos_device.mixins.pos_device_mixin import POSDeviceMixin
|
||||
from common.tools import CustomOperations
|
||||
|
||||
|
||||
@@ -111,7 +112,7 @@ class HerdViewSet(viewsets.ModelViewSet):
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
|
||||
class RancherViewSet(viewsets.ModelViewSet, DynamicSearchMixin):
|
||||
class RancherViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDeviceMixin):
|
||||
queryset = Rancher.objects.all() # noqa
|
||||
serializer_class = RancherSerializer
|
||||
permission_classes = [AllowAny]
|
||||
@@ -140,11 +141,17 @@ class RancherViewSet(viewsets.ModelViewSet, DynamicSearchMixin):
|
||||
""" check national code & existence of rancher """
|
||||
|
||||
rancher = self.queryset.filter(national_code=request.data['national_code'])
|
||||
org = self.get_device_organization()
|
||||
|
||||
if len(rancher) > 1:
|
||||
raise DuplicateRancherException()
|
||||
|
||||
if rancher.exists():
|
||||
|
||||
# if not RancherOrganizationLink.objects.filter(organization=org, rancher=rancher).exists():
|
||||
# if org.purchase_policy == 'INTERNAL_ONLY':
|
||||
# raise RancherOrganizationLinkException()
|
||||
|
||||
serializer = self.serializer_class(rancher.first())
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
else:
|
||||
|
||||
@@ -12,17 +12,11 @@ class HerdRancherSyncService:
|
||||
optimized bulk sync for large datasets
|
||||
"""
|
||||
|
||||
# -------------------------
|
||||
# Cache Cities
|
||||
# -------------------------
|
||||
city_map = {
|
||||
name.strip(): id
|
||||
for id, name in City.objects.all().values_list('id', 'name')
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Cache existing ranchers
|
||||
# -------------------------
|
||||
rancher_map = {
|
||||
r.national_code: r
|
||||
for r in Rancher.objects.filter(
|
||||
@@ -49,9 +43,6 @@ class HerdRancherSyncService:
|
||||
|
||||
for temp in queryset.iterator(chunk_size=batch_size):
|
||||
|
||||
# -------------------------
|
||||
# Rancher
|
||||
# -------------------------
|
||||
rancher = rancher_map.get(temp.rancher_national_code)
|
||||
|
||||
if not rancher:
|
||||
@@ -66,9 +57,6 @@ class HerdRancherSyncService:
|
||||
new_ranchers.append(rancher)
|
||||
rancher_map[temp.rancher_national_code] = rancher
|
||||
|
||||
# -------------------------
|
||||
# Herd
|
||||
# -------------------------
|
||||
herd_key = (temp.rancher_national_code, temp.herd_code)
|
||||
|
||||
if herd_key in existing_herds:
|
||||
@@ -95,9 +83,6 @@ class HerdRancherSyncService:
|
||||
}
|
||||
)
|
||||
|
||||
# -------------------------
|
||||
# Bulk DB Operations
|
||||
# -------------------------
|
||||
with transaction.atomic():
|
||||
Rancher.objects.bulk_create(
|
||||
new_ranchers,
|
||||
@@ -119,7 +104,7 @@ class HerdRancherSyncService:
|
||||
rancher = rancher_map.get(item["rancher_code"])
|
||||
|
||||
if not rancher:
|
||||
continue # یا raise error
|
||||
continue
|
||||
|
||||
herd = item["herd"]
|
||||
herd.rancher = rancher
|
||||
|
||||
@@ -4,7 +4,7 @@ from datetime import timedelta
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils.timezone import now
|
||||
from rest_framework import status
|
||||
from rest_framework import status, filters
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import APIException
|
||||
@@ -70,7 +70,7 @@ class ProviderCompanyViewSet(SoftDeleteMixin, viewsets.ModelViewSet): # noqa
|
||||
class DeviceViewSet(BaseViewSet, SoftDeleteMixin, viewsets.ModelViewSet, AdminFilterMixin):
|
||||
queryset = pos_models.Device.objects.all()
|
||||
serializer_class = device_serializer.DeviceSerializer
|
||||
# filter_backends = [filters.SearchFilter]
|
||||
filter_backends = [filters.SearchFilter]
|
||||
search_fields = [
|
||||
'device_identity',
|
||||
'acceptor',
|
||||
@@ -176,7 +176,7 @@ class DeviceViewSet(BaseViewSet, SoftDeleteMixin, viewsets.ModelViewSet, AdminFi
|
||||
def devices_by_psp(self, request, pk=None):
|
||||
""" list of devices by their psp """
|
||||
|
||||
devices = self.queryset.filter(organization__id=pk).order_by('-create_date')
|
||||
devices = self.filter_queryset(self.queryset.filter(organization__id=pk).order_by('-create_date'))
|
||||
|
||||
# paginate devices
|
||||
page = self.paginate_queryset(devices)
|
||||
|
||||
@@ -31,7 +31,7 @@ class Command(BaseCommand):
|
||||
processed = 0
|
||||
start_time = time.time()
|
||||
|
||||
LOG_EVERY = 10_000 # هر چند رکورد لاگ بده
|
||||
LOG_EVERY = 10000
|
||||
|
||||
buffer = []
|
||||
for temp in qs.iterator(chunk_size=CHUNK_SIZE):
|
||||
@@ -63,8 +63,6 @@ class Command(BaseCommand):
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("DONE ✅"))
|
||||
|
||||
# ----------------------------------------------------
|
||||
|
||||
def process_batch(self, temps):
|
||||
herd_codes = {t.herd_code for t in temps if t.herd_code}
|
||||
|
||||
@@ -103,7 +101,7 @@ class Command(BaseCommand):
|
||||
for temp in temps:
|
||||
herd = herds.get(temp.herd_code)
|
||||
if not herd:
|
||||
continue # گله باید وجود داشته باشد
|
||||
continue
|
||||
|
||||
birthdate = parse_birthdate(temp.birthdate)
|
||||
gender = 1 if temp.gender == 'M' else 2
|
||||
@@ -113,7 +111,6 @@ class Command(BaseCommand):
|
||||
key = (temp.herd_code, birthdate, gender)
|
||||
livestock = livestock_map.get(key)
|
||||
|
||||
# ---------- دام وجود ندارد ----------
|
||||
if not livestock:
|
||||
if not temp.tag:
|
||||
continue
|
||||
@@ -139,7 +136,6 @@ class Command(BaseCommand):
|
||||
temp.sync_status = 'S'
|
||||
continue
|
||||
|
||||
# ---------- دام وجود دارد ولی پلاک ندارد ----------
|
||||
if livestock.tag is None and temp.tag:
|
||||
tag = existing_tags.get(temp.tag)
|
||||
|
||||
@@ -153,7 +149,6 @@ class Command(BaseCommand):
|
||||
|
||||
temp.sync_status = 'S'
|
||||
|
||||
# ---------- BULK ----------
|
||||
with transaction.atomic():
|
||||
Tag.objects.bulk_create(new_tags, batch_size=BATCH_SIZE)
|
||||
LiveStock.objects.bulk_create(
|
||||
|
||||
Reference in New Issue
Block a user