2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-18 13:05:42 +00:00

Merge branch 'inventree:master' into matmair/issue2385

This commit is contained in:
Matthias Mair
2022-05-02 21:50:52 +02:00
committed by GitHub
143 changed files with 39337 additions and 29974 deletions

View File

@ -1,5 +1,6 @@
{% extends "skeleton.html" %}
{% load static %}
{% load inventree_extras %}
{% load i18n %}
{% block head %}

View File

@ -165,6 +165,7 @@
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
<script type='text/javascript' src="{% static 'script/qr-scanner.umd.min.js' %}"></script>
<!-- general JS -->
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>

View File

@ -19,6 +19,7 @@
linkBarcodeDialog,
scanItemsIntoLocation,
unlinkBarcode,
onBarcodeScanClicked,
*/
function makeBarcodeInput(placeholderText='', hintText='') {
@ -31,6 +32,9 @@ function makeBarcodeInput(placeholderText='', hintText='') {
hintText = hintText || '{% trans "Enter barcode data" %}';
var html = `
<div id='barcode_scan_video_container' class='text-center' style='height: 240px; display: none;'>
<video id='barcode_scan_video' disablepictureinpicture playsinline height='240' style='object-fit: fill;'></video>
</div>
<div class='form-group'>
<label class='control-label' for='barcode'>{% trans "Barcode" %}</label>
<div class='controls'>
@ -39,6 +43,7 @@ function makeBarcodeInput(placeholderText='', hintText='') {
<span class='fas fa-qrcode'></span>
</span>
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
<button id='barcode_scan_btn' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'><span class='fas fa-camera'></span></button>
</div>
<div id='hint_barcode_data' class='help-block'>${hintText}</div>
</div>
@ -48,6 +53,44 @@ function makeBarcodeInput(placeholderText='', hintText='') {
return html;
}
qrScanner = null;
function startQrScanner() {
$('#barcode_scan_video_container').show();
qrScanner.start();
}
function stopQrScanner() {
if (qrScanner != null) qrScanner.stop();
$('#barcode_scan_video_container').hide();
}
function onBarcodeScanClicked(e) {
if ($('#barcode_scan_video_container').is(':visible') == false) startQrScanner(); else stopQrScanner();
}
function onCameraAvailable(hasCamera, options) {
if ( hasCamera == true ) {
// Camera is only acccessible if page is served over secure connection
if ( window.isSecureContext == true ) {
qrScanner = new QrScanner(document.getElementById('barcode_scan_video'), (result) => {
onBarcodeScanCompleted(result, options);
}, {
highlightScanRegion: true,
highlightCodeOutline: true,
});
$('#barcode_scan_btn').show();
}
}
}
function onBarcodeScanCompleted(result, options) {
if (result.data == '') return;
console.log('decoded qr code:', result.data);
stopQrScanner();
postBarcodeData(result.data, options);
}
function makeNotesField(options={}) {
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}';
@ -186,6 +229,11 @@ function barcodeDialog(title, options={}) {
$(modal).on('shown.bs.modal', function() {
$(modal + ' .modal-form-content').scrollTop(0);
// Check for qr-scanner camera
QrScanner.hasCamera().then( (hasCamera) => {
onCameraAvailable(hasCamera, options);
});
var barcode = $(modal + ' #barcode');
// Handle 'enter' key on barcode
@ -220,6 +268,12 @@ function barcodeDialog(title, options={}) {
});
$(modal).on('hidden.bs.modal', function() {
stopQrScanner();
if (qrScanner != null) qrScanner.destroy();
qrScanner = null;
});
modalSetTitle(modal, title);
if (options.onSubmit) {

View File

@ -26,15 +26,19 @@
editPurchaseOrderLineItem,
exportOrder,
loadPurchaseOrderLineItemTable,
loadPurchaseOrderExtraLineTable
loadPurchaseOrderTable,
loadSalesOrderAllocationTable,
loadSalesOrderLineItemTable,
loadSalesOrderExtraLineTable
loadSalesOrderShipmentTable,
loadSalesOrderTable,
newPurchaseOrderFromOrderWizard,
newSupplierPartFromOrderWizard,
removeOrderRowFromOrderWizard,
removePurchaseOrderLineItem,
loadOrderTotal,
extraLineFields,
*/
@ -272,7 +276,7 @@ function createPurchaseOrder(options={}) {
if (options.onSuccess) {
options.onSuccess(data);
} else {
// Default action is to redirect browser to the new PO
// Default action is to redirect browser to the new PurchaseOrder
location.href = `/order/purchase-order/${data.pk}/`;
}
},
@ -305,6 +309,28 @@ function soLineItemFields(options={}) {
}
/* Construct a set of fields for a OrderExtraLine form */
function extraLineFields(options={}) {
var fields = {
order: {
hidden: true,
},
quantity: {},
reference: {},
price: {},
price_currency: {},
notes: {},
};
if (options.order) {
fields.order.value = options.order;
}
return fields;
}
/* Construct a set of fields for the PurchaseOrderLineItem form */
function poLineItemFields(options={}) {
@ -502,7 +528,7 @@ function newPurchaseOrderFromOrderWizard(e) {
/**
* Receive stock items against a PurchaseOrder
* Uses the POReceive API endpoint
* Uses the PurchaseOrderReceive API endpoint
*
* arguments:
* - order_id, ID / PK for the PurchaseOrder instance
@ -1373,6 +1399,226 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
}
/**
* Load a table displaying lines for a particular PurchaseOrder
*
* @param {String} table : HTML ID tag e.g. '#table'
* @param {Object} options : object which contains:
* - order {integer} : pk of the PurchaseOrder
* - status: {integer} : status code for the order
*/
function loadPurchaseOrderExtraLineTable(table, options={}) {
options.table = table;
options.params = options.params || {};
if (!options.order) {
console.log('ERROR: function called without order ID');
return;
}
if (!options.status) {
console.log('ERROR: function called without order status');
return;
}
options.params.order = options.order;
options.params.part_detail = true;
options.params.allocations = true;
var filters = loadTableFilters('purchaseorderextraline');
for (var key in options.params) {
filters[key] = options.params[key];
}
options.url = options.url || '{% url "api-po-extra-line-list" %}';
var filter_target = options.filter_target || '#filter-list-purchase-order-extra-lines';
setupFilterList('purchaseorderextraline', $(table), filter_target);
// Is the order pending?
var pending = options.status == {{ SalesOrderStatus.PENDING }};
// Table columns to display
var columns = [
{
sortable: true,
field: 'reference',
title: '{% trans "Reference" %}',
switchable: true,
},
{
sortable: true,
field: 'quantity',
title: '{% trans "Quantity" %}',
footerFormatter: function(data) {
return data.map(function(row) {
return +row['quantity'];
}).reduce(function(sum, i) {
return sum + i;
}, 0);
},
switchable: false,
},
{
sortable: true,
field: 'price',
title: '{% trans "Unit Price" %}',
formatter: function(value, row) {
var formatter = new Intl.NumberFormat(
'en-US',
{
style: 'currency',
currency: row.price_currency
}
);
return formatter.format(row.price);
}
},
{
field: 'total_price',
sortable: true,
title: '{% trans "Total Price" %}',
formatter: function(value, row) {
var formatter = new Intl.NumberFormat(
'en-US',
{
style: 'currency',
currency: row.price_currency
}
);
return formatter.format(row.price * row.quantity);
},
footerFormatter: function(data) {
var total = data.map(function(row) {
return +row['price'] * row['quantity'];
}).reduce(function(sum, i) {
return sum + i;
}, 0);
var currency = (data.slice(-1)[0] && data.slice(-1)[0].price_currency) || 'USD';
var formatter = new Intl.NumberFormat(
'en-US',
{
style: 'currency',
currency: currency
}
);
return formatter.format(total);
}
}
];
columns.push({
field: 'notes',
title: '{% trans "Notes" %}',
});
if (pending) {
columns.push({
field: 'buttons',
switchable: false,
formatter: function(value, row, index, field) {
var html = `<div class='btn-group float-right' role='group'>`;
var pk = row.pk;
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
html += `</div>`;
return html;
}
});
}
function reloadTable() {
$(table).bootstrapTable('refresh');
reloadTotal();
}
// Configure callback functions once the table is loaded
function setupCallbacks() {
// Callback for duplicating lines
$(table).find('.button-duplicate').click(function() {
var pk = $(this).attr('pk');
inventreeGet(`/api/order/po-extra-line/${pk}/`, {}, {
success: function(data) {
var fields = extraLineFields();
constructForm('{% url "api-po-extra-line-list" %}', {
method: 'POST',
fields: fields,
data: data,
title: '{% trans "Duplicate Line" %}',
onSuccess: function(response) {
$(table).bootstrapTable('refresh');
}
});
}
});
});
// Callback for editing lines
$(table).find('.button-edit').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/order/po-extra-line/${pk}/`, {
fields: {
quantity: {},
reference: {},
price: {},
price_currency: {},
notes: {},
},
title: '{% trans "Edit Line" %}',
onSuccess: reloadTable,
});
});
// Callback for deleting lines
$(table).find('.button-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/order/po-extra-line/${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Line" %}',
onSuccess: reloadTable,
});
});
}
$(table).inventreeTable({
onPostBody: setupCallbacks,
name: 'purchaseorderextraline',
sidePagination: 'client',
formatNoMatches: function() {
return '{% trans "No matching line" %}';
},
queryParams: filters,
original: options.params,
url: options.url,
showFooter: true,
uniqueId: 'pk',
detailViewByClick: false,
columns: columns,
});
}
/*
* Load table displaying list of sales orders
*/
@ -2167,7 +2413,7 @@ function showAllocationSubTable(index, row, element, options) {
},
{
field: 'buttons',
title: '{% trans "" %}',
title: '',
formatter: function(value, row, index, field) {
var html = `<div class='btn-group float-right' role='group'>`;
@ -2259,6 +2505,26 @@ function showFulfilledSubTable(index, row, element, options) {
});
}
var TotalPriceRef = ''; // reference to total price field
var TotalPriceOptions = {}; // options to reload the price
function loadOrderTotal(reference, options={}) {
TotalPriceRef = reference;
TotalPriceOptions = options;
}
function reloadTotal() {
inventreeGet(
TotalPriceOptions.url,
{},
{
success: function(data) {
$(TotalPriceRef).html(data.total_price_string);
}
}
);
};
/**
* Load a table displaying line items for a particular SalesOrder
@ -2556,6 +2822,7 @@ function loadSalesOrderLineItemTable(table, options={}) {
function reloadTable() {
$(table).bootstrapTable('refresh');
reloadTotal();
}
// Configure callback functions once the table is loaded
@ -2765,3 +3032,223 @@ function loadSalesOrderLineItemTable(table, options={}) {
columns: columns,
});
}
/**
* Load a table displaying lines for a particular SalesOrder
*
* @param {String} table : HTML ID tag e.g. '#table'
* @param {Object} options : object which contains:
* - order {integer} : pk of the SalesOrder
* - status: {integer} : status code for the order
*/
function loadSalesOrderExtraLineTable(table, options={}) {
options.table = table;
options.params = options.params || {};
if (!options.order) {
console.log('ERROR: function called without order ID');
return;
}
if (!options.status) {
console.log('ERROR: function called without order status');
return;
}
options.params.order = options.order;
options.params.part_detail = true;
options.params.allocations = true;
var filters = loadTableFilters('salesorderextraline');
for (var key in options.params) {
filters[key] = options.params[key];
}
options.url = options.url || '{% url "api-so-extra-line-list" %}';
var filter_target = options.filter_target || '#filter-list-sales-order-extra-lines';
setupFilterList('salesorderextraline', $(table), filter_target);
// Is the order pending?
var pending = options.status == {{ SalesOrderStatus.PENDING }};
// Table columns to display
var columns = [
{
sortable: true,
field: 'reference',
title: '{% trans "Reference" %}',
switchable: true,
},
{
sortable: true,
field: 'quantity',
title: '{% trans "Quantity" %}',
footerFormatter: function(data) {
return data.map(function(row) {
return +row['quantity'];
}).reduce(function(sum, i) {
return sum + i;
}, 0);
},
switchable: false,
},
{
sortable: true,
field: 'price',
title: '{% trans "Unit Price" %}',
formatter: function(value, row) {
var formatter = new Intl.NumberFormat(
'en-US',
{
style: 'currency',
currency: row.price_currency
}
);
return formatter.format(row.price);
}
},
{
field: 'total_price',
sortable: true,
title: '{% trans "Total Price" %}',
formatter: function(value, row) {
var formatter = new Intl.NumberFormat(
'en-US',
{
style: 'currency',
currency: row.price_currency
}
);
return formatter.format(row.price * row.quantity);
},
footerFormatter: function(data) {
var total = data.map(function(row) {
return +row['price'] * row['quantity'];
}).reduce(function(sum, i) {
return sum + i;
}, 0);
var currency = (data.slice(-1)[0] && data.slice(-1)[0].price_currency) || 'USD';
var formatter = new Intl.NumberFormat(
'en-US',
{
style: 'currency',
currency: currency
}
);
return formatter.format(total);
}
}
];
columns.push({
field: 'notes',
title: '{% trans "Notes" %}',
});
if (pending) {
columns.push({
field: 'buttons',
switchable: false,
formatter: function(value, row, index, field) {
var html = `<div class='btn-group float-right' role='group'>`;
var pk = row.pk;
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
html += `</div>`;
return html;
}
});
}
function reloadTable() {
$(table).bootstrapTable('refresh');
reloadTotal();
}
// Configure callback functions once the table is loaded
function setupCallbacks() {
// Callback for duplicating lines
$(table).find('.button-duplicate').click(function() {
var pk = $(this).attr('pk');
inventreeGet(`/api/order/so-extra-line/${pk}/`, {}, {
success: function(data) {
var fields = extraLineFields();
constructForm('{% url "api-so-extra-line-list" %}', {
method: 'POST',
fields: fields,
data: data,
title: '{% trans "Duplicate Line" %}',
onSuccess: function(response) {
$(table).bootstrapTable('refresh');
}
});
}
});
});
// Callback for editing lines
$(table).find('.button-edit').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/order/so-extra-line/${pk}/`, {
fields: {
quantity: {},
reference: {},
price: {},
price_currency: {},
notes: {},
},
title: '{% trans "Edit Line" %}',
onSuccess: reloadTable,
});
});
// Callback for deleting lines
$(table).find('.button-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/order/so-extra-line/${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Line" %}',
onSuccess: reloadTable,
});
});
}
$(table).inventreeTable({
onPostBody: setupCallbacks,
name: 'salesorderextraline',
sidePagination: 'client',
formatNoMatches: function() {
return '{% trans "No matching lines" %}';
},
queryParams: filters,
original: options.params,
url: options.url,
showFooter: true,
uniqueId: 'pk',
detailViewByClick: false,
columns: columns,
});
}

View File

@ -271,7 +271,7 @@ function printBomReports(parts) {
function printPurchaseOrderReports(orders) {
/**
* Print PO reports for the provided purchase order(s)
* Print PurchaseOrder reports for the provided purchase order(s)
*/
if (orders.length == 0) {
@ -325,7 +325,7 @@ function printPurchaseOrderReports(orders) {
function printSalesOrderReports(orders) {
/**
* Print SO reports for the provided purchase order(s)
* Print SalesOrder reports for the provided purchase order(s)
*/
if (orders.length == 0) {