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

[Feature] Add RMA support (#4488)

* Adds ReturnOrder and ReturnOrderAttachment models

* Adds new 'role' specific for return orders

* Refactor total_price into a mixin

- Required for PurchaseOrder and SalesOrder
- May not be required for ReturnOrder (remains to be seen)

* Adds API endpoints for ReturnOrder

- Add list endpoint
- Add detail endpoint
- Adds required serializer models

* Adds basic "index" page for Return Order model

* Update API version

* Update navbar text

* Add db migration for new "role"

* Add ContactList and ContactDetail API endpoints

* Adds template and JS code for manipulation of contacts

- Display a table
- Create / edit / delete

* Splits order.js into multiple files

- Javascript files was becoming extremely large
- Hard to debug and find code
- Split into purchase_order / return_order / sales_order

* Fix role name (change 'returns' to 'return_order')

- Similar to existing roles for purchase_order and sales_order

* Adds detail page for ReturnOrder

* URL cleanup

- Use <int:pk> instead of complex regex

* More URL cleanup

* Add "return orders" list to company detail page

* Break JS status codes into new javascript file

- Always difficult to track down where these are rendered
- Enough to warrant their own file now

* Add ability to edit return order from detail page

* Database migrations

- Add new ReturnOrder modeles
- Add new 'contact' field to external orders

* Adds "contact" to ReturnOrder

- Implement check to ensure that the selected "contact" matches the selected "company"

* Adjust filters to limit contact options

* Fix typo

* Expose 'contact' field for PurchaseOrder model

* Render contact information

* Add "contact" for SalesOrder

* Adds setting to enable / disable return order functionality

- Simply hides the navigation elements
- API is not disabled

* Support filtering ReturnOrder by 'status'

- Refactors existing filter into the OrderFilter class

* js linting

* More JS linting

* Adds ReturnOrderReport model

* Add serializer for the ReturnOrderReport model

- A little bit of refactoring along the way

* Admin integration for new report model

* Refactoring for report.api

- Adds generic mixins for filtering queryset (based on updates to label.api)
- Reduces repeated code a *lot*

* Exposes API endpoints for ReturnOrderReport

* Adds default example report file for ReturnOrder

- Requires some more work :)

* Refactor report printing javascript code

- Replace all existing functions with 'printReports'

* Improvements for default StockItem test report template

- Fix bug in template
- Handle potential errors in template tags
- Add more helpers to report tags
- Improve test result rendering

* Reduce logging verbosity from weasyprint

* Refactor javascript for label printing

- Consolidate into a single function
- Similar to refactor of report functions

* Add report print button to return order page

* Record user reference when creating via API

* Refactor order serializers

- Move common code into AbstractOrderSerializer class

* Adds extra line item model for the return order

- Adds serializer and API endpoints as appropriate

* Render extra line table for return order

- Refactor existing functions into a single generic function
- Reduces repeated JS code a lot

* Add ability to create a new extra line item

* Adds button for creating a new lien item

* JS linting

* Update test

* Typo fix

(cherry picked from commit 28ac2be35b)

* Enable search for return order

* Don't do pricing (yet) for returnorder extra line table

- Fixes an uncaught error

* Error catching for api.js

* Updates for order models:

- Add 'target_date' field to abstract Order model
- Add IN_PROGRESS status code for return order
- Refactor 'overdue' and 'outstanding' API queries
- Refactor OVERDUE_FILTER on order models
- Refactor is_overdue on order models
- More table filters for return order model

* JS cleanup

* Create ReturnOrderLineItem model

- New type of status label
- Add TotalPriceMixin to ReturnOrder model

* Adds an API serializer for the ReturnOrderLineItem model

* Add API endpoints for ReturnOrderLineItem model

- Including some refactoring along the way

* javascript: refactor loadTableFilters function

- Pass enforced query through to the filters
- Call Object.assign() to construct a superset query
- Removes a lot of code duplication

* Refactor hard-coded URLS to use {% url %} lookup

- Forces error if the URL is wrong
- If we ever change the URL, will still work

* Implement creation of new return order line items

* Adds 'part_detail' annotation to ReturnOrderLineItem serializer

- Required for rendering part information

* javascript: refactor method for creating a group of buttons in a table

* javascript: refactor common buttons with helper functions

* Allow edit and delete of return order line items

* Add form option to automatically reload a table on success

- Pass table name to options.refreshTable

* JS linting

* Add common function for createExtraLineItem

* Refactor loading of attachment tables

- Setup drag-and-drop as part of core function

* CI fixes

* Refactoring out some more common API endpoint code

* Update migrations

* Fix permission typo

* Refactor for unit testing code

* Add unit tests for Contact model

* Tests for returnorder list API

* Annotate 'line_items' to ReturnOrder serializer

* Driving the refactor tractor

* More unit tests for the ReturnOrder API endpoints

* Refactor "print orders" button for various order tables

- Move into "setupFilterList" code (generic)

* add generic 'label printing' button to table actions buttons

* Refactor build output table

* Refactoring icon generation for js

* Refactoring for Part API

* Fix database model type for 'received_date'

* Add API endpoint to "issue" a ReturnOrder

* Improvements for stock tracking table

- Add new status codes
- Add rendering for SalesOrder
- Add rendering for ReturnOrder
- Fix status badges

* Adds functionality to receive line items against a return order

* Add endpoints for completing and cancelling orders

* Add option to allow / prevent editing of ReturnOrder after completed

* js linting

* Wrap "add extra line" button in setting check

* Updates to order/admin.py

* Remove inline admin for returnorderline model

* Updates to pass CI

* Serializer fix

* order template fixes

* Unit test fix

* Fixes for ReturnOrder.receive_line_item

* Unit testing for receiving line items against an RMA

* Improve example report for return order

* Extend unit tests for reporting

* Cleanup here and there

* Unit testing for order views

* Clear "sales_order" field when returning against ReturnOrder

* Add 'location' to deltas when returning from customer

* Bug fix for unit test
This commit is contained in:
Oliver
2023-03-29 10:35:43 +11:00
committed by GitHub
parent d4a64b4f7d
commit 27aa16d55d
122 changed files with 10391 additions and 7053 deletions

View File

@ -75,9 +75,7 @@ $('#history-delete').click(function() {
multi_delete: true,
preFormContent: html,
title: '{% trans "Delete Notifications" %}',
onSuccess: function() {
$('#history-table').bootstrapTable('refresh');
},
refreshTable: '#history-table',
form_data: {
filters: {
read: true,
@ -88,7 +86,7 @@ $('#history-delete').click(function() {
});
$("#history-table").on('click', '.notification-delete', function() {
constructForm(`/api/notifications/${$(this).attr('pk')}/`, {
constructForm(`{% url "api-notifications-list" %}${$(this).attr('pk')}/`, {
method: 'DELETE',
title: '{% trans "Delete Notification" %}',
onSuccess: function(data) {

View File

@ -0,0 +1,20 @@
{% extends "panel.html" %}
{% load i18n %}
{% block label %}return-order{% endblock %}
{% block heading %}
{% trans "Return Order Settings" %}
{% endblock %}
{% block content %}
<table class='table table-striped table-condensed'>
<tbody>
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_ENABLED" icon="fa-check-circle" %}
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_REFERENCE_PATTERN" %}
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_EDIT_COMPLETED_ORDERS" icon="fa-edit" %}
</tbody>
</table>
{% endblock %}

View File

@ -42,6 +42,7 @@
{% include "InvenTree/settings/build.html" %}
{% include "InvenTree/settings/po.html" %}
{% include "InvenTree/settings/so.html" %}
{% include "InvenTree/settings/returns.html" %}
{% include "InvenTree/settings/plugin.html" %}
{% plugin_list as pl_list %}

View File

@ -284,9 +284,7 @@ onPanelLoad('parts', function() {
},
method: 'POST',
title: '{% trans "Create Part Parameter Template" %}',
onSuccess: function() {
$("#param-table").bootstrapTable('refresh');
},
refreshTable: '#param-table',
});
});
@ -303,9 +301,7 @@ onPanelLoad('parts', function() {
description: {},
},
title: '{% trans "Edit Part Parameter Template" %}',
onSuccess: function() {
$("#param-table").bootstrapTable('refresh');
},
refreshTable: '#param-table',
}
);
});
@ -325,9 +321,7 @@ onPanelLoad('parts', function() {
method: 'DELETE',
preFormContent: html,
title: '{% trans "Delete Part Parameter Template" %}',
onSuccess: function() {
$("#param-table").bootstrapTable('refresh');
},
refreshTable: '#param-table',
}
);
});

View File

@ -52,6 +52,8 @@
{% include "sidebar_item.html" with label='purchase-order' text=text icon="fa-shopping-cart" %}
{% trans "Sales Orders" as text %}
{% include "sidebar_item.html" with label='sales-order' text=text icon="fa-truck" %}
{% trans "Return Orders" as text %}
{% include "sidebar_item.html" with label='return-order' text=text icon="fa-undo" %}
{% trans "Plugin Settings" as text %}
{% include "sidebar_header.html" with text=text %}

View File

@ -28,6 +28,8 @@
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_EXCLUDE_INACTIVE_PURCHASE_ORDERS" user_setting=True icon='fa-eye-slash' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_SALES_ORDERS" user_setting=True icon='fa-truck' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_EXCLUDE_INACTIVE_SALES_ORDERS" user_setting=True icon='fa-eye-slash' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_RETURN_ORDERS" user_setting=True icon='fa-truck' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_EXCLUDE_INACTIVE_RETURN_ORDERS" user_setting=True icon='fa-eye-slash' %}
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_RESULTS" user_setting=True icon='fa-search' %}

View File

@ -5,6 +5,7 @@
{% plugins_enabled as plugins_enabled %}
{% settings_value 'BARCODE_ENABLE' as barcodes %}
{% settings_value 'REPORT_ENABLE_TEST_REPORT' as test_report_enabled %}
{% settings_value 'RETURNORDER_ENABLED' as return_order_enabled %}
{% settings_value "REPORT_ENABLE" as report_enabled %}
{% settings_value "SERVER_RESTART_REQUIRED" as server_restart_required %}
{% settings_value "LABEL_ENABLE" as labels_enabled %}
@ -164,8 +165,12 @@
<script defer type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'order.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'part.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'purchase_order.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'return_order.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'report.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'sales_order.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'search.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'status_codes.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'stock.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'plugin.js' %}"></script>
<script defer type='text/javascript' src="{% i18n_static 'pricing.js' %}"></script>

View File

@ -0,0 +1,11 @@
{% extends "email/email.html" %}
{% load i18n %}
{% load inventree_extras %}
{% block title %}
{{ message }}
{% if link %}
<p>{% trans "Click on the following link to view this order" %}: <a href='{{ link }}'>{{ link }}</a></p>
{% endif %}
{% endblock title %}

View File

@ -14,18 +14,24 @@
* Helper functions for calendar display
*/
/*
* Extract the first displayed date on the calendar
*/
function startDate(calendar) {
// Extract the first displayed date on the calendar
return calendar.currentData.dateProfile.activeRange.start.toISOString().split('T')[0];
}
/*
* Extract the last display date on the calendar
*/
function endDate(calendar) {
// Extract the last display date on the calendar
return calendar.currentData.dateProfile.activeRange.end.toISOString().split('T')[0];
}
/*
* Remove all events from the calendar
*/
function clearEvents(calendar) {
// Remove all events from the calendar
var events = calendar.getEvents();

View File

@ -40,8 +40,17 @@ function getCookie(name) {
return cookieValue;
}
/*
* Perform a GET request to the InvenTree server
*/
function inventreeGet(url, filters={}, options={}) {
if (!url) {
console.error('inventreeGet called without url');
return;
}
// Middleware token required for data update
var csrftoken = getCookie('csrftoken');
@ -78,14 +87,20 @@ function inventreeGet(url, filters={}, options={}) {
});
}
/* Upload via AJAX using the FormData approach.
*
* Note that the following AJAX parameters are required for FormData upload
*
* processData: false
* contentType: false
*/
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
*/
if (!url) {
console.error('inventreeFormDataUpload called without url');
return;
}
// CSRF cookie token
var csrftoken = getCookie('csrftoken');
@ -116,8 +131,17 @@ function inventreeFormDataUpload(url, data, options={}) {
});
}
/*
* Perform a PUT or PATCH request to the InvenTree server
*/
function inventreePut(url, data={}, options={}) {
if (!url) {
console.error('inventreePut called without url');
return;
}
var method = options.method || 'PUT';
// Middleware token required for data update
@ -164,6 +188,11 @@ function inventreePut(url, data={}, options={}) {
*/
function inventreeDelete(url, options={}) {
if (!url) {
console.error('inventreeDelete called without url');
return;
}
options = options || {};
options.method = 'DELETE';

View File

@ -1,15 +1,14 @@
{% load i18n %}
/* globals
makeIconButton,
renderLink,
wrapButtons,
*/
/* exported
attachmentLink,
addAttachmentButtonCallbacks,
loadAttachmentTable,
reloadAttachmentTable,
loadAttachmentTable
*/
@ -35,7 +34,7 @@ function addAttachmentButtonCallbacks(url, fields={}) {
constructForm(url, {
fields: file_fields,
method: 'POST',
onSuccess: reloadAttachmentTable,
refreshTable: '#attachment-table',
title: '{% trans "Add Attachment" %}',
});
});
@ -57,7 +56,7 @@ function addAttachmentButtonCallbacks(url, fields={}) {
constructForm(url, {
fields: link_fields,
method: 'POST',
onSuccess: reloadAttachmentTable,
refreshTable: '#attachment-table',
title: '{% trans "Add Link" %}',
});
});
@ -79,9 +78,9 @@ function deleteAttachments(attachments, url, options={}) {
var icon = '';
if (attachment.filename) {
icon = `<span class='fas fa-file-alt'></span>`;
icon = makeIcon(attachmentIcon(attachment.filename), '');
} else if (attachment.link) {
icon = `<span class='fas fa-link'></span>`;
icon = makeIcon('fa-link', '');
}
return `
@ -123,29 +122,15 @@ function deleteAttachments(attachments, url, options={}) {
items: ids,
filters: options.filters,
},
onSuccess: function() {
// Refresh the table once all attachments are deleted
$('#attachment-table').bootstrapTable('refresh');
}
refreshTable: '#attachment-table',
});
}
function reloadAttachmentTable() {
$('#attachment-table').bootstrapTable('refresh');
}
/*
* Render a link (with icon) to an internal attachment (file)
* Return a particular icon based on filename extension
*/
function attachmentLink(filename) {
if (!filename) {
return null;
}
function attachmentIcon(filename) {
// Default file icon (if no better choice is found)
let icon = 'fa-file-alt';
let fn = filename.toLowerCase();
@ -171,10 +156,25 @@ function attachmentLink(filename) {
});
}
let split = filename.split('/');
fn = split[split.length - 1];
return icon;
}
let html = `<span class='fas ${icon}'></span> ${fn}`;
/*
* Render a link (with icon) to an internal attachment (file)
*/
function attachmentLink(filename) {
if (!filename) {
return null;
}
let split = filename.split('/');
let fn = split[split.length - 1];
let icon = attachmentIcon(filename);
let html = makeIcon(icon) + ` ${fn}`;
return renderLink(html, filename, {download: true});
@ -271,7 +271,7 @@ function loadAttachmentTable(url, options) {
delete opts.fields.link;
}
},
onSuccess: reloadAttachmentTable,
refreshTable: '#attachment-table',
title: '{% trans "Edit Attachment" %}',
});
});
@ -299,7 +299,7 @@ function loadAttachmentTable(url, options) {
if (row.attachment) {
return attachmentLink(row.attachment);
} else if (row.link) {
var html = `<span class='fas fa-link'></span> ${row.link}`;
let html = makeIcon('fa-link') + ` ${row.link}`;
return renderLink(html, row.link);
} else {
return '-';
@ -327,13 +327,10 @@ function loadAttachmentTable(url, options) {
{
field: 'actions',
formatter: function(value, row) {
var html = '';
html = `<div class='btn-group float-right' role='group'>`;
let buttons = '';
if (permissions.change) {
html += makeIconButton(
'fa-edit icon-blue',
buttons += makeEditButton(
'button-attachment-edit',
row.pk,
'{% trans "Edit attachment" %}',
@ -341,19 +338,30 @@ function loadAttachmentTable(url, options) {
}
if (permissions.delete) {
html += makeIconButton(
'fa-trash-alt icon-red',
buttons += makeDeleteButton(
'button-attachment-delete',
row.pk,
'{% trans "Delete attachment" %}',
);
}
html += `</div>`;
return html;
return wrapButtons(buttons);
}
}
]
});
// Enable drag-and-drop functionality
enableDragAndDrop(
'#attachment-dropzone',
url,
{
data: options.filters,
label: 'attachment',
method: 'POST',
success: function() {
reloadBootstrapTable('#attachment-table');
}
}
);
}

View File

@ -3,7 +3,6 @@
/* globals
imageHoverIcon,
inventreePut,
makeIconButton,
modalEnable,
modalSetContent,
modalSetTitle,
@ -43,11 +42,11 @@ function makeBarcodeInput(placeholderText='', hintText='') {
<div class='controls'>
<div class='input-group'>
<span class='input-group-text'>
<span class='fas fa-qrcode'></span>
${makeIcon('fa-qrcode')}
</span>
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
<button title='{% trans "Scan barcode using connected webcam" %}' id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'>
<span class='fas fa-camera'></span>
${makeIcon('fa-camera')}
</button>
</div>
<div id='hint_barcode_data' class='help-block'>${hintText}</div>
@ -132,7 +131,7 @@ function makeNotesField(options={}) {
<div class='controls'>
<div class='input-group'>
<span class='input-group-text'>
<span class='fas fa-sticky-note'></span>
${makeIcon('fa-sticky-note')}
</span>
<input id='notes' class='textinput textInput form-control' type='text' name='notes' placeholder='${placeholder}'>
</div>
@ -149,7 +148,7 @@ function postBarcodeData(barcode_data, options={}) {
var modal = options.modal || '#modal-form';
var url = options.url || '/api/barcode/';
var url = options.url || '{% url "api-barcode-scan" %}';
var data = options.data || {};
@ -462,7 +461,7 @@ function unlinkBarcode(data, options={}) {
accept_text: '{% trans "Unlink" %}',
accept: function() {
inventreePut(
'/api/barcode/unlink/',
'{% url "api-barcode-unlink" %}',
data,
{
method: 'POST',
@ -521,7 +520,7 @@ function barcodeCheckInStockItems(location_id, options={}) {
<td>${imageHoverIcon(item.part_detail.thumbnail)} ${item.part_detail.name}</td>
<td>${location_info}</td>
<td>${item.quantity}</td>
<td>${makeIconButton('fa-times-circle icon-red', 'button-item-remove', item.pk, '{% trans "Remove stock item" %}')}</td>
<td>${makeRemoveButton('button-item-remove', item.pk, '{% trans "Remove stock item" %}')}</td>
</tr>`;
});
@ -691,7 +690,7 @@ function barcodeCheckInStockLocations(location_id, options={}) {
if ('stocklocation' in response) {
var pk = response.stocklocation.pk;
var url = `/api/stock/location/${pk}/`;
var url = `{% url "api-location-list" %}${pk}/`;
// Move the scanned location into *this* location
inventreePut(
@ -812,7 +811,7 @@ function scanItemsIntoLocation(item_list, options={}) {
var pk = response.stocklocation.pk;
inventreeGet(`/api/stock/location/${pk}/`, {}, {
inventreeGet(`{% url "api-location-list" %}${pk}/`, {}, {
success: function(response) {
stock_location = response;

View File

@ -96,12 +96,12 @@ function constructBomUploadTable(data, options={}) {
var optional = constructRowField('optional');
var note = constructRowField('note');
var buttons = `<div class='btn-group float-right' role='group'>`;
let buttons = '';
buttons += makeIconButton('fa-info-circle', 'button-row-data', idx, '{% trans "Display row data" %}');
buttons += makeIconButton('fa-times icon-red', 'button-row-remove', idx, '{% trans "Remove row" %}');
buttons += makeInfoButton('button-row-data', idx, '{% trans "Display row data" %}');
buttons += makeRemoveButton('button-row-remove', idx, '{% trans "Remove row" %}');
buttons += `</div>`;
buttons = wrapButtons(buttons);
var html = `
<tr id='items_${idx}' class='bom-import-row' idx='${idx}'>
@ -557,7 +557,7 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) {
var buttons = '';
buttons += makeIconButton('fa-times icon-red', 'button-row-remove', pk, '{% trans "Remove substitute part" %}');
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove substitute part" %}');
// Render a single row
var html = `
@ -626,7 +626,7 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) {
</div>
`;
constructForm(`/api/bom/substitute/${pk}/`, {
constructForm(`{% url "api-bom-substitute-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Remove Substitute Part" %}',
preFormContent: pre,
@ -785,9 +785,7 @@ function loadBomTable(table, options={}) {
filters = loadTableFilters('bom');
}
for (var key in params) {
filters[key] = params[key];
}
Object.assign(filters, params);
setupFilterList('bom', $(table));
@ -1142,7 +1140,7 @@ function loadBomTable(table, options={}) {
}
if (available_stock <= 0) {
text += `<span class='fas fa-times-circle icon-red float-right' title='{% trans "No Stock Available" %}'></span>`;
text += makeIconBadge('fa-times-circle icon-red', '{% trans "No Stock Available" %}');
} else {
var extra = '';
@ -1160,7 +1158,10 @@ function loadBomTable(table, options={}) {
}
if (row.on_order && row.on_order > 0) {
text += `<span class='fas fa-shopping-cart float-right' title='{% trans "On Order" %}: ${row.on_order}'></span>`;
text += makeIconBadge(
'fa-shopping-cart',
`{% trans "On Order" %}: ${row.on_order}`,
);
}
return renderLink(text, url);
@ -1242,11 +1243,10 @@ function loadBomTable(table, options={}) {
var bSubs = makeIconButton('fa-exchange-alt icon-blue', 'bom-substitutes-button', row.pk, '{% trans "Edit substitute parts" %}');
var bEdit = makeIconButton('fa-edit icon-blue', 'bom-edit-button', row.pk, '{% trans "Edit BOM Item" %}');
var bEdit = makeEditButton('bom-edit-button', row.pk, '{% trans "Edit BOM Item" %}');
var bDelt = makeIconButton('fa-trash-alt icon-red', 'bom-delete-button', row.pk, '{% trans "Delete BOM Item" %}');
var bDelt = makeDeleteButton('bom-delete-button', row.pk, '{% trans "Delete BOM Item" %}');
var html = `<div class='btn-group float-right' role='group' style='min-width: 100px;'>`;
if (!row.validated) {
html += bValidate;
@ -1254,13 +1254,13 @@ function loadBomTable(table, options={}) {
html += bValid;
}
html += bEdit;
html += bSubs;
html += bDelt;
var buttons = '';
buttons += bEdit;
buttons += bSubs;
buttons += bDelt;
html += `</div>`;
return wrapButtons(buttons);
return html;
} else {
// Return a link to the external BOM
@ -1273,7 +1273,7 @@ function loadBomTable(table, options={}) {
footerFormatter: function(data) {
return `
<button class='btn btn-success float-right' type='button' title='{% trans "Add BOM Item" %}' id='bom-item-new-footer'>
<span class='fas fa-plus-circle'></span> {% trans "Add BOM Item" %}
${makeIcon('fa-plus-circle')} {% trans "Add BOM Item" %}
</button>
`;
}
@ -1436,7 +1436,7 @@ function loadBomTable(table, options={}) {
var fields = bomItemFields();
constructForm(`/api/bom/${pk}/`, {
constructForm(`{% url "api-bom-list" %}${pk}/`, {
fields: fields,
title: '{% trans "Edit BOM Item" %}',
focus: 'sub_part',
@ -1508,15 +1508,7 @@ function loadUsedInTable(table, part_id, options={}) {
params.part_detail = true;
params.sub_part_detail = true;
var filters = {};
if (!options.disableFilters) {
filters = loadTableFilters('usedin');
}
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('usedin', params);
setupFilterList('usedin', $(table), options.filterTarget || '#filter-list-usedin');

View File

@ -90,7 +90,7 @@ function editBuildOrder(pk) {
var fields = buildFormFields();
constructForm(`/api/build/${pk}/`, {
constructForm(`{% url "api-build-list" %}${pk}/`, {
fields: fields,
reload: true,
title: '{% trans "Edit Build Order" %}',
@ -147,7 +147,7 @@ function newBuildOrder(options={}) {
*/
function duplicateBuildOrder(build_id, options={}) {
inventreeGet(`/api/build/${build_id}/`, {}, {
inventreeGet(`{% url "api-build-list" %}${build_id}/`, {}, {
success: function(data) {
// Clear out data we do not want to be duplicated
delete data['pk'];
@ -166,7 +166,7 @@ function duplicateBuildOrder(build_id, options={}) {
function cancelBuildOrder(build_id, options={}) {
constructForm(
`/api/build/${build_id}/cancel/`,
`{% url "api-build-list" %}${build_id}/cancel/`,
{
method: 'POST',
title: '{% trans "Cancel Build Order" %}',
@ -208,7 +208,7 @@ function cancelBuildOrder(build_id, options={}) {
/* Construct a form to "complete" (finish) a build order */
function completeBuildOrder(build_id, options={}) {
constructForm(`/api/build/${build_id}/finish/`, {
constructForm(`{% url "api-build-list" %}${build_id}/finish/`, {
fieldsFunction: function(opts) {
var ctx = opts.context || {};
@ -287,7 +287,7 @@ function createBuildOutput(build_id, options) {
// Request build order information from the server
inventreeGet(
`/api/build/${build_id}/`,
`{% url "api-build-list" %}${build_id}/`,
{},
{
success: function(build) {
@ -312,7 +312,7 @@ function createBuildOutput(build_id, options) {
};
// Work out the next available serial numbers
inventreeGet(`/api/part/${build.part}/serial-numbers/`, {}, {
inventreeGet(`{% url "api-part-list" %}${build.part}/serial-numbers/`, {}, {
success: function(data) {
if (data.next) {
fields.serial_numbers.placeholder = `{% trans "Next available serial number" %}: ${data.next}`;
@ -341,7 +341,7 @@ function createBuildOutput(build_id, options) {
`;
}
constructForm(`/api/build/${build_id}/create-output/`, {
constructForm(`{% url "api-build-list" %}${build_id}/create-output/`, {
method: 'POST',
title: '{% trans "Create Build Output" %}',
confirm: true,
@ -364,7 +364,7 @@ function createBuildOutput(build_id, options) {
*/
function makeBuildOutputButtons(output_id, build_info, options={}) {
var html = `<div class='btn-group float-right' role='group'>`;
var html = '';
// Tracked parts? Must be individually allocated
if (options.has_bom_items) {
@ -398,17 +398,13 @@ function makeBuildOutputButtons(output_id, build_info, options={}) {
);
// Add a button to "delete" this build output
html += makeIconButton(
'fa-trash-alt icon-red',
html += makeDeleteButton(
'button-output-delete',
output_id,
'{% trans "Delete build output" %}',
);
html += `</div>`;
return html;
return wrapButtons(html);
}
@ -421,7 +417,7 @@ function makeBuildOutputButtons(output_id, build_info, options={}) {
*/
function unallocateStock(build_id, options={}) {
var url = `/api/build/${build_id}/unallocate/`;
var url = `{% url "api-build-list" %}${build_id}/unallocate/`;
var html = `
<div class='alert alert-block alert-warning'>
@ -486,7 +482,7 @@ function completeBuildOutputs(build_id, outputs, options={}) {
var buttons = `<div class='btn-group float-right' role='group'>`;
buttons += makeIconButton('fa-times icon-red', 'button-row-remove', pk, '{% trans "Remove row" %}');
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
buttons += '</div>';
@ -529,7 +525,7 @@ function completeBuildOutputs(build_id, outputs, options={}) {
</tbody>
</table>`;
constructForm(`/api/build/${build_id}/complete/`, {
constructForm(`{% url "api-build-list" %}${build_id}/complete/`, {
method: 'POST',
preFormContent: html,
fields: {
@ -647,7 +643,7 @@ function deleteBuildOutputs(build_id, outputs, options={}) {
var buttons = `<div class='btn-group float-right' role='group'>`;
buttons += makeIconButton('fa-times icon-red', 'button-row-remove', pk, '{% trans "Remove row" %}');
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
buttons += '</div>';
@ -690,7 +686,7 @@ function deleteBuildOutputs(build_id, outputs, options={}) {
</tbody>
</table>`;
constructForm(`/api/build/${build_id}/delete-outputs/`, {
constructForm(`{% url "api-build-list" %}${build_id}/delete-outputs/`, {
method: 'POST',
preFormContent: html,
fields: {},
@ -768,11 +764,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
options.params['location_detail'] = true;
options.params['stock_detail'] = true;
var filters = loadTableFilters('buildorderallocation');
for (var key in options.params) {
filters[key] = options.params[key];
}
var filters = loadTableFilters('buildorderallocation', options.params);
setupFilterList('buildorderallocation', $(table));
@ -893,7 +885,12 @@ function loadBuildOutputTable(build_info, options={}) {
filters[key] = params[key];
}
setupFilterList('builditems', $(table), options.filterTarget || '#filter-list-incompletebuilditems');
setupFilterList('builditems', $(table), options.filterTarget || '#filter-list-incompletebuilditems', {
labels: {
url: '{% url "api-stockitem-label-list" %}',
key: 'item',
}
});
function setupBuildOutputButtonCallbacks() {
@ -1407,19 +1404,6 @@ function loadBuildOutputTable(build_info, options={}) {
);
});
// Print stock item labels
$('#incomplete-output-print-label').click(function() {
var outputs = getTableData(table);
var stock_id_values = [];
outputs.forEach(function(output) {
stock_id_values.push(output.pk);
});
printStockItemLabels(stock_id_values);
});
$('#outputs-expand').click(function() {
$(table).bootstrapTable('expandAllRows');
});
@ -1482,13 +1466,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
}
// Filters
var filters = loadTableFilters('builditems');
var params = options.params || {};
for (var key in params) {
filters[key] = params[key];
}
let filters = loadTableFilters('builditems', options.params);
setupFilterList('builditems', $(table), options.filterTarget);
@ -1703,6 +1681,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
name: 'build-allocation',
uniqueId: 'sub_part',
search: options.search || false,
queryParams: filters,
original: options.params,
onPostBody: function(data) {
// Setup button callbacks
setupCallbacks();
@ -1796,15 +1776,13 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
var pk = row.pk;
var html = `<div class='btn-group float-right' role='group'>`;
var html = '';
html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
html += makeEditButton('button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
html += makeDeleteButton('button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
html += `</div>`;
return html;
return wrapButtons(html);
}
}
]
@ -1814,7 +1792,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
subTable.find('.button-allocation-edit').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/build/item/${pk}/`, {
constructForm(`{% url "api-build-item-list" %}${pk}/`, {
fields: {
quantity: {},
},
@ -1826,7 +1804,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
subTable.find('.button-allocation-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/build/item/${pk}/`, {
constructForm(`{% url "api-build-item-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Remove Allocation" %}',
onSuccess: reloadAllocationData,
@ -1935,9 +1913,9 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
icons += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Consumable item" %}'></span>`;
} else {
if (available_stock < (required - allocated)) {
icons += `<span class='fas fa-times-circle icon-red float-right' title='{% trans "Insufficient stock available" %}'></span>`;
icons += makeIconBadge('fa-times-circle icon-red', '{% trans "Insufficient stock available" %}');
} else {
icons += `<span class='fas fa-check-circle icon-green float-right' title='{% trans "Sufficient stock available" %}'></span>`;
icons += makeIconBadge('fa-check-circle icon-green', '{% trans "Sufficient stock available" %}');
}
if (available_stock <= 0) {
@ -1953,13 +1931,15 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
}
if (extra) {
icons += `<span title='${extra}' class='fas fa-info-circle float-right icon-blue'></span>`;
icons += makeInfoButton('fa-info-circle icon-blue', extra);
}
}
}
if (row.on_order && row.on_order > 0) {
icons += `<span class='fas fa-shopping-cart float-right' title='{% trans "On Order" %}: ${row.on_order}'></span>`;
makeIconBadge('fa-shopping-cart', '{% trans "On Order" %}', {
content: row.on_order,
});
}
return renderLink(text, url) + icons;
@ -2027,7 +2007,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
}
// Generate action buttons for this build output
var html = `<div class='btn-group float-right' role='group'>`;
let html = '';
if (allocatedQuantity(row) < requiredQuantity(row)) {
if (row.sub_part_detail.assembly) {
@ -2041,17 +2021,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', row.sub_part, '{% trans "Allocate stock" %}');
}
html += makeIconButton(
'fa-minus-circle icon-red', 'button-unallocate', row.sub_part,
html += makeRemoveButton(
'button-unallocate',
row.sub_part,
'{% trans "Unallocate stock" %}',
{
disabled: allocatedQuantity(row) == 0,
}
);
html += '</div>';
return html;
return wrapButtons(html);
}
},
]
@ -2093,7 +2072,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
if (output_id) {
// Request information on the particular build output (stock item)
inventreeGet(`/api/stock/${output_id}/`, {}, {
inventreeGet(`{% url "api-stock-list" %}${output_id}/`, {}, {
success: function(output) {
if (output.quantity == 1 && output.serial != null) {
auto_fill_filters.serial = output.serial;
@ -2112,8 +2091,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
var delete_button = `<div class='btn-group float-right' role='group'>`;
delete_button += makeIconButton(
'fa-times icon-red',
delete_button += makeRemoveButton(
'button-row-remove',
pk,
'{% trans "Remove row" %}',
@ -2245,7 +2223,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
</table>
`;
constructForm(`/api/build/${build_id}/allocate/`, {
constructForm(`{% url "api-build-list" %}${build_id}/allocate/`, {
method: 'POST',
fields: {},
preFormContent: html,
@ -2459,7 +2437,7 @@ function autoAllocateStockToBuild(build_id, bom_items=[], options={}) {
},
};
constructForm(`/api/build/${build_id}/auto-allocate/`, {
constructForm(`{% url "api-build-list" %}${build_id}/auto-allocate/`, {
method: 'POST',
fields: fields,
title: '{% trans "Allocate Stock Items" %}',
@ -2484,21 +2462,19 @@ function loadBuildTable(table, options) {
var params = options.params || {};
var filters = {};
params['part_detail'] = true;
if (!options.disableFilters) {
filters = loadTableFilters('build');
}
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('build', params);
var filterTarget = options.filterTarget || null;
setupFilterList('build', table, filterTarget, {download: true});
setupFilterList('build', table, filterTarget, {
download: true,
report: {
url: '{% url "api-build-report-list" %}',
key: 'build',
}
});
// Which display mode to use for the build table?
var display_mode = inventreeLoad('build-table-display-mode', 'list');

View File

@ -4,23 +4,26 @@
constructForm,
imageHoverIcon,
loadTableFilters,
makeIconButton,
renderLink,
setupFilterList,
*/
/* exported
createCompany,
createContact,
createManufacturerPart,
createSupplierPart,
createSupplierPartPriceBreak,
deleteContacts,
deleteManufacturerParts,
deleteManufacturerPartParameters,
deleteSupplierParts,
duplicateSupplierPart,
editCompany,
editContact,
editSupplierPartPriceBreak,
loadCompanyTable,
loadContactTable,
loadManufacturerPartTable,
loadManufacturerPartParameterTable,
loadSupplierPartTable,
@ -197,7 +200,7 @@ function createSupplierPart(options={}) {
var header = '';
if (options.part) {
var part_model = {};
inventreeGet(`/api/part/${options.part}/.*`, {}, {
inventreeGet(`{% url "api-part-list" %}${options.part}/.*`, {}, {
async: false,
success: function(response) {
part_model = response;
@ -226,7 +229,7 @@ function duplicateSupplierPart(part, options={}) {
var fields = options.fields || supplierPartFields();
// Retrieve information for the supplied part
inventreeGet(`/api/company/part/${part}/`, {}, {
inventreeGet(`{% url "api-supplier-part-list" %}${part}/`, {}, {
success: function(data) {
// Remove fields which we do not want to duplicate
@ -234,7 +237,7 @@ function duplicateSupplierPart(part, options={}) {
delete data['available'];
delete data['availability_updated'];
constructForm(`/api/company/part/`, {
constructForm('{% url "api-supplier-part-list" %}', {
method: 'POST',
fields: fields,
title: '{% trans "Duplicate Supplier Part" %}',
@ -260,7 +263,7 @@ function editSupplierPart(part, options={}) {
fields.part.hidden = true;
}
constructForm(`/api/company/part/${part}/`, {
constructForm(`{% url "api-supplier-part-list" %}${part}/`, {
fields: fields,
title: options.title || '{% trans "Edit Supplier Part" %}',
onSuccess: options.onSuccess
@ -443,24 +446,18 @@ function createCompany(options={}) {
}
/*
* Load company listing data into specified table.
*
* Args:
* - table: Table element on the page
* - url: Base URL for the API query
* - options: table options.
*/
function loadCompanyTable(table, url, options={}) {
/*
* Load company listing data into specified table.
*
* Args:
* - table: Table element on the page
* - url: Base URL for the API query
* - options: table options.
*/
// Query parameters
var params = options.params || {};
var filters = loadTableFilters('company');
for (var key in params) {
filters[key] = params[key];
}
let params = options.params || {};
let filters = loadTableFilters('company', params);
setupFilterList('company', $(table));
@ -547,6 +544,230 @@ function loadCompanyTable(table, url, options={}) {
}
/*
* Construct a set of form fields for the Contact model
*/
function contactFields(options={}) {
let fields = {
company: {
icon: 'fa-building',
},
name: {
icon: 'fa-user',
},
phone: {
icon: 'fa-phone'
},
email: {
icon: 'fa-at',
},
role: {
icon: 'fa-user-tag',
},
};
if (options.company) {
fields.company.value = options.company;
}
return fields;
}
/*
* Launches a form to create a new Contact
*/
function createContact(options={}) {
let fields = options.fields || contactFields(options);
constructForm('{% url "api-contact-list" %}', {
method: 'POST',
fields: fields,
title: '{% trans "Create New Contact" %}',
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/*
* Launches a form to edit an existing Contact
*/
function editContact(pk, options={}) {
let fields = options.fields || contactFields(options);
constructForm(`{% url "api-contact-list" %}${pk}/`, {
fields: fields,
title: '{% trans "Edit Contact" %}',
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/*
* Launches a form to delete one (or more) contacts
*/
function deleteContacts(contacts, options={}) {
if (contacts.length == 0) {
return;
}
function renderContact(contact) {
return `
<tr>
<td>${contact.name}</td>
<td>${contact.email}</td>
<td>${contact.role}</td>
</tr>`;
}
let rows = '';
let ids = [];
contacts.forEach(function(contact) {
rows += renderContact(contact);
ids.push(contact.pk);
});
let html = `
<div class='alert alert-block alert-danger'>
{% trans "All selected contacts will be deleted" %}
</div>
<table class='table table-striped table-condensed'>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Email" %}</th>
<th>{% trans "Role" %}</th>
</tr>
${rows}
</table>`;
constructForm('{% url "api-contact-list" %}', {
method: 'DELETE',
multi_delete: true,
title: '{% trans "Delete Contacts" %}',
preFormContent: html,
form_data: {
items: ids,
},
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/*
* Load table listing company contacts
*/
function loadContactTable(table, options={}) {
var params = options.params || {};
var filters = loadTableFilters('contact', params);
setupFilterList('contact', $(table), '#filter-list-contacts');
$(table).inventreeTable({
url: '{% url "api-contact-list" %}',
queryParams: filters,
original: params,
idField: 'pk',
uniqueId: 'pk',
sidePagination: 'server',
formatNoMatches: function() {
return '{% trans "No contacts found" %}';
},
showColumns: true,
name: 'contacts',
columns: [
{
field: 'name',
title: '{% trans "Name" %}',
sortable: true,
switchable: false,
},
{
field: 'phone',
title: '{% trans "Phone Number" %}',
sortable: false,
switchable: true,
},
{
field: 'email',
title: '{% trans "Email Address" %}',
sortable: false,
switchable: true,
},
{
field: 'role',
title: '{% trans "Role" %}',
sortable: false,
switchable: false,
},
{
field: 'actions',
title: '',
sortable: false,
switchable: false,
visible: options.allow_edit || options.allow_delete,
formatter: function(value, row) {
var pk = row.pk;
let html = '';
if (options.allow_edit) {
html += makeEditButton('btn-contact-edit', pk, '{% trans "Edit Contact" %}');
}
if (options.allow_delete) {
html += makeDeleteButton('btn-contact-delete', pk, '{% trans "Delete Contact" %}');
}
return wrapButtons(html);
}
}
],
onPostBody: function() {
// Edit button callback
if (options.allow_edit) {
$(table).find('.btn-contact-edit').click(function() {
var pk = $(this).attr('pk');
editContact(pk, {
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
});
});
}
// Delete button callback
if (options.allow_delete) {
$(table).find('.btn-contact-delete').click(function() {
var pk = $(this).attr('pk');
var row = $(table).bootstrapTable('getRowByUniqueId', pk);
if (row && row.pk) {
deleteContacts([row], {
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
});
}
});
}
}
});
}
/* Delete one or more ManufacturerPart objects from the database.
* - User will be provided with a modal form, showing all the parts to be deleted.
* - Delete operations are performed sequentialy, not simultaneously
@ -653,21 +874,16 @@ function deleteManufacturerPartParameters(selections, options={}) {
}
/*
* Load manufacturer part table
*/
function loadManufacturerPartTable(table, url, options) {
/*
* Load manufacturer part table
*
*/
// Query parameters
var params = options.params || {};
// Load filters
var filters = loadTableFilters('manufacturer-part');
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('manufacturer-part', params);
var filterTarget = options.filterTarget || '#filter-list-manufacturer-part';
@ -703,11 +919,11 @@ function loadManufacturerPartTable(table, url, options) {
var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value, url);
if (row.part_detail.is_template) {
html += `<span class='fas fa-clone float-right' title='{% trans "Template part" %}'></span>`;
html += makeIconBadge('fa-clone', '{% trans "Template part" %}');
}
if (row.part_detail.assembly) {
html += `<span class='fas fa-tools float-right' title='{% trans "Assembled part" %}'></span>`;
html += makeIconBadge('fa-tools', '{% trans "Assembled part" %}');
}
if (!row.part_detail.active) {
@ -764,16 +980,13 @@ function loadManufacturerPartTable(table, url, options) {
sortable: false,
switchable: false,
formatter: function(value, row) {
var pk = row.pk;
let pk = row.pk;
let html = '';
var html = `<div class='btn-group float-right' role='group'>`;
html += makeEditButton('button-manufacturer-part-edit', pk, '{% trans "Edit manufacturer part" %}');
html += makeDeleteButton('button-manufacturer-part-delete', pk, '{% trans "Delete manufacturer part" %}');
html += makeIconButton('fa-edit icon-blue', 'button-manufacturer-part-edit', pk, '{% trans "Edit manufacturer part" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-manufacturer-part-delete', pk, '{% trans "Delete manufacturer part" %}');
html += '</div>';
return html;
return wrapButtons(html);
}
}
],
@ -810,20 +1023,15 @@ function loadManufacturerPartTable(table, url, options) {
}
/*
* Load table of ManufacturerPartParameter objects
*/
function loadManufacturerPartParameterTable(table, url, options) {
/*
* Load table of ManufacturerPartParameter objects
*/
var params = options.params || {};
// Load filters
var filters = loadTableFilters('manufacturer-part-parameters');
// Overwrite explicit parameters
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('manufacturer-part-parameters', params);
setupFilterList('manufacturer-part-parameters', $(table));
@ -867,17 +1075,13 @@ function loadManufacturerPartParameterTable(table, url, options) {
switchable: false,
sortable: false,
formatter: function(value, row) {
let pk = row.pk;
let html = '';
var pk = row.pk;
html += makeEditButton('button-parameter-edit', pk, '{% trans "Edit parameter" %}');
html += makeDeleteButton('button-parameter-delete', pk, '{% trans "Delete parameter" %}');
var html = `<div class='btn-group float-right' role='group'>`;
html += makeIconButton('fa-edit icon-blue', 'button-parameter-edit', pk, '{% trans "Edit parameter" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-parameter-delete', pk, '{% trans "Delete parameter" %}');
html += `</div>`;
return html;
return wrapButtons(html);
}
}
],
@ -886,27 +1090,23 @@ function loadManufacturerPartParameterTable(table, url, options) {
$(table).find('.button-parameter-edit').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/company/part/manufacturer/parameter/${pk}/`, {
constructForm(`{% url "api-manufacturer-part-parameter-list" %}${pk}/`, {
fields: {
name: {},
value: {},
units: {},
},
title: '{% trans "Edit Parameter" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
refreshTable: table,
});
});
$(table).find('.button-parameter-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/company/part/manufacturer/parameter/${pk}/`, {
constructForm(`{% url "api-manufacturer-part-parameter-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Parameter" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
refreshTable: table,
});
});
}
@ -914,21 +1114,16 @@ function loadManufacturerPartParameterTable(table, url, options) {
}
/*
* Load supplier part table
*/
function loadSupplierPartTable(table, url, options) {
/*
* Load supplier part table
*
*/
// Query parameters
var params = options.params || {};
// Load filters
var filters = loadTableFilters('supplier-part');
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('supplier-part', params);
setupFilterList('supplier-part', $(table));
@ -964,11 +1159,11 @@ function loadSupplierPartTable(table, url, options) {
var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value, url);
if (row.part_detail.is_template) {
html += `<span class='fas fa-clone float-right' title='{% trans "Template part" %}'></span>`;
html += makeIconBadge('fa-clone', '{% trans "Template part" %}');
}
if (row.part_detail.assembly) {
html += `<span class='fas fa-tools float-right' title='{% trans "Assembled part" %}'></span>`;
html += makeIconBadge('fa-tools', '{% trans "Assembled part" %}');
}
if (!row.part_detail.active) {
@ -1088,9 +1283,13 @@ function loadSupplierPartTable(table, url, options) {
sortable: true,
formatter: function(value, row) {
if (row.availability_updated) {
var html = formatDecimal(value);
var date = renderDate(row.availability_updated, {showTime: true});
html += `<span class='fas fa-info-circle float-right' title='{% trans "Last Updated" %}: ${date}'></span>`;
let html = formatDecimal(value);
let date = renderDate(row.availability_updated, {showTime: true});
html += makeIconBadge(
'fa-info-circle',
`{% trans "Last Updated" %}: ${date}`
);
return html;
} else {
return '-';
@ -1108,16 +1307,13 @@ function loadSupplierPartTable(table, url, options) {
sortable: false,
switchable: false,
formatter: function(value, row) {
var pk = row.pk;
let pk = row.pk;
let html = '';
var html = `<div class='btn-group float-right' role='group'>`;
html += makeEditButton('button-supplier-part-edit', pk, '{% trans "Edit supplier part" %}');
html += makeDeleteButton('button-supplier-part-delete', pk, '{% trans "Delete supplier part" %}');
html += makeIconButton('fa-edit icon-blue', 'button-supplier-part-edit', pk, '{% trans "Edit supplier part" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-supplier-part-delete', pk, '{% trans "Delete supplier part" %}');
html += '</div>';
return html;
return wrapButtons(html);
}
}
],
@ -1166,24 +1362,20 @@ function loadSupplierPriceBreakTable(options={}) {
table.find('.button-price-break-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/company/price-break/${pk}/`, {
constructForm(`{% url "api-part-supplier-price-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Price Break" %}',
onSuccess: function() {
table.bootstrapTable('refresh');
},
refreshTable: table,
});
});
table.find('.button-price-break-edit').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/company/price-break/${pk}/`, {
constructForm(`{% url "api-part-supplier-price-list" %}${pk}/`, {
fields: supplierPartPriceBreakFields(),
title: '{% trans "Edit Price Break" %}',
onSuccess: function() {
table.bootstrapTable('refresh');
}
refreshTable: table,
});
});
}
@ -1231,10 +1423,12 @@ function loadSupplierPriceBreakTable(options={}) {
formatter: function(value, row) {
var html = renderDate(value);
html += `<div class='btn-group float-right' role='group'>`;
html += makeIconButton('fa-edit icon-blue', 'button-price-break-edit', row.pk, '{% trans "Edit price break" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-price-break-delete', row.pk, '{% trans "Delete price break" %}');
html += `</div>`;
let buttons = '';
buttons += makeEditButton('button-price-break-edit', row.pk, '{% trans "Edit price break" %}');
buttons += makeDeleteButton('button-price-break-delete', row.pk, '{% trans "Delete price break" %}');
html += wrapButtons(buttons);
return html;
}

View File

@ -43,7 +43,7 @@ function defaultFilters() {
* @param tableKey - String key for the particular table
* @param defaults - Default filters for this table e.g. 'cascade=1&location=5'
*/
function loadTableFilters(tableKey) {
function loadTableFilters(tableKey, query={}) {
var lookup = 'table-filters-' + tableKey.toLowerCase();
@ -67,6 +67,9 @@ function loadTableFilters(tableKey) {
}
});
// Override configurable filters with hard-coded query
Object.assign(filters, query);
return filters;
}
@ -258,6 +261,18 @@ function generateFilterInput(tableKey, filterKey) {
}
/*
* Helper function to make a 'filter' style button
*/
function makeFilterButton(options={}) {
return `
<button id='${options.id}' title='${options.title}' class='btn btn-outline-secondary filter-button'>
<span class='fas ${options.icon}'></span>
</button>`;
}
/**
* Configure a filter list for a given table
*
@ -290,21 +305,58 @@ function setupFilterList(tableKey, table, target, options={}) {
// One blank slate, please
element.empty();
// Construct a set of buttons
var buttons = '';
// Add 'print reports' button
if (options.report && global_settings.REPORT_ENABLE) {
buttons += makeFilterButton({
id: `print-report-${tableKey}`,
title: options.report.title || '{% trans "Print reports for selected items" %}',
icon: 'fa-print',
});
}
// Add 'print labels' button
if (options.labels && global_settings.LABEL_ENABLE) {
buttons += makeFilterButton({
id: `print-labels-${tableKey}`,
title: options.labels.title || '{% trans "Print labels for selected items" %}',
icon: 'fa-tag',
});
}
// Add download button
if (options.download) {
buttons += `<button id='download-${tableKey}' title='{% trans "Download data" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-download'></span></button>`;
buttons += makeFilterButton({
id: `download-${tableKey}`,
title: '{% trans "Download table data" %}',
icon: 'fa-download',
});
}
buttons += `<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-redo-alt'></span></button>`;
buttons += makeFilterButton({
id: `reload-${tableKey}`,
title: '{% trans "Reload table data" %}',
icon: 'fa-redo-alt',
});
// If there are filters defined for this table, add more buttons
if (!jQuery.isEmptyObject(getAvailableTableFilters(tableKey))) {
buttons += `<button id='${add}' title='{% trans "Add new filter" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-filter'></span></button>`;
buttons += makeFilterButton({
id: add,
title: '{% trans "Add new filter" %}',
icon: 'fa-filter',
});
if (Object.keys(filters).length > 0) {
buttons += `<button id='${clear}' title='{% trans "Clear all filters" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-backspace icon-red'></span></button>`;
buttons += makeFilterButton({
id: clear,
title: '{% trans "Clear all filters" %}',
icon: 'fa-backspace icon-red',
});
}
}
@ -331,6 +383,42 @@ function setupFilterList(tableKey, table, target, options={}) {
element.append(filter_tag);
}
// Callback for printing reports
if (options.report && global_settings.REPORT_ENABLE) {
element.find(`#print-report-${tableKey}`).click(function() {
let data = getTableData(table);
let items = [];
data.forEach(function(row) {
items.push(row.pk);
});
printReports({
items: items,
url: options.report.url,
key: options.report.key
});
});
}
// Callback for printing labels
if (options.labels && global_settings.LABEL_ENABLE) {
element.find(`#print-labels-${tableKey}`).click(function() {
let data = getTableData(table);
let items = [];
data.forEach(function(row) {
items.push(row.pk);
});
printLabels({
items: items,
url: options.labels.url,
key: options.labels.key,
});
});
}
// Callback for reloading the table
element.find(`#reload-${tableKey}`).click(function() {
reloadTableFilters(table);

View File

@ -308,7 +308,7 @@ function constructDeleteForm(fields, options) {
* - confirmText: Text for confirm button (default = "Confirm")
*
*/
function constructForm(url, options) {
function constructForm(url, options={}) {
// An "empty" form will be defined locally
if (url == null) {
@ -1169,6 +1169,11 @@ function handleFormSuccess(response, options) {
$(options.modal).modal('hide');
}
// Refresh a table
if (options.refreshTable) {
reloadBootstrapTable(options.refreshTable);
}
if (options.onSuccess) {
// Callback function
options.onSuccess(response, options);

View File

@ -6,9 +6,14 @@
editButton,
formatDecimal,
imageHoverIcon,
makeCopyButton,
makeDeleteButton,
makeEditButton,
makeIconBadge,
makeIconButton,
makeInfoButton,
makeProgressBar,
makeRemoveButton,
renderLink,
sanitizeInputString,
select2Thumbnail,
@ -17,8 +22,14 @@
thumbnailImage
yesNoLabel,
withTitle,
wrapButtons,
*/
/* exported
makeIcon,
*/
function yesNoLabel(value, options={}) {
var text = '';
var color = '';
@ -147,17 +158,47 @@ function select2Thumbnail(image) {
}
/*
* Construct a simple FontAwesome icon span
*/
function makeIcon(icon, title='', options={}) {
let classes = options.classes || 'fas';
return `<span class='${classes} ${icon}' title='${title}'></span>`;
}
/*
* Construct an 'icon badge' which floats to the right of an object
*/
function makeIconBadge(icon, title) {
function makeIconBadge(icon, title='', options={}) {
var html = `<span class='icon-badge fas ${icon} float-right' title='${title}'></span>`;
let content = options.content || '';
let html = `
<span class='icon-badge fas ${icon} float-right' title='${title}'>
${content}
</span>`;
return html;
}
/*
* Wrap list of buttons in a button group <div>
*/
function wrapButtons(buttons) {
if (!buttons) {
// Return empty element if no buttons are provided
return '';
}
return `<div class='btn-group float-right' role='group'>${buttons}</div>`;
}
/*
* Construct an 'icon button' using the fontawesome set
*/
@ -187,6 +228,46 @@ function makeIconButton(icon, cls, pk, title, options={}) {
}
/*
* Helper function for making a common 'info' button
*/
function makeInfoButton(cls, pk, title, options={}) {
return makeIconButton('fa-info-circle', cls, pk, title, options);
}
/*
* Helper function for making a common 'edit' button
*/
function makeEditButton(cls, pk, title, options={}) {
return makeIconButton('fa-edit icon-blue', cls, pk, title, options);
}
/*
* Helper function for making a common 'copy' button
*/
function makeCopyButton(cls, pk, title, options={}) {
return makeIconButton('fa-clone', cls, pk, title, options);
}
/*
* Helper function for making a common 'delete' button
*/
function makeDeleteButton(cls, pk, title, options={}) {
return makeIconButton('fa-trash-alt icon-red', cls, pk, title, options);
}
/*
* Helper function for making a common 'remove' button
*/
function makeRemoveButton(cls, pk, title, options={}) {
return makeIconButton('fa-times-circle icon-red', cls, pk, title, options);
}
/*
* Render a progessbar!
*

View File

@ -16,218 +16,16 @@
/* exported
printLabels,
printPartLabels,
printStockItemLabels,
printStockLocationLabels,
*/
/*
* Perform the "print" action.
/**
* Present the user with the available labels,
* and allow them to select which label to print.
*
* The intent is that the available labels have been requested
* (via AJAX) from the server.
*/
function printLabels(url, plugin=null) {
if (plugin) {
// If a plugin is provided, do not redirect the browser.
// Instead, perform an API request and display a message
url = url + `plugin=${plugin}`;
inventreeGet(url, {}, {
success: function(response) {
showMessage(
'{% trans "Labels sent to printer" %}',
{
style: 'success',
}
);
}
});
} else {
window.open(url);
}
}
function printStockItemLabels(items) {
/**
* Print stock item labels for the given stock items
*/
if (items.length == 0) {
showAlertDialog(
'{% trans "Select Stock Items" %}',
'{% trans "Stock item(s) must be selected before printing labels" %}'
);
return;
}
// Request available labels from the server
inventreeGet(
'{% url "api-stockitem-label-list" %}',
{
enabled: true,
items: items,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Labels Found" %}',
'{% trans "No labels found which match selected stock item(s)" %}',
);
return;
}
// Select label to print
selectLabel(
response,
items,
{
success: function(data) {
var pk = data.label;
var href = `/api/label/stock/${pk}/print/?`;
items.forEach(function(item) {
href += `items[]=${item}&`;
});
printLabels(href, data.plugin);
}
}
);
}
}
);
}
function printStockLocationLabels(locations) {
if (locations.length == 0) {
showAlertDialog(
'{% trans "Select Stock Locations" %}',
'{% trans "Stock location(s) must be selected before printing labels" %}'
);
return;
}
// Request available labels from the server
inventreeGet(
'{% url "api-stocklocation-label-list" %}',
{
enabled: true,
locations: locations,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Labels Found" %}',
'{% trans "No labels found which match selected stock location(s)" %}',
);
return;
}
// Select label to print
selectLabel(
response,
locations,
{
success: function(data) {
var pk = data.label;
var href = `/api/label/location/${pk}/print/?`;
locations.forEach(function(location) {
href += `locations[]=${location}&`;
});
printLabels(href, data.plugin);
}
}
);
}
}
);
}
function printPartLabels(parts) {
/**
* Print labels for the provided parts
*/
if (parts.length == 0) {
showAlertDialog(
'{% trans "Select Parts" %}',
'{% trans "Part(s) must be selected before printing labels" %}',
);
return;
}
// Request available labels from the server
inventreeGet(
'{% url "api-part-label-list" %}',
{
enabled: true,
parts: parts,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Labels Found" %}',
'{% trans "No labels found which match the selected part(s)" %}',
);
return;
}
// Select label to print
selectLabel(
response,
parts,
{
success: function(data) {
var pk = data.label;
var href = `/api/label/part/${pk}/print/?`;
parts.forEach(function(part) {
href += `parts[]=${part}&`;
});
printLabels(href, data.plugin);
}
}
);
}
}
);
}
function selectLabel(labels, items, options={}) {
/**
* Present the user with the available labels,
* and allow them to select which label to print.
*
* The intent is that the available labels have been requested
* (via AJAX) from the server.
*/
// Array of available plugins for label printing
var plugins = [];
@ -347,3 +145,71 @@ function selectLabel(labels, items, options={}) {
}
});
}
/*
* Print label(s) for the selected items:
*
* - Retrieve a list of matching label templates from the server
* - Present the available templates to the user (if more than one available)
* - Request printed labels
*
* Required options:
* - url: The list URL for the particular template type
* - items: The list of items to be printed
* - key: The key to use in the query parameters
*/
function printLabels(options) {
if (!options.items || options.items.length == 0) {
showAlertDialog(
'{% trans "Select Items" %}',
'{% trans "No items selected for printing" %}',
);
return;
}
let params = {
enabled: true,
};
params[options.key] = options.items;
// Request a list of available label templates
inventreeGet(options.url, params, {
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Labels Found" %}',
'{% trans "No label templates found which match the selected items" %}',
);
return;
}
// Select label template for printing
selectLabel(response, options.items, {
success: function(data) {
let href = `${options.url}${data.label}/print/?`;
options.items.forEach(function(item) {
href += `${options.key}=${item}&`;
});
if (data.plugin) {
href += `plugin=${data.plugin}`;
inventreeGet(href, {}, {
success: function(response) {
showMessage('{% trans "Labels sent to printer" %}', {
style: 'success',
});
}
});
} else {
window.open(href);
}
}
});
}
});
}

View File

@ -10,11 +10,13 @@
getModelRenderer,
renderBuild,
renderCompany,
renderContact,
renderGroup,
renderManufacturerPart,
renderOwner,
renderPart,
renderPartCategory,
renderReturnOrder,
renderStockItem,
renderStockLocation,
renderSupplierPart,
@ -44,6 +46,8 @@ function getModelRenderer(model) {
switch (model) {
case 'company':
return renderCompany;
case 'contact':
return renderContact;
case 'stockitem':
return renderStockItem;
case 'stocklocation':
@ -58,6 +62,8 @@ function getModelRenderer(model) {
return renderPurchaseOrder;
case 'salesorder':
return renderSalesOrder;
case 'returnorder':
return renderReturnOrder;
case 'salesordershipment':
return renderSalesOrderShipment;
case 'manufacturerpart':
@ -150,6 +156,17 @@ function renderCompany(data, parameters={}) {
}
// Renderer for "Contact" model
function renderContact(data, parameters={}) {
return renderModel(
{
text: data.name,
},
parameters
);
}
// Renderer for "StockItem" model
function renderStockItem(data, parameters={}) {
@ -158,9 +175,6 @@ function renderStockItem(data, parameters={}) {
let render_location_detail = ('render_location_detail' in parameters) ? parameters.render_location_detail : false;
let render_available_quantity = ('render_available_quantity' in parameters) ? parameters.render_available_quantity : false;
if (render_part_detail) {
}
let text = '';
let stock_detail = '';
@ -360,6 +374,26 @@ function renderSalesOrder(data, parameters={}) {
}
// Renderer for "ReturnOrder" model
function renderReturnOrder(data, parameters={}) {
let image = blankImage();
if (data.customer_detail) {
image = data.customer_detail.thumbnail || data.customer_detail.image || blankImage();
}
return renderModel(
{
image: image,
text: `${data.reference} - ${data.customer_detail.name}`,
textSecondary: shortenString(data.description),
url: data.url || `/order/return-order/${data.pk}/`,
},
parameters,
);
}
// Renderer for "SalesOrderShipment" model
function renderSalesOrderShipment(data, parameters={}) {

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@
loadTableFilters,
makeIconBadge,
makeIconButton,
printPartLabels,
renderLink,
setFormGroupVisibility,
setupFilterList,
@ -366,7 +365,7 @@ function createPart(options={}) {
*/
function editPart(pk) {
var url = `/api/part/${pk}/`;
var url = `{% url "api-part-list" %}${pk}/`;
var fields = partFields({
edit: true
@ -397,7 +396,7 @@ function duplicatePart(pk, options={}) {
}
// First we need all the part information
inventreeGet(`/api/part/${pk}/`, {}, {
inventreeGet(`{% url "api-part-list" %}${pk}/`, {}, {
success: function(data) {
@ -446,7 +445,7 @@ function duplicatePart(pk, options={}) {
// Launch form to delete a part
function deletePart(pk, options={}) {
inventreeGet(`/api/part/${pk}/`, {}, {
inventreeGet(`{% url "api-part-list" %}${pk}/`, {}, {
success: function(part) {
if (part.active) {
showAlertDialog(
@ -473,7 +472,7 @@ function deletePart(pk, options={}) {
</div>`;
constructForm(
`/api/part/${pk}/`,
`{% url "api-part-list" %}${pk}/`,
{
method: 'DELETE',
title: '{% trans "Delete Part" %}',
@ -542,7 +541,7 @@ function validateBom(part_id, options={}) {
</div>
`;
constructForm(`/api/part/${part_id}/bom-validate/`, {
constructForm(`{% url "api-part-list" %}${part_id}/bom-validate/`, {
method: 'PUT',
fields: {
valid: {},
@ -560,7 +559,7 @@ function validateBom(part_id, options={}) {
/* Duplicate a BOM */
function duplicateBom(part_id, options={}) {
constructForm(`/api/part/${part_id}/bom-copy/`, {
constructForm(`{% url "api-part-list" %}${part_id}/bom-copy/`, {
method: 'POST',
fields: {
part: {
@ -646,7 +645,7 @@ function partStockLabel(part, options={}) {
var required_build_order_quantity = null;
var required_sales_order_quantity = null;
inventreeGet(`/api/part/${part.pk}/requirements/`, {}, {
inventreeGet(`{% url "api-part-list" %}${part.pk}/requirements/`, {}, {
async: false,
success: function(response) {
required_build_order_quantity = 0;
@ -953,11 +952,7 @@ function loadPartStocktakeTable(partId, options={}) {
params.part = partId;
var filters = loadTableFilters('stocktake');
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('stocktake', params);
setupFilterList('stocktake', $(table), '#filter-list-partstocktake');
@ -1024,19 +1019,17 @@ function loadPartStocktakeTable(partId, options={}) {
switchable: false,
sortable: false,
formatter: function(value, row) {
var html = `<div class='btn-group float-right' role='group'>`;
let html = '';
if (options.allow_edit) {
html += makeIconButton('fa-edit icon-blue', 'button-edit-stocktake', row.pk, '{% trans "Edit Stocktake Entry" %}');
html += makeEditButton('button-edit-stocktake', row.pk, '{% trans "Edit Stocktake Entry" %}');
}
if (options.allow_delete) {
html += makeIconButton('fa-trash-alt icon-red', 'button-delete-stocktake', row.pk, '{% trans "Delete Stocktake Entry" %}');
html += makeDeleteButton('button-delete-stocktake', row.pk, '{% trans "Delete Stocktake Entry" %}');
}
html += `</div>`;
return html;
return wrapButtons(html);
}
}
],
@ -1045,7 +1038,7 @@ function loadPartStocktakeTable(partId, options={}) {
$(table).find('.button-edit-stocktake').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/part/stocktake/${pk}/`, {
constructForm(`{% url "api-part-stocktake-list" %}${pk}/`, {
fields: {
item_count: {},
quantity: {},
@ -1066,21 +1059,17 @@ function loadPartStocktakeTable(partId, options={}) {
},
},
title: '{% trans "Edit Stocktake Entry" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
refreshTable: table,
});
});
$(table).find('.button-delete-stocktake').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/part/stocktake/${pk}/`, {
constructForm(`{% url "api-part-stocktake-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Stocktake Entry" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
refreshTable: table,
});
});
}
@ -1088,7 +1077,8 @@ function loadPartStocktakeTable(partId, options={}) {
}
/* Load part variant table
/*
* Load part variant table
*/
function loadPartVariantTable(table, partId, options={}) {
@ -1097,11 +1087,7 @@ function loadPartVariantTable(table, partId, options={}) {
params.ancestor = partId;
// Load filters
var filters = loadTableFilters('variants');
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('variants', params);
setupFilterList('variants', $(table));
@ -1247,11 +1233,7 @@ function loadPartParameterTable(table, options) {
var params = options.params || {};
// Load filters
var filters = loadTableFilters('part-parameters');
for (var key in params) {
filters[key] = params[key];
}
var filters = loadTableFilters('part-parameters', params);
var filterTarget = options.filterTarget || '#filter-list-parameters';
@ -1302,16 +1284,13 @@ function loadPartParameterTable(table, options) {
switchable: false,
sortable: false,
formatter: function(value, row) {
var pk = row.pk;
let pk = row.pk;
let html = '';
var html = `<div class='btn-group float-right' role='group'>`;
html += makeEditButton('button-parameter-edit', pk, '{% trans "Edit parameter" %}');
html += makeDeleteButton('button-parameter-delete', pk, '{% trans "Delete parameter" %}');
html += makeIconButton('fa-edit icon-blue', 'button-parameter-edit', pk, '{% trans "Edit parameter" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-parameter-delete', pk, '{% trans "Delete parameter" %}');
html += `</div>`;
return html;
return wrapButtons(html);
}
}
],
@ -1320,26 +1299,22 @@ function loadPartParameterTable(table, options) {
$(table).find('.button-parameter-edit').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/part/parameter/${pk}/`, {
constructForm(`{% url "api-part-parameter-list" %}${pk}/`, {
fields: {
data: {},
},
title: '{% trans "Edit Parameter" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
refreshTable: table,
});
});
$(table).find('.button-parameter-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/part/parameter/${pk}/`, {
constructForm(`{% url "api-part-parameter-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Parameter" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
refreshTable: table,
});
});
}
@ -1361,11 +1336,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
options.params.part_detail = true;
options.params.order_detail = true;
var filters = loadTableFilters('purchaseorderlineitem');
for (var key in options.params) {
filters[key] = options.params[key];
}
var filters = loadTableFilters('purchaseorderlineitem', options.params);
setupFilterList('purchaseorderlineitem', $(table), '#filter-list-partpurchaseorders');
@ -1474,12 +1445,16 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
field: 'quantity',
title: '{% trans "Quantity" %}',
formatter: function(value, row) {
var data = value;
let data = value;
if (row.supplier_part_detail.pack_size != 1.0) {
var pack_size = row.supplier_part_detail.pack_size;
var total = value * pack_size;
data += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Pack Quantity" %}: ${pack_size} - {% trans "Total Quantity" %}: ${total}'></span>`;
let pack_size = row.supplier_part_detail.pack_size;
let total = value * pack_size;
data += makeIconBadge(
'fa-info-circle icon-blue',
`{% trans "Pack Quantity" %}: ${pack_size} - {% trans "Total Quantity" %}: ${total}`
);
}
return data;
@ -1515,7 +1490,10 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
}
if (overdue) {
html += `<span class='fas fa-calendar-alt icon-red float-right' title='{% trans "This line item is overdue" %}'></span>`;
html += makeIconBadge(
'fa-calendar-alt icon-red',
'{% trans "This line item is overdue" %}',
);
}
return html;
@ -1557,13 +1535,12 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
// Already recevied
return `<span class='badge bg-success rounded-pill'>{% trans "Received" %}</span>`;
} else if (row.order_detail && row.order_detail.status == {{ PurchaseOrderStatus.PLACED }}) {
var html = `<div class='btn-group' role='group'>`;
let html = '';
var pk = row.pk;
html += makeIconButton('fa-sign-in-alt', 'button-line-receive', pk, '{% trans "Receive line item" %}');
html += `</div>`;
return html;
return wrapButtons(html);
} else {
return '';
}
@ -1627,14 +1604,10 @@ function loadRelatedPartsTable(table, part_id, options={}) {
title: '',
switchable: false,
formatter: function(value, row) {
let html = '';
html += makeDeleteButton('button-related-delete', row.pk, '{% trans "Delete part relationship" %}');
var html = `<div class='btn-group float-right' role='group'>`;
html += makeIconButton('fa-trash-alt icon-red', 'button-related-delete', row.pk, '{% trans "Delete part relationship" %}');
html += '</div>';
return html;
return wrapButtons(html);
}
}
];
@ -1652,12 +1625,10 @@ function loadRelatedPartsTable(table, part_id, options={}) {
$(table).find('.button-related-delete').click(function() {
var pk = $(this).attr('pk');
constructForm(`/api/part/related/${pk}/`, {
constructForm(`{% url "api-part-related-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Part Relationship" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
refreshTable: table,
});
});
},
@ -1833,17 +1804,15 @@ function loadPartTable(table, url, options={}) {
var params = options.params || {};
var filters = {};
var filters = loadTableFilters('parts', options.params);
if (!options.disableFilters) {
filters = loadTableFilters('parts');
}
for (var key in params) {
filters[key] = params[key];
}
setupFilterList('parts', $(table), options.filterTarget, {download: true});
setupFilterList('parts', $(table), options.filterTarget, {
download: true,
labels: {
url: '{% url "api-part-label-list" %}',
key: 'part',
}
});
var columns = [
{
@ -2153,7 +2122,7 @@ function loadPartTable(table, url, options={}) {
var part = parts.shift();
inventreePut(
`/api/part/${part}/`,
`{% url "api-part-list" %}${part}/`,
{
category: category,
},
@ -2176,19 +2145,6 @@ function loadPartTable(table, url, options={}) {
},
});
});
// Callback function for the "print label" button
$('#multi-part-print-label').click(function() {
var selections = getTableData(table);
var items = [];
selections.forEach(function(item) {
items.push(item.pk);
});
printPartLabels(items);
});
}
@ -2201,15 +2157,8 @@ function loadPartCategoryTable(table, options) {
var filterListElement = options.filterList || '#filter-list-category';
var filters = {};
var filterKey = options.filterKey || options.name || 'category';
if (!options.disableFilters) {
filters = loadTableFilters(filterKey);
}
var tree_view = options.allowTreeView && inventreeLoad('category-tree-view') == 1;
if (tree_view) {
@ -2217,12 +2166,7 @@ function loadPartCategoryTable(table, options) {
params.depth = global_settings.INVENTREE_TREE_DEPTH;
}
var original = {};
for (var key in params) {
original[key] = params[key];
filters[key] = params[key];
}
let filters = loadTableFilters(filterKey, params);
setupFilterList(filterKey, table, filterListElement, {download: true});
@ -2270,7 +2214,7 @@ function loadPartCategoryTable(table, options) {
serverSort: !tree_view,
search: !tree_view,
name: 'category',
original: original,
original: params,
showColumns: true,
sortable: true,
buttons: options.allowTreeView ? [
@ -2461,13 +2405,7 @@ function loadPartTestTemplateTable(table, options) {
var filterListElement = options.filterList || '#filter-list-parttests';
var filters = loadTableFilters('parttests');
var original = {};
for (var k in params) {
original[k] = params[k];
}
var filters = loadTableFilters('parttests', params);
setupFilterList('parttests', table, filterListElement);
@ -2484,7 +2422,7 @@ function loadPartTestTemplateTable(table, options) {
url: '{% url "api-part-test-template-list" %}',
queryParams: filters,
name: 'testtemplate',
original: original,
original: params,
columns: [
{
field: 'pk',
@ -2528,14 +2466,12 @@ function loadPartTestTemplateTable(table, options) {
var pk = row.pk;
if (row.part == part) {
var html = `<div class='btn-group float-right' role='group'>`;
let html = '';
html += makeIconButton('fa-edit icon-blue', 'button-test-edit', pk, '{% trans "Edit test result" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
html += makeEditButton('button-test-edit', pk, '{% trans "Edit test result" %}');
html += makeDeleteButton('button-test-delete', pk, '{% trans "Delete test result" %}');
html += `</div>`;
return html;
return wrapButtons(html);
} else {
var text = '{% trans "This test is defined for a parent part" %}';
@ -2568,9 +2504,7 @@ function loadPartTestTemplateTable(table, options) {
constructForm(url, {
method: 'DELETE',
title: '{% trans "Delete Test Result Template" %}',
onSuccess: function() {
table.bootstrapTable('refresh');
},
refreshTable: table,
});
});
}
@ -2593,7 +2527,7 @@ function loadPartSchedulingChart(canvas_id, part_id) {
var was_error = false;
// First, grab updated data for the particular part
inventreeGet(`/api/part/${part_id}/`, {}, {
inventreeGet(`{% url "api-part-list" %}${part_id}/`, {}, {
async: false,
success: function(response) {
part_info = response;
@ -2632,7 +2566,7 @@ function loadPartSchedulingChart(canvas_id, part_id) {
* and arranged in increasing chronological order
*/
inventreeGet(
`/api/part/${part_id}/scheduling/`,
`{% url "api-part-list" %}${part_id}/scheduling/`,
{},
{
async: false,
@ -2649,15 +2583,15 @@ function loadPartSchedulingChart(canvas_id, part_id) {
if (date == null) {
date_string = '<em>{% trans "No date specified" %}</em>';
date_string += `<span class='fas fa-exclamation-circle icon-red float-right' title='{% trans "No date specified" %}'></span>`;
date_string += makeIconBadge('fa-exclamation-circle icon-red', '{% trans "No date specified" %}');
} else if (date < today) {
date_string += `<span class='fas fa-exclamation-circle icon-yellow float-right' title='{% trans "Specified date is in the past" %}'></span>`;
date_string += makeIconBadge('fa-exclamation-circle icon-yellow', '{% trans "Specified date is in the past" %}');
}
var quantity_string = entry.quantity + entry.speculative_quantity;
if (entry.speculative_quantity != 0) {
quantity_string += `<span class='fas fa-question-circle icon-blue float-right' title='{% trans "Speculative" %}'></span>`;
quantity_string += makeIconBadge('fa-question-circle icon-blue', '{% trans "Speculative" %}');
}
// Add an entry to the scheduling table

View File

@ -603,14 +603,14 @@ function loadPriceBreakTable(table, options={}) {
title: '{% trans "Price" %}',
sortable: true,
formatter: function(value, row) {
var html = formatCurrency(value, {currency: row.price_currency});
let html = formatCurrency(value, {currency: row.price_currency});
html += `<div class='btn-group float-right' role='group'>`;
let buttons = '';
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}" %}`);
buttons += makeEditButton(`button-${name}-edit`, row.pk, `{% trans "Edit ${human_name}" %}`);
buttons += makeDeleteButton(`button-${name}-delete`, row.pk, `{% trans "Delete ${human_name}" %}`);
html += `</div>`;
html += wrapButtons(buttons);
return html;
}

File diff suppressed because it is too large Load Diff

View File

@ -14,21 +14,17 @@
*/
/* exported
printBomReports,
printBuildReports,
printPurchaseOrderReports,
printSalesOrderReports,
printTestReports,
printReports,
*/
/**
* Present the user with the available reports,
* and allow them to select which report to print.
*
* The intent is that the available report templates have been requested
* (via AJAX) from the server.
*/
function selectReport(reports, items, options={}) {
/**
* Present the user with the available reports,
* and allow them to select which report to print.
*
* The intent is that the available report templates have been requested
* (via AJAX) from the server.
*/
// If there is only a single report available, just print!
if (reports.length == 1) {
@ -108,270 +104,57 @@ function selectReport(reports, items, options={}) {
}
function printTestReports(items) {
/**
* Print test reports for the provided stock item(s)
*/
/*
* Print report(s) for the selected items:
*
* - Retrieve a list of matching report templates from the server
* - Present the available templates to the user (if more than one available)
* - Request printed document
*
* Required options:
* - url: The list URL for the particular template type
* - items: The list of objects to print
* - key: The key to use in the query parameters
*/
function printReports(options) {
if (items.length == 0) {
if (!options.items || options.items.length == 0) {
showAlertDialog(
'{% trans "Select Stock Items" %}',
'{% trans "Stock item(s) must be selected before printing reports" %}'
'{% trans "Select Items" %}',
'{% trans "No items selected for printing" }',
);
return;
}
// Request available reports from the server
inventreeGet(
'{% url "api-stockitem-testreport-list" %}',
{
enabled: true,
items: items,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match selected stock item(s)" %}',
);
let params = {
enabled: true,
};
return;
}
params[options.key] = options.items;
// Select report template to print
selectReport(
response,
items,
{
success: function(pk) {
var href = `/api/report/test/${pk}/print/?`;
items.forEach(function(item) {
href += `item=${item}&`;
});
window.open(href);
}
}
// Request a list of available report templates
inventreeGet(options.url, params, {
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match the selected items" %}',
);
return;
}
}
);
}
// Select report template for printing
selectReport(response, options.items, {
success: function(pk) {
let href = `${options.url}${pk}/print/?`;
function printBuildReports(builds) {
/**
* Print Build report for the provided build(s)
*/
options.items.forEach(function(item) {
href += `${options.key}=${item}&`;
});
if (builds.length == 0) {
showAlertDialog(
'{% trans "Select Builds" %}',
'{% trans "Build(s) must be selected before printing reports" %}',
);
return;
}
inventreeGet(
'{% url "api-build-report-list" %}',
{
enabled: true,
builds: builds,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match selected build(s)" %}'
);
return;
window.open(href);
}
// Select which report to print
selectReport(
response,
builds,
{
success: function(pk) {
var href = `/api/report/build/${pk}/print/?`;
builds.forEach(function(build) {
href += `build=${build}&`;
});
window.open(href);
}
}
);
}
});
}
);
}
function printBomReports(parts) {
/**
* Print BOM reports for the provided part(s)
*/
if (parts.length == 0) {
showAlertDialog(
'{% trans "Select Parts" %}',
'{% trans "Part(s) must be selected before printing reports" %}'
);
return;
}
// Request available reports from the server
inventreeGet(
'{% url "api-bom-report-list" %}',
{
enabled: true,
parts: parts,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match selected part(s)" %}',
);
return;
}
// Select which report to print
selectReport(
response,
parts,
{
success: function(pk) {
var href = `/api/report/bom/${pk}/print/?`;
parts.forEach(function(part) {
href += `part=${part}&`;
});
window.open(href);
}
}
);
}
}
);
}
function printPurchaseOrderReports(orders) {
/**
* Print PurchaseOrder reports for the provided purchase order(s)
*/
if (orders.length == 0) {
showAlertDialog(
'{% trans "Select Purchase Orders" %}',
'{% trans "Purchase Order(s) must be selected before printing report" %}',
);
return;
}
// Request avaiable report templates
inventreeGet(
'{% url "api-po-report-list" %}',
{
enabled: true,
orders: orders,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match selected orders" %}',
);
return;
}
// Select report template
selectReport(
response,
orders,
{
success: function(pk) {
var href = `/api/report/po/${pk}/print/?`;
orders.forEach(function(order) {
href += `order=${order}&`;
});
window.open(href);
}
}
);
}
}
);
}
function printSalesOrderReports(orders) {
/**
* Print SalesOrder reports for the provided purchase order(s)
*/
if (orders.length == 0) {
showAlertDialog(
'{% trans "Select Sales Orders" %}',
'{% trans "Sales Order(s) must be selected before printing report" %}',
);
return;
}
// Request avaiable report templates
inventreeGet(
'{% url "api-so-report-list" %}',
{
enabled: true,
orders: orders,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match selected orders" %}',
);
return;
}
// Select report template
selectReport(
response,
orders,
{
success: function(pk) {
var href = `/api/report/so/${pk}/print/?`;
orders.forEach(function(order) {
href += `order=${order}&`;
});
window.open(href);
}
}
);
}
}
);
});
}

View File

@ -0,0 +1,743 @@
{% load i18n %}
{% load inventree_extras %}
/* globals
companyFormFields,
constructForm,
imageHoverIcon,
loadTableFilters,
renderLink,
returnOrderStatusDisplay,
setupFilterList,
*/
/* exported
cancelReturnOrder,
completeReturnOrder,
createReturnOrder,
createReturnOrderLineItem,
editReturnOrder,
editReturnOrderLineItem,
issueReturnOrder,
loadReturnOrderTable,
loadReturnOrderLineItemTable,
*/
/*
* Construct a set of fields for a ReturnOrder form
*/
function returnOrderFields(options={}) {
let fields = {
reference: {
icon: 'fa-hashtag',
},
description: {},
customer: {
icon: 'fa-user-tie',
secondary: {
title: '{% trans "Add Customer" %}',
fields: function() {
var fields = companyFormFields();
fields.is_customer.value = true;
return fields;
}
}
},
customer_reference: {},
target_date: {
icon: 'fa-calendar-alt',
},
link: {
icon: 'fa-link',
},
contact: {
icon: 'fa-user',
adjustFilters: function(filters) {
let customer = getFormFieldValue('customer', {}, {modal: options.modal});
if (customer) {
filters.company = customer;
}
return filters;
}
},
responsible: {
icon: 'fa-user',
}
};
return fields;
}
/*
* Create a new Return Order
*/
function createReturnOrder(options={}) {
let fields = returnOrderFields(options);
if (options.customer) {
fields.customer.value = options.customer;
}
constructForm('{% url "api-return-order-list" %}', {
method: 'POST',
fields: fields,
title: '{% trans "Create Return Order" %}',
onSuccess: function(data) {
location.href = `/order/return-order/${data.pk}/`;
},
});
}
/*
* Edit an existing Return Order
*/
function editReturnOrder(order_id, options={}) {
constructForm(`{% url "api-return-order-list" %}${order_id}/`, {
fields: returnOrderFields(options),
title: '{% trans "Edit Return Order" %}',
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/*
* "Issue" a ReturnOrder, to mark it as "in progress"
*/
function issueReturnOrder(order_id, options={}) {
let html = `
<div class='alert alert-block alert-warning'>
{% trans 'After placing this order, line items will no longer be editable.' %}
</div>`;
constructForm(`{% url "api-return-order-list" %}${order_id}/issue/`, {
method: 'POST',
title: '{% trans "Issue Return Order" %}',
confirm: true,
preFormContent: html,
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/*
* Launches a modal form to cancel a ReturnOrder
*/
function cancelReturnOrder(order_id, options={}) {
let html = `
<div class='alert alert-danger alert-block'>
{% trans "Are you sure you wish to cancel this Return Order?" %}
</div>`;
constructForm(
`{% url "api-return-order-list" %}${order_id}/cancel/`,
{
method: 'POST',
title: '{% trans "Cancel Return Order" %}',
confirm: true,
preFormContent: html,
onSuccess: function(response) {
handleFormSuccess(response, options);
}
}
);
}
/*
* Launches a modal form to mark a ReturnOrder as "complete"
*/
function completeReturnOrder(order_id, options={}) {
let html = `
<div class='alert alert-block alert-warning'>
{% trans "Mark this order as complete?" %}
</div>
`;
constructForm(
`{% url "api-return-order-list" %}${order_id}/complete/`,
{
method: 'POST',
title: '{% trans "Complete Return Order" %}',
confirm: true,
preFormContent: html,
onSuccess: function(response) {
handleFormSuccess(response, options);
}
}
);
}
/*
* Load a table of return orders
*/
function loadReturnOrderTable(table, options={}) {
// Ensure the table starts in a known state
$(table).bootstrapTable('destroy');
options.params = options.params || {};
options.params['customer_detail'] = true;
let filters = loadTableFilters('returnorder', options.params);
setupFilterList('returnorder', $(table), '#filter-list-returnorder', {
download: true,
report: {
url: '{% url "api-return-order-report-list" %}',
key: 'order',
}
});
let display_mode = inventreeLoad('returnorder-table-display-mode', 'list');
let is_calendar = display_mode == 'calendar';
$(table).inventreeTable({
url: '{% url "api-return-order-list" %}',
queryParams: filters,
name: 'returnorder',
sidePagination: 'server',
original: options.params,
showColumns: !is_calendar,
search: !is_calendar,
showCustomViewButton: false,
showCustomView: is_calendar,
disablePagination: is_calendar,
formatNoMatches: function() {
return '{% trans "No return orders found" %}';
},
onRefresh: function() {
loadReturnOrderTable(table, options);
},
onLoadSuccess: function() {
// TODO
},
columns: [
{
title: '',
checkbox: true,
visible: true,
switchable: false,
},
{
sortable: true,
field: 'reference',
title: '{% trans "Return Order" %}',
formatter: function(value, row) {
let html = renderLink(value, `/order/return-order/${row.pk}/`);
if (row.overdue) {
html += makeIconBadge('fa-calendar-times icon-red', '{% trans "Order is overdue" %}');
}
return html;
},
},
{
sortable: true,
sortName: 'customer__name',
field: 'customer_detail',
title: '{% trans "Customer" %}',
formatter: function(value, row) {
if (!row.customer_detail) {
return '{% trans "Invalid Customer" %}';
}
return imageHoverIcon(row.customer_detail.image) + renderLink(row.customer_detail.name, `/company/${row.customer}/sales-orders/`);
}
},
{
sortable: true,
field: 'customer_reference',
title: '{% trans "Customer Reference" %}',
},
{
sortable: false,
field: 'description',
title: '{% trans "Description" %}',
},
{
sortable: true,
field: 'status',
title: '{% trans "Status" %}',
formatter: function(value, row) {
return returnOrderStatusDisplay(row.status);
}
},
{
sortable: true,
field: 'creation_date',
title: '{% trans "Creation Date" %}',
formatter: function(value) {
return renderDate(value);
}
},
{
sortable: true,
field: 'target_date',
title: '{% trans "Target Date" %}',
formatter: function(value) {
return renderDate(value);
}
},
{
field: 'line_items',
title: '{% trans "Items" %}',
sortable: true,
},
{
field: 'responsible',
title: '{% trans "Responsible" %}',
switchable: true,
sortable: true,
formatter: function(value, row) {
if (!row.responsible_detail) {
return '-';
}
let html = row.responsible_detail.name;
if (row.responsible_detail.label == 'group') {
html += `<span class='float-right fas fa-users'></span>`;
} else {
html += `<span class='float-right fas fa-user'></span>`;
}
return html;
}
},
{
// TODO: Add in the 'total cost' field
field: 'total_price',
title: '{% trans "Total Cost" %}',
switchable: true,
sortable: true,
visible: false,
formatter: function(value, row) {
return formatCurrency(value, {
currency: row.total_price_currency
});
}
}
]
});
}
/*
* Construct a set of fields for a ReturnOrderLineItem form
*/
function returnOrderLineItemFields(options={}) {
let fields = {
order: {
filters: {
customer_detail: true,
}
},
item: {
filters: {
part_detail: true,
serialized: true,
}
},
reference: {},
outcome: {
icon: 'fa-route',
},
price: {
icon: 'fa-dollar-sign',
},
price_currency: {
icon: 'fa-coins',
},
target_date: {
icon: 'fa-calendar-alt',
},
notes: {
icon: 'fa-sticky-note',
}
};
return fields;
}
/*
* Create a new ReturnOrderLineItem
*/
function createReturnOrderLineItem(options={}) {
let fields = returnOrderLineItemFields();
if (options.order) {
fields.order.value = options.order;
fields.order.hidden = true;
}
if (options.customer) {
Object.assign(fields.item.filters, {
customer: options.customer
});
}
constructForm('{% url "api-return-order-line-list" %}', {
fields: fields,
method: 'POST',
title: '{% trans "Add Line Item" %}',
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/*
* Edit an existing ReturnOrderLineItem
*/
function editReturnOrderLineItem(pk, options={}) {
let fields = returnOrderLineItemFields();
constructForm(`{% url "api-return-order-line-list" %}${pk}/`, {
fields: fields,
title: '{% trans "Edit Line Item" %}',
onSuccess: function(response) {
handleFormSuccess(response, options);
}
});
}
/*
* Receive one or more items against a ReturnOrder
*/
function receiveReturnOrderItems(order_id, line_items, options={}) {
if (line_items.length == 0) {
showAlertDialog(
'{% trans "Select Line Items"% }',
'{% trans "At least one line item must be selected" %}'
);
return;
}
function renderLineItem(line_item) {
let pk = line_item.pk;
// Render thumbnail + description
let thumb = thumbnailImage(line_item.part_detail.thumbnail);
let buttons = '';
if (line_items.length > 1) {
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
}
buttons = wrapButtons(buttons);
let html = `
<tr id='receive_row_${pk}' class='stock-receive-row'>
<td id='part_${pk}'>
${thumb} ${line_item.part_detail.full_name}
</td>
<td id='item_${pk}'>
${line_item.item_detail.serial}
</td>
<td id='actions_${pk}'>${buttons}</td>
</tr>`;
return html;
}
let table_entries = '';
line_items.forEach(function(item) {
if (!item.received_date) {
table_entries += renderLineItem(item);
}
});
let html = '';
html += `
<table class='table table-striped table-condensed' id='order-receive-table'>
<thead>
<tr>
<th>{% trans "Part" %}</th>
<th>{% trans "Serial Number" %}</th>
</tr>
</thead>
<tbody>${table_entries}</tbody>
</table>`;
constructForm(`{% url "api-return-order-list" %}${order_id}/receive/`, {
method: 'POST',
preFormContent: html,
fields: {
location: {
filters: {
strucutral: false,
}
}
},
confirm: true,
confirmMessage: '{% trans "Confirm receipt of items" %}',
title: '{% trans "Receive Return Order Items" %}',
afterRender: function(fields, opts) {
// Add callback to remove rows
$(opts.modal).find('.button-row-remove').click(function() {
let pk = $(this).attr('pk');
$(opts.modal).find(`#receive_row_${pk}`).remove();
});
},
onSubmit: function(fields, opts) {
// Extract data elements from the form
let data = {
items: [],
location: getFormFieldValue('location', {}, opts),
};
let item_pk_values = [];
line_items.forEach(function(item) {
let pk = item.pk;
let row = $(opts.modal).find(`#receive_row_${pk}`);
if (row.exists()) {
data.items.push({
item: pk,
});
item_pk_values.push(pk);
}
});
opts.nested = {
'items': item_pk_values,
};
inventreePut(
opts.url,
data,
{
method: 'POST',
success: function(response) {
$(opts.modal).modal('hide');
handleFormSuccess(response, options);
},
error: function(xhr) {
switch (xhr.status) {
case 400:
handleFormErrors(xhr.responseJSON, fields, opts);
break;
default:
$(opts.modal).modal('hide');
showApiError(xhr, opts.url);
break;
}
}
}
);
}
});
}
/*
* Load a table displaying line items for a particular ReturnOrder
*/
function loadReturnOrderLineItemTable(options={}) {
var table = options.table;
options.params = options.params || {};
options.params.order = options.order;
options.params.item_detail = true;
options.params.order_detail = false;
options.params.part_detail = true;
let filters = loadTableFilters('returnorderlineitem', options.params);
setupFilterList('returnorderlineitem', $(table), '#filter-list-returnorderlines', {download: true});
function setupCallbacks() {
if (options.allow_edit) {
// Callback for "receive" button
if (options.allow_receive) {
$(table).find('.button-line-receive').click(function() {
let pk = $(this).attr('pk');
let line = $(table).bootstrapTable('getRowByUniqueId', pk);
receiveReturnOrderItems(
options.order,
[line],
{
onSuccess: function(response) {
reloadBootstrapTable(table);
}
}
);
});
}
// Callback for "edit" button
$(table).find('.button-line-edit').click(function() {
let pk = $(this).attr('pk');
constructForm(`{% url "api-return-order-line-list" %}${pk}/`, {
fields: returnOrderLineItemFields(),
title: '{% trans "Edit Line Item" %}',
refreshTable: table,
});
});
}
if (options.allow_delete) {
// Callback for "delete" button
$(table).find('.button-line-delete').click(function() {
let pk = $(this).attr('pk');
constructForm(`{% url "api-return-order-line-list" %}${pk}/`, {
method: 'DELETE',
title: '{% trans "Delete Line Item" %}',
refreshTable: table,
});
});
}
}
$(table).inventreeTable({
url: '{% url "api-return-order-line-list" %}',
name: 'returnorderlineitems',
formatNoMatches: function() {
return '{% trans "No matching line items" %}';
},
onPostBody: setupCallbacks,
queryParams: filters,
original: options.params,
showColumns: true,
showFooter: true,
uniqueId: 'pk',
columns: [
{
checkbox: true,
switchable: false,
},
{
field: 'part',
sortable: true,
switchable: false,
title: '{% trans "Part" %}',
formatter: function(value, row) {
let part = row.part_detail;
let html = thumbnailImage(part.thumbnail) + ' ';
html += renderLink(part.full_name, `/part/${part.pk}/`);
return html;
}
},
{
field: 'item',
sortable: true,
switchable: false,
title: '{% trans "Item" %}',
formatter: function(value, row) {
return renderLink(`{% trans "Serial Number" %}: ${row.item_detail.serial}`, `/stock/item/${row.item}/`);
}
},
{
field: 'reference',
title: '{% trans "Reference" %}',
},
{
field: 'outcome',
title: '{% trans "Outcome" %}',
sortable: true,
formatter: function(value, row) {
return returnOrderLineItemStatusDisplay(value);
}
},
{
field: 'price',
title: '{% trans "Price" %}',
formatter: function(value, row) {
return formatCurrency(row.price, {
currency: row.price_currency,
});
}
},
{
sortable: true,
field: 'target_date',
title: '{% trans "Target Date" %}',
formatter: function(value, row) {
let html = renderDate(value);
if (row.overdue) {
html += makeIconBadge('fa-calendar-times icon-red', '{% trans "This line item is overdue" %}');
}
return html;
}
},
{
field: 'received_date',
title: '{% trans "Received" %}',
sortable: true,
formatter: function(value) {
if (!value) {
yesNoLabel(value);
} else {
return renderDate(value);
}
}
},
{
field: 'notes',
title: '{% trans "Notes" %}',
},
{
field: 'buttons',
title: '',
switchable: false,
formatter: function(value, row) {
let buttons = '';
let pk = row.pk;
if (options.allow_edit) {
if (options.allow_receive && !row.received_date) {
buttons += makeIconButton('fa-sign-in-alt icon-green', 'button-line-receive', pk, '{% trans "Mark item as received" %}');
}
buttons += makeEditButton('button-line-edit', pk, '{% trans "Edit line item" %}');
}
if (options.allow_delete) {
buttons += makeDeleteButton('button-line-delete', pk, '{% trans "Delete line item" %}');
}
return wrapButtons(buttons);
}
}
]
});
}

File diff suppressed because it is too large Load Diff

View File

@ -222,7 +222,7 @@ function updateSearch() {
if (checkPermission('purchase_order') && user_settings.SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS) {
var filters = {
let filters = {
supplier_detail: true,
};
@ -235,7 +235,7 @@ function updateSearch() {
if (checkPermission('sales_order') && user_settings.SEARCH_PREVIEW_SHOW_SALES_ORDERS) {
var filters = {
let filters = {
customer_detail: true,
};
@ -247,6 +247,19 @@ function updateSearch() {
addSearchQuery('salesorder', '{% trans "Sales Orders" %}', filters);
}
if (checkPermission('return_order') && user_settings.SEARCH_PREVIEW_SHOW_RETURN_ORDERS) {
let filters = {
customer_detail: true,
};
// Hide inactive (not "outstanding" orders)
if (user_settings.SEARCH_PREVIEW_EXCLUDE_INACTIVE_RETURN_ORDERS) {
filters.outstanding = true;
}
addSearchQuery('returnorder', '{% trans "Return Orders" %}', filters);
}
let ctx = $('#offcanvas-search').find('#search-context');
ctx.html(`

View File

@ -0,0 +1,24 @@
{% load i18n %}
{% load status_codes %}
{% load inventree_extras %}
/* globals
*/
/* exported
buildStatusDisplay,
purchaseOrderStatusDisplay,
returnOrderStatusDisplay,
returnOrderLineItemStatusDisplay,
salesOrderStatusDisplay,
stockHistoryStatusDisplay,
stockStatusDisplay,
*/
{% include "status_codes.html" with label='stock' options=StockStatus.list %}
{% include "status_codes.html" with label='stockHistory' options=StockHistoryCode.list %}
{% include "status_codes.html" with label='build' options=BuildStatus.list %}
{% include "status_codes.html" with label='purchaseOrder' options=PurchaseOrderStatus.list %}
{% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %}
{% include "status_codes.html" with label='returnOrder' options=ReturnOrderStatus.list %}
{% include "status_codes.html" with label='returnOrderLineItem' options=ReturnOrderLineStatus.list %}

View File

@ -24,8 +24,6 @@
modalSetTitle,
modalSubmit,
openModal,
printStockItemLabels,
printTestReports,
renderLink,
scanItemsIntoLocation,
showAlertDialog,
@ -88,7 +86,7 @@ function serializeStockItem(pk, options={}) {
if (options.part) {
// Work out the next available serial number
inventreeGet(`/api/part/${options.part}/serial-numbers/`, {}, {
inventreeGet(`{% url "api-part-list" %}${options.part}/serial-numbers/`, {}, {
success: function(data) {
if (data.next) {
options.fields.serial_numbers.placeholder = `{% trans "Next available serial number" %}: ${data.next}`;
@ -230,7 +228,7 @@ function stockItemFields(options={}) {
enableFormInput('serial_numbers', opts);
// Request part serial number information from the server
inventreeGet(`/api/part/${data.pk}/serial-numbers/`, {}, {
inventreeGet(`{% url "api-part-list" %}${data.pk}/serial-numbers/`, {}, {
success: function(data) {
var placeholder = '';
if (data.next) {
@ -379,7 +377,7 @@ function duplicateStockItem(pk, options) {
}
// First, we need the StockItem information
inventreeGet(`/api/stock/${pk}/`, {}, {
inventreeGet(`{% url "api-stock-list" %}${pk}/`, {}, {
success: function(data) {
// Do not duplicate the serial number
@ -656,8 +654,7 @@ function assignStockToCustomer(items, options={}) {
var buttons = `<div class='btn-group' role='group'>`;
buttons += makeIconButton(
'fa-times icon-red',
buttons += makeRemoveButton(
'button-stock-item-remove',
pk,
'{% trans "Remove row" %}',
@ -824,13 +821,13 @@ function mergeStockItems(items, options={}) {
quantity += stockStatusDisplay(item.status, {classes: 'float-right'});
var buttons = `<div class='btn-group' role='group'>`;
buttons += makeIconButton(
'fa-times icon-red',
'button-stock-item-remove',
pk,
'{% trans "Remove row" %}',
let buttons = wrapButtons(
makeIconButton(
'fa-times icon-red',
'button-stock-item-remove',
pk,
'{% trans "Remove row" %}',
)
);
html += `
@ -1094,16 +1091,11 @@ function adjustStock(action, items, options={}) {
);
}
var buttons = `<div class='btn-group float-right' role='group'>`;
buttons += makeIconButton(
'fa-times icon-red',
let buttons = wrapButtons(makeRemoveButton(
'button-stock-item-remove',
pk,
'{% trans "Remove stock item" %}',
);
buttons += `</div>`;
));
html += `
<tr id='stock_item_${pk}' class='stock-item-row'>
@ -1341,18 +1333,11 @@ function loadStockTestResultsTable(table, options) {
var filterKey = options.filterKey || options.name || 'stocktests';
var filters = loadTableFilters(filterKey);
var params = {
let params = {
part: options.part,
};
var original = {};
for (var k in params) {
original[k] = params[k];
filters[k] = params[k];
}
var filters = loadTableFilters(filterKey, params);
setupFilterList(filterKey, table, filterTarget);
@ -1360,7 +1345,7 @@ function loadStockTestResultsTable(table, options) {
// Helper function for rendering buttons
var html = `<div class='btn-group float-right' role='group'>`;
let html = '';
if (row.requires_attachment == false && row.requires_value == false && !row.result) {
// Enable a "quick tick" option for this test result
@ -1371,13 +1356,11 @@ function loadStockTestResultsTable(table, options) {
if (!grouped && row.result != null) {
var pk = row.pk;
html += makeIconButton('fa-edit icon-blue', 'button-test-edit', pk, '{% trans "Edit test result" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
html += makeEditButton('button-test-edit', pk, '{% trans "Edit test result" %}');
html += makeDeleteButton('button-test-delete', pk, '{% trans "Delete test result" %}');
}
html += '</div>';
return html;
return wrapButtons(html);
}
var parent_node = 'parent node';
@ -1396,7 +1379,7 @@ function loadStockTestResultsTable(table, options) {
return '{% trans "No test results found" %}';
},
queryParams: filters,
original: original,
original: params,
onPostBody: function() {
table.treegrid({
treeColumn: 0,
@ -1444,7 +1427,7 @@ function loadStockTestResultsTable(table, options) {
var html = value;
if (row.attachment) {
var text = `<span class='fas fa-file-alt float-right'></span>`;
let text = makeIconBadge('fa-file-alt', '');
html += renderLink(text, row.attachment, {download: true});
}
@ -1700,65 +1683,50 @@ function locationDetail(row, showLink=true) {
}
/* Load data into a stock table with adjustable options.
* Fetches data (via AJAX) and loads into a bootstrap table.
* Also links in default button callbacks.
*
* Options:
* url - URL for the stock query
* params - query params for augmenting stock data request
* buttons - Which buttons to link to stock selection callbacks
* filterList - <ul> element where filters are displayed
* disableFilters: If true, disable custom filters
*/
function loadStockTable(table, options) {
/* Load data into a stock table with adjustable options.
* Fetches data (via AJAX) and loads into a bootstrap table.
* Also links in default button callbacks.
*
* Options:
* url - URL for the stock query
* params - query params for augmenting stock data request
* groupByField - Column for grouping stock items
* buttons - Which buttons to link to stock selection callbacks
* filterList - <ul> element where filters are displayed
* disableFilters: If true, disable custom filters
*/
// List of user-params which override the default filters
options.params['location_detail'] = true;
options.params['part_detail'] = true;
var params = options.params || {};
var filterTarget = options.filterTarget || '#filter-list-stock';
const filterTarget = options.filterTarget || '#filter-list-stock';
var filters = {};
const filterKey = options.filterKey || options.name || 'stock';
var filterKey = options.filterKey || options.name || 'stock';
let filters = loadTableFilters(filterKey, params);
if (!options.disableFilters) {
filters = loadTableFilters(filterKey);
}
var original = {};
for (var k in params) {
original[k] = params[k];
}
setupFilterList(filterKey, table, filterTarget, {download: true});
setupFilterList(filterKey, table, filterTarget, {
download: true,
report: {
url: '{% url "api-stockitem-testreport-list" %}',
key: 'item',
},
labels: {
url: '{% url "api-stockitem-label-list" %}',
key: 'item',
}
});
// Override the default values, or add new ones
for (var key in params) {
filters[key] = params[key];
}
var grouping = true;
if ('grouping' in options) {
grouping = options.grouping;
}
var col = null;
// Explicitly disable part grouping functionality
// Might be able to add this in later on,
// but there is a bug which makes this crash if paginating on the server side.
// Ref: https://github.com/wenzhixin/bootstrap-table/issues/3250
// eslint-disable-next-line no-unused-vars
grouping = false;
var columns = [
{
checkbox: true,
@ -2175,14 +2143,13 @@ function loadStockTable(table, options) {
queryParams: filters,
sidePagination: 'server',
name: 'stock',
original: original,
original: params,
showColumns: true,
showFooter: true,
columns: columns,
});
var buttons = [
'#stock-print-options',
'#stock-options',
];
@ -2206,31 +2173,6 @@ function loadStockTable(table, options) {
}
// Automatically link button callbacks
$('#multi-item-print-label').click(function() {
var selections = getTableData(table);
var items = [];
selections.forEach(function(item) {
items.push(item.pk);
});
printStockItemLabels(items);
});
$('#multi-item-print-test-report').click(function() {
var selections = getTableData(table);
var items = [];
selections.forEach(function(item) {
items.push(item.pk);
});
printTestReports(items);
});
if (global_settings.BARCODE_ENABLE) {
$('#multi-item-barcode-scan-into-location').click(function() {
var selections = getTableData(table);
@ -2420,21 +2362,17 @@ function loadStockLocationTable(table, options) {
params.depth = global_settings.INVENTREE_TREE_DEPTH;
}
var filters = {};
var filterKey = options.filterKey || options.name || 'location';
if (!options.disableFilters) {
filters = loadTableFilters(filterKey);
}
let filters = loadTableFilters(filterKey, params);
var original = {};
for (var k in params) {
original[k] = params[k];
}
setupFilterList(filterKey, table, filterListElement, {download: true});
setupFilterList(filterKey, table, filterListElement, {
download: true,
labels: {
url: '{% url "api-stocklocation-label-list" %}',
key: 'location'
}
});
for (var key in params) {
filters[key] = params[key];
@ -2484,7 +2422,7 @@ function loadStockLocationTable(table, options) {
url: options.url || '{% url "api-location-list" %}',
queryParams: filters,
name: 'location',
original: original,
original: params,
sortable: true,
showColumns: true,
onPostBody: function() {
@ -2654,26 +2592,20 @@ function loadStockLocationTable(table, options) {
});
}
/*
* Load stock history / tracking table for a given StockItem
*/
function loadStockTrackingTable(table, options) {
var cols = [];
var filterTarget = '#filter-list-stocktracking';
const filterKey = 'stocktracking';
var filterKey = 'stocktracking';
let params = options.params || {};
var filters = loadTableFilters(filterKey);
let filters = loadTableFilters(filterKey, params);
var params = options.params;
var original = {};
for (var k in params) {
original[k] = params[k];
filters[k] = params[k];
}
setupFilterList(filterKey, table, filterTarget);
setupFilterList(filterKey, table, '#filter-list-stocktracking');
// Date
cols.push({
@ -2747,10 +2679,10 @@ function loadStockTrackingTable(table, options) {
html += '</td></tr>';
}
// Purchase Order Information
// PurchaseOrder Information
if (details.purchaseorder) {
html += `<tr><th>{% trans "Purchase Order" %}</td>`;
html += `<tr><th>{% trans "Purchase Order" %}</th>`;
html += '<td>';
@ -2766,6 +2698,40 @@ function loadStockTrackingTable(table, options) {
html += '</td></tr>';
}
// SalesOrder information
if (details.salesorder) {
html += `<tr><th>{% trans "Sales Order" %}</th>`;
html += '<td>';
if (details.salesorder_detail) {
html += renderLink(
details.salesorder_detail.reference,
`/order/sales-order/${details.salesorder}`
);
} else {
html += `<em>{% trans "Sales Order no longer exists" %}</em>`;
}
html += `</td></tr>`;
}
// ReturnOrder information
if (details.returnorder) {
html += `<tr><th>{% trans "Return Order" %}</th>`;
html += '<td>';
if (details.returnorder_detail) {
html += renderLink(
details.returnorder_detail.reference,
`/order/return-order/${details.returnorder}/`
);
} else {
html += `<em>{% trans "Return Order no longer exists" %}</em>`;
}
html += `</td></tr>`;
}
// Customer information
if (details.customer) {
@ -2808,12 +2774,7 @@ function loadStockTrackingTable(table, options) {
html += `<tr><th>{% trans "Status" %}</td>`;
html += '<td>';
html += stockStatusDisplay(
details.status,
{
classes: 'float-right',
}
);
html += stockStatusDisplay(details.status);
html += '</td></tr>';
}
@ -2865,7 +2826,7 @@ function loadStockTrackingTable(table, options) {
table.inventreeTable({
method: 'get',
queryParams: filters,
original: original,
original: params,
columns: cols,
url: options.url,
});
@ -2951,14 +2912,12 @@ function loadInstalledInTable(table, options) {
title: '',
switchable: false,
formatter: function(value, row) {
var pk = row.pk;
var html = '';
let pk = row.pk;
let html = '';
html += `<div class='btn-group float-right' role='group'>`;
html += makeIconButton('fa-unlink', 'button-uninstall', pk, '{% trans "Uninstall Stock Item" %}');
html += `</div>`;
return html;
return wrapButtons(html);
}
}
],

View File

@ -2,23 +2,15 @@
{% load status_codes %}
{% load inventree_extras %}
{% include "status_codes.html" with label='stock' options=StockStatus.list %}
{% include "status_codes.html" with label='stockHistory' options=StockHistoryCode.list %}
{% include "status_codes.html" with label='build' options=BuildStatus.list %}
{% include "status_codes.html" with label='purchaseOrder' options=PurchaseOrderStatus.list %}
{% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %}
/* globals
global_settings
purchaseOrderCodes,
returnOrderCodes,
salesOrderCodes,
*/
/* exported
buildStatusDisplay,
getAvailableTableFilters,
purchaseOrderStatusDisplay,
salesOrderStatusDisplay,
stockHistoryStatusDisplay,
stockStatusDisplay,
*/
@ -26,6 +18,42 @@ function getAvailableTableFilters(tableKey) {
tableKey = tableKey.toLowerCase();
// Filters for "returnorder" table
if (tableKey == 'returnorder') {
return {
status: {
title: '{% trans "Order status" %}',
options: returnOrderCodes
},
outstanding: {
type: 'bool',
title: '{% trans "Outstanding" %}',
},
overdue: {
type: 'bool',
title: '{% trans "Overdue" %}',
},
assigned_to_me: {
type: 'bool',
title: '{% trans "Assigned to me" %}',
},
};
}
// Filters for "returnorderlineitem" table
if (tableKey == 'returnorderlineitem') {
return {
received: {
type: 'bool',
title: '{% trans "Received" %}',
},
outcome: {
title: '{% trans "Outcome" %}',
options: returnOrderLineItemCodes,
}
};
}
// Filters for "variant" table
if (tableKey == 'variants') {
return {
@ -363,7 +391,7 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Responsible" %}',
options: function() {
var ownersList = {};
inventreeGet(`/api/user/owner/`, {}, {
inventreeGet('{% url "api-owner-list" %}', {}, {
async: false,
success: function(response) {
for (key in response) {
@ -445,6 +473,10 @@ function getAvailableTableFilters(tableKey) {
type: 'bool',
title: '{% trans "Overdue" %}',
},
assigned_to_me: {
type: 'bool',
title: '{% trans "Assigned to me" %}',
},
};
}

View File

@ -9,7 +9,7 @@
customGroupSorter,
downloadTableData,
getTableData,
reloadtable,
reloadBootstrapTable,
renderLink,
reloadTableFilters,
constructExpandCollapseButtons,
@ -20,8 +20,23 @@
* Reload a named table
* @param table
*/
function reloadtable(table) {
$(table).bootstrapTable('refresh');
function reloadBootstrapTable(table) {
let tbl = table;
if (tbl) {
if (typeof tbl === 'string' || tbl instanceof String) {
tbl = $(tbl);
}
if (tbl.exists()) {
tbl.bootstrapTable('refresh');
} else {
console.error(`Invalid table name passed to reloadTable(): ${table}`);
}
} else {
console.error(`Null value passed to reloadTable()`);
}
}
@ -127,7 +142,7 @@ function constructExpandCollapseButtons(table, idx=0) {
*/
function getTableData(table, allowEmpty=false) {
var data = $(table).bootstrapTable('getSelections');
let data = $(table).bootstrapTable('getSelections');
if (data.length == 0 && !allowEmpty) {
data = $(table).bootstrapTable('getData');

View File

@ -47,18 +47,23 @@
<ul class='dropdown-menu' aria-labelledby="buyMenuDropdown">
<li><a class='dropdown-item' href="{% url 'supplier-index' %}"><span class='fas fa-building icon-header'></span>{% trans "Suppliers" %}</a></li>
<li><a class='dropdown-item' href="{% url 'manufacturer-index' %}"><span class='fas fa-industry icon-header'></span>{% trans "Manufacturers" %}</a></li>
<li><a class='dropdown-item' href="{% url 'po-index' %}"><span class='fas fa-list icon-header'></span>{% trans "Purchase Orders" %}</a></li>
<li><a class='dropdown-item' href="{% url 'purchase-order-index' %}"><span class='fas fa-list icon-header'></span>{% trans "Purchase Orders" %}</a></li>
</ul>
</li>
{% endif %}
{% if roles.sales_order.view %}
{% if roles.sales_order.view or roles.return_order.view %}
<li class='nav-item dropdown'>
<a class='nav-link dropdown-toggle' href='#' id='sellMenuDropdown' role='button' data-bs-toggle='dropdown'>
<span class='fas fa-truck icon-header'></span>{% trans "Sell" %}
</a>
<ul class='dropdown-menu'>
<li><a class='dropdown-item' href="{% url 'customer-index' %}"><span class='fas fa-user-tie icon-header'></span>{% trans "Customers" %}</a>
<li><a class='dropdown-item' href="{% url 'so-index' %}"><span class='fas fa-list icon-header'></span>{% trans "Sales Orders" %}</a></li>
{% if roles.sales_order.view %}
<li><a class='dropdown-item' href="{% url 'sales-order-index' %}"><span class='fas fa-list icon-header'></span>{% trans "Sales Orders" %}</a></li>
{% endif %}
{% if roles.return_order.view and return_order_enabled %}
<li><a class='dropdown-item' href="{% url 'return-order-index' %}"><span class='fas fa-undo icon-header'></span>{% trans "Return Orders" %}</a></li>
{% endif %}
</ul>
</li>
{% endif %}

View File

@ -22,18 +22,6 @@
</ul>
</div>
{% endif %}
<!-- Printing actions menu -->
<div class='btn-group'>
<button id='stock-print-options' class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle="dropdown" title='{% trans "Printing Actions" %}'>
<span class='fas fa-print'></span> <span class='caret'></span>
</button>
<ul class='dropdown-menu'>
<li><a class='dropdown-item' href='#' id='multi-item-print-label' title='{% trans "Print labels" %}'><span class='fas fa-tags'></span> {% trans "Print labels" %}</a></li>
{% if test_report_enabled %}
<li><a class='dropdown-item' href='#' id='multi-item-print-test-report' title='{% trans "Print test reports" %}'><span class='fas fa-file-pdf'></span> {% trans "Print test reports" %}</a></li>
{% endif %}
</ul>
</div>
{% if not read_only %}
{% if roles.stock.change or roles.stock.delete %}
<div class="btn-group" role="group">