mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-19 21:45:39 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into fr-1421-sso
This commit is contained in:
@ -42,6 +42,12 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class='list-group-item' title='{% trans "Forms" %}'>
|
||||
<a href='#' class='nav-toggle' id='select-user-forms'>
|
||||
<span class='fas fa-table'></span>{% trans "Forms" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!--
|
||||
<li class='list-group-item' title='{% trans "Settings" %}'>
|
||||
<a href='#' class='nav-toggle' id='select-user-settings'>
|
||||
|
@ -17,7 +17,6 @@
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_ALLOW_DUPLICATE_IPN" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_ALLOW_EDIT_IPN" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_QUANTITY_IN_FORMS" icon="fa-hashtag" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_PRICE_IN_FORMS" icon="fa-dollar-sign" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_RELATED" icon="fa-random" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_CREATE_INITIAL" icon="fa-boxes" %}
|
||||
|
@ -20,6 +20,7 @@
|
||||
{% include "InvenTree/settings/user_search.html" %}
|
||||
{% include "InvenTree/settings/user_labels.html" %}
|
||||
{% include "InvenTree/settings/user_reports.html" %}
|
||||
{% include "InvenTree/settings/user_forms.html" %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
|
||||
|
22
InvenTree/templates/InvenTree/settings/user_forms.html
Normal file
22
InvenTree/templates/InvenTree/settings/user_forms.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "panel.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block label %}user-forms{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
{% trans "Form Settings" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class='row'>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_QUANTITY_IN_FORMS" icon="fa-hashtag" user_setting=True %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -10,6 +10,7 @@
|
||||
/* exported
|
||||
attachClipboard,
|
||||
enableDragAndDrop,
|
||||
exportFormatOptions,
|
||||
inventreeDocReady,
|
||||
inventreeLoad,
|
||||
inventreeSave,
|
||||
@ -46,6 +47,31 @@ function attachClipboard(selector, containerselector, textElement) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a standard list of export format options *
|
||||
*/
|
||||
function exportFormatOptions() {
|
||||
return [
|
||||
{
|
||||
value: 'csv',
|
||||
display_name: 'CSV',
|
||||
},
|
||||
{
|
||||
value: 'tsv',
|
||||
display_name: 'TSV',
|
||||
},
|
||||
{
|
||||
value: 'xls',
|
||||
display_name: 'XLS',
|
||||
},
|
||||
{
|
||||
value: 'xlsx',
|
||||
display_name: 'XLSX',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function inventreeDocReady() {
|
||||
/* Run this function when the HTML document is loaded.
|
||||
* This will be called for every page that extends "base.html"
|
||||
|
@ -42,6 +42,8 @@ function buildFormFields() {
|
||||
part_detail: true,
|
||||
}
|
||||
},
|
||||
sales_order: {
|
||||
},
|
||||
batch: {},
|
||||
target_date: {},
|
||||
take_from: {},
|
||||
@ -76,23 +78,32 @@ function newBuildOrder(options={}) {
|
||||
|
||||
var fields = buildFormFields();
|
||||
|
||||
// Specify the target part
|
||||
if (options.part) {
|
||||
fields.part.value = options.part;
|
||||
}
|
||||
|
||||
// Specify the desired quantity
|
||||
if (options.quantity) {
|
||||
fields.quantity.value = options.quantity;
|
||||
}
|
||||
|
||||
// Specify the parent build order
|
||||
if (options.parent) {
|
||||
fields.parent.value = options.parent;
|
||||
}
|
||||
|
||||
// Specify a parent sales order
|
||||
if (options.sales_order) {
|
||||
fields.sales_order.value = options.sales_order;
|
||||
}
|
||||
|
||||
constructForm(`/api/build/`, {
|
||||
fields: fields,
|
||||
follow: true,
|
||||
method: 'POST',
|
||||
title: '{% trans "Create Build Order" %}'
|
||||
title: '{% trans "Create Build Order" %}',
|
||||
onSuccess: options.onSuccess,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1568,6 +1568,9 @@ function renderModelData(name, model, data, parameters, options) {
|
||||
case 'partparametertemplate':
|
||||
renderer = renderPartParameterTemplate;
|
||||
break;
|
||||
case 'salesorder':
|
||||
renderer = renderSalesOrder;
|
||||
break;
|
||||
case 'manufacturerpart':
|
||||
renderer = renderManufacturerPart;
|
||||
break;
|
||||
|
@ -159,7 +159,24 @@ function renderPart(name, data, parameters, options) {
|
||||
html += ` - <i>${data.description}</i>`;
|
||||
}
|
||||
|
||||
html += `<span class='float-right'><small>{% trans "Part ID" %}: ${data.pk}</small></span>`;
|
||||
var stock = '';
|
||||
|
||||
// Display available part quantity
|
||||
if (user_settings.PART_SHOW_QUANTITY_IN_FORMS) {
|
||||
if (data.in_stock == 0) {
|
||||
stock = `<span class='label-form label-red'>{% trans "No Stock" %}</span>`;
|
||||
} else {
|
||||
stock = `<span class='label-form label-green'>{% trans "In Stock" %}: ${data.in_stock}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += `
|
||||
<span class='float-right'>
|
||||
<small>
|
||||
${stock}
|
||||
{% trans "Part ID" %}: ${data.pk}
|
||||
</small>
|
||||
</span>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
@ -199,6 +216,26 @@ function renderOwner(name, data, parameters, options) {
|
||||
}
|
||||
|
||||
|
||||
// Renderer for "SalesOrder" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderSalesOrder(name, data, parameters, options) {
|
||||
var html = `<span>${data.reference}</span>`;
|
||||
|
||||
if (data.description) {
|
||||
html += ` - <i>${data.description}</i>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<span class='float-right'>
|
||||
<small>
|
||||
{% trans "Order ID" %}: ${data.pk}
|
||||
</small>
|
||||
</span>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
// Renderer for "PartCategory" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPartCategory(name, data, parameters, options) {
|
||||
|
@ -21,9 +21,11 @@
|
||||
/* exported
|
||||
createSalesOrder,
|
||||
editPurchaseOrderLineItem,
|
||||
exportOrder,
|
||||
loadPurchaseOrderLineItemTable,
|
||||
loadPurchaseOrderTable,
|
||||
loadSalesOrderAllocationTable,
|
||||
loadSalesOrderLineItemTable,
|
||||
loadSalesOrderTable,
|
||||
newPurchaseOrderFromOrderWizard,
|
||||
newSupplierPartFromOrderWizard,
|
||||
@ -186,6 +188,49 @@ function newSupplierPartFromOrderWizard(e) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export an order (PurchaseOrder or SalesOrder)
|
||||
*
|
||||
* - Display a simple form which presents the user with export options
|
||||
*
|
||||
*/
|
||||
function exportOrder(redirect_url, options={}) {
|
||||
|
||||
var format = options.format;
|
||||
|
||||
// If default format is not provided, lookup
|
||||
if (!format) {
|
||||
format = inventreeLoad('order-export-format', 'csv');
|
||||
}
|
||||
|
||||
constructFormBody({}, {
|
||||
title: '{% trans "Export Order" %}',
|
||||
fields: {
|
||||
format: {
|
||||
label: '{% trans "Format" %}',
|
||||
help_text: '{% trans "Select file format" %}',
|
||||
required: true,
|
||||
type: 'choice',
|
||||
value: format,
|
||||
choices: exportFormatOptions(),
|
||||
}
|
||||
},
|
||||
onSubmit: function(fields, opts) {
|
||||
|
||||
var format = getFormFieldValue('format', fields['format'], opts);
|
||||
|
||||
// Save the format for next time
|
||||
inventreeSave('order-export-format', format);
|
||||
|
||||
// Hide the modal
|
||||
$(opts.modal).modal('hide');
|
||||
|
||||
// Download the file!
|
||||
location.href = `${redirect_url}?format=${format}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function newPurchaseOrderFromOrderWizard(e) {
|
||||
/* Create a new purchase order directly from an order form.
|
||||
* Launches a secondary modal and (if successful),
|
||||
@ -531,6 +576,7 @@ function editPurchaseOrderLineItem(e) {
|
||||
|
||||
var url = $(src).attr('url');
|
||||
|
||||
// TODO: Migrate this to the API forms
|
||||
launchModalForm(url, {
|
||||
reload: true,
|
||||
});
|
||||
@ -546,7 +592,8 @@ function removePurchaseOrderLineItem(e) {
|
||||
var src = e.target || e.srcElement;
|
||||
|
||||
var url = $(src).attr('url');
|
||||
|
||||
|
||||
// TODO: Migrate this to the API forms
|
||||
launchModalForm(url, {
|
||||
reload: true,
|
||||
});
|
||||
@ -1126,3 +1173,601 @@ function loadSalesOrderAllocationTable(table, options={}) {
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display an "allocations" sub table, showing stock items allocated againt a sales order
|
||||
* @param {*} index
|
||||
* @param {*} row
|
||||
* @param {*} element
|
||||
*/
|
||||
function showAllocationSubTable(index, row, element, options) {
|
||||
|
||||
// Construct a sub-table element
|
||||
var html = `
|
||||
<div class='sub-table'>
|
||||
<table class='table table-striped table-condensed' id='allocation-table-${row.pk}'>
|
||||
</table>
|
||||
</div>`;
|
||||
|
||||
element.html(html);
|
||||
|
||||
var table = $(`#allocation-table-${row.pk}`);
|
||||
|
||||
// Is the parent SalesOrder pending?
|
||||
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
||||
|
||||
function setupCallbacks() {
|
||||
// Add callbacks for 'edit' buttons
|
||||
table.find('.button-allocation-edit').click(function() {
|
||||
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
// Edit the sales order alloction
|
||||
constructForm(
|
||||
`/api/order/so-allocation/${pk}/`,
|
||||
{
|
||||
fields: {
|
||||
quantity: {},
|
||||
},
|
||||
title: '{% trans "Edit Stock Allocation" %}',
|
||||
onSuccess: function() {
|
||||
// Refresh the parent table
|
||||
$(options.table).bootstrapTable('refresh');
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Add callbacks for 'delete' buttons
|
||||
table.find('.button-allocation-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
constructForm(
|
||||
`/api/order/so-allocation/${pk}/`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
confirmMessage: '{% trans "Confirm Delete Operation" %}',
|
||||
title: '{% trans "Delete Stock Allocation" %}',
|
||||
onSuccess: function() {
|
||||
// Refresh the parent table
|
||||
$(options.table).bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
table.bootstrapTable({
|
||||
onPostBody: setupCallbacks,
|
||||
data: row.allocations,
|
||||
showHeader: false,
|
||||
columns: [
|
||||
{
|
||||
field: 'allocated',
|
||||
title: '{% trans "Quantity" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
var text = '';
|
||||
|
||||
if (row.serial != null && row.quantity == 1) {
|
||||
text = `{% trans "Serial Number" %}: ${row.serial}`;
|
||||
} else {
|
||||
text = `{% trans "Quantity" %}: ${row.quantity}`;
|
||||
}
|
||||
|
||||
return renderLink(text, `/stock/item/${row.item}/`);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
title: '{% trans "Location" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
// Location specified
|
||||
if (row.location) {
|
||||
return renderLink(
|
||||
row.location_detail.pathstring || '{% trans "Location" %}',
|
||||
`/stock/location/${row.location}/`
|
||||
);
|
||||
} else {
|
||||
return `<i>{% trans "Stock location not specified" %}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
// TODO: ?? What is 'po' field all about?
|
||||
/*
|
||||
{
|
||||
field: 'po'
|
||||
},
|
||||
*/
|
||||
{
|
||||
field: 'buttons',
|
||||
title: '{% trans "Actions" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
var pk = row.pk;
|
||||
|
||||
if (pending) {
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a "fulfilled" sub table, showing stock items fulfilled against a purchase order
|
||||
*/
|
||||
function showFulfilledSubTable(index, row, element, options) {
|
||||
// Construct a table showing stock items which have been fulfilled against this line item
|
||||
|
||||
if (!options.order) {
|
||||
return 'ERROR: Order ID not supplied';
|
||||
}
|
||||
|
||||
var id = `fulfilled-table-${row.pk}`;
|
||||
|
||||
var html = `
|
||||
<div class='sub-table'>
|
||||
<table class='table table-striped table-condensed' id='${id}'>
|
||||
</table>
|
||||
</div>`;
|
||||
|
||||
element.html(html);
|
||||
|
||||
$(`#${id}`).bootstrapTable({
|
||||
url: '{% url "api-stock-list" %}',
|
||||
queryParams: {
|
||||
part: row.part,
|
||||
sales_order: options.order,
|
||||
},
|
||||
showHeader: false,
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
field: 'stock',
|
||||
formatter: function(value, row) {
|
||||
var text = '';
|
||||
if (row.serial && row.quantity == 1) {
|
||||
text = `{% trans "Serial Number" %}: ${row.serial}`;
|
||||
} else {
|
||||
text = `{% trans "Quantity" %}: ${row.quantity}`;
|
||||
}
|
||||
|
||||
return renderLink(text, `/stock/item/${row.pk}/`);
|
||||
},
|
||||
},
|
||||
/*
|
||||
{
|
||||
field: 'po'
|
||||
},
|
||||
*/
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load a table displaying line items 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 loadSalesOrderLineItemTable(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('salesorderlineitem');
|
||||
|
||||
for (var key in options.params) {
|
||||
filters[key] = options.params[key];
|
||||
}
|
||||
|
||||
options.url = options.url || '{% url "api-so-line-list" %}';
|
||||
|
||||
var filter_target = options.filter_target || '#filter-list-sales-order-lines';
|
||||
|
||||
setupFilterList('salesorderlineitems', $(table), filter_target);
|
||||
|
||||
// Is the order pending?
|
||||
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
||||
|
||||
// Has the order shipped?
|
||||
var shipped = options.status == {{ SalesOrderStatus.SHIPPED }};
|
||||
|
||||
// Show detail view if the PurchaseOrder is PENDING or SHIPPED
|
||||
var show_detail = pending || shipped;
|
||||
|
||||
// Table columns to display
|
||||
var columns = [
|
||||
/*
|
||||
{
|
||||
checkbox: true,
|
||||
visible: true,
|
||||
switchable: false,
|
||||
},
|
||||
*/
|
||||
{
|
||||
sortable: true,
|
||||
sortName: 'part__name',
|
||||
field: 'part',
|
||||
title: '{% trans "Part" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
if (row.part) {
|
||||
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${value}/`);
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
footerFormatter: function() {
|
||||
return '{% trans "Total" %}';
|
||||
},
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
field: 'reference',
|
||||
title: '{% trans "Reference" %}',
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
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: 'sale_price',
|
||||
title: '{% trans "Unit Price" %}',
|
||||
formatter: function(value, row) {
|
||||
return row.sale_price_string || row.sale_price;
|
||||
}
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
title: '{% trans "Total price" %}',
|
||||
formatter: function(value, row) {
|
||||
var total = row.sale_price * row.quantity;
|
||||
var formatter = new Intl.NumberFormat(
|
||||
'en-US',
|
||||
{
|
||||
style: 'currency',
|
||||
currency: row.sale_price_currency
|
||||
}
|
||||
);
|
||||
|
||||
return formatter.format(total);
|
||||
},
|
||||
footerFormatter: function(data) {
|
||||
var total = data.map(function(row) {
|
||||
return +row['sale_price'] * row['quantity'];
|
||||
}).reduce(function(sum, i) {
|
||||
return sum + i;
|
||||
}, 0);
|
||||
|
||||
var currency = (data.slice(-1)[0] && data.slice(-1)[0].sale_price_currency) || 'USD';
|
||||
|
||||
var formatter = new Intl.NumberFormat(
|
||||
'en-US',
|
||||
{
|
||||
style: 'currency',
|
||||
currency: currency
|
||||
}
|
||||
);
|
||||
|
||||
return formatter.format(total);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
if (pending) {
|
||||
columns.push(
|
||||
{
|
||||
field: 'stock',
|
||||
title: '{% trans "In Stock" %}',
|
||||
formatter: function(value, row) {
|
||||
return row.part_detail.stock;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
columns.push(
|
||||
{
|
||||
field: 'allocated',
|
||||
title: pending ? '{% trans "Allocated" %}' : '{% trans "Fulfilled" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
var quantity = pending ? row.allocated : row.fulfilled;
|
||||
return makeProgressBar(quantity, row.quantity, {
|
||||
id: `order-line-progress-${row.pk}`,
|
||||
});
|
||||
},
|
||||
sorter: function(valA, valB, rowA, rowB) {
|
||||
|
||||
var A = pending ? rowA.allocated : rowA.fulfilled;
|
||||
var B = pending ? rowB.allocated : rowB.fulfilled;
|
||||
|
||||
if (A == 0 && B == 0) {
|
||||
return (rowA.quantity > rowB.quantity) ? 1 : -1;
|
||||
}
|
||||
|
||||
var progressA = parseFloat(A) / rowA.quantity;
|
||||
var progressB = parseFloat(B) / rowB.quantity;
|
||||
|
||||
return (progressA < progressB) ? 1 : -1;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'notes',
|
||||
title: '{% trans "Notes" %}',
|
||||
},
|
||||
);
|
||||
|
||||
if (pending) {
|
||||
columns.push({
|
||||
field: 'buttons',
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
var pk = row.pk;
|
||||
|
||||
if (row.part) {
|
||||
var part = row.part_detail;
|
||||
|
||||
if (part.trackable) {
|
||||
html += makeIconButton('fa-hashtag icon-green', 'button-add-by-sn', pk, '{% trans "Allocate serial numbers" %}');
|
||||
}
|
||||
|
||||
html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}');
|
||||
|
||||
if (part.purchaseable) {
|
||||
html += makeIconButton('fa-shopping-cart', 'button-buy', row.part, '{% trans "Purchase stock" %}');
|
||||
}
|
||||
|
||||
if (part.assembly) {
|
||||
html += makeIconButton('fa-tools', 'button-build', row.part, '{% trans "Build stock" %}');
|
||||
}
|
||||
|
||||
html += makeIconButton('fa-dollar-sign icon-green', 'button-price', pk, '{% trans "Calculate price" %}');
|
||||
}
|
||||
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line item " %}');
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
// Configure callback functions once the table is loaded
|
||||
function setupCallbacks() {
|
||||
|
||||
// Callback for editing line items
|
||||
$(table).find('.button-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
constructForm(`/api/order/so-line/${pk}/`, {
|
||||
fields: {
|
||||
quantity: {},
|
||||
reference: {},
|
||||
sale_price: {},
|
||||
sale_price_currency: {},
|
||||
notes: {},
|
||||
},
|
||||
title: '{% trans "Edit Line Item" %}',
|
||||
onSuccess: reloadTable,
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for deleting line items
|
||||
$(table).find('.button-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
constructForm(`/api/order/so-line/${pk}/`, {
|
||||
method: 'DELETE',
|
||||
title: '{% trans "Delete Line Item" %}',
|
||||
onSuccess: reloadTable,
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for allocating stock items by serial number
|
||||
$(table).find('.button-add-by-sn').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
// TODO: Migrate this form to the API forms
|
||||
inventreeGet(`/api/order/so-line/${pk}/`, {},
|
||||
{
|
||||
success: function(response) {
|
||||
launchModalForm('{% url "so-assign-serials" %}', {
|
||||
success: reloadTable,
|
||||
data: {
|
||||
line: pk,
|
||||
part: response.part,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Callback for allocation stock items to the order
|
||||
$(table).find('.button-add').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var line_item = $(table).bootstrapTable('getRowByUniqueId', pk);
|
||||
|
||||
var fields = {
|
||||
// SalesOrderLineItem reference
|
||||
line: {
|
||||
hidden: true,
|
||||
value: pk,
|
||||
},
|
||||
item: {
|
||||
filters: {
|
||||
part_detail: true,
|
||||
location_detail: true,
|
||||
in_stock: true,
|
||||
part: line_item.part,
|
||||
exclude_so_allocation: options.order,
|
||||
}
|
||||
},
|
||||
quantity: {
|
||||
},
|
||||
};
|
||||
|
||||
// Exclude expired stock?
|
||||
if (global_settings.STOCK_ENABLE_EXPIRY && !global_settings.STOCK_ALLOW_EXPIRED_SALE) {
|
||||
fields.item.filters.expired = false;
|
||||
}
|
||||
|
||||
constructForm(
|
||||
`/api/order/so-allocation/`,
|
||||
{
|
||||
method: 'POST',
|
||||
fields: fields,
|
||||
title: '{% trans "Allocate Stock Item" %}',
|
||||
onSuccess: reloadTable,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Callback for creating a new build
|
||||
$(table).find('.button-build').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
// Extract the row data from the table!
|
||||
var idx = $(this).closest('tr').attr('data-index');
|
||||
|
||||
var row = $(table).bootstrapTable('getData')[idx];
|
||||
|
||||
var quantity = 1;
|
||||
|
||||
if (row.allocated < row.quantity) {
|
||||
quantity = row.quantity - row.allocated;
|
||||
}
|
||||
|
||||
// Create a new build order
|
||||
newBuildOrder({
|
||||
part: pk,
|
||||
sales_order: options.order,
|
||||
quantity: quantity,
|
||||
success: reloadTable
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for purchasing parts
|
||||
$(table).find('.button-buy').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm('{% url "order-parts" %}', {
|
||||
data: {
|
||||
parts: [
|
||||
pk
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for displaying price
|
||||
$(table).find('.button-price').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
var idx = $(this).closest('tr').attr('data-index');
|
||||
var row = $(table).bootstrapTable('getData')[idx];
|
||||
|
||||
launchModalForm(
|
||||
'{% url "line-pricing" %}',
|
||||
{
|
||||
submit_text: '{% trans "Calculate price" %}',
|
||||
data: {
|
||||
line_item: pk,
|
||||
quantity: row.quantity,
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
name: 'update_price',
|
||||
title: '{% trans "Update Unit Price" %}'
|
||||
},
|
||||
],
|
||||
success: reloadTable,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
$(table).inventreeTable({
|
||||
onPostBody: setupCallbacks,
|
||||
name: 'salesorderlineitems',
|
||||
sidePagination: 'server',
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No matching line items" %}';
|
||||
},
|
||||
queryParams: filters,
|
||||
original: options.params,
|
||||
url: options.url,
|
||||
showFooter: true,
|
||||
uniqueId: 'pk',
|
||||
detailView: show_detail,
|
||||
detailViewByClick: show_detail,
|
||||
detailFilter: function(index, row) {
|
||||
if (pending) {
|
||||
// Order is pending
|
||||
return row.allocated > 0;
|
||||
} else {
|
||||
return row.fulfilled > 0;
|
||||
}
|
||||
},
|
||||
detailFormatter: function(index, row, element) {
|
||||
if (pending) {
|
||||
return showAllocationSubTable(index, row, element, options);
|
||||
} else {
|
||||
return showFulfilledSubTable(index, row, element, options);
|
||||
}
|
||||
},
|
||||
columns: columns,
|
||||
});
|
||||
}
|
||||
|
@ -98,24 +98,7 @@ function exportStock(params={}) {
|
||||
required: true,
|
||||
type: 'choice',
|
||||
value: 'csv',
|
||||
choices: [
|
||||
{
|
||||
value: 'csv',
|
||||
display_name: 'CSV',
|
||||
},
|
||||
{
|
||||
value: 'tsv',
|
||||
display_name: 'TSV',
|
||||
},
|
||||
{
|
||||
value: 'xls',
|
||||
display_name: 'XLS',
|
||||
},
|
||||
{
|
||||
value: 'xlsx',
|
||||
display_name: 'XLSX',
|
||||
},
|
||||
],
|
||||
choices: exportFormatOptions(),
|
||||
},
|
||||
sublocations: {
|
||||
label: '{% trans "Include Sublocations" %}',
|
||||
|
Reference in New Issue
Block a user