2019-04-27 12:49:16 +00:00
|
|
|
"""
|
|
|
|
Django views for interacting with Stock app
|
|
|
|
"""
|
|
|
|
|
2018-04-27 15:16:47 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
from django.views.generic import DetailView, ListView
|
2019-04-18 13:28:46 +00:00
|
|
|
from django.forms.models import model_to_dict
|
2019-04-29 08:35:16 +00:00
|
|
|
from django.forms import HiddenInput
|
2018-04-15 10:10:49 +00:00
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
|
|
|
|
2018-04-15 13:27:56 +00:00
|
|
|
from part.models import Part
|
2019-04-25 12:11:10 +00:00
|
|
|
from .models import StockItem, StockLocation, StockItemTracking
|
2018-04-14 05:13:16 +00:00
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
from .forms import EditStockLocationForm
|
2018-04-29 15:00:18 +00:00
|
|
|
from .forms import CreateStockItemForm
|
2018-04-15 10:10:49 +00:00
|
|
|
from .forms import EditStockItemForm
|
2018-04-29 15:00:18 +00:00
|
|
|
from .forms import MoveStockItemForm
|
2018-04-30 11:03:25 +00:00
|
|
|
from .forms import StocktakeForm
|
2018-04-15 15:02:17 +00:00
|
|
|
|
2019-04-13 23:23:24 +00:00
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
class StockIndex(ListView):
|
2019-04-27 12:49:16 +00:00
|
|
|
""" StockIndex view loads all StockLocation and StockItem object
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
2018-04-15 10:10:49 +00:00
|
|
|
model = StockItem
|
2018-05-04 09:28:28 +00:00
|
|
|
template_name = 'stock/location.html'
|
2018-04-27 12:59:08 +00:00
|
|
|
context_obect_name = 'locations'
|
2018-04-15 10:10:49 +00:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super(StockIndex, self).get_context_data(**kwargs).copy()
|
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
# Return all top-level locations
|
2018-04-15 10:10:49 +00:00
|
|
|
locations = StockLocation.objects.filter(parent=None)
|
|
|
|
|
|
|
|
context['locations'] = locations
|
2018-04-27 12:59:08 +00:00
|
|
|
context['items'] = StockItem.objects.all()
|
2018-04-15 10:10:49 +00:00
|
|
|
|
|
|
|
return context
|
|
|
|
|
2018-04-15 15:02:17 +00:00
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
class StockLocationDetail(DetailView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
Detailed view of a single StockLocation object
|
|
|
|
"""
|
2019-04-13 23:39:01 +00:00
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
context_object_name = 'location'
|
|
|
|
template_name = 'stock/location.html'
|
|
|
|
queryset = StockLocation.objects.all()
|
|
|
|
model = StockLocation
|
|
|
|
|
|
|
|
|
|
|
|
class StockItemDetail(DetailView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
Detailed view of a single StockItem object
|
|
|
|
"""
|
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
context_object_name = 'item'
|
|
|
|
template_name = 'stock/item.html'
|
|
|
|
queryset = StockItem.objects.all()
|
|
|
|
model = StockItem
|
|
|
|
|
2018-04-14 05:13:16 +00:00
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
class StockLocationEdit(AjaxUpdateView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View for editing details of a StockLocation.
|
|
|
|
This view is used with the EditStockLocationForm to deliver a modal form to the web view
|
|
|
|
"""
|
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
model = StockLocation
|
|
|
|
form_class = EditStockLocationForm
|
|
|
|
context_object_name = 'location'
|
2018-04-27 12:59:08 +00:00
|
|
|
ajax_template_name = 'modal_form.html'
|
|
|
|
ajax_form_title = 'Edit Stock Location'
|
2018-04-14 05:13:16 +00:00
|
|
|
|
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
class StockItemEdit(AjaxUpdateView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View for editing details of a single StockItem
|
|
|
|
"""
|
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
model = StockItem
|
|
|
|
form_class = EditStockItemForm
|
|
|
|
context_object_name = 'item'
|
2019-04-16 12:32:43 +00:00
|
|
|
ajax_template_name = 'modal_form.html'
|
2018-04-27 12:59:08 +00:00
|
|
|
ajax_form_title = 'Edit Stock Item'
|
2018-04-14 05:13:16 +00:00
|
|
|
|
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
class StockLocationCreate(AjaxCreateView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View for creating a new StockLocation
|
|
|
|
A parent location (another StockLocation object) can be passed as a query parameter
|
|
|
|
"""
|
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
model = StockLocation
|
|
|
|
form_class = EditStockLocationForm
|
|
|
|
context_object_name = 'location'
|
2018-04-27 12:59:08 +00:00
|
|
|
ajax_template_name = 'modal_form.html'
|
|
|
|
ajax_form_title = 'Create new Stock Location'
|
2018-04-14 05:13:16 +00:00
|
|
|
|
2018-04-15 13:27:56 +00:00
|
|
|
def get_initial(self):
|
|
|
|
initials = super(StockLocationCreate, self).get_initial().copy()
|
|
|
|
|
|
|
|
loc_id = self.request.GET.get('location', None)
|
|
|
|
|
|
|
|
if loc_id:
|
2019-04-28 01:09:19 +00:00
|
|
|
try:
|
|
|
|
initials['parent'] = StockLocation.objects.get(pk=loc_id)
|
|
|
|
except StockLocation.DoesNotExist:
|
|
|
|
pass
|
2018-04-15 13:27:56 +00:00
|
|
|
|
|
|
|
return initials
|
|
|
|
|
2018-04-14 05:13:16 +00:00
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
class StockItemCreate(AjaxCreateView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View for creating a new StockItem
|
|
|
|
Parameters can be pre-filled by passing query items:
|
|
|
|
- part: The part of which the new StockItem is an instance
|
|
|
|
- location: The location of the new StockItem
|
|
|
|
"""
|
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
model = StockItem
|
2018-04-29 15:00:18 +00:00
|
|
|
form_class = CreateStockItemForm
|
2018-04-15 10:10:49 +00:00
|
|
|
context_object_name = 'item'
|
2018-04-27 12:59:08 +00:00
|
|
|
ajax_template_name = 'modal_form.html'
|
|
|
|
ajax_form_title = 'Create new Stock Item'
|
2018-04-14 05:13:16 +00:00
|
|
|
|
2019-04-28 01:24:26 +00:00
|
|
|
def get_form(self):
|
|
|
|
""" Get form for StockItem creation.
|
|
|
|
Overrides the default get_form() method to intelligently limit
|
|
|
|
ForeignKey choices based on other selections
|
|
|
|
"""
|
|
|
|
|
|
|
|
form = super(AjaxCreateView, self).get_form()
|
|
|
|
|
|
|
|
# If the user has selected a Part, limit choices for SupplierPart
|
2019-05-03 13:52:30 +00:00
|
|
|
if form['part'].value():
|
|
|
|
part_id = form['part'].value()
|
|
|
|
|
|
|
|
try:
|
|
|
|
part = Part.objects.get(id=part_id)
|
|
|
|
parts = form.fields['supplier_part'].queryset
|
|
|
|
parts = parts.filter(part=part.id)
|
|
|
|
form.fields['supplier_part'].queryset = parts
|
|
|
|
except Part.DoesNotExist:
|
|
|
|
pass
|
2019-04-28 01:24:26 +00:00
|
|
|
|
2019-04-29 08:35:16 +00:00
|
|
|
# Hide the 'part' field
|
|
|
|
form.fields['part'].widget = HiddenInput()
|
|
|
|
|
2019-04-28 01:24:26 +00:00
|
|
|
# Otherwise if the user has selected a SupplierPart, we know what Part they meant!
|
|
|
|
elif form['supplier_part'].value() is not None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return form
|
|
|
|
|
2018-04-15 13:27:56 +00:00
|
|
|
def get_initial(self):
|
2019-04-28 01:09:19 +00:00
|
|
|
""" Provide initial data to create a new StockItem object
|
|
|
|
"""
|
2019-04-18 13:28:46 +00:00
|
|
|
|
|
|
|
# Is the client attempting to copy an existing stock item?
|
|
|
|
item_to_copy = self.request.GET.get('copy', None)
|
|
|
|
|
|
|
|
if item_to_copy:
|
|
|
|
try:
|
|
|
|
original = StockItem.objects.get(pk=item_to_copy)
|
|
|
|
initials = model_to_dict(original)
|
|
|
|
self.ajax_form_title = "Copy Stock Item"
|
|
|
|
except StockItem.DoesNotExist:
|
|
|
|
initials = super(StockItemCreate, self).get_initial().copy()
|
|
|
|
|
|
|
|
else:
|
|
|
|
initials = super(StockItemCreate, self).get_initial().copy()
|
2018-04-15 13:27:56 +00:00
|
|
|
|
|
|
|
part_id = self.request.GET.get('part', None)
|
|
|
|
loc_id = self.request.GET.get('location', None)
|
|
|
|
|
2019-04-28 01:09:19 +00:00
|
|
|
# Part field has been specified
|
2018-04-15 13:27:56 +00:00
|
|
|
if part_id:
|
2019-04-28 01:09:19 +00:00
|
|
|
try:
|
|
|
|
part = Part.objects.get(pk=part_id)
|
|
|
|
initials['part'] = part
|
2018-04-17 08:23:24 +00:00
|
|
|
initials['location'] = part.default_location
|
|
|
|
initials['supplier_part'] = part.default_supplier
|
2019-04-28 01:09:19 +00:00
|
|
|
except Part.DoesNotExist:
|
|
|
|
pass
|
2018-04-15 13:27:56 +00:00
|
|
|
|
2019-04-28 01:09:19 +00:00
|
|
|
# Location has been specified
|
2018-04-15 13:27:56 +00:00
|
|
|
if loc_id:
|
2019-04-28 01:09:19 +00:00
|
|
|
try:
|
|
|
|
initials['location'] = StockLocation.objects.get(pk=loc_id)
|
|
|
|
except StockLocation.DoesNotExist:
|
|
|
|
pass
|
2018-04-15 13:27:56 +00:00
|
|
|
|
|
|
|
return initials
|
|
|
|
|
2018-04-14 05:13:16 +00:00
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
class StockLocationDelete(AjaxDeleteView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View to delete a StockLocation
|
|
|
|
Presents a deletion confirmation form to the user
|
|
|
|
"""
|
|
|
|
|
2018-04-15 10:10:49 +00:00
|
|
|
model = StockLocation
|
2018-04-15 13:27:56 +00:00
|
|
|
success_url = '/stock'
|
|
|
|
template_name = 'stock/location_delete.html'
|
|
|
|
context_object_name = 'location'
|
2018-04-27 12:59:08 +00:00
|
|
|
ajax_form_title = 'Delete Stock Location'
|
2018-04-14 05:13:16 +00:00
|
|
|
|
|
|
|
|
2018-04-27 12:59:08 +00:00
|
|
|
class StockItemDelete(AjaxDeleteView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View to delete a StockItem
|
|
|
|
Presents a deletion confirmation form to the user
|
|
|
|
"""
|
|
|
|
|
2018-04-15 13:27:56 +00:00
|
|
|
model = StockItem
|
2018-04-15 10:10:49 +00:00
|
|
|
success_url = '/stock/'
|
2019-04-28 01:24:26 +00:00
|
|
|
ajax_template_name = 'stock/item_delete.html'
|
2018-04-15 13:27:56 +00:00
|
|
|
context_object_name = 'item'
|
2018-04-27 12:59:08 +00:00
|
|
|
ajax_form_title = 'Delete Stock Item'
|
2018-04-29 15:00:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
class StockItemMove(AjaxUpdateView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View to move a StockItem from one location to another
|
|
|
|
Performs some data validation to prevent illogical stock moves
|
|
|
|
"""
|
|
|
|
|
2018-04-29 15:00:18 +00:00
|
|
|
model = StockItem
|
2019-04-28 01:24:26 +00:00
|
|
|
ajax_template_name = 'modal_form.html'
|
2018-04-29 15:00:18 +00:00
|
|
|
context_object_name = 'item'
|
|
|
|
ajax_form_title = 'Move Stock Item'
|
|
|
|
form_class = MoveStockItemForm
|
|
|
|
|
2018-04-30 11:36:50 +00:00
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
form = self.form_class(request.POST, instance=self.get_object())
|
|
|
|
|
|
|
|
if form.is_valid():
|
2018-05-06 11:53:06 +00:00
|
|
|
|
2019-04-12 14:08:13 +00:00
|
|
|
obj = self.get_object()
|
2018-04-30 11:36:50 +00:00
|
|
|
|
|
|
|
try:
|
2019-04-12 14:08:13 +00:00
|
|
|
loc_id = form['location'].value()
|
|
|
|
|
|
|
|
if loc_id:
|
|
|
|
loc = StockLocation.objects.get(pk=form['location'].value())
|
|
|
|
if str(loc.pk) == str(obj.pk):
|
|
|
|
form.errors['location'] = ['Item is already in this location']
|
|
|
|
else:
|
|
|
|
obj.move(loc, form['note'].value(), request.user)
|
|
|
|
else:
|
|
|
|
form.errors['location'] = ['Cannot move to an empty location']
|
|
|
|
|
2018-04-30 11:36:50 +00:00
|
|
|
except StockLocation.DoesNotExist:
|
2019-04-12 14:08:13 +00:00
|
|
|
form.errors['location'] = ['Location does not exist']
|
2018-04-30 11:36:50 +00:00
|
|
|
|
|
|
|
data = {
|
2019-04-12 14:08:13 +00:00
|
|
|
'form_valid': form.is_valid() and len(form.errors) == 0,
|
2018-04-30 11:36:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return self.renderJsonResponse(request, form, data)
|
|
|
|
|
2018-04-29 15:00:18 +00:00
|
|
|
|
2018-04-30 11:03:25 +00:00
|
|
|
class StockItemStocktake(AjaxUpdateView):
|
2019-04-13 23:23:24 +00:00
|
|
|
"""
|
|
|
|
View to perform stocktake on a single StockItem
|
|
|
|
Updates the quantity, which will also create a new StockItemTracking item
|
|
|
|
"""
|
|
|
|
|
2018-04-30 11:03:25 +00:00
|
|
|
model = StockItem
|
|
|
|
template_name = 'modal_form.html'
|
|
|
|
context_object_name = 'item'
|
|
|
|
ajax_form_title = 'Item stocktake'
|
|
|
|
form_class = StocktakeForm
|
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
|
|
|
|
form = self.form_class(request.POST, instance=self.get_object())
|
|
|
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
2018-04-30 11:36:50 +00:00
|
|
|
obj = self.get_object()
|
2018-04-30 11:03:25 +00:00
|
|
|
|
2018-04-30 11:36:50 +00:00
|
|
|
obj.stocktake(form.data['quantity'], request.user)
|
2018-04-30 11:03:25 +00:00
|
|
|
|
|
|
|
data = {
|
|
|
|
'form_valid': form.is_valid()
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.renderJsonResponse(request, form, data)
|
2019-04-25 12:11:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
class StockTrackingIndex(ListView):
|
|
|
|
"""
|
|
|
|
StockTrackingIndex provides a page to display StockItemTracking objects
|
|
|
|
"""
|
|
|
|
|
|
|
|
model = StockItemTracking
|
|
|
|
template_name = 'stock/tracking.html'
|
|
|
|
context_object_name = 'items'
|