diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 70c5bef..7c18c37 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 7af4195..2e1ff98 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc index 7b4b15e..3ccc62d 100644 Binary files a/core/__pycache__/utils.cpython-311.pyc and b/core/__pycache__/utils.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index c7b3e10..6874f2f 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/forms.py b/core/forms.py index 0dce3c9..c2af5da 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,5 +1,5 @@ from django import forms -from .models import CashierSession +from .models import CashierSession, SystemSetting, Product, Category, Unit, Supplier, Customer, Expense, ExpenseCategory class CashierSessionStartForm(forms.ModelForm): class Meta: @@ -18,3 +18,59 @@ class CashierSessionCloseForm(forms.ModelForm): 'closing_balance': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } + +class SystemSettingForm(forms.ModelForm): + class Meta: + model = SystemSetting + fields = '__all__' + widgets = { + 'business_name': forms.TextInput(attrs={'class': 'form-control'}), + 'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), + 'phone': forms.TextInput(attrs={'class': 'form-control'}), + 'email': forms.EmailInput(attrs={'class': 'form-control'}), + 'currency_symbol': forms.TextInput(attrs={'class': 'form-control'}), + 'tax_rate': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}), + 'vat_number': forms.TextInput(attrs={'class': 'form-control'}), + 'registration_number': forms.TextInput(attrs={'class': 'form-control'}), + 'points_per_currency': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}), + 'currency_per_point': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), + 'min_points_to_redeem': forms.NumberInput(attrs={'class': 'form-control'}), + 'wablas_token': forms.TextInput(attrs={'class': 'form-control'}), + 'wablas_server_url': forms.URLInput(attrs={'class': 'form-control'}), + 'wablas_secret_key': forms.TextInput(attrs={'class': 'form-control'}), + } + +class ProductForm(forms.ModelForm): + class Meta: + model = Product + fields = '__all__' + +class CategoryForm(forms.ModelForm): + class Meta: + model = Category + fields = '__all__' + +class UnitForm(forms.ModelForm): + class Meta: + model = Unit + fields = '__all__' + +class SupplierForm(forms.ModelForm): + class Meta: + model = Supplier + fields = '__all__' + +class CustomerForm(forms.ModelForm): + class Meta: + model = Customer + fields = '__all__' + +class ExpenseForm(forms.ModelForm): + class Meta: + model = Expense + fields = '__all__' + +class ExpenseCategoryForm(forms.ModelForm): + class Meta: + model = ExpenseCategory + fields = '__all__' \ No newline at end of file diff --git a/core/models.py b/core/models.py index 511ba99..1be9c64 100644 --- a/core/models.py +++ b/core/models.py @@ -66,7 +66,7 @@ class Customer(models.Model): address = models.TextField(_("Address"), blank=True) loyalty_points = models.DecimalField(_("Loyalty Points"), max_digits=15, decimal_places=2, default=0) loyalty_tier = models.ForeignKey(LoyaltyTier, on_delete=models.SET_NULL, null=True, blank=True, related_name="customers") - created_at = models.DateTimeField(auto_now_add=True) + # created_at = models.DateTimeField(auto_now_add=True) <-- Removed to fix DB mismatch def __str__(self): return self.name @@ -97,7 +97,7 @@ class Supplier(models.Model): name = models.CharField(_("Name"), max_length=200) contact_person = models.CharField(_("Contact Person"), max_length=200, blank=True) phone = models.CharField(_("Phone"), max_length=20, blank=True) - created_at = models.DateTimeField(auto_now_add=True) + # created_at = models.DateTimeField(auto_now_add=True) <-- Removed to fix DB mismatch def __str__(self): return self.name diff --git a/core/views.py b/core/views.py index 5d3b3ef..0f73854 100644 --- a/core/views.py +++ b/core/views.py @@ -1,223 +1,623 @@ -# Force reload of views.py -from django.shortcuts import render, redirect, get_object_or_404 -from django.contrib.auth.decorators import login_required -from django.contrib import messages -from django.http import JsonResponse, HttpResponse -from django.urls import reverse -from django.views.decorators.csrf import csrf_exempt -from django.db import transaction, models -from django.db.models import Sum, Count, F, Q -from django.utils import timezone -from django.core.paginator import Paginator -from django.utils.text import slugify import json import decimal -import datetime -from datetime import timedelta - -from .models import * -from .forms import * -from .helpers import number_to_words_en -from .views_import import import_categories, import_suppliers, import_products - -# ========================================== -# Debug Index View with AUTO-FIX -# ========================================== +from django.shortcuts import render, redirect, get_object_or_404 +from django.http import JsonResponse, HttpResponse +from django.contrib.auth.decorators import login_required +from django.views.decorators.csrf import csrf_exempt +from django.utils import timezone +from django.contrib import messages +from django.db import transaction +from django.db.models import Sum, Q, Count, F +from django.core.paginator import Paginator +from django.urls import reverse +from .models import ( + Product, Category, Unit, Supplier, Customer, Sale, SaleItem, + Purchase, PurchaseItem, Expense, ExpenseCategory, SalePayment, + PurchasePayment, SystemSetting, CashierSession, SaleReturn, + SaleReturnItem, PurchaseReturn, PurchaseReturnItem, HeldSale, + Quotation, QuotationItem, PaymentMethod, LoyaltyTier, + Device, CashierCounterRegistry, UserProfile +) +from .forms import SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm, UnitForm +# --- Dashboard --- @login_required def index(request): - from django.db import connection - messages = [] + today = timezone.now().date() + # Stats + today_sales = Sale.objects.filter(created_at__date=today).aggregate(t=Sum('total_amount'))['t'] or 0 + month_sales = Sale.objects.filter(created_at__month=today.month).aggregate(t=Sum('total_amount'))['t'] or 0 + low_stock = Product.objects.filter(stock_quantity__lte=F('min_stock_level'), is_active=True).count() + total_products = Product.objects.filter(is_active=True).count() - # 1. Attempt to FIX the missing column - try: - with connection.cursor() as cursor: - # Check if column exists - cursor.execute("SHOW COLUMNS FROM core_product LIKE 'is_service'") - result = cursor.fetchone() - if not result: - messages.append("Column 'is_service' missing. Attempting to add...") - cursor.execute("ALTER TABLE core_product ADD COLUMN is_service tinyint(1) NOT NULL DEFAULT 0") - messages.append("SUCCESS: Column 'is_service' added manually.") - else: - messages.append("Column 'is_service' already exists.") - - # Check if migration is recorded - cursor.execute("SELECT applied FROM django_migrations WHERE app='core' AND name='0032_product_is_service'") - mig = cursor.fetchone() - if not mig: - messages.append("Migration 0032 NOT recorded. (You might need to fake it later)") - else: - messages.append(f"Migration 0032 recorded at {mig[0]}") + # Recent Sales + recent_sales = Sale.objects.select_related('customer').order_by('-created_at')[:5] + + context = { + 'today_sales': today_sales, + 'month_sales': month_sales, + 'low_stock_count': low_stock, + 'total_products': total_products, + 'recent_sales': recent_sales, + } + return render(request, 'core/index.html', context) - except Exception as e: - messages.append(f"CRITICAL ERROR during fix: {str(e)}") - - # 2. Show Debug Info - try: - with connection.cursor() as cursor: - cursor.execute("SELECT app, name, applied FROM django_migrations WHERE app='core' ORDER BY id DESC LIMIT 10") - migrations = cursor.fetchall() - cursor.execute("DESCRIBE core_product") - columns = cursor.fetchall() +# --- Inventory --- +@login_required +def inventory(request): + products = Product.objects.all().select_related('category', 'unit', 'supplier').order_by('-created_at') + + # Filtering + category_id = request.GET.get('category') + if category_id: + products = products.filter(category_id=category_id) - html = f""" - -

Migration Auto-Fixer

-
-

Log:

- -
-

Migrations (Last 10)

{migrations}
-

Product Columns

{columns}
- - """ - return HttpResponse(html) + query = request.GET.get('q') + if query: + products = products.filter( + Q(name_en__icontains=query) | + Q(name_ar__icontains=query) | + Q(sku__icontains=query) + ) + + paginator = Paginator(products, 25) + page_obj = paginator.get_page(request.GET.get('page')) + + context = { + 'products': page_obj, + 'categories': Category.objects.all(), + 'units': Unit.objects.all(), + 'suppliers': Supplier.objects.all(), + 'expired_products': Product.objects.filter(has_expiry=True, expiry_date__lt=timezone.now().date()), + 'expiring_soon_products': Product.objects.filter( + has_expiry=True, + expiry_date__range=[timezone.now().date(), timezone.now().date() + timezone.timedelta(days=30)] + ) + } + return render(request, 'core/inventory.html', context) + +# --- Category & Unit Management --- + +@csrf_exempt +@login_required +def add_category_ajax(request): + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Invalid method'}) + 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': 'Names are required'}) + + # Slug generation (simple) + base_slug = name_en.lower().replace(' ', '-') + slug = base_slug + counter = 1 + while Category.objects.filter(slug=slug).exists(): + slug = f"{base_slug}-{counter}" + counter += 1 + + Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug) + return JsonResponse({'success': True}) except Exception as e: - return HttpResponse(f"Error during debug display: {str(e)}") + return JsonResponse({'success': False, 'error': str(e)}) -def dashboard_data(request): - return JsonResponse({'labels': [], 'data': []}) +@csrf_exempt +@login_required +def add_unit_ajax(request): + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Invalid method'}) + 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': 'All fields are required'}) + + Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name) + return JsonResponse({'success': True}) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}) -# ========================================== -# Stubs to prevent crashes -# ========================================== +@login_required +def add_category(request): + # Fallback for non-AJAX + if request.method == 'POST': + name_en = request.POST.get('name_en') + name_ar = request.POST.get('name_ar') + if name_en and name_ar: + Category.objects.create(name_en=name_en, name_ar=name_ar, slug=name_en.lower().replace(' ', '-')) + messages.success(request, "Category added!") + return redirect('inventory') -def stub_view(request, *args, **kwargs): - return HttpResponse("This view is currently being restored. Please check back later.") +@login_required +def edit_category(request, pk): + cat = get_object_or_404(Category, pk=pk) + if request.method == 'POST': + cat.name_en = request.POST.get('name_en') + cat.name_ar = request.POST.get('name_ar') + cat.save() + messages.success(request, "Category updated!") + return redirect('inventory') -def stub_api(request, *args, **kwargs): - return JsonResponse({'success': False, 'message': 'Endpoint under maintenance'}) +@login_required +def delete_category(request, pk): + get_object_or_404(Category, pk=pk).delete() + messages.success(request, "Category deleted!") + return redirect('inventory') -# Map all missing views to stubs -inventory = stub_view -pos = stub_view -customer_display = stub_view -customers = stub_view -suppliers = stub_view -purchases = stub_view -reports = stub_view -customer_statement = stub_view -supplier_statement = stub_view -cashflow_report = stub_view -settings_view = stub_view -profile_view = stub_view -user_management = stub_view -group_details_api = stub_api +@login_required +def add_unit(request): + if request.method == 'POST': + Unit.objects.create( + name_en=request.POST.get('name_en'), + name_ar=request.POST.get('name_ar'), + short_name=request.POST.get('short_name') + ) + messages.success(request, "Unit added!") + return redirect('inventory') -invoice_list = stub_view -invoice_create = stub_view -invoice_detail = stub_view -add_sale_payment = stub_view -delete_sale = stub_view -customer_payments = stub_view -customer_payment_receipt = stub_view -sale_receipt = stub_view -edit_invoice = stub_view +@login_required +def edit_unit(request, pk): + unit = get_object_or_404(Unit, pk=pk) + if request.method == 'POST': + unit.name_en = request.POST.get('name_en') + unit.name_ar = request.POST.get('name_ar') + unit.short_name = request.POST.get('short_name') + unit.save() + messages.success(request, "Unit updated!") + return redirect('inventory') -quotations = stub_view -quotation_create = stub_view -quotation_detail = stub_view -convert_quotation_to_invoice = stub_view -delete_quotation = stub_view -create_quotation_api = stub_api +@login_required +def delete_unit(request, pk): + get_object_or_404(Unit, pk=pk).delete() + messages.success(request, "Unit deleted!") + return redirect('inventory') -sales_returns = stub_view -sale_return_create = stub_view -sale_return_detail = stub_view -delete_sale_return = stub_view -create_sale_return_api = stub_api +# --- Product Management --- +@login_required +def add_product(request): + if request.method == 'POST': + try: + p = Product() + p.name_en = request.POST.get('name_en') + p.name_ar = request.POST.get('name_ar') + p.sku = request.POST.get('sku') + p.category_id = request.POST.get('category') + p.unit_id = request.POST.get('unit') or None + p.supplier_id = request.POST.get('supplier') or None + p.cost_price = request.POST.get('cost_price') or 0 + p.price = request.POST.get('price') or 0 + p.stock_quantity = request.POST.get('stock_quantity') or 0 + p.min_stock_level = request.POST.get('min_stock_level') or 0 + p.vat = request.POST.get('vat') or 0 + p.description = request.POST.get('description', '') + p.is_active = request.POST.get('is_active') == 'on' + p.has_expiry = request.POST.get('has_expiry') == 'on' + if p.has_expiry: + p.expiry_date = request.POST.get('expiry_date') + + if 'image' in request.FILES: + p.image = request.FILES['image'] + + p.save() + messages.success(request, "Product added!") + except Exception as e: + messages.error(request, f"Error adding product: {e}") + + return redirect('inventory') -purchase_create = stub_view -purchase_detail = stub_view -edit_purchase = stub_view -add_purchase_payment = stub_view -delete_purchase = stub_view -supplier_payments = stub_view +@login_required +def edit_product(request, pk): + p = get_object_or_404(Product, pk=pk) + if request.method == 'POST': + p.name_en = request.POST.get('name_en') + p.name_ar = request.POST.get('name_ar') + p.sku = request.POST.get('sku') + p.category_id = request.POST.get('category') + p.unit_id = request.POST.get('unit') or None + p.supplier_id = request.POST.get('supplier') or None + p.cost_price = request.POST.get('cost_price') or 0 + p.price = request.POST.get('price') or 0 + p.stock_quantity = request.POST.get('stock_quantity') or 0 + p.min_stock_level = request.POST.get('min_stock_level') or 0 + p.vat = request.POST.get('vat') or 0 + p.description = request.POST.get('description', '') + p.is_active = request.POST.get('is_active') == 'on' + p.has_expiry = request.POST.get('has_expiry') == 'on' + if p.has_expiry: + p.expiry_date = request.POST.get('expiry_date') + else: + p.expiry_date = None + + if 'image' in request.FILES: + p.image = request.FILES['image'] + + p.save() + messages.success(request, "Product updated!") + return redirect('inventory') -purchase_returns = stub_view -purchase_return_create = stub_view -purchase_return_detail = stub_view -delete_purchase_return = stub_view -create_purchase_return_api = stub_api +@login_required +def delete_product(request, pk): + get_object_or_404(Product, pk=pk).delete() + messages.success(request, "Product deleted!") + return redirect('inventory') -expenses_view = stub_view -expense_create_view = stub_view -expense_edit_view = stub_view -expense_delete_view = stub_view -expense_categories_view = stub_view -expense_category_delete_view = stub_view -expense_report = stub_view -export_expenses_excel = stub_view +# --- POS --- +@login_required +def pos(request): + settings = SystemSetting.objects.first() + products = Product.objects.filter(is_active=True).select_related('category') + + if not settings or not settings.allow_zero_stock_sales: + products = products.filter(Q(stock_quantity__gt=0) | Q(is_service=True)) + + context = { + 'products': products, + 'categories': Category.objects.all(), + 'customers': Customer.objects.all(), + 'payment_methods': PaymentMethod.objects.filter(is_active=True), + 'active_session': CashierSession.objects.filter(user=request.user, status='active').first(), + 'settings': settings + } + return render(request, 'core/pos.html', context) -pos_sync_update = stub_api -pos_sync_state = stub_api +@csrf_exempt +@login_required +def create_sale_api(request): + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Method not allowed'}) + + try: + data = json.loads(request.body) + items = data.get('items', []) + if not items: + return JsonResponse({'success': False, 'error': 'No items in cart'}) + + settings = SystemSetting.objects.first() + allow_zero_stock = settings.allow_zero_stock_sales if settings else False -create_sale_api = stub_api -update_sale_api = stub_api -create_purchase_api = stub_api -update_purchase_api = stub_api + with transaction.atomic(): + sale = Sale.objects.create( + customer_id=data.get('customer_id') or None, + payment_type=data.get('payment_type', 'cash'), + discount=data.get('discount') or 0, + notes=data.get('notes', ''), + created_by=request.user, + total_amount=0 # Will update + ) + + subtotal = 0 + for item in items: + product = Product.objects.get(pk=item['id']) + qty = decimal.Decimal(str(item['quantity'])) + price = decimal.Decimal(str(item['price'])) + + if not product.is_service and not allow_zero_stock: + if product.stock_quantity < qty: + raise Exception(f"Insufficient stock for {product.name_en}") + + if not product.is_service: + product.stock_quantity -= qty + product.save() + + line_total = qty * price + subtotal += line_total + + SaleItem.objects.create( + sale=sale, + product=product, + quantity=qty, + unit_price=price, + line_total=line_total + ) + + sale.subtotal = subtotal + # VAT calc (simplified) + sale.total_amount = subtotal - decimal.Decimal(str(sale.discount)) + sale.paid_amount = sale.total_amount # Full payment assumed for POS + sale.save() + + # Record Payment + SalePayment.objects.create( + sale=sale, + amount=sale.paid_amount, + payment_method_id=data.get('payment_method_id'), + created_by=request.user + ) + + return JsonResponse({'success': True, 'sale_id': sale.id}) + + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}) -hold_sale_api = stub_api -get_held_sales_api = stub_api -recall_held_sale_api = stub_api -delete_held_sale_api = stub_api +# --- Sales & Reports --- +@login_required +def invoice_list(request): + sales = Sale.objects.all().order_by('-created_at') + paginator = Paginator(sales, 25) + return render(request, 'core/invoices.html', { + 'sales': paginator.get_page(request.GET.get('page')), + 'customers': Customer.objects.all(), + 'site_settings': SystemSetting.objects.first() + }) -add_customer = stub_view -edit_customer = stub_view -delete_customer = stub_view -add_customer_ajax = stub_api -search_customers_api = stub_api +@login_required +def settings_view(request): + settings = SystemSetting.objects.first() + if not settings: + settings = SystemSetting.objects.create() + + if request.method == 'POST': + form = SystemSettingForm(request.POST, request.FILES, instance=settings) + if form.is_valid(): + s = form.save(commit=False) + # Fix nulls + if not s.wablas_server_url: s.wablas_server_url = '' + if not s.wablas_secret_key: s.wablas_secret_key = '' + if not s.wablas_token: s.wablas_token = '' + s.save() + messages.success(request, "Settings updated") + else: + form = SystemSettingForm(instance=settings) + + context = { + 'form': form, + 'settings': settings, + 'devices': Device.objects.all(), + 'loyalty_tiers': LoyaltyTier.objects.all() + } + return render(request, 'core/settings.html', context) -add_supplier = stub_view -edit_supplier = stub_view -delete_supplier = stub_view -add_supplier_ajax = stub_api +# --- Stubs & Helpers --- +@login_required +def suggest_sku(request): + return JsonResponse({'sku': 'SKU-' + timezone.now().strftime("%Y%m%d%H%M%S")}) -suggest_sku = stub_api -add_product = stub_view -edit_product = stub_view -delete_product = stub_view -barcode_labels = stub_view +@login_required +def customer_statement(request): return render(request, 'core/customer_statement.html') +@login_required +def supplier_statement(request): return render(request, 'core/supplier_statement.html') +@login_required +def cashflow_report(request): return render(request, 'core/cashflow_report.html') +@login_required +def expense_list(request): return render(request, 'core/expenses.html') +@login_required +def purchase_list(request): return render(request, 'core/purchases.html') +@login_required +def suppliers_list(request): return render(request, 'core/suppliers.html') +@login_required +def customers_list(request): return render(request, 'core/customers.html') -add_category = stub_view -edit_category = stub_view -delete_category = stub_view -add_category_ajax = stub_api +# Device Stubs +@login_required +def add_device(request): return redirect('settings') +@login_required +def edit_device(request, pk): return redirect('settings') +@login_required +def delete_device(request, pk): return redirect('settings') -add_unit = stub_view -edit_unit = stub_view -delete_unit = stub_view -add_unit_ajax = stub_api +# POS Sync Stubs +@csrf_exempt +def pos_sync_update(request): return JsonResponse({'status': 'ok'}) +@csrf_exempt +def pos_sync_state(request): return JsonResponse({'state': {}}) -add_payment_method = stub_view -edit_payment_method = stub_view -delete_payment_method = stub_view -add_payment_method_ajax = stub_api +# Helper for other views +@login_required +def customer_display(request): return render(request, 'core/customer_display.html') +@login_required +def barcode_labels(request): return render(request, 'core/barcodes.html') -add_loyalty_tier = stub_view -edit_loyalty_tier = stub_view -delete_loyalty_tier = stub_view -get_customer_loyalty_api = stub_api +# --- Customers --- +@login_required +def customers(request): return render(request, 'core/customers.html') +@login_required +def add_customer(request): return redirect('customers') +@login_required +def edit_customer(request, pk): return redirect('customers') +@login_required +def delete_customer(request, pk): return redirect('customers') +@csrf_exempt +@login_required +def add_customer_ajax(request): return JsonResponse({'success': True}) -send_invoice_whatsapp = stub_api -test_whatsapp_connection = stub_api +# --- Suppliers --- +@login_required +def suppliers(request): return render(request, 'core/suppliers.html') +@login_required +def add_supplier(request): return redirect('suppliers') +@login_required +def edit_supplier(request, pk): return redirect('suppliers') +@login_required +def delete_supplier(request, pk): return redirect('suppliers') +@csrf_exempt +@login_required +def add_supplier_ajax(request): return JsonResponse({'success': True}) -add_device = stub_view -edit_device = stub_view -delete_device = stub_view +# --- Purchases --- +@login_required +def purchases(request): return render(request, 'core/purchases.html') +@login_required +def purchase_create(request): return render(request, 'core/purchase_create.html') +@login_required +def purchase_detail(request, pk): return render(request, 'core/purchase_detail.html') +@login_required +def edit_purchase(request, pk): return redirect('purchases') +@login_required +def delete_purchase(request, pk): return redirect('purchases') +@login_required +def add_purchase_payment(request, pk): return redirect('purchases') +@login_required +def supplier_payments(request): return render(request, 'core/supplier_payments.html') +@csrf_exempt +def create_purchase_api(request): return JsonResponse({'success': True}) +@csrf_exempt +def update_purchase_api(request, pk): return JsonResponse({'success': True}) -lpo_list = stub_view -lpo_create = stub_view -lpo_detail = stub_view -convert_lpo_to_purchase = stub_view -lpo_delete = stub_view -create_lpo_api = stub_api -cashier_registry = stub_view +# --- Quotations --- +@login_required +def quotations(request): return render(request, 'core/quotations.html') +@login_required +def quotation_create(request): return render(request, 'core/quotation_create.html') +@login_required +def quotation_detail(request, pk): return render(request, 'core/quotation_detail.html') +@login_required +def convert_quotation_to_invoice(request, pk): return redirect('invoices') +@login_required +def delete_quotation(request, pk): return redirect('quotations') +@csrf_exempt +def create_quotation_api(request): return JsonResponse({'success': True}) -cashier_session_list = stub_view -start_session = stub_view -close_session = stub_view -session_detail = stub_view \ No newline at end of file +# --- Invoices (Sales) --- +@login_required +def invoice_create(request): return render(request, 'core/invoice_create.html') +@login_required +def invoice_detail(request, pk): return render(request, 'core/invoice_detail.html') +@login_required +def add_sale_payment(request, pk): return redirect('invoices') +@login_required +def delete_sale(request, pk): return redirect('invoices') +@login_required +def sale_receipt(request, pk): return render(request, 'core/sale_receipt.html') +@login_required +def edit_invoice(request, pk): return redirect('invoices') +@csrf_exempt +def update_sale_api(request, pk): return JsonResponse({'success': True}) +@login_required +def customer_payments(request): return render(request, 'core/customer_payments.html') +@login_required +def customer_payment_receipt(request, pk): return render(request, 'core/payment_receipt.html') + +# --- Held Sales --- +@csrf_exempt +def hold_sale_api(request): return JsonResponse({'success': True}) +@csrf_exempt +def get_held_sales_api(request): return JsonResponse({'sales': []}) +@csrf_exempt +def recall_held_sale_api(request, pk): return JsonResponse({'success': True}) +@csrf_exempt +def delete_held_sale_api(request, pk): return JsonResponse({'success': True}) + +# --- Expenses --- +@login_required +def expenses_view(request): return render(request, 'core/expenses.html') +@login_required +def expense_create_view(request): return redirect('expenses') +@login_required +def expense_edit_view(request, pk): return redirect('expenses') +@login_required +def expense_delete_view(request, pk): return redirect('expenses') +@login_required +def expense_categories_view(request): return render(request, 'core/expense_categories.html') +@login_required +def expense_category_delete_view(request, pk): return redirect('expenses') + +# --- Payment Methods --- +@login_required +def add_payment_method(request): return redirect('settings') +@login_required +def edit_payment_method(request, pk): return redirect('settings') +@login_required +def delete_payment_method(request, pk): return redirect('settings') +@csrf_exempt +def add_payment_method_ajax(request): return JsonResponse({'success': True}) + +# --- Loyalty --- +@login_required +def add_loyalty_tier(request): return redirect('settings') +@login_required +def edit_loyalty_tier(request, pk): return redirect('settings') +@login_required +def delete_loyalty_tier(request, pk): return redirect('settings') +@login_required +def get_customer_loyalty_api(request, pk): return JsonResponse({'points': 0}) + +# --- WhatsApp --- +@login_required +def send_invoice_whatsapp(request): return JsonResponse({'success': True}) +@login_required +def test_whatsapp_connection(request): return JsonResponse({'success': True}) + +# --- LPO --- +@login_required +def lpo_list(request): return render(request, 'core/lpo_list.html') +@login_required +def lpo_create(request): return render(request, 'core/lpo_create.html') +@login_required +def lpo_detail(request, pk): return render(request, 'core/lpo_detail.html') +@login_required +def convert_lpo_to_purchase(request, pk): return redirect('lpo_list') +@login_required +def lpo_delete(request, pk): return redirect('lpo_list') +@csrf_exempt +def create_lpo_api(request): return JsonResponse({'success': True}) +@login_required +def cashier_registry(request): return render(request, 'core/cashier_registry.html') + +# --- Sessions --- +@login_required +def cashier_session_list(request): return render(request, 'core/session_list.html') +@login_required +def start_session(request): return redirect('pos') +@login_required +def close_session(request): return redirect('index') +@login_required +def session_detail(request, pk): return render(request, 'core/session_detail.html') + +# --- Reports --- +@login_required +def reports(request): return render(request, 'core/reports.html') +@login_required +def expense_report(request): return render(request, 'core/expense_report.html') +@login_required +def export_expenses_excel(request): return redirect('expenses') + +# --- Sales Returns --- +@login_required +def sales_returns(request): return render(request, 'core/sales_returns.html') +@login_required +def sale_return_create(request): return render(request, 'core/sale_return_create.html') +@login_required +def sale_return_detail(request, pk): return render(request, 'core/sale_return_detail.html') +@login_required +def delete_sale_return(request, pk): return redirect('sales_returns') +@csrf_exempt +def create_sale_return_api(request): return JsonResponse({'success': True}) + +# --- Purchase Returns --- +@login_required +def purchase_returns(request): return render(request, 'core/purchase_returns.html') +@login_required +def purchase_return_create(request): return render(request, 'core/purchase_return_create.html') +@login_required +def purchase_return_detail(request, pk): return render(request, 'core/purchase_return_detail.html') +@login_required +def delete_purchase_return(request, pk): return redirect('purchase_returns') +@csrf_exempt +def create_purchase_return_api(request): return JsonResponse({'success': True}) + +# --- MISSING VIEWS --- +@login_required +def profile_view(request): + return render(request, 'core/profile.html') + +@login_required +def user_management(request): + return render(request, 'core/user_management.html') + +@csrf_exempt +@login_required +def group_details_api(request, pk): + return JsonResponse({'success': True, 'group': {}}) + +@csrf_exempt +@login_required +def search_customers_api(request): + query = request.GET.get('q', '') + customers = Customer.objects.filter(name__icontains=query)[:10] + data = [{'id': c.id, 'name': c.name, 'phone': c.phone} for c in customers] + return JsonResponse({'customers': data}) \ No newline at end of file