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:
@ -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'),
|
||||
])),
|
||||
|
@ -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
|
||||
|
27
InvenTree/stock/migrations/0075_auto_20220515_1440.py
Normal file
27
InvenTree/stock/migrations/0075_auto_20220515_1440.py
Normal 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(),
|
||||
),
|
||||
]
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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: {
|
||||
|
@ -2,9 +2,6 @@
|
||||
Unit testing for the Stock API
|
||||
"""
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import io
|
||||
import tablib
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user