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:
@ -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,
|
||||
|
@ -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>
|
@ -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>
|
||||
|
141
InvenTree/templates/js/api.js
Normal file
141
InvenTree/templates/js/api.js
Normal 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);
|
||||
|
||||
}
|
86
InvenTree/templates/js/attachment.js
Normal file
86
InvenTree/templates/js/attachment.js
Normal 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;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
@ -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() {
|
||||
|
@ -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');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
1629
InvenTree/templates/js/forms.js
Normal file
1629
InvenTree/templates/js/forms.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -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">×</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
|
||||
|
155
InvenTree/templates/js/model_renderers.js
Normal file
155
InvenTree/templates/js/model_renderers.js
Normal 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;
|
||||
|
||||
}
|
@ -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/`);
|
||||
}
|
||||
},
|
||||
|
@ -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'
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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" %}',
|
||||
|
@ -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" %}',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user