mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
Prune a lot of dead code
This commit is contained in:
parent
bd3d6f47a1
commit
5cf30a850d
@ -95,24 +95,6 @@ class EditPartParameterTemplateForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditCategoryForm(HelperForm):
|
|
||||||
""" Form for editing a PartCategory object """
|
|
||||||
|
|
||||||
field_prefix = {
|
|
||||||
'default_keywords': 'fa-key',
|
|
||||||
}
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = PartCategory
|
|
||||||
fields = [
|
|
||||||
'parent',
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'default_location',
|
|
||||||
'default_keywords',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EditCategoryParameterTemplateForm(HelperForm):
|
class EditCategoryParameterTemplateForm(HelperForm):
|
||||||
""" Form for editing a PartCategoryParameterTemplate object """
|
""" Form for editing a PartCategoryParameterTemplate object """
|
||||||
|
|
||||||
|
@ -1001,45 +1001,6 @@ class CategoryDetail(InvenTreeRoleMixin, DetailView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class CategoryEdit(AjaxUpdateView):
|
|
||||||
"""
|
|
||||||
Update view to edit a PartCategory
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = PartCategory
|
|
||||||
form_class = part_forms.EditCategoryForm
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Edit Part Category')
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(CategoryEdit, self).get_context_data(**kwargs).copy()
|
|
||||||
|
|
||||||
try:
|
|
||||||
context['category'] = self.get_object()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
""" Customize form data for PartCategory editing.
|
|
||||||
|
|
||||||
Limit the choices for 'parent' field to those which make sense
|
|
||||||
"""
|
|
||||||
|
|
||||||
form = super(AjaxUpdateView, self).get_form()
|
|
||||||
|
|
||||||
category = self.get_object()
|
|
||||||
|
|
||||||
# Remove any invalid choices for the parent category part
|
|
||||||
parent_choices = PartCategory.objects.all()
|
|
||||||
parent_choices = parent_choices.exclude(id__in=category.getUniqueChildren())
|
|
||||||
|
|
||||||
form.fields['parent'].queryset = parent_choices
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
|
|
||||||
class CategoryDelete(AjaxDeleteView):
|
class CategoryDelete(AjaxDeleteView):
|
||||||
"""
|
"""
|
||||||
Delete view to delete a PartCategory
|
Delete view to delete a PartCategory
|
||||||
|
@ -32,23 +32,6 @@ class ReturnStockItemForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditStockLocationForm(HelperForm):
|
|
||||||
"""
|
|
||||||
Form for editing a StockLocation
|
|
||||||
|
|
||||||
TODO: Migrate this form to the modern API forms interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = StockLocation
|
|
||||||
fields = [
|
|
||||||
'name',
|
|
||||||
'parent',
|
|
||||||
'description',
|
|
||||||
'owner',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ConvertStockItemForm(HelperForm):
|
class ConvertStockItemForm(HelperForm):
|
||||||
"""
|
"""
|
||||||
Form for converting a StockItem to a variant of its current part.
|
Form for converting a StockItem to a variant of its current part.
|
||||||
@ -63,159 +46,6 @@ class ConvertStockItemForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CreateStockItemForm(HelperForm):
|
|
||||||
"""
|
|
||||||
Form for creating a new StockItem
|
|
||||||
|
|
||||||
TODO: Migrate this form to the modern API forms interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
expiry_date = DatePickerFormField(
|
|
||||||
label=_('Expiry Date'),
|
|
||||||
help_text=_('Expiration date for this stock item'),
|
|
||||||
)
|
|
||||||
|
|
||||||
serial_numbers = forms.CharField(label=_('Serial Numbers'), required=False, help_text=_('Enter unique serial numbers (or leave blank)'))
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
self.field_prefix = {
|
|
||||||
'serial_numbers': 'fa-hashtag',
|
|
||||||
'link': 'fa-link',
|
|
||||||
}
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = StockItem
|
|
||||||
fields = [
|
|
||||||
'part',
|
|
||||||
'supplier_part',
|
|
||||||
'location',
|
|
||||||
'quantity',
|
|
||||||
'batch',
|
|
||||||
'serial_numbers',
|
|
||||||
'packaging',
|
|
||||||
'purchase_price',
|
|
||||||
'expiry_date',
|
|
||||||
'link',
|
|
||||||
'delete_on_deplete',
|
|
||||||
'status',
|
|
||||||
'owner',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Custom clean to prevent complex StockItem.clean() logic from running (yet)
|
|
||||||
def full_clean(self):
|
|
||||||
self._errors = ErrorDict()
|
|
||||||
|
|
||||||
if not self.is_bound: # Stop further processing.
|
|
||||||
return
|
|
||||||
|
|
||||||
self.cleaned_data = {}
|
|
||||||
|
|
||||||
# If the form is permitted to be empty, and none of the form data has
|
|
||||||
# changed from the initial data, short circuit any validation.
|
|
||||||
if self.empty_permitted and not self.has_changed():
|
|
||||||
return
|
|
||||||
|
|
||||||
# Don't run _post_clean() as this will run StockItem.clean()
|
|
||||||
self._clean_fields()
|
|
||||||
self._clean_form()
|
|
||||||
|
|
||||||
|
|
||||||
class SerializeStockForm(HelperForm):
|
|
||||||
"""
|
|
||||||
Form for serializing a StockItem.
|
|
||||||
|
|
||||||
TODO: Migrate this form to the modern API forms interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
destination = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label=_('Destination'), required=True, help_text=_('Destination for serialized stock (by default, will remain in current location)'))
|
|
||||||
|
|
||||||
serial_numbers = forms.CharField(label=_('Serial numbers'), required=True, help_text=_('Unique serial numbers (must match quantity)'))
|
|
||||||
|
|
||||||
note = forms.CharField(label=_('Notes'), required=False, help_text=_('Add transaction note (optional)'))
|
|
||||||
|
|
||||||
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
# Extract the stock item
|
|
||||||
item = kwargs.pop('item', None)
|
|
||||||
|
|
||||||
if item:
|
|
||||||
self.field_placeholder['serial_numbers'] = item.part.getSerialNumberString(item.quantity)
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = StockItem
|
|
||||||
|
|
||||||
fields = [
|
|
||||||
'quantity',
|
|
||||||
'serial_numbers',
|
|
||||||
'destination',
|
|
||||||
'note',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class UninstallStockForm(forms.ModelForm):
|
|
||||||
"""
|
|
||||||
Form for uninstalling a stock item which is installed in another item.
|
|
||||||
|
|
||||||
TODO: Migrate this form to the modern API forms interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
location = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label=_('Location'), help_text=_('Destination location for uninstalled items'))
|
|
||||||
|
|
||||||
note = forms.CharField(label=_('Notes'), required=False, help_text=_('Add transaction note (optional)'))
|
|
||||||
|
|
||||||
confirm = forms.BooleanField(required=False, initial=False, label=_('Confirm uninstall'), help_text=_('Confirm removal of installed stock items'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = StockItem
|
|
||||||
|
|
||||||
fields = [
|
|
||||||
'location',
|
|
||||||
'note',
|
|
||||||
'confirm',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EditStockItemForm(HelperForm):
|
|
||||||
""" Form for editing a StockItem object.
|
|
||||||
Note that not all fields can be edited here (even if they can be specified during creation.
|
|
||||||
|
|
||||||
location - Must be updated in a 'move' transaction
|
|
||||||
quantity - Must be updated in a 'stocktake' transaction
|
|
||||||
part - Cannot be edited after creation
|
|
||||||
|
|
||||||
TODO: Migrate this form to the modern API forms interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
expiry_date = DatePickerFormField(
|
|
||||||
label=_('Expiry Date'),
|
|
||||||
help_text=_('Expiration date for this stock item'),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = StockItem
|
|
||||||
|
|
||||||
fields = [
|
|
||||||
'supplier_part',
|
|
||||||
'serial',
|
|
||||||
'batch',
|
|
||||||
'status',
|
|
||||||
'expiry_date',
|
|
||||||
'purchase_price',
|
|
||||||
'packaging',
|
|
||||||
'link',
|
|
||||||
'delete_on_deplete',
|
|
||||||
'owner',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class TrackingEntryForm(HelperForm):
|
class TrackingEntryForm(HelperForm):
|
||||||
"""
|
"""
|
||||||
Form for creating / editing a StockItemTracking object.
|
Form for creating / editing a StockItemTracking object.
|
||||||
|
@ -19,7 +19,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from moneyed import CURRENCIES
|
from moneyed import CURRENCIES
|
||||||
|
|
||||||
from InvenTree.views import AjaxView
|
|
||||||
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
||||||
from InvenTree.views import QRCodeView
|
from InvenTree.views import QRCodeView
|
||||||
from InvenTree.views import InvenTreeRoleMixin
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
@ -135,139 +134,6 @@ class StockItemDetail(InvenTreeRoleMixin, DetailView):
|
|||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class StockLocationEdit(AjaxUpdateView):
|
|
||||||
"""
|
|
||||||
View for editing details of a StockLocation.
|
|
||||||
This view is used with the EditStockLocationForm to deliver a modal form to the web view
|
|
||||||
|
|
||||||
TODO: Remove this code as location editing has been migrated to the API forms
|
|
||||||
- Have to still validate that all form functionality (as below) as been ported
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = StockLocation
|
|
||||||
form_class = StockForms.EditStockLocationForm
|
|
||||||
context_object_name = 'location'
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Edit Stock Location')
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
""" Customize form data for StockLocation editing.
|
|
||||||
|
|
||||||
Limit the choices for 'parent' field to those which make sense.
|
|
||||||
If ownership control is enabled and location has parent, disable owner field.
|
|
||||||
"""
|
|
||||||
|
|
||||||
form = super(AjaxUpdateView, self).get_form()
|
|
||||||
|
|
||||||
location = self.get_object()
|
|
||||||
|
|
||||||
# Remove any invalid choices for the 'parent' field
|
|
||||||
parent_choices = StockLocation.objects.all()
|
|
||||||
parent_choices = parent_choices.exclude(id__in=location.getUniqueChildren())
|
|
||||||
|
|
||||||
form.fields['parent'].queryset = parent_choices
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if not stock_ownership_control:
|
|
||||||
# Hide owner field
|
|
||||||
form.fields['owner'].widget = HiddenInput()
|
|
||||||
else:
|
|
||||||
# Get location's owner
|
|
||||||
location_owner = location.owner
|
|
||||||
|
|
||||||
if location_owner:
|
|
||||||
if location.parent:
|
|
||||||
try:
|
|
||||||
# If location has parent and owner: automatically select parent's owner
|
|
||||||
parent_owner = location.parent.owner
|
|
||||||
form.fields['owner'].initial = parent_owner
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# If current owner exists: automatically select it
|
|
||||||
form.fields['owner'].initial = location_owner
|
|
||||||
|
|
||||||
# Update queryset or disable field (only if not admin)
|
|
||||||
if not self.request.user.is_superuser:
|
|
||||||
if type(location_owner.owner) is Group:
|
|
||||||
user_as_owner = Owner.get_owner(self.request.user)
|
|
||||||
queryset = location_owner.get_related_owners(include_group=True)
|
|
||||||
|
|
||||||
if user_as_owner not in queryset:
|
|
||||||
# Only owners or admin can change current owner
|
|
||||||
form.fields['owner'].disabled = True
|
|
||||||
else:
|
|
||||||
form.fields['owner'].queryset = queryset
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def save(self, object, form, **kwargs):
|
|
||||||
""" If location has children and ownership control is enabled:
|
|
||||||
- update owner of all children location of this location
|
|
||||||
- update owner for all stock items at this location
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.object = form.save()
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if stock_ownership_control and self.object.owner:
|
|
||||||
# Get authorized users
|
|
||||||
authorized_owners = self.object.owner.get_related_owners()
|
|
||||||
|
|
||||||
# Update children locations
|
|
||||||
children_locations = self.object.get_children()
|
|
||||||
for child in children_locations:
|
|
||||||
# Check if current owner is subset of new owner
|
|
||||||
if child.owner and authorized_owners:
|
|
||||||
if child.owner in authorized_owners:
|
|
||||||
continue
|
|
||||||
|
|
||||||
child.owner = self.object.owner
|
|
||||||
child.save()
|
|
||||||
|
|
||||||
# Update stock items
|
|
||||||
stock_items = self.object.get_stock_items()
|
|
||||||
|
|
||||||
for stock_item in stock_items:
|
|
||||||
# Check if current owner is subset of new owner
|
|
||||||
if stock_item.owner and authorized_owners:
|
|
||||||
if stock_item.owner in authorized_owners:
|
|
||||||
continue
|
|
||||||
|
|
||||||
stock_item.owner = self.object.owner
|
|
||||||
stock_item.save()
|
|
||||||
|
|
||||||
return self.object
|
|
||||||
|
|
||||||
def validate(self, item, form):
|
|
||||||
""" Check that owner is set if stock ownership control is enabled """
|
|
||||||
|
|
||||||
parent = form.cleaned_data.get('parent', None)
|
|
||||||
|
|
||||||
owner = form.cleaned_data.get('owner', None)
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if stock_ownership_control:
|
|
||||||
if not owner and not self.request.user.is_superuser:
|
|
||||||
form.add_error('owner', _('Owner is required (ownership control is enabled)'))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
if parent.owner:
|
|
||||||
if parent.owner != owner:
|
|
||||||
error = f'Owner requires to be equivalent to parent\'s owner ({parent.owner})'
|
|
||||||
form.add_error('owner', error)
|
|
||||||
except AttributeError:
|
|
||||||
# No parent
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocationQRCode(QRCodeView):
|
class StockLocationQRCode(QRCodeView):
|
||||||
""" View for displaying a QR code for a StockLocation object """
|
""" View for displaying a QR code for a StockLocation object """
|
||||||
|
|
||||||
@ -366,261 +232,6 @@ class StockItemQRCode(QRCodeView):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class StockItemUninstall(AjaxView, FormMixin):
|
|
||||||
"""
|
|
||||||
View for uninstalling one or more StockItems,
|
|
||||||
which are installed in another stock item.
|
|
||||||
|
|
||||||
Stock items are uninstalled into a location,
|
|
||||||
defaulting to the location that they were "in" before they were installed.
|
|
||||||
|
|
||||||
If multiple default locations are detected,
|
|
||||||
leave the final location up to the user.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ajax_template_name = 'stock/stock_uninstall.html'
|
|
||||||
ajax_form_title = _('Uninstall Stock Items')
|
|
||||||
form_class = StockForms.UninstallStockForm
|
|
||||||
role_required = 'stock.change'
|
|
||||||
|
|
||||||
# List of stock items to uninstall (initially empty)
|
|
||||||
stock_items = []
|
|
||||||
|
|
||||||
def get_stock_items(self):
|
|
||||||
|
|
||||||
return self.stock_items
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
|
|
||||||
initials = super().get_initial().copy()
|
|
||||||
|
|
||||||
# Keep track of the current locations of stock items
|
|
||||||
current_locations = set()
|
|
||||||
|
|
||||||
# Keep track of the default locations for stock items
|
|
||||||
default_locations = set()
|
|
||||||
|
|
||||||
for item in self.stock_items:
|
|
||||||
|
|
||||||
if item.location:
|
|
||||||
current_locations.add(item.location)
|
|
||||||
|
|
||||||
if item.part.default_location:
|
|
||||||
default_locations.add(item.part.default_location)
|
|
||||||
|
|
||||||
if len(current_locations) == 1:
|
|
||||||
# If the selected stock items are currently in a single location,
|
|
||||||
# select that location as the destination.
|
|
||||||
initials['location'] = next(iter(current_locations))
|
|
||||||
elif len(current_locations) == 0:
|
|
||||||
# There are no current locations set
|
|
||||||
if len(default_locations) == 1:
|
|
||||||
# Select the single default location
|
|
||||||
initials['location'] = next(iter(default_locations))
|
|
||||||
|
|
||||||
return initials
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
""" Extract list of stock items, which are supplied as a list,
|
|
||||||
e.g. items[]=1,2,3
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 'items[]' in request.GET:
|
|
||||||
self.stock_items = StockItem.objects.filter(id__in=request.GET.getlist('items[]'))
|
|
||||||
else:
|
|
||||||
self.stock_items = []
|
|
||||||
|
|
||||||
return self.renderJsonResponse(request, self.get_form())
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Extract a list of stock items which are included as hidden inputs in the form data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
items = []
|
|
||||||
|
|
||||||
for item in self.request.POST:
|
|
||||||
if item.startswith('stock-item-'):
|
|
||||||
pk = item.replace('stock-item-', '')
|
|
||||||
|
|
||||||
try:
|
|
||||||
stock_item = StockItem.objects.get(pk=pk)
|
|
||||||
items.append(stock_item)
|
|
||||||
except (ValueError, StockItem.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.stock_items = items
|
|
||||||
|
|
||||||
# Assume the form is valid, until it isn't!
|
|
||||||
valid = True
|
|
||||||
|
|
||||||
confirmed = str2bool(request.POST.get('confirm'))
|
|
||||||
|
|
||||||
note = request.POST.get('note', '')
|
|
||||||
|
|
||||||
location = request.POST.get('location', None)
|
|
||||||
|
|
||||||
if location:
|
|
||||||
try:
|
|
||||||
location = StockLocation.objects.get(pk=location)
|
|
||||||
except (ValueError, StockLocation.DoesNotExist):
|
|
||||||
location = None
|
|
||||||
|
|
||||||
if not location:
|
|
||||||
# Location is required!
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
form = self.get_form()
|
|
||||||
|
|
||||||
if not confirmed:
|
|
||||||
valid = False
|
|
||||||
form.add_error('confirm', _('Confirm stock adjustment'))
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'form_valid': valid,
|
|
||||||
}
|
|
||||||
|
|
||||||
if valid:
|
|
||||||
# Ok, now let's actually uninstall the stock items
|
|
||||||
for item in self.stock_items:
|
|
||||||
item.uninstallIntoLocation(location, request.user, note)
|
|
||||||
|
|
||||||
data['success'] = _('Uninstalled stock items')
|
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form=form, data=data)
|
|
||||||
|
|
||||||
def get_context_data(self):
|
|
||||||
|
|
||||||
context = super().get_context_data()
|
|
||||||
|
|
||||||
context['stock_items'] = self.get_stock_items()
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemEdit(AjaxUpdateView):
|
|
||||||
"""
|
|
||||||
View for editing details of a single StockItem
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = StockItem
|
|
||||||
form_class = StockForms.EditStockItemForm
|
|
||||||
context_object_name = 'item'
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Edit Stock Item')
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
""" Get form for StockItem editing.
|
|
||||||
|
|
||||||
Limit the choices for supplier_part
|
|
||||||
"""
|
|
||||||
|
|
||||||
form = super(AjaxUpdateView, self).get_form()
|
|
||||||
|
|
||||||
# Hide the "expiry date" field if the feature is not enabled
|
|
||||||
if not common.settings.stock_expiry_enabled():
|
|
||||||
form.fields['expiry_date'].widget = HiddenInput()
|
|
||||||
|
|
||||||
item = self.get_object()
|
|
||||||
|
|
||||||
# If the part cannot be purchased, hide the supplier_part field
|
|
||||||
if not item.part.purchaseable:
|
|
||||||
form.fields['supplier_part'].widget = HiddenInput()
|
|
||||||
|
|
||||||
form.fields.pop('purchase_price')
|
|
||||||
else:
|
|
||||||
query = form.fields['supplier_part'].queryset
|
|
||||||
query = query.filter(part=item.part.id)
|
|
||||||
form.fields['supplier_part'].queryset = query
|
|
||||||
|
|
||||||
# Hide the serial number field if it is not required
|
|
||||||
if not item.part.trackable and not item.serialized:
|
|
||||||
form.fields['serial'].widget = HiddenInput()
|
|
||||||
|
|
||||||
location = item.location
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if not stock_ownership_control:
|
|
||||||
form.fields['owner'].widget = HiddenInput()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
location_owner = location.owner
|
|
||||||
except AttributeError:
|
|
||||||
location_owner = None
|
|
||||||
|
|
||||||
# Check if location has owner
|
|
||||||
if location_owner:
|
|
||||||
form.fields['owner'].initial = location_owner
|
|
||||||
|
|
||||||
# Check location's owner type and filter potential owners
|
|
||||||
if type(location_owner.owner) is Group:
|
|
||||||
user_as_owner = Owner.get_owner(self.request.user)
|
|
||||||
queryset = location_owner.get_related_owners(include_group=True)
|
|
||||||
|
|
||||||
if user_as_owner in queryset:
|
|
||||||
form.fields['owner'].initial = user_as_owner
|
|
||||||
|
|
||||||
form.fields['owner'].queryset = queryset
|
|
||||||
|
|
||||||
elif type(location_owner.owner) is get_user_model():
|
|
||||||
# If location's owner is a user: automatically set owner field and disable it
|
|
||||||
form.fields['owner'].disabled = True
|
|
||||||
form.fields['owner'].initial = location_owner
|
|
||||||
|
|
||||||
try:
|
|
||||||
item_owner = item.owner
|
|
||||||
except AttributeError:
|
|
||||||
item_owner = None
|
|
||||||
|
|
||||||
# Check if item has owner
|
|
||||||
if item_owner:
|
|
||||||
form.fields['owner'].initial = item_owner
|
|
||||||
|
|
||||||
# Check item's owner type and filter potential owners
|
|
||||||
if type(item_owner.owner) is Group:
|
|
||||||
user_as_owner = Owner.get_owner(self.request.user)
|
|
||||||
queryset = item_owner.get_related_owners(include_group=True)
|
|
||||||
|
|
||||||
if user_as_owner in queryset:
|
|
||||||
form.fields['owner'].initial = user_as_owner
|
|
||||||
|
|
||||||
form.fields['owner'].queryset = queryset
|
|
||||||
|
|
||||||
elif type(item_owner.owner) is get_user_model():
|
|
||||||
# If item's owner is a user: automatically set owner field and disable it
|
|
||||||
form.fields['owner'].disabled = True
|
|
||||||
form.fields['owner'].initial = item_owner
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def validate(self, item, form):
|
|
||||||
""" Check that owner is set if stock ownership control is enabled """
|
|
||||||
|
|
||||||
owner = form.cleaned_data.get('owner', None)
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if stock_ownership_control:
|
|
||||||
if not owner and not self.request.user.is_superuser:
|
|
||||||
form.add_error('owner', _('Owner is required (ownership control is enabled)'))
|
|
||||||
|
|
||||||
def save(self, object, form, **kwargs):
|
|
||||||
"""
|
|
||||||
Override the save method, to track the user who updated the model
|
|
||||||
"""
|
|
||||||
|
|
||||||
item = form.save(commit=False)
|
|
||||||
|
|
||||||
item.save(user=self.request.user)
|
|
||||||
|
|
||||||
return item
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemConvert(AjaxUpdateView):
|
class StockItemConvert(AjaxUpdateView):
|
||||||
"""
|
"""
|
||||||
View for 'converting' a StockItem to a variant of its current part.
|
View for 'converting' a StockItem to a variant of its current part.
|
||||||
@ -655,435 +266,6 @@ class StockItemConvert(AjaxUpdateView):
|
|||||||
return stock_item
|
return stock_item
|
||||||
|
|
||||||
|
|
||||||
class StockLocationCreate(AjaxCreateView):
|
|
||||||
"""
|
|
||||||
View for creating a new StockLocation
|
|
||||||
A parent location (another StockLocation object) can be passed as a query parameter
|
|
||||||
|
|
||||||
TODO: Remove this class entirely, as it has been migrated to the API forms
|
|
||||||
- Still need to check that all the functionality (as below) has been implemented
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = StockLocation
|
|
||||||
form_class = StockForms.EditStockLocationForm
|
|
||||||
context_object_name = 'location'
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Create new Stock Location')
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
initials = super(StockLocationCreate, self).get_initial().copy()
|
|
||||||
|
|
||||||
loc_id = self.request.GET.get('location', None)
|
|
||||||
|
|
||||||
if loc_id:
|
|
||||||
try:
|
|
||||||
initials['parent'] = StockLocation.objects.get(pk=loc_id)
|
|
||||||
except StockLocation.DoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return initials
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
""" Disable owner field when:
|
|
||||||
- creating child location
|
|
||||||
- and stock ownership control is enable
|
|
||||||
"""
|
|
||||||
|
|
||||||
form = super().get_form()
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if not stock_ownership_control:
|
|
||||||
# Hide owner field
|
|
||||||
form.fields['owner'].widget = HiddenInput()
|
|
||||||
else:
|
|
||||||
# If user did not selected owner: automatically match to parent's owner
|
|
||||||
if not form['owner'].data:
|
|
||||||
try:
|
|
||||||
parent_id = form['parent'].value()
|
|
||||||
parent = StockLocation.objects.get(pk=parent_id)
|
|
||||||
|
|
||||||
if parent:
|
|
||||||
form.fields['owner'].initial = parent.owner
|
|
||||||
if not self.request.user.is_superuser:
|
|
||||||
form.fields['owner'].disabled = True
|
|
||||||
except StockLocation.DoesNotExist:
|
|
||||||
pass
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def save(self, form):
|
|
||||||
""" If parent location exists then use it to set the owner """
|
|
||||||
|
|
||||||
self.object = form.save(commit=False)
|
|
||||||
|
|
||||||
parent = form.cleaned_data.get('parent', None)
|
|
||||||
|
|
||||||
if parent:
|
|
||||||
# Select parent's owner
|
|
||||||
self.object.owner = parent.owner
|
|
||||||
|
|
||||||
self.object.save()
|
|
||||||
|
|
||||||
return self.object
|
|
||||||
|
|
||||||
def validate(self, item, form):
|
|
||||||
""" Check that owner is set if stock ownership control is enabled """
|
|
||||||
|
|
||||||
parent = form.cleaned_data.get('parent', None)
|
|
||||||
|
|
||||||
owner = form.cleaned_data.get('owner', None)
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if stock_ownership_control:
|
|
||||||
if not owner and not self.request.user.is_superuser:
|
|
||||||
form.add_error('owner', _('Owner is required (ownership control is enabled)'))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
if parent.owner:
|
|
||||||
if parent.owner != owner:
|
|
||||||
error = f'Owner requires to be equivalent to parent\'s owner ({parent.owner})'
|
|
||||||
form.add_error('owner', error)
|
|
||||||
except AttributeError:
|
|
||||||
# No parent
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemCreate(AjaxCreateView):
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
If the parent part is a "tracked" part, provide an option to create uniquely serialized items
|
|
||||||
rather than a bulk quantity of stock items
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = StockItem
|
|
||||||
form_class = StockForms.CreateStockItemForm
|
|
||||||
context_object_name = 'item'
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Create new Stock Item')
|
|
||||||
|
|
||||||
def get_part(self, form=None):
|
|
||||||
"""
|
|
||||||
Attempt to get the "part" associted with this new stockitem.
|
|
||||||
|
|
||||||
- May be passed to the form as a query parameter (e.g. ?part=<id>)
|
|
||||||
- May be passed via the form field itself.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Try to extract from the URL query
|
|
||||||
part_id = self.request.GET.get('part', None)
|
|
||||||
|
|
||||||
if part_id:
|
|
||||||
try:
|
|
||||||
part = Part.objects.get(pk=part_id)
|
|
||||||
return part
|
|
||||||
except (Part.DoesNotExist, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Try to get from the form
|
|
||||||
if form:
|
|
||||||
try:
|
|
||||||
part_id = form['part'].value()
|
|
||||||
part = Part.objects.get(pk=part_id)
|
|
||||||
return part
|
|
||||||
except (Part.DoesNotExist, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Could not extract a part object
|
|
||||||
return None
|
|
||||||
|
|
||||||
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().get_form()
|
|
||||||
|
|
||||||
# Hide the "expiry date" field if the feature is not enabled
|
|
||||||
if not common.settings.stock_expiry_enabled():
|
|
||||||
form.fields['expiry_date'].widget = HiddenInput()
|
|
||||||
|
|
||||||
part = self.get_part(form=form)
|
|
||||||
|
|
||||||
if part is not None:
|
|
||||||
|
|
||||||
# Add placeholder text for the serial number field
|
|
||||||
form.field_placeholder['serial_numbers'] = part.getSerialNumberString()
|
|
||||||
|
|
||||||
form.rebuild_layout()
|
|
||||||
|
|
||||||
if not part.purchaseable:
|
|
||||||
form.fields.pop('purchase_price')
|
|
||||||
|
|
||||||
# Hide the 'part' field (as a valid part is selected)
|
|
||||||
# form.fields['part'].widget = HiddenInput()
|
|
||||||
|
|
||||||
# Trackable parts get special consideration:
|
|
||||||
if part.trackable:
|
|
||||||
form.fields['delete_on_deplete'].disabled = True
|
|
||||||
else:
|
|
||||||
form.fields['serial_numbers'].disabled = True
|
|
||||||
|
|
||||||
# If the part is NOT purchaseable, hide the supplier_part field
|
|
||||||
if not part.purchaseable:
|
|
||||||
form.fields['supplier_part'].widget = HiddenInput()
|
|
||||||
else:
|
|
||||||
# Pre-select the allowable SupplierPart options
|
|
||||||
parts = form.fields['supplier_part'].queryset
|
|
||||||
parts = parts.filter(part=part.id)
|
|
||||||
|
|
||||||
form.fields['supplier_part'].queryset = parts
|
|
||||||
|
|
||||||
# If there is one (and only one) supplier part available, pre-select it
|
|
||||||
all_parts = parts.all()
|
|
||||||
|
|
||||||
if len(all_parts) == 1:
|
|
||||||
|
|
||||||
# TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate
|
|
||||||
form.fields['supplier_part'].initial = all_parts[0].id
|
|
||||||
|
|
||||||
else:
|
|
||||||
# No Part has been selected!
|
|
||||||
# We must not provide *any* options for SupplierPart
|
|
||||||
form.fields['supplier_part'].queryset = SupplierPart.objects.none()
|
|
||||||
|
|
||||||
form.fields['serial_numbers'].disabled = True
|
|
||||||
|
|
||||||
# Otherwise if the user has selected a SupplierPart, we know what Part they meant!
|
|
||||||
if form['supplier_part'].value() is not None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
location = None
|
|
||||||
try:
|
|
||||||
loc_id = form['location'].value()
|
|
||||||
location = StockLocation.objects.get(pk=loc_id)
|
|
||||||
except StockLocation.DoesNotExist:
|
|
||||||
pass
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
if not stock_ownership_control:
|
|
||||||
form.fields['owner'].widget = HiddenInput()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
location_owner = location.owner
|
|
||||||
except AttributeError:
|
|
||||||
location_owner = None
|
|
||||||
|
|
||||||
if location_owner:
|
|
||||||
# Check location's owner type and filter potential owners
|
|
||||||
if type(location_owner.owner) is Group:
|
|
||||||
user_as_owner = Owner.get_owner(self.request.user)
|
|
||||||
queryset = location_owner.get_related_owners()
|
|
||||||
|
|
||||||
if user_as_owner in queryset:
|
|
||||||
form.fields['owner'].initial = user_as_owner
|
|
||||||
|
|
||||||
form.fields['owner'].queryset = queryset
|
|
||||||
|
|
||||||
elif type(location_owner.owner) is get_user_model():
|
|
||||||
# If location's owner is a user: automatically set owner field and disable it
|
|
||||||
form.fields['owner'].disabled = True
|
|
||||||
form.fields['owner'].initial = location_owner
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
""" Provide initial data to create a new StockItem object
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 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 = _("Duplicate Stock Item")
|
|
||||||
except StockItem.DoesNotExist:
|
|
||||||
initials = super(StockItemCreate, self).get_initial().copy()
|
|
||||||
|
|
||||||
else:
|
|
||||||
initials = super(StockItemCreate, self).get_initial().copy()
|
|
||||||
|
|
||||||
part = self.get_part()
|
|
||||||
|
|
||||||
loc_id = self.request.GET.get('location', None)
|
|
||||||
sup_part_id = self.request.GET.get('supplier_part', None)
|
|
||||||
|
|
||||||
location = None
|
|
||||||
supplier_part = None
|
|
||||||
|
|
||||||
if part is not None:
|
|
||||||
initials['part'] = part
|
|
||||||
initials['location'] = part.get_default_location()
|
|
||||||
initials['supplier_part'] = part.default_supplier
|
|
||||||
|
|
||||||
# If the part has a defined expiry period, extrapolate!
|
|
||||||
if part.default_expiry > 0:
|
|
||||||
expiry_date = datetime.now().date() + timedelta(days=part.default_expiry)
|
|
||||||
initials['expiry_date'] = expiry_date
|
|
||||||
|
|
||||||
currency_code = common.settings.currency_code_default()
|
|
||||||
|
|
||||||
# SupplierPart field has been specified
|
|
||||||
# It must match the Part, if that has been supplied
|
|
||||||
if sup_part_id:
|
|
||||||
try:
|
|
||||||
supplier_part = SupplierPart.objects.get(pk=sup_part_id)
|
|
||||||
|
|
||||||
if part is None or supplier_part.part == part:
|
|
||||||
initials['supplier_part'] = supplier_part
|
|
||||||
|
|
||||||
currency_code = supplier_part.supplier.currency_code
|
|
||||||
|
|
||||||
except (ValueError, SupplierPart.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Location has been specified
|
|
||||||
if loc_id:
|
|
||||||
try:
|
|
||||||
location = StockLocation.objects.get(pk=loc_id)
|
|
||||||
initials['location'] = location
|
|
||||||
except (ValueError, StockLocation.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
currency = CURRENCIES.get(currency_code, None)
|
|
||||||
|
|
||||||
if currency:
|
|
||||||
initials['purchase_price'] = (None, currency)
|
|
||||||
|
|
||||||
return initials
|
|
||||||
|
|
||||||
def validate(self, item, form):
|
|
||||||
"""
|
|
||||||
Extra form validation steps
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = form.cleaned_data
|
|
||||||
|
|
||||||
part = data.get('part', None)
|
|
||||||
|
|
||||||
quantity = data.get('quantity', None)
|
|
||||||
|
|
||||||
owner = data.get('owner', None)
|
|
||||||
|
|
||||||
if not part:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not quantity:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
quantity = Decimal(quantity)
|
|
||||||
except (ValueError, InvalidOperation):
|
|
||||||
form.add_error('quantity', _('Invalid quantity provided'))
|
|
||||||
return
|
|
||||||
|
|
||||||
if quantity < 0:
|
|
||||||
form.add_error('quantity', _('Quantity cannot be negative'))
|
|
||||||
|
|
||||||
# Trackable parts are treated differently
|
|
||||||
if part.trackable:
|
|
||||||
sn = data.get('serial_numbers', '')
|
|
||||||
sn = str(sn).strip()
|
|
||||||
|
|
||||||
if len(sn) > 0:
|
|
||||||
try:
|
|
||||||
serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt())
|
|
||||||
except ValidationError as e:
|
|
||||||
serials = None
|
|
||||||
form.add_error('serial_numbers', e.messages)
|
|
||||||
|
|
||||||
if serials is not None:
|
|
||||||
existing = part.find_conflicting_serial_numbers(serials)
|
|
||||||
|
|
||||||
if len(existing) > 0:
|
|
||||||
exists = ','.join([str(x) for x in existing])
|
|
||||||
|
|
||||||
form.add_error(
|
|
||||||
'serial_numbers',
|
|
||||||
_('Serial numbers already exist') + ': ' + exists
|
|
||||||
)
|
|
||||||
|
|
||||||
# Is ownership control enabled?
|
|
||||||
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
|
||||||
|
|
||||||
if stock_ownership_control:
|
|
||||||
# Check if owner is set
|
|
||||||
if not owner and not self.request.user.is_superuser:
|
|
||||||
form.add_error('owner', _('Owner is required (ownership control is enabled)'))
|
|
||||||
return
|
|
||||||
|
|
||||||
def save(self, form, **kwargs):
|
|
||||||
"""
|
|
||||||
Create a new StockItem based on the provided form data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = form.cleaned_data
|
|
||||||
|
|
||||||
part = data['part']
|
|
||||||
|
|
||||||
quantity = data['quantity']
|
|
||||||
|
|
||||||
if part.trackable:
|
|
||||||
sn = data.get('serial_numbers', '')
|
|
||||||
sn = str(sn).strip()
|
|
||||||
|
|
||||||
# Create a single stock item for each provided serial number
|
|
||||||
if len(sn) > 0:
|
|
||||||
serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt())
|
|
||||||
|
|
||||||
for serial in serials:
|
|
||||||
item = StockItem(
|
|
||||||
part=part,
|
|
||||||
quantity=1,
|
|
||||||
serial=serial,
|
|
||||||
supplier_part=data.get('supplier_part', None),
|
|
||||||
location=data.get('location', None),
|
|
||||||
batch=data.get('batch', None),
|
|
||||||
delete_on_deplete=False,
|
|
||||||
status=data.get('status'),
|
|
||||||
link=data.get('link', ''),
|
|
||||||
)
|
|
||||||
|
|
||||||
item.save(user=self.request.user)
|
|
||||||
|
|
||||||
# Create a single StockItem of the specified quantity
|
|
||||||
else:
|
|
||||||
form._post_clean()
|
|
||||||
|
|
||||||
item = form.save(commit=False)
|
|
||||||
item.user = self.request.user
|
|
||||||
item.save(user=self.request.user)
|
|
||||||
|
|
||||||
return item
|
|
||||||
|
|
||||||
# Non-trackable part
|
|
||||||
else:
|
|
||||||
|
|
||||||
form._post_clean()
|
|
||||||
|
|
||||||
item = form.save(commit=False)
|
|
||||||
item.user = self.request.user
|
|
||||||
item.save(user=self.request.user)
|
|
||||||
|
|
||||||
return item
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocationDelete(AjaxDeleteView):
|
class StockLocationDelete(AjaxDeleteView):
|
||||||
"""
|
"""
|
||||||
View to delete a StockLocation
|
View to delete a StockLocation
|
||||||
|
Loading…
x
Reference in New Issue
Block a user