2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 11:10:54 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue3005

This commit is contained in:
Matthias Mair
2022-05-16 17:48:01 +02:00
126 changed files with 13394 additions and 12710 deletions

View File

@ -2,9 +2,6 @@
JSON API for the Stock app
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from collections import OrderedDict
from datetime import datetime, timedelta
@ -43,6 +40,8 @@ from order.serializers import PurchaseOrderSerializer
from part.models import BomItem, Part, PartCategory
from part.serializers import PartBriefSerializer
from plugin.serializers import MetadataSerializer
from stock.admin import StockItemResource
from stock.models import StockLocation, StockItem
from stock.models import StockItemTracking
@ -92,6 +91,15 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
return self.serializer_class(*args, **kwargs)
class StockMetadata(generics.RetrieveUpdateAPIView):
"""API endpoint for viewing / updating StockItem metadata"""
def get_serializer(self, *args, **kwargs):
return MetadataSerializer(StockItem, *args, **kwargs)
queryset = StockItem.objects.all()
class StockItemContextMixin:
""" Mixin class for adding StockItem object to serializer context """
@ -1368,6 +1376,15 @@ class StockTrackingList(generics.ListAPIView):
]
class LocationMetadata(generics.RetrieveUpdateAPIView):
"""API endpoint for viewing / updating StockLocation metadata"""
def get_serializer(self, *args, **kwargs):
return MetadataSerializer(StockLocation, *args, **kwargs)
queryset = StockLocation.objects.all()
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail view of StockLocation object
@ -1385,7 +1402,14 @@ stock_api_urls = [
re_path(r'^tree/', StockLocationTree.as_view(), name='api-location-tree'),
re_path(r'^(?P<pk>\d+)/', LocationDetail.as_view(), name='api-location-detail'),
# Stock location detail endpoints
re_path(r'^(?P<pk>\d+)/', include([
re_path(r'^metadata/', LocationMetadata.as_view(), name='api-location-metadata'),
re_path(r'^.*$', LocationDetail.as_view(), name='api-location-detail'),
])),
re_path(r'^.*$', StockLocationList.as_view(), name='api-location-list'),
])),
@ -1417,8 +1441,9 @@ stock_api_urls = [
# Detail views for a single stock item
re_path(r'^(?P<pk>\d+)/', include([
re_path(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'),
re_path(r'^install/', StockItemInstall.as_view(), name='api-stock-item-install'),
re_path(r'^metadata/', StockMetadata.as_view(), name='api-stock-item-metadata'),
re_path(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'),
re_path(r'^uninstall/', StockItemUninstall.as_view(), name='api-stock-item-uninstall'),
re_path(r'^.*$', StockDetail.as_view(), name='api-stock-detail'),
])),

View File

@ -2,9 +2,6 @@
Django Forms for interacting with Stock app
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from InvenTree.forms import HelperForm
from .models import StockItem, StockItemTracking

View File

@ -0,0 +1,27 @@
# Generated by Django 3.2.13 on 2022-05-15 14:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stock', '0074_alter_stockitem_batch'),
]
operations = [
migrations.AddField(
model_name='stockitem',
name='metadata',
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
),
migrations.AddField(
model_name='stocklocation',
name='metadata',
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
),
migrations.AlterUniqueTogether(
name='stocklocation',
unique_together=set(),
),
]

View File

@ -2,10 +2,6 @@
Stock database model definitions
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from jinja2 import Template
@ -38,6 +34,7 @@ import common.models
import report.models
import label.models
from plugin.models import MetadataMixin
from plugin.events import trigger_event
from InvenTree.status_codes import StockStatus, StockHistoryCode
@ -52,7 +49,7 @@ from part import models as PartModels
from part import tasks as part_tasks
class StockLocation(InvenTreeTree):
class StockLocation(MetadataMixin, InvenTreeTree):
""" Organization tree for StockItem objects
A "StockLocation" can be considered a warehouse, or storage location
Stock locations can be heirarchical as required
@ -243,7 +240,7 @@ def generate_batch_code():
return Template(batch_template).render(context)
class StockItem(MPTTModel):
class StockItem(MetadataMixin, MPTTModel):
"""
A StockItem object represents a quantity of physical instances of a part.
@ -405,7 +402,7 @@ class StockItem(MPTTModel):
deltas = {}
# Status changed?
if not old.status == self.status:
if old.status != self.status:
deltas['status'] = self.status
# TODO - Other interesting changes we are interested in...
@ -494,7 +491,7 @@ class StockItem(MPTTModel):
try:
if self.part.trackable:
# Trackable parts must have integer values for quantity field!
if not self.quantity == int(self.quantity):
if self.quantity != int(self.quantity):
raise ValidationError({
'quantity': _('Quantity must be integer value for trackable parts')
})
@ -512,7 +509,7 @@ class StockItem(MPTTModel):
# The 'supplier_part' field must point to the same part!
try:
if self.supplier_part is not None:
if not self.supplier_part.part == self.part:
if self.supplier_part.part != self.part:
raise ValidationError({'supplier_part': _("Part type ('{pf}') must be {pe}").format(
pf=str(self.supplier_part.part),
pe=str(self.part))
@ -1322,10 +1319,10 @@ class StockItem(MPTTModel):
if quantity > self.quantity:
raise ValidationError({"quantity": _("Quantity must not exceed available stock quantity ({n})").format(n=self.quantity)})
if not type(serials) in [list, tuple]:
if type(serials) not in [list, tuple]:
raise ValidationError({"serial_numbers": _("Serial numbers must be a list of integers")})
if not quantity == len(serials):
if quantity != len(serials):
raise ValidationError({"quantity": _("Quantity does not match serial numbers")})
# Test if each of the serial numbers are valid

View File

@ -2,9 +2,6 @@
JSON serializers for Stock app
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from decimal import Decimal
from datetime import datetime, timedelta
from django.db import transaction

View File

@ -1,5 +1,6 @@
{% extends "page_base.html" %}
{% load static %}
{% load plugin_extras %}
{% load inventree_extras %}
{% load status_codes %}
{% load i18n %}
@ -18,7 +19,6 @@
<div id="breadcrumb-tree"></div>
{% endblock breadcrumb_tree %}
{% block heading %}
{% trans "Stock Item" %}: {{ item.part.full_name}}
{% endblock heading %}
@ -29,6 +29,12 @@
{% url 'admin:stock_stockitem_change' item.pk as url %}
{% include "admin_button.html" with url=url %}
{% endif %}
{% mixin_available "locate" as locate_available %}
{% if plugins_enabled and locate_available %}
<button id='locate-item-button' title='{% trans "Locate stock item" %}' class='btn btn-outline-secondary' typy='button'>
<span class='fas fa-search-location'></span>
</button>
{% endif %}
{% if barcodes %}
<!-- Barcode actions menu -->
<div class='btn-group' role='group'>
@ -514,6 +520,14 @@ $("#barcode-scan-into-location").click(function() {
});
});
{% if plugins_enabled %}
$('#locate-item-button').click(function() {
locateItemOrLocation({
item: {{ item.pk }},
});
});
{% endif %}
function itemAdjust(action) {
inventreeGet(

View File

@ -1,6 +1,7 @@
{% extends "stock/stock_app_base.html" %}
{% load static %}
{% load inventree_extras %}
{% load plugin_extras %}
{% load i18n %}
{% block sidebar %}
@ -27,6 +28,14 @@
{% include "admin_button.html" with url=url %}
{% endif %}
{% mixin_available "locate" as locate_available %}
{% if location and plugins_enabled and locate_available %}
<button id='locate-location-button' title='{% trans "Locate stock location" %}' class='btn btn-outline-secondary' typy='button'>
<span class='fas fa-search-location'></span>
</button>
{% endif %}
{% if barcodes %}
<!-- Barcode actions menu -->
{% if location %}
@ -206,6 +215,14 @@
{% block js_ready %}
{{ block.super }}
{% if plugins_enabled and location %}
$('#locate-location-button').click(function() {
locateItemOrLocation({
location: {{ location.pk }},
});
});
{% endif %}
onPanelLoad('sublocations', function() {
loadStockLocationTable($('#sublocation-table'), {
params: {

View File

@ -2,9 +2,6 @@
Unit testing for the Stock API
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import io
import tablib

View File

@ -2,9 +2,6 @@
Django views for interacting with Stock app
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import datetime
from django.views.generic import DetailView, ListView