deploying 5

This commit is contained in:
Flatlogic Bot 2026-02-08 18:03:40 +00:00
parent 89eb33ae77
commit f4761157f9
8 changed files with 1102 additions and 1023 deletions

View File

@ -0,0 +1,60 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="container-fluid">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{% trans "Import Products" %}</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<a href="{% url 'inventory' %}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> {% trans "Back to Inventory" %}
</a>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">{% trans "Upload Excel File" %}</h5>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> {% trans "Excel Format Instructions:" %}<br>
<ul>
<li><strong>{% trans "Column A" %}:</strong> {% trans "Name (English) - Required" %}</li>
<li><strong>{% trans "Column B" %}:</strong> {% trans "Name (Arabic) - Optional" %}</li>
<li><strong>{% trans "Column C" %}:</strong> {% trans "SKU/Barcode - Required & Unique" %}</li>
<li><strong>{% trans "Column D" %}:</strong> {% trans "Cost Price - Optional (Default 0)" %}</li>
<li><strong>{% trans "Column E" %}:</strong> {% trans "Sale Price - Required" %}</li>
<li><strong>{% trans "Column F" %}:</strong> {% trans "Category Name (English) - Required (Created if missing)" %}</li>
<li><strong>{% trans "Column G" %}:</strong> {% trans "Unit Name (English) - Optional (Created if missing)" %}</li>
<li><strong>{% trans "Column H" %}:</strong> {% trans "Stock Quantity - Optional (Default 0)" %}</li>
</ul>
<small>{% trans "Please skip the first row (header)." %}</small>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-upload"></i> {% trans "Import Products" %}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,3 +1,4 @@
# Force reload of urls.py
from django.urls import path from django.urls import path
from . import views from . import views
from . import views_import from . import views_import
@ -106,7 +107,7 @@ urlpatterns = [
path('inventory/edit/<int:pk>/', views.edit_product, name='edit_product'), path('inventory/edit/<int:pk>/', views.edit_product, name='edit_product'),
path('inventory/delete/<int:pk>/', views.delete_product, name='delete_product'), path('inventory/delete/<int:pk>/', views.delete_product, name='delete_product'),
path('inventory/barcodes/', views.barcode_labels, name='barcode_labels'), path('inventory/barcodes/', views.barcode_labels, name='barcode_labels'),
path('inventory/import/', views.import_products, name='import_products'), path('inventory/import/', views_import.import_products, name='import_products'),
# Categories # Categories
path('inventory/category/add/', views.add_category, name='add_category'), path('inventory/category/add/', views.add_category, name='add_category'),

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,20 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
import openpyxl from .models import Category, Supplier, Product, Unit
from .models import Category, Supplier
from .forms_import import ImportFileForm 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 @login_required
def import_categories(request): def import_categories(request):
@ -18,8 +29,13 @@ def import_categories(request):
if form.is_valid(): if form.is_valid():
excel_file = request.FILES['file'] 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: try:
wb = openpyxl.load_workbook(excel_file) wb = openpyxl_lib.load_workbook(excel_file)
sheet = wb.active sheet = wb.active
count = 0 count = 0
@ -84,8 +100,13 @@ def import_suppliers(request):
if form.is_valid(): if form.is_valid():
excel_file = request.FILES['file'] 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: try:
wb = openpyxl.load_workbook(excel_file) wb = openpyxl_lib.load_workbook(excel_file)
sheet = wb.active sheet = wb.active
count = 0 count = 0
@ -128,7 +149,7 @@ def import_suppliers(request):
if errors: if errors:
for error in errors: for error in errors:
messages.warning(request, error) messages.warning(request, error)
except Exception as e: except Exception as e:
messages.error(request, f"Error processing file: {str(e)}") messages.error(request, f"Error processing file: {str(e)}")
@ -136,4 +157,102 @@ def import_suppliers(request):
else: else:
form = ImportFileForm() form = ImportFileForm()
return render(request, 'core/import_suppliers.html', {'form': form}) 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})

View File

@ -5,3 +5,4 @@ pyzk==0.9
gunicorn==21.2.0 gunicorn==21.2.0
whitenoise==6.6.0 whitenoise==6.6.0
requests requests
openpyxl