38086-vm/core/views_import.py
2026-02-08 18:03:40 +00:00

258 lines
10 KiB
Python

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.utils.text import slugify
from .models import Category, Supplier, Product, Unit
from .forms_import import ImportFileForm
import decimal
import logging
logger = logging.getLogger(__name__)
# Safely handle openpyxl import only when needed
def get_openpyxl():
try:
import openpyxl
return openpyxl
except ImportError:
return None
@login_required
def import_categories(request):
"""
Import categories from an Excel (.xlsx) file.
Expected columns: Name (Eng), Name (Ar)
"""
if request.method == 'POST':
form = ImportFileForm(request.POST, request.FILES)
if form.is_valid():
excel_file = request.FILES['file']
openpyxl_lib = get_openpyxl()
if not openpyxl_lib:
messages.error(request, "Error: The 'openpyxl' library is not installed on the server. Please contact support.")
return redirect(reverse('inventory') + '#categories-list')
try:
wb = openpyxl_lib.load_workbook(excel_file)
sheet = wb.active
count = 0
updated_count = 0
errors = []
# Skip header row (min_row=2)
for i, row in enumerate(sheet.iter_rows(min_row=2, values_only=True), start=2):
if not any(row): continue # Skip empty rows
# Unpack columns with fallbacks for safety
# Format: name_en, name_ar
name_en = str(row[0]).strip() if row[0] else None
name_ar = str(row[1]).strip() if len(row) > 1 and row[1] else name_en
if not name_en:
errors.append(f"Row {i}: Missing English Name. Skipped.")
continue
slug = slugify(name_en)
category, created = Category.objects.update_or_create(
slug=slug,
defaults={
'name_en': name_en,
'name_ar': name_ar,
}
)
if created:
count += 1
else:
updated_count += 1
if count > 0 or updated_count > 0:
msg = f"Import completed: {count} new categories added"
if updated_count > 0:
msg += f", {updated_count} categories updated"
messages.success(request, msg)
if errors:
for error in errors:
messages.warning(request, error)
except Exception as e:
messages.error(request, f"Error processing file: {str(e)}")
return redirect(reverse('inventory') + '#categories-list')
else:
form = ImportFileForm()
return render(request, 'core/import_categories.html', {'form': form})
@login_required
def import_suppliers(request):
"""
Import suppliers from an Excel (.xlsx) file.
Expected columns: Name, Contact Person, Phone
"""
if request.method == 'POST':
form = ImportFileForm(request.POST, request.FILES)
if form.is_valid():
excel_file = request.FILES['file']
openpyxl_lib = get_openpyxl()
if not openpyxl_lib:
messages.error(request, "Error: The 'openpyxl' library is not installed on the server. Please contact support.")
return redirect('suppliers')
try:
wb = openpyxl_lib.load_workbook(excel_file)
sheet = wb.active
count = 0
updated_count = 0
errors = []
# Skip header row (min_row=2)
for i, row in enumerate(sheet.iter_rows(min_row=2, values_only=True), start=2):
if not any(row): continue # Skip empty rows
# Unpack columns with fallbacks for safety
# Format: Name, Contact Person, Phone
name = str(row[0]).strip() if row[0] else None
contact_person = str(row[1]).strip() if len(row) > 1 and row[1] else ''
phone = str(row[2]).strip() if len(row) > 2 and row[2] else ''
if not name:
errors.append(f"Row {i}: Missing Name. Skipped.")
continue
supplier, created = Supplier.objects.update_or_create(
name=name,
defaults={
'contact_person': contact_person,
'phone': phone,
}
)
if created:
count += 1
else:
updated_count += 1
if count > 0 or updated_count > 0:
msg = f"Import completed: {count} new suppliers added"
if updated_count > 0:
msg += f", {updated_count} suppliers updated"
messages.success(request, msg)
if errors:
for error in errors:
messages.warning(request, error)
except Exception as e:
messages.error(request, f"Error processing file: {str(e)}")
return redirect('suppliers')
else:
form = ImportFileForm()
return render(request, 'core/import_suppliers.html', {'form': form})
@login_required
def import_products(request):
"""
Import products from an Excel (.xlsx) file.
Expected columns: Name (En), Name (Ar), SKU, Cost, Price, Category, Unit, Stock
"""
if request.method == 'POST':
form = ImportFileForm(request.POST, request.FILES)
if form.is_valid():
excel_file = request.FILES['file']
openpyxl_lib = get_openpyxl()
if not openpyxl_lib:
messages.error(request, "Error: The 'openpyxl' library is not installed on the server. Please contact support.")
return redirect('inventory')
try:
wb = openpyxl_lib.load_workbook(excel_file)
sheet = wb.active
count = 0
updated_count = 0
errors = []
# Skip header row (min_row=2)
for i, row in enumerate(sheet.iter_rows(min_row=2, values_only=True), start=2):
if not any(row): continue # Skip empty rows
# Unpack columns
try:
name_en = str(row[0]).strip() if row[0] else None
name_ar = str(row[1]).strip() if len(row) > 1 and row[1] else name_en
sku = str(row[2]).strip() if len(row) > 2 and row[2] else None
cost_price = row[3] if len(row) > 3 and row[3] is not None else 0
price = row[4] if len(row) > 4 and row[4] is not None else 0
category_name = str(row[5]).strip() if len(row) > 5 and row[5] else None
unit_name = str(row[6]).strip() if len(row) > 6 and row[6] else None
stock = row[7] if len(row) > 7 and row[7] is not None else 0
except Exception as e:
errors.append(f"Row {i}: Error reading columns. {str(e)}")
continue
if not name_en or not sku or not price or not category_name:
errors.append(f"Row {i}: Missing required fields (Name, SKU, Price, Category). Skipped.")
continue
# Handle Category
category_slug = slugify(category_name)
category, _ = Category.objects.get_or_create(
slug=category_slug,
defaults={'name_en': category_name, 'name_ar': category_name}
)
# Handle Unit
unit = None
if unit_name:
unit, _ = Unit.objects.get_or_create(
name_en=unit_name,
defaults={'name_ar': unit_name, 'short_name': unit_name[:10]}
)
product, created = Product.objects.update_or_create(
sku=sku,
defaults={
'name_en': name_en,
'name_ar': name_ar,
'category': category,
'unit': unit,
'cost_price': decimal.Decimal(str(cost_price)),
'price': decimal.Decimal(str(price)),
'stock_quantity': decimal.Decimal(str(stock)),
}
)
if created:
count += 1
else:
updated_count += 1
if count > 0 or updated_count > 0:
msg = f"Import completed: {count} new products added"
if updated_count > 0:
msg += f", {updated_count} products updated"
messages.success(request, msg)
if errors:
for error in errors:
messages.warning(request, error)
except Exception as e:
messages.error(request, f"Error processing file: {str(e)}")
return redirect('inventory')
else:
form = ImportFileForm()
return render(request, 'core/import_products.html', {'form': form})