From 1e0d4f6540de32b225345ad010996c2c44be78d4 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 2 Feb 2026 10:37:01 +0000 Subject: [PATCH] Autosave: 20260202-103700 --- core/__pycache__/urls.cpython-311.pyc | Bin 6150 -> 6732 bytes core/__pycache__/views.cpython-311.pyc | Bin 49572 -> 57973 bytes core/templates/core/inventory.html | 280 +++++++++++++++++++++++-- core/urls.py | 7 +- core/views.py | 165 +++++++++++++++ 5 files changed, 430 insertions(+), 22 deletions(-) diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 87e44739974384d82911de13b51c01fa1389eeb1..47009879ccccd683a2c47907850f71c3e0699078 100644 GIT binary patch delta 1212 zcmY+D%}*0S7>C<3rId!27P?50uOeSUDNzvlsJ22S#X>PstspH8ZAkfO>$VGmB0U-7 z1)C+2c(DhgHE^hMX<|$~df>2|%n3d42WVVRUY(ik0&cTUW`6IpGjHCc&$h;iAPpT*XglIi0G~t)S?t7@#PMqZo?%Pog-E z9-?UAB%)E&ot{5OYV?zIt%1>L~V5!Fa?P423KSCLUjo zglS7~G5E-Oq(@mm$^tmspd!)La5NdC*R2U=VIiDIS`tf)^+;3L1<;Ny)xI+8PsPYe zJVqz2M>=s4h|{qUGoNJNXww%@uAUDj!wcxxvsX(Qxtg`=)><|UQmaffk}SAoxJ|q# z-bB0Y;DfG9PSJP*^bm#(;~K_w7M4$HM$uA^-o&PgwdyT$4?C-`L0s3@*gSHbLMWCg z)v1x^)Jx+|b;ihnRvxrM&?=}%wM49*f644@WoJ0h&x3vl`USOz<@L4boZp0H2E6Vt z)FF-$Mhp#T{ep0h@X%0?lAolisle4v!g0bV;WlBe0-GFJ|EXvgCY=nBPB?XYwik8` zb7OuE1b7gDARr79VW=ROGfsHH&W#2*Fvo*A2<8Nvhn=c4qchhCUlV>IJSHqM;%W<_ zi*T86oA8s-TuiPAu@1^q6h`V_VGOBlbVNGWa84eyeAaDqqqVZHRnlfJ4+?S7X zfc-(0sJFzjUBUjMOlpUMTzEslmRFh7!{Tm4!A@72ww&T_OmTL6SMq4QZXUQHa0|CY z!z~$|*)Z&KeA98j#{(Y(K4DxmCM2UPr-a>3&OOTkKM(v6_yv!sOiGG07Vnw4+!md? zl2eyazSZWYIbi339Rj;>PZWHT(3Wl9w|>=}wt8O_rrGaRrpq(p=7D6_?lQ3cQojCl P%(KKuY4DGBqJPOhOr~p( delta 815 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 delta 1929 zcmZ{k3rrMO6o%)_>eCOPA{&Vgl zbN9g|>DX(M^|-2<1so4v-o1IavDNC>uuc?&cLgF4@CrI;po^NQOPT~cCVEY}tjW5f zDSChw;Eze(Ks`tc(#@LLKR0=Ub&F>4`?6Qnt(p~mg#>s*bem?=?OJQAr^%lV^g1;s zwhi(o=!sgQKW6qO>B(BM-w*bt=&4$&-?w;OdYYDozS=Bk>BNf6AR$OMu^}^w9hpT! zk=Y~+nL{QZb2nN^c(c%rhwSAi^$)Enk4(g5K8Zk1CJtl)i9}8zQOK!S6g{qJ8i~PN zA&Es!C(j{gkT_%!nS`8)HSyzWW)Uamib(>}LlTiCB&k_w(q@xnoXo+ZlyOCKNh;>% z5f^ekNkf*BbmRh5XN*&q5jW<_NhWe3$wDq7*~rBt2U$UKkxNJ(aw*A2R+7ohWrDVh z6yRhznSxwFrXs7zH01N75V?{}M^=*=NR1RBUm!D)tH>T0RS?qHY0&H$`nuZEWZXzbF&I{@q1QO5wl zCb})RhFZZMU61Mma8wJ}bCJM3Xig9$9m z*pAzGFt-sYLJXT<|2;r5o3v9^Af09UHkqKBy|eod2?%?md5;XcSm@r1lCmG)S>1

bYXk6m|>#pVD^V{r@YY zH9phR`mI2OMypSLs>Xu)xX~KHCqJ_&gbr7`Y5NgJxxE|Zy}ZOf8f_%hCv17L>`uW* z{`Z`OMvh4ZpKWdM*u-Z$VxbpLC#lh65kc5uZR?4p7v@{2tH&<7!l~XFC*PLpYIe}> z&dP8%Ub)eNa$_GyCZA?ewQGviW*p>^T^!{CZR&~$w{pLjqk|)gi*sma*PQ=pGu%}c zoR6X_c)4|d0&#^28+_bhQC24d$-lvmye32vD-xkpjyTsT=c5I-J&&1N( zJxSnh>&=LIR>_l{IOv*QJLJ*ry~#=ew)kCS?Y(J|JQaN_yL$2j{~mYrH9#R9>YHSq zj^POsZtit@p??OeIyG6ujAtN`_OW>LOw7jeY#V>FpPYULposcDb*jafNWsMD2m7}* znpM+9K*RdOBV3YkjUVDVM+xd094oeNuBR=1QM9_h7)t2D{up^SCfw}v{z4H0f1N2q zdImhoJPa2|^nQ5^{jF4Grv@4T=F*I_&&j2jDP|RC>p(6;cOJcbu2OWZVTrew18w~A zd=Y(bmDJ&? {% trans "Add Item" %} + @@ -475,8 +478,7 @@ - - + {% empty %} @@ -516,34 +518,54 @@

- +
+ + +
- +
+ + +
- +
+ + +
- +
+ + +
@@ -598,6 +620,131 @@
+ + + + + + + + + + + + + + {% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index a8f3132..4c768d2 100644 --- a/core/urls.py +++ b/core/urls.py @@ -59,20 +59,25 @@ urlpatterns = [ path('suppliers/add/', views.add_supplier, name='add_supplier'), path('suppliers/edit//', views.edit_supplier, name='edit_supplier'), path('suppliers/delete//', views.delete_supplier, name='delete_supplier'), + path('api/add-supplier-ajax/', views.add_supplier_ajax, name='add_supplier_ajax'), # Inventory + path('inventory/suggest-sku/', views.suggest_sku, name='suggest_sku'), path('inventory/add/', views.add_product, name='add_product'), path('inventory/edit//', views.edit_product, name='edit_product'), path('inventory/delete//', views.delete_product, name='delete_product'), path('inventory/barcodes/', views.barcode_labels, name='barcode_labels'), + path('inventory/import/', views.import_products, name='import_products'), # Categories path('inventory/category/add/', views.add_category, name='add_category'), path('inventory/category/edit//', views.edit_category, name='edit_category'), path('inventory/category/delete//', views.delete_category, name='delete_category'), + path('api/add-category-ajax/', views.add_category_ajax, name='add_category_ajax'), # Units path('inventory/unit/add/', views.add_unit, name='add_unit'), path('inventory/unit/edit//', views.edit_unit, name='edit_unit'), path('inventory/unit/delete//', views.delete_unit, name='delete_unit'), -] + path('api/add-unit-ajax/', views.add_unit_ajax, name='add_unit_ajax'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index ca90f7f..1617e4c 100644 --- a/core/views.py +++ b/core/views.py @@ -1,3 +1,5 @@ +import random +import string from django.shortcuts import render, get_object_or_404, redirect from django.db.models import Sum, Count, F from django.db.models.functions import TruncDate, TruncMonth @@ -15,6 +17,7 @@ from datetime import timedelta from django.utils import timezone from django.contrib import messages from django.utils.text import slugify +import openpyxl def index(request): """ @@ -709,6 +712,17 @@ def delete_supplier(request, pk): messages.success(request, "Supplier deleted successfully!") return redirect('suppliers') + +def suggest_sku(request): + """ + API endpoint to suggest a unique SKU. + """ + while True: + # Generate a random 8-digit number + sku = "".join(random.choices(string.digits, k=8)) + if not Product.objects.filter(sku=sku).exists(): + return JsonResponse({"sku": sku}) + def add_product(request): if request.method == 'POST': name_en = request.POST.get('name_en') @@ -717,6 +731,11 @@ def add_product(request): unit_id = request.POST.get('unit') supplier_id = request.POST.get('supplier') sku = request.POST.get('sku') + if not sku: + while True: + sku = ''.join(random.choices(string.digits, k=8)) + if not Product.objects.filter(sku=sku).exists(): + break cost_price = request.POST.get('cost_price', 0) price = request.POST.get('price', 0) vat = request.POST.get('vat', 0) @@ -839,3 +858,149 @@ def barcode_labels(request): products = Product.objects.filter(is_active=True).order_by('name_en') context = {'products': products} return render(request, 'core/barcode_labels.html', context) + +def import_products(request): + """ + Import products from an Excel (.xlsx) file. + Expected columns: Name (Eng), Name (Ar), SKU, Cost Price, Sale Price + """ + if request.method == 'POST' and request.FILES.get('excel_file'): + excel_file = request.FILES['excel_file'] + + if not excel_file.name.endswith('.xlsx'): + messages.error(request, "Please upload a valid .xlsx file.") + return redirect('inventory') + + try: + wb = openpyxl.load_workbook(excel_file) + sheet = wb.active + + # Get or create a default category + default_category, _ = Category.objects.get_or_create( + name_en="General", + defaults={'name_ar': "عام", 'slug': 'general'} + ) + + 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, sku, cost_price, sale_price + 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 + sale_price = row[4] if len(row) > 4 and row[4] is not None else 0 + + if not name_en: + errors.append(f"Row {i}: Missing English Name. Skipped.") + continue + + if not sku: + # Generate unique SKU if missing + while True: + sku = "".join(random.choices(string.digits, k=8)) + if not Product.objects.filter(sku=sku).exists(): + break + + product, created = Product.objects.update_or_create( + sku=sku, + defaults={ + 'name_en': name_en, + 'name_ar': name_ar, + 'cost_price': cost_price, + 'price': sale_price, + 'category': default_category, + 'is_active': True + } + ) + + if created: + count += 1 + else: + updated_count += 1 + + if count > 0 or updated_count > 0: + msg = f"Import completed: {count} new items added" + if updated_count > 0: + msg += f", {updated_count} items 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') + +@csrf_exempt +def add_category_ajax(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + name_en = data.get('name_en') + name_ar = data.get('name_ar') + if not name_en or not name_ar: + return JsonResponse({'success': False, 'error': 'Missing names'}, status=400) + + slug = slugify(name_en) + category = Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug) + return JsonResponse({ + 'success': True, + 'id': category.id, + 'name_en': category.name_en, + 'name_ar': category.name_ar + }) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}, status=400) + return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405) + +@csrf_exempt +def add_unit_ajax(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + name_en = data.get('name_en') + name_ar = data.get('name_ar') + short_name = data.get('short_name') + if not name_en or not name_ar or not short_name: + return JsonResponse({'success': False, 'error': 'Missing fields'}, status=400) + + unit = Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name) + return JsonResponse({ + 'success': True, + 'id': unit.id, + 'name_en': unit.name_en, + 'name_ar': unit.name_ar + }) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}, status=400) + return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405) + +@csrf_exempt +def add_supplier_ajax(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + name = data.get('name') + contact_person = data.get('contact_person', '') + phone = data.get('phone', '') + if not name: + return JsonResponse({'success': False, 'error': 'Missing name'}, status=400) + + supplier = Supplier.objects.create(name=name, contact_person=contact_person, phone=phone) + return JsonResponse({ + 'success': True, + 'id': supplier.id, + 'name': supplier.name + }) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}, status=400) + return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)