mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Adds API endpoint for serialization of stock items
This commit is contained in:
parent
2b69d9c2af
commit
be7b224f14
@ -101,6 +101,27 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
instance.mark_for_deletion()
|
instance.mark_for_deletion()
|
||||||
|
|
||||||
|
|
||||||
|
class StockItemSerialize(generics.CreateAPIView):
|
||||||
|
"""
|
||||||
|
API endpoint for serializing a stock item
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = StockItem.objects.none()
|
||||||
|
serializer_class = StockSerializers.SerializeStockItemSerializer
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
|
||||||
|
context = super().get_serializer_context()
|
||||||
|
context['request'] = self.request
|
||||||
|
|
||||||
|
try:
|
||||||
|
context['item'] = StockItem.objects.get(pk=self.kwargs.get('pk', None))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class StockAdjustView(generics.CreateAPIView):
|
class StockAdjustView(generics.CreateAPIView):
|
||||||
"""
|
"""
|
||||||
A generic class for handling stocktake actions.
|
A generic class for handling stocktake actions.
|
||||||
@ -1126,8 +1147,11 @@ stock_api_urls = [
|
|||||||
url(r'^.*$', StockTrackingList.as_view(), name='api-stock-tracking-list'),
|
url(r'^.*$', StockTrackingList.as_view(), name='api-stock-tracking-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# Detail for a single stock item
|
# Detail views for a single stock item
|
||||||
url(r'^(?P<pk>\d+)/', StockDetail.as_view(), name='api-stock-detail'),
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
|
url(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'),
|
||||||
|
url(r'^.*$', StockDetail.as_view(), name='api-stock-detail'),
|
||||||
|
])),
|
||||||
|
|
||||||
# Anything else
|
# Anything else
|
||||||
url(r'^.*$', StockList.as_view(), name='api-stock-list'),
|
url(r'^.*$', StockList.as_view(), name='api-stock-list'),
|
||||||
|
@ -9,6 +9,7 @@ from decimal import Decimal
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.db.models import Case, When, Value
|
from django.db.models import Case, When, Value
|
||||||
@ -27,14 +28,15 @@ from .models import StockItemTestResult
|
|||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
from common.settings import currency_code_default, currency_code_mappings
|
from common.settings import currency_code_default, currency_code_mappings
|
||||||
|
|
||||||
from company.serializers import SupplierPartSerializer
|
from company.serializers import SupplierPartSerializer
|
||||||
|
|
||||||
|
import InvenTree.helpers
|
||||||
|
import InvenTree.serializers
|
||||||
|
|
||||||
from part.serializers import PartBriefSerializer
|
from part.serializers import PartBriefSerializer
|
||||||
from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer, InvenTreeMoneySerializer
|
|
||||||
from InvenTree.serializers import InvenTreeAttachmentSerializer, InvenTreeAttachmentSerializerField
|
|
||||||
|
|
||||||
|
|
||||||
class LocationBriefSerializer(InvenTreeModelSerializer):
|
class LocationBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""
|
"""
|
||||||
Provides a brief serializer for a StockLocation object
|
Provides a brief serializer for a StockLocation object
|
||||||
"""
|
"""
|
||||||
@ -48,7 +50,7 @@ class LocationBriefSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockItemSerializerBrief(InvenTreeModelSerializer):
|
class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
""" Brief serializers for a StockItem """
|
""" Brief serializers for a StockItem """
|
||||||
|
|
||||||
location_name = serializers.CharField(source='location', read_only=True)
|
location_name = serializers.CharField(source='location', read_only=True)
|
||||||
@ -70,7 +72,7 @@ class StockItemSerializerBrief(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockItemSerializer(InvenTreeModelSerializer):
|
class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
""" Serializer for a StockItem:
|
""" Serializer for a StockItem:
|
||||||
|
|
||||||
- Includes serialization for the linked part
|
- Includes serialization for the linked part
|
||||||
@ -146,7 +148,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False)
|
required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False)
|
||||||
|
|
||||||
purchase_price = InvenTreeMoneySerializer(
|
purchase_price = InvenTree.serializers.InvenTreeMoneySerializer(
|
||||||
label=_('Purchase Price'),
|
label=_('Purchase Price'),
|
||||||
max_digits=19, decimal_places=4,
|
max_digits=19, decimal_places=4,
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
@ -247,14 +249,127 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockQuantitySerializer(InvenTreeModelSerializer):
|
class SerializeStockItemSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
A DRF serializer for "serializing" a StockItem.
|
||||||
|
|
||||||
|
(Sorry for the confusing naming...)
|
||||||
|
|
||||||
|
Here, "serializing" means splitting out a single StockItem,
|
||||||
|
into multiple single-quantity items with an assigned serial number
|
||||||
|
|
||||||
|
Note: The base StockItem object is provided to the serializer context
|
||||||
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StockItem
|
fields = [
|
||||||
fields = ('quantity',)
|
'quantity',
|
||||||
|
'serial_numbers',
|
||||||
|
'destination',
|
||||||
|
'notes',
|
||||||
|
]
|
||||||
|
|
||||||
|
quantity = serializers.IntegerField(
|
||||||
|
min_value=0,
|
||||||
|
required=True,
|
||||||
|
label=_('Quantity'),
|
||||||
|
help_text=_('Enter number of stock items to serialize'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_quantity(self, quantity):
|
||||||
|
|
||||||
|
item = self.context['item']
|
||||||
|
|
||||||
|
if quantity < 0:
|
||||||
|
raise ValidationError(_("Quantity must be greater than zero"))
|
||||||
|
|
||||||
|
if quantity > item.quantity:
|
||||||
|
q = item.quantity
|
||||||
|
raise ValidationError(_(f"Quantity must not exceed available stock quantity ({q})"))
|
||||||
|
|
||||||
|
return quantity
|
||||||
|
|
||||||
|
serial_numbers = serializers.CharField(
|
||||||
|
label=_('Serial Numbers'),
|
||||||
|
help_text=_('Enter serial numbers for new items'),
|
||||||
|
allow_blank=False,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
destination = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=StockLocation.objects.all(),
|
||||||
|
many=False,
|
||||||
|
required=True,
|
||||||
|
allow_null=False,
|
||||||
|
label=_('Location'),
|
||||||
|
help_text=_('Destination stock location'),
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
allow_blank=True,
|
||||||
|
label=_("Notes"),
|
||||||
|
help_text=_("Optional note field")
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
"""
|
||||||
|
Check that the supplied serial numbers are valid
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = super().validate(data)
|
||||||
|
|
||||||
|
item = self.context['item']
|
||||||
|
|
||||||
|
if not item.part.trackable:
|
||||||
|
raise ValidationError(_("Serial numbers cannot be assigned to this part"))
|
||||||
|
|
||||||
|
# Ensure the serial numbers are valid!
|
||||||
|
quantity = data['quantity']
|
||||||
|
serial_numbers = data['serial_numbers']
|
||||||
|
|
||||||
|
try:
|
||||||
|
serials = InvenTree.helpers.extract_serial_numbers(serial_numbers, quantity)
|
||||||
|
except DjangoValidationError as e:
|
||||||
|
raise ValidationError({
|
||||||
|
'serial_numbers': e.messages,
|
||||||
|
})
|
||||||
|
|
||||||
|
existing = item.part.find_conflicting_serial_numbers(serials)
|
||||||
|
|
||||||
|
if len(existing) > 0:
|
||||||
|
exists = ','.join([str(x) for x in existing])
|
||||||
|
error = _('Serial numbers already exist') + ": " + exists
|
||||||
|
|
||||||
|
raise ValidationError({
|
||||||
|
'serial_numbers': error,
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
|
||||||
|
item = self.context['item']
|
||||||
|
request = self.context['request']
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
data = self.validated_data
|
||||||
|
|
||||||
|
serials = InvenTree.helpers.extract_serial_numbers(
|
||||||
|
data['serial_numbers'],
|
||||||
|
data['quantity'],
|
||||||
|
)
|
||||||
|
|
||||||
|
item.serializeStock(
|
||||||
|
data['quantity'],
|
||||||
|
serials,
|
||||||
|
user,
|
||||||
|
notes=data.get('notes', ''),
|
||||||
|
location=data['destination'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LocationSerializer(InvenTreeModelSerializer):
|
class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
""" Detailed information about a stock location
|
""" Detailed information about a stock location
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -278,7 +393,7 @@ class LocationSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
|
class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer):
|
||||||
""" Serializer for StockItemAttachment model """
|
""" Serializer for StockItemAttachment model """
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -289,9 +404,9 @@ class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
if user_detail is not True:
|
if user_detail is not True:
|
||||||
self.fields.pop('user_detail')
|
self.fields.pop('user_detail')
|
||||||
|
|
||||||
user_detail = UserSerializerBrief(source='user', read_only=True)
|
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
|
||||||
|
|
||||||
attachment = InvenTreeAttachmentSerializerField(required=True)
|
attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=True)
|
||||||
|
|
||||||
# TODO: Record the uploading user when creating or updating an attachment!
|
# TODO: Record the uploading user when creating or updating an attachment!
|
||||||
|
|
||||||
@ -316,14 +431,14 @@ class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockItemTestResultSerializer(InvenTreeModelSerializer):
|
class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
""" Serializer for the StockItemTestResult model """
|
""" Serializer for the StockItemTestResult model """
|
||||||
|
|
||||||
user_detail = UserSerializerBrief(source='user', read_only=True)
|
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
|
||||||
|
|
||||||
key = serializers.CharField(read_only=True)
|
key = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
attachment = InvenTreeAttachmentSerializerField(required=False)
|
attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
user_detail = kwargs.pop('user_detail', False)
|
user_detail = kwargs.pop('user_detail', False)
|
||||||
@ -357,7 +472,7 @@ class StockItemTestResultSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockTrackingSerializer(InvenTreeModelSerializer):
|
class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
""" Serializer for StockItemTracking model """
|
""" Serializer for StockItemTracking model """
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -377,7 +492,7 @@ class StockTrackingSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True)
|
item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True)
|
||||||
|
|
||||||
user_detail = UserSerializerBrief(source='user', many=False, read_only=True)
|
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', many=False, read_only=True)
|
||||||
|
|
||||||
deltas = serializers.JSONField(read_only=True)
|
deltas = serializers.JSONField(read_only=True)
|
||||||
|
|
||||||
|
@ -418,12 +418,18 @@
|
|||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
$("#stock-serialize").click(function() {
|
$("#stock-serialize").click(function() {
|
||||||
launchModalForm(
|
|
||||||
"{% url 'stock-item-serialize' item.id %}",
|
serializeStockItem({{ item.pk }}, {
|
||||||
{
|
reload: true,
|
||||||
reload: true,
|
data: {
|
||||||
|
quantity: {{ item.quantity }},
|
||||||
|
{% if item.location %}
|
||||||
|
destination: {{ item.location.pk }},
|
||||||
|
{% elif item.part.default_location %}
|
||||||
|
destination: {{ item.part.default_location.pk }},
|
||||||
|
{% endif %}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#stock-install-in').click(function() {
|
$('#stock-install-in').click(function() {
|
||||||
|
@ -126,46 +126,6 @@ class StockItemTest(StockViewTestCase):
|
|||||||
|
|
||||||
self.assertIn(expected, str(response.content))
|
self.assertIn(expected, str(response.content))
|
||||||
|
|
||||||
def test_serialize_item(self):
|
|
||||||
# Test the serialization view
|
|
||||||
|
|
||||||
url = reverse('stock-item-serialize', args=(100,))
|
|
||||||
|
|
||||||
# GET the form
|
|
||||||
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
data_valid = {
|
|
||||||
'quantity': 5,
|
|
||||||
'serial_numbers': '1-5',
|
|
||||||
'destination': 4,
|
|
||||||
'notes': 'Serializing stock test'
|
|
||||||
}
|
|
||||||
|
|
||||||
data_invalid = {
|
|
||||||
'quantity': 4,
|
|
||||||
'serial_numbers': 'dd-23-adf',
|
|
||||||
'destination': 'blorg'
|
|
||||||
}
|
|
||||||
|
|
||||||
# POST
|
|
||||||
response = self.client.post(url, data_valid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
data = json.loads(response.content)
|
|
||||||
self.assertTrue(data['form_valid'])
|
|
||||||
|
|
||||||
# Try again to serialize with the same numbers
|
|
||||||
response = self.client.post(url, data_valid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
data = json.loads(response.content)
|
|
||||||
self.assertFalse(data['form_valid'])
|
|
||||||
|
|
||||||
# POST with invalid data
|
|
||||||
response = self.client.post(url, data_invalid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
data = json.loads(response.content)
|
|
||||||
self.assertFalse(data['form_valid'])
|
|
||||||
|
|
||||||
|
|
||||||
class StockOwnershipTest(StockViewTestCase):
|
class StockOwnershipTest(StockViewTestCase):
|
||||||
""" Tests for stock ownership views """
|
""" Tests for stock ownership views """
|
||||||
|
@ -20,7 +20,6 @@ location_urls = [
|
|||||||
|
|
||||||
stock_item_detail_urls = [
|
stock_item_detail_urls = [
|
||||||
url(r'^convert/', views.StockItemConvert.as_view(), name='stock-item-convert'),
|
url(r'^convert/', views.StockItemConvert.as_view(), name='stock-item-convert'),
|
||||||
url(r'^serialize/', views.StockItemSerialize.as_view(), name='stock-item-serialize'),
|
|
||||||
url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'),
|
url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'),
|
||||||
url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
|
url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
|
||||||
url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'),
|
url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'),
|
||||||
|
@ -1027,89 +1027,6 @@ class StockLocationCreate(AjaxCreateView):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class StockItemSerialize(AjaxUpdateView):
|
|
||||||
""" View for manually serializing a StockItem """
|
|
||||||
|
|
||||||
model = StockItem
|
|
||||||
ajax_template_name = 'stock/item_serialize.html'
|
|
||||||
ajax_form_title = _('Serialize Stock')
|
|
||||||
form_class = StockForms.SerializeStockForm
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
|
|
||||||
context = self.get_form_kwargs()
|
|
||||||
|
|
||||||
# Pass the StockItem object through to the form
|
|
||||||
context['item'] = self.get_object()
|
|
||||||
|
|
||||||
form = StockForms.SerializeStockForm(**context)
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
|
|
||||||
initials = super().get_initial().copy()
|
|
||||||
|
|
||||||
item = self.get_object()
|
|
||||||
|
|
||||||
initials['quantity'] = item.quantity
|
|
||||||
initials['serial_numbers'] = item.part.getSerialNumberString(item.quantity)
|
|
||||||
if item.location is not None:
|
|
||||||
initials['destination'] = item.location.pk
|
|
||||||
|
|
||||||
return initials
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
form = self.get_form()
|
|
||||||
|
|
||||||
item = self.get_object()
|
|
||||||
|
|
||||||
quantity = request.POST.get('quantity', 0)
|
|
||||||
serials = request.POST.get('serial_numbers', '')
|
|
||||||
dest_id = request.POST.get('destination', None)
|
|
||||||
notes = request.POST.get('note', '')
|
|
||||||
user = request.user
|
|
||||||
|
|
||||||
valid = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
destination = StockLocation.objects.get(pk=dest_id)
|
|
||||||
except (ValueError, StockLocation.DoesNotExist):
|
|
||||||
destination = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
numbers = extract_serial_numbers(serials, quantity)
|
|
||||||
except ValidationError as e:
|
|
||||||
form.add_error('serial_numbers', e.messages)
|
|
||||||
valid = False
|
|
||||||
numbers = []
|
|
||||||
|
|
||||||
if valid:
|
|
||||||
try:
|
|
||||||
item.serializeStock(quantity, numbers, user, notes=notes, location=destination)
|
|
||||||
except ValidationError as e:
|
|
||||||
messages = e.message_dict
|
|
||||||
|
|
||||||
for k in messages.keys():
|
|
||||||
if k in ['quantity', 'destination', 'serial_numbers']:
|
|
||||||
form.add_error(k, messages[k])
|
|
||||||
else:
|
|
||||||
form.add_error(None, messages[k])
|
|
||||||
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'form_valid': valid,
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form, data=data)
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemCreate(AjaxCreateView):
|
class StockItemCreate(AjaxCreateView):
|
||||||
"""
|
"""
|
||||||
View for creating a new StockItem
|
View for creating a new StockItem
|
||||||
|
@ -52,12 +52,39 @@
|
|||||||
loadStockTrackingTable,
|
loadStockTrackingTable,
|
||||||
loadTableFilters,
|
loadTableFilters,
|
||||||
removeStockRow,
|
removeStockRow,
|
||||||
|
serializeStockItem,
|
||||||
stockItemFields,
|
stockItemFields,
|
||||||
stockLocationFields,
|
stockLocationFields,
|
||||||
stockStatusCodes,
|
stockStatusCodes,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Launches a modal form to serialize a particular StockItem
|
||||||
|
*/
|
||||||
|
|
||||||
|
function serializeStockItem(pk, options={}) {
|
||||||
|
|
||||||
|
var url = `/api/stock/${pk}/serialize/`;
|
||||||
|
|
||||||
|
options.method = 'POST';
|
||||||
|
options.title = '{% trans "Serialize Stock Item" %}';
|
||||||
|
|
||||||
|
options.fields = {
|
||||||
|
quantity: {},
|
||||||
|
serial_numbers: {
|
||||||
|
icon: 'fa-hashtag',
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
icon: 'fa-sitemap',
|
||||||
|
},
|
||||||
|
notes: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
constructForm(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function stockLocationFields(options={}) {
|
function stockLocationFields(options={}) {
|
||||||
var fields = {
|
var fields = {
|
||||||
parent: {
|
parent: {
|
||||||
|
Loading…
Reference in New Issue
Block a user