from django.shortcuts import render, get_object_or_404, redirect from django.http import JsonResponse from django.db import transaction from django.db.models import Sum, F from django.utils import timezone from django.contrib import messages from django.contrib.auth import login, logout, authenticate from django.contrib.auth.forms import AuthenticationForm, UserCreationForm from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.models import User from .models import Ingredient, MenuItem, MenuItemIngredient, Order, OrderItem, UserProfile import json def is_manager(user): return user.is_authenticated and hasattr(user, 'profile') and user.profile.role == 'manager' def is_cashier(user): return user.is_authenticated and hasattr(user, 'profile') and user.profile.role == 'cashier' def manager_required(view_func): return user_passes_test(is_manager, login_url='login')(view_func) def cashier_or_manager_required(view_func): return user_passes_test(lambda u: is_manager(u) or is_cashier(u), login_url='login')(view_func) def login_view(request): if request.method == 'POST': form = AuthenticationForm(request, data=request.POST) if form.is_valid(): user = form.get_user() login(request, user) if is_manager(user): return redirect('dashboard') return redirect('pos') else: form = AuthenticationForm() return render(request, 'core/login.html', {'form': form}) def logout_view(request): logout(request) return redirect('login') def home(request): """Redirect home to login or dashboard/pos based on role.""" if request.user.is_authenticated: if is_manager(request.user): return redirect('dashboard') return redirect('pos') return render(request, "core/index.html") @cashier_or_manager_required def pos_view(request): """Cashier POS interface.""" menu_items = MenuItem.objects.filter(is_active=True) return render(request, "core/pos.html", {"menu_items": menu_items}) @cashier_or_manager_required def create_order(request): """Handle order creation and stock deduction.""" if request.method == "POST": try: data = json.loads(request.body) cart = data.get("cart", []) notes = data.get("notes", "") if not cart: return JsonResponse({"success": False, "error": "Cart is empty"}, status=400) with transaction.atomic(): order = Order.objects.create(customer_notes=notes) total_price = 0 for item in cart: menu_item = get_object_or_404(MenuItem, id=item["id"]) quantity = int(item["quantity"]) # Deduct stock for recipe_item in menu_item.ingredients.all(): required_qty = recipe_item.quantity_required * quantity ingredient = recipe_item.ingredient if ingredient.stock_quantity < required_qty: raise Exception(f"Insufficient stock for {ingredient.name}") ingredient.stock_quantity = F('stock_quantity') - required_qty ingredient.save() # Create OrderItem OrderItem.objects.create( order=order, menu_item=menu_item, quantity=quantity, price_at_order=menu_item.price ) total_price += menu_item.price * quantity order.total_price = total_price order.save() return JsonResponse({"success": True, "order_number": order.order_number}) except Exception as e: return JsonResponse({"success": False, "error": str(e)}, status=400) return JsonResponse({"success": False, "error": "Invalid request"}, status=405) @cashier_or_manager_required def receipt_view(request, order_number): """Printable receipt view.""" order = get_object_or_404(Order, order_number=order_number) return render(request, "core/receipt.html", {"order": order}) @manager_required def dashboard_view(request): """Manager dashboard for stock and reports.""" ingredients = Ingredient.objects.all() # Summary of last 30 orders orders = Order.objects.prefetch_related('items__menu_item').order_by('-created_at')[:50] total_sales = Order.objects.aggregate(Sum('total_price'))['total_price__sum'] or 0 return render(request, "core/dashboard.html", { "ingredients": ingredients, "orders": orders, "total_sales": total_sales, }) @manager_required def manage_users(request): """Manager only: Create and view accounts.""" users = User.objects.all().select_related('profile') if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') role = request.POST.get('role') if username and password and role: if User.objects.filter(username=username).exists(): messages.error(request, f"User {username} already exists.") else: user = User.objects.create_user(username=username, password=password) profile, created = UserProfile.objects.get_or_create(user=user) profile.role = role profile.save() messages.success(request, f"User {username} created as {role}.") return redirect('manage_users') else: messages.error(request, "Please fill all fields.") return render(request, 'core/user_management.html', {'users': users}) @manager_required def delete_user(request, user_id): """Manager only: Delete a user account.""" if request.user.id == user_id: messages.error(request, "You cannot delete your own account.") return redirect('manage_users') user = get_object_or_404(User, id=user_id) username = user.username user.delete() messages.success(request, f"User {username} has been deleted.") return redirect('manage_users') @manager_required def manage_ingredients(request): """Manager only: View and add ingredients.""" if request.method == 'POST': name = request.POST.get('name') stock_quantity = request.POST.get('stock_quantity', 0) unit = request.POST.get('unit', 'grams') if name: if Ingredient.objects.filter(name__iexact=name).exists(): messages.error(request, f"Ingredient '{name}' already exists.") else: Ingredient.objects.create( name=name, stock_quantity=stock_quantity, unit=unit ) messages.success(request, f"Ingredient '{name}' added successfully.") else: messages.error(request, "Ingredient name is required.") return redirect('dashboard') return redirect('dashboard') @manager_required def manage_menu(request): """Manager only: View and create menu items.""" menu_items = MenuItem.objects.all() ingredients = Ingredient.objects.all() if request.method == 'POST': name = request.POST.get('name') price = request.POST.get('price') description = request.POST.get('description', '') image_url = request.POST.get('image_url', '') is_active = request.POST.get('is_active') == 'on' # Handle ingredient IDs and quantities from the form ingredient_ids = request.POST.getlist('ingredients') quantities = request.POST.getlist('quantities') if name and price: try: with transaction.atomic(): menu_item = MenuItem.objects.create( name=name, price=price, description=description, image_url=image_url, is_active=is_active ) # Add ingredients if provided for i_id, qty in zip(ingredient_ids, quantities): if i_id and qty and float(qty) > 0: ingredient = get_object_or_404(Ingredient, id=i_id) MenuItemIngredient.objects.create( menu_item=menu_item, ingredient=ingredient, quantity_required=qty ) messages.success(request, f"Menu item '{name}' created successfully.") return redirect('manage_menu') except Exception as e: messages.error(request, f"Error creating menu item: {str(e)}") else: messages.error(request, "Name and price are required.") return render(request, 'core/menu_management.html', { 'menu_items': menu_items, 'ingredients': ingredients }) @manager_required def edit_menu_item(request, pk): """Manager only: Edit an existing menu item.""" menu_item = get_object_or_404(MenuItem, pk=pk) ingredients = Ingredient.objects.all() if request.method == 'POST': menu_item.name = request.POST.get('name') menu_item.price = request.POST.get('price') menu_item.description = request.POST.get('description', '') menu_item.image_url = request.POST.get('image_url', '') menu_item.is_active = request.POST.get('is_active') == 'on' ingredient_ids = request.POST.getlist('ingredients') quantities = request.POST.getlist('quantities') if menu_item.name and menu_item.price: try: with transaction.atomic(): menu_item.save() # Update ingredients: Clear existing and re-add menu_item.ingredients.all().delete() for i_id, qty in zip(ingredient_ids, quantities): if i_id and qty and float(qty) > 0: ingredient = get_object_or_404(Ingredient, id=i_id) MenuItemIngredient.objects.create( menu_item=menu_item, ingredient=ingredient, quantity_required=qty ) messages.success(request, f"Menu item '{menu_item.name}' updated successfully.") return redirect('manage_menu') except Exception as e: messages.error(request, f"Error updating menu item: {str(e)}") else: messages.error(request, "Name and price are required.") return render(request, 'core/edit_menu_item.html', { 'menu_item': menu_item, 'ingredients': ingredients, 'current_ingredients': menu_item.ingredients.all() }) @manager_required def toggle_menu_item_status(request, pk): """Manager only: Toggle menu item active/inactive status.""" menu_item = get_object_or_404(MenuItem, pk=pk) menu_item.is_active = not menu_item.is_active menu_item.save() status = "activated" if menu_item.is_active else "deactivated" messages.success(request, f"Menu item '{menu_item.name}' {status}.") return redirect('manage_menu')