fix --> sync livestocks with table ExcelLivestocks
This commit is contained in:
@@ -0,0 +1,144 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import jdatetime
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from apps.herd.models import Herd
|
||||||
|
from apps.livestock.models import (
|
||||||
|
LiveStock,
|
||||||
|
LiveStockSpecies,
|
||||||
|
ExcelLiveStocks
|
||||||
|
)
|
||||||
|
|
||||||
|
BATCH_SIZE = 100
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Import livestock from ExcelLiveStocks into LiveStock using bulk_create"
|
||||||
|
|
||||||
|
def normalize_herd_code(self, value, length=10):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return str(value).strip().zfill(length)
|
||||||
|
|
||||||
|
def parse_jalali_datetime(self, date_str: str):
|
||||||
|
if not date_str:
|
||||||
|
return None
|
||||||
|
|
||||||
|
year, month, day = map(int, date_str.split('/'))
|
||||||
|
|
||||||
|
# jalali → gregorian (date)
|
||||||
|
g_date = jdatetime.date(year, month, day).togregorian()
|
||||||
|
|
||||||
|
# date → naive datetime
|
||||||
|
naive_dt = datetime.combine(g_date, datetime.min.time())
|
||||||
|
|
||||||
|
# naive → aware (VERY IMPORTANT)
|
||||||
|
return timezone.make_aware(naive_dt)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
qs = ExcelLiveStocks.objects.all()
|
||||||
|
|
||||||
|
if not qs.exists():
|
||||||
|
self.stdout.write(self.style.WARNING("No records to import"))
|
||||||
|
return
|
||||||
|
|
||||||
|
# ---------- preload lookups ----------
|
||||||
|
herd_map = {
|
||||||
|
h.code: h
|
||||||
|
for h in Herd.objects.all()
|
||||||
|
}
|
||||||
|
|
||||||
|
species_map = {
|
||||||
|
s.name.strip(): s
|
||||||
|
for s in LiveStockSpecies.objects.all()
|
||||||
|
}
|
||||||
|
|
||||||
|
livestocks_to_create = []
|
||||||
|
processed_ids = []
|
||||||
|
|
||||||
|
created_count = 0
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
self.stdout.write("Starting import...")
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for row in qs.iterator(chunk_size=BATCH_SIZE):
|
||||||
|
herd = herd_map.get(self.normalize_herd_code(row.herd_code))
|
||||||
|
# print(self.normalize_herd_code(row.herd_code))
|
||||||
|
if not herd:
|
||||||
|
# print("herd")
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# species cache / create
|
||||||
|
species_name = (row.species or "").strip()
|
||||||
|
if not species_name:
|
||||||
|
# print("species")
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
species = species_map.get(species_name)
|
||||||
|
if not species:
|
||||||
|
species = LiveStockSpecies.objects.create(
|
||||||
|
name=species_name
|
||||||
|
)
|
||||||
|
species_map[species_name] = species
|
||||||
|
|
||||||
|
livestocks_to_create.append(
|
||||||
|
LiveStock(
|
||||||
|
herd=herd,
|
||||||
|
species=species,
|
||||||
|
gender=self.map_gender(row.gender),
|
||||||
|
birthdate=self.parse_jalali_datetime(row.birthdate),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
processed_ids.append(row.id)
|
||||||
|
|
||||||
|
if len(livestocks_to_create) >= BATCH_SIZE:
|
||||||
|
print("-----------------------------CREATE------------------------------------")
|
||||||
|
print(livestocks_to_create)
|
||||||
|
LiveStock.objects.bulk_create(
|
||||||
|
livestocks_to_create,
|
||||||
|
batch_size=BATCH_SIZE
|
||||||
|
)
|
||||||
|
created_count += len(livestocks_to_create)
|
||||||
|
livestocks_to_create.clear()
|
||||||
|
break
|
||||||
|
|
||||||
|
# flush remaining
|
||||||
|
if livestocks_to_create:
|
||||||
|
LiveStock.objects.bulk_create(
|
||||||
|
livestocks_to_create,
|
||||||
|
batch_size=BATCH_SIZE
|
||||||
|
)
|
||||||
|
created_count += len(livestocks_to_create)
|
||||||
|
|
||||||
|
# mark excel rows as archived
|
||||||
|
# ExcelLiveStocks.objects.filter(
|
||||||
|
# id__in=processed_ids
|
||||||
|
# ).update(archive=True)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(
|
||||||
|
f"Import finished. Created: {created_count}, Skipped: {skipped}"
|
||||||
|
))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def map_gender(value):
|
||||||
|
if not value:
|
||||||
|
return 1
|
||||||
|
value = value.strip().lower()
|
||||||
|
if value in ['female', 'f', 'ماده']:
|
||||||
|
return 2
|
||||||
|
return 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_date(value):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return datetime.strptime(value, '%Y/%m/%d')
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2026-02-10 08:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('livestock', '0019_excellivestocks'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='excellivestocks',
|
||||||
|
name='sync_status',
|
||||||
|
field=models.CharField(max_length=50, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -110,6 +110,7 @@ class ExcelLiveStocks(BaseModel):
|
|||||||
birthdate = models.CharField(max_length=150, null=True)
|
birthdate = models.CharField(max_length=150, null=True)
|
||||||
gender = models.CharField(max_length=150, null=True)
|
gender = models.CharField(max_length=150, null=True)
|
||||||
agent_code = models.CharField(max_length=150, null=True)
|
agent_code = models.CharField(max_length=150, null=True)
|
||||||
|
sync_status = models.CharField(max_length=50, null=True)
|
||||||
|
|
||||||
|
|
||||||
class TemporaryLiveStock(BaseModel):
|
class TemporaryLiveStock(BaseModel):
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ from django.core.management.base import BaseCommand
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from apps.herd.models import Herd
|
from apps.herd.models import Herd
|
||||||
from apps.livestock.models import LiveStock, LiveStockType
|
from apps.livestock.models import LiveStock, LiveStockType, ExcelLiveStocks
|
||||||
from apps.tag.models import Tag, TemporaryTags
|
from apps.tag.models import Tag
|
||||||
from common.generics import parse_birthdate
|
from common.generics import parse_birthdate
|
||||||
|
|
||||||
BATCH_SIZE = 5000
|
BATCH_SIZE = 1000
|
||||||
CHUNK_SIZE = 10000
|
CHUNK_SIZE = 1000
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@@ -22,16 +22,16 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
|
|
||||||
qs = (
|
qs = (
|
||||||
TemporaryTags.objects
|
ExcelLiveStocks.objects
|
||||||
.filter(sync_status__isnull=True)
|
.filter(sync_status__isnull=True)
|
||||||
.only('herd_code', 'birthdate', 'gender', 'tag')
|
.only('herd_code', 'birthdate', 'gender', 'national_id')
|
||||||
)
|
)
|
||||||
|
|
||||||
total = qs.count()
|
total = qs.count()
|
||||||
processed = 0
|
processed = 0
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
LOG_EVERY = 10000
|
LOG_EVERY = 1000
|
||||||
|
|
||||||
buffer = []
|
buffer = []
|
||||||
for temp in qs.iterator(chunk_size=CHUNK_SIZE):
|
for temp in qs.iterator(chunk_size=CHUNK_SIZE):
|
||||||
@@ -64,7 +64,7 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(self.style.SUCCESS("DONE ✅"))
|
self.stdout.write(self.style.SUCCESS("DONE ✅"))
|
||||||
|
|
||||||
def process_batch(self, temps):
|
def process_batch(self, temps):
|
||||||
herd_codes = {t.herd_code for t in temps if t.herd_code}
|
herd_codes = {self.normalize_herd_code(t.herd_code) for t in temps if t.herd_code}
|
||||||
|
|
||||||
herds = {
|
herds = {
|
||||||
h.code: h
|
h.code: h
|
||||||
@@ -90,7 +90,7 @@ class Command(BaseCommand):
|
|||||||
existing_tags = {
|
existing_tags = {
|
||||||
t.tag_code: t
|
t.tag_code: t
|
||||||
for t in Tag.objects.filter(
|
for t in Tag.objects.filter(
|
||||||
tag_code__in=[t.tag for t in temps if t.tag]
|
tag_code__in=[t.national_id for t in temps if t.national_id]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,28 +99,28 @@ class Command(BaseCommand):
|
|||||||
new_tags = []
|
new_tags = []
|
||||||
|
|
||||||
for temp in temps:
|
for temp in temps:
|
||||||
herd = herds.get(temp.herd_code)
|
herd = herds.get(self.normalize_herd_code(temp.herd_code))
|
||||||
if not herd:
|
if not herd:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
birthdate = parse_birthdate(temp.birthdate)
|
birthdate = parse_birthdate(temp.birthdate)
|
||||||
gender = 1 if temp.gender == 'M' else 2
|
gender = 1 if temp.gender == 'M' else 2
|
||||||
livestock_type = livestock_types.get(temp.type)
|
livestock_type = livestock_types.get(temp.species)
|
||||||
weight_type = livestock_type.weight_type
|
weight_type = livestock_type.weight_type
|
||||||
|
|
||||||
key = (temp.herd_code, birthdate, gender)
|
key = (self.normalize_herd_code(temp.herd_code), birthdate, gender)
|
||||||
livestock = livestock_map.get(key)
|
livestock = livestock_map.get(key)
|
||||||
|
|
||||||
if not livestock:
|
if not livestock:
|
||||||
if not temp.tag:
|
if not temp.national_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tag = existing_tags.get(temp.tag)
|
tag = existing_tags.get(temp.national_id)
|
||||||
|
|
||||||
if not tag:
|
if not tag:
|
||||||
tag = Tag(tag_code=temp.tag, status='A')
|
tag = Tag(tag_code=temp.national_id, status='A')
|
||||||
new_tags.append(tag)
|
new_tags.append(tag)
|
||||||
existing_tags[temp.tag] = tag
|
existing_tags[temp.national_id] = tag
|
||||||
|
|
||||||
livestock = LiveStock(
|
livestock = LiveStock(
|
||||||
herd=herd,
|
herd=herd,
|
||||||
@@ -136,13 +136,13 @@ class Command(BaseCommand):
|
|||||||
temp.sync_status = 'S'
|
temp.sync_status = 'S'
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if livestock.tag is None and temp.tag:
|
if livestock.tag is None and temp.national_id:
|
||||||
tag = existing_tags.get(temp.tag)
|
tag = existing_tags.get(temp.national_id)
|
||||||
|
|
||||||
if not tag:
|
if not tag:
|
||||||
tag = Tag(tag_code=temp.tag, status='A')
|
tag = Tag(tag_code=temp.national_id, status='A')
|
||||||
new_tags.append(tag)
|
new_tags.append(tag)
|
||||||
existing_tags[temp.tag] = tag
|
existing_tags[temp.national_id] = tag
|
||||||
|
|
||||||
livestock.tag = tag
|
livestock.tag = tag
|
||||||
updated_livestock.append(livestock)
|
updated_livestock.append(livestock)
|
||||||
@@ -151,18 +151,24 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
Tag.objects.bulk_create(new_tags, batch_size=BATCH_SIZE)
|
Tag.objects.bulk_create(new_tags, batch_size=BATCH_SIZE)
|
||||||
LiveStock.objects.bulk_create(
|
ss = LiveStock.objects.bulk_create(
|
||||||
new_livestock,
|
new_livestock,
|
||||||
batch_size=BATCH_SIZE,
|
batch_size=BATCH_SIZE,
|
||||||
ignore_conflicts=True
|
ignore_conflicts=True
|
||||||
)
|
)
|
||||||
|
print(ss)
|
||||||
LiveStock.objects.bulk_update(
|
LiveStock.objects.bulk_update(
|
||||||
updated_livestock,
|
updated_livestock,
|
||||||
['tag'],
|
['tag'],
|
||||||
batch_size=BATCH_SIZE
|
batch_size=BATCH_SIZE
|
||||||
)
|
)
|
||||||
TemporaryTags.objects.bulk_update(
|
ExcelLiveStocks.objects.bulk_update(
|
||||||
temps,
|
temps,
|
||||||
['sync_status'],
|
['sync_status'],
|
||||||
batch_size=BATCH_SIZE
|
batch_size=BATCH_SIZE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def normalize_herd_code(self, value, length=10):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return str(value).strip().zfill(length)
|
||||||
|
|||||||
Reference in New Issue
Block a user