mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Implemented API to move multiple items at once
- Added ability to override request method in inventreeUpdate - Added inventree/script/stock.js to handle stock API js - Added StockMove API endpoint
This commit is contained in:
parent
87f96d6b3c
commit
d8922aa9db
@ -46,6 +46,12 @@ function inventreeUpdate(url, data={}, options={}) {
|
|||||||
data["_is_final"] = true;
|
data["_is_final"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var method = 'put';
|
||||||
|
|
||||||
|
if ('method' in options) {
|
||||||
|
method = options.method;
|
||||||
|
}
|
||||||
|
|
||||||
// Middleware token required for data update
|
// Middleware token required for data update
|
||||||
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
|
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
|
||||||
var csrftoken = getCookie('csrftoken');
|
var csrftoken = getCookie('csrftoken');
|
||||||
@ -55,7 +61,7 @@ function inventreeUpdate(url, data={}, options={}) {
|
|||||||
xhr.setRequestHeader('X-CSRFToken', csrftoken);
|
xhr.setRequestHeader('X-CSRFToken', csrftoken);
|
||||||
},
|
},
|
||||||
url: url,
|
url: url,
|
||||||
type: 'put',
|
type: method,
|
||||||
data: data,
|
data: data,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response, status) {
|
success: function(response, status) {
|
||||||
|
72
InvenTree/static/script/inventree/stock.js
Normal file
72
InvenTree/static/script/inventree/stock.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
function moveStock(rows, options) {
|
||||||
|
|
||||||
|
var modal = '#modal-form';
|
||||||
|
|
||||||
|
if ('modal' in options) {
|
||||||
|
modal = options.modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rows.length == 0) {
|
||||||
|
alert('No stock items selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doMove(location, parts) {
|
||||||
|
inventreeUpdate("/api/stock/move/",
|
||||||
|
{
|
||||||
|
location: location,
|
||||||
|
parts: parts
|
||||||
|
},
|
||||||
|
{
|
||||||
|
success: function(response) {
|
||||||
|
closeModal(modal);
|
||||||
|
if (options.success) {
|
||||||
|
options.success();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
alert('error!:\n' + error);
|
||||||
|
},
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStockLocations({},
|
||||||
|
{
|
||||||
|
success: function(response) {
|
||||||
|
openModal(modal);
|
||||||
|
modalSetTitle(modal, "Move " + rows.length + " stock items");
|
||||||
|
modalSetButtonText(modal, "Move");
|
||||||
|
|
||||||
|
// Extact part row info
|
||||||
|
var parts = [];
|
||||||
|
|
||||||
|
for (i = 0; i < rows.length; i++) {
|
||||||
|
parts.push(rows[i].pk);
|
||||||
|
}
|
||||||
|
|
||||||
|
var form = "<select class='select' id='stock-location'>";
|
||||||
|
|
||||||
|
for (i = 0; i < response.length; i++) {
|
||||||
|
var loc = response[i];
|
||||||
|
|
||||||
|
form += makeOption(loc.pk, loc.name + ' - <i>' + loc.description + '</i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
form += "</select">
|
||||||
|
|
||||||
|
modalSetContent(modal, form);
|
||||||
|
attachSelect(modal);
|
||||||
|
|
||||||
|
$(modal).on('click', '#modal-form-submit', function() {
|
||||||
|
var locId = $(modal).find("#stock-location").val();
|
||||||
|
|
||||||
|
doMove(locId, parts);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
alert('Error getting stock locations:\n' + error.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -61,6 +61,9 @@ function modalSetButtonText(modal, text) {
|
|||||||
$(modal).find("#modal-form-submit").html(text);
|
$(modal).find("#modal-form-submit").html(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeModal(modal='#modal-form') {
|
||||||
|
$(modal).modal('hide');
|
||||||
|
}
|
||||||
|
|
||||||
function openModal(modal, title='', content='') {
|
function openModal(modal, title='', content='') {
|
||||||
|
|
||||||
|
@ -13,6 +13,12 @@ from .serializers import LocationSerializer
|
|||||||
from InvenTree.views import TreeSerializer
|
from InvenTree.views import TreeSerializer
|
||||||
from InvenTree.serializers import DraftRUDView
|
from InvenTree.serializers import DraftRUDView
|
||||||
|
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import authentication, permissions
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
class StockCategoryTree(TreeSerializer):
|
class StockCategoryTree(TreeSerializer):
|
||||||
title = 'Stock'
|
title = 'Stock'
|
||||||
model = StockLocation
|
model = StockLocation
|
||||||
@ -45,6 +51,54 @@ class StockFilter(FilterSet):
|
|||||||
fields = ['quantity', 'part', 'location']
|
fields = ['quantity', 'part', 'location']
|
||||||
|
|
||||||
|
|
||||||
|
class StockMove(APIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
permissions.IsAuthenticatedOrReadOnly,
|
||||||
|
]
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
data = request.data
|
||||||
|
|
||||||
|
if not u'location' in data:
|
||||||
|
raise ValidationError({'location': 'Destination must be specified'})
|
||||||
|
|
||||||
|
loc_id = data.get(u'location')
|
||||||
|
|
||||||
|
try:
|
||||||
|
location = StockLocation.objects.get(pk=loc_id)
|
||||||
|
except StockLocation.DoesNotExist:
|
||||||
|
raise ValidationError({'location': 'Location does not exist'})
|
||||||
|
|
||||||
|
if not u'parts[]' in data:
|
||||||
|
raise ValidationError({'parts[]': 'Parts list must be specified'})
|
||||||
|
|
||||||
|
part_list = data.getlist(u'parts[]')
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for pid in part_list:
|
||||||
|
try:
|
||||||
|
part = StockItem.objects.get(pk=pid)
|
||||||
|
parts.append(part)
|
||||||
|
except StockItem.DoesNotExist:
|
||||||
|
errors.append({'part': 'Part {id} does not exist'.format(id=part_id)})
|
||||||
|
|
||||||
|
if len(errors) > 0:
|
||||||
|
raise ValidationError(errors)
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
part.move(location, request.user)
|
||||||
|
|
||||||
|
return Response({'success': 'Moved {n} parts to {loc}'.format(
|
||||||
|
n=len(parts),
|
||||||
|
loc=location.name
|
||||||
|
)})
|
||||||
|
|
||||||
|
|
||||||
class StockLocationList(generics.ListCreateAPIView):
|
class StockLocationList(generics.ListCreateAPIView):
|
||||||
|
|
||||||
queryset = StockLocation.objects.all()
|
queryset = StockLocation.objects.all()
|
||||||
@ -167,6 +221,8 @@ stock_api_urls = [
|
|||||||
|
|
||||||
url(r'location/(?P<pk>\d+)/', include(location_endpoints)),
|
url(r'location/(?P<pk>\d+)/', include(location_endpoints)),
|
||||||
|
|
||||||
|
url(r'move/?', StockMove.as_view(), name='api-stock-move'),
|
||||||
|
|
||||||
url(r'^tree/?', StockCategoryTree.as_view(), name='api-stock-tree'),
|
url(r'^tree/?', StockCategoryTree.as_view(), name='api-stock-tree'),
|
||||||
|
|
||||||
url(r'^.*$', StockList.as_view(), name='api-stock-list'),
|
url(r'^.*$', StockList.as_view(), name='api-stock-list'),
|
||||||
|
@ -192,6 +192,23 @@ class StockItem(models.Model):
|
|||||||
|
|
||||||
track.save()
|
track.save()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def move(self, location, user):
|
||||||
|
|
||||||
|
if location == self.location:
|
||||||
|
return
|
||||||
|
|
||||||
|
note = "Moved to {loc}".format(loc=location.name)
|
||||||
|
|
||||||
|
self.location = location
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
self.add_transaction_note('Transfer',
|
||||||
|
user,
|
||||||
|
notes=note,
|
||||||
|
system=True)
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def stocktake(self, count, user):
|
def stocktake(self, count, user):
|
||||||
""" Perform item stocktake.
|
""" Perform item stocktake.
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
{% block js_load %}
|
{% block js_load %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
|
||||||
|
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% static 'script/modal_form.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/modal_form.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
@ -125,9 +126,12 @@
|
|||||||
|
|
||||||
var items = selectedStock();
|
var items = selectedStock();
|
||||||
|
|
||||||
alert('Moving ' + items.length + ' items');
|
moveStock(items,
|
||||||
|
{
|
||||||
return false;
|
success: function() {
|
||||||
|
$("#stock-table").bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#multi-item-delete").click(function() {
|
$("#multi-item-delete").click(function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user