From 80c69b02d217a87e583dbea9ca9fc22205d8ef5b Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 9 Feb 2026 18:01:15 +0000 Subject: [PATCH] Autosave: 20260209-180113 --- core/__pycache__/models.cpython-311.pyc | Bin 45725 -> 45911 bytes core/__pycache__/views.cpython-311.pyc | Bin 61701 -> 69359 bytes core/models.py | 18 +- core/views.py | 1127 +++++++---------------- 4 files changed, 356 insertions(+), 789 deletions(-) diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 55cf2dde18099dddf2f2aea434550f23a9902696..7af41952a2a1f46c9fb4d36a716694edde42457e 100644 GIT binary patch delta 5380 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 DFJYe} delta 5177 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 L8=^;mh|&K6pno)c diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 6c77f41e5f19846186368a0456c28fea7f655215..6495a9b922cbe711811291933cea9184baf70069 100644 GIT binary patch literal 69359 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 BE?xit diff --git a/core/models.py b/core/models.py index 79a6af6..511ba99 100644 --- a/core/models.py +++ b/core/models.py @@ -42,6 +42,7 @@ class Product(models.Model): expiry_date = models.DateField(_("Expiry Date"), null=True, blank=True) image = models.FileField(_("Product Image"), upload_to="product_images/", blank=True, null=True) is_active = models.BooleanField(_("Active"), default=True) + is_service = models.BooleanField(_("Is Service"), default=False) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): @@ -65,6 +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) def __str__(self): return self.name @@ -95,6 +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) def __str__(self): return self.name @@ -374,11 +377,10 @@ class PurchaseReturnItem(models.Model): return f"{self.product.name_en} x {self.quantity}" class HeldSale(models.Model): - customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True, related_name="held_sales") - cart_data = models.JSONField(_("Cart Data")) - total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) - notes = models.TextField(_("Notes"), blank=True) created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="held_sales") + cart_data = models.TextField(_("Cart Data")) + customer_name = models.CharField(_("Customer Name"), max_length=200, blank=True) + note = models.TextField(_("Note"), blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): @@ -483,10 +485,10 @@ class CashierSession(models.Model): @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: - UserProfile.objects.create(user=instance) + UserProfile.objects.get_or_create(user=instance) @receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): - if not hasattr(instance, 'profile'): - UserProfile.objects.create(user=instance) - instance.profile.save() \ No newline at end of file + UserProfile.objects.get_or_create(user=instance) + if hasattr(instance, 'profile'): + instance.profile.save() \ No newline at end of file diff --git a/core/views.py b/core/views.py index 41af225..84ef718 100644 --- a/core/views.py +++ b/core/views.py @@ -9,6 +9,7 @@ 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 @@ -25,20 +26,14 @@ from .views_import import import_categories, import_suppliers, import_products @login_required def index(request): - # 1. Basic Counts total_sales_amount = Sale.objects.aggregate(total=Sum('total_amount'))['total'] or 0 total_sales_count = Sale.objects.count() total_products = Product.objects.count() total_customers = Customer.objects.count() - - # New: Receivables and Payables total_receivables = Sale.objects.aggregate(total=Sum('balance_due'))['total'] or 0 total_payables = Purchase.objects.aggregate(total=Sum('balance_due'))['total'] or 0 - # 2. Charts Data today = timezone.now().date() - - # A. Monthly Sales (Current Year) current_year = today.year monthly_sales = (Sale.objects.filter(created_at__year=current_year) .annotate(month=models.functions.ExtractMonth('created_at')) @@ -48,7 +43,6 @@ def index(request): monthly_labels = [] monthly_data = [] - # Initialize 12 months with 0 months_map = {i: 0 for i in range(1, 13)} for entry in monthly_sales: months_map[entry['month']] = float(entry['total']) @@ -58,7 +52,6 @@ def index(request): monthly_labels.append(calendar.month_abbr[i]) monthly_data.append(months_map[i]) - # B. Daily Sales (Last 7 Days) seven_days_ago = today - timedelta(days=6) daily_sales = (Sale.objects.filter(created_at__date__gte=seven_days_ago) .annotate(day=models.functions.ExtractDay('created_at')) @@ -68,7 +61,6 @@ def index(request): chart_labels = [] chart_data = [] - # Map dates to ensure continuity date_map = {} current_date = seven_days_ago while current_date <= today: @@ -82,7 +74,6 @@ def index(request): chart_labels.append(date_key.strftime('%d %b')) chart_data.append(date_map[date_key]) - # C. Sales by Category category_sales = (SaleItem.objects.values('product__category__name_en') .annotate(total=Sum('line_total')) .order_by('-total')[:5]) @@ -90,7 +81,6 @@ def index(request): category_labels = [item['product__category__name_en'] for item in category_sales] category_data = [float(item['total']) for item in category_sales] - # D. Payment Methods payment_stats = (SalePayment.objects.values('payment_method_name') .annotate(total=Sum('amount')) .order_by('-total')) @@ -98,23 +88,13 @@ def index(request): payment_labels = [item['payment_method_name'] if item['payment_method_name'] else 'Unknown' for item in payment_stats] payment_data = [float(item['total']) for item in payment_stats] - # 3. Top Products top_products = (SaleItem.objects.values('product__name_en', 'product__name_ar') .annotate(total_qty=Sum('quantity'), total_rev=Sum('line_total')) .order_by('-total_rev')[:5]) - # 4. Recent Sales recent_sales = Sale.objects.select_related('customer').order_by('-created_at')[:5] - - # 5. Low Stock Alert low_stock_products = Product.objects.filter(is_active=True, stock_quantity__lte=F('min_stock_level'))[:5] - - # 6. Expired Products (if applicable) - expired_products = Product.objects.filter( - is_active=True, - has_expiry=True, - expiry_date__lt=today - )[:5] + expired_products = Product.objects.filter(is_active=True, has_expiry=True, expiry_date__lt=today)[:5] context = { 'total_sales_amount': total_sales_amount, @@ -140,981 +120,536 @@ def index(request): @login_required def inventory(request): - products = Product.objects.filter(is_active=True) + products = Product.objects.filter(is_active=True).select_related('category', 'unit', 'supplier') categories = Category.objects.all() units = Unit.objects.all() suppliers = Supplier.objects.all() - # Filter by category category_id = request.GET.get('category') if category_id: products = products.filter(category_id=category_id) - - # Search query = request.GET.get('q') if query: - products = products.filter( - Q(name_en__icontains=query) | - Q(name_ar__icontains=query) | - Q(sku__icontains=query) - ) + products = products.filter(Q(name_en__icontains=query) | Q(name_ar__icontains=query) | Q(sku__icontains=query)) + + today = timezone.now().date() + expiring_soon_products = Product.objects.filter(is_active=True, has_expiry=True, expiry_date__lte=today + timedelta(days=30), expiry_date__gte=today) + expired_products = Product.objects.filter(is_active=True, has_expiry=True, expiry_date__lt=today) + + paginator = Paginator(products, 20) + page_number = request.GET.get('page') + page_obj = paginator.get_page(page_number) context = { - 'products': products, + 'products': page_obj, 'categories': categories, 'units': units, 'suppliers': suppliers, + 'expiring_soon_products': expiring_soon_products, + 'expired_products': expired_products, + 'site_settings': SystemSetting.objects.first(), } return render(request, 'core/inventory.html', context) @login_required def customers(request): customers_list = Customer.objects.all().order_by('-created_at') - query = request.GET.get('q') if query: - customers_list = customers_list.filter( - Q(name__icontains=query) | - Q(phone__icontains=query) | - Q(email__icontains=query) - ) - - paginator = Paginator(customers_list, 10) - page_number = request.GET.get('page') - page_obj = paginator.get_page(page_number) - - return render(request, 'core/customers.html', {'page_obj': page_obj}) + customers_list = customers_list.filter(Q(name__icontains=query) | Q(phone__icontains=query) | Q(email__icontains=query)) + paginator = Paginator(customers_list, 20) + page_obj = paginator.get_page(request.GET.get('page')) + return render(request, 'core/customers.html', {'customers': page_obj}) @login_required def suppliers(request): suppliers_list = Supplier.objects.all().order_by('-created_at') - query = request.GET.get('q') if query: - suppliers_list = suppliers_list.filter( - Q(name__icontains=query) | - Q(contact_person__icontains=query) | - Q(phone__icontains=query) - ) - - paginator = Paginator(suppliers_list, 10) - page_number = request.GET.get('page') - page_obj = paginator.get_page(page_number) - - return render(request, 'core/suppliers.html', {'page_obj': page_obj}) + suppliers_list = suppliers_list.filter(Q(name__icontains=query) | Q(contact_person__icontains=query) | Q(phone__icontains=query)) + paginator = Paginator(suppliers_list, 20) + page_obj = paginator.get_page(request.GET.get('page')) + return render(request, 'core/suppliers.html', {'suppliers': page_obj}) @login_required def purchases(request): purchases_list = Purchase.objects.all().select_related('supplier').order_by('-created_at') - start_date = request.GET.get('start_date') end_date = request.GET.get('end_date') supplier_id = request.GET.get('supplier') - - if start_date: - purchases_list = purchases_list.filter(created_at__date__gte=start_date) - if end_date: - purchases_list = purchases_list.filter(created_at__date__lte=end_date) - if supplier_id: - purchases_list = purchases_list.filter(supplier_id=supplier_id) - - suppliers = Supplier.objects.all() - - paginator = Paginator(purchases_list, 10) - page_number = request.GET.get('page') - page_obj = paginator.get_page(page_number) - - return render(request, 'core/purchases.html', { - 'page_obj': page_obj, - 'suppliers': suppliers - }) + if start_date: purchases_list = purchases_list.filter(created_at__date__gte=start_date) + if end_date: purchases_list = purchases_list.filter(created_at__date__lte=end_date) + if supplier_id: purchases_list = purchases_list.filter(supplier_id=supplier_id) + paginator = Paginator(purchases_list, 20) + page_obj = paginator.get_page(request.GET.get('page')) + return render(request, 'core/purchases.html', {'page_obj': page_obj, 'suppliers': Supplier.objects.all()}) @login_required -def reports(request): - return render(request, 'core/reports.html') - +def reports(request): return render(request, 'core/reports.html') @login_required -def customer_statement(request): - return render(request, 'core/reports.html') # Placeholder - +def customer_statement(request): return render(request, 'core/reports.html') @login_required -def supplier_statement(request): - return render(request, 'core/reports.html') # Placeholder - +def supplier_statement(request): return render(request, 'core/reports.html') @login_required -def cashflow_report(request): - return render(request, 'core/reports.html') # Placeholder +def cashflow_report(request): return render(request, 'core/reports.html') @login_required def settings_view(request): - return render(request, 'core/settings.html') + settings = SystemSetting.objects.first() + payment_methods = PaymentMethod.objects.all() + devices = Device.objects.all() + loyalty_tiers = LoyaltyTier.objects.all() + return render(request, 'core/settings.html', { + 'settings': settings, + 'payment_methods': payment_methods, + 'devices': devices, + 'loyalty_tiers': loyalty_tiers + }) @login_required -def profile_view(request): - return render(request, 'core/profile.html') - +def profile_view(request): return render(request, 'core/profile.html') @login_required def user_management(request): from django.contrib.auth.models import User, Group - users = User.objects.all() - groups = Group.objects.all() - return render(request, 'core/user_management.html', {'users': users, 'groups': groups}) + return render(request, 'core/user_management.html', {'users': User.objects.all(), 'groups': Group.objects.all()}) @login_required def group_details_api(request, pk): - from django.contrib.auth.models import Group, Permission + from django.contrib.auth.models import Group group = get_object_or_404(Group, pk=pk) - permissions = group.permissions.all() - data = { 'id': group.id, 'name': group.name, - 'permissions': [{'id': p.id, 'name': p.name, 'codename': p.codename} for p in permissions] + 'permissions': [{'id': p.id, 'name': p.name, 'codename': p.codename} for p in group.permissions.all()] } return JsonResponse(data) - # ========================================== -# POS & Sales Views +# POS & Sales # ========================================== @login_required def pos(request): - categories = Category.objects.all() - products = Product.objects.filter(is_active=True).select_related('category', 'unit') - customers = Customer.objects.all() - payment_methods = PaymentMethod.objects.filter(is_active=True) - - # Check for active session - session = CashierSession.objects.filter(user=request.user, end_time__isnull=True).last() - - # Retrieve held sales - held_sales = HeldSale.objects.filter(created_by=request.user).order_by('-created_at') - + session = CashierSession.objects.filter(user=request.user, status='active').last() context = { - 'categories': categories, - 'products': products, - 'customers': customers, - 'payment_methods': payment_methods, + 'categories': Category.objects.all(), + 'products': Product.objects.filter(is_active=True).select_related('category', 'unit'), + 'customers': Customer.objects.all(), + 'payment_methods': PaymentMethod.objects.filter(is_active=True), 'session': session, - 'held_sales': held_sales, + 'held_sales': HeldSale.objects.filter(created_by=request.user).order_by('-created_at'), } return render(request, 'core/pos.html', context) @login_required -def customer_display(request): - return render(request, 'core/customer_display.html') +def customer_display(request): return render(request, 'core/customer_display.html') @csrf_exempt @login_required def create_sale_api(request): - if request.method != 'POST': - return JsonResponse({'success': False, 'message': 'Invalid method'}, status=405) - + if request.method != 'POST': return JsonResponse({'success': False}, status=405) try: data = json.loads(request.body) - customer_id = data.get('customer_id') items = data.get('items', []) payments = data.get('payments', []) - # --- Handle Single Payment Payload (from Invoice Create & Simple POS) --- if not payments: - payment_type = data.get('payment_type', 'cash') paid_amount = decimal.Decimal(str(data.get('paid_amount', 0))) - payment_method_id = data.get('payment_method_id') - - if payment_type == 'credit': - # No payment - pass - elif paid_amount > 0: - # Fetch method name - method_name = "Cash" - if payment_method_id: - try: - pm = PaymentMethod.objects.get(id=payment_method_id) - method_name = pm.name_en # Or whatever field we want to store - except: - pass - - payments.append({ - 'method': method_name, - 'amount': paid_amount - }) - # ---------------------------------------------------------------------- - - discount = decimal.Decimal(str(data.get('discount', 0))) - notes = data.get('notes', '') - invoice_number = data.get('invoice_number', '') - due_date = data.get('due_date') - - if not items: - return JsonResponse({'success': False, 'message': 'No items in cart'}, status=400) - - # Validate Session - session = CashierSession.objects.filter(user=request.user, end_time__isnull=True).last() - # if not session: ... (Optional check) + if paid_amount > 0: + method_name = "Cash" + pm_id = data.get('payment_method_id') + if pm_id: + try: method_name = PaymentMethod.objects.get(id=pm_id).name_en + except: pass + payments.append({'method': method_name, 'amount': paid_amount}) with transaction.atomic(): - customer = None - if customer_id: - customer = Customer.objects.get(id=customer_id) - + customer = Customer.objects.get(id=customer_id) if customer_id else None sale = Sale.objects.create( created_by=request.user, customer=customer, - invoice_number=invoice_number, - total_amount=0, # Will calculate - discount=discount, - notes=notes, - payment_status='Pending' + invoice_number=data.get('invoice_number', ''), + total_amount=0, + discount=decimal.Decimal(str(data.get('discount', 0))), + notes=data.get('notes', ''), + status='unpaid' ) - - if due_date: - sale.due_date = due_date + if data.get('due_date'): sale.due_date = data.get('due_date') subtotal = decimal.Decimal(0) - vat_amount = decimal.Decimal(0) # Track total VAT - + vat_amount = decimal.Decimal(0) for item in items: product = Product.objects.select_for_update().get(id=item['id']) qty = decimal.Decimal(str(item['quantity'])) - price = decimal.Decimal(str(item['price'])) + price = decimal.Decimal(str(item['price'])) - # Check System Setting for Zero Stock setting = SystemSetting.objects.first() - allow_zero = setting.allow_zero_stock_sales if setting else False - - if not product.is_service and product.stock_quantity < qty and not allow_zero: + if not product.is_service and product.stock_quantity < qty and (not setting or not setting.allow_zero_stock_sales): raise Exception(f"Insufficient stock for {product.name_en}") line_total = price * qty subtotal += line_total - - # Calculate VAT for this line - # Assuming price includes VAT? Or excludes? - # POS usually excludes or includes based on settings. - # Let's assume price is Unit Price *before* VAT if we add VAT separately? - # Or let's assume simple logic: Line Total is what user sees. - # But we should probably calculate VAT based on product.vat - item_vat = line_total * (product.vat / 100) - vat_amount += item_vat - - SaleItem.objects.create( - sale=sale, - product=product, - quantity=qty, - unit_price=price, # Fixed field name - line_total=line_total - ) - - # Update stock + vat_amount += line_total * (product.vat / 100) + SaleItem.objects.create(sale=sale, product=product, quantity=qty, unit_price=price, line_total=line_total) if not product.is_service: product.stock_quantity -= qty product.save() - # Recalculate Totals sale.subtotal = subtotal sale.vat_amount = vat_amount - sale.total_amount = subtotal + vat_amount - discount + sale.total_amount = subtotal + vat_amount - sale.discount - # Process Payments - paid_amount = decimal.Decimal(0) + paid = decimal.Decimal(0) for p in payments: - amount = decimal.Decimal(str(p['amount'])) - method_name = p['method'] - - SalePayment.objects.create( - sale=sale, - payment_method_name=method_name, - amount=amount, - created_by=request.user - ) - paid_amount += amount - - sale.paid_amount = paid_amount - sale.balance_due = sale.total_amount - paid_amount - - if sale.balance_due <= 0: - sale.payment_status = 'Paid' - elif paid_amount > 0: - sale.payment_status = 'Partial' - else: - sale.payment_status = 'Unpaid' - + amt = decimal.Decimal(str(p['amount'])) + SalePayment.objects.create(sale=sale, payment_method_name=p['method'], amount=amt, created_by=request.user) + paid += amt + sale.paid_amount = paid + sale.balance_due = sale.total_amount - paid + sale.status = 'paid' if sale.balance_due <= 0 else ('partial' if paid > 0 else 'unpaid') sale.save() - - return JsonResponse({'success': True, 'sale_id': sale.id, 'message': 'Sale created successfully'}) - + return JsonResponse({'success': True, 'sale_id': sale.id}) except Exception as e: - import traceback - traceback.print_exc() return JsonResponse({'success': False, 'message': str(e)}, status=500) -@csrf_exempt -@login_required -def update_sale_api(request, pk): - return JsonResponse({'success': False, 'message': 'Not implemented'}, status=501) - @csrf_exempt @login_required def hold_sale_api(request): - if request.method != 'POST': - return JsonResponse({'success': False, 'message': 'Invalid method'}, status=405) + if request.method != 'POST': return JsonResponse({'success': False}, status=405) try: data = json.loads(request.body) - cart_data = json.dumps(data.get('cart_data', {})) - note = data.get('note', '') - customer_name = data.get('customer_name', '') - HeldSale.objects.create( created_by=request.user, - cart_data=cart_data, - note=note, - customer_name=customer_name + cart_data=json.dumps(data.get('cart_data', {})), + note=data.get('note', ''), + customer_name=data.get('customer_name', '') ) return JsonResponse({'success': True}) - except Exception as e: - return JsonResponse({'success': False, 'message': str(e)}, status=500) + except Exception as e: return JsonResponse({'success': False, 'message': str(e)}, status=500) @login_required def get_held_sales_api(request): sales = HeldSale.objects.filter(created_by=request.user).order_by('-created_at') - data = [] - for s in sales: - data.append({ - 'id': s.id, - 'created_at': s.created_at.strftime('%Y-%m-%d %H:%M'), - 'customer_name': s.customer_name, - 'note': s.note, - 'cart_data': json.loads(s.cart_data) - }) + data = [{'id': s.id, 'created_at': s.created_at.strftime('%Y-%m-%d %H:%M'), 'customer_name': s.customer_name, 'note': s.note, 'cart_data': json.loads(s.cart_data)} for s in sales] return JsonResponse({'sales': data}) @csrf_exempt @login_required def recall_held_sale_api(request, pk): - # Just return the data, maybe delete it or keep it until finalized? - # Usually we delete it after recall or keep it. Let's keep it until explicitly deleted or completed. - held_sale = get_object_or_404(HeldSale, pk=pk, created_by=request.user) - return JsonResponse({ - 'success': True, - 'cart_data': json.loads(held_sale.cart_data), - 'customer_name': held_sale.customer_name, - 'note': held_sale.note - }) + s = get_object_or_404(HeldSale, pk=pk, created_by=request.user) + return JsonResponse({'success': True, 'cart_data': json.loads(s.cart_data), 'customer_name': s.customer_name, 'note': s.note}) @csrf_exempt @login_required def delete_held_sale_api(request, pk): - held_sale = get_object_or_404(HeldSale, pk=pk, created_by=request.user) - held_sale.delete() + get_object_or_404(HeldSale, pk=pk, created_by=request.user).delete() return JsonResponse({'success': True}) -# ========================================== -# Invoice / Quotation / Return Views -# ========================================== - +# Invoices @login_required def invoice_list(request): sales = Sale.objects.select_related('customer', 'created_by').order_by('-created_at') - - # Filter - status = request.GET.get('status') - if status: - sales = sales.filter(payment_status=status) - - start_date = request.GET.get('start_date') - end_date = request.GET.get('end_date') - if start_date: - sales = sales.filter(created_at__date__gte=start_date) - if end_date: - sales = sales.filter(created_at__date__lte=end_date) - - customers = Customer.objects.all() - + if request.GET.get('status'): sales = sales.filter(status=request.GET.get('status')) + if request.GET.get('start_date'): sales = sales.filter(created_at__date__gte=request.GET.get('start_date')) + if request.GET.get('end_date'): sales = sales.filter(created_at__date__lte=request.GET.get('end_date')) paginator = Paginator(sales, 20) - page_number = request.GET.get('page') - page_obj = paginator.get_page(page_number) - - payment_methods = PaymentMethod.objects.filter(is_active=True) - return render(request, 'core/invoices.html', { - 'page_obj': page_obj, - 'sales': page_obj, - 'payment_methods': payment_methods, - 'customers': customers + 'sales': paginator.get_page(request.GET.get('page')), + 'payment_methods': PaymentMethod.objects.filter(is_active=True), + 'customers': Customer.objects.all(), + 'site_settings': SystemSetting.objects.first() }) @login_required def invoice_detail(request, pk): sale = get_object_or_404(Sale, pk=pk) - return render(request, 'core/invoice_detail.html', {'sale': sale}) + return render(request, 'core/invoice_detail.html', {'sale': sale, 'payment_methods': PaymentMethod.objects.filter(is_active=True)}) @login_required def invoice_create(request): - # Retrieve data for the invoice form - products = Product.objects.filter(is_active=True).select_related('category', 'unit') - customers = Customer.objects.all() - payment_methods = PaymentMethod.objects.filter(is_active=True) - site_settings = SystemSetting.objects.first() - - context = { - 'products': products, - 'customers': customers, - 'payment_methods': payment_methods, - 'site_settings': site_settings, - 'decimal_places': site_settings.decimal_places if site_settings else 3, - } - return render(request, 'core/invoice_create.html', context) - -@login_required -def delete_sale(request, pk): - sale = get_object_or_404(Sale, pk=pk) - if request.method == 'POST': - # Restore stock? - with transaction.atomic(): - for item in sale.items.all(): - if not item.product.is_service: - item.product.stock_quantity += item.quantity - item.product.save() - sale.delete() - messages.success(request, "Invoice deleted and stock restored.") - return redirect('invoices') - return render(request, 'core/confirm_delete.html', {'object': sale}) + return render(request, 'core/invoice_create.html', { + 'products': Product.objects.filter(is_active=True).select_related('category', 'unit'), + 'customers': Customer.objects.all(), + 'payment_methods': PaymentMethod.objects.filter(is_active=True), + 'site_settings': SystemSetting.objects.first() + }) @login_required def add_sale_payment(request, pk): sale = get_object_or_404(Sale, pk=pk) if request.method == 'POST': amount = decimal.Decimal(request.POST.get('amount', 0)) - method = request.POST.get('method') - + pm_id = request.POST.get('payment_method_id') if amount > 0: - SalePayment.objects.create( - sale=sale, - payment_method_name=method, - amount=amount - ) - sale.paid_amount += amount - sale.balance_due = sale.total_amount - sale.paid_amount - if sale.balance_due <= 0: - sale.payment_status = 'Paid' - elif sale.paid_amount > 0: - sale.payment_status = 'Partial' - sale.save() - messages.success(request, "Payment added.") + method_name = "Cash" + if pm_id: + try: method_name = PaymentMethod.objects.get(id=pm_id).name_en + except: pass + SalePayment.objects.create(sale=sale, payment_method_name=method_name, amount=amount, created_by=request.user, notes=request.POST.get('notes', '')) + sale.update_balance() + messages.success(request, _("Payment recorded.")) return redirect('invoice_detail', pk=pk) +@login_required +def delete_sale(request, pk): + sale = get_object_or_404(Sale, pk=pk) + if request.method == 'POST': + with transaction.atomic(): + for item in sale.items.all(): + if not item.product.is_service: + item.product.stock_quantity += item.quantity + item.product.save() + sale.delete() + messages.success(request, _("Invoice deleted.")) + return redirect('invoices') + return render(request, 'core/confirm_delete.html', {'object': sale}) # Quotations @login_required -def quotations(request): - quots = Quotation.objects.all().order_by('-created_at') - return render(request, 'core/quotations.html', {'quotations': quots}) - +def quotations(request): return render(request, 'core/quotations.html', {'quotations': Quotation.objects.all().order_by('-created_at')}) @login_required -def quotation_create(request): - customers = Customer.objects.all() - products = Product.objects.filter(is_active=True) - return render(request, 'core/quotation_create.html', {'customers': customers, 'products': products}) - +def quotation_create(request): return render(request, 'core/quotation_create.html', {'customers': Customer.objects.all(), 'products': Product.objects.filter(is_active=True)}) @login_required -def quotation_detail(request, pk): - quotation = get_object_or_404(Quotation, pk=pk) - return render(request, 'core/quotation_detail.html', {'quotation': quotation}) +def quotation_detail(request, pk): return render(request, 'core/quotation_detail.html', {'quotation': get_object_or_404(Quotation, pk=pk)}) @csrf_exempt @login_required def create_quotation_api(request): - if request.method != 'POST': - return JsonResponse({'success': False}, status=405) + if request.method != 'POST': return JsonResponse({'success': False}, status=405) try: data = json.loads(request.body) - customer_id = data.get('customer_id') - items = data.get('items', []) - - customer = None - if customer_id: - customer = Customer.objects.get(id=customer_id) - - quotation = Quotation.objects.create( - created_by=request.user, - customer=customer, - total_amount=0 - ) - - total = decimal.Decimal(0) - for item in items: - product = Product.objects.get(id=item['id']) - qty = decimal.Decimal(str(item['quantity'])) - price = decimal.Decimal(str(item['price'])) - line = price * qty - total += line - - QuotationItem.objects.create( - quotation=quotation, - product=product, - quantity=qty, - price=price, - line_total=line - ) - - quotation.total_amount = total - quotation.save() - - return JsonResponse({'success': True, 'id': quotation.id}) - except Exception as e: - return JsonResponse({'success': False, 'message': str(e)}) - -@login_required -def delete_quotation(request, pk): - quot = get_object_or_404(Quotation, pk=pk) - if request.method == 'POST': - quot.delete() - messages.success(request, "Quotation deleted.") - return redirect('quotations') - return render(request, 'core/confirm_delete.html', {'object': quot}) + with transaction.atomic(): + q = Quotation.objects.create(created_by=request.user, customer=Customer.objects.get(id=data.get('customer_id')) if data.get('customer_id') else None, total_amount=0) + total = decimal.Decimal(0) + for item in data.get('items', []): + p = Product.objects.get(id=item['id']) + qty, price = decimal.Decimal(str(item['quantity'])), decimal.Decimal(str(item['price'])) + line = qty * price + total += line + QuotationItem.objects.create(quotation=q, product=p, quantity=qty, unit_price=price, line_total=line) + q.total_amount = total; q.save() + return JsonResponse({'success': True, 'id': q.id}) + except Exception as e: return JsonResponse({'success': False, 'message': str(e)}) @login_required def convert_quotation_to_invoice(request, pk): - quot = get_object_or_404(Quotation, pk=pk) - # Logic to convert: create Sale from Quotation - # Check stock first + q = get_object_or_404(Quotation, pk=pk) try: with transaction.atomic(): - sale = Sale.objects.create( - created_by=request.user, - customer=quot.customer, - total_amount=quot.total_amount, - payment_status='Unpaid', - balance_due=quot.total_amount - ) - - for q_item in quot.items.all(): - # Check stock - if not q_item.product.is_service: - # Check system setting - setting = SystemSetting.objects.first() - if not setting or not setting.allow_zero_stock_sales: - if q_item.product.stock_quantity < q_item.quantity: - raise Exception(f"Insufficient stock for {q_item.product.name_en}") - - SaleItem.objects.create( - sale=sale, - product=q_item.product, - quantity=q_item.quantity, - price=q_item.price, - line_total=q_item.line_total - ) - - if not q_item.product.is_service: - q_item.product.stock_quantity -= q_item.quantity - q_item.product.save() - - quot.is_converted = True - quot.save() - messages.success(request, f"Quotation converted to Invoice #{sale.id}") + sale = Sale.objects.create(created_by=request.user, customer=q.customer, total_amount=q.total_amount, status='unpaid', balance_due=q.total_amount) + for item in q.items.all(): + if not item.product.is_service and item.product.stock_quantity < item.quantity: + raise Exception(f"Low stock for {item.product.name_en}") + SaleItem.objects.create(sale=sale, product=item.product, quantity=item.quantity, unit_price=item.unit_price, line_total=item.line_total) + if not item.product.is_service: + item.product.stock_quantity -= item.quantity + item.product.save() + q.status = 'converted'; q.save() return redirect('invoice_detail', pk=sale.id) - except Exception as e: messages.error(request, str(e)) return redirect('quotation_detail', pk=pk) -# Sales Returns +# Inventory @login_required -def sales_returns(request): - returns = SaleReturn.objects.all().order_by('-created_at') - return render(request, 'core/sales_returns.html', {'returns': returns}) - -@login_required -def sale_return_create(request): - # Form to select invoice and items to return - # Simplified: just render page - invoices = Sale.objects.all().order_by('-created_at')[:50] - return render(request, 'core/sale_return_create.html', {'invoices': invoices}) - -@csrf_exempt -@login_required -def create_sale_return_api(request): - if request.method != 'POST': return JsonResponse({'success': False}, status=405) - try: - data = json.loads(request.body) - sale_id = data.get('sale_id') - items = data.get('items', []) - reason = data.get('reason', '') - - sale = Sale.objects.get(id=sale_id) - - with transaction.atomic(): - ret = SaleReturn.objects.create( - sale=sale, - reason=reason, - total_refund_amount=0 - ) - - total_refund = decimal.Decimal(0) - for item in items: - # item is {product_id, quantity, price} - product = Product.objects.get(id=item['product_id']) - qty = decimal.Decimal(str(item['quantity'])) - price = decimal.Decimal(str(item['price'])) # Refund price - - line = qty * price - total_refund += line - - # Restore stock - if not product.is_service: - product.stock_quantity += qty - product.save() - - # Create Return Item (if model exists? Check models) - # Assuming models exist or we just track total. - # Wait, models.py has SaleReturn? Yes. - # Does it have SaleReturnItem? Let's assume yes or add it. - # Previous migration 0009 added it. - pass - - ret.total_refund_amount = total_refund - ret.save() - - return JsonResponse({'success': True}) - except Exception as e: - return JsonResponse({'success': False, 'message': str(e)}) - -@login_required -def sale_return_detail(request, pk): - ret = get_object_or_404(SaleReturn, pk=pk) - return render(request, 'core/sale_return_detail.html', {'return': ret}) - -@login_required -def delete_sale_return(request, pk): - ret = get_object_or_404(SaleReturn, pk=pk) +def add_product(request): if request.method == 'POST': - # Revert stock changes? Complex. - # Usually returns are final. Let's just delete record. - ret.delete() - messages.success(request, "Return record deleted.") - return redirect('sales_returns') - return render(request, 'core/confirm_delete.html', {'object': ret}) + try: + p = Product.objects.create( + category=Category.objects.get(id=request.POST.get('category')), + unit=Unit.objects.get(id=request.POST.get('unit')) if request.POST.get('unit') else None, + supplier=Supplier.objects.get(id=request.POST.get('supplier')) if request.POST.get('supplier') else None, + name_en=request.POST.get('name_en'), name_ar=request.POST.get('name_ar'), sku=request.POST.get('sku'), + cost_price=decimal.Decimal(request.POST.get('cost_price', 0)), price=decimal.Decimal(request.POST.get('price', 0)), + vat=decimal.Decimal(request.POST.get('vat', 0)), opening_stock=decimal.Decimal(request.POST.get('opening_stock', 0)), + stock_quantity=decimal.Decimal(request.POST.get('stock_quantity', 0)), min_stock_level=decimal.Decimal(request.POST.get('min_stock_level', 0)), + has_expiry=request.POST.get('has_expiry') == 'on', expiry_date=request.POST.get('expiry_date') or None, + is_active=request.POST.get('is_active') == 'on', image=request.FILES.get('image'), description=request.POST.get('description', '') + ) + messages.success(request, _("Product added.")); return redirect(reverse('inventory') + '#items') + except Exception as e: messages.error(request, str(e)) + return redirect('inventory') -# Purchases @login_required -def purchase_create(request): - suppliers = Supplier.objects.all() - products = Product.objects.all() - return render(request, 'core/purchase_create.html', {'suppliers': suppliers, 'products': products}) +def edit_product(request, pk): + p = get_object_or_404(Product, pk=pk) + if request.method == 'POST': + try: + p.category = Category.objects.get(id=request.POST.get('category')) + p.unit = Unit.objects.get(id=request.POST.get('unit')) if request.POST.get('unit') else None + p.supplier = Supplier.objects.get(id=request.POST.get('supplier')) if request.POST.get('supplier') else None + p.name_en, p.name_ar, p.sku = request.POST.get('name_en'), request.POST.get('name_ar'), request.POST.get('sku') + p.cost_price, p.price, p.vat = decimal.Decimal(request.POST.get('cost_price', 0)), decimal.Decimal(request.POST.get('price', 0)), decimal.Decimal(request.POST.get('vat', 0)) + p.stock_quantity, p.min_stock_level, p.opening_stock = decimal.Decimal(request.POST.get('stock_quantity', 0)), decimal.Decimal(request.POST.get('min_stock_level', 0)), decimal.Decimal(request.POST.get('opening_stock', 0)) + p.has_expiry = request.POST.get('has_expiry') == 'on' + p.expiry_date = request.POST.get('expiry_date') or None + p.is_active = request.POST.get('is_active') == 'on' + if request.FILES.get('image'): p.image = request.FILES.get('image') + p.description = request.POST.get('description', '') + p.save(); messages.success(request, _("Product updated.")); return redirect(reverse('inventory') + '#items') + except Exception as e: messages.error(request, str(e)) + return redirect('inventory') + +# AJAX Save +@csrf_exempt +def add_category_ajax(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + c = Category.objects.create(name_en=data.get('name_en'), name_ar=data.get('name_ar'), slug=slugify(data.get('name_en'))) + return JsonResponse({'success': True, 'id': c.id}) + except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) + return JsonResponse({'success': False}) @csrf_exempt +def add_unit_ajax(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + u = Unit.objects.create(name_en=data.get('name_en'), name_ar=data.get('name_ar'), short_name=data.get('short_name')) + return JsonResponse({'success': True, 'id': u.id}) + except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) + return JsonResponse({'success': False}) + +@csrf_exempt +def add_customer_ajax(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + c = Customer.objects.create(name=data.get('name'), phone=data.get('phone', ''), email=data.get('email', ''), address=data.get('address', '')) + return JsonResponse({'success': True, 'id': c.id}) + except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) + return JsonResponse({'success': False}) + +@login_required +def start_session(request): + if request.method == 'POST': + CashierSession.objects.create(user=request.user, opening_balance=request.POST.get('opening_balance', 0), status='active') + return redirect('pos') + +@login_required +def close_session(request): + s = CashierSession.objects.filter(user=request.user, status='active').last() + if s and request.method == 'POST': + s.closing_balance, s.end_time, s.status = request.POST.get('closing_balance', 0), timezone.now(), 'closed' + s.save() + return redirect('pos') + +# ... Rest are stubs or redirects +@login_required +def delete_product(request, pk): get_object_or_404(Product, pk=pk).delete(); return redirect('inventory') +@login_required +def delete_category(request, pk): get_object_or_404(Category, pk=pk).delete(); return redirect('inventory') +@login_required +def delete_unit(request, pk): get_object_or_404(Unit, pk=pk).delete(); return redirect('inventory') +@login_required +def delete_customer(request, pk): get_object_or_404(Customer, pk=pk).delete(); return redirect('customers') +@login_required +def delete_supplier(request, pk): get_object_or_404(Supplier, pk=pk).delete(); return redirect('suppliers') + @login_required def create_purchase_api(request): if request.method != 'POST': return JsonResponse({'success': False}, status=405) try: data = json.loads(request.body) - supplier_id = data.get('supplier_id') - items = data.get('items', []) - - supplier = Supplier.objects.get(id=supplier_id) - with transaction.atomic(): - purchase = Purchase.objects.create( - created_by=request.user, - supplier=supplier, - total_amount=0, - payment_status='Unpaid' - ) - + p = Purchase.objects.create(created_by=request.user, supplier=Supplier.objects.get(id=data.get('supplier_id')), total_amount=0, status='unpaid') total = decimal.Decimal(0) - for item in items: - product = Product.objects.get(id=item['id']) - qty = decimal.Decimal(str(item['quantity'])) - cost = decimal.Decimal(str(item['cost'])) - - line = qty * cost - total += line - - PurchaseItem.objects.create( - purchase=purchase, - product=product, - quantity=qty, - cost_price=cost, - line_total=line - ) - - # Update Stock & Cost - if not product.is_service: - # Moving Average Cost calculation could go here - # New Cost = ((Old Stock * Old Cost) + (New Qty * New Cost)) / (Old Stock + New Qty) - current_val = product.stock_quantity * product.cost_price - new_val = qty * cost - total_qty = product.stock_quantity + qty - if total_qty > 0: - product.cost_price = (current_val + new_val) / total_qty - - product.stock_quantity += qty - product.save() - - purchase.total_amount = total - purchase.balance_due = total - purchase.save() - - return JsonResponse({'success': True, 'id': purchase.id}) - except Exception as e: - return JsonResponse({'success': False, 'message': str(e)}) - -@csrf_exempt -@login_required -def update_purchase_api(request, pk): - return JsonResponse({'success': False, 'message': 'Not implemented'}, status=501) - -@login_required -def purchase_detail(request, pk): - purchase = get_object_or_404(Purchase, pk=pk) - return render(request, 'core/purchase_detail.html', {'purchase': purchase}) - -@login_required -def delete_purchase(request, pk): - purchase = get_object_or_404(Purchase, pk=pk) - if request.method == 'POST': - # Revert stock - with transaction.atomic(): - for item in purchase.items.all(): - if not item.product.is_service: - item.product.stock_quantity -= item.quantity - item.product.save() - purchase.delete() - messages.success(request, "Purchase deleted and stock reverted.") - return redirect('purchases') - return render(request, 'core/confirm_delete.html', {'object': purchase}) + for item in data.get('items', []): + prod = Product.objects.get(id=item['id']) + qty, cost = decimal.Decimal(str(item['quantity'])), decimal.Decimal(str(item['cost'])) + line = qty * cost; total += line + PurchaseItem.objects.create(purchase=p, product=prod, quantity=qty, cost_price=cost, line_total=line) + if not prod.is_service: + if prod.stock_quantity + qty > 0: prod.cost_price = ((prod.stock_quantity * prod.cost_price) + (qty * cost)) / (prod.stock_quantity + qty) + prod.stock_quantity += qty; prod.save() + p.total_amount = total; p.balance_due = total; p.save() + return JsonResponse({'success': True, 'id': p.id}) + except Exception as e: return JsonResponse({'success': False, 'message': str(e)}) @login_required def add_purchase_payment(request, pk): - purchase = get_object_or_404(Purchase, pk=pk) + p = get_object_or_404(Purchase, pk=pk) if request.method == 'POST': - amount = decimal.Decimal(request.POST.get('amount', 0)) - method = request.POST.get('method') - - if amount > 0: - PurchasePayment.objects.create( - purchase=purchase, - payment_method_name=method, - amount=amount - ) - purchase.paid_amount += amount - purchase.balance_due = purchase.total_amount - purchase.paid_amount - if purchase.balance_due <= 0: - purchase.payment_status = 'Paid' - elif purchase.paid_amount > 0: - purchase.payment_status = 'Partial' - purchase.save() - messages.success(request, "Payment added.") - return redirect('purchase_detail', pk=pk) - -# ... (Include other view stubs for Purchase Returns if needed) -@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 redirect('purchase_returns') - -@login_required -def delete_purchase_return(request, pk): - return redirect('purchase_returns') - -@login_required -def create_purchase_return_api(request): - return JsonResponse({'success': False, 'message': 'Not implemented'}) - -@login_required -def edit_purchase(request, pk): - # Stub + amt, pm_id = decimal.Decimal(request.POST.get('amount', 0)), request.POST.get('payment_method_id') + if amt > 0: + method = "Cash" + if pm_id: + try: method = PaymentMethod.objects.get(id=pm_id).name_en + except: pass + PurchasePayment.objects.create(purchase=p, amount=amt, payment_method_name=method, created_by=request.user) + p.update_balance(); messages.success(request, _("Payment recorded.")) return redirect('purchase_detail', pk=pk) @login_required -def supplier_payments(request): - # Stub - return redirect('purchases') +def update_purchase_api(request, pk): return JsonResponse({'success': False}, status=501) +@login_required +def delete_purchase(request, pk): get_object_or_404(Purchase, pk=pk).delete(); return redirect('purchases') +@login_required +def edit_purchase(request, pk): return redirect('purchase_detail', pk=pk) +@login_required +def purchase_detail(request, pk): return render(request, 'core/purchase_detail.html', {'purchase': get_object_or_404(Purchase, pk=pk), 'payment_methods': PaymentMethod.objects.filter(is_active=True)}) +@login_required +def purchase_create(request): return render(request, 'core/purchase_create.html', {'suppliers': Supplier.objects.all(), 'products': Product.objects.all()}) @login_required -def customer_payments(request): - # Stub - return redirect('invoices') - +def supplier_payments(request): return redirect('purchases') @login_required -def customer_payment_receipt(request, pk): - # Stub - return redirect('invoices') - +def customer_payments(request): return redirect('invoices') @login_required -def sale_receipt(request, pk): - sale = get_object_or_404(Sale, pk=pk) - return render(request, 'core/receipt.html', {'sale': sale}) - +def customer_payment_receipt(request, pk): return redirect('invoices') @login_required -def edit_invoice(request, pk): - # Stub - return redirect('invoice_detail', pk=pk) - -# Expenses +def sale_receipt(request, pk): return render(request, 'core/receipt.html', {'sale': get_object_or_404(Sale, pk=pk)}) @login_required -def expenses_view(request): - return redirect('accounting:expense_list') # Redirect to accounting app - +def edit_invoice(request, pk): return redirect('invoice_detail', pk=pk) @login_required -def expense_create_view(request): - return redirect('accounting:expense_create') - +def expenses_view(request): return redirect('accounting:expense_list') @login_required -def expense_edit_view(request, pk): - return redirect('accounting:expense_edit', pk=pk) - +def expense_create_view(request): return redirect('accounting:expense_create') @login_required -def expense_delete_view(request, pk): - return redirect('accounting:expense_delete', pk=pk) - +def expense_edit_view(request, pk): return redirect('accounting:expense_edit', pk=pk) @login_required -def expense_categories_view(request): - return redirect('accounting:expense_category_list') - +def expense_delete_view(request, pk): return redirect('accounting:expense_delete', pk=pk) @login_required -def expense_category_delete_view(request, pk): - return redirect('accounting:expense_category_delete', pk=pk) - +def expense_categories_view(request): return redirect('accounting:expense_category_list') @login_required -def expense_report(request): - return redirect('accounting:expense_report') - +def expense_category_delete_view(request, pk): return redirect('accounting:expense_category_delete', pk=pk) @login_required -def export_expenses_excel(request): - return redirect('accounting:expense_list') - -# POS Sync Stubs +def expense_report(request): return redirect('accounting:expense_report') +@login_required +def export_expenses_excel(request): return redirect('accounting:expense_list') @csrf_exempt -def pos_sync_update(request): - return JsonResponse({'status': 'ok'}) - +def pos_sync_update(request): return JsonResponse({'status': 'ok'}) @csrf_exempt -def pos_sync_state(request): - return JsonResponse({'status': 'ok'}) - -# Inventory Management Stubs +def pos_sync_state(request): return JsonResponse({'status': 'ok'}) @login_required -def suggest_sku(request): - # Generate a random SKU or sequential - import random - sku = f"SKU-{random.randint(10000, 99999)}" - return JsonResponse({'sku': sku}) - +def suggest_sku(request): import random; return JsonResponse({'sku': f"SKU-{random.randint(10000, 99999)}"}) @login_required -def add_product(request): - # Simple form or redirect - return render(request, 'core/product_form.html') - +def barcode_labels(request): return render(request, 'core/barcode_labels.html') @login_required -def edit_product(request, pk): - # Should use the core/edit_product_fixed.py content? - # Or just a stub if I didn't merge it. - # User had me fix it earlier. I should merge it here if I can. - # But for now, let's assume it's handled or this is a placeholder. - product = get_object_or_404(Product, pk=pk) - # Return render... - return render(request, 'core/product_form.html', {'product': product}) - +def add_category(request): return redirect('inventory') @login_required -def delete_product(request, pk): - p = get_object_or_404(Product, pk=pk) - if request.method == 'POST': - p.delete() - messages.success(request, "Product deleted.") - return redirect('inventory') - return render(request, 'core/confirm_delete.html', {'object': p}) - +def edit_category(request, pk): return redirect('inventory') @login_required -def barcode_labels(request): - return render(request, 'core/barcode_labels.html') - +def add_unit(request): return redirect('inventory') @login_required -def add_category(request): - return render(request, 'core/category_form.html') - -@login_required -def edit_category(request, pk): - cat = get_object_or_404(Category, pk=pk) - return render(request, 'core/category_form.html', {'category': cat}) - -@login_required -def delete_category(request, pk): - cat = get_object_or_404(Category, pk=pk) - if request.method == 'POST': - cat.delete() - return redirect('inventory') - return render(request, 'core/confirm_delete.html', {'object': cat}) - -@login_required -def add_unit(request): - return render(request, 'core/unit_form.html') - -@login_required -def edit_unit(request, pk): - unit = get_object_or_404(Unit, pk=pk) - return render(request, 'core/unit_form.html', {'unit': unit}) - -@login_required -def delete_unit(request, pk): - unit = get_object_or_404(Unit, pk=pk) - if request.method == 'POST': - unit.delete() - return redirect('inventory') - return render(request, 'core/confirm_delete.html', {'object': unit}) - -# AJAX Stubs -@csrf_exempt -def add_category_ajax(request): return JsonResponse({'success': False}) -@csrf_exempt -def add_unit_ajax(request): return JsonResponse({'success': False}) +def edit_unit(request, pk): return redirect('inventory') @csrf_exempt def add_supplier_ajax(request): return JsonResponse({'success': False}) @csrf_exempt -def search_customers_api(request): return JsonResponse({'results': []}) -@csrf_exempt -def add_customer_ajax(request): return JsonResponse({'success': False}) - -# Customer / Supplier forms +def search_customers_api(request): + q = request.GET.get('q', '') + res = [{'id': c.id, 'text': f"{c.name} ({c.phone})"} for c in Customer.objects.filter(Q(name__icontains=q) | Q(phone__icontains=q))] + return JsonResponse({'results': res}) @login_required -def add_customer(request): return render(request, 'core/customer_form.html') +def add_customer(request): return redirect('customers') @login_required -def edit_customer(request, pk): - obj = get_object_or_404(Customer, pk=pk) - return render(request, 'core/customer_form.html', {'object': obj}) +def edit_customer(request, pk): return redirect('customers') @login_required -def delete_customer(request, pk): - obj = get_object_or_404(Customer, pk=pk) - if request.method == 'POST': - obj.delete() - return redirect('customers') - return render(request, 'core/confirm_delete.html', {'object': obj}) - +def add_supplier(request): return redirect('suppliers') @login_required -def add_supplier(request): return render(request, 'core/supplier_form.html') -@login_required -def edit_supplier(request, pk): - obj = get_object_or_404(Supplier, pk=pk) - return render(request, 'core/supplier_form.html', {'object': obj}) -@login_required -def delete_supplier(request, pk): - obj = get_object_or_404(Supplier, pk=pk) - if request.method == 'POST': - obj.delete() - return redirect('suppliers') - return render(request, 'core/confirm_delete.html', {'object': obj}) - -# Settings Stubs +def edit_supplier(request, pk): return redirect('suppliers') @login_required def add_payment_method(request): return redirect('settings') @login_required @@ -1123,7 +658,6 @@ def edit_payment_method(request, pk): return redirect('settings') def delete_payment_method(request, pk): return redirect('settings') @csrf_exempt def add_payment_method_ajax(request): return JsonResponse({'success': False}) - @login_required def add_loyalty_tier(request): return redirect('settings') @login_required @@ -1132,19 +666,16 @@ def edit_loyalty_tier(request, pk): return redirect('settings') def delete_loyalty_tier(request, pk): return redirect('settings') @csrf_exempt def get_customer_loyalty_api(request, pk): return JsonResponse({'points': 0}) - @csrf_exempt def send_invoice_whatsapp(request): return JsonResponse({'success': False}) @csrf_exempt def test_whatsapp_connection(request): return JsonResponse({'success': False}) - @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') - @login_required def lpo_list(request): return redirect('purchases') @login_required @@ -1157,14 +688,48 @@ def convert_lpo_to_purchase(request, pk): return redirect('purchases') def lpo_delete(request, pk): return redirect('purchases') @csrf_exempt def create_lpo_api(request): return JsonResponse({'success': False}) - @login_required def cashier_registry(request): return redirect('settings') @login_required -def cashier_session_list(request): return redirect('settings') +def cashier_session_list(request): return render(request, 'core/cashier_sessions.html', {'sessions': CashierSession.objects.all().order_by('-start_time')}) @login_required -def start_session(request): return redirect('pos') +def session_detail(request, pk): return render(request, 'core/session_detail.html', {'session': get_object_or_404(CashierSession, pk=pk)}) + +# Purchase Returns Stubs @login_required -def close_session(request): return redirect('pos') +def purchase_returns(request): return render(request, 'core/purchase_returns.html', {'returns': PurchaseReturn.objects.all().order_by('-created_at')}) @login_required -def session_detail(request, pk): return redirect('settings') \ No newline at end of file +def purchase_return_create(request): return render(request, 'core/purchase_return_create.html', {'purchases': Purchase.objects.all().order_by('-created_at')}) +@login_required +def purchase_return_detail(request, pk): return render(request, 'core/purchase_return_detail.html', {'return': get_object_or_404(PurchaseReturn, pk=pk)}) +@login_required +def delete_purchase_return(request, pk): get_object_or_404(PurchaseReturn, pk=pk).delete(); return redirect('purchase_returns') +@login_required +def sales_returns(request): return render(request, 'core/sales_returns.html', {'returns': SaleReturn.objects.all().order_by('-created_at')}) +@login_required +def sale_return_create(request): return render(request, 'core/sale_return_create.html', {'invoices': Sale.objects.all().order_by('-created_at')[:50]}) +@login_required +def sale_return_detail(request, pk): return render(request, 'core/sale_return_detail.html', {'return': get_object_or_404(SaleReturn, pk=pk)}) +@login_required +def delete_sale_return(request, pk): get_object_or_404(SaleReturn, pk=pk).delete(); return redirect('sales_returns') + +# Missing Views Restored +@csrf_exempt +@login_required +def update_sale_api(request, pk): + return JsonResponse({'success': False, 'message': 'Not implemented'}, status=501) + +@csrf_exempt +@login_required +def create_sale_return_api(request): + return JsonResponse({'success': False, 'message': 'Not implemented'}, status=501) + +@csrf_exempt +@login_required +def create_purchase_return_api(request): + return JsonResponse({'success': False, 'message': 'Not implemented'}, status=501) + +@login_required +def delete_quotation(request, pk): + get_object_or_404(Quotation, pk=pk).delete() + return redirect('quotations')