mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
# Conflicts: # InvenTree/static/script/inventree/stock.js # InvenTree/stock/forms.py # InvenTree/stock/urls.py # InvenTree/stock/views.py
This commit is contained in:
commit
9577e4e505
@ -6,10 +6,7 @@
|
|||||||
{% block collapse_panel_setup %}class='panel part-allocation' id='allocation-panel-{{ item.sub_part.id }}'{% endblock %}
|
{% block collapse_panel_setup %}class='panel part-allocation' id='allocation-panel-{{ item.sub_part.id }}'{% endblock %}
|
||||||
|
|
||||||
{% block collapse_title %}
|
{% block collapse_title %}
|
||||||
<div class='hover-icon media-left' style='float: left;'>
|
{% include "hover_image.html" with image=item.sub_part.image %}
|
||||||
<img class='hover-img-thumb' src="{% if item.sub_part.image %}{{ item.sub_part.image.url }}{% endif %}">
|
|
||||||
<img class='hover-img-large' src="{% if item.sub_part.image %}{{ item.sub_part.image.url }}{% endif %}">
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
{{ item.sub_part.full_name }}
|
{{ item.sub_part.full_name }}
|
||||||
<small><i>{{ item.sub_part.description }}</i></small>
|
<small><i>{{ item.sub_part.description }}</i></small>
|
||||||
|
@ -21,10 +21,7 @@ Automatically allocate stock to this build?
|
|||||||
{% for item in allocations %}
|
{% for item in allocations %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class='hover-icon'>
|
{% include "hover_image.html" with image=item.stock_item.part.image %}
|
||||||
<img class='hover-img-thumb' src='{% if item.stock_item.part.image %}{{ item.stock_item.part.image.url }}{% endif %}'>
|
|
||||||
<img class='hover-img-large' src='{% if item.stock_item.part.image %}{{ item.stock_item.part.image.url }}{% endif %}'>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.stock_item.part.full_name }}<br>
|
{{ item.stock_item.part.full_name }}<br>
|
||||||
|
@ -30,7 +30,7 @@ InvenTree | Build - {{ build }}
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Part</td>
|
<td>Part</td>
|
||||||
<td>{{ build.part.full_name }}</td>
|
<td><a href="{% url 'part-detail' build.part.id %}">{{ build.part.full_name }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Quantity</td>
|
<td>Quantity</td>
|
||||||
|
@ -18,10 +18,7 @@ The following items will be removed from stock:
|
|||||||
{% for item in taking %}
|
{% for item in taking %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class='hover-icon'>
|
{% include "hover_image.html" with image=item.stock_item.part.image %}
|
||||||
<img class='hover-img-thumb' src='{{ item.stock_item.part.image.url }}'>
|
|
||||||
<img class='hover-img-large' src='{{ item.stock_item.part.image.url }}'>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.stock_item.part.full_name }}<br>
|
{{ item.stock_item.part.full_name }}<br>
|
||||||
@ -38,10 +35,7 @@ No parts have been allocated to this build.
|
|||||||
<hr>
|
<hr>
|
||||||
The following items will be created:
|
The following items will be created:
|
||||||
<div class='panel panel-default'>
|
<div class='panel panel-default'>
|
||||||
<a class='hover-icon'>
|
{% include "hover_image.html" with image=build.part.image %}
|
||||||
<img class='hover-img-thumb' src='{{ build.part.image.url }}'>
|
|
||||||
<img class='hover-img-large' src='{{ build.part.image.url }}'>
|
|
||||||
</a>
|
|
||||||
{{ build.quantity }} x {{ build.part.full_name }}
|
{{ build.quantity }} x {{ build.part.full_name }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -19,10 +19,7 @@
|
|||||||
{% for item in build.required_parts %}
|
{% for item in build.required_parts %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class='hover-icon'>
|
{% include "hover_image.html" with image=item.part.image %}
|
||||||
<img class='hover-img-thumb' src='{% if item.part.image %}{{ item.part.image.url }}{% endif %}'>
|
|
||||||
<img class='hover-img-large' src='{% if item.part.image %}{{ item.part.image.url }}{% endif %}'>
|
|
||||||
</a>
|
|
||||||
<a class='hover-icon'a href="{% url 'part-detail' item.part.id %}">{{ item.part.full_name }}</a>
|
<a class='hover-icon'a href="{% url 'part-detail' item.part.id %}">{{ item.part.full_name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.part.total_stock }}</td>
|
<td>{{ item.part.total_stock }}</td>
|
||||||
|
@ -33,12 +33,7 @@
|
|||||||
{% for variant in part.variants.all %}
|
{% for variant in part.variants.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class='hover-icon media-left' style='float: left;'>
|
{% include "hover_image.html" with image=variant.image %}
|
||||||
<img class='hover-img-thumb' src="{% if variant.image %}{{ variant.image.url }}{% else %}{% static 'img/blank_image.png' %}{% endif %}">
|
|
||||||
{% if variant.image %}
|
|
||||||
<img class='hover-img-large' src="{{ variant.image.url }}">
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<a href="{% url 'part-detail' variant.id %}">{{ variant.full_name }}</a>
|
<a href="{% url 'part-detail' variant.id %}">{{ variant.full_name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ variant.description }}</td>
|
<td>{{ variant.description }}</td>
|
||||||
|
@ -192,7 +192,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
width: 45%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-secondary .modal-dialog {
|
.modal-secondary .modal-dialog {
|
||||||
@ -225,6 +225,7 @@
|
|||||||
/* Force a control-label div to be 100% width */
|
/* Force a control-label div to be 100% width */
|
||||||
.modal .control-label {
|
.modal .control-label {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal .control-label .btn {
|
.modal .control-label .btn {
|
||||||
@ -281,6 +282,13 @@
|
|||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-remove {
|
||||||
|
padding: 3px;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
color: #A11;
|
||||||
|
}
|
||||||
|
|
||||||
.button-toolbar {
|
.button-toolbar {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
|
@ -14,377 +14,34 @@ function getStockLocations(filters={}, options={}) {
|
|||||||
return inventreeGet('/api/stock/location/', filters, options)
|
return inventreeGet('/api/stock/location/', filters, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Functions for interacting with stock management forms
|
||||||
/* Present user with a dialog to update multiple stock items
|
|
||||||
* Possible actions:
|
|
||||||
* - Stocktake
|
|
||||||
* - Take stock
|
|
||||||
* - Add stock
|
|
||||||
*/
|
|
||||||
function updateStock(items, options={}) {
|
|
||||||
|
|
||||||
if (!options.action) {
|
|
||||||
alert('No action supplied to stock update');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var modal = options.modal || '#modal-form';
|
|
||||||
|
|
||||||
if (items.length == 0) {
|
|
||||||
alert('No items selected');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var html = '';
|
|
||||||
|
|
||||||
html += "<table class='table table-striped table-condensed' id='stocktake-table'>\n";
|
|
||||||
|
|
||||||
html += '<thead><tr>';
|
|
||||||
html += '<th>Item</th>';
|
|
||||||
html += '<th>Location</th>';
|
|
||||||
html += '<th>Quantity</th>';
|
|
||||||
html += '<th>' + options.action + '</th>';
|
|
||||||
|
|
||||||
html += '</thead><tbody>';
|
|
||||||
|
|
||||||
for (idx=0; idx<items.length; idx++) {
|
|
||||||
var item = items[idx];
|
|
||||||
|
|
||||||
var vMin = 0;
|
|
||||||
var vMax = 0;
|
|
||||||
var vCur = item.quantity;
|
|
||||||
|
|
||||||
if (options.action == 'remove') {
|
|
||||||
vCur = 0;
|
|
||||||
vMax = item.quantity;
|
|
||||||
}
|
|
||||||
else if (options.action == 'add') {
|
|
||||||
vCur = 0;
|
|
||||||
vMax = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<tr>';
|
|
||||||
|
|
||||||
html += '<td>' + item.part.full_name + '</td>';
|
|
||||||
|
|
||||||
if (item.location) {
|
|
||||||
html += '<td>' + item.location.name + '</td>';
|
|
||||||
} else {
|
|
||||||
html += '<td><i>No location set</i></td>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<td>' + item.quantity + '</td>';
|
|
||||||
|
|
||||||
html += "<td><input class='form-control' ";
|
|
||||||
html += "value='" + vCur + "' ";
|
|
||||||
html += "min='" + vMin + "' ";
|
|
||||||
|
|
||||||
if (vMax > 0) {
|
|
||||||
html += "max='" + vMax + "' ";
|
|
||||||
}
|
|
||||||
|
|
||||||
html += "type='number' id='q-update-" + item.pk + "'/></td>";
|
|
||||||
|
|
||||||
html += '</tr>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</tbody></table>';
|
|
||||||
|
|
||||||
html += "<hr><input type='text' id='stocktake-notes' placeholder='Notes'/>";
|
|
||||||
html += "<p class='help-inline' id='note-warning'><strong>Note field must be filled</strong></p>";
|
|
||||||
|
|
||||||
html += `
|
|
||||||
<hr>
|
|
||||||
<div class='control-group'>
|
|
||||||
<label class='checkbox'>
|
|
||||||
<input type='checkbox' id='stocktake-confirm' placeholder='Confirm'/>
|
|
||||||
Confirm Stocktake
|
|
||||||
</label>
|
|
||||||
<p class='help-inline' id='confirm-warning'><strong>Confirm stock count</strong></p>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
|
|
||||||
var title = '';
|
|
||||||
|
|
||||||
if (options.action == 'stocktake') {
|
|
||||||
title = 'Stocktake';
|
|
||||||
}
|
|
||||||
else if (options.action == 'remove') {
|
|
||||||
title = 'Remove stock items';
|
|
||||||
}
|
|
||||||
else if (options.action == 'add') {
|
|
||||||
title = 'Add stock items';
|
|
||||||
}
|
|
||||||
|
|
||||||
openModal({
|
|
||||||
modal: modal,
|
|
||||||
title: title,
|
|
||||||
content: html
|
|
||||||
});
|
|
||||||
|
|
||||||
$(modal).find('#note-warning').hide();
|
|
||||||
$(modal).find('#confirm-warning').hide();
|
|
||||||
|
|
||||||
modalEnable(modal, true);
|
|
||||||
|
|
||||||
modalSubmit(modal, function() {
|
|
||||||
|
|
||||||
var stocktake = [];
|
|
||||||
var notes = $(modal).find('#stocktake-notes').val();
|
|
||||||
var confirm = $(modal).find('#stocktake-confirm').is(':checked');
|
|
||||||
|
|
||||||
var valid = true;
|
|
||||||
|
|
||||||
if (!notes) {
|
|
||||||
$(modal).find('#note-warning').show();
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confirm) {
|
|
||||||
$(modal).find('#confirm-warning').show();
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Form stocktake data
|
|
||||||
for (idx = 0; idx < items.length; idx++) {
|
|
||||||
var item = items[idx];
|
|
||||||
|
|
||||||
var q = $(modal).find("#q-update-" + item.pk).val();
|
|
||||||
|
|
||||||
stocktake.push({
|
|
||||||
pk: item.pk,
|
|
||||||
quantity: q
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
alert('Invalid data');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inventreePut("/api/stock/stocktake/",
|
|
||||||
{
|
|
||||||
'action': options.action,
|
|
||||||
'items[]': stocktake,
|
|
||||||
'notes': $(modal).find('#stocktake-notes').val()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
}).then(function(response) {
|
|
||||||
closeModal(modal);
|
|
||||||
afterForm(response, options);
|
|
||||||
}).fail(function(xhr, status, error) {
|
|
||||||
alert(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function selectStockItems(options) {
|
|
||||||
/* Return list of selections from stock table
|
|
||||||
* If options.table not provided, assumed to be '#stock-table'
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var table_name = options.table || '#stock-table';
|
function removeStockRow(e) {
|
||||||
|
// Remove a selected row from a stock modal form
|
||||||
|
|
||||||
// Return list of selected items from the bootstrap table
|
e = e || window.event;
|
||||||
return $(table_name).bootstrapTable('getSelections');
|
var src = e.target || e.srcElement;
|
||||||
}
|
|
||||||
|
|
||||||
|
var row = $(src).attr('row');
|
||||||
|
|
||||||
function adjustStock(options) {
|
$('#' + row).remove();
|
||||||
if (options.items) {
|
|
||||||
updateStock(options.items, options);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Lookup of individual item
|
|
||||||
if (options.query.pk) {
|
|
||||||
getStockDetail(options.query.pk).then(function(response) {
|
|
||||||
updateStock([response], options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
getStockList(options.query).then(function(response) {
|
|
||||||
updateStock(response, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function updateStockItems(options) {
|
|
||||||
/* Update one or more stock items selected from a stock-table
|
|
||||||
* Options available:
|
|
||||||
* 'action' - Action to perform - 'add' / 'remove' / 'stocktake'
|
|
||||||
* 'table' - ID of the stock table (default = '#stock-table'
|
|
||||||
*/
|
|
||||||
|
|
||||||
var table = options.table || '#stock-table';
|
|
||||||
|
|
||||||
var items = selectStockItems({
|
|
||||||
table: table,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pass items through
|
|
||||||
options.items = items;
|
|
||||||
options.table = table;
|
|
||||||
|
|
||||||
// On success, reload the table
|
|
||||||
options.success = function() {
|
|
||||||
$(table).bootstrapTable('refresh');
|
|
||||||
};
|
|
||||||
|
|
||||||
adjustStock(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveStockItems(items, options) {
|
|
||||||
|
|
||||||
var modal = options.modal || '#modal-form';
|
|
||||||
|
|
||||||
if (items.length == 0) {
|
|
||||||
alert('No stock items selected');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function doMove(location, parts, notes) {
|
|
||||||
inventreePut("/api/stock/move/",
|
|
||||||
{
|
|
||||||
location: location,
|
|
||||||
'stock': parts,
|
|
||||||
'notes': notes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
}).then(function(response) {
|
|
||||||
closeModal(modal);
|
|
||||||
afterForm(response, options);
|
|
||||||
}).fail(function(xhr, status, error) {
|
|
||||||
alert(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getStockLocations({},
|
|
||||||
{
|
|
||||||
success: function(response) {
|
|
||||||
|
|
||||||
// Extact part row info
|
|
||||||
var parts = [];
|
|
||||||
|
|
||||||
var html = "Select new location:<br>\n";
|
|
||||||
|
|
||||||
html += "<select class='select' id='stock-location'>";
|
|
||||||
|
|
||||||
for (i = 0; i < response.length; i++) {
|
|
||||||
var loc = response[i];
|
|
||||||
|
|
||||||
html += makeOption(loc.pk, loc.pathstring + ' - <i>' + loc.description + '</i>');
|
|
||||||
}
|
|
||||||
|
|
||||||
html += "</select><br>";
|
|
||||||
|
|
||||||
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:<hr>";
|
|
||||||
|
|
||||||
html += `
|
|
||||||
<table class='table table-striped table-condensed'>
|
|
||||||
<tr>
|
|
||||||
<th>Part</th>
|
|
||||||
<th>Location</th>
|
|
||||||
<th>Available</th>
|
|
||||||
<th>Moving</th>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
|
|
||||||
for (i = 0; i < items.length; i++) {
|
|
||||||
|
|
||||||
parts.push({
|
|
||||||
pk: items[i].pk,
|
|
||||||
quantity: items[i].quantity,
|
|
||||||
});
|
|
||||||
|
|
||||||
var item = items[i];
|
|
||||||
|
|
||||||
var name = item.part__IPN;
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
name += ' | ';
|
|
||||||
}
|
|
||||||
|
|
||||||
name += item.part__name;
|
|
||||||
|
|
||||||
html += "<tr>";
|
|
||||||
|
|
||||||
html += "<td>" + name + "</td>";
|
|
||||||
html += "<td>" + item.location__path + "</td>";
|
|
||||||
html += "<td>" + item.quantity + "</td>";
|
|
||||||
|
|
||||||
html += "<td>";
|
|
||||||
html += "<input class='form-control' min='0' max='" + item.quantity + "'";
|
|
||||||
html += " value='" + item.quantity + "'";
|
|
||||||
html += "type='number' id='q-move-" + item.pk + "'/></td>";
|
|
||||||
|
|
||||||
html += "</tr>";
|
|
||||||
}
|
|
||||||
|
|
||||||
html += "</table>";
|
|
||||||
|
|
||||||
openModal({
|
|
||||||
modal: modal,
|
|
||||||
title: "Move " + items.length + " stock items",
|
|
||||||
submit_text: "Move",
|
|
||||||
content: html
|
|
||||||
});
|
|
||||||
|
|
||||||
//modalSetContent(modal, html);
|
|
||||||
attachSelect(modal);
|
|
||||||
|
|
||||||
modalEnable(modal, true);
|
|
||||||
|
|
||||||
$(modal).find('#note-warning').hide();
|
|
||||||
|
|
||||||
modalSubmit(modal, function() {
|
|
||||||
var locId = $(modal).find("#stock-location").val();
|
|
||||||
|
|
||||||
var notes = $(modal).find('#notes').val();
|
|
||||||
|
|
||||||
if (!notes) {
|
|
||||||
$(modal).find('#note-warning').show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the quantity for each item
|
|
||||||
for (var ii = 0; ii < parts.length; ii++) {
|
|
||||||
var pk = parts[ii].pk;
|
|
||||||
|
|
||||||
var q = $(modal).find('#q-move-' + pk).val();
|
|
||||||
|
|
||||||
parts[ii].quantity = q;
|
|
||||||
}
|
|
||||||
|
|
||||||
doMove(locId, parts, notes);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
alert('Error getting stock locations:\n' + error.error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadStockTable(table, options) {
|
function loadStockTable(table, options) {
|
||||||
|
/* Load data into a stock table with adjustable options.
|
||||||
|
* Fetches data (via AJAX) and loads into a bootstrap table.
|
||||||
|
* Also links in default button callbacks.
|
||||||
|
*
|
||||||
|
* Options:
|
||||||
|
* url - URL for the stock query
|
||||||
|
* params - query params for augmenting stock data request
|
||||||
|
* groupByField - Column for grouping stock items
|
||||||
|
* buttons - Which buttons to link to stock selection callbacks
|
||||||
|
*/
|
||||||
|
|
||||||
var params = options.params || {};
|
var params = options.params || {};
|
||||||
|
|
||||||
// Aggregate stock items
|
|
||||||
//params.aggregate = true;
|
|
||||||
|
|
||||||
table.bootstrapTable({
|
table.bootstrapTable({
|
||||||
sortable: true,
|
sortable: true,
|
||||||
search: true,
|
search: true,
|
||||||
@ -526,31 +183,8 @@ function loadStockTable(table, options) {
|
|||||||
linkButtonsToSelection(table, options.buttons);
|
linkButtonsToSelection(table, options.buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically link button callbacks
|
function stockAdjustment(action) {
|
||||||
$('#multi-item-stocktake').click(function() {
|
var items = $("#stock-table").bootstrapTable("getSelections");
|
||||||
updateStockItems({
|
|
||||||
action: 'stocktake',
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#multi-item-remove').click(function() {
|
|
||||||
updateStockItems({
|
|
||||||
action: 'remove',
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#multi-item-add').click(function() {
|
|
||||||
updateStockItems({
|
|
||||||
action: 'add',
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#multi-item-move").click(function() {
|
|
||||||
|
|
||||||
var items = $('#stock-table').bootstrapTable('getSelections');
|
|
||||||
|
|
||||||
var stock = [];
|
var stock = [];
|
||||||
|
|
||||||
@ -558,27 +192,34 @@ function loadStockTable(table, options) {
|
|||||||
stock.push(item.pk);
|
stock.push(item.pk);
|
||||||
});
|
});
|
||||||
|
|
||||||
launchModalForm("/stock/move/",
|
launchModalForm("/stock/adjust/",
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
|
action: action,
|
||||||
stock: stock,
|
stock: stock,
|
||||||
},
|
},
|
||||||
|
success: function() {
|
||||||
|
$("#stock-table").bootstrapTable('refresh');
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
var items = $("#stock-table").bootstrapTable('getSelections');
|
|
||||||
|
|
||||||
moveStockItems(items,
|
|
||||||
{
|
|
||||||
success: function() {
|
|
||||||
$("#stock-table").bootstrapTable('refresh');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatically link button callbacks
|
||||||
|
$('#multi-item-stocktake').click(function() {
|
||||||
|
stockAdjustment('count');
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
$('#multi-item-remove').click(function() {
|
||||||
*/
|
stockAdjustment('take');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#multi-item-add').click(function() {
|
||||||
|
stockAdjustment('add');
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#multi-item-move").click(function() {
|
||||||
|
stockAdjustment('move');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,9 +470,9 @@ stock_api_urls = [
|
|||||||
|
|
||||||
url(r'location/(?P<pk>\d+)/', include(location_endpoints)),
|
url(r'location/(?P<pk>\d+)/', include(location_endpoints)),
|
||||||
|
|
||||||
url(r'stocktake/?', StockStocktake.as_view(), name='api-stock-stocktake'),
|
# These JSON endpoints have been replaced (for now) with server-side form rendering - 02/06/2019
|
||||||
|
# url(r'stocktake/?', StockStocktake.as_view(), name='api-stock-stocktake'),
|
||||||
url(r'move/?', StockMove.as_view(), name='api-stock-move'),
|
# url(r'move/?', StockMove.as_view(), name='api-stock-move'),
|
||||||
|
|
||||||
url(r'track/?', StockTrackingList.as_view(), name='api-stock-track'),
|
url(r'track/?', StockTrackingList.as_view(), name='api-stock-track'),
|
||||||
|
|
||||||
|
@ -42,8 +42,16 @@ class CreateStockItemForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MoveStockItemForm(forms.ModelForm):
|
class AdjustStockForm(forms.ModelForm):
|
||||||
""" Form for moving a StockItem to a new location """
|
""" Form for performing simple stock adjustments.
|
||||||
|
|
||||||
|
- Add stock
|
||||||
|
- Remove stock
|
||||||
|
- Count stock
|
||||||
|
- Move stock
|
||||||
|
|
||||||
|
This form is used for managing stock adjuments for single or multiple stock items.
|
||||||
|
"""
|
||||||
|
|
||||||
def get_location_choices(self):
|
def get_location_choices(self):
|
||||||
locs = StockLocation.objects.all()
|
locs = StockLocation.objects.all()
|
||||||
@ -55,37 +63,27 @@ class MoveStockItemForm(forms.ModelForm):
|
|||||||
|
|
||||||
return choices
|
return choices
|
||||||
|
|
||||||
location = forms.ChoiceField(label='Destination', required=True, help_text='Destination stock location')
|
destination = forms.ChoiceField(label='Destination', required=True, help_text='Destination stock location')
|
||||||
note = forms.CharField(label='Notes', required=True, help_text='Add note (required)')
|
note = forms.CharField(label='Notes', required=True, help_text='Add note (required)')
|
||||||
transaction = forms.BooleanField(required=False, initial=False, label='Create Transaction', help_text='Create a stock transaction for these parts')
|
# transaction = forms.BooleanField(required=False, initial=False, label='Create Transaction', help_text='Create a stock transaction for these parts')
|
||||||
confirm = forms.BooleanField(required=False, initial=False, label='Confirm Stock Movement', help_text='Confirm movement of stock items')
|
confirm = forms.BooleanField(required=False, initial=False, label='Confirm Stock Movement', help_text='Confirm movement of stock items')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(MoveStockItemForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields['location'].choices = self.get_location_choices()
|
self.fields['destination'].choices = self.get_location_choices()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StockItem
|
model = StockItem
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
'location',
|
'destination',
|
||||||
'note',
|
'note',
|
||||||
'transaction',
|
# 'transaction',
|
||||||
'confirm',
|
'confirm',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StocktakeForm(forms.ModelForm):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = StockItem
|
|
||||||
|
|
||||||
fields = [
|
|
||||||
'quantity',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EditStockItemForm(HelperForm):
|
class EditStockItemForm(HelperForm):
|
||||||
""" Form for editing a StockItem object.
|
""" Form for editing a StockItem object.
|
||||||
Note that not all fields can be edited here (even if they can be specified during creation.
|
Note that not all fields can be edited here (even if they can be specified during creation.
|
||||||
|
@ -22,11 +22,13 @@
|
|||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% if item.in_stock %}
|
{% if item.in_stock %}
|
||||||
<li><a href="#" id='stock-edit' title='Edit stock item'>Edit stock item</a></li>
|
<li><a href="#" id='stock-edit' title='Edit stock item'>Edit stock item</a></li>
|
||||||
<li><a href="#" id='stock-move' title='Move stock item'>Move stock item</a></li>
|
<hr>
|
||||||
<li><a href='#' id='stock-add' title='Add stock'>Add to stock</a></li>
|
<li><a href='#' id='stock-add' title='Add stock'>Add to stock</a></li>
|
||||||
<li><a href='#' id='stock-remove' title='Remove stock'>Take from stock</a></li>
|
<li><a href='#' id='stock-remove' title='Take stock'>Take from stock</a></li>
|
||||||
<li><a href='#' id='stock-stocktake' title='Count stock'>Stocktake</a></li>
|
<li><a href='#' id='stock-stocktake' title='Count stock'>Stocktake</a></li>
|
||||||
|
<li><a href="#" id='stock-move' title='Move stock'>Move stock item</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<hr>
|
||||||
<li><a href="#" id='stock-delete' title='Delete stock item'>Delete stock item</a></li>
|
<li><a href="#" id='stock-delete' title='Delete stock item'>Delete stock item</a></li>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -155,40 +157,33 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
{% if item.in_stock %}
|
{% if item.in_stock %}
|
||||||
$("#stock-move").click(function() {
|
|
||||||
launchModalForm(
|
|
||||||
"{% url 'stock-item-move' item.id %}",
|
|
||||||
{
|
|
||||||
reload: true,
|
|
||||||
submit_text: "Move"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function itemAdjust(action) {
|
function itemAdjust(action) {
|
||||||
adjustStock({
|
launchModalForm("/stock/adjust/",
|
||||||
query: {
|
{
|
||||||
pk: {{ item.id }},
|
data: {
|
||||||
},
|
|
||||||
action: action,
|
action: action,
|
||||||
success: function() {
|
item: {{ item.id }},
|
||||||
location.reload();
|
},
|
||||||
|
reload: true,
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#stock-move").click(function() {
|
||||||
|
itemAdjust("move");
|
||||||
|
});
|
||||||
|
|
||||||
$("#stock-stocktake").click(function() {
|
$("#stock-stocktake").click(function() {
|
||||||
itemAdjust('stocktake');
|
itemAdjust('count');
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#stock-remove').click(function() {
|
$('#stock-remove').click(function() {
|
||||||
itemAdjust('remove');
|
itemAdjust('take');
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#stock-add').click(function() {
|
$('#stock-add').click(function() {
|
||||||
itemAdjust('add');
|
itemAdjust('add');
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
39
InvenTree/stock/templates/stock/stock_adjust.html
Normal file
39
InvenTree/stock/templates/stock/stock_adjust.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{% block pre_form_content %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<form method="post" action='' class='js-modal-form' enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
<input type='hidden' name='stock_action' value='{{ stock_action }}'/>
|
||||||
|
|
||||||
|
<table class='table table-condensed table-striped' id='stock-table'>
|
||||||
|
<tr>
|
||||||
|
<th>Stock Item</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>{{ stock_action_title }}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
{% for item in stock_items %}
|
||||||
|
<tr id='stock-row-{{ item.id }}' class='error'>
|
||||||
|
<td>{% include "hover_image.html" with image=item.part.image %}
|
||||||
|
{{ item.part.full_name }} <small><i>{{ item.part.description }}</i></small></td>
|
||||||
|
<td>{{ item.location.pathstring }}</td>
|
||||||
|
<td>
|
||||||
|
<input class='numberinput'
|
||||||
|
min='0'
|
||||||
|
{% if stock_action == 'take' or stock_action == 'move' %} max='{{ item.quantity }}' {% endif %}
|
||||||
|
value='{{ item.new_quantity }}' type='number' name='stock-id-{{ item.id }}' id='stock-id-{{ item.id }}'/>
|
||||||
|
{% if item.error %}
|
||||||
|
<br><span class='help-inline'>{{ item.error }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td><button class='btn btn-default btn-remove' id='del-{{ item.id }}' title='Remove item' type='button'><span row='stock-row-{{ item.id }}' onclick='removeStockRow()' class='glyphicon glyphicon-small glyphicon-remove'></span></button></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% crispy form %}
|
||||||
|
|
||||||
|
</form>
|
@ -19,8 +19,6 @@ stock_location_detail_urls = [
|
|||||||
stock_item_detail_urls = [
|
stock_item_detail_urls = [
|
||||||
url(r'^edit/?', views.StockItemEdit.as_view(), name='stock-item-edit'),
|
url(r'^edit/?', views.StockItemEdit.as_view(), name='stock-item-edit'),
|
||||||
url(r'^delete/?', views.StockItemDelete.as_view(), name='stock-item-delete'),
|
url(r'^delete/?', views.StockItemDelete.as_view(), name='stock-item-delete'),
|
||||||
url(r'^move/?', views.StockItemMove.as_view(), name='stock-item-move'),
|
|
||||||
url(r'^stocktake/?', views.StockItemStocktake.as_view(), name='stock-item-stocktake'),
|
|
||||||
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('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
|
url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
|
||||||
@ -36,7 +34,7 @@ stock_urls = [
|
|||||||
|
|
||||||
url(r'^track/?', views.StockTrackingIndex.as_view(), name='stock-tracking-list'),
|
url(r'^track/?', views.StockTrackingIndex.as_view(), name='stock-tracking-list'),
|
||||||
|
|
||||||
url(r'^move/?', views.StockItemMoveMultiple.as_view(), name='stock-item-move-multiple'),
|
url(r'^adjust/?', views.StockAdjust.as_view(), name='stock-adjust'),
|
||||||
|
|
||||||
# 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)),
|
||||||
|
@ -10,19 +10,21 @@ from django.views.generic import DetailView, ListView
|
|||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
from django.forms import HiddenInput
|
from django.forms import HiddenInput
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from InvenTree.views import AjaxView
|
from InvenTree.views import AjaxView
|
||||||
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
||||||
from InvenTree.views import QRCodeView
|
from InvenTree.views import QRCodeView
|
||||||
|
|
||||||
|
from InvenTree.helpers import str2bool
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from .models import StockItem, StockLocation, StockItemTracking
|
from .models import StockItem, StockLocation, StockItemTracking
|
||||||
|
|
||||||
from .forms import EditStockLocationForm
|
from .forms import EditStockLocationForm
|
||||||
from .forms import CreateStockItemForm
|
from .forms import CreateStockItemForm
|
||||||
from .forms import EditStockItemForm
|
from .forms import EditStockItemForm
|
||||||
from .forms import MoveStockItemForm
|
from .forms import AdjustStockForm
|
||||||
from .forms import StocktakeForm
|
|
||||||
from .forms import MoveStockItemForm
|
|
||||||
|
|
||||||
|
|
||||||
class StockIndex(ListView):
|
class StockIndex(ListView):
|
||||||
@ -125,52 +127,273 @@ class StockItemQRCode(QRCodeView):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class StockItemMoveMultiple(AjaxView, FormMixin):
|
class StockAdjust(AjaxView, FormMixin):
|
||||||
""" Move multiple stock items """
|
""" View for enacting simple stock adjustments:
|
||||||
|
|
||||||
ajax_template_name = 'stock/stock_move.html'
|
- Take items from stock
|
||||||
ajax_form_title = 'Move Stock'
|
- Add items to stock
|
||||||
form_class = MoveStockItemForm
|
- Count items
|
||||||
|
- Move stock
|
||||||
|
|
||||||
def get_items(self, item_list):
|
"""
|
||||||
""" Return list of stock items. """
|
|
||||||
|
|
||||||
items = []
|
ajax_template_name = 'stock/stock_adjust.html'
|
||||||
|
ajax_form_title = 'Adjust Stock'
|
||||||
|
form_class = AdjustStockForm
|
||||||
|
stock_items = []
|
||||||
|
|
||||||
for pk in item_list:
|
def get_GET_items(self):
|
||||||
try:
|
""" Return list of stock items initally requested using GET """
|
||||||
items.append(StockItem.objects.get(pk=pk))
|
|
||||||
except StockItem.DoesNotExist:
|
# Start with all 'in stock' items
|
||||||
pass
|
items = StockItem.objects.filter(customer=None, belongs_to=None)
|
||||||
|
|
||||||
|
# Client provides a list of individual stock items
|
||||||
|
if 'stock[]' in self.request.GET:
|
||||||
|
items = items.filter(id__in=self.request.GET.getlist('stock[]'))
|
||||||
|
|
||||||
|
# Client provides a PART reference
|
||||||
|
elif 'part' in self.request.GET:
|
||||||
|
items = items.filter(part=self.request.GET.get('part'))
|
||||||
|
|
||||||
|
# Client provides a LOCATION reference
|
||||||
|
elif 'location' in self.request.GET:
|
||||||
|
items = items.filter(location=self.request.GET.get('location'))
|
||||||
|
|
||||||
|
# Client provides a single StockItem lookup
|
||||||
|
elif 'item' in self.request.GET:
|
||||||
|
items = [StockItem.objects.get(id=self.request.GET.get('item'))]
|
||||||
|
|
||||||
|
# Unsupported query
|
||||||
|
else:
|
||||||
|
items = None
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
|
||||||
|
# Initialize quantity to zero for addition/removal
|
||||||
|
if self.stock_action in ['take', 'add']:
|
||||||
|
item.new_quantity = 0
|
||||||
|
# Initialize quantity at full amount for counting or moving
|
||||||
|
else:
|
||||||
|
item.new_quantity = item.quantity
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def get_POST_items(self):
|
||||||
|
""" Return list of stock items sent back by client on a POST request """
|
||||||
|
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for item in self.request.POST:
|
||||||
|
if item.startswith('stock-id-'):
|
||||||
|
|
||||||
|
pk = item.replace('stock-id-', '')
|
||||||
|
q = self.request.POST[item]
|
||||||
|
|
||||||
|
try:
|
||||||
|
stock_item = StockItem.objects.get(pk=pk)
|
||||||
|
except StockItem.DoesNotExist:
|
||||||
|
continue
|
||||||
|
|
||||||
|
stock_item.new_quantity = q
|
||||||
|
|
||||||
|
items.append(stock_item)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
|
||||||
|
context = super().get_context_data()
|
||||||
|
|
||||||
|
context['stock_items'] = self.stock_items
|
||||||
|
|
||||||
|
context['stock_action'] = self.stock_action
|
||||||
|
|
||||||
|
context['stock_action_title'] = self.stock_action.capitalize()
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
|
||||||
|
form = super().get_form()
|
||||||
|
|
||||||
|
if not self.stock_action == 'move':
|
||||||
|
form.fields.pop('destination')
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
item_list = request.GET.getlist('stock[]')
|
self.request = request
|
||||||
|
|
||||||
items = self.get_items(item_list)
|
# Action
|
||||||
|
self.stock_action = request.GET.get('action', '').lower()
|
||||||
|
|
||||||
print(items)
|
# Pick a default action...
|
||||||
|
if self.stock_action not in ['move', 'count', 'take', 'add']:
|
||||||
|
self.stock_action = 'count'
|
||||||
|
|
||||||
return self.renderJsonResponse(request, self.form_class())
|
# Choose the form title based on the action
|
||||||
|
titles = {
|
||||||
|
'move': 'Move Stock',
|
||||||
|
'count': 'Count Stock',
|
||||||
|
'take': 'Remove Stock',
|
||||||
|
'add': 'Add Stock'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ajax_form_title = titles[self.stock_action]
|
||||||
|
|
||||||
|
# Save list of items!
|
||||||
|
self.stock_items = self.get_GET_items()
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, self.get_form())
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
|
||||||
|
self.stock_action = request.POST.get('stock_action').lower()
|
||||||
|
|
||||||
|
# Update list of stock items
|
||||||
|
self.stock_items = self.get_POST_items()
|
||||||
|
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
|
|
||||||
valid = form.is_valid()
|
valid = form.is_valid()
|
||||||
|
|
||||||
print("Valid:", valid)
|
for item in self.stock_items:
|
||||||
|
try:
|
||||||
|
item.new_quantity = int(item.new_quantity)
|
||||||
|
except ValueError:
|
||||||
|
item.error = _('Must enter integer value')
|
||||||
|
valid = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item.new_quantity < 0:
|
||||||
|
item.error = _('Quantity must be positive')
|
||||||
|
valid = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.stock_action in ['move', 'take']:
|
||||||
|
|
||||||
|
if item.new_quantity > item.quantity:
|
||||||
|
item.error = _('Quantity must not exceed {x}'.format(x=item.quantity))
|
||||||
|
valid = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
confirmed = str2bool(request.POST.get('confirm'))
|
||||||
|
|
||||||
|
if not confirmed:
|
||||||
|
valid = False
|
||||||
|
form.errors['confirm'] = [_('Confirm stock adjustment')]
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'form_valid': False,
|
'form_valid': valid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#form.errors['note'] = ['hello world']
|
if valid:
|
||||||
|
|
||||||
|
data['success'] = self.do_action()
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form, data=data)
|
return self.renderJsonResponse(request, form, data=data)
|
||||||
|
|
||||||
|
def do_action(self):
|
||||||
|
""" Perform stock adjustment action """
|
||||||
|
|
||||||
|
if self.stock_action == 'move':
|
||||||
|
destination = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
destination = StockLocation.objects.get(id=self.request.POST.get('destination'))
|
||||||
|
except StockLocation.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return self.do_move(destination)
|
||||||
|
|
||||||
|
elif self.stock_action == 'add':
|
||||||
|
return self.do_add()
|
||||||
|
|
||||||
|
elif self.stock_action == 'take':
|
||||||
|
return self.do_take()
|
||||||
|
|
||||||
|
elif self.stock_action == 'count':
|
||||||
|
return self.do_count()
|
||||||
|
|
||||||
|
else:
|
||||||
|
return 'No action performed'
|
||||||
|
|
||||||
|
def do_add(self):
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
note = self.request.POST['note']
|
||||||
|
|
||||||
|
for item in self.stock_items:
|
||||||
|
if item.new_quantity <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item.add_stock(item.new_quantity, self.request.user, notes=note)
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
return _("Added stock to {n} items".format(n=count))
|
||||||
|
|
||||||
|
def do_take(self):
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
note = self.request.POST['note']
|
||||||
|
|
||||||
|
for item in self.stock_items:
|
||||||
|
if item.new_quantity <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item.take_stock(item.new_quantity, self.request.user, notes=note)
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
return _("Removed stock from {n} items".format(n=count))
|
||||||
|
|
||||||
|
def do_count(self):
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
note = self.request.POST['note']
|
||||||
|
|
||||||
|
for item in self.stock_items:
|
||||||
|
|
||||||
|
item.stocktake(item.new_quantity, self.request.user, notes=note)
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
return _("Counted stock for {n} items".format(n=count))
|
||||||
|
|
||||||
|
def do_move(self, destination):
|
||||||
|
""" Perform actual stock movement """
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
note = self.request.POST['note']
|
||||||
|
|
||||||
|
for item in self.stock_items:
|
||||||
|
# Avoid moving zero quantity
|
||||||
|
if item.new_quantity <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Do not move to the same location
|
||||||
|
if destination == item.location:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item.move(destination, note, self.request.user, quantity=int(item.new_quantity))
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
return _('No items were moved')
|
||||||
|
|
||||||
|
else:
|
||||||
|
return _('Moved {n} items to {dest}'.format(
|
||||||
|
n=count,
|
||||||
|
dest=destination.pathstring))
|
||||||
|
|
||||||
|
|
||||||
class StockItemEdit(AjaxUpdateView):
|
class StockItemEdit(AjaxUpdateView):
|
||||||
@ -357,76 +580,6 @@ class StockItemDelete(AjaxDeleteView):
|
|||||||
ajax_form_title = 'Delete Stock Item'
|
ajax_form_title = 'Delete Stock Item'
|
||||||
|
|
||||||
|
|
||||||
class StockItemMove(AjaxUpdateView):
|
|
||||||
"""
|
|
||||||
View to move a StockItem from one location to another
|
|
||||||
Performs some data validation to prevent illogical stock moves
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = StockItem
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
context_object_name = 'item'
|
|
||||||
ajax_form_title = 'Move Stock Item'
|
|
||||||
form_class = MoveStockItemForm
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
form = self.form_class(request.POST, instance=self.get_object())
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
obj = self.get_object()
|
|
||||||
|
|
||||||
try:
|
|
||||||
loc_id = form['location'].value()
|
|
||||||
|
|
||||||
if loc_id:
|
|
||||||
loc = StockLocation.objects.get(pk=form['location'].value())
|
|
||||||
if str(loc.pk) == str(obj.pk):
|
|
||||||
form.errors['location'] = ['Item is already in this location']
|
|
||||||
else:
|
|
||||||
obj.move(loc, form['note'].value(), request.user)
|
|
||||||
else:
|
|
||||||
form.errors['location'] = ['Cannot move to an empty location']
|
|
||||||
|
|
||||||
except StockLocation.DoesNotExist:
|
|
||||||
form.errors['location'] = ['Location does not exist']
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'form_valid': form.is_valid() and len(form.errors) == 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form, data)
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemStocktake(AjaxUpdateView):
|
|
||||||
"""
|
|
||||||
View to perform stocktake on a single StockItem
|
|
||||||
Updates the quantity, which will also create a new StockItemTracking item
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = StockItem
|
|
||||||
template_name = 'modal_form.html'
|
|
||||||
context_object_name = 'item'
|
|
||||||
ajax_form_title = 'Item stocktake'
|
|
||||||
form_class = StocktakeForm
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
form = self.form_class(request.POST, instance=self.get_object())
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
obj = self.get_object()
|
|
||||||
|
|
||||||
obj.stocktake(form.data['quantity'], request.user)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'form_valid': form.is_valid()
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form, data)
|
|
||||||
|
|
||||||
|
|
||||||
class StockTrackingIndex(ListView):
|
class StockTrackingIndex(ListView):
|
||||||
"""
|
"""
|
||||||
StockTrackingIndex provides a page to display StockItemTracking objects
|
StockTrackingIndex provides a page to display StockItemTracking objects
|
||||||
|
12
InvenTree/templates/hover_image.html
Normal file
12
InvenTree/templates/hover_image.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<div class='hover-icon media-left' style='float: left;'>
|
||||||
|
{% if image %}
|
||||||
|
<a class='hover-icon'>
|
||||||
|
{% endif %}
|
||||||
|
<img class='hover-img-thumb' {% if image %}src='{{ image.url }}'{% else %}src='{% static "img/blank_image.png" %}'{% endif %}>
|
||||||
|
{% if image %}
|
||||||
|
<img class='hover-img-large' src='{{ image.url }}'>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
@ -8,8 +8,8 @@
|
|||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="#" id='multi-item-add' title='Add to selected stock items'>Add stock</a></li>
|
<li><a href="#" id='multi-item-add' title='Add to selected stock items'>Add stock</a></li>
|
||||||
<li><a href="#" id='multi-item-remove' title='Remove from selected stock items'>Remove stock</a></li>
|
<li><a href="#" id='multi-item-remove' title='Remove from selected stock items'>Remove stock</a></li>
|
||||||
<li><a href="#" id='multi-item-stocktake' title='Stocktake selected stock items'>Stocktake</a></li>
|
<li><a href="#" id='multi-item-stocktake' title='Stocktake selected stock items'>Count stock</a></li>
|
||||||
<li><a href='#' id='multi-item-move' title='Move selected stock items'>Move</a></li>
|
<li><a href='#' id='multi-item-move' title='Move selected stock items'>Move stock</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user