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.contrib import admin
from django.conf import settings from django.conf import settings
from inventory.models import Item, Documentation from inventory.models import Item, Documentation, Settings
class DocumentationAdmin(admin.ModelAdmin): class DocumentationAdmin(admin.ModelAdmin):
@ -19,6 +19,12 @@ class ItemAdmin(admin.ModelAdmin):
readonly_fields = ['created_at', 'changed_at'] readonly_fields = ['created_at', 'changed_at']
filter_horizontal = ('tags', 'documentation') 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): def view_on_site(self, obj):
url = reverse('item-detail', kwargs={'pk': obj.id}) url = reverse('item-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url 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) documentation = models.ManyToManyField('inventory.Documentation', related_name='items', blank=True)
tags = models.ManyToManyField('inventory.Tag', blank=True) tags = models.ManyToManyField('inventory.Tag', blank=True)
metadata = models.JSONField('Custom metadata, used by templates', blank=True, null=True) count = models.PositiveIntegerField(
created_at = models.DateTimeField(auto_now_add=True) default=1,
changed_at = models.DateTimeField(auto_now=True) 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): def __str__(self):
items = [self.name, self.description] items = [self.name, self.description]
@ -33,5 +41,9 @@ class Item(CanBeContained):
else: else:
return list(self.tags.all()) return list(self.tags.all())
@property
def value(self):
return self.count * self.price
class Meta: class Meta:
ordering = ("name", ) ordering = ("name", )

View file

@ -8,7 +8,16 @@ class Settings(models.Model):
default=None, default=None,
null=True, null=True,
blank=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): def __str__(self):

View file

@ -89,6 +89,24 @@ td.disabled {
opacity: 0.75; 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 { .cell .form_factor {
position: absolute; position: absolute;
display: inline; display: inline;
@ -154,6 +172,12 @@ td.disabled {
right: 7px; right: 7px;
} }
.cell .stock {
font-size: 75%;
top: 3px;
left: 3px;
}
.cell .price { .cell .price {
font-size: 75%; font-size: 75%;
bottom: 3px; bottom: 3px;

View file

@ -26,6 +26,11 @@ table.attribute-list th {
border-style: solid; border-style: solid;
} }
table.attribute-list .small {
color: #808080;
font-size: xx-small;
}
table.box { table.box {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@ -158,8 +163,7 @@ img.icon {
margin-right: 10px; margin-right: 10px;
width: 18px; width: 18px;
height: 18px; height: 18px;
position: relative; vertical-align: middle;
top: 2px;
} }
img.logo { img.logo {
@ -277,9 +281,6 @@ table.list thead th {
padding: 10px; padding: 10px;
} }
table.list tbody tr {
}
table.list tbody td { table.list tbody td {
border-bottom: 1px solid black; border-bottom: 1px solid black;
padding: 10px; padding: 10px;
@ -322,3 +323,16 @@ table.list tbody td {
font-weight: normal; font-weight: normal;
color: #808080; 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"> <td rowspan="2">
<div class="compartments-vertical"> <div class="compartments-vertical">
{% for compartment in layouted.0.0 %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -17,7 +17,7 @@
<td class="triple-height" colspan="3"> <td class="triple-height" colspan="3">
<div class="compartments"> <div class="compartments">
{% for compartment in layouted.0.1 %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -27,7 +27,7 @@
<td class="triple-height"> <td class="triple-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -9,7 +9,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in layouted.0 %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -10,7 +10,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -20,7 +20,7 @@
<td rowspan="2"> <td rowspan="2">
<div class="compartments-vertical"> <div class="compartments-vertical">
{% for compartment in layouted.1.0 %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -29,7 +29,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -40,7 +40,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -11,7 +11,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -27,7 +27,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -42,7 +42,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -11,7 +11,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -27,7 +27,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -42,7 +42,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -11,7 +11,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -27,7 +27,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -42,7 +42,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -11,7 +11,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -10,7 +10,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -21,7 +21,7 @@
<td> <td>
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -10,7 +10,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -21,7 +21,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -32,7 +32,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -45,7 +45,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>

View file

@ -10,7 +10,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -21,7 +21,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -32,7 +32,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </td>
@ -45,7 +45,7 @@
<td class="double-height"> <td class="double-height">
<div class="compartments"> <div class="compartments">
{% for compartment in item %} {% 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 %} {% endfor %}
</div> </div>
</td> </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> <a class="details" href="{% url "item-detail" pk=item.pk %}"><img class="icon" src="{% static "inventory/img/detail.svg" %}"></a>
</div> </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 %} {% if item.price %}
<div class="price">{{ item.price | floatformat:2 }} &euro;</div> <div class="price">{{ item.price | floatformat:2 }} &euro;</div>
{% endif %} {% endif %}

View file

@ -29,6 +29,25 @@
<th>Description</th> <th>Description</th>
<td>{{ item.description }}</td> <td>{{ item.description }}</td>
</tr> </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> <tr>
<th>Tags</th> <th>Tags</th>
<td> <td>

View file

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

View file

@ -1,15 +1,30 @@
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView 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') @method_decorator(login_required, name='dispatch')
class ItemView(DetailView): class ItemView(DetailView):
context_object_name = 'item' context_object_name = 'item'
queryset = Item.objects.all().select_related('container', 'container__layout', 'manufacturer', 'distributor').prefetch_related('documentation') 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') @method_decorator(login_required, name='dispatch')
class ItemListView(ListView): class ItemListView(ListView):