38323-vm/core/views.py
2026-02-09 17:24:55 +00:00

289 lines
12 KiB
Python

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')