from django.contrib.auth.decorators import login_required from accounts.decorators import permission_required from django.shortcuts import render, get_object_or_404, redirect from projects.models import ProjectMembership from .models import InventoryItem, InventoryCategory, InventoryLocation, ProjectInventoryItem, ShoppingItem, Category, Supplier from django.contrib.auth.models import User from .forms import InventoryItemForm, ShoppingItemForm from django.db.models import Sum, Value, F from django_tables2 import RequestConfig from .tables import ProjectInventoryItemTable, ShoppingItemTable, InventoryAddTable, InventoryGlobalTable from django.db.models.functions import Coalesce import json from collections import Counter from django.utils import timezone @login_required @permission_required("projects_inventory_inventory_view") def inventory_overview(request, project_id): membership = get_object_or_404( ProjectMembership, user=request.user, project_id=project_id ) project = membership.project items = ( ProjectInventoryItem.objects .select_related( "item", "item__category", "item__location", "item__responsible", ) .filter(project=project) .order_by("item__name") ) table = ProjectInventoryItemTable(items) # Zusatzdaten für TemplateColumns table.project = project RequestConfig( request, paginate=False ).configure(table) shopping_items = ( ShoppingItem.objects .select_related( "category", "supplier" ) .filter(project=project) .order_by("name") ) shopping_table = ShoppingItemTable(shopping_items) shopping_table.project = project shopping_table.categories = Category.objects.order_by("name") shopping_table.suppliers = Supplier.objects.order_by("name") return render(request, "inventory/overview.html", { "project": project, "table": table, "shopping_table": shopping_table, }) @login_required @permission_required("projects_inventory_shopping_add") def shopping_add(request, project_id): membership = get_object_or_404( ProjectMembership, user=request.user, project_id=project_id ) project = membership.project if request.method == "POST": form = ShoppingItemForm(request.POST) if form.is_valid(): item = form.save(commit=False) item.project = project item.save() return redirect( "inventory:overview", project_id=project.id ) else: form = ShoppingItemForm() return render( request, "inventory/shopping_add.html", { "project": project, "form": form, } ) @login_required @permission_required("projects_inventory_inventory_add") def inventory_add(request, project_id): membership = get_object_or_404( ProjectMembership, user=request.user, project_id=project_id ) project = membership.project items = ( InventoryItem.objects .select_related( "category", "location" ) .annotate( used_quantity=Coalesce( Sum("projectinventoryitem__quantity"), Value(0) ) ) .annotate( available_quantity=F("quantity") - F("used_quantity") ) .filter( available_quantity__gt=0 ) .order_by("name") ) if request.method == "POST": for item in items: quantity = request.POST.get( f"item_{item.id}" ) if not quantity: continue quantity = int(quantity) if quantity <= 0: continue if quantity > item.available_quantity: continue project_item, created = ProjectInventoryItem.objects.get_or_create( project=project, item=item, defaults={ "quantity": quantity, "status": ProjectInventoryItem.Status.OPEN, } ) if not created: project_item.quantity += quantity project_item.save() return redirect( "inventory:overview", project_id=project.id ) table = InventoryAddTable(items) RequestConfig( request, paginate=False ).configure(table) return render( request, "inventory/add.html", { "project": project, "table": table, } ) @login_required @permission_required("inventory_view") def inventory_global_overview(request): items = ( InventoryItem.objects .select_related( "category", "location", "responsible" ) .annotate( used_quantity=Coalesce( Sum("projectinventoryitem__quantity"), Value(0) ) ) .annotate( available_quantity=F("quantity") - F("used_quantity") ) .order_by("name") ) for item in items: used_quantity = ( ProjectInventoryItem.objects .filter(item=item) .aggregate(total=Sum("quantity")) .get("total") or 0 ) item.available_quantity = max( 0, item.quantity - used_quantity ) table = InventoryGlobalTable(items) table.categories = InventoryCategory.objects.all() table.locations = InventoryLocation.objects.all() table.users = User.objects.order_by("username") RequestConfig( request, paginate=False ).configure(table) return render( request, "inventory/global/overview.html", { "table": table, } ) @login_required @permission_required("inventory_add") def inventory_global_add(request): if request.method == "POST": form = InventoryItemForm(request.POST) if form.is_valid(): form.save() return redirect("inventory_global:overview") else: form = InventoryItemForm() return render(request, "inventory/global/add.html", {"form": form,}) @login_required @permission_required("projects_inventory_inventory_delete") def inventory_delete(request, project_id, item_id): membership = get_object_or_404(ProjectMembership, user=request.user, project_id=project_id) item = get_object_or_404(ProjectInventoryItem, id=item_id, project=membership.project) if request.method == "POST": item.delete() return redirect("inventory:overview", project_id=project_id) @login_required @permission_required("projects_inventory_shopping_delete") def shopping_delete(request, project_id, item_id): membership = get_object_or_404(ProjectMembership, user=request.user, project_id=project_id) item = get_object_or_404(ShoppingItem, id=item_id, project=membership.project) if request.method == "POST": item.delete() return redirect("inventory:overview", project_id=project_id) @login_required @permission_required("inventory_delete") def inventory_global_delete(request, item_id): item = get_object_or_404( InventoryItem, id=item_id ) if request.method == "POST": item.delete() return redirect("inventory_global:overview") @login_required @permission_required("projects_inventory_inventory_edit") def inventory_edit(request, project_id, item_id): membership = get_object_or_404(ProjectMembership, user=request.user, project_id=project_id) item = get_object_or_404(ProjectInventoryItem, id=item_id, project=membership.project) if request.method == "POST": item.quantity = request.POST.get("quantity") or 0 item.status = request.POST.get("status") item.save() return redirect("inventory:overview", project_id=project_id) return render( request, "inventory/edit.html", { "project": membership.project, "item": item, } ) @login_required @permission_required("projects_inventory_shopping_edit") def shopping_edit(request, project_id, item_id): membership = get_object_or_404(ProjectMembership, user=request.user, project_id=project_id) item = get_object_or_404(ShoppingItem, id=item_id, project=membership.project) if request.method == "POST": item.name = request.POST.get("name") item.description = request.POST.get("description", "") item.quantity = request.POST.get("quantity") or 1 item.price = request.POST.get("price") or None item.category_id = request.POST.get("category") or None item.supplier_id = request.POST.get("supplier") or None item.save() return redirect("inventory:overview", project_id=project_id) return render( request, "inventory/shopping_edit.html", { "project": membership.project, "item": item, "categories": Category.objects.all(), "suppliers": Supplier.objects.all(), } ) @login_required @permission_required("inventory_edit") def inventory_global_edit(request, item_id): item = get_object_or_404( InventoryItem, id=item_id ) if request.method == "POST": item.name = request.POST.get("name") item.quantity = request.POST.get("quantity") item.article_number = request.POST.get("article_number") item.description = request.POST.get("description", "") item.category_id = request.POST.get("category") or None item.location_id = request.POST.get("location") or None item.responsible_id = request.POST.get("responsible") or None item.save() return redirect("inventory_global:overview") return render( request, "inventory/global/edit.html", { "item": item, "categories": InventoryCategory.objects.all(), "locations": InventoryLocation.objects.all(), "users": User.objects.order_by("username"), } ) @login_required @permission_required("projects_inventory_shopping_to_expense") def shopping_to_expense(request, project_id): ids = request.POST.getlist("selection") items = ShoppingItem.objects.filter(pk__in=ids) data = [] total_amount = 0 categories = [] suppliers = [] for item in items: total_amount += (item.quantity * item.price) if item.category: categories.append(item.category.id) if item.supplier: suppliers.append(item.supplier.id) data.append({ "name": item.name, "quantity": item.quantity, "price": str(item.price), "supplier": str(item.supplier) if item.supplier else None, "category": str(item.category) if item.category else None, "description": item.description, }) most_category = ( Counter(categories).most_common(1)[0][0] if categories else None ) most_supplier = ( Counter(suppliers).most_common(1)[0][0] if suppliers else None ) request.session["expense_initial"] = { "name": "Einkauf vom " + timezone.now().strftime("%d.%m.%Y"), "amount": str(total_amount), "date": timezone.now().strftime("%Y-%m-%dT%H:%M"), "category": most_category, "supplier": most_supplier, "note": json.dumps( data, ensure_ascii=False, indent=2 ) } request.session["shopping_item_ids"] = list( items.values_list("id", flat=True) ) return redirect( "finance:add_expense", project_id=project_id )