Implement amount tracking

This commit is contained in:
Johannes Schriewer 2025-01-07 21:36:13 +01:00
parent b1c5994727
commit e8a54dcbc7
23 changed files with 191 additions and 42 deletions

View file

@ -3,7 +3,7 @@ from django.shortcuts import redirect
from django.contrib import admin
from django.conf import settings
from inventory.models import Item, Documentation
from inventory.models import Item, Documentation, Settings
class DocumentationAdmin(admin.ModelAdmin):
@ -19,6 +19,12 @@ class ItemAdmin(admin.ModelAdmin):
readonly_fields = ['created_at', 'changed_at']
filter_horizontal = ('tags', 'documentation')
def get_exclude(self, request, obj=None):
s = Settings.objects.first()
if (s.track_amount):
return self.exclude
return (self.exclude or tuple()) + ('count', 'low_count')
def view_on_site(self, obj):
url = reverse('item-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url

View file

@ -0,0 +1,23 @@
# Generated by Django 5.1.4 on 2025-01-07 17:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0005_alter_sorting_collation'),
]
operations = [
migrations.AddField(
model_name='item',
name='count',
field=models.PositiveIntegerField(default=1, help_text='Number of parts available'),
),
migrations.AddField(
model_name='item',
name='low_count',
field=models.PositiveIntegerField(default=0, help_text='Low watermark on which to alert ordering more'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2025-01-07 17:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0006_item_count_item_low_count'),
]
operations = [
migrations.AddField(
model_name='settings',
name='track_amount',
field=models.BooleanField(default=False, help_text='Show item count in overview and warn on low watermarks'),
),
]

View file

@ -16,9 +16,17 @@ class Item(CanBeContained):
documentation = models.ManyToManyField('inventory.Documentation', related_name='items', blank=True)
tags = models.ManyToManyField('inventory.Tag', blank=True)
metadata = models.JSONField('Custom metadata, used by templates', blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
count = models.PositiveIntegerField(
default=1,
null=False,
help_text="Number of parts available"
)
low_count = models.PositiveIntegerField(
default=0,
null=False,
help_text="Low watermark on which to alert ordering more"
)
def __str__(self):
items = [self.name, self.description]
@ -32,6 +40,10 @@ class Item(CanBeContained):
return list(self.tags.all()) + list(self.form_factor.tags.all())
else:
return list(self.tags.all())
@property
def value(self):
return self.count * self.price
class Meta:
ordering = ("name", )

View file

@ -8,7 +8,16 @@ class Settings(models.Model):
default=None,
null=True,
blank=True,
help_text='Default container to display when calling the index page'
)
track_amount = models.BooleanField(
default=False,
help_text="Show item count in overview and warn on low watermarks"
)
currency = models.CharField(max_length=30, help_text="Currency name", default="Euro")
currency_symbol = models.CharField(max_length=20, default="€")
currency_symbol_position = models.BooleanField(
default=True,
help_text="Currency symbol after amount"
)
def __str__(self):

View file

@ -89,6 +89,24 @@ td.disabled {
opacity: 0.75;
}
.cell .stock {
position: absolute;
top: 5px;
left: 5px;
display: inline;
font-size: xx-small;
color: #808080;
line-height: 10px;
}
.cell .stock .icon {
display: inline-block;
width: 10px;
height: 10px;
margin: 0 2px 0 0;
vertical-align: text-top;
}
.cell .form_factor {
position: absolute;
display: inline;
@ -153,7 +171,13 @@ td.disabled {
top: 3px;
right: 7px;
}
.cell .stock {
font-size: 75%;
top: 3px;
left: 3px;
}
.cell .price {
font-size: 75%;
bottom: 3px;

View file

@ -26,6 +26,11 @@ table.attribute-list th {
border-style: solid;
}
table.attribute-list .small {
color: #808080;
font-size: xx-small;
}
table.box {
width: 100%;
border-collapse: collapse;
@ -158,8 +163,7 @@ img.icon {
margin-right: 10px;
width: 18px;
height: 18px;
position: relative;
top: 2px;
vertical-align: middle;
}
img.logo {
@ -277,9 +281,6 @@ table.list thead th {
padding: 10px;
}
table.list tbody tr {
}
table.list tbody td {
border-bottom: 1px solid black;
padding: 10px;
@ -322,3 +323,16 @@ table.list tbody td {
font-weight: normal;
color: #808080;
}
div.icon {
width: 18px;
height: 18px;
display: inline-block;
vertical-align: middle;
}
div.warning-icon {
background-color: #c00000;
-webkit-mask: url(/static/inventory/img/warn.svg) no-repeat center;
mask: url(/static/inventory/img/warn.svg) no-repeat center;
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>

After

Width:  |  Height:  |  Size: 542 B

View file

@ -9,7 +9,7 @@
<td rowspan="2">
<div class="compartments-vertical">
{% for compartment in layouted.0.0 %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -17,7 +17,7 @@
<td class="triple-height" colspan="3">
<div class="compartments">
{% for compartment in layouted.0.1 %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -27,7 +27,7 @@
<td class="triple-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -9,7 +9,7 @@
<td>
<div class="compartments">
{% for compartment in layouted.0 %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -10,7 +10,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -20,7 +20,7 @@
<td rowspan="2">
<div class="compartments-vertical">
{% for compartment in layouted.1.0 %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -29,7 +29,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -40,7 +40,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -11,7 +11,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -27,7 +27,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -42,7 +42,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -11,7 +11,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -27,7 +27,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -42,7 +42,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -11,7 +11,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -27,7 +27,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -42,7 +42,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -11,7 +11,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -10,7 +10,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -21,7 +21,7 @@
<td>
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -10,7 +10,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -21,7 +21,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -32,7 +32,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -45,7 +45,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -10,7 +10,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -21,7 +21,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -32,7 +32,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>
@ -45,7 +45,7 @@
<td class="double-height">
<div class="compartments">
{% for compartment in item %}
{% include "inventory/cell.html" with item=compartment hilight=hilight %}
{% include "inventory/cell.html" with item=compartment hilight=hilight settings=settings %}
{% endfor %}
</div>
</td>

View file

@ -18,6 +18,13 @@
<a class="details" href="{% url "item-detail" pk=item.pk %}"><img class="icon" src="{% static "inventory/img/detail.svg" %}"></a>
</div>
{% if settings.track_amount %}
{% if item.count <= item.low_count %}
<div class="stock"><div class="icon warning-icon" title="Low stock"></div>{{ item.count }}</div>
{% else %}
<div class="stock">{{ item.count }}</div>
{% endif %}
{% endif %}
{% if item.price %}
<div class="price">{{ item.price | floatformat:2 }} &euro;</div>
{% endif %}

View file

@ -29,6 +29,25 @@
<th>Description</th>
<td>{{ item.description }}</td>
</tr>
{% if settings.track_amount %}
<tr>
<th>Amount</th>
<td>
<form method="post" action="{% url 'item-detail' item.pk %}">
{% csrf_token %}
<label for="amount">
{% if item.count <= item.low_count %}
<div class="icon warning-icon" title="Low stock"></div>
{% endif %}
</label>
<input type="text" name="amount" id="amount" value="{{ item.count }}" placeholder="Amount">
<button name="save" type="submit">Update</button>
<button name="dec" type="submit">-1</button>
</form>
<span class="small right">Low watermark: {{ item.low_count }}</span>
</td>
</tr>
{% endif %}
<tr>
<th>Tags</th>
<td>

View file

@ -4,7 +4,7 @@ from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView
from django.db.models import QuerySet
from inventory.models import Box
from inventory.models import Box, Settings
from .utils import CanBeIndexMixin
@ -54,6 +54,7 @@ class BoxView(CanBeIndexMixin, DetailView):
context = self.get_context_data(object=self.object)
context['layouted'], _ = self.layout(self.object.item_related.all().order_by('index'), self.object.layout.data)
context['hilight'] = hilighted
context['settings'] = Settings.objects.first()
return self.render_to_response(context)

View file

@ -1,15 +1,30 @@
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView
from django.shortcuts import get_object_or_404
from inventory.models import Item
from inventory.models import Item, Settings
@method_decorator(login_required, name='dispatch')
class ItemView(DetailView):
context_object_name = 'item'
queryset = Item.objects.all().select_related('container', 'container__layout', 'manufacturer', 'distributor').prefetch_related('documentation')
def get_context_data(self, *args, object_list=None, **kwargs):
result = super().get_context_data(*args, object_list=object_list, **kwargs)
result['settings'] = Settings.objects.first()
return result
def post(self, request, pk):
item = get_object_or_404(Item.objects.filter(pk=pk))
if request.POST.get('dec', None) is not None:
if item.count > 0:
item.count -= 1
item.save()
if request.POST.get('save', None) is not None:
item.count = request.POST.get('amount', 0)
item.save()
return self.get(request)
@method_decorator(login_required, name='dispatch')
class ItemListView(ListView):