prvi kod za live uporabu
Some checks failed
ERP CI Pipeline / test (push) Has been cancelled

This commit is contained in:
mariomitte
2026-07-09 21:21:17 +02:00
parent 857bb65d52
commit 5d39e048ba
239 changed files with 25151 additions and 0 deletions

View File

View 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

View 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