This commit is contained in:
0
backend/core/management/__init__.py
Normal file
0
backend/core/management/__init__.py
Normal file
0
backend/core/management/commands/__init__.py
Normal file
0
backend/core/management/commands/__init__.py
Normal file
385
backend/core/management/commands/seed_all_models.py
Normal file
385
backend/core/management/commands/seed_all_models.py
Normal file
@@ -0,0 +1,385 @@
|
||||
import random
|
||||
import sys
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
from modules.crm.models import Client
|
||||
from modules.fleet.models import (
|
||||
Vehicle,
|
||||
WorkOrder,
|
||||
VehicleNotification,
|
||||
VehicleServiceRecord,
|
||||
VehicleServicePhoto,
|
||||
)
|
||||
from modules.invoicing.models import Invoice, InvoiceItem, InvoiceTransaction
|
||||
from modules.task_management.models import Task
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
"Generira demo podatke za sve domenske modele. "
|
||||
"Ako --count nije zadan, komanda će u terminalu pitati količinu po modelu."
|
||||
)
|
||||
|
||||
model_labels = [
|
||||
("clients", "CRM klijenti"),
|
||||
("users", "korisnici"),
|
||||
("vehicles", "vozila"),
|
||||
("work_orders", "radni/putni nalozi"),
|
||||
("service_records", "servisni zapisi"),
|
||||
("notifications", "notifikacije"),
|
||||
("tasks", "task zapisi"),
|
||||
("invoices", "fakture"),
|
||||
("invoice_items", "stavke fakture"),
|
||||
("invoice_transactions", "transakcije fakture"),
|
||||
("service_photos", "fotografije servisa"),
|
||||
]
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--count",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Količina zapisa za svaki model. Ako nije zadano, pita se po modelu.",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
global_count = options["count"]
|
||||
if global_count is not None and global_count < 0:
|
||||
raise CommandError("--count mora biti >= 0")
|
||||
|
||||
counts = self._resolve_counts(global_count)
|
||||
|
||||
self.stdout.write(self.style.MIGRATE_HEADING("Pokrećem generiranje demo podataka..."))
|
||||
with transaction.atomic():
|
||||
clients = self._seed_clients(counts["clients"])
|
||||
users = self._seed_users(counts["users"], clients)
|
||||
vehicles = self._seed_vehicles(counts["vehicles"], users)
|
||||
work_orders = self._seed_work_orders(counts["work_orders"], vehicles, users)
|
||||
tasks = self._seed_tasks(counts["tasks"], users, vehicles)
|
||||
service_records = self._seed_service_records(counts["service_records"], users, tasks)
|
||||
self._seed_notifications(counts["notifications"], users)
|
||||
invoices = self._seed_invoices(counts["invoices"], users, clients)
|
||||
self._seed_invoice_items(counts["invoice_items"], invoices)
|
||||
self._seed_invoice_transactions(counts["invoice_transactions"], invoices)
|
||||
self._seed_service_photos(counts["service_photos"], service_records, users)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Generiranje završeno."))
|
||||
|
||||
def _resolve_counts(self, global_count):
|
||||
counts = {}
|
||||
for key, label in self.model_labels:
|
||||
counts[key] = self._ask_count(label, global_count)
|
||||
return counts
|
||||
|
||||
def _ask_count(self, label, global_count):
|
||||
if global_count is not None:
|
||||
return global_count
|
||||
|
||||
if not sys.stdin.isatty():
|
||||
raise CommandError(
|
||||
"Detektiran je non-interactive terminal. "
|
||||
"Pokreni s --count (npr. `python manage.py seed_all_models --count 10`) "
|
||||
"ili interaktivno s TTY (`docker compose exec -it backend python manage.py seed_all_models`)."
|
||||
)
|
||||
|
||||
while True:
|
||||
raw = input(f"Koliko zapisa želiš generirati za {label}? [0]: ").strip()
|
||||
if raw == "":
|
||||
return 0
|
||||
try:
|
||||
value = int(raw)
|
||||
if value < 0:
|
||||
self.stdout.write(self.style.WARNING("Vrijednost mora biti >= 0."))
|
||||
continue
|
||||
return value
|
||||
except ValueError:
|
||||
self.stdout.write(self.style.WARNING("Unesi cijeli broj."))
|
||||
|
||||
def _seed_clients(self, count):
|
||||
created = []
|
||||
for i in range(count):
|
||||
suffix = uuid.uuid4().hex[:8]
|
||||
client = Client.objects.create(
|
||||
name=f"Klijent {suffix}",
|
||||
client_type=random.choice(["legal", "individual"]),
|
||||
tax_id=f"{random.randint(10000000000, 99999999999)}",
|
||||
email=f"client.{suffix}@example.test",
|
||||
phone=f"+38591{random.randint(1000000, 9999999)}",
|
||||
address=f"Ulica {random.randint(1, 120)}",
|
||||
city=random.choice(["Zagreb", "Split", "Rijeka", "Osijek"]),
|
||||
postal_code=str(random.randint(10000, 53000)),
|
||||
)
|
||||
created.append(client)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"Client: kreirano {len(created)}"))
|
||||
return created or list(Client.objects.all())
|
||||
|
||||
def _seed_users(self, count, clients):
|
||||
User = get_user_model()
|
||||
created = []
|
||||
clients_pool = clients or list(Client.objects.all())
|
||||
|
||||
for i in range(count):
|
||||
suffix = uuid.uuid4().hex[:8]
|
||||
is_kupac = random.choice([True, False])
|
||||
client_profile = random.choice(clients_pool) if is_kupac and clients_pool else None
|
||||
user = User.objects.create_user(
|
||||
email=f"user.{suffix}@example.test",
|
||||
password="password123",
|
||||
first_name=f"Ime{i + 1}",
|
||||
last_name=f"Prezime{i + 1}",
|
||||
is_serviser=random.choice([True, False]),
|
||||
is_team_member=random.choice([True, False]),
|
||||
is_kupac=is_kupac if client_profile else False,
|
||||
client_profile=client_profile,
|
||||
telefon=f"+38595{random.randint(1000000, 9999999)}",
|
||||
is_verified=random.choice([True, False]),
|
||||
)
|
||||
created.append(user)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"CustomUser: kreirano {len(created)}"))
|
||||
return created or list(User.objects.all())
|
||||
|
||||
def _seed_vehicles(self, count, users):
|
||||
created = []
|
||||
users_pool = [u for u in users if getattr(u, "is_serviser", False)]
|
||||
assigned_servicer_ids = set(
|
||||
Vehicle.objects.exclude(assigned_servicer__isnull=True).values_list("assigned_servicer_id", flat=True)
|
||||
)
|
||||
clients_pool = list(Client.objects.all())
|
||||
|
||||
for _ in range(count):
|
||||
reg = f"ZG-{random.randint(1000, 9999)}-{uuid.uuid4().hex[:2].upper()}"
|
||||
available_servicers = [u for u in users_pool if u.id not in assigned_servicer_ids]
|
||||
assigned = random.choice(available_servicers) if available_servicers and random.choice([True, False]) else None
|
||||
asset_type = random.choice(["vehicle", "crane"])
|
||||
|
||||
# 40% vozila su klijentova (is_company_vehicle=False, client=random klijent)
|
||||
is_company = random.random() > 0.4
|
||||
client_obj = None if is_company else (random.choice(clients_pool) if clients_pool else None)
|
||||
|
||||
vehicle = Vehicle.objects.create(
|
||||
asset_type=asset_type,
|
||||
registration_number=reg,
|
||||
crane_serial_number=f"{random.randint(0, 999999):06d}" if asset_type == "crane" else None,
|
||||
make=random.choice(["VW", "Ford", "Mercedes", "Renault"]),
|
||||
model=random.choice(["Model A", "Model B", "Model C"]),
|
||||
year=random.randint(2008, timezone.now().year),
|
||||
vin=f"VIN{uuid.uuid4().hex[:16].upper()}",
|
||||
current_mileage=random.randint(10000, 250000),
|
||||
service_interval_km=random.choice([10000, 15000, 20000]),
|
||||
assigned_servicer=assigned,
|
||||
is_company_vehicle=is_company,
|
||||
client=client_obj,
|
||||
)
|
||||
if assigned:
|
||||
assigned_servicer_ids.add(assigned.id)
|
||||
created.append(vehicle)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"Vehicle: kreirano {len(created)} ({sum(1 for v in created if not v.is_company_vehicle)} klijentskih)"))
|
||||
return created or list(Vehicle.objects.all())
|
||||
|
||||
def _seed_work_orders(self, count, vehicles, users):
|
||||
created = []
|
||||
if not vehicles or not users:
|
||||
return created
|
||||
|
||||
for _ in range(count):
|
||||
vehicle = random.choice(vehicles)
|
||||
creator = random.choice(users)
|
||||
start_mileage = max(0, vehicle.current_mileage - random.randint(0, 2000))
|
||||
is_closed = random.choice([True, False])
|
||||
end_mileage = start_mileage + random.randint(1, 600) if is_closed else None
|
||||
status = "closed" if is_closed else "open"
|
||||
|
||||
order = WorkOrder.objects.create(
|
||||
vehicle=vehicle,
|
||||
creator=creator,
|
||||
start_mileage=start_mileage,
|
||||
end_mileage=end_mileage,
|
||||
purpose=random.choice(["Redovni pregled", "Hitna intervencija", "Kontrola"]),
|
||||
notes="Auto-generirani demo nalog.",
|
||||
status=status,
|
||||
)
|
||||
created.append(order)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"WorkOrder: kreirano {len(created)}"))
|
||||
return created or list(WorkOrder.objects.all())
|
||||
|
||||
def _seed_service_records(self, count, users, tasks):
|
||||
created = []
|
||||
if not users:
|
||||
return created
|
||||
|
||||
tasks_pool = tasks or list(Task.objects.filter(is_active=True))
|
||||
tasks_with_vehicle = [t for t in tasks_pool if t.vehicle_id]
|
||||
|
||||
for _ in range(count):
|
||||
if not tasks_with_vehicle:
|
||||
break
|
||||
task = random.choice(tasks_with_vehicle)
|
||||
vehicle = task.vehicle
|
||||
performed_by = random.choice(users)
|
||||
mileage = max(vehicle.current_mileage, random.randint(5000, 280000))
|
||||
record = VehicleServiceRecord.objects.create(
|
||||
vehicle=vehicle,
|
||||
task=task,
|
||||
performed_by=performed_by,
|
||||
description=random.choice(
|
||||
["Zamjena ulja i filtera", "Kočnice i diskovi", "Dijagnostika motora", "Generalni servis"]
|
||||
),
|
||||
parts="Filter ulja, ulje 5W30",
|
||||
cost=Decimal(str(random.randint(50, 900))),
|
||||
mileage=mileage,
|
||||
next_service_due_at=mileage + random.choice([8000, 10000, 12000]),
|
||||
)
|
||||
created.append(record)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"VehicleServiceRecord: kreirano {len(created)}"))
|
||||
return created or list(VehicleServiceRecord.objects.all())
|
||||
|
||||
def _seed_notifications(self, count, users):
|
||||
created = []
|
||||
users_pool = users or list(get_user_model().objects.all())
|
||||
if not users_pool:
|
||||
return created
|
||||
|
||||
for _ in range(count):
|
||||
notification = VehicleNotification.objects.create(
|
||||
recipient=random.choice(users_pool),
|
||||
title=random.choice(["Novi nalog", "Servis uskoro", "Status ažuriran"]),
|
||||
message="Auto-generirana notifikacija za testiranje.",
|
||||
level=random.choice(["info", "warning", "critical"]),
|
||||
is_sent=random.choice([True, False]),
|
||||
is_read=random.choice([True, False]),
|
||||
)
|
||||
created.append(notification)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"VehicleNotification: kreirano {len(created)}"))
|
||||
return created
|
||||
|
||||
def _seed_tasks(self, count, users, vehicles):
|
||||
created = []
|
||||
users_pool = users or list(get_user_model().objects.all())
|
||||
# Preferiramo dizalice (asset_type='crane') za taskove
|
||||
cranes_pool = [v for v in vehicles if getattr(v, 'asset_type', None) == 'crane']
|
||||
vehicles_pool = cranes_pool or vehicles or list(Vehicle.objects.filter(asset_type='crane'))
|
||||
|
||||
for i in range(count):
|
||||
status = random.choice(["aktivan", "servis"])
|
||||
vehicle = random.choice(vehicles_pool) if vehicles_pool else None
|
||||
task = Task.objects.create(
|
||||
title=f"Task {uuid.uuid4().hex[:6]}",
|
||||
description="Auto-generirani zadatak.",
|
||||
status=status,
|
||||
assigned_to=random.choice(users_pool) if users_pool else None,
|
||||
vehicle=vehicle,
|
||||
)
|
||||
created.append(task)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"Task: kreirano {len(created)}"))
|
||||
return created
|
||||
|
||||
def _seed_invoices(self, count, users, clients):
|
||||
created = []
|
||||
users_pool = users or list(get_user_model().objects.all())
|
||||
clients_pool = clients or list(Client.objects.all())
|
||||
if not users_pool or not clients_pool:
|
||||
return created
|
||||
|
||||
for _ in range(count):
|
||||
amount = Decimal(str(random.randint(100, 5000)))
|
||||
invoice = Invoice.objects.create(
|
||||
creator=random.choice(users_pool),
|
||||
client=random.choice(clients_pool),
|
||||
amount=amount,
|
||||
currency="EUR",
|
||||
tax_rate=Decimal("25.00"),
|
||||
status=random.choice(["draft", "sent", "paid", "cancelled"]),
|
||||
due_date=timezone.now().date() + timedelta(days=random.randint(7, 45)),
|
||||
)
|
||||
created.append(invoice)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"Invoice: kreirano {len(created)}"))
|
||||
return created or list(Invoice.objects.all())
|
||||
|
||||
def _seed_invoice_items(self, count, invoices):
|
||||
created = []
|
||||
invoices_pool = invoices or list(Invoice.objects.all())
|
||||
if not invoices_pool:
|
||||
return created
|
||||
|
||||
for _ in range(count):
|
||||
item = InvoiceItem.objects.create(
|
||||
invoice=random.choice(invoices_pool),
|
||||
description=random.choice(["Servisna usluga", "Rezervni dio", "Terenski rad"]),
|
||||
quantity=Decimal(str(random.randint(1, 5))),
|
||||
unit_price=Decimal(str(random.randint(20, 700))),
|
||||
)
|
||||
created.append(item)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"InvoiceItem: kreirano {len(created)}"))
|
||||
return created
|
||||
|
||||
def _seed_invoice_transactions(self, count, invoices):
|
||||
created = []
|
||||
invoices_pool = invoices or list(Invoice.objects.all())
|
||||
if not invoices_pool:
|
||||
return created
|
||||
|
||||
for _ in range(count):
|
||||
invoice = random.choice(invoices_pool)
|
||||
txn = InvoiceTransaction.objects.create(
|
||||
invoice=invoice,
|
||||
invoice_number=invoice.invoice_number,
|
||||
amount=invoice.amount,
|
||||
payment_date=timezone.now().date() - timedelta(days=random.randint(0, 30)),
|
||||
)
|
||||
created.append(txn)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"InvoiceTransaction: kreirano {len(created)}"))
|
||||
return created
|
||||
|
||||
def _seed_service_photos(self, count, service_records, users):
|
||||
created = []
|
||||
records_pool = service_records or list(VehicleServiceRecord.objects.all())
|
||||
users_pool = users or list(get_user_model().objects.all())
|
||||
if not records_pool:
|
||||
return created
|
||||
|
||||
tiny_gif = (
|
||||
b"\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00"
|
||||
b"\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00"
|
||||
b"\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b"
|
||||
)
|
||||
|
||||
for i in range(count):
|
||||
photo = VehicleServicePhoto(
|
||||
service_record=random.choice(records_pool),
|
||||
uploaded_by=random.choice(users_pool) if users_pool else None,
|
||||
description="Auto-generirana servisna fotografija.",
|
||||
)
|
||||
photo.image.save(f"service_photo_{i}_{uuid.uuid4().hex[:6]}.gif", ContentFile(tiny_gif), save=True)
|
||||
created.append(photo)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"VehicleServicePhoto: kreirano {len(created)}"))
|
||||
return created
|
||||
99
backend/core/management/commands/seed_fleet_assets.py
Normal file
99
backend/core/management/commands/seed_fleet_assets.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import random
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from modules.fleet.models import Vehicle
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Kreira/demo-popunjava fleet assete: default 12 dizalica i 2 vozila."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--cranes",
|
||||
type=int,
|
||||
default=12,
|
||||
help="Broj dizalica za kreirati/ažurirati (default: 12).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vehicles",
|
||||
type=int,
|
||||
default=2,
|
||||
help="Broj vozila za kreirati/ažurirati (default: 2).",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
cranes_count = options["cranes"]
|
||||
vehicles_count = options["vehicles"]
|
||||
if cranes_count < 0 or vehicles_count < 0:
|
||||
raise CommandError("--cranes i --vehicles moraju biti >= 0")
|
||||
|
||||
with transaction.atomic():
|
||||
created_cranes, updated_cranes = self._seed_assets(
|
||||
asset_type="crane",
|
||||
count=cranes_count,
|
||||
reg_prefix="CRN",
|
||||
make="Liebherr",
|
||||
model="LTM",
|
||||
mileage_start=12000,
|
||||
service_interval=8000,
|
||||
)
|
||||
created_vehicles, updated_vehicles = self._seed_assets(
|
||||
asset_type="vehicle",
|
||||
count=vehicles_count,
|
||||
reg_prefix="VEH",
|
||||
make="Mercedes",
|
||||
model="Sprinter",
|
||||
mileage_start=45000,
|
||||
service_interval=15000,
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"Fleet seed završen: "
|
||||
f"crane created={created_cranes}, updated={updated_cranes}; "
|
||||
f"vehicle created={created_vehicles}, updated={updated_vehicles}"
|
||||
)
|
||||
)
|
||||
|
||||
def _seed_assets(
|
||||
self,
|
||||
*,
|
||||
asset_type,
|
||||
count,
|
||||
reg_prefix,
|
||||
make,
|
||||
model,
|
||||
mileage_start,
|
||||
service_interval,
|
||||
):
|
||||
created = 0
|
||||
updated = 0
|
||||
|
||||
for index in range(1, count + 1):
|
||||
reg = f"ZG-{reg_prefix}-{index:02d}"
|
||||
vin = f"VIN-{reg_prefix}-{index:02d}"
|
||||
defaults = {
|
||||
"asset_type": asset_type,
|
||||
"crane_serial_number": f"{random.randint(0, 999999):06d}" if asset_type == "crane" else None,
|
||||
"make": make,
|
||||
"model": f"{model}-{index:02d}",
|
||||
"year": 2020 + (index % 4),
|
||||
"vin": vin,
|
||||
"current_mileage": mileage_start + (index * 1000),
|
||||
"service_interval_km": service_interval,
|
||||
"is_company_vehicle": True,
|
||||
"client": None,
|
||||
"assigned_servicer": None,
|
||||
"is_active": True,
|
||||
}
|
||||
_, was_created = Vehicle.objects.update_or_create(
|
||||
registration_number=reg,
|
||||
defaults=defaults,
|
||||
)
|
||||
if was_created:
|
||||
created += 1
|
||||
else:
|
||||
updated += 1
|
||||
|
||||
return created, updated
|
||||
0
backend/core/management/commands/seed_knezljubo.py
Normal file
0
backend/core/management/commands/seed_knezljubo.py
Normal file
Reference in New Issue
Block a user