Implement amount tracking
This commit is contained in:
parent
b1c5994727
commit
e8a54dcbc7
23 changed files with 191 additions and 42 deletions
|
@ -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
|
||||
|
|
23
inventory/migrations/0006_item_count_item_low_count.py
Normal file
23
inventory/migrations/0006_item_count_item_low_count.py
Normal 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'),
|
||||
),
|
||||
]
|
18
inventory/migrations/0007_settings_track_amount.py
Normal file
18
inventory/migrations/0007_settings_track_amount.py
Normal 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'),
|
||||
),
|
||||
]
|
|
@ -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", )
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
1
inventory/static/inventory/img/warn.svg
Normal file
1
inventory/static/inventory/img/warn.svg
Normal 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 |
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 }} €</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue