258 lines
10 KiB
Python
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}) |