Implement Storage navigation and other helpful stuff
This commit is contained in:
parent
c065720ded
commit
e8538cd534
@ -50,3 +50,6 @@ class DistributorSerializer(serializers.HyperlinkedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = parts_models.Distributor
|
model = parts_models.Distributor
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
class StockIncrementDecrementSerializer(serializers.Serializer):
|
||||||
|
increment = serializers.IntegerField()
|
||||||
|
@ -15,6 +15,7 @@ from django.utils import timezone
|
|||||||
from rest_framework.authtoken.views import ObtainAuthToken
|
from rest_framework.authtoken.views import ObtainAuthToken
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.throttling import AnonRateThrottle
|
from rest_framework.throttling import AnonRateThrottle
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
@ -48,6 +49,19 @@ class PartsStockViewSet(viewsets.ModelViewSet):
|
|||||||
serializer_class = StockSerializer
|
serializer_class = StockSerializer
|
||||||
permission_classes = [permissions.DjangoModelPermissions]
|
permission_classes = [permissions.DjangoModelPermissions]
|
||||||
|
|
||||||
|
@action(detail=True, methods=['patch'], name="change-stock-count")
|
||||||
|
def update_stock(self, request, pk=None):
|
||||||
|
stock = self.get_object()
|
||||||
|
serializer = StockIncrementDecrementSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
increment = serializer.data['increment']
|
||||||
|
if stock.atomic_increment(increment):
|
||||||
|
return Response({'status': 'Stock updated', 'update_value': increment})
|
||||||
|
else:
|
||||||
|
return Response({'status': 'Stock not updated. Would be negative'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
class PartsPackageViewSet(viewsets.ModelViewSet):
|
class PartsPackageViewSet(viewsets.ModelViewSet):
|
||||||
queryset = parts_models.Package.objects.all()
|
queryset = parts_models.Package.objects.all()
|
||||||
serializer_class = PackageSerializer
|
serializer_class = PackageSerializer
|
||||||
|
@ -89,7 +89,10 @@ class Storage(models.Model):
|
|||||||
|
|
||||||
def get_total_stock_amount(self):
|
def get_total_stock_amount(self):
|
||||||
stocks = Stock.objects.filter(storage=self)
|
stocks = Stock.objects.filter(storage=self)
|
||||||
return stocks.aggregate(Sum('amount'))['amount__sum']
|
sum = stocks.aggregate(Sum('amount'))['amount__sum']
|
||||||
|
if sum is None:
|
||||||
|
sum = 0
|
||||||
|
return sum
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.validate_unique()
|
self.validate_unique()
|
||||||
@ -216,9 +219,16 @@ class Stock(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.component) + ' @ ' + str(self.amount) + ' in ' + str(
|
return str(self.component) + ' @ ' + str(self.amount) + ' in ' + str(
|
||||||
self.storage)
|
self.storage)
|
||||||
|
|
||||||
def get_under_watermark():
|
@staticmethod
|
||||||
return Stock.objects.filter(amount__lt = F('watermark'))
|
def get_under_watermark(user_filter = None, invert: bool = False):
|
||||||
|
query = Stock.objects.filter(amount__lt = F('watermark'))
|
||||||
|
if user_filter is not None:
|
||||||
|
if invert:
|
||||||
|
query = query.exclude(storage__responsible=user_filter)
|
||||||
|
else:
|
||||||
|
query = query.filter(storage__responsible=user_filter)
|
||||||
|
return query
|
||||||
|
|
||||||
class DistributorNum(models.Model):
|
class DistributorNum(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -10,6 +10,7 @@ from django.views.generic import TemplateView, DetailView
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||||
from .models import Storage, Stock
|
from .models import Storage, Stock
|
||||||
from .qr_parser import QrCodeValidator
|
from .qr_parser import QrCodeValidator
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
class QrSearchForm(forms.Form):
|
class QrSearchForm(forms.Form):
|
||||||
my_qr_validator = QrCodeValidator()
|
my_qr_validator = QrCodeValidator()
|
||||||
@ -101,11 +102,25 @@ class StockView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
|
|||||||
template_name = 'parts/stocks.html'
|
template_name = 'parts/stocks.html'
|
||||||
base_title = 'Stocks'
|
base_title = 'Stocks'
|
||||||
navbar_selected = 'Stocks'
|
navbar_selected = 'Stocks'
|
||||||
|
default_pagination_size = 25
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['low_stocks'] = Stock.get_under_watermark()
|
|
||||||
context['storages'] = Storage.objects.filter(parent_storage=None)
|
storage_page = self.request.GET.get('storage_page')
|
||||||
|
if storage_page is None:
|
||||||
|
storage_page = 1
|
||||||
|
low_stock_page = self.request.GET.get('low_stock_page')
|
||||||
|
if low_stock_page is None:
|
||||||
|
low_stock_page = 1
|
||||||
|
|
||||||
|
storage_paginator = Paginator(Storage.objects.filter(parent_storage=None), self.default_pagination_size)
|
||||||
|
low_stock_paginator = Paginator(Stock.get_under_watermark(),
|
||||||
|
self.default_pagination_size)
|
||||||
|
|
||||||
|
context['low_stocks'] = low_stock_paginator.get_page(low_stock_page)
|
||||||
|
context['storages'] = storage_paginator.get_page(storage_page)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
|
class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
|
||||||
@ -114,6 +129,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
|
|||||||
pk_url_kwarg = 'uuid'
|
pk_url_kwarg = 'uuid'
|
||||||
base_title = ''
|
base_title = ''
|
||||||
navbar_selected = 'Stocks'
|
navbar_selected = 'Stocks'
|
||||||
|
default_pagination_size = 8
|
||||||
|
|
||||||
def get_breadcrumbs(self):
|
def get_breadcrumbs(self):
|
||||||
crumbs = self.object.get_path_components()
|
crumbs = self.object.get_path_components()
|
||||||
@ -125,4 +141,11 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
|
|||||||
self.base_title = 'Stocks / ' + self.object.name
|
self.base_title = 'Stocks / ' + self.object.name
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['breadcrumbs'] = self.get_breadcrumbs()
|
context['breadcrumbs'] = self.get_breadcrumbs()
|
||||||
|
|
||||||
|
storage_page = self.request.GET.get('storage_page')
|
||||||
|
if storage_page is None:
|
||||||
|
storage_page = 1
|
||||||
|
storage_paginator = Paginator(Storage.objects.filter(parent_storage=self.object), self.default_pagination_size)
|
||||||
|
|
||||||
|
context['storages'] = storage_paginator.get_page(storage_page)
|
||||||
return context
|
return context
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% if base.navbar.has_user %}
|
{% if base.navbar.has_user %}
|
||||||
<form action="" method="post" class="ms-auto">
|
<form action="" method="post" class="ms-auto" autocomplete="off">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="search" name="qr_search" id="qr_search_field" aria-label="QR Scan" placeholder="QR Search" class="form-control me-2 text-success">
|
<input type="search" name="qr_search" id="qr_search_field" aria-label="QR Scan" placeholder="QR Search" class="form-control me-2 text-success">
|
||||||
</form>
|
</form>
|
||||||
|
@ -2,18 +2,59 @@
|
|||||||
{% load qr_code %}
|
{% load qr_code %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<nav aria-label="breadcrumb" class="fs-4">
|
<nav aria-label="breadcrumb" class="fs-4">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"></li>
|
<li class="breadcrumb-item"></li>
|
||||||
{% for crumb in breadcrumbs %}
|
{% for crumb in breadcrumbs %}
|
||||||
<li class="breadcrumb-item"><a href="{% url 'parts-stocks-detail' uuid=crumb.id %}">{{crumb.name}}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'parts-stocks-detail' uuid=crumb.id %}">{{crumb.name}}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{object.name}}</li>
|
<li class="breadcrumb-item active" aria-current="page">{{object.name}}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<h1>Stocks in {{object.get_full_path }}</h1>
|
<div class="row">
|
||||||
{% qr_from_text object.get_qr_code size="m" image_format="svg" %}
|
<div class="col-md">
|
||||||
|
{% qr_from_text object.get_qr_code size="m" image_format="svg" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md">
|
||||||
|
{% if object.parent_storage %}
|
||||||
|
<h1>Sub-Storages <a class="btn btn-secondary" href="{% url 'parts-stocks-detail' uuid=object.parent_storage.id %}">Parent Storage</a></h1>
|
||||||
|
{% else %}
|
||||||
|
<h1>Sub-Storages <a class="btn btn-secondary" href="{% url 'parts-stocks'%}">Stock Overview</a></h1>
|
||||||
|
{% endif %}
|
||||||
|
<div class="list-group">
|
||||||
|
{% for storage in storages %}
|
||||||
|
<a href="{% url 'parts-stocks-detail' uuid=storage.id %}" class="text-decoration-none">
|
||||||
|
<li class="list-group-item list-group-item-action justify-content-between align-items-center d-flex">
|
||||||
|
<div>
|
||||||
|
<h5>{{storage.name}}</h5>
|
||||||
|
Responsible: {{ storage.responsible }}
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-primary rounded-pill">{{storage.get_total_stock_amount}}</span>
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Storage Navigation">
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if storages.number > 1 %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.previous_page_number}}">«</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.previous_page_number}}">{{storages.previous_page_number}}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">«</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="page-item active"><span class="page-link">{{storages.number}}</span></li>
|
||||||
|
{% if storages.paginator.num_pages > storages.number %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.next_page_number}}">{{storages.next_page_number}}</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.next_page_number}}">»</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">»</span></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
@ -31,6 +31,24 @@
|
|||||||
</li></a>
|
</li></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<nav aria-label="Low Stock Navigation">
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if low_stocks.number > 1 %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?low_stock_page={{low_stocks.previous_page_number}}">«</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?low_stock_page={{low_stocks.previous_page_number}}">{{low_stocks.previous_page_number}}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">«</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="page-item active"><span class="page-link">{{low_stocks.number}}</span></li>
|
||||||
|
{% if low_stocks.paginator.num_pages > low_stocks.number %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?low_stock_page={{low_stocks.next_page_number}}">{{low_stocks.next_page_number}}</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?low_stock_page={{low_stocks.next_page_number}}">»</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">»</span></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<h1>Storages</h1>
|
<h1>Storages</h1>
|
||||||
@ -47,6 +65,24 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<nav aria-label="Storage Navigation">
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if storages.number > 1 %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.previous_page_number}}">«</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.previous_page_number}}">{{storages.previous_page_number}}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">«</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="page-item active"><span class="page-link">{{storages.number}}</span></li>
|
||||||
|
{% if storages.paginator.num_pages > storages.number %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.next_page_number}}">{{storages.next_page_number}}</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?storage_page={{storages.next_page_number}}">»</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">»</span></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user