From 2bc1931defbfd8801cc515efc463d373b838ca5b Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Tue, 5 Aug 2025 16:33:42 +0330 Subject: [PATCH] change on broker (organization type) - some changes on livestock & herd --- .../management/commands/import_ranchers.py | 94 +++++++++++++++++++ apps/herd/web/api/v1/api.py | 2 +- .../management/commands/import_livestock.py | 86 +++++++++++++++++ apps/livestock/web/api/v1/api.py | 2 + ...8_alter_deviceactivationcode_expires_at.py | 19 ++++ ...9_alter_deviceactivationcode_expires_at.py | 19 ++++ .../0066_remove_broker_organization.py | 17 ++++ .../0067_broker_organization_type.py | 20 ++++ apps/product/models.py | 6 +- apps/tag/web/api/v1/serializers.py | 5 + requirements.txt | 1 + 11 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 apps/herd/management/commands/import_ranchers.py create mode 100644 apps/livestock/management/commands/import_livestock.py create mode 100644 apps/pos_device/migrations/0028_alter_deviceactivationcode_expires_at.py create mode 100644 apps/pos_device/migrations/0029_alter_deviceactivationcode_expires_at.py create mode 100644 apps/product/migrations/0066_remove_broker_organization.py create mode 100644 apps/product/migrations/0067_broker_organization_type.py diff --git a/apps/herd/management/commands/import_ranchers.py b/apps/herd/management/commands/import_ranchers.py new file mode 100644 index 0000000..97822db --- /dev/null +++ b/apps/herd/management/commands/import_ranchers.py @@ -0,0 +1,94 @@ +import pandas as pd +from django.core.management.base import BaseCommand +from apps.herd.models import Rancher +from apps.herd.models import Herd +from apps.authentication.models import Province, City, Organization + + +class Command(BaseCommand): + help = "Import Rancher and Herd data from Excel" + + def add_arguments(self, parser): + parser.add_argument('excel_path', type=str) + + def handle(self, *args, **options): + path = options['excel_path'] + df = pd.read_excel(path) + records = df.to_dict(orient='records') + + self.stdout.write(self.style.SUCCESS(f"{len(records)} records loaded.")) + + ranchers_to_create = [] + herds_to_create = [] + + province_cache = {p.id: p for p in Province.objects.all()} + city_cache = {c.id: c for c in City.objects.all()} + org_cache = {o.id: o for o in Organization.objects.all()} + + for r in records: + full_name = str(r.get("fullname") or "").strip() + first_name = full_name.split(" ")[0] if full_name else "" + last_name = " ".join(full_name.split(" ")[1:]) if len(full_name.split(" ")) > 1 else "" + + # province = province_cache.get((Province.objects.get(name=r.get("province"))).id) + province = Province.objects.get(id=1) + city = City.objects.get(id=1) + + rancher = Rancher( + first_name=first_name, + last_name=last_name, + mobile=r.get("mobile"), + national_code=r.get("national_id"), + address="", + province=province, + city=city, + without_herd=not bool(r.get("herd_code")) + ) + ranchers_to_create.append(rancher) + + # ذخیره‌ی دامداران + Rancher.objects.bulk_create(ranchers_to_create, batch_size=10000) + self.stdout.write(self.style.SUCCESS(f"✅ Created {len(ranchers_to_create)} ranchers.")) + + # بازیابی مجدد همه‌ی دامداران برای نگاشت herd + rancher_cache = {r.national_code: r for r in Rancher.objects.all()} + + for r in records: + if not r.get("herd_code"): + print("not herd code") + continue + + province = Province.objects.get(id=1) + city = City.objects.get(id=1) + coop = Organization.objects.get(id=1) + print(f"province {province}") + print(f"city {city}") + print(f"coop {coop}") + + key = r.get("national_id") + print(f'key {key}') + rancher = Rancher.objects.get(national_code=key) + print(f'ranchers {rancher}') + if not rancher: + print("not rancher") + continue + + herd = Herd( + code=r.get("herd_code"), + name=r.get("herd_name"), + rancher=rancher, + province=province, + city=city, + postal=r.get("postal_code"), + institution=r.get("unit_id"), + epidemiologic=r.get("epidemiological_code"), + cooperative=coop, + heavy_livestock_number=r.get("heavy_livestock") or 0, + light_livestock_number=r.get("light_livestock") or 0, + ) + print(herd) + herds_to_create.append(herd) + print(herds_to_create) + + Herd.objects.bulk_create(herds_to_create, batch_size=10000) + self.stdout.write(self.style.SUCCESS(f"✅ Created {len(herds_to_create)} herds.")) \ No newline at end of file diff --git a/apps/herd/web/api/v1/api.py b/apps/herd/web/api/v1/api.py index 7a91199..ef5407d 100644 --- a/apps/herd/web/api/v1/api.py +++ b/apps/herd/web/api/v1/api.py @@ -151,7 +151,7 @@ class RancherViewSet(viewsets.ModelViewSet, DynamicSearchMixin): """ list of rancher herds """ rancher = self.get_object() - queryset = rancher.herd.all() # get rancher herds + queryset = rancher.herd.all().order_by('-modify_date') # get rancher herds # paginate queryset page = self.paginate_queryset(queryset) diff --git a/apps/livestock/management/commands/import_livestock.py b/apps/livestock/management/commands/import_livestock.py new file mode 100644 index 0000000..d0d6764 --- /dev/null +++ b/apps/livestock/management/commands/import_livestock.py @@ -0,0 +1,86 @@ +import pandas as pd +from django.core.management.base import BaseCommand +from django.utils.dateparse import parse_datetime +from apps.livestock.models import LiveStock, LiveStockType, LiveStockSpecies +from apps.herd.models import Herd +from apps.tag.models import Tag + + +class Command(BaseCommand): + help = "Import livestock data from Excel" + + def add_arguments(self, parser): + parser.add_argument('excel_path', type=str) + + def handle(self, *args, **options): + path = options['excel_path'] + df = pd.read_excel(path) + records = df.to_dict(orient='records') + + self.stdout.write(self.style.SUCCESS(f"{len(records)} records loaded.")) + + herd_cache = {h.code: h for h in Herd.objects.all()} + type_cache = {t.name.strip(): t for t in LiveStockType.objects.all()} + species_male = LiveStockSpecies.objects.filter(name__icontains='نر').first() + species_female = LiveStockSpecies.objects.filter(name__icontains='ماده').first() + + livestocks_to_create = [] + skipped = 0 + + for r in records: + try: + herd_code = str(r.get('herd_code')).strip() + if not herd_code: + skipped += 1 + continue + + herd = herd_cache.get(herd_code) + if not herd: + herd = Herd.objects.create( + code=herd_code, + name=f"گله {herd_code}", + province_id=int(r.get('province_id')) if r.get('province_id') else None, + city_id=int(r.get('city_id')) if r.get('city_id') else None, + cooperative_id=int(r.get('cooperative_id')) if r.get('cooperative_id') else None, + ) + herd_cache[herd_code] = herd + + tag_code = r.get('national_id_livestock_code') + tag = Tag.objects.filter(code=tag_code).first() + if not tag: + skipped += 1 + continue + + type_name = str(r.get('type')).strip() + livestock_type = type_cache.get(type_name) + if not livestock_type: + skipped += 1 + continue + + gender_str = str(r.get('gender')).strip() + gender = 1 if gender_str == 'نر' else 2 if gender_str == 'ماده' else None + species = species_male if gender == 1 else species_female + + birthdate = parse_datetime(str(r.get('birth_day_gh'))) if r.get('birth_day_gh') else None + weight_type = 'H' if type_name == 'گاو' else 'L' + + livestocks_to_create.append(LiveStock( + herd=herd, + tag=tag, + type=livestock_type, + use_type=None, + species=species, + weight_type=weight_type, + gender=gender, + birthdate=birthdate + )) + + except Exception as e: + skipped += 1 + print(e) + continue + + LiveStock.objects.bulk_create(livestocks_to_create, batch_size=10000) + + self.stdout.write(self.style.SUCCESS(f"✅ Created {len(livestocks_to_create)} records.")) + self.stdout.write(self.style.WARNING(f"⚠️ Skipped {skipped} records due to missing/invalid data.")) diff --git a/apps/livestock/web/api/v1/api.py b/apps/livestock/web/api/v1/api.py index a1b073b..81cf2f8 100644 --- a/apps/livestock/web/api/v1/api.py +++ b/apps/livestock/web/api/v1/api.py @@ -1,9 +1,11 @@ from rest_framework import viewsets from apps.livestock import models as livestock_models +from apps.tag.web.api.v1.api import TagViewSet from . import serializers as livestock_serializers from rest_framework.exceptions import APIException from rest_framework.decorators import action from rest_framework.response import Response +from common.tools import CustomOperations from django.db import transaction from rest_framework import status diff --git a/apps/pos_device/migrations/0028_alter_deviceactivationcode_expires_at.py b/apps/pos_device/migrations/0028_alter_deviceactivationcode_expires_at.py new file mode 100644 index 0000000..02214ad --- /dev/null +++ b/apps/pos_device/migrations/0028_alter_deviceactivationcode_expires_at.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2025-08-05 12:38 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0027_alter_deviceactivationcode_expires_at'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(default=datetime.datetime(2025, 8, 5, 16, 8, 12, 82964)), + ), + ] diff --git a/apps/pos_device/migrations/0029_alter_deviceactivationcode_expires_at.py b/apps/pos_device/migrations/0029_alter_deviceactivationcode_expires_at.py new file mode 100644 index 0000000..0c7a7a5 --- /dev/null +++ b/apps/pos_device/migrations/0029_alter_deviceactivationcode_expires_at.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0 on 2025-08-05 12:40 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0028_alter_deviceactivationcode_expires_at'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(default=datetime.datetime(2025, 8, 5, 16, 10, 58, 428777)), + ), + ] diff --git a/apps/product/migrations/0066_remove_broker_organization.py b/apps/product/migrations/0066_remove_broker_organization.py new file mode 100644 index 0000000..73976f8 --- /dev/null +++ b/apps/product/migrations/0066_remove_broker_organization.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0 on 2025-08-05 12:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0065_productstats_given_distribution_number'), + ] + + operations = [ + migrations.RemoveField( + model_name='broker', + name='organization', + ), + ] diff --git a/apps/product/migrations/0067_broker_organization_type.py b/apps/product/migrations/0067_broker_organization_type.py new file mode 100644 index 0000000..35c4d30 --- /dev/null +++ b/apps/product/migrations/0067_broker_organization_type.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0 on 2025-08-05 12:40 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0027_remove_organizationstats_total_buyers_and_more'), + ('product', '0066_remove_broker_organization'), + ] + + operations = [ + migrations.AddField( + model_name='broker', + name='organization_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_organization', to='authentication.organizationtype'), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index af30931..0ce7975 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -3,7 +3,7 @@ from simple_history.models import HistoricalRecords from django.db import models from apps.core.models import BaseModel from apps.authorization.models import UserRelations -from apps.authentication.models import Organization +from apps.authentication.models import OrganizationType, Organization from django.contrib.postgres.fields import ArrayField from apps.livestock.models import LiveStockType from datetime import datetime @@ -238,8 +238,8 @@ class Broker(BaseModel): related_name='product_broker', null=True ) - organization = models.ForeignKey( - Organization, + organization_type = models.ForeignKey( + OrganizationType, on_delete=models.CASCADE, related_name='product_organization', null=True diff --git a/apps/tag/web/api/v1/serializers.py b/apps/tag/web/api/v1/serializers.py index 8695521..c4c8134 100644 --- a/apps/tag/web/api/v1/serializers.py +++ b/apps/tag/web/api/v1/serializers.py @@ -19,6 +19,11 @@ class TagSerializer(serializers.ModelSerializer): 'organization', 'status', ] + extra_kwargs = { + 'serial': { + 'required': False + } + } def update(self, instance, validated_data): """ update tag information """ diff --git a/requirements.txt b/requirements.txt index cc9894f..153ee4b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,6 +48,7 @@ pyOpenSSL pyparsing python-dateutil pytz +pandas redis requests rsa==4.8