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:
Oliver 2018-05-06 21:39:33 +10:00
parent 87f96d6b3c
commit d8922aa9db
6 changed files with 162 additions and 4 deletions

View File

@ -46,6 +46,12 @@ function inventreeUpdate(url, data={}, options={}) {
data["_is_final"] = true;
}
var method = 'put';
if ('method' in options) {
method = options.method;
}
// Middleware token required for data update
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
var csrftoken = getCookie('csrftoken');
@ -55,7 +61,7 @@ function inventreeUpdate(url, data={}, options={}) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
},
url: url,
type: 'put',
type: method,
data: data,
dataType: 'json',
success: function(response, status) {

View 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);
}
});
}

View File

@ -61,6 +61,9 @@ function modalSetButtonText(modal, text) {
$(modal).find("#modal-form-submit").html(text);
}
function closeModal(modal='#modal-form') {
$(modal).modal('hide');
}
function openModal(modal, title='', content='') {

View File

@ -13,6 +13,12 @@ from .serializers import LocationSerializer
from InvenTree.views import TreeSerializer
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):
title = 'Stock'
model = StockLocation
@ -45,6 +51,54 @@ class StockFilter(FilterSet):
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):
queryset = StockLocation.objects.all()
@ -167,6 +221,8 @@ stock_api_urls = [
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'^.*$', StockList.as_view(), name='api-stock-list'),

View File

@ -192,6 +192,23 @@ class StockItem(models.Model):
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
def stocktake(self, count, user):
""" Perform item stocktake.

View File

@ -61,6 +61,7 @@
{% block js_load %}
{{ block.super }}
<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>
{% endblock %}
{% block js_ready %}
@ -125,9 +126,12 @@
var items = selectedStock();
alert('Moving ' + items.length + ' items');
return false;
moveStock(items,
{
success: function() {
$("#stock-table").bootstrapTable('refresh');
}
});
});
$("#multi-item-delete").click(function() {