mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
commit
2ff5f57f60
@ -65,7 +65,12 @@ function updateStock(items, options={}) {
|
|||||||
html += '<tr>';
|
html += '<tr>';
|
||||||
|
|
||||||
html += '<td>' + item.part.name + '</td>';
|
html += '<td>' + item.part.name + '</td>';
|
||||||
|
|
||||||
|
if (item.location) {
|
||||||
html += '<td>' + item.location.name + '</td>';
|
html += '<td>' + item.location.name + '</td>';
|
||||||
|
} else {
|
||||||
|
html += '<td><i>No location set</i></td>';
|
||||||
|
}
|
||||||
html += "<td><input class='form-control' ";
|
html += "<td><input class='form-control' ";
|
||||||
html += "value='" + vCur + "' ";
|
html += "value='" + vCur + "' ";
|
||||||
html += "min='" + vMin + "' ";
|
html += "min='" + vMin + "' ";
|
||||||
@ -144,9 +149,7 @@ function updateStock(items, options={}) {
|
|||||||
method: 'post',
|
method: 'post',
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
closeModal(modal);
|
closeModal(modal);
|
||||||
if (options.success) {
|
afterForm(response, options);
|
||||||
options.success();
|
|
||||||
}
|
|
||||||
}).fail(function(xhr, status, error) {
|
}).fail(function(xhr, status, error) {
|
||||||
alert(error);
|
alert(error);
|
||||||
});
|
});
|
||||||
@ -220,34 +223,28 @@ function moveStockItems(items, options) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function doMove(location, parts) {
|
function doMove(location, parts, notes) {
|
||||||
inventreeUpdate("/api/stock/move/",
|
inventreeUpdate("/api/stock/move/",
|
||||||
{
|
{
|
||||||
location: location,
|
location: location,
|
||||||
'parts[]': parts
|
'parts[]': parts,
|
||||||
|
'notes': notes,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
success: function(response) {
|
method: 'post',
|
||||||
|
}).then(function(response) {
|
||||||
closeModal(modal);
|
closeModal(modal);
|
||||||
if (options.success) {
|
afterForm(response, options);
|
||||||
options.success();
|
}).fail(function(xhr, status, error) {
|
||||||
}
|
alert(error);
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
alert('error!:\n' + error);
|
|
||||||
},
|
|
||||||
method: 'post'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getStockLocations({},
|
getStockLocations({},
|
||||||
{
|
{
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
openModal({
|
|
||||||
modal: modal,
|
|
||||||
title: "Move " + items.length + " stock items",
|
|
||||||
submit_text: "Move"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extact part row info
|
// Extact part row info
|
||||||
var parts = [];
|
var parts = [];
|
||||||
@ -262,9 +259,13 @@ function moveStockItems(items, options) {
|
|||||||
html += makeOption(loc.pk, loc.name + ' - <i>' + loc.description + '</i>');
|
html += makeOption(loc.pk, loc.name + ' - <i>' + loc.description + '</i>');
|
||||||
}
|
}
|
||||||
|
|
||||||
html += "</select><br><hr>";
|
html += "</select><br>";
|
||||||
|
|
||||||
html += "The following stock items will be moved:<br><ul class='list-group'>\n";
|
html += "<hr><input type='text' id='notes' placeholder='Notes'/>";
|
||||||
|
|
||||||
|
html += "<p class='warning-msg' id='note-warning'><i>Note field must be filled</i></p>";
|
||||||
|
|
||||||
|
html += "<hr>The following stock items will be moved:<br><ul class='list-group'>\n";
|
||||||
|
|
||||||
for (i = 0; i < items.length; i++) {
|
for (i = 0; i < items.length; i++) {
|
||||||
parts.push(items[i].pk);
|
parts.push(items[i].pk);
|
||||||
@ -280,13 +281,29 @@ function moveStockItems(items, options) {
|
|||||||
|
|
||||||
html += "</ul>\n";
|
html += "</ul>\n";
|
||||||
|
|
||||||
modalSetContent(modal, html);
|
openModal({
|
||||||
|
modal: modal,
|
||||||
|
title: "Move " + items.length + " stock items",
|
||||||
|
submit_text: "Move",
|
||||||
|
content: html
|
||||||
|
});
|
||||||
|
|
||||||
|
//modalSetContent(modal, html);
|
||||||
attachSelect(modal);
|
attachSelect(modal);
|
||||||
|
|
||||||
|
$(modal).find('#note-warning').hide();
|
||||||
|
|
||||||
modalSubmit(modal, function() {
|
modalSubmit(modal, function() {
|
||||||
var locId = $(modal).find("#stock-location").val();
|
var locId = $(modal).find("#stock-location").val();
|
||||||
|
|
||||||
doMove(locId, parts);
|
var notes = $(modal).find('#notes').val();
|
||||||
|
|
||||||
|
if (!notes) {
|
||||||
|
$(modal).find('#note-warning').show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
doMove(locId, parts, notes);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function(error) {
|
error: function(error) {
|
||||||
@ -358,7 +375,7 @@ function loadStockTable(table, options) {
|
|||||||
return renderLink(row.location.pathstring, row.location.url);
|
return renderLink(row.location.pathstring, row.location.url);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return '';
|
return '<i>No stock location set</i>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -383,4 +400,94 @@ function loadStockTable(table, options) {
|
|||||||
if (options.buttons) {
|
if (options.buttons) {
|
||||||
linkButtonsToSelection(table, options.buttons);
|
linkButtonsToSelection(table, options.buttons);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadStockTrackingTable(table, options) {
|
||||||
|
|
||||||
|
var cols = [
|
||||||
|
{
|
||||||
|
field: 'pk',
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'date',
|
||||||
|
title: 'Date',
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
var m = moment(value);
|
||||||
|
if (m.isValid()) {
|
||||||
|
var html = m.format('dddd MMMM Do YYYY') + '<br>' + m.format('h:mm a');
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// If enabled, provide a link to the referenced StockItem
|
||||||
|
if (options.partColumn) {
|
||||||
|
cols.push({
|
||||||
|
field: 'item',
|
||||||
|
title: 'Stock Item',
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
return renderLink(value.part_name, value.url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stock transaction description
|
||||||
|
cols.push({
|
||||||
|
field: 'title',
|
||||||
|
title: 'Description',
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
var html = "<b>" + value + "</b>";
|
||||||
|
|
||||||
|
if (row.notes) {
|
||||||
|
html += "<br><i>" + row.notes + "</i>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push({
|
||||||
|
field: 'quantity',
|
||||||
|
title: 'Quantity',
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push({
|
||||||
|
sortable: true,
|
||||||
|
field: 'user',
|
||||||
|
title: 'User',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
// TODO - Format the user's first and last names
|
||||||
|
return value.username;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "No user information";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.bootstrapTable({
|
||||||
|
sortable: true,
|
||||||
|
search: true,
|
||||||
|
method: 'get',
|
||||||
|
rememberOrder: true,
|
||||||
|
queryParams: options.params,
|
||||||
|
columns: cols,
|
||||||
|
pagination: true,
|
||||||
|
url: options.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.buttons) {
|
||||||
|
linkButtonsToSelection(table, options.buttons);
|
||||||
|
}
|
||||||
|
}
|
@ -111,17 +111,22 @@ class StockStocktake(APIView):
|
|||||||
if 'notes' in request.data:
|
if 'notes' in request.data:
|
||||||
notes = request.data['notes']
|
notes = request.data['notes']
|
||||||
|
|
||||||
|
n = 0
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
quantity = int(item['quantity'])
|
quantity = int(item['quantity'])
|
||||||
|
|
||||||
if action == u'stocktake':
|
if action == u'stocktake':
|
||||||
item['item'].stocktake(quantity, request.user, notes=notes)
|
if item['item'].stocktake(quantity, request.user, notes=notes):
|
||||||
|
n += 1
|
||||||
elif action == u'remove':
|
elif action == u'remove':
|
||||||
item['item'].take_stock(quantity, request.user, notes=notes)
|
if item['item'].take_stock(quantity, request.user, notes=notes):
|
||||||
|
n += 1
|
||||||
elif action == u'add':
|
elif action == u'add':
|
||||||
item['item'].add_stock(quantity, request.user, notes=notes)
|
if item['item'].add_stock(quantity, request.user, notes=notes):
|
||||||
|
n += 1
|
||||||
|
|
||||||
return Response({'success': 'success'})
|
return Response({'success': 'Updated stock for {n} items'.format(n=n)})
|
||||||
|
|
||||||
|
|
||||||
class StockMove(APIView):
|
class StockMove(APIView):
|
||||||
@ -153,6 +158,9 @@ class StockMove(APIView):
|
|||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
|
if u'notes' not in data:
|
||||||
|
errors.append({'notes': 'Notes field must be supplied'})
|
||||||
|
|
||||||
for pid in part_list:
|
for pid in part_list:
|
||||||
try:
|
try:
|
||||||
part = StockItem.objects.get(pk=pid)
|
part = StockItem.objects.get(pk=pid)
|
||||||
@ -163,12 +171,15 @@ class StockMove(APIView):
|
|||||||
if len(errors) > 0:
|
if len(errors) > 0:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
||||||
|
n = 0
|
||||||
|
|
||||||
for part in parts:
|
for part in parts:
|
||||||
part.move(location, request.user)
|
if part.move(location, data.get('notes'), request.user):
|
||||||
|
n += 1
|
||||||
|
|
||||||
return Response({'success': 'Moved {n} parts to {loc}'.format(
|
return Response({'success': 'Moved {n} parts to {loc}'.format(
|
||||||
n=len(parts),
|
n=n,
|
||||||
loc=location.name
|
loc=str(location)
|
||||||
)})
|
)})
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,7 +64,9 @@ class EditStockItemForm(HelperForm):
|
|||||||
model = StockItem
|
model = StockItem
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
|
'supplier_part',
|
||||||
'batch',
|
'batch',
|
||||||
'status',
|
'status',
|
||||||
'notes'
|
'notes',
|
||||||
|
'URL',
|
||||||
]
|
]
|
||||||
|
@ -67,6 +67,7 @@ class StockItem(models.Model):
|
|||||||
self.add_transaction_note(
|
self.add_transaction_note(
|
||||||
'Created stock item',
|
'Created stock item',
|
||||||
None,
|
None,
|
||||||
|
notes="Created new stock item for part '{p}'".format(p=str(self.part)),
|
||||||
system=True
|
system=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -220,13 +221,17 @@ class StockItem(models.Model):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def move(self, location, notes, user):
|
def move(self, location, notes, user):
|
||||||
|
|
||||||
if location.pk == self.location.pk:
|
if location is None:
|
||||||
return False # raise forms.ValidationError("Cannot move item to its current location")
|
# TODO - Raise appropriate error (cannot move to blank location)
|
||||||
|
return False
|
||||||
|
elif self.location and (location.pk == self.location.pk):
|
||||||
|
# TODO - Raise appropriate error (cannot move to same location)
|
||||||
|
return False
|
||||||
|
|
||||||
msg = "Moved to {loc} (from {src})".format(
|
msg = "Moved to {loc}".format(loc=str(location))
|
||||||
loc=location.name,
|
|
||||||
src=self.location.name
|
if self.location:
|
||||||
)
|
msg += " (from {loc})".format(loc=str(self.location))
|
||||||
|
|
||||||
self.location = location
|
self.location = location
|
||||||
self.save()
|
self.save()
|
||||||
@ -329,7 +334,8 @@ class StockItemTracking(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('stock-tracking-detail', kwargs={'pk': self.id})
|
return '/stock/track/{pk}'.format(pk=self.id)
|
||||||
|
# return reverse('stock-tracking-detail', kwargs={'pk': self.id})
|
||||||
|
|
||||||
# Stock item
|
# Stock item
|
||||||
item = models.ForeignKey(StockItem, on_delete=models.CASCADE,
|
item = models.ForeignKey(StockItem, on_delete=models.CASCADE,
|
||||||
|
@ -24,31 +24,22 @@ class LocationBriefSerializer(serializers.ModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockTrackingSerializer(serializers.ModelSerializer):
|
class StockItemSerializerBrief(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Provide a brief serializer for StockItem
|
||||||
|
"""
|
||||||
|
|
||||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||||
|
|
||||||
user = UserSerializerBrief(many=False, read_only=True)
|
part_name = serializers.CharField(source='part.name', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StockItemTracking
|
model = StockItem
|
||||||
fields = [
|
fields = [
|
||||||
'pk',
|
'pk',
|
||||||
|
'uuid',
|
||||||
'url',
|
'url',
|
||||||
'item',
|
'part_name',
|
||||||
'date',
|
|
||||||
'title',
|
|
||||||
'notes',
|
|
||||||
'quantity',
|
|
||||||
'user',
|
|
||||||
'system',
|
|
||||||
]
|
|
||||||
|
|
||||||
read_only_fields = [
|
|
||||||
'date',
|
|
||||||
'user',
|
|
||||||
'system',
|
|
||||||
'quantity',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -118,3 +109,33 @@ class LocationSerializer(serializers.ModelSerializer):
|
|||||||
'parent',
|
'parent',
|
||||||
'pathstring'
|
'pathstring'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class StockTrackingSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||||
|
|
||||||
|
user = UserSerializerBrief(many=False, read_only=True)
|
||||||
|
|
||||||
|
item = StockItemSerializerBrief(many=False, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = StockItemTracking
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'url',
|
||||||
|
'item',
|
||||||
|
'date',
|
||||||
|
'title',
|
||||||
|
'notes',
|
||||||
|
'quantity',
|
||||||
|
'user',
|
||||||
|
'system',
|
||||||
|
]
|
||||||
|
|
||||||
|
read_only_fields = [
|
||||||
|
'date',
|
||||||
|
'user',
|
||||||
|
'system',
|
||||||
|
'quantity',
|
||||||
|
]
|
||||||
|
@ -121,22 +121,11 @@
|
|||||||
|
|
||||||
{% if item.has_tracking_info %}
|
{% if item.has_tracking_info %}
|
||||||
|
|
||||||
<hr>
|
<div id='table-toolbar'>
|
||||||
<div class="panel-group">
|
<h4>Stock Tracking Information</h4>
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a data-toggle="collapse" href="#collapse1">Stock Tracking</a><span class='badge'>{{ item.tracking_info.all|length }}</span>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="collapse1" class="panel-collapse collapse">
|
|
||||||
<div class="panel-body">
|
|
||||||
<table class='table table-condensed table-striped' id='track-table'>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<table class='table table-condensed table-striped' id='track-table' data-toolbar='#table-toolbar'>
|
||||||
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
@ -210,66 +199,14 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#track-table').bootstrapTable({
|
loadStockTrackingTable($("#track-table"), {
|
||||||
sortable: true,
|
params: function(p) {
|
||||||
search: true,
|
|
||||||
method: 'get',
|
|
||||||
queryParams: function(p) {
|
|
||||||
return {
|
return {
|
||||||
|
ordering: '-date',
|
||||||
item: {{ item.pk }},
|
item: {{ item.pk }},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
field: 'date',
|
|
||||||
title: 'Date',
|
|
||||||
sortable: true,
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
var m = moment(value);
|
|
||||||
if (m.isValid()) {
|
|
||||||
var html = m.format('dddd MMMM Do YYYY') + '<br>' + m.format('h:mm a');
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'title',
|
|
||||||
title: 'Description',
|
|
||||||
sortable: true,
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
var html = "<b>" + value + "</b>";
|
|
||||||
|
|
||||||
if (row.notes) {
|
|
||||||
html += "<br><i>" + row.notes + "</i>";
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'quantity',
|
|
||||||
title: 'Quantity',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sortable: true,
|
|
||||||
field: 'user',
|
|
||||||
title: 'User',
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
// TODO - Format the user's first and last names
|
|
||||||
return value.username;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "No user information";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
url: "{% url 'api-stock-track' %}",
|
url: "{% url 'api-stock-track' %}",
|
||||||
})
|
});
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
28
InvenTree/stock/templates/stock/tracking.html
Normal file
28
InvenTree/stock/templates/stock/tracking.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% extends "stock/stock_app_base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>Stock list here!</h3>
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='tracking-table'>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% include 'modals.html' %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_ready %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
loadStockTrackingTable($("#tracking-table"), {
|
||||||
|
params: function(p) {
|
||||||
|
return {
|
||||||
|
ordering: '-date',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
partColumn: true,
|
||||||
|
url: "{% url 'api-stock-track' %}",
|
||||||
|
});
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -28,6 +28,8 @@ stock_urls = [
|
|||||||
|
|
||||||
url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'),
|
url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'),
|
||||||
|
|
||||||
|
url(r'^track/?', views.StockTrackingIndex.as_view(), name='stock-tracking-list'),
|
||||||
|
|
||||||
# Individual stock items
|
# Individual stock items
|
||||||
url(r'^item/(?P<pk>\d+)/', include(stock_item_detail_urls)),
|
url(r'^item/(?P<pk>\d+)/', include(stock_item_detail_urls)),
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from django.forms.models import model_to_dict
|
|||||||
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from .models import StockItem, StockLocation
|
from .models import StockItem, StockLocation, StockItemTracking
|
||||||
|
|
||||||
from .forms import EditStockLocationForm
|
from .forms import EditStockLocationForm
|
||||||
from .forms import CreateStockItemForm
|
from .forms import CreateStockItemForm
|
||||||
@ -248,3 +248,13 @@ class StockItemStocktake(AjaxUpdateView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form, data)
|
return self.renderJsonResponse(request, form, data)
|
||||||
|
|
||||||
|
|
||||||
|
class StockTrackingIndex(ListView):
|
||||||
|
"""
|
||||||
|
StockTrackingIndex provides a page to display StockItemTracking objects
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = StockItemTracking
|
||||||
|
template_name = 'stock/tracking.html'
|
||||||
|
context_object_name = 'items'
|
||||||
|
Loading…
Reference in New Issue
Block a user