Initial commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import (
|
||||
InventoryCategory,
|
||||
InventoryLocation,
|
||||
InventoryItem,
|
||||
ProjectInventoryItem,
|
||||
ShoppingItem,
|
||||
)
|
||||
|
||||
@admin.register(InventoryCategory)
|
||||
class InventoryCategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
@admin.register(InventoryLocation)
|
||||
class InventoryLocationAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
@admin.register(InventoryItem)
|
||||
class InventoryItemAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"name",
|
||||
"quantity",
|
||||
"category",
|
||||
"location",
|
||||
"responsible",
|
||||
"article_number",
|
||||
)
|
||||
list_filter = (
|
||||
"category",
|
||||
"location",
|
||||
)
|
||||
search_fields = (
|
||||
"name",
|
||||
"article_number",
|
||||
"description",
|
||||
)
|
||||
|
||||
|
||||
@admin.register(ProjectInventoryItem)
|
||||
class ProjectInventoryItemAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"project",
|
||||
"item",
|
||||
"quantity",
|
||||
"status",
|
||||
"created_at",
|
||||
)
|
||||
list_filter = (
|
||||
"status",
|
||||
"project",
|
||||
)
|
||||
search_fields = (
|
||||
"project__name",
|
||||
"item__name",
|
||||
)
|
||||
|
||||
|
||||
@admin.register(ShoppingItem)
|
||||
class ShoppingItemAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"name",
|
||||
"project",
|
||||
"quantity",
|
||||
"price",
|
||||
"category",
|
||||
"supplier",
|
||||
"created_at",
|
||||
)
|
||||
list_filter = (
|
||||
"project",
|
||||
"category",
|
||||
"supplier",
|
||||
)
|
||||
search_fields = (
|
||||
"name",
|
||||
"description",
|
||||
"project__name",
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class InventoryConfig(AppConfig):
|
||||
name = 'inventory'
|
||||
@@ -0,0 +1,43 @@
|
||||
from django import forms
|
||||
|
||||
from .models import InventoryItem, ShoppingItem
|
||||
from core.forms.base import BaseForm
|
||||
|
||||
|
||||
class InventoryItemForm(BaseForm):
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = [
|
||||
"name",
|
||||
"description",
|
||||
"quantity",
|
||||
"location",
|
||||
"article_number",
|
||||
"responsible",
|
||||
"category",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.apply_style()
|
||||
|
||||
|
||||
class ShoppingItemForm(BaseForm):
|
||||
|
||||
class Meta:
|
||||
model = ShoppingItem
|
||||
|
||||
fields = [
|
||||
"name",
|
||||
"description",
|
||||
"quantity",
|
||||
"supplier",
|
||||
"price",
|
||||
"category",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.apply_style()
|
||||
@@ -0,0 +1,90 @@
|
||||
# Generated by Django 6.0.6 on 2026-06-23 22:12
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InventoryCategory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, unique=True, verbose_name='Name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Inventarkategorie',
|
||||
'verbose_name_plural': 'Inventarkategorien',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InventoryLocation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, unique=True, verbose_name='Name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Inventarstandort',
|
||||
'verbose_name_plural': 'Inventarstandorte',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ShoppingItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('description', models.TextField(blank=True, verbose_name='Beschreibung')),
|
||||
('quantity', models.PositiveIntegerField(default=1, verbose_name='Anzahl')),
|
||||
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Preis')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Einkaufslistenartikel',
|
||||
'verbose_name_plural': 'Einkaufslistenartikel',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InventoryItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('description', models.TextField(blank=True, verbose_name='Beschreibung')),
|
||||
('quantity', models.PositiveIntegerField(default=0, verbose_name='Anzahl')),
|
||||
('article_number', models.CharField(blank=True, max_length=255, verbose_name='Artikelnummer')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.inventorycategory', verbose_name='Kategorie')),
|
||||
('responsible', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Verantwortlicher')),
|
||||
('location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.inventorylocation', verbose_name='Standort')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Inventarartikel',
|
||||
'verbose_name_plural': 'Inventarartikel',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProjectInventoryItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.PositiveIntegerField(default=1, verbose_name='Anzahl')),
|
||||
('status', models.CharField(choices=[('open', 'Offen'), ('packed', 'Eingepackt'), ('on_site', 'Vor Ort')], default='open', max_length=20, verbose_name='Status')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.inventoryitem', verbose_name='Artikel')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Projektartikel',
|
||||
'verbose_name_plural': 'Projektartikel',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 6.0.6 on 2026-06-23 22:12
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('finance', '0002_initial'),
|
||||
('inventory', '0001_initial'),
|
||||
('projects', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='projectinventoryitem',
|
||||
name='project',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory_items', to='projects.project', verbose_name='Projekt'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shoppingitem',
|
||||
name='category',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='finance.category', verbose_name='Kategorie'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shoppingitem',
|
||||
name='project',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shopping_items', to='projects.project', verbose_name='Projekt'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shoppingitem',
|
||||
name='supplier',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='finance.supplier', verbose_name='Lieferant'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='projectinventoryitem',
|
||||
unique_together={('project', 'item')},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,87 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from projects.models import Project
|
||||
from finance.models import Category, Supplier
|
||||
from django.db.models import Sum
|
||||
|
||||
class InventoryCategory(models.Model):
|
||||
name = models.CharField(max_length=255, unique=True, verbose_name="Name")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Inventarkategorie"
|
||||
verbose_name_plural = "Inventarkategorien"
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class InventoryLocation(models.Model):
|
||||
name = models.CharField(max_length=255, unique=True, verbose_name="Name")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Inventarstandort"
|
||||
verbose_name_plural = "Inventarstandorte"
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class InventoryItem(models.Model):
|
||||
name = models.CharField(max_length=255, verbose_name="Name")
|
||||
description = models.TextField(blank=True, verbose_name="Beschreibung")
|
||||
quantity = models.PositiveIntegerField(default=0, verbose_name="Anzahl")
|
||||
location = models.ForeignKey(InventoryLocation, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Standort")
|
||||
article_number = models.CharField(max_length=255, blank=True, verbose_name="Artikelnummer")
|
||||
responsible = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Verantwortlicher")
|
||||
category = models.ForeignKey(InventoryCategory, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Kategorie")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Inventarartikel"
|
||||
verbose_name_plural = "Inventarartikel"
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ProjectInventoryItem(models.Model):
|
||||
class Status(models.TextChoices):
|
||||
OPEN = "open", "Offen"
|
||||
PACKED = "packed", "Eingepackt"
|
||||
ON_SITE = "on_site", "Vor Ort"
|
||||
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="inventory_items", verbose_name="Projekt")
|
||||
item = models.ForeignKey(InventoryItem, on_delete=models.CASCADE, verbose_name="Artikel")
|
||||
quantity = models.PositiveIntegerField(default=1, verbose_name="Anzahl")
|
||||
status = models.CharField(max_length=20, choices=Status.choices, default=Status.OPEN, verbose_name="Status")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Projektartikel"
|
||||
verbose_name_plural = "Projektartikel"
|
||||
unique_together = ("project", "item")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.project} - {self.item}"
|
||||
|
||||
|
||||
class ShoppingItem(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="shopping_items", verbose_name="Projekt")
|
||||
name = models.CharField(max_length=255, verbose_name="Name")
|
||||
description = models.TextField(blank=True, verbose_name="Beschreibung")
|
||||
quantity = models.PositiveIntegerField(default=1, verbose_name="Anzahl")
|
||||
supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Lieferant")
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Preis")
|
||||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Kategorie")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Einkaufslistenartikel"
|
||||
verbose_name_plural = "Einkaufslistenartikel"
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -0,0 +1,19 @@
|
||||
from masterdata.registry import register_setting
|
||||
|
||||
from .models import (
|
||||
InventoryCategory,
|
||||
InventoryLocation,
|
||||
)
|
||||
|
||||
|
||||
register_setting(
|
||||
group="Inventar",
|
||||
name="Kategorien",
|
||||
model=InventoryCategory,
|
||||
)
|
||||
|
||||
register_setting(
|
||||
group="Inventar",
|
||||
name="Standorte",
|
||||
model=InventoryLocation,
|
||||
)
|
||||
@@ -0,0 +1,197 @@
|
||||
import django_tables2 as tables
|
||||
from .models import ProjectInventoryItem, ShoppingItem, InventoryItem
|
||||
import django_tables2 as tables
|
||||
|
||||
class InventoryGlobalTable(tables.Table):
|
||||
toggle_prefix = "inv"
|
||||
detail_template = "inventory/tables/inventory_global/edit_form.html"
|
||||
|
||||
name = tables.Column(
|
||||
verbose_name="Name"
|
||||
)
|
||||
|
||||
category = tables.Column(
|
||||
verbose_name="Kategorie"
|
||||
)
|
||||
|
||||
available = tables.TemplateColumn(
|
||||
template_name="inventory/tables/inventory_global/available.html",
|
||||
verbose_name="Anzahl",
|
||||
orderable=False
|
||||
)
|
||||
|
||||
location = tables.Column(
|
||||
verbose_name="Standort"
|
||||
)
|
||||
|
||||
article_number = tables.Column(
|
||||
verbose_name="Artikelnummer"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
template_name = "django_tables2/table_toggle.html"
|
||||
fields = (
|
||||
"expand",
|
||||
"name",
|
||||
"category",
|
||||
"available",
|
||||
"location",
|
||||
"article_number",
|
||||
)
|
||||
attrs = {
|
||||
"class": "w-full text-sm min-w-[700px]"
|
||||
}
|
||||
|
||||
|
||||
class InventoryAddTable(tables.Table):
|
||||
name = tables.Column(
|
||||
accessor="name",
|
||||
verbose_name="Name"
|
||||
)
|
||||
|
||||
category = tables.Column(
|
||||
accessor="category",
|
||||
verbose_name="Kategorie"
|
||||
)
|
||||
|
||||
available = tables.Column(
|
||||
accessor="available_quantity",
|
||||
verbose_name="Verfügbar"
|
||||
)
|
||||
|
||||
article_number = tables.Column(
|
||||
accessor="article_number",
|
||||
verbose_name="Artikelnummer"
|
||||
)
|
||||
|
||||
quantity = tables.TemplateColumn(
|
||||
template_name="inventory/tables/inventory_add/quantity.html",
|
||||
verbose_name="Anzahl hinzufügen",
|
||||
orderable=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = (
|
||||
"name",
|
||||
"category",
|
||||
"available",
|
||||
"article_number",
|
||||
"quantity",
|
||||
)
|
||||
attrs = {
|
||||
"class": "w-full text-sm min-w-[700px]"
|
||||
}
|
||||
|
||||
class ShoppingItemTable(tables.Table):
|
||||
|
||||
name = tables.TemplateColumn(
|
||||
template_name="inventory/tables/shopping/name.html",
|
||||
verbose_name="Name"
|
||||
)
|
||||
|
||||
category = tables.TemplateColumn(
|
||||
template_name="inventory/tables/shopping/category.html",
|
||||
verbose_name="Kategorie"
|
||||
)
|
||||
|
||||
quantity = tables.TemplateColumn(
|
||||
template_name="inventory/tables/shopping/quantity.html",
|
||||
verbose_name="Anzahl"
|
||||
)
|
||||
|
||||
supplier = tables.TemplateColumn(
|
||||
template_name="inventory/tables/shopping/supplier.html",
|
||||
verbose_name="Lieferant"
|
||||
)
|
||||
|
||||
price = tables.TemplateColumn(
|
||||
template_name="inventory/tables/shopping/price.html",
|
||||
verbose_name="Preis"
|
||||
)
|
||||
|
||||
description = tables.TemplateColumn(
|
||||
template_name="inventory/tables/shopping/description.html",
|
||||
verbose_name="Beschreibung"
|
||||
)
|
||||
|
||||
actions = tables.TemplateColumn(
|
||||
template_name="inventory/tables/shopping/actions.html",
|
||||
verbose_name="Aktionen",
|
||||
orderable=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ShoppingItem
|
||||
fields = (
|
||||
"name",
|
||||
"category",
|
||||
"quantity",
|
||||
"supplier",
|
||||
"price",
|
||||
"description",
|
||||
"actions",
|
||||
)
|
||||
attrs = {
|
||||
"class": "w-full text-sm"
|
||||
}
|
||||
|
||||
class ProjectInventoryItemTable(tables.Table):
|
||||
|
||||
name = tables.Column(
|
||||
accessor="item.name",
|
||||
verbose_name="Name"
|
||||
)
|
||||
|
||||
category = tables.Column(
|
||||
accessor="item.category",
|
||||
verbose_name="Kategorie"
|
||||
)
|
||||
|
||||
quantity = tables.TemplateColumn(
|
||||
template_name="inventory/tables/quantity.html",
|
||||
verbose_name="Anzahl"
|
||||
)
|
||||
|
||||
location = tables.Column(
|
||||
accessor="item.location",
|
||||
verbose_name="Standort"
|
||||
)
|
||||
|
||||
article_number = tables.Column(
|
||||
accessor="item.article_number",
|
||||
verbose_name="Artikelnummer"
|
||||
)
|
||||
|
||||
responsible = tables.Column(
|
||||
accessor="item.responsible",
|
||||
verbose_name="Verantwortlicher"
|
||||
)
|
||||
|
||||
status = tables.TemplateColumn(
|
||||
template_name="inventory/tables/status.html",
|
||||
verbose_name="Status"
|
||||
)
|
||||
|
||||
actions = tables.TemplateColumn(
|
||||
template_name="inventory/tables/actions.html",
|
||||
verbose_name="Aktionen",
|
||||
orderable=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ProjectInventoryItem
|
||||
fields = (
|
||||
"name",
|
||||
"category",
|
||||
"quantity",
|
||||
"location",
|
||||
"article_number",
|
||||
"responsible",
|
||||
"status",
|
||||
"actions",
|
||||
)
|
||||
attrs = {
|
||||
"class": "w-full text-sm"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ project.name }} - Inventar
|
||||
{% endblock %}
|
||||
|
||||
{% block header_title %}
|
||||
Packartikel hinzufügen
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Artikel zur Packliste hinzufügen
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% load django_tables2 %}
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="overflow-x-auto rounded-xl">
|
||||
{% render_table table %}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn mt-4 btn-save">
|
||||
Speichern
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Inventar
|
||||
{% endblock %}
|
||||
|
||||
{% block header_title %}
|
||||
Inventarartikel hinzufügen
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Neuen Artikel zum globalen Inventar hinzufügen
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
|
||||
|
||||
<form method="post" class="space-y-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{{ form.as_p }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn mt-4 btn-save">
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,46 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Inventar
|
||||
{% endblock %}
|
||||
|
||||
{% block header_title %}
|
||||
Inventar
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Globale Inventarverwaltung
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% load django_tables2 %}
|
||||
<div class="mb-8">
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold">Aktionen</h2>
|
||||
<p class="text-slate-400 text-sm mt-1">Inventarartikel hinzufügen</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<a href="{% url 'inventory_global:add' %}" class="btn btn-add">+ Artikel</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
|
||||
<div class="mb-6">
|
||||
<h2 class="text-xl font-bold">Inventarliste</h2>
|
||||
<p class="text-slate-400 text-sm mt-1">Gelagerte und verfügbare Artikel</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto rounded-xl">
|
||||
{% render_table table %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,73 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ project.name }} - Inventar
|
||||
{% endblock %}
|
||||
|
||||
{% block header_title %}
|
||||
Pack-/Einkaufsliste
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Vorhandenen & benötigtes Material
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% load django_tables2 %}
|
||||
<div class="mb-8">
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold">Aktionen</h2>
|
||||
<p class="text-slate-400 text-sm mt-1">Artikel zur Packliste oder Einkaufsliste hinzufügen</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<a href="{% url 'inventory:add' project.id %}" class="btn btn-add">+ Packliste</a>
|
||||
<a href="{% url 'inventory:shopping_add' project.id %}" class="btn btn-add">+ Einkaufsliste</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
|
||||
<div class="mb-6">
|
||||
<h2 class="text-xl font-bold">Packliste</h2>
|
||||
<p class="text-slate-400 text-sm mt-1">Vorhandene und benötigte Artikel</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto rounded-xl">
|
||||
{% render_table table %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors mt-8">
|
||||
|
||||
<div class="flex flex-wrap justify-between items-center mb-6">
|
||||
|
||||
<div>
|
||||
<h2 class="text-xl font-bold">
|
||||
Einkaufsliste
|
||||
</h2>
|
||||
|
||||
<p class="text-slate-400 text-sm mt-1">
|
||||
Nicht-Vorhandene und benötigte Artikel
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto rounded-xl">
|
||||
|
||||
{% render_table shopping_table %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ project.name }} - Inventar
|
||||
{% endblock %}
|
||||
|
||||
{% block header_title %}
|
||||
Einkaufsartikel hinzufügen
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Artikel zur Einkaufsliste hinzufügen
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
<form method="post" class="space-y-4">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="space-y-4">
|
||||
|
||||
{{ form.as_p }}
|
||||
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn mt-4 btn-save">
|
||||
|
||||
Speichern
|
||||
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,29 @@
|
||||
<form
|
||||
id="save-form-{{ record.id }}"
|
||||
method="post"
|
||||
action="{% url 'inventory:edit' project.id record.id %}">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="item_id"
|
||||
value="{{ record.id }}"
|
||||
form="save-form-{{ record.id }}">
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
form="save-form-{{ record.id }}"
|
||||
class="btn btn-save">
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
form="save-form-{{ record.id }}"
|
||||
formaction="{% url 'inventory:delete' project.id record.id %}"
|
||||
class="btn btn-delete">
|
||||
Löschen
|
||||
</button>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="{{ record.available }}"
|
||||
value="0"
|
||||
name="item_{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-24">
|
||||
@@ -0,0 +1,10 @@
|
||||
{% if record.available_quantity > 0 %}
|
||||
<span class="text-green-600 font-semibold">
|
||||
{{ record.available_quantity }}
|
||||
</span>
|
||||
{% elif record.available_quantity == 0 %}
|
||||
<span class="text-orange-600 font-semibold">
|
||||
0
|
||||
</span>
|
||||
{% endif %}
|
||||
/ {{ record.quantity }}
|
||||
@@ -0,0 +1,155 @@
|
||||
<form method="post" action="{% url 'inventory_global:edit' item.id %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<input type="hidden" name="item_id" value="{{ item.id }}">
|
||||
|
||||
<div class="grid md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1">
|
||||
Name
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value="{{ item.name }}"
|
||||
class="border rounded px-3 py-2 w-full">
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1">
|
||||
Beschreibung
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="description"
|
||||
value="{{ item.description }}"
|
||||
class="border rounded px-3 py-2 w-full">
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1">
|
||||
Anzahl
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
value="{{ item.quantity }}"
|
||||
class="border rounded px-3 py-2 w-full">
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1">
|
||||
Kategorie
|
||||
</label>
|
||||
|
||||
<select
|
||||
name="category"
|
||||
class="border rounded px-3 py-2 w-full">
|
||||
|
||||
<option value="">
|
||||
---
|
||||
</option>
|
||||
|
||||
{% for c in categories %}
|
||||
<option
|
||||
value="{{ c.id }}"
|
||||
{% if item.category_id == c.id %}selected{% endif %}>
|
||||
{{ c.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1">
|
||||
Standort
|
||||
</label>
|
||||
|
||||
<select
|
||||
name="location"
|
||||
class="border rounded px-3 py-2 w-full">
|
||||
|
||||
<option value="">
|
||||
---
|
||||
</option>
|
||||
|
||||
{% for l in locations %}
|
||||
<option
|
||||
value="{{ l.id }}"
|
||||
{% if item.location_id == l.id %}selected{% endif %}>
|
||||
{{ l.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1">
|
||||
Artikelnummer
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="article_number"
|
||||
value="{{ item.article_number }}"
|
||||
class="border rounded px-3 py-2 w-full">
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="block text-sm mb-1">
|
||||
Verantwortlicher
|
||||
</label>
|
||||
|
||||
<select
|
||||
name="responsible"
|
||||
class="border rounded px-3 py-2 w-full">
|
||||
|
||||
<option value="">
|
||||
---
|
||||
</option>
|
||||
|
||||
{% for u in users %}
|
||||
<option
|
||||
value="{{ u.id }}"
|
||||
{% if item.responsible_id == u.id %}selected{% endif %}>
|
||||
{{ u.get_full_name|default:u.username }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-5 flex gap-2">
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-save">
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
formaction="{% url 'inventory_global:delete' item.id %}"
|
||||
class="btn btn-delete">
|
||||
Löschen
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,6 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick="toggleItem('{{ record.id }}')"
|
||||
class="font-bold">
|
||||
▼
|
||||
</button>
|
||||
@@ -0,0 +1,7 @@
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
min="0"
|
||||
value="{{ record.quantity }}"
|
||||
form="save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-24">
|
||||
@@ -0,0 +1,32 @@
|
||||
<form
|
||||
id="shopping-save-form-{{ record.id }}"
|
||||
method="post"
|
||||
action="{% url 'inventory:shopping_edit' table.project.id record.id %}">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="shopping_item_id"
|
||||
value="{{ record.id }}">
|
||||
|
||||
<div class="flex gap-2">
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
class="btn btn-save">
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
formaction="{% url 'inventory:shopping_delete' table.project.id record.id %}"
|
||||
class="btn btn-delete">
|
||||
Löschen
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,15 @@
|
||||
<select
|
||||
name="category"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-full">
|
||||
|
||||
<option value="">---</option>
|
||||
|
||||
{% for c in table.categories %}
|
||||
<option value="{{ c.id }}"
|
||||
{% if record.category_id == c.id %}selected{% endif %}>
|
||||
{{ c.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
@@ -0,0 +1,6 @@
|
||||
<input
|
||||
type="text"
|
||||
name="description"
|
||||
value="{{ record.description }}"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-full">
|
||||
@@ -0,0 +1,6 @@
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value="{{ record.name }}"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-full">
|
||||
@@ -0,0 +1,7 @@
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
name="price"
|
||||
value="{{ record.price }}"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-28">
|
||||
@@ -0,0 +1,6 @@
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
value="{{ record.quantity }}"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-24">
|
||||
@@ -0,0 +1,15 @@
|
||||
<select
|
||||
name="supplier"
|
||||
form="shopping-save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1 w-full">
|
||||
|
||||
<option value="">---</option>
|
||||
|
||||
{% for s in table.suppliers %}
|
||||
<option value="{{ s.id }}"
|
||||
{% if record.supplier_id == s.id %}selected{% endif %}>
|
||||
{{ s.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
@@ -0,0 +1,21 @@
|
||||
<select
|
||||
name="status"
|
||||
form="save-form-{{ record.id }}"
|
||||
class="border rounded px-2 py-1">
|
||||
|
||||
<option value="open"
|
||||
{% if record.status == "open" %}selected{% endif %}>
|
||||
Offen
|
||||
</option>
|
||||
|
||||
<option value="packed"
|
||||
{% if record.status == "packed" %}selected{% endif %}>
|
||||
Eingepackt
|
||||
</option>
|
||||
|
||||
<option value="on_site"
|
||||
{% if record.status == "on_site" %}selected{% endif %}>
|
||||
Vor Ort
|
||||
</option>
|
||||
|
||||
</select>
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,16 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = "inventory"
|
||||
|
||||
urlpatterns = [
|
||||
path("<int:project_id>/inventory/", views.inventory_overview, name="overview"),
|
||||
path("<int:project_id>/inventory/add/", views.inventory_add, name="add"),
|
||||
path("<int:project_id>/inventory/shopping-add/", views.shopping_add, name="shopping_add"),
|
||||
|
||||
path("<int:project_id>/inventory/edit/<int:item_id>/", views.inventory_edit, name="edit"),
|
||||
path("<int:project_id>/inventory/shopping-edit/<int:item_id>/", views.shopping_edit, name="shopping_edit"),
|
||||
|
||||
path("<int:project_id>/inventory/delete/<int:item_id>/", views.inventory_delete, name="delete"),
|
||||
path("<int:project_id>/inventory/shopping-delete/<int:item_id>/", views.shopping_delete, name="shopping_delete"),
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = "inventory_global"
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.inventory_global_overview, name="overview"),
|
||||
path("add/", views.inventory_global_add, name="add"),
|
||||
path("edit/<int:item_id>/", views.inventory_global_edit, name="edit"),
|
||||
path("delete/<int:item_id>/", views.inventory_global_delete, name="delete"),
|
||||
]
|
||||
@@ -0,0 +1,431 @@
|
||||
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
|
||||
|
||||
@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"),
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user