patch oko teksta PDFa i UI tablica
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
name,client_type,tax_id,email,phone,address,city,postal_code
|
||||
Adriacink transport d.o.o.,legal,10000000001,adriacink.transport@example.com,+38591111001,Ulica 1,Zagreb,10000
|
||||
BOSMAN,legal,10000000002,bosman@example.com,+38591111002,Ulica 2,Zagreb,10000
|
||||
BOSSIL,legal,10000000003,bossil@example.com,+38591111003,Ulica 3,Zagreb,10000
|
||||
PAKLOG,legal,10000000004,paklog@example.com,+38591111004,Ulica 4,Zagreb,10000
|
||||
Palace,legal,10000000005,palace@example.com,+38591111005,Ulica 5,Zagreb,10000
|
||||
Petrokemija Kutina,legal,10000000006,petrokemija.kutina@example.com,+38591111006,Ulica 6,Kutina,44320
|
||||
Paron,legal,10000000007,paron@example.com,+38591111007,Ulica 7,Zagreb,10000
|
||||
PRANGL,legal,10000000008,prangl@example.com,+38591111008,Ulica 8,Zagreb,10000
|
||||
Salona MONT,legal,10000000009,salona.mont@example.com,+38591111009,Ulica 9,Split,21000
|
||||
STRABAG,legal,10000000010,strabag@example.com,+38591111010,Ulica 10,Zagreb,10000
|
||||
|
0
backend/modules/crm/management/__init__.py
Normal file
0
backend/modules/crm/management/__init__.py
Normal file
0
backend/modules/crm/management/commands/__init__.py
Normal file
0
backend/modules/crm/management/commands/__init__.py
Normal file
130
backend/modules/crm/management/commands/import_clients_csv.py
Normal file
130
backend/modules/crm/management/commands/import_clients_csv.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
|
||||
from modules.crm.models import Client
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Uvozi kupce (vlasnike dizalica) iz CSV datoteke u Client model."
|
||||
|
||||
expected_columns = [
|
||||
"name",
|
||||
"client_type",
|
||||
"tax_id",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"city",
|
||||
"postal_code",
|
||||
]
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("csv_path", type=str, help="Putanja do CSV datoteke.")
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Validira i simulira import bez spremanja u bazu.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--create-only",
|
||||
action="store_true",
|
||||
help="Kreira samo nove kupce; postojeće preskače.",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
csv_path = Path(options["csv_path"]).expanduser().resolve()
|
||||
dry_run = options["dry_run"]
|
||||
create_only = options["create_only"]
|
||||
|
||||
if not csv_path.exists():
|
||||
raise CommandError(f"CSV datoteka ne postoji: {csv_path}")
|
||||
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
with csv_path.open("r", encoding="utf-8-sig", newline="") as handle:
|
||||
reader = csv.DictReader(handle)
|
||||
if not reader.fieldnames:
|
||||
raise CommandError("CSV nema zaglavlje (header).")
|
||||
|
||||
normalized_headers = [str(h).strip() for h in reader.fieldnames]
|
||||
missing = [column for column in self.expected_columns if column not in normalized_headers]
|
||||
if missing:
|
||||
raise CommandError("CSV nema obavezna polja: " + ", ".join(missing))
|
||||
|
||||
row_number = 1
|
||||
with transaction.atomic():
|
||||
for row in reader:
|
||||
row_number += 1
|
||||
if not any((value or "").strip() for value in row.values()):
|
||||
continue
|
||||
|
||||
tax_id = (row.get("tax_id") or "").strip()
|
||||
email = (row.get("email") or "").strip().lower()
|
||||
if not tax_id:
|
||||
raise CommandError(f"Red {row_number}: tax_id je obavezan.")
|
||||
if not email:
|
||||
raise CommandError(f"Red {row_number}: email je obavezan.")
|
||||
|
||||
client = Client.objects.filter(tax_id=tax_id).first()
|
||||
if client and create_only:
|
||||
skipped_count += 1
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f"Red {row_number}: preskočeno (kupac već postoji) tax_id={tax_id}"
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
is_create = client is None
|
||||
if is_create:
|
||||
client = Client(tax_id=tax_id)
|
||||
|
||||
client_type = (row.get("client_type") or "legal").strip()
|
||||
if client_type not in {"legal", "individual"}:
|
||||
raise CommandError(
|
||||
f"Red {row_number}: client_type mora biti 'legal' ili 'individual'."
|
||||
)
|
||||
|
||||
client.name = (row.get("name") or "").strip()
|
||||
client.client_type = client_type
|
||||
client.email = email
|
||||
client.phone = (row.get("phone") or "").strip() or None
|
||||
client.address = (row.get("address") or "").strip()
|
||||
client.city = (row.get("city") or "").strip()
|
||||
client.postal_code = (row.get("postal_code") or "").strip()
|
||||
|
||||
try:
|
||||
client.full_clean()
|
||||
except ValidationError as exc:
|
||||
raise CommandError(
|
||||
f"Red {row_number}: validacija nije prošla -> {exc.message_dict}"
|
||||
) from exc
|
||||
|
||||
if not dry_run:
|
||||
client.save()
|
||||
|
||||
if is_create:
|
||||
created_count += 1
|
||||
else:
|
||||
updated_count += 1
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Red {row_number}: {'kreiran' if is_create else 'ažuriran'} kupac {client.name}"
|
||||
)
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
transaction.set_rollback(True)
|
||||
|
||||
summary = (
|
||||
f"Import kupaca završen. created={created_count}, updated={updated_count}, "
|
||||
f"skipped={skipped_count}, dry_run={dry_run}"
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(summary))
|
||||
Reference in New Issue
Block a user