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

Merge branch 'master' of https://github.com/inventree/InvenTree into extend-build-order

This commit is contained in:
2021-07-04 00:02:29 +02:00
237 changed files with 6341 additions and 3613 deletions

View File

@ -128,6 +128,7 @@ loadSimplePartTable("#table-bom-validation", "{% url 'api-part-list' %}", {
addHeaderTitle('{% trans "Stock" %}');
addHeaderAction('recently-updated-stock', '{% trans "Recently Updated" %}', 'fa-clock');
addHeaderAction('low-stock', '{% trans "Low Stock" %}', 'fa-shopping-cart');
addHeaderAction('depleted-stock', '{% trans "Depleted Stock" %}', 'fa-times');
addHeaderAction('stock-to-build', '{% trans "Required for Build Orders" %}', 'fa-bullhorn');
loadStockTable($('#table-recently-updated-stock'), {
@ -170,6 +171,13 @@ loadSimplePartTable("#table-low-stock", "{% url 'api-part-list' %}", {
name: "low_stock_parts",
});
loadSimplePartTable("#table-depleted-stock", "{% url 'api-part-list' %}", {
params: {
depleted_stock: true,
},
name: "depleted_stock_parts",
});
loadSimplePartTable("#table-stock-to-build", "{% url 'api-part-list' %}", {
params: {
stock_to_build: true,

View File

@ -10,35 +10,6 @@
<div class='dropzone' id='attachment-dropzone'>
<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
<thead>
<tr>
<th data-field='file' data-sortable='true' data-searchable='true'>{% trans "File" %}</th>
<th data-field='comment' data-sortable='true' data-searchable='true'>{% trans "Comment" %}</th>
<th data-field='user' data-sortable='true' data-searchable='true'>{% trans "Uploaded" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for attachment in attachments %}
<tr>
<td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
<td>{{ attachment.comment }}</td>
<td>
{% if attachment.upload_date %}{{ attachment.upload_date }}{% endif %}
{% if attachment.user %}<span class='badge'>{{ attachment.user.username }}</div>{% endif %}
</td>
<td>
<div class='btn-group' style='float: right;'>
<button type='button' class='btn btn-default btn-glyph attachment-edit-button' pk="{{ attachment.id }}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
<span class='fas fa-edit icon-blue'/>
</button>
<button type='button' class='btn btn-default btn-glyph attachment-delete-button' pk="{{ attachment.id }}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
<span class='fas fa-trash-alt icon-red'/>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View File

@ -40,8 +40,8 @@
<link rel="stylesheet" href="{% static 'bootstrap-table/bootstrap-table.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.css' %}">
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
<link rel="stylesheet" href="{% static 'css/select2-bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'select2/css/select2.css' %}">
<link rel="stylesheet" href="{% static 'select2/css/select2-bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap-toggle.css' %}">
<link rel="stylesheet" href="{% static 'fullcalendar/main.css' %}">
<link rel="stylesheet" href="{% static 'script/jquery-ui/jquery-ui.min.css' %}">
@ -136,7 +136,7 @@
<!-- 3rd party general js -->
<script type="text/javascript" src="{% static 'fullcalendar/main.js' %}"></script>
<script type="text/javascript" src="{% static 'fullcalendar/locales-all.js' %}"></script>
<script type="text/javascript" src="{% static 'script/select2/select2.js' %}"></script>
<script type="text/javascript" src="{% static 'select2/js/select2.full.js' %}"></script>
<script type='text/javascript' src="{% static 'script/moment.js' %}"></script>
<script type='text/javascript' src="{% static 'script/chart.min.js' %}"></script>
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
@ -144,11 +144,14 @@
<!-- general InvenTree -->
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
<!-- translated -->
<script type='text/javascript' src="{% i18n_static 'api.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'attachment.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'forms.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'barcode.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'bom.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'company.js' %}"></script>

View File

@ -0,0 +1,141 @@
var jQuery = window.$;
// using jQuery
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function inventreeGet(url, filters={}, options={}) {
// Middleware token required for data update
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
var csrftoken = getCookie('csrftoken');
return $.ajax({
beforeSend: function(xhr, settings) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
},
url: url,
type: 'GET',
data: filters,
dataType: 'json',
contentType: 'application/json',
success: function(response) {
if (options.success) {
options.success(response);
}
},
error: function(xhr, ajaxOptions, thrownError) {
console.error('Error on GET at ' + url);
console.error(thrownError);
if (options.error) {
options.error({
error: thrownError
});
}
}
});
}
function inventreeFormDataUpload(url, data, options={}) {
/* Upload via AJAX using the FormData approach.
*
* Note that the following AJAX parameters are required for FormData upload
*
* processData: false
* contentType: false
*/
// CSRF cookie token
var csrftoken = getCookie('csrftoken');
return $.ajax({
beforeSend: function(xhr, settings) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
},
url: url,
method: options.method || 'POST',
data: data,
processData: false,
contentType: false,
success: function(data, status, xhr) {
if (options.success) {
options.success(data, status, xhr);
}
},
error: function(xhr, status, error) {
console.log('Form data upload failure: ' + status);
if (options.error) {
options.error(xhr, status, error);
}
}
});
}
function inventreePut(url, data={}, options={}) {
var method = options.method || 'PUT';
// Middleware token required for data update
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
var csrftoken = getCookie('csrftoken');
return $.ajax({
beforeSend: function(xhr, settings) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
},
url: url,
type: method,
data: JSON.stringify(data),
dataType: 'json',
contentType: 'application/json',
success: function(response, status) {
if (options.success) {
options.success(response, status);
}
if (options.reloadOnSuccess) {
location.reload();
}
},
error: function(xhr, ajaxOptions, thrownError) {
if (options.error) {
options.error(xhr, ajaxOptions, thrownError);
} else {
console.error(`Error on ${method} to '${url}' - STATUS ${xhr.status}`);
console.error(thrownError);
}
},
complete: function(xhr, status) {
if (options.complete) {
options.complete(xhr, status);
}
}
});
}
function inventreeDelete(url, options={}) {
/*
* Delete a record
*/
options = options || {};
options.method = 'DELETE';
inventreePut(url, {}, options);
}

View File

@ -0,0 +1,86 @@
{% load i18n %}
function reloadAttachmentTable() {
$('#attachment-table').bootstrapTable("refresh");
}
function loadAttachmentTable(url, options) {
var table = options.table || '#attachment-table';
$(table).inventreeTable({
url: url,
name: options.name || 'attachments',
formatNoMatches: function() { return '{% trans "No attachments found" %}'},
sortable: true,
search: false,
queryParams: options.filters || {},
onPostBody: function() {
// Add callback for 'edit' button
$(table).find('.button-attachment-edit').click(function() {
var pk = $(this).attr('pk');
if (options.onEdit) {
options.onEdit(pk);
}
});
// Add callback for 'delete' button
$(table).find('.button-attachment-delete').click(function() {
var pk = $(this).attr('pk');
if (options.onDelete) {
options.onDelete(pk);
}
});
},
columns: [
{
field: 'attachment',
title: '{% trans "File" %}',
formatter: function(value, row) {
var split = value.split('/');
return renderLink(split[split.length - 1], value);
}
},
{
field: 'comment',
title: '{% trans "Comment" %}',
},
{
field: 'upload_date',
title: '{% trans "Upload Date" %}',
},
{
field: 'actions',
formatter: function(value, row) {
var html = '';
html = `<div class='btn-group float-right' role='group'>`;
html += makeIconButton(
'fa-edit icon-blue',
'button-attachment-edit',
row.pk,
'{% trans "Edit attachment" %}',
);
html += makeIconButton(
'fa-trash-alt icon-red',
'button-attachment-delete',
row.pk,
'{% trans "Delete attachment" %}',
);
html += `</div>`;
return html;
}
}
]
});
}

View File

@ -514,14 +514,13 @@ function loadBomTable(table, options) {
var pk = $(this).attr('pk');
var url = `/part/bom/${pk}/delete/`;
launchModalForm(
url,
{
success: function() {
reloadBomTable(table);
}
constructForm(`/api/bom/${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete BOM Item" %}',
onSuccess: function() {
reloadBomTable(table);
}
);
});
});
table.on('click', '.bom-edit-button', function() {

View File

@ -1,5 +1,74 @@
{% load i18n %}
// Returns a default form-set for creating / editing a Company object
function companyFormFields(options={}) {
return {
name: {},
description: {},
website: {
icon: 'fa-globe',
},
address: {
icon: 'fa-envelope',
},
currency: {
icon: 'fa-dollar-sign',
},
phone: {
icon: 'fa-phone',
},
email: {
icon: 'fa-at',
},
contact: {
icon: 'fa-address-card',
},
is_supplier: {},
is_manufacturer: {},
is_customer: {}
};
}
function editCompany(pk, options={}) {
var fields = options.fields || companyFormFields();
constructForm(
`/api/company/${pk}/`,
{
method: 'PATCH',
fields: fields,
reload: true,
title: '{% trans "Edit Company" %}',
}
);
};
/*
* Launches a form to create a new company.
* As this can be called from many different contexts,
* we abstract it here!
*/
function createCompany(options={}) {
// Default field set
var fields = options.fields || companyFormFields();
constructForm(
'{% url "api-company-list" %}',
{
method: 'POST',
fields: fields,
follow: true,
title: '{% trans "Add new Company" %}',
}
);
}
function loadCompanyTable(table, url, options={}) {
/*
* Load company listing data into specified table.
@ -101,6 +170,61 @@ function loadCompanyTable(table, url, options={}) {
}
function deleteManufacturerParts(selections, options={}) {
if (selections.length == 0) {
return;
}
var parts = [];
var text = `
<div class='alert alert-block alert-danger'>
<p>{% trans "The following manufacturer parts will be deleted" %}:</p>
<ul>`;
selections.forEach(function(item) {
parts.push(item.pk);
text += `
<li>
<p>${item.MPN} - ${item.part_detail.full_name}</p>
</li>`;
});
text += `
</ul>
</div>`;
showQuestionDialog(
'{% trans "Delete Manufacturer Parts" %}',
text,
{
accept_text: '{% trans "Delete" %}',
accept: function() {
// Delete each manufacturer part
var requests = [];
parts.forEach(function(pk) {
var url = `/api/company/part/manufacturer/${pk}`;
requests.push(inventreeDelete(url));
});
// Wait for all the requests to complete
$.when.apply($, requests).then(function() {
if (options.onSuccess) {
options.onSuccess();
}
})
}
}
);
}
function loadManufacturerPartTable(table, url, options) {
/*
* Load manufacturer part table
@ -228,7 +352,7 @@ function loadManufacturerPartParameterTable(table, url, options) {
{
checkbox: true,
switchable: false,
visible: false,
visible: true,
},
{
field: 'name',
@ -273,27 +397,28 @@ function loadManufacturerPartParameterTable(table, url, options) {
$(table).find('.button-parameter-edit').click(function() {
var pk = $(this).attr('pk');
launchModalForm(
`/manufacturer-part/parameter/${pk}/edit/`,
{
success: function() {
$(table).bootstrapTable('refresh');
}
constructForm(`/api/company/part/manufacturer/parameter/${pk}/`, {
fields: {
name: {},
value: {},
units: {},
},
title: '{% trans "Edit Parameter" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
);
});
});
$(table).find('.button-parameter-delete').click(function() {
var pk = $(this).attr('pk');
launchModalForm(
`/manufacturer-part/parameter/${pk}/delete/`,
{
success: function() {
$(table).bootstrapTable('refresh');
}
constructForm(`/api/company/part/manufacturer/parameter/${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Parameter" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
);
});
});
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,114 @@
{% load i18n %}
/*
* Create and display a new modal dialog
*
* options:
* - title: Form title to render
* - submitText: Text to render on 'submit' button (default = "Submit")
* - closeText: Text to render on 'close' button (default = "Cancel")
* - focus: Name of field to focus on after launching
*/
function createNewModal(options={}) {
var id = 1;
// Check out what modal forms are already being displayed
$('.inventree-modal').each(function() {
var split = this.id.split('-');
var modal_id = parseInt(split[2]);
if (modal_id >= id) {
id = modal_id + 1;
}
});
var html = `
<div class='modal fade modal-fixed-footer modal-primary inventree-modal' role='dialog' id='modal-form-${id}'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label='{% trans "Close" %}'>
<span aria-hidden="true">&times;</span>
</button>
<h3 id='modal-title'>
<!-- Form title to be injected here -->
</h3>
</div>
<div class='modal-form-content-wrapper'>
<div id='pre-form-content'>
<!-- Content can be inserted here *before* the form fields -->
</div>
<div id='non-field-errors'>
<!-- Form error messages go here -->
</div>
<div id='form-content' class='modal-form-content'>
<!-- Form content will be injected here-->
</div>
<div id='post-form-content'>
<!-- Content can be inserted here *after* the form fields -->
</div>
</div>
<div class='modal-footer'>
<div id='modal-footer-buttons'>
<!-- Extra buttons can be inserted here -->
</div>
<button type='button' class='btn btn-default' id='modal-form-close' data-dismiss='modal'>{% trans "Cancel" %}</button>
<button type='button' class='btn btn-primary' id='modal-form-submit'>{% trans "Submit" %}</button>
</div>
</div>
</div>
</div>
`;
$('body').append(html);
var modal_name = `#modal-form-${id}`;
$(modal_name).on('shown.bs.modal', function() {
$(modal_name + ' .modal-form-content').scrollTop(0);
if (options.focus) {
getFieldByName(modal_name, options.focus).focus();
}
});
// Automatically remove the modal when it is deleted!
$(modal_name).on('hidden.bs.modal', function(e) {
$(modal_name).remove();
});
// Capture "enter" key input
$(modal_name).on('keydown', 'input', function(event) {
if (event.keyCode == 13) {
event.preventDefault();
// Simulate a click on the 'Submit' button
$(modal_name).find("#modal-form-submit").click();
return false;
}
});
$(modal_name).modal({
backdrop: 'static',
keyboard: false,
});
// Set labels based on supplied options
modalSetTitle(modal_name, options.title || '{% trans "Form Title" %}');
modalSetSubmitText(modal_name, options.submitText || '{% trans "Submit" %}');
modalSetCloseText(modal_name, options.cancelText || '{% trans "Cancel" %}');
// Return the "name" of the modal
return modal_name;
}
function makeOption(text, value, title) {
/* Format an option for a select element
*/
@ -991,8 +1100,6 @@ function hideModalImage() {
function showModalImage(image_url) {
// Display full-screen modal image
console.log('showing modal image: ' + image_url);
var modal = $('#modal-image-dialog');
// Set image content

View File

@ -0,0 +1,155 @@
{% load i18n %}
/*
* This file contains functions for rendering various InvenTree database models,
* in particular for displaying them in modal forms in a 'select2' context.
*
* Each renderer is provided with three arguments:
*
* - name: The 'name' of the model instance in the referring model
* - data: JSON data which represents the model instance. Returned via a GET request.
* - parameters: The field parameters provided via an OPTIONS request to the endpoint.
* - options: User options provided by the client
*/
// Renderer for "Company" model
function renderCompany(name, data, parameters, options) {
var html = `<span>${data.name}</span> - <i>${data.description}</i>`;
html += `<span class='float-right'>{% trans "Company ID" %}: ${data.pk}</span>`;
return html;
}
// Renderer for "StockItem" model
function renderStockItem(name, data, parameters, options) {
var image = data.part_detail.thumbnail || data.part_detail.image;
if (!image) {
image = `/static/img/blank_image.png`;
}
var html = `<img src='${image}' class='select2-thumbnail'>`;
html += ` <span>${data.part_detail.full_name || data.part_detail.name}</span>`;
if (data.serial && data.quantity == 1) {
html += ` - <i>{% trans "Serial Number" %}: ${data.serial}`;
} else {
html += ` - <i>{% trans "Quantity" %}: ${data.quantity}`;
}
if (data.part_detail.description) {
html += `<p><small>${data.part_detail.description}</small></p>`;
}
return html;
}
// Renderer for "StockLocation" model
function renderStockLocation(name, data, parameters, options) {
var html = `<span>${data.name}</span>`;
if (data.description) {
html += ` - <i>${data.description}</i>`;
}
html += `<span class='float-right'>{% trans "Location ID" %}: ${data.pk}</span>`;
if (data.pathstring) {
html += `<p><small>${data.pathstring}</small></p>`;
}
return html;
}
// Renderer for "Part" model
function renderPart(name, data, parameters, options) {
var image = data.image;
if (!image) {
image = `/static/img/blank_image.png`;
}
var html = `<img src='${image}' class='select2-thumbnail'>`;
html += ` <span>${data.full_name || data.name}</span>`;
if (data.description) {
html += ` - <i>${data.description}</i>`;
}
html += `<span class='float-right'>{% trans "Part ID" %}: ${data.pk}</span>`;
return html;
}
// Renderer for "Owner" model
function renderOwner(name, data, parameters, options) {
var html = `<span>${data.name}</span>`;
switch (data.label) {
case 'user':
html += `<span class='float-right fas fa-user'></span>`;
break;
case 'group':
html += `<span class='float-right fas fa-users'></span>`;
break;
default:
break;
}
return html;
}
// Renderer for "PartCategory" model
function renderPartCategory(name, data, parameters, options) {
var html = `<span><b>${data.name}</b></span>`;
if (data.description) {
html += ` - <i>${data.description}</i>`;
}
html += `<span class='float-right'>{% trans "Category ID" %}: ${data.pk}</span>`;
if (data.pathstring) {
html += `<p><small>${data.pathstring}</small></p>`;
}
return html;
}
// Rendered for "SupplierPart" model
function renderSupplierPart(name, data, parameters, options) {
var image = data.supplier_detail.image;
if (!image) {
image = `/static/img/blank_image.png`;
}
var html = `<img src='${image}' class='select2-thumbnail'>`;
html += ` <span><b>${data.supplier_detail.name}</b> - ${data.SKU}</span>`;
html += ` - <i>${data.part_detail.full_name}</i>`;
html += `<span class='float-right'>{% trans "Supplier Part ID" %}: ${data.pk}</span>`;
return html;
}

View File

@ -1,6 +1,68 @@
{% load i18n %}
{% load inventree_extras %}
// Create a new SalesOrder
function createSalesOrder(options={}) {
constructForm('{% url "api-so-list" %}', {
method: 'POST',
fields: {
reference: {
prefix: '{% settings_value "SALESORDER_REFERENCE_PREFIX" %}',
},
customer: {
value: options.customer,
},
description: {},
target_date: {
icon: 'fa-calendar-alt',
},
link: {
icon: 'fa-link',
},
responsible: {
icon: 'fa-user',
}
},
onSuccess: function(data) {
location.href = `/order/sales-order/${data.pk}/`;
},
title: '{% trans "Create Sales Order" %}',
});
}
// Create a new PurchaseOrder
function createPurchaseOrder(options={}) {
constructForm('{% url "api-po-list" %}', {
method: 'POST',
fields: {
reference: {
prefix: "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}",
},
supplier: {
value: options.supplier,
},
description: {},
target_date: {
icon: 'fa-calendar-alt',
},
link: {
icon: 'fa-link',
},
responsible: {
icon: 'fa-user',
}
},
onSuccess: function(data) {
location.href = `/order/purchase-order/${data.pk}/`;
},
title: '{% trans "Create Purchase Order" %}',
});
}
function removeOrderRowFromOrderWizard(e) {
/* Remove a part selection from an order form. */
@ -266,6 +328,11 @@ function loadSalesOrderTable(table, options) {
field: 'customer_detail',
title: '{% trans "Customer" %}',
formatter: function(value, row, index, field) {
if (!row.customer_detail) {
return '{% trans "Invalid Customer" %}';
}
return imageHoverIcon(row.customer_detail.image) + renderLink(row.customer_detail.name, `/company/${row.customer}/sales-orders/`);
}
},

View File

@ -769,6 +769,159 @@ function loadPartTestTemplateTable(table, options) {
}
function loadPriceBreakTable(table, options) {
/*
* Load PriceBreak table.
*/
var name = options.name || 'pricebreak';
var human_name = options.human_name || 'price break';
var linkedGraph = options.linkedGraph || null;
var chart = null;
table.inventreeTable({
name: name,
method: 'get',
formatNoMatches: function() {
return `{% trans "No ${human_name} information found" %}`;
},
url: options.url,
onLoadSuccess: function(tableData) {
if (linkedGraph) {
// sort array
tableData = tableData.sort((a,b)=>a.quantity-b.quantity);
// split up for graph definition
var graphLabels = Array.from(tableData, x => x.quantity);
var graphData = Array.from(tableData, x => parseFloat(x.price));
// destroy chart if exists
if (chart){
chart.destroy();
}
chart = loadLineChart(linkedGraph,
{
labels: graphLabels,
datasets: [
{
label: '{% trans "Unit Price" %}',
data: graphData,
backgroundColor: 'rgba(255, 206, 86, 0.2)',
borderColor: 'rgb(255, 206, 86)',
stepped: true,
fill: true,
},]
}
);
}
},
columns: [
{
field: 'pk',
title: 'ID',
visible: false,
switchable: false,
},
{
field: 'quantity',
title: '{% trans "Quantity" %}',
sortable: true,
},
{
field: 'price',
title: '{% trans "Price" %}',
sortable: true,
formatter: function(value, row, index) {
var html = value;
html += `<div class='btn-group float-right' role='group'>`
html += makeIconButton('fa-edit icon-blue', `button-${name}-edit`, row.pk, `{% trans "Edit ${human_name}" %}`);
html += makeIconButton('fa-trash-alt icon-red', `button-${name}-delete`, row.pk, `{% trans "Delete ${human_name}" %}`);
html += `</div>`;
return html;
}
},
]
});
}
function loadLineChart(context, data) {
return new Chart(context, {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {position: 'bottom'},
}
}
});
}
function initPriceBreakSet(table, options) {
var part_id = options.part_id;
var pb_human_name = options.pb_human_name;
var pb_url_slug = options.pb_url_slug;
var pb_url = options.pb_url;
var pb_new_btn = options.pb_new_btn;
var pb_new_url = options.pb_new_url;
var linkedGraph = options.linkedGraph || null;
loadPriceBreakTable(
table,
{
name: pb_url_slug,
human_name: pb_human_name,
url: pb_url,
linkedGraph: linkedGraph,
}
);
function reloadPriceBreakTable(){
table.bootstrapTable("refresh");
}
pb_new_btn.click(function() {
launchModalForm(pb_new_url,
{
success: reloadPriceBreakTable,
data: {
part: part_id,
}
}
);
});
table.on('click', `.button-${pb_url_slug}-delete`, function() {
var pk = $(this).attr('pk');
launchModalForm(
`/part/${pb_url_slug}/${pk}/delete/`,
{
success: reloadPriceBreakTable
}
);
});
table.on('click', `.button-${pb_url_slug}-edit`, function() {
var pk = $(this).attr('pk');
launchModalForm(
`/part/${pb_url_slug}/${pk}/edit/`,
{
success: reloadPriceBreakTable
}
);
});
}
function loadStockPricingChart(context, data) {
return new Chart(context, {
type: 'bar',
@ -824,3 +977,36 @@ function loadBomChart(context, data) {
}
});
}
function loadSellPricingChart(context, data) {
return new Chart(context, {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {legend: {position: 'bottom'}},
scales: {
y: {
type: 'linear',
position: 'left',
grid: {display: false},
title: {
display: true,
text: '{% trans "Unit Price" %}'
}
},
y1: {
type: 'linear',
position: 'right',
grid: {display: false},
titel: {
display: true,
text: '{% trans "Quantity" %}',
position: 'right'
}
},
},
}
});
}

View File

@ -685,6 +685,20 @@ function loadStockTable(table, options) {
return renderLink(text, link);
}
},
{
field: 'supplier_part',
title: '{% trans "Supplier Part" %}',
formatter: function(value, row) {
if (!value) {
return '-';
}
var link = `/supplier-part/${row.supplier_part}/stock/`;
var text = `${row.supplier_part_detail.SKU}`;
return renderLink(text, link);
}
},
{
field: 'purchase_price',
title: '{% trans "Purchase Price" %}',

View File

@ -205,7 +205,12 @@ function getAvailableTableFilters(tableKey) {
batch: {
title: '{% trans "Batch" %}',
description: '{% trans "Batch code" %}',
}
},
has_purchase_price: {
type: 'bool',
title: '{% trans "Has purchase price" %}',
description: '{% trans "Show stock items which have a purchase price set" %}',
},
};
}

View File

@ -7,7 +7,7 @@
</div>
<div class='modal fade modal-fixed-footer modal-primary' tabindex='-1' role='dialog' id='modal-form'>
<div class='modal fade modal-fixed-footer modal-primary' role='dialog' id='modal-form'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
@ -33,7 +33,7 @@
</div>
</div>
<div class='modal fade modal-fixed-footer modal-secondary' tabindex='-1' role='dialog' id='modal-form-secondary'>
<div class='modal fade modal-fixed-footer modal-secondary' role='dialog' id='modal-form-secondary'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
@ -59,7 +59,7 @@
</div>
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-question-dialog'>
<div class='modal fade modal-fixed-footer' role='dialog' id='modal-question-dialog'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
@ -79,7 +79,7 @@
</div>
</div>
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-alert-dialog'>
<div class='modal fade modal-fixed-footer' role='dialog' id='modal-alert-dialog'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">

View File

@ -63,7 +63,7 @@
<td>{% trans "Email Settings" %}</td>
<td>
<a href='https://inventree.readthedocs.io/en/latest/admin/email'>
<span class='label label-red'>{% trans "Email settings not configured" %}</span>
<span class='label label-yellow'>{% trans "Email settings not configured" %}</span>
</a>
</td>
</tr>