mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Table custom buttons (#5075)
* Add generic implementation for barcode actions - Commonize code against tables - Cleaner UI - Better code - Will make future react refactor easier * Add permissions.js - Separate .js file for dynamically checking permissions * Update stock table to use client-side actions * API endpoint for bulk category adjustment * Bug fix for purchase_order.js - Prevent some really strange API calls * Refactor actions for part table - Now done dynamically * Refactor actions for the attachment tables * Refactor actions for build output table * Increment API version * Cleanup janky button * Refactor supplier part table * Refactor manufacturer part table * Remove linkButtonsToSelection - no longer needed - Cleanup, yay! * Cleanup purchase order line table * Refactor BOM table buttons * JS linting * Further cleanup * Template cleanup - remove extra div elements * js linting * js fix
This commit is contained in:
		@@ -2,11 +2,14 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# InvenTree API version
 | 
			
		||||
INVENTREE_API_VERSION = 125
 | 
			
		||||
INVENTREE_API_VERSION = 126
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
 | 
			
		||||
 | 
			
		||||
v126 -> 2023-06-19 : https://github.com/inventree/InvenTree/pull/5075
 | 
			
		||||
    - Adds API endpoint for setting the "category" for multiple parts simultaneously
 | 
			
		||||
 | 
			
		||||
v125 -> 2023-06-17 : https://github.com/inventree/InvenTree/pull/5064
 | 
			
		||||
    - Adds API endpoint for setting the "status" field for multiple stock items simultaneously
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -269,10 +269,6 @@ main {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Styles for table buttons and filtering */
 | 
			
		||||
.button-toolbar .btn {
 | 
			
		||||
    margin-left: 1px;
 | 
			
		||||
    margin-right: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.filter-list {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
 
 | 
			
		||||
@@ -95,6 +95,7 @@ notifications_urls = [
 | 
			
		||||
dynamic_javascript_urls = [
 | 
			
		||||
    re_path(r'^calendar.js', DynamicJsView.as_view(template_name='js/dynamic/calendar.js'), name='calendar.js'),
 | 
			
		||||
    re_path(r'^nav.js', DynamicJsView.as_view(template_name='js/dynamic/nav.js'), name='nav.js'),
 | 
			
		||||
    re_path(r'^permissions.js', DynamicJsView.as_view(template_name='js/dynamic/permissions.js'), name='permissions.js'),
 | 
			
		||||
    re_path(r'^settings.js', DynamicJsView.as_view(template_name='js/dynamic/settings.js'), name='settings.js'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -165,9 +165,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='child-button-toolbar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid float-right'>
 | 
			
		||||
                {% include "filter_list.html" with id='sub-build' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id='sub-build' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='sub-build-table' data-toolbar='#child-button-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -199,26 +197,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        {% if build.active %}
 | 
			
		||||
        {% if build.is_fully_allocated %}
 | 
			
		||||
        <div class='alert alert-block alert-success'>
 | 
			
		||||
            {% trans "Untracked stock has been fully allocated for this Build Order" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% else %}
 | 
			
		||||
        <div class='alert alert-block alert-danger'>
 | 
			
		||||
            {% trans "Untracked stock has not been fully allocated for this Build Order" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <div id='build-lines-toolbar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
                <div class='btn-group'>
 | 
			
		||||
                    <button id='allocate-selected-items' class='btn btn-success' title='{% trans "Allocate selected items" %}'>
 | 
			
		||||
                        <span class='fas fa-sign-in-alt'></span>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% include "filter_list.html" with id='buildlines' %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id='buildlines' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='build-lines-table' data-toolbar='#build-lines-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -240,37 +220,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='build-output-toolbar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid'>
 | 
			
		||||
                {% if build.active %}
 | 
			
		||||
                <div class='btn-group'>
 | 
			
		||||
 | 
			
		||||
                    <!-- Build output actions -->
 | 
			
		||||
                    <div class='btn-group'>
 | 
			
		||||
                        <button id='output-options' class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle='dropdown' title='{% trans "Output Actions" %}'>
 | 
			
		||||
                            <span class='fas fa-tools'></span> <span class='caret'></span>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <ul class='dropdown-menu'>
 | 
			
		||||
                            {% if roles.build.add %}
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='multi-output-complete' title='{% trans "Complete selected build outputs" %}'>
 | 
			
		||||
                                <span class='fas fa-check-circle icon-green'></span> {% trans "Complete outputs" %}
 | 
			
		||||
                            </a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if roles.build.change %}
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='multi-output-scrap' title='{% trans "Scrap selected build outputs" %}'>
 | 
			
		||||
                                <span class='fas fa-times-circle icon-red'></span> {% trans "Scrap outputs" %}
 | 
			
		||||
                            </a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if roles.build.delete %}
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='multi-output-delete' title='{% trans "Delete selected build outputs" %}'>
 | 
			
		||||
                                <span class='fas fa-trash-alt icon-red'></span> {% trans "Delete outputs" %}
 | 
			
		||||
                            </a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% include "filter_list.html" with id='incompletebuilditems' %}
 | 
			
		||||
                </div>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id='incompletebuilditems' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='build-output-table' data-toolbar='#build-output-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -501,10 +451,6 @@ $('#btn-unallocate').on('click', function() {
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$('#allocate-selected-items').click(function() {
 | 
			
		||||
    allocateSelectedLines();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$("#btn-allocate").on('click', function() {
 | 
			
		||||
    allocateSelectedLines();
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,7 @@
 | 
			
		||||
 | 
			
		||||
<div class='panel-content'>
 | 
			
		||||
    <div id='button-toolbar'>
 | 
			
		||||
        <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="build" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% include "filter_list.html" with id="build" %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <table class='table table-striped table-condensed' id='build-table' data-toolbar='#button-toolbar'>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,28 +26,7 @@
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        {% if roles.purchase_order.change %}
 | 
			
		||||
        <div id='supplier-part-button-toolbar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid'>
 | 
			
		||||
                <div class='btn-group' role='group'>
 | 
			
		||||
                    <div class='btn-group'>
 | 
			
		||||
                        <button class="btn btn-primary dropdown-toggle" id='supplier-table-options' type="button" data-bs-toggle="dropdown">
 | 
			
		||||
                            <span class='fas fa-tools'></span> <span class="caret"></span>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <ul class="dropdown-menu">
 | 
			
		||||
                            {% if roles.purchase_order.add %}
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='multi-supplier-part-order' title='{% trans "Order parts" %}'>
 | 
			
		||||
                                <span class='fas fa-shopping-cart'></span> {% trans "Order Parts" %}
 | 
			
		||||
                            </a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if roles.purchase_order.delete %}
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='multi-supplier-part-delete' title='{% trans "Delete parts" %}'>
 | 
			
		||||
                                <span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Parts" %}
 | 
			
		||||
                            </a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% include "filter_list.html" with id="supplier-part" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="supplier-part" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
 | 
			
		||||
@@ -73,29 +52,7 @@
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        {% if roles.purchase_order.change %}
 | 
			
		||||
        <div id='manufacturer-part-button-toolbar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid'>
 | 
			
		||||
                <div class='btn-group' role='group'>
 | 
			
		||||
                    <div class='btn-group' role='group'>
 | 
			
		||||
                        <button class="btn btn-primary dropdown-toggle" id='manufacturer-table-options' type="button" data-bs-toggle="dropdown">
 | 
			
		||||
                            <span class='fas fa-tools'></span>
 | 
			
		||||
                            <span class="caret"></span>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <ul class="dropdown-menu">
 | 
			
		||||
                            {% if roles.purchase_order.add %}
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='multi-manufacturer-part-order' title='{% trans "Order parts" %}'>
 | 
			
		||||
                                <span class='fas fa-shopping-cart'></span> {% trans "Order Parts" %}
 | 
			
		||||
                            </a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if roles.purchase_order.delete %}
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='multi-manufacturer-part-delete' title='{% trans "Delete parts" %}'>
 | 
			
		||||
                                <span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Parts" %}
 | 
			
		||||
                            </a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% include "filter_list.html" with id="manufacturer-part" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="manufacturer-part" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <table class='table table-striped table-condensed' id='manufacturer-part-table' data-toolbar='#manufacturer-part-button-toolbar'>
 | 
			
		||||
@@ -128,9 +85,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='po-button-bar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
                {% include "filter_list.html" with id="purchaseorder" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="purchaseorder" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#po-button-bar'>
 | 
			
		||||
@@ -156,9 +111,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='so-button-bar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
                {% include "filter_list.html" with id="salesorder" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="salesorder" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed po-table' id='sales-order-table' data-toolbar='#so-button-bar'>
 | 
			
		||||
@@ -174,9 +127,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='assigned-stock-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="customerstock" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="customerstock" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' id='assigned-stock-table' data-toolbar='#assigned-stock-button-toolbar'></table>
 | 
			
		||||
@@ -201,11 +152,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='table-buttons'>
 | 
			
		||||
            <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
                <div class='btn-group'>
 | 
			
		||||
                    {% include "filter_list.html" with id="returnorder" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="returnorder" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#table-buttons' id='return-order-table'>
 | 
			
		||||
        </table>
 | 
			
		||||
@@ -246,9 +193,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='contacts-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="contacts" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="contacts" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' id='contacts-table' data-toolbar='#contacts-button-toolbar'></table>
 | 
			
		||||
@@ -271,9 +216,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='addresses-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="addresses" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="addresses" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' id='addresses-table' data-toolbar='#addresses-button-toolbar'></table>
 | 
			
		||||
@@ -498,32 +441,6 @@
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $("#multi-manufacturer-part-delete").click(function() {
 | 
			
		||||
        var selections = getTableData('#manufacturer-part-table');
 | 
			
		||||
 | 
			
		||||
        deleteManufacturerParts(selections, {
 | 
			
		||||
            success: function() {
 | 
			
		||||
                $("#manufacturer-part-table").bootstrapTable('refresh');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#multi-manufacturer-part-order").click(function() {
 | 
			
		||||
        var selections = getTableData('#manufacturer-part-table');
 | 
			
		||||
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            var part = item.part_detail;
 | 
			
		||||
            part.manufacturer_part = item.pk;
 | 
			
		||||
            parts.push(part);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        orderParts(
 | 
			
		||||
            parts,
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if company.is_supplier %}
 | 
			
		||||
@@ -552,37 +469,6 @@
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $("#multi-supplier-part-delete").click(function() {
 | 
			
		||||
 | 
			
		||||
        var selections = getTableData("#supplier-part-table");
 | 
			
		||||
 | 
			
		||||
        deleteSupplierParts(selections, {
 | 
			
		||||
            success: function() {
 | 
			
		||||
                $('#supplier-part-table').bootstrapTable('refresh');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#multi-supplier-part-order").click(function() {
 | 
			
		||||
 | 
			
		||||
        var selections = getTableData('#supplier-part-table');
 | 
			
		||||
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            var part = item.part_detail;
 | 
			
		||||
            parts.push(part);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        orderParts(
 | 
			
		||||
            parts,
 | 
			
		||||
            {
 | 
			
		||||
                supplier: {{ company.pk }},
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    enableSidebar('company');
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,11 @@
 | 
			
		||||
 | 
			
		||||
<div class='panel-content'>
 | 
			
		||||
 | 
			
		||||
    <div id='button-toolbar'>
 | 
			
		||||
    <div id='company-button-toolbar'>
 | 
			
		||||
        {% include "filter_list.html" with id='company' %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <table class='table table-striped table-condensed' id='company-table' data-toolbar='#button-toolbar'>
 | 
			
		||||
    <table class='table table-striped table-condensed' id='company-table' data-toolbar='#company-button-toolbar'>
 | 
			
		||||
    </table>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -127,17 +127,7 @@ src="{% static 'img/blank_image.png' %}"
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='supplier-button-toolbar'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                <div id='opt-dropdown' class="btn-group">
 | 
			
		||||
                    <button id='supplier-part-options' class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
 | 
			
		||||
                        <span class='fas fa-tools'></span> <span class="caret"></span>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <ul class="dropdown-menu">
 | 
			
		||||
                        <li><a class='dropdown-item' href='#' id='supplier-part-delete' title='{% trans "Delete supplier parts" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete" %}</a></li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
                {% include "filter_list.html" with id='supplier-part' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id='supplier-part' %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class="table table-striped table-condensed" id='supplier-table' data-toolbar='#supplier-button-toolbar'>
 | 
			
		||||
@@ -174,17 +164,7 @@ src="{% static 'img/blank_image.png' %}"
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='parameter-toolbar'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                <div id='opt-dropdown' class="btn-group">
 | 
			
		||||
                    <button id='parameter-options' class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
 | 
			
		||||
                        <span class='fas fa-tools'></span> <span class="caret"></span>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <ul class="dropdown-menu">
 | 
			
		||||
                        <li><a class='dropdown-item' href='#' id='multi-parameter-delete' title='{% trans "Delete parameters" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete" %}</a></li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
                {% include "filter_list.html" with id="manufacturer-part-parameters" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="manufacturer-part-parameters" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' id='parameter-table' data-toolbar='#parameter-toolbar'></table>
 | 
			
		||||
@@ -240,26 +220,6 @@ $('#supplier-create').click(function () {
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$("#supplier-part-delete").click(function() {
 | 
			
		||||
 | 
			
		||||
    var selections = getTableData('#supplier-table');
 | 
			
		||||
 | 
			
		||||
    deleteSupplierParts(selections, {
 | 
			
		||||
        success: reloadSupplierPartTable,
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$("#multi-parameter-delete").click(function() {
 | 
			
		||||
 | 
			
		||||
    var selections = getTableData('#parameter-table');
 | 
			
		||||
 | 
			
		||||
    deleteManufacturerPartParameters(selections, {
 | 
			
		||||
        success: function() {
 | 
			
		||||
            $('#parameter-table').bootstrapTable('refresh');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
loadSupplierPartTable(
 | 
			
		||||
    "#supplier-table",
 | 
			
		||||
    "{% url 'api-supplier-part-list' %}",
 | 
			
		||||
 
 | 
			
		||||
@@ -234,9 +234,7 @@ src="{% static 'img/blank_image.png' %}"
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='button-bar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id='purchaseorder' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id='purchaseorder' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'>
 | 
			
		||||
        </table>
 | 
			
		||||
@@ -258,10 +256,8 @@ src="{% static 'img/blank_image.png' %}"
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='price-break-toolbar' class='btn-group'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id='supplierpricebreak' %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='price-break-toolbar'>
 | 
			
		||||
            {% include "filter_list.html" with id='supplierpricebreak' %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' id='price-break-table' data-toolbar='#price-break-toolbar'>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,23 +37,7 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
 | 
			
		||||
            {% if roles.purchase_order.change %}
 | 
			
		||||
            {% if order.is_open or allow_extra_editing %}
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                <!-- Multiple-select actions -->
 | 
			
		||||
                <button id='multi-select-options' class='btn btn-primary dropdown-toggle' data-bs-toggle='dropdown'>
 | 
			
		||||
                    <span class='fas fa-tools'></span> <span class='caret'></span>
 | 
			
		||||
                </button>
 | 
			
		||||
                <ul class='dropdown-menu'>
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='po-lines-bulk-delete' title='{% trans "Delete Line Items" %}'>
 | 
			
		||||
                        <span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Line Items" %}
 | 
			
		||||
                    </a></li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
 | 
			
		||||
        <div id='order-toolbar-buttons'>
 | 
			
		||||
            {% include "filter_list.html" with id="purchase-order-lines" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@@ -77,10 +61,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='order-extra-toolbar-buttons' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="purchase-order-extra-lines" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='order-extra-toolbar-buttons'>
 | 
			
		||||
            {% include "filter_list.html" with id="purchase-order-extra-lines" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='po-extra-lines-table' data-toolbar='#order-extra-toolbar-buttons'>
 | 
			
		||||
        </table>
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,7 @@
 | 
			
		||||
 | 
			
		||||
<div class='panel-content'>
 | 
			
		||||
    <div id='table-buttons'>
 | 
			
		||||
        <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="purchaseorder" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% include "filter_list.html" with id="purchaseorder" %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <table class='table table-striped table-condensed po-table' data-toolbar='#table-buttons' id='purchase-order-table'>
 | 
			
		||||
 
 | 
			
		||||
@@ -34,10 +34,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="returnorderlines" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='order-toolbar-buttons'>
 | 
			
		||||
            {% include "filter_list.html" with id="returnorderlines" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='return-order-lines-table' data-toolbar='#order-toolbar-buttons'>
 | 
			
		||||
        </table>
 | 
			
		||||
@@ -58,10 +56,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='order-extra-toolbar-buttons' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="return-order-extra-lines" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='order-extra-toolbar-buttons'>
 | 
			
		||||
            {% include "filter_list.html" with id="return-order-extra-lines" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='return-order-extra-lines-table' data-toolbar='#order-extra-toolbar-buttons'>
 | 
			
		||||
        </table>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,15 +26,11 @@
 | 
			
		||||
{% block page_info %}
 | 
			
		||||
 | 
			
		||||
<div class='panel-content'>
 | 
			
		||||
    <div id='table-buttons'>
 | 
			
		||||
        <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="returnorder" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    <div id='return-order-table-buttons'>
 | 
			
		||||
        {% include "filter_list.html" with id="returnorder" %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <table class='table table-striped table-condensed' data-toolbar='#table-buttons' id='return-order-table'>
 | 
			
		||||
    <table class='table table-striped table-condensed' data-toolbar='#return-order-table-buttons' id='return-order-table'>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    <div id='return-order-calendar'></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -29,10 +29,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="sales-order-lines" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='order-toolbar-buttons'>
 | 
			
		||||
            {% include "filter_list.html" with id="sales-order-lines" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='so-lines-table' data-toolbar='#order-toolbar-buttons'>
 | 
			
		||||
        </table>
 | 
			
		||||
@@ -54,10 +52,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='order-extra-toolbar-buttons' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="sales-order-extra-lines" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='order-extra-toolbar-buttons'>
 | 
			
		||||
            {% include "filter_list.html" with id="sales-order-extra-lines" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='so-extra-lines-table' data-toolbar='#order-extra-toolbar-buttons'>
 | 
			
		||||
        </table>
 | 
			
		||||
@@ -89,10 +85,8 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        {% if roles.sales_order.change %}
 | 
			
		||||
        <div id='pending-shipment-toolbar' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="pending-shipments" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='pending-shipment-toolbar'>
 | 
			
		||||
            {% include "filter_list.html" with id="pending-shipments" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <table class='table table-striped table-condensed' id='pending-shipments-table' data-toolbar='#pending-shipment-toolbar'></table>
 | 
			
		||||
@@ -105,10 +99,8 @@
 | 
			
		||||
        <h4>{% trans "Completed Shipments" %}</h4>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='completed-shipment-toolbar' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="completed-shipments" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='completed-shipment-toolbar'>
 | 
			
		||||
            {% include "filter_list.html" with id="completed-shipments" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='completed-shipments-table' data-toolbar='#completed-shipment-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -119,10 +111,8 @@
 | 
			
		||||
        <h4>{% trans "Build Orders" %}</h4>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='builds-toolbar' class='btn-group' style='float: right;'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id='build' %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div id='builds-toolbar'>
 | 
			
		||||
            {% include "filter_list.html" with id='build' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='builds-table' data-toolbar='#builds-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -27,10 +27,8 @@
 | 
			
		||||
 | 
			
		||||
<div class='panel-content'>
 | 
			
		||||
    <div id='table-buttons'>
 | 
			
		||||
        <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="salesorder" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        <div class='button-toolbar'>
 | 
			
		||||
            {% include "filter_list.html" with id="salesorder" %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1271,6 +1271,13 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartChangeCategory(CreateAPI):
 | 
			
		||||
    """API endpoint to change the location of multiple parts in bulk"""
 | 
			
		||||
 | 
			
		||||
    serializer_class = part_serializers.PartSetCategorySerializer
 | 
			
		||||
    queryset = Part.objects.none()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartDetail(PartMixin, RetrieveUpdateDestroyAPI):
 | 
			
		||||
    """API endpoint for detail view of a single Part object."""
 | 
			
		||||
 | 
			
		||||
@@ -2020,6 +2027,8 @@ part_api_urls = [
 | 
			
		||||
        re_path(r'^.*$', PartDetail.as_view(), name='api-part-detail'),
 | 
			
		||||
    ])),
 | 
			
		||||
 | 
			
		||||
    re_path(r'^change_category/', PartChangeCategory.as_view(), name='api-part-change-category'),
 | 
			
		||||
 | 
			
		||||
    re_path(r'^.*$', PartList.as_view(), name='api-part-list'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -291,6 +291,56 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
			
		||||
    pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartSetCategorySerializer(serializers.Serializer):
 | 
			
		||||
    """Serializer for changing PartCategory for multiple Part objects"""
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        """Metaclass options"""
 | 
			
		||||
        fields = [
 | 
			
		||||
            'parts',
 | 
			
		||||
            'category',
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    parts = serializers.PrimaryKeyRelatedField(
 | 
			
		||||
        queryset=Part.objects.all(),
 | 
			
		||||
        many=True, required=True, allow_null=False,
 | 
			
		||||
        label=_('Parts'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def validate_parts(self, parts):
 | 
			
		||||
        """Validate the selected parts"""
 | 
			
		||||
        if len(parts) == 0:
 | 
			
		||||
            raise serializers.ValidationError(_("No parts selected"))
 | 
			
		||||
 | 
			
		||||
        return parts
 | 
			
		||||
 | 
			
		||||
    category = serializers.PrimaryKeyRelatedField(
 | 
			
		||||
        queryset=PartCategory.objects.filter(structural=False),
 | 
			
		||||
        many=False, required=True, allow_null=False,
 | 
			
		||||
        label=_('Category'),
 | 
			
		||||
        help_text=_('Select category',)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def save(self):
 | 
			
		||||
        """Save the serializer to change the location of the selected parts"""
 | 
			
		||||
 | 
			
		||||
        data = self.validated_data
 | 
			
		||||
        parts = data['parts']
 | 
			
		||||
        category = data['category']
 | 
			
		||||
 | 
			
		||||
        parts_to_save = []
 | 
			
		||||
 | 
			
		||||
        for p in parts:
 | 
			
		||||
            if p.category == category:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            p.category = category
 | 
			
		||||
            parts_to_save.append(p)
 | 
			
		||||
 | 
			
		||||
        Part.objects.bulk_update(parts_to_save, ['category'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DuplicatePartSerializer(serializers.Serializer):
 | 
			
		||||
    """Serializer for specifying options when duplicating a Part.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,20 +23,7 @@
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
<div id='bom-button-toolbar'>
 | 
			
		||||
    <div class="btn-group" role="group" aria-label="...">
 | 
			
		||||
        {% if roles.part.change %}
 | 
			
		||||
        <!-- Action menu -->
 | 
			
		||||
        <div class='btn-group'>
 | 
			
		||||
            <button id='bom-actions' title='{% trans "BOM actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
 | 
			
		||||
                <span class='fas fa-tools'></span> <span class='caret'></span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <ul class='dropdown-menu' role='menu'>
 | 
			
		||||
                <li><a class='dropdown-item' href='#' id='bom-item-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Items" %}</a></li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% include "filter_list.html" with id="bom" %}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% include "filter_list.html" with id="bom" %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<table class='table table-bom table-condensed' data-toolbar="#bom-button-toolbar" id='bom-table'>
 | 
			
		||||
 
 | 
			
		||||
@@ -169,24 +169,7 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id='part-button-toolbar'>
 | 
			
		||||
        <div class='btn-group' role='group'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                <button id='part-options' class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle="dropdown">
 | 
			
		||||
                    <span class='fas fa-tools' title='{% trans "Options" %}'></span>
 | 
			
		||||
                </button>
 | 
			
		||||
                <ul class='dropdown-menu'>
 | 
			
		||||
                    {% if roles.part.change %}
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-part-category' title='{% trans "Set category" %}'>
 | 
			
		||||
                        <span class='fas fa-sitemap'></span> {% trans "Set Category" %}
 | 
			
		||||
                    </a></li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-part-order' title='{% trans "Order parts" %}'>
 | 
			
		||||
                        <span class='fas fa-shopping-cart'></span> {% trans "Order Parts" %}
 | 
			
		||||
                    </a></li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="parts" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% include "filter_list.html" with id="parts" %}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#part-button-toolbar' id='part-table'>
 | 
			
		||||
@@ -209,9 +192,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='param-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="parameters" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="parameters" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#param-button-toolbar' id='parametric-part-table'>
 | 
			
		||||
@@ -235,9 +216,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='subcategory-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="category" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="category" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' id='subcategory-table' data-toolbar='#subcategory-button-toolbar'></table>
 | 
			
		||||
 
 | 
			
		||||
@@ -93,9 +93,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='test-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="parttests" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="parttests" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#test-button-toolbar' id='test-template-table'></table>
 | 
			
		||||
@@ -116,9 +114,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='po-button-bar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
                {% include "filter_list.html" with id="partpurchaseorders" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="partpurchaseorders" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#po-button-bar'>
 | 
			
		||||
@@ -132,9 +128,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='so-button-bar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="salesorder" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="salesorder" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed po-table' id='sales-order-table' data-toolbar='#so-button-bar'>
 | 
			
		||||
@@ -145,11 +139,8 @@
 | 
			
		||||
        <h4>{% trans "Sales Order Allocations" %}</h4>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
 | 
			
		||||
        <div id='sales-order-allocation-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="salesorderallocation" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="salesorderallocation" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='sales-order-allocation-table' data-toolbar='#sales-order-allocation-button-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -190,11 +181,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='variant-button-toolbar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid'>
 | 
			
		||||
                <div class='btn-group' role='group'>
 | 
			
		||||
                    {% include "filter_list.html" with id="variants" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="variants" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' id='variants-table' data-toolbar='#variant-button-toolbar'>
 | 
			
		||||
@@ -218,11 +205,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='param-button-toolbar'>
 | 
			
		||||
            <div class='button-toolbar container-fluid'>
 | 
			
		||||
                <div class='btn-group' role='group'>
 | 
			
		||||
                    {% include "filter_list.html" with id="parameters" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="parameters" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table id='parameter-table' class='table table-condensed table-striped' data-toolbar="#param-button-toolbar"></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -259,9 +242,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='related-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="related" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="related" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table id='related-parts-table' class='table table-striped table-condensed' data-toolbar='#related-button-toolbar'></table>
 | 
			
		||||
@@ -317,9 +298,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='assembly-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="usedin" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="usedin" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class="table table-striped table-condensed" id='used-table' data-toolbar='#assembly-button-toolbar'>
 | 
			
		||||
@@ -346,9 +325,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='build-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="build" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="build" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#build-button-toolbar' id='build-table'>
 | 
			
		||||
@@ -362,9 +339,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='build-allocation-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="buildorderallocation" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="buildorderallocation" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='build-order-allocation-table' data-toolbar='#build-allocation-button-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -385,17 +360,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='supplier-button-toolbar'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                <div id='opt-dropdown' class="btn-group">
 | 
			
		||||
                    <button id='supplier-part-options' class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">{% trans "Options" %} <span class="caret"></span></button>
 | 
			
		||||
                    <ul class="dropdown-menu">
 | 
			
		||||
                        <li><a class='dropdown-item' href='#' id='supplier-part-delete' title='{% trans "Delete supplier parts" %}'>
 | 
			
		||||
                            <span class='fas fa-trash-alt icon-red'></span> {% trans "Delete" %}
 | 
			
		||||
                        </a></li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
                {% include "filter_list.html" with id="supplier-part" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="supplier-part" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class="table table-striped table-condensed" id='supplier-part-table' data-toolbar='#supplier-button-toolbar'>
 | 
			
		||||
@@ -416,15 +381,7 @@
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div class='panel-content'>
 | 
			
		||||
            <div id='manufacturer-button-toolbar'>
 | 
			
		||||
                <div class='btn-group'>
 | 
			
		||||
                    <div id='opt-dropdown' class="btn-group">
 | 
			
		||||
                        <button id='manufacturer-part-options' class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">{% trans "Options" %} <span class="caret"></span></button>
 | 
			
		||||
                        <ul class="dropdown-menu">
 | 
			
		||||
                            <li><a class='dropdown-item' href='#' id='manufacturer-part-delete' title='{% trans "Delete manufacturer parts" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete" %}</a></li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                        {% include "filter_list.html" with id="manufacturer-part" %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                {% include "filter_list.html" with id="manufacturer-part" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <table class='table table-condensed table-striped' id='manufacturer-part-table' data-toolbar='#manufacturer-button-toolbar'></table>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -526,8 +483,6 @@
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        linkButtonsToSelection($("#supplier-part-table"), ['#supplier-part-options']);
 | 
			
		||||
 | 
			
		||||
        loadManufacturerPartTable(
 | 
			
		||||
            '#manufacturer-part-table',
 | 
			
		||||
            "{% url 'api-manufacturer-part-list' %}",
 | 
			
		||||
@@ -540,8 +495,6 @@
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        linkButtonsToSelection($("#manufacturer-part-table"), ['#manufacturer-part-options']);
 | 
			
		||||
 | 
			
		||||
        $("#manufacturer-part-delete").click(function() {
 | 
			
		||||
 | 
			
		||||
            var selectionss = getTableData('#manufacturer-part-table');
 | 
			
		||||
@@ -625,12 +578,6 @@
 | 
			
		||||
            sub_part_detail: true,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        linkButtonsToSelection($("#bom-table"),
 | 
			
		||||
            [
 | 
			
		||||
                "#bom-item-delete",
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $('#bom-item-delete').click(function() {
 | 
			
		||||
 | 
			
		||||
            // Get a list of the selected BOM items
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='tracking-table-toolbar'>
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                {% include "filter_list.html" with id="stocktracking" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="stocktracking" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-condensed table-striped' id='track-table' data-toolbar='#tracking-table-toolbar'>
 | 
			
		||||
        </table>
 | 
			
		||||
@@ -40,9 +38,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='build-order-allocations-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="buildorderallocation" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="buildorderallocation" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#build-order-allocations-toolbar' id='build-order-allocation-table'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -55,9 +51,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='sales-order-allocations-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="salesorderallocation" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="salesorderallocation" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#sales-order-allocations-toolbar' id='sales-order-allocation-table'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -102,9 +96,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='test-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="stocktests" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="stocktests" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#test-button-toolbar' id='test-result-table'></table>
 | 
			
		||||
@@ -157,9 +149,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='installed-table-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id='installed-items' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id='installed-items' %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <table class='table table-striped table-condensed' id='installed-table' data-toolbar='#installed-table-toolbar'></table>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -221,9 +221,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='panel-content'>
 | 
			
		||||
        <div id='sublocation-button-toolbar'>
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                {% include "filter_list.html" with id="location" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "filter_list.html" with id="location" %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <table class='table table-striped table-condensed' data-toolbar='#sublocation-button-toolbar' id='sublocation-table'></table>
 | 
			
		||||
@@ -270,13 +268,6 @@
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    linkButtonsToSelection(
 | 
			
		||||
        $('#sublocation-table'),
 | 
			
		||||
        [
 | 
			
		||||
            '#location-print-options',
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    {% if labels_enabled %}
 | 
			
		||||
    $('#print-label').click(function() {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,7 @@
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
<div id='history-buttons'>
 | 
			
		||||
    <div class='btn-group' role='group'>
 | 
			
		||||
        {% include "filter_list.html" with id="notifications-history" %}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% include "filter_list.html" with id="notifications-history" %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class='row'>
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,7 @@
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
<div id='inbox-buttons'>
 | 
			
		||||
    <div class='btn-group' role='group'>
 | 
			
		||||
        {% include "filter_list.html" with id="notifications-inbox" %}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% include "filter_list.html" with id="notifications-inbox" %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class='row'>
 | 
			
		||||
 
 | 
			
		||||
@@ -35,9 +35,7 @@
 | 
			
		||||
 | 
			
		||||
<div class='panel-content'>
 | 
			
		||||
    <div id='part-stocktake-report-toolbar'>
 | 
			
		||||
        <div class='btn-group' role='group'>
 | 
			
		||||
            {% include "filter_list.html" with id="stocktakereport" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% include "filter_list.html" with id="stocktakereport" %}
 | 
			
		||||
    </div>
 | 
			
		||||
    <table class='table table-striped table-condensed' id='stocktake-report-table' data-toolbar='#part-stocktake-report-toolbar'></table>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -51,11 +51,7 @@
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
<div id='plugin-button-toolbar'>
 | 
			
		||||
    <div class='button-toolbar container-fluid'>
 | 
			
		||||
        <div class='btn-group' role='group'>
 | 
			
		||||
            {% include "filter_list.html" with id="plugins" %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% include "filter_list.html" with id="plugins" %}
 | 
			
		||||
</div>
 | 
			
		||||
<div class='table-responsive'>
 | 
			
		||||
    <table class='table table-striped table-condensed' id='plugin-table' data-toolbar='#plugin-button-toolbar'></table>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<div id='attachment-buttons'>
 | 
			
		||||
    <div class='btn-group' role='group'>
 | 
			
		||||
        <div class='btn-group' id='multi-attachment-actions'>
 | 
			
		||||
            <button class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle='dropdown' title='{% trans "Actions" %}'>
 | 
			
		||||
                <span class='fas fa-tools'></span> <span class='caret'></span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <ul class='dropdown-menu'>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <a class='dropdown-item' href='#' id='multi-attachment-delete' title='{% trans "Delete selected attachments" %}'>
 | 
			
		||||
                        <span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Attachments" %}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% include "filter_list.html" with id="attachments" %}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% include "filter_list.html" with id="attachments" %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class='dropzone' id='attachment-dropzone'>
 | 
			
		||||
 
 | 
			
		||||
@@ -147,6 +147,7 @@
 | 
			
		||||
<!-- dynamic javascript templates -->
 | 
			
		||||
<script defer type='text/javascript' src="{% url 'calendar.js' %}"></script>
 | 
			
		||||
<script defer type='text/javascript' src="{% url 'nav.js' %}"></script>
 | 
			
		||||
<script defer type='text/javascript' src="{% url 'permissions.js' %}"></script>
 | 
			
		||||
<script defer type='text/javascript' src="{% url 'settings.js' %}"></script>
 | 
			
		||||
 | 
			
		||||
<!-- translated javascript templates-->
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,3 @@
 | 
			
		||||
<div class='filter-list d-flex flex-row form-row' id='filter-list-{% if prefix %}{{ prefix }}{% endif %}{{ id }}'><!-- Empty div for table filters --></div>
 | 
			
		||||
<div class='filter-list d-flex flex-row form-row' id='filter-list-{% if prefix %}{{ prefix }}{% endif %}{{ id }}'>
 | 
			
		||||
  <!-- Empty div for table filters -->
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								InvenTree/templates/js/dynamic/permissions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								InvenTree/templates/js/dynamic/permissions.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
/*
 | 
			
		||||
 * globals
 | 
			
		||||
    inventreeGet,
 | 
			
		||||
 | 
			
		||||
/* exported
 | 
			
		||||
    checkPermission,
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// Keep track of the current user permissions
 | 
			
		||||
var user_roles = null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Check if the user has the specified role and permission
 | 
			
		||||
 */
 | 
			
		||||
function checkPermission(role, permission='view') {
 | 
			
		||||
 | 
			
		||||
    // Allow permission to be specified in dotted notation, e.g. 'part.add'
 | 
			
		||||
    if (role.indexOf('.') > 0) {
 | 
			
		||||
        let parts = role.split('.');
 | 
			
		||||
        role = parts[0];
 | 
			
		||||
        permission = parts[1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Request user roles if we do not have them
 | 
			
		||||
    if (user_roles == null) {
 | 
			
		||||
        inventreeGet('{% url "api-user-roles" %}', {}, {
 | 
			
		||||
            async: false,
 | 
			
		||||
            success: function(response) {
 | 
			
		||||
                user_roles = response.roles || {};
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (user_roles == null) {
 | 
			
		||||
        console.error("Failed to fetch user roles");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!(role in user_roles)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let roles = user_roles[role];
 | 
			
		||||
 | 
			
		||||
    if (!roles) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let found = false;
 | 
			
		||||
 | 
			
		||||
    user_roles[role].forEach(function(p) {
 | 
			
		||||
        if (String(p).valueOf() == String(permission).valueOf()) {
 | 
			
		||||
            found = true;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return found;
 | 
			
		||||
}
 | 
			
		||||
@@ -187,7 +187,27 @@ function attachmentLink(filename) {
 | 
			
		||||
    let html = makeIcon(icon) + ` ${fn}`;
 | 
			
		||||
 | 
			
		||||
    return renderLink(html, filename, {download: true});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Construct a set of actions for an attachment table,
 | 
			
		||||
 * with the provided permission set
 | 
			
		||||
 */
 | 
			
		||||
function makeAttachmentActions(permissions, options) {
 | 
			
		||||
 | 
			
		||||
    let actions = [];
 | 
			
		||||
 | 
			
		||||
    if (permissions.delete) {
 | 
			
		||||
        actions.push({
 | 
			
		||||
            label: 'delete',
 | 
			
		||||
            icon: 'fa-trash-alt icon-red',
 | 
			
		||||
            title: '{% trans "Delete attachments" %}',
 | 
			
		||||
            callback: options.callback,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return actions;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -225,7 +245,20 @@ function loadAttachmentTable(url, options) {
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setupFilterList('attachments', $(table), '#filter-list-attachments');
 | 
			
		||||
    setupFilterList('attachments', $(table), '#filter-list-attachments', {
 | 
			
		||||
        custom_actions: [
 | 
			
		||||
            {
 | 
			
		||||
                label: 'attachments',
 | 
			
		||||
                icon: 'fa-tools',
 | 
			
		||||
                title: '{% trans "Attachment actions" %}',
 | 
			
		||||
                actions: makeAttachmentActions(permissions, {
 | 
			
		||||
                    callback: function(attachments) {
 | 
			
		||||
                        deleteAttachments(attachments, url, options);
 | 
			
		||||
                    }
 | 
			
		||||
                }),
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (permissions.add) {
 | 
			
		||||
        addAttachmentButtonCallbacks(url, options.fields || {});
 | 
			
		||||
@@ -235,19 +268,6 @@ function loadAttachmentTable(url, options) {
 | 
			
		||||
        $('#new-attachment-link').hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (permissions.delete) {
 | 
			
		||||
        // Add callback for the 'multi delete' button
 | 
			
		||||
        $('#multi-attachment-delete').click(function() {
 | 
			
		||||
            var attachments = getTableData(table);
 | 
			
		||||
 | 
			
		||||
            if (attachments.length > 0) {
 | 
			
		||||
                deleteAttachments(attachments, url, options);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        $('#multi-attachment-actions').hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $(table).inventreeTable({
 | 
			
		||||
        url: url,
 | 
			
		||||
        name: options.name || 'attachments',
 | 
			
		||||
@@ -286,16 +306,6 @@ function loadAttachmentTable(url, options) {
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (permissions.delete) {
 | 
			
		||||
                // Add callback for 'delete' button
 | 
			
		||||
                $(table).find('.button-attachment-delete').click(function() {
 | 
			
		||||
                    var pk = $(this).attr('pk');
 | 
			
		||||
 | 
			
		||||
                    var attachment = $(table).bootstrapTable('getRowByUniqueId', pk);
 | 
			
		||||
                    deleteAttachments([attachment], url, options);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        columns: [
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -763,7 +763,7 @@ function scanItemsIntoLocation(item_list, options={}) {
 | 
			
		||||
    // Extra form fields
 | 
			
		||||
    var extra = makeNotesField();
 | 
			
		||||
 | 
			
		||||
    // Header contentfor
 | 
			
		||||
    // Header content
 | 
			
		||||
    var header = `
 | 
			
		||||
    <div id='header-div'>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@
 | 
			
		||||
    modalSetContent,
 | 
			
		||||
    partFields,
 | 
			
		||||
    partGroups,
 | 
			
		||||
    reloadBootstrapTable,
 | 
			
		||||
    renderLink,
 | 
			
		||||
    setupFilterList,
 | 
			
		||||
    shortenString,
 | 
			
		||||
@@ -817,7 +818,24 @@ function loadBomTable(table, options={}) {
 | 
			
		||||
 | 
			
		||||
    Object.assign(filters, params);
 | 
			
		||||
 | 
			
		||||
    setupFilterList('bom', $(table));
 | 
			
		||||
    setupFilterList('bom', $(table), '#filter-list-bom', {
 | 
			
		||||
        custom_actions: [{
 | 
			
		||||
            label: 'actions',
 | 
			
		||||
            actions: [{
 | 
			
		||||
                label: 'delete',
 | 
			
		||||
                title: '{% trans "Delete items" %}',
 | 
			
		||||
                icon: 'fa-trash-alt icon-red',
 | 
			
		||||
                permission: 'part.change',
 | 
			
		||||
                callback: function(data) {
 | 
			
		||||
                    deleteBomItems(data, {
 | 
			
		||||
                        success: function() {
 | 
			
		||||
                            reloadBootstrapTable('#bom-table');
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }]
 | 
			
		||||
        }]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function availableQuantity(row) {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@
 | 
			
		||||
    inventreeLoad,
 | 
			
		||||
    inventreePut,
 | 
			
		||||
    launchModalForm,
 | 
			
		||||
    linkButtonsToSelection,
 | 
			
		||||
    loadTableFilters,
 | 
			
		||||
    locationDetail,
 | 
			
		||||
    makeDeleteButton,
 | 
			
		||||
@@ -34,6 +33,7 @@
 | 
			
		||||
    makePartIcons,
 | 
			
		||||
    makeProgressBar,
 | 
			
		||||
    orderParts,
 | 
			
		||||
    reloadBootstrapTable,
 | 
			
		||||
    renderDate,
 | 
			
		||||
    renderLink,
 | 
			
		||||
    setupFilterList,
 | 
			
		||||
@@ -387,7 +387,7 @@ function createBuildOutput(build_id, options) {
 | 
			
		||||
                    fields: fields,
 | 
			
		||||
                    preFormContent: html,
 | 
			
		||||
                    onSuccess: function(response) {
 | 
			
		||||
                        location.reload();
 | 
			
		||||
                        reloadBootstrapTable(options.table || '#build-output-table');
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
@@ -995,6 +995,70 @@ function loadBuildOrderAllocationTable(table, options={}) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Construct a set of actions for the build output table
 | 
			
		||||
 */
 | 
			
		||||
function makeBuildOutputActions(build_info) {
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
            label: 'complete',
 | 
			
		||||
            title: '{% trans "Complete outputs" %}',
 | 
			
		||||
            icon: 'fa-check-circle icon-green',
 | 
			
		||||
            permission: 'build.add',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                completeBuildOutputs(
 | 
			
		||||
                    build_info.pk,
 | 
			
		||||
                    data,
 | 
			
		||||
                    {
 | 
			
		||||
                        success: function() {
 | 
			
		||||
                            $('#build-output-table').bootstrapTable('refresh');  // Reload the "in progress" table
 | 
			
		||||
                            $('#build-stock-table').bootstrapTable('refresh');  // Reload the "completed" table
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'scrap',
 | 
			
		||||
            title: '{% trans "Scrap outputs" %}',
 | 
			
		||||
            icon: 'fa-times-circle icon-red',
 | 
			
		||||
            permission: 'build.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                scrapBuildOutputs(
 | 
			
		||||
                    build_info.pk,
 | 
			
		||||
                    data,
 | 
			
		||||
                    {
 | 
			
		||||
                        success: function() {
 | 
			
		||||
                            $('#build-output-table').bootstrapTable('refresh');  // Reload the "in progress" table
 | 
			
		||||
                            $('#build-stock-table').bootstrapTable('refresh');  // Reload the "completed" table
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'delete',
 | 
			
		||||
            title: '{% trans "Delete outputs" %}',
 | 
			
		||||
            icon: 'fa-trash-alt icon-red',
 | 
			
		||||
            permission: 'build.delete',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                deleteBuildOutputs(
 | 
			
		||||
                    build_info.pk,
 | 
			
		||||
                    data,
 | 
			
		||||
                    {
 | 
			
		||||
                        success: function() {
 | 
			
		||||
                            $('#build-output-table').bootstrapTable('refresh');  // Reload the "in progress" table
 | 
			
		||||
                            $('#build-stock-table').bootstrapTable('refresh');  // Reload the "completed" table
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Display a "build output" table for a particular build.
 | 
			
		||||
 *
 | 
			
		||||
@@ -1035,6 +1099,12 @@ function loadBuildOutputTable(build_info, options={}) {
 | 
			
		||||
        },
 | 
			
		||||
        singular_name: '{% trans "build output" %}',
 | 
			
		||||
        plural_name: '{% trans "build outputs" %}',
 | 
			
		||||
        custom_actions: [{
 | 
			
		||||
            label: 'buildoutput',
 | 
			
		||||
            icon: 'fa-tools',
 | 
			
		||||
            title: '{% trans "Build output actions" %}',
 | 
			
		||||
            actions: makeBuildOutputActions(build_info),
 | 
			
		||||
        }]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Request list of required tests for the part being assembled
 | 
			
		||||
@@ -1383,25 +1453,6 @@ function loadBuildOutputTable(build_info, options={}) {
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Complete multiple outputs
 | 
			
		||||
    $('#multi-output-complete').click(function() {
 | 
			
		||||
        var outputs = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        completeBuildOutputs(
 | 
			
		||||
            build_info.pk,
 | 
			
		||||
            outputs,
 | 
			
		||||
            {
 | 
			
		||||
                success: function() {
 | 
			
		||||
                    // Reload the "in progress" table
 | 
			
		||||
                    $('#build-output-table').bootstrapTable('refresh');
 | 
			
		||||
 | 
			
		||||
                    // Reload the "completed" table
 | 
			
		||||
                    $('#build-stock-table').bootstrapTable('refresh');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Delete multiple build outputs
 | 
			
		||||
    $('#multi-output-delete').click(function() {
 | 
			
		||||
        var outputs = getTableData(table);
 | 
			
		||||
@@ -1421,25 +1472,6 @@ function loadBuildOutputTable(build_info, options={}) {
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Scrap multiple outputs
 | 
			
		||||
    $('#multi-output-scrap').click(function() {
 | 
			
		||||
        var outputs = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        scrapBuildOutputs(
 | 
			
		||||
            build_info.pk,
 | 
			
		||||
            outputs,
 | 
			
		||||
            {
 | 
			
		||||
                success: function() {
 | 
			
		||||
                    // Reload the "in progress" table
 | 
			
		||||
                    $('#build-output-table').bootstrapTable('refresh');
 | 
			
		||||
 | 
			
		||||
                    // Reload the "completed" table
 | 
			
		||||
                    $('#build-stock-table').bootstrapTable('refresh');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('#outputs-expand').click(function() {
 | 
			
		||||
        $(table).bootstrapTable('expandAllRows');
 | 
			
		||||
    });
 | 
			
		||||
@@ -2180,13 +2212,6 @@ function loadBuildTable(table, options) {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    linkButtonsToSelection(
 | 
			
		||||
        table,
 | 
			
		||||
        [
 | 
			
		||||
            '#build-print-options',
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
    makeDeleteButton,
 | 
			
		||||
    makeEditButton,
 | 
			
		||||
    makeIconBadge,
 | 
			
		||||
    orderParts,
 | 
			
		||||
    renderClipboard,
 | 
			
		||||
    renderDate,
 | 
			
		||||
    renderLink,
 | 
			
		||||
@@ -1213,6 +1214,43 @@ function deleteManufacturerPartParameters(selections, options={}) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Construct a set of actions for the manufacturer part table
 | 
			
		||||
function makeManufacturerPartActions(options={}) {
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
            label: 'order',
 | 
			
		||||
            title: '{% trans "Order parts" %}',
 | 
			
		||||
            icon: 'fa-shopping-cart',
 | 
			
		||||
            permission: 'purchase_order.add',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                let parts = [];
 | 
			
		||||
 | 
			
		||||
                data.forEach(function(item) {
 | 
			
		||||
                    let part = item.part_detail;
 | 
			
		||||
                    part.manufacturer_part = item.pk;
 | 
			
		||||
                    parts.push(part);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                orderParts(parts);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'delete',
 | 
			
		||||
            title: '{% trans "Delete manufacturer parts" %}',
 | 
			
		||||
            icon: 'fa-trash-alt icon-red',
 | 
			
		||||
            permission: 'purchase_order.delete',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                deleteManufacturerParts(data, {
 | 
			
		||||
                    success: function() {
 | 
			
		||||
                        $('#manufacturer-part-table').bootstrapTable('refresh');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Load manufacturer part table
 | 
			
		||||
 */
 | 
			
		||||
@@ -1226,7 +1264,18 @@ function loadManufacturerPartTable(table, url, options) {
 | 
			
		||||
 | 
			
		||||
    var filterTarget = options.filterTarget || '#filter-list-manufacturer-part';
 | 
			
		||||
 | 
			
		||||
    setupFilterList('manufacturer-part', $(table), filterTarget);
 | 
			
		||||
    setupFilterList('manufacturer-part', $(table), filterTarget, {
 | 
			
		||||
        custom_actions: [
 | 
			
		||||
            {
 | 
			
		||||
                label: 'manufacturer-part',
 | 
			
		||||
                title: '{% trans "Manufacturer part actions" %}',
 | 
			
		||||
                icon: 'fa-tools',
 | 
			
		||||
                actions: makeManufacturerPartActions({
 | 
			
		||||
                    manufacturer_id: options.params.manufacturer,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(table).inventreeTable({
 | 
			
		||||
        url: url,
 | 
			
		||||
@@ -1453,6 +1502,43 @@ function loadManufacturerPartParameterTable(table, url, options) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Construct a set of actions for the supplier part table
 | 
			
		||||
function makeSupplierPartActions(options={}) {
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
            label: 'order',
 | 
			
		||||
            title: '{% trans "Order parts" %}',
 | 
			
		||||
            icon: 'fa-shopping-cart',
 | 
			
		||||
            permission: 'purchase_order.add',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                let parts = []
 | 
			
		||||
 | 
			
		||||
                data.forEach(function(entry) {
 | 
			
		||||
                    parts.push(entry.part_detail);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                orderParts(parts, {
 | 
			
		||||
                    supplier: options.supplier_id,
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'delete',
 | 
			
		||||
            title: '{% trans "Delete supplier parts" %}',
 | 
			
		||||
            icon: 'fa-trash-alt icon-red',
 | 
			
		||||
            permission: 'purchase_order.delete',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                deleteSupplierParts(data, {
 | 
			
		||||
                    success: function() {
 | 
			
		||||
                        $('#supplier-part-table').bootstrapTable('refresh');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Load supplier part table
 | 
			
		||||
 */
 | 
			
		||||
@@ -1464,7 +1550,18 @@ function loadSupplierPartTable(table, url, options) {
 | 
			
		||||
    // Load filters
 | 
			
		||||
    var filters = loadTableFilters('supplierpart', params);
 | 
			
		||||
 | 
			
		||||
    setupFilterList('supplierpart', $(table));
 | 
			
		||||
    setupFilterList('supplierpart', $(table), '#filter-list-supplier-part', {
 | 
			
		||||
        custom_actions: [
 | 
			
		||||
            {
 | 
			
		||||
                label: 'supplier-part',
 | 
			
		||||
                title: '{% trans "Supplier part actions" %}',
 | 
			
		||||
                icon: 'fa-tools',
 | 
			
		||||
                actions: makeSupplierPartActions({
 | 
			
		||||
                    supplier_id: options.params.supplier,
 | 
			
		||||
                }),
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(table).inventreeTable({
 | 
			
		||||
        url: url,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
/* globals
 | 
			
		||||
    checkPermission,
 | 
			
		||||
    downloadTableData,
 | 
			
		||||
    getAvailableTableFilters,
 | 
			
		||||
    getTableData,
 | 
			
		||||
@@ -266,6 +267,102 @@ function generateFilterInput(tableKey, filterKey) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Construct a single action button based on the provided definition
 | 
			
		||||
 */
 | 
			
		||||
function makeFilterActionButton(button, options={}) {
 | 
			
		||||
    let prefix = options.prefix || 'action';
 | 
			
		||||
 | 
			
		||||
    // Check for required permission (if specified)
 | 
			
		||||
    if (button.permission) {
 | 
			
		||||
        if (!checkPermission(button.permission)) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return `
 | 
			
		||||
    <li><a class='dropdown-item' href='#' id='action-${prefix}-${button.label}'>
 | 
			
		||||
        <span class='fas ${button.icon}'></span> ${button.title}
 | 
			
		||||
    </a></li>`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Construct a set of custom actions for a given table
 | 
			
		||||
 */
 | 
			
		||||
function makeCustomActionGroup(action_group, table) {
 | 
			
		||||
 | 
			
		||||
    let buttons = [];
 | 
			
		||||
    let label = action_group.label || 'actions';
 | 
			
		||||
    let title = action_group.title || '{% trans "Actions" %}';
 | 
			
		||||
    let icon = action_group.icon || 'fa-tools';
 | 
			
		||||
 | 
			
		||||
    // Construct the HTML for each button
 | 
			
		||||
    action_group.actions.forEach(function(action) {
 | 
			
		||||
        buttons.push(makeFilterActionButton(action, {prefix: label}));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (buttons.length == 0) {
 | 
			
		||||
        // Don't display anything if there are no buttons to show
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let html = `
 | 
			
		||||
    <div class='btn-group' role='group'>
 | 
			
		||||
    <button id='${label}-actions' title='${title}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
 | 
			
		||||
        <span class='fas ${icon}'></span>
 | 
			
		||||
    </button>
 | 
			
		||||
    <ul class='dropdown-menu' role='menu'>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    buttons.forEach(function(button) {
 | 
			
		||||
        html += button;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    html += `</ul></div>`;
 | 
			
		||||
    return html;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Construct a set of custom barcode actions for a given table
 | 
			
		||||
 *
 | 
			
		||||
 * To define barcode actions for a data table, use options.barcode_actions
 | 
			
		||||
 */
 | 
			
		||||
function makeBarcodeActions(barcode_actions, table) {
 | 
			
		||||
 | 
			
		||||
    let html = `
 | 
			
		||||
    <div class='btn-group' role='group'>
 | 
			
		||||
    <button id='barcode-actions' title='{% trans "Barcode actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
 | 
			
		||||
        <span class='fas fa-qrcode'></span>
 | 
			
		||||
    </button>
 | 
			
		||||
    <ul class='dropdown-menu' role='menu'>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    barcode_actions.forEach(function(action) {
 | 
			
		||||
        html += makeFilterActionButton(action, {prefix: 'barcode'});
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    html += `</ul></div>`;
 | 
			
		||||
 | 
			
		||||
    return html;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Add callbacks for custom actions
 | 
			
		||||
 */
 | 
			
		||||
function addFilterActionCallbacks(element, label, table, actions) {
 | 
			
		||||
    actions.forEach(function(action) {
 | 
			
		||||
        let id = `action-${label}-${action.label}`;
 | 
			
		||||
        element.find(`#${id}`).click(function() {
 | 
			
		||||
            let data = getTableData(table);
 | 
			
		||||
            action.callback(data);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Helper function to make a 'filter' style button
 | 
			
		||||
 */
 | 
			
		||||
@@ -315,6 +412,19 @@ function setupFilterList(tableKey, table, target, options={}) {
 | 
			
		||||
 | 
			
		||||
    let report_button = options.report && global_settings.REPORT_ENABLE;
 | 
			
		||||
    let labels_button = options.labels && global_settings.LABEL_ENABLE;
 | 
			
		||||
    let barcode_actions = options.barcode_actions && global_settings.BARCODE_ENABLE;
 | 
			
		||||
 | 
			
		||||
    // Add in "custom" actions first (to the left of the table buttons)
 | 
			
		||||
    if (options.custom_actions) {
 | 
			
		||||
        options.custom_actions.forEach(function(action_group) {
 | 
			
		||||
            buttons += makeCustomActionGroup(action_group, table);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add in button for custom barcode actions
 | 
			
		||||
    if (barcode_actions) {
 | 
			
		||||
        buttons += makeBarcodeActions(options.barcode_actions, table);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (report_button || labels_button) {
 | 
			
		||||
        let print_buttons = `
 | 
			
		||||
@@ -394,6 +504,19 @@ function setupFilterList(tableKey, table, target, options={}) {
 | 
			
		||||
        element.append(filter_tag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Callback for custom actions
 | 
			
		||||
    if (options.custom_actions) {
 | 
			
		||||
        options.custom_actions.forEach(function(action_group) {
 | 
			
		||||
            let label = action_group.label || 'actions';
 | 
			
		||||
            addFilterActionCallbacks(element, label, table, action_group.actions);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Callback for barcode actions
 | 
			
		||||
    if (barcode_actions) {
 | 
			
		||||
        addFilterActionCallbacks(element, 'barcode', table, options.barcode_actions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Callback for printing reports
 | 
			
		||||
    if (options.report && global_settings.REPORT_ENABLE) {
 | 
			
		||||
        element.find(`#print-report-${tableKey}`).click(function() {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@
 | 
			
		||||
    inventreeLoad,
 | 
			
		||||
    inventreePut,
 | 
			
		||||
    inventreeSave,
 | 
			
		||||
    linkButtonsToSelection,
 | 
			
		||||
    loadTableFilters,
 | 
			
		||||
    makeDeleteButton,
 | 
			
		||||
    makeEditButton,
 | 
			
		||||
@@ -2159,6 +2158,69 @@ function partGridTile(part) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Update the category for a set of parts
 | 
			
		||||
 */
 | 
			
		||||
function setPartCategory(data, options={}) {
 | 
			
		||||
 | 
			
		||||
    let parts = [];
 | 
			
		||||
 | 
			
		||||
    data.forEach(function(item) {
 | 
			
		||||
        parts.push(item.pk);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var html = `
 | 
			
		||||
    <div class='alert alert-block alert-info'>
 | 
			
		||||
        {% trans "Set the part category for the selected parts" %}
 | 
			
		||||
    </div>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    constructForm('{% url "api-part-change-category" %}',{
 | 
			
		||||
        title: '{% trans "Set Part Category" %}',
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        preFormContent: html,
 | 
			
		||||
        fields: {
 | 
			
		||||
            category: {},
 | 
			
		||||
        },
 | 
			
		||||
        processBeforeUpload: function(data) {
 | 
			
		||||
            data.parts = parts;
 | 
			
		||||
            return data;
 | 
			
		||||
        },
 | 
			
		||||
        onSuccess: function() {
 | 
			
		||||
            $(options.table).bootstrapTable('refresh');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Construct a set of custom actions for the part table
 | 
			
		||||
 */
 | 
			
		||||
function makePartActions(table) {
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
            label: 'set-category',
 | 
			
		||||
            title: '{% trans "Set category" %}',
 | 
			
		||||
            icon: 'fa-sitemap',
 | 
			
		||||
            permission: 'part.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                setPartCategory(data, {table: table});
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'order',
 | 
			
		||||
            title: '{% trans "Order parts" %}',
 | 
			
		||||
            icon: 'fa-shopping-cart',
 | 
			
		||||
            permission: 'purchase_order.add',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                orderParts(data);
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Load part listing data into specified table.
 | 
			
		||||
 *
 | 
			
		||||
 * Args:
 | 
			
		||||
@@ -2190,6 +2252,14 @@ function loadPartTable(table, url, options={}) {
 | 
			
		||||
        },
 | 
			
		||||
        singular_name: '{% trans "part" %}',
 | 
			
		||||
        plural_name: '{% trans "parts" %}',
 | 
			
		||||
        custom_actions: [
 | 
			
		||||
            {
 | 
			
		||||
                label: 'parts',
 | 
			
		||||
                icon: 'fa-tools',
 | 
			
		||||
                title: '{% trans "Part actions" %}',
 | 
			
		||||
                actions: makePartActions(table),
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var columns = [
 | 
			
		||||
@@ -2438,97 +2508,6 @@ function loadPartTable(table, url, options={}) {
 | 
			
		||||
            return html;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (options.buttons) {
 | 
			
		||||
        linkButtonsToSelection($(table), options.buttons);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Button callbacks for part table buttons */
 | 
			
		||||
 | 
			
		||||
    // Callback function for the "order parts" button
 | 
			
		||||
    $('#multi-part-order').click(function() {
 | 
			
		||||
        var selections = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(part) {
 | 
			
		||||
            parts.push(part);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        orderParts(parts, {});
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback function for the "set category" button
 | 
			
		||||
    $('#multi-part-category').click(function() {
 | 
			
		||||
        var selections = getTableData(table);
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            parts.push(item.pk);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var html = `
 | 
			
		||||
        <div class='alert alert-block alert-info'>
 | 
			
		||||
            {% trans "Set the part category for the selected parts" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        constructFormBody({}, {
 | 
			
		||||
            title: '{% trans "Set Part Category" %}',
 | 
			
		||||
            preFormContent: html,
 | 
			
		||||
            fields: {
 | 
			
		||||
                category: {
 | 
			
		||||
                    label: '{% trans "Category" %}',
 | 
			
		||||
                    help_text: '{% trans "Select Part Category" %}',
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    type: 'related field',
 | 
			
		||||
                    model: 'partcategory',
 | 
			
		||||
                    api_url: '{% url "api-part-category-list" %}',
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            onSubmit: function(fields, opts) {
 | 
			
		||||
                var category = getFormFieldValue('category', fields['category'], opts);
 | 
			
		||||
 | 
			
		||||
                if (category == null) {
 | 
			
		||||
                    handleFormErrors(
 | 
			
		||||
                        {
 | 
			
		||||
                            'category': ['{% trans "Category is required" %}']
 | 
			
		||||
                        },
 | 
			
		||||
                        opts.fields,
 | 
			
		||||
                        opts
 | 
			
		||||
                    );
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Set the category for each part in sequence
 | 
			
		||||
                function setCategory() {
 | 
			
		||||
                    if (parts.length > 0) {
 | 
			
		||||
                        var part = parts.shift();
 | 
			
		||||
 | 
			
		||||
                        inventreePut(
 | 
			
		||||
                            `{% url "api-part-list" %}${part}/`,
 | 
			
		||||
                            {
 | 
			
		||||
                                category: category,
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                method: 'PATCH',
 | 
			
		||||
                                complete: setCategory,
 | 
			
		||||
                            }
 | 
			
		||||
                        );
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // We are done!
 | 
			
		||||
                        $(opts.modal).modal('hide');
 | 
			
		||||
 | 
			
		||||
                        $(table).bootstrapTable('refresh');
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Start the ball rolling
 | 
			
		||||
                showModalSpinner(opts.modal);
 | 
			
		||||
                setCategory();
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@
 | 
			
		||||
    inventreeLoad,
 | 
			
		||||
    inventreePut,
 | 
			
		||||
    launchModalForm,
 | 
			
		||||
    linkButtonsToSelection,
 | 
			
		||||
    loadTableFilters,
 | 
			
		||||
    makeCopyButton,
 | 
			
		||||
    makeDeleteButton,
 | 
			
		||||
@@ -606,7 +605,7 @@ function newSupplierPartFromOrderWizard(e) {
 | 
			
		||||
/*
 | 
			
		||||
 * Create a new form to order parts based on the list of provided parts.
 | 
			
		||||
 */
 | 
			
		||||
function orderParts(parts_list, options) {
 | 
			
		||||
function orderParts(parts_list, options={}) {
 | 
			
		||||
 | 
			
		||||
    var parts = [];
 | 
			
		||||
 | 
			
		||||
@@ -786,7 +785,7 @@ function orderParts(parts_list, options) {
 | 
			
		||||
        supplier_part_filters.manufacturer_part = options.manufacturer_part;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Construct API filtres for the PurchaseOrder field
 | 
			
		||||
    // Construct API filters for the PurchaseOrder field
 | 
			
		||||
    var order_filters = {
 | 
			
		||||
        status: {{ PurchaseOrderStatus.PENDING }},
 | 
			
		||||
        supplier_detail: true,
 | 
			
		||||
@@ -822,6 +821,10 @@ function orderParts(parts_list, options) {
 | 
			
		||||
 | 
			
		||||
                    $(opts.modal).find(`#info-pack-size-${pk}`).remove();
 | 
			
		||||
 | 
			
		||||
                    if (typeof value === 'object') {
 | 
			
		||||
                        value = value.pk;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (value != null) {
 | 
			
		||||
                        inventreeGet(
 | 
			
		||||
                            `/api/company/part/${value}/`,
 | 
			
		||||
@@ -1866,14 +1869,9 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
 | 
			
		||||
 | 
			
		||||
    var filters = loadTableFilters('purchaseorderlineitem', options.params);
 | 
			
		||||
 | 
			
		||||
    setupFilterList(
 | 
			
		||||
        'purchaseorderlineitem',
 | 
			
		||||
        $(table),
 | 
			
		||||
        options.filter_target || '#filter-list-purchase-order-lines',
 | 
			
		||||
        {
 | 
			
		||||
            download: true
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
    setupFilterList('purchaseorderlineitem', $(table), options.filter_target || '#filter-list-purchase-order-lines', {
 | 
			
		||||
        download: true,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function setupCallbacks() {
 | 
			
		||||
        if (options.allow_edit) {
 | 
			
		||||
@@ -2211,12 +2209,4 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    linkButtonsToSelection(
 | 
			
		||||
        table,
 | 
			
		||||
        [
 | 
			
		||||
            '#multi-select-options',
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
/* globals
 | 
			
		||||
    checkPermission,
 | 
			
		||||
    getModelRenderer,
 | 
			
		||||
    inventreeGet,
 | 
			
		||||
    inventreePut,
 | 
			
		||||
@@ -22,40 +23,6 @@ function closeSearchPanel() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Keep track of the roles / permissions available to the current user
 | 
			
		||||
var search_user_roles = null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Check if the user has the specified role and permission
 | 
			
		||||
 */
 | 
			
		||||
function checkPermission(role, permission='view') {
 | 
			
		||||
 | 
			
		||||
    if (!search_user_roles) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!(role in search_user_roles)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var roles = search_user_roles[role];
 | 
			
		||||
 | 
			
		||||
    if (!roles) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var found = false;
 | 
			
		||||
 | 
			
		||||
    search_user_roles[role].forEach(function(p) {
 | 
			
		||||
        if (String(p).valueOf() == String(permission).valueOf()) {
 | 
			
		||||
            found = true;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return found;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Callback when the search panel is opened.
 | 
			
		||||
@@ -71,15 +38,6 @@ function openSearchPanel() {
 | 
			
		||||
 | 
			
		||||
    clearSearchResults();
 | 
			
		||||
 | 
			
		||||
    // Request user roles if we do not have them
 | 
			
		||||
    if (search_user_roles == null) {
 | 
			
		||||
        inventreeGet('{% url "api-user-roles" %}', {}, {
 | 
			
		||||
            success: function(response) {
 | 
			
		||||
                search_user_roles = response.roles || {};
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Callback for text input changed
 | 
			
		||||
    search_input.on('keyup change', searchTextChanged);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,6 @@
 | 
			
		||||
    inventreePut,
 | 
			
		||||
    inventreeSave,
 | 
			
		||||
    launchModalForm,
 | 
			
		||||
    linkButtonsToSelection,
 | 
			
		||||
    loadTableFilters,
 | 
			
		||||
    makeDeleteButton,
 | 
			
		||||
    makeEditButton,
 | 
			
		||||
@@ -1707,6 +1706,121 @@ function locationDetail(row, showLink=true) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Construct a set of custom actions for the stock table
 | 
			
		||||
 */
 | 
			
		||||
function makeStockActions(table) {
 | 
			
		||||
    let actions = [
 | 
			
		||||
        {
 | 
			
		||||
            label: 'add',
 | 
			
		||||
            icon: 'fa-plus-circle icon-green',
 | 
			
		||||
            title: '{% trans "Add stock" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                stockAdjustment('add', data, table);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'remove',
 | 
			
		||||
            icon: 'fa-minus-circle icon-red',
 | 
			
		||||
            title: '{% trans "Remove stock" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                stockAdjustment('take', data, table);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'stocktake',
 | 
			
		||||
            icon: 'fa-check-circle icon-blue',
 | 
			
		||||
            title: '{% trans "Count stock" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                stockAdjustment('count', data, table);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'move',
 | 
			
		||||
            icon: 'fa-exchange-alt icon-blue',
 | 
			
		||||
            title: '{% trans "Transfer stock" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                stockAdjustment('move', data, table);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'status',
 | 
			
		||||
            icon: 'fa-info-circle icon-blue',
 | 
			
		||||
            title: '{% trans "Change stock status" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                setStockStatus(data, {table: table});
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'merge',
 | 
			
		||||
            icon: 'fa-object-group',
 | 
			
		||||
            title: '{% trans "Merge stock" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                mergeStockItems(data, {
 | 
			
		||||
                    success: function(response) {
 | 
			
		||||
                        $(table).bootstrapTable('refresh');
 | 
			
		||||
 | 
			
		||||
                        showMessage('{% trans "Merged stock items" %}', {
 | 
			
		||||
                            style: 'success',
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'order',
 | 
			
		||||
            icon: 'fa-shopping-cart',
 | 
			
		||||
            title: '{% trans "Order stock" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                let parts = [];
 | 
			
		||||
 | 
			
		||||
                data.forEach(function(item) {
 | 
			
		||||
                    var part = item.part_detail;
 | 
			
		||||
 | 
			
		||||
                    if (part) {
 | 
			
		||||
                        parts.push(part);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                orderParts(parts, {});
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'assign',
 | 
			
		||||
            icon: 'fa-user-tie',
 | 
			
		||||
            title: '{% trans "Assign to customer" %}',
 | 
			
		||||
            permission: 'stock.change',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                assignStockToCustomer(data, {
 | 
			
		||||
                    success: function() {
 | 
			
		||||
                        $(table).bootstrapTable('refresh');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: 'delete',
 | 
			
		||||
            icon: 'fa-trash-alt icon-red',
 | 
			
		||||
            title: '{% trans "Delete stock" %}',
 | 
			
		||||
            permission: 'stock.delete',
 | 
			
		||||
            callback: function(data) {
 | 
			
		||||
                stockAdjustment('delete', data, table);
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return actions;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* 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.
 | 
			
		||||
@@ -1746,7 +1860,26 @@ function loadStockTable(table, options) {
 | 
			
		||||
            key: 'item',
 | 
			
		||||
        },
 | 
			
		||||
        singular_name: '{% trans "stock item" %}',
 | 
			
		||||
        plural_name: '{% trans "stock items" %}'
 | 
			
		||||
        plural_name: '{% trans "stock items" %}',
 | 
			
		||||
        barcode_actions: [
 | 
			
		||||
            {
 | 
			
		||||
                icon: 'fa-sitemap',
 | 
			
		||||
                label: 'scantolocation',
 | 
			
		||||
                title: '{% trans "Scan to location" %}',
 | 
			
		||||
                permission: 'stock.change',
 | 
			
		||||
                callback: function(items) {
 | 
			
		||||
                    scanItemsIntoLocation(items);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        custom_actions: [
 | 
			
		||||
            {
 | 
			
		||||
                actions: makeStockActions(table),
 | 
			
		||||
                icon: 'fa-boxes',
 | 
			
		||||
                title: '{% trans "Stock Actions" %}',
 | 
			
		||||
                label: 'stock',
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Override the default values, or add new ones
 | 
			
		||||
@@ -2267,114 +2400,6 @@ function loadStockTable(table, options) {
 | 
			
		||||
        buttons.push('#stock-barcode-options');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    linkButtonsToSelection(
 | 
			
		||||
        table,
 | 
			
		||||
        buttons,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    function stockAdjustment(action) {
 | 
			
		||||
        var items = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        adjustStock(action, items, {
 | 
			
		||||
            success: function() {
 | 
			
		||||
                $(table).bootstrapTable('refresh');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Automatically link button callbacks
 | 
			
		||||
    if (global_settings.BARCODE_ENABLE) {
 | 
			
		||||
        $('#multi-item-barcode-scan-into-location').click(function() {
 | 
			
		||||
            var selections = getTableData(table);
 | 
			
		||||
 | 
			
		||||
            var items = [];
 | 
			
		||||
 | 
			
		||||
            selections.forEach(function(item) {
 | 
			
		||||
                items.push(item);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            scanItemsIntoLocation(items);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Callback for 'stocktake' button
 | 
			
		||||
    $('#multi-item-stocktake').click(function() {
 | 
			
		||||
        stockAdjustment('count');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'remove stock' button
 | 
			
		||||
    $('#multi-item-remove').click(function() {
 | 
			
		||||
        stockAdjustment('take');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'add stock' button
 | 
			
		||||
    $('#multi-item-add').click(function() {
 | 
			
		||||
        stockAdjustment('add');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'move stock' button
 | 
			
		||||
    $('#multi-item-move').click(function() {
 | 
			
		||||
        stockAdjustment('move');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'merge stock' button
 | 
			
		||||
    $('#multi-item-merge').click(function() {
 | 
			
		||||
        var items = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        mergeStockItems(items, {
 | 
			
		||||
            success: function(response) {
 | 
			
		||||
                $(table).bootstrapTable('refresh');
 | 
			
		||||
 | 
			
		||||
                showMessage('{% trans "Merged stock items" %}', {
 | 
			
		||||
                    style: 'success',
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'assign stock' button
 | 
			
		||||
    $('#multi-item-assign').click(function() {
 | 
			
		||||
 | 
			
		||||
        var items = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        assignStockToCustomer(items, {
 | 
			
		||||
            success: function() {
 | 
			
		||||
                $(table).bootstrapTable('refresh');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'un-assign stock' button
 | 
			
		||||
    $('#multi-item-order').click(function() {
 | 
			
		||||
 | 
			
		||||
        var selections = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            var part = item.part_detail;
 | 
			
		||||
 | 
			
		||||
            if (part) {
 | 
			
		||||
                parts.push(part);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        orderParts(parts, {});
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'delete stock' button
 | 
			
		||||
    $('#multi-item-delete').click(function() {
 | 
			
		||||
        var selections = getTableData(table);
 | 
			
		||||
 | 
			
		||||
        var stock = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            stock.push(item.pk);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        stockAdjustment('delete');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Callback for 'change status' button
 | 
			
		||||
    $('#multi-item-status').click(function() {
 | 
			
		||||
        let selections = getTableData(table);
 | 
			
		||||
@@ -2384,35 +2409,9 @@ function loadStockTable(table, options) {
 | 
			
		||||
            items.push(item.pk);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (items.length == 0) {
 | 
			
		||||
            showAlertDialog(
 | 
			
		||||
                '{% trans "Select Stock Items" %}',
 | 
			
		||||
                '{% trans "Select one or more stock items" %}'
 | 
			
		||||
            );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let html = `
 | 
			
		||||
        <div class='alert alert-info alert-block>
 | 
			
		||||
        {% trans "Selected stock items" %}: ${items.length}
 | 
			
		||||
        </div>`;
 | 
			
		||||
 | 
			
		||||
        constructForm('{% url "api-stock-change-status" %}', {
 | 
			
		||||
            title: '{% trans "Change Stock Status" %}',
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            preFormContent: html,
 | 
			
		||||
            fields: {
 | 
			
		||||
                status: {},
 | 
			
		||||
                note: {},
 | 
			
		||||
            },
 | 
			
		||||
            processBeforeUpload: function(data) {
 | 
			
		||||
                data.items = items;
 | 
			
		||||
                return data;
 | 
			
		||||
            },
 | 
			
		||||
            onSuccess: function() {
 | 
			
		||||
                $(table).bootstrapTable('refresh');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -2917,10 +2916,6 @@ function loadStockTrackingTable(table, options) {
 | 
			
		||||
        url: options.url,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (options.buttons) {
 | 
			
		||||
        linkButtonsToSelection(table, options.buttons);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table.on('click', '.btn-entry-edit', function() {
 | 
			
		||||
        var button = $(this);
 | 
			
		||||
 | 
			
		||||
@@ -3131,3 +3126,55 @@ function installStockItem(stock_item_id, part_id, options={}) {
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Perform the specified stock adjustment action against the selected items
 | 
			
		||||
function stockAdjustment(action, items, table) {
 | 
			
		||||
    adjustStock(action, items, {
 | 
			
		||||
        success: function() {
 | 
			
		||||
            $(table).bootstrapTable('refresh');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Set the status of the selected stock items
 | 
			
		||||
 */
 | 
			
		||||
function setStockStatus(items, options={}) {
 | 
			
		||||
 | 
			
		||||
    if (items.length == 0) {
 | 
			
		||||
        showAlertDialog(
 | 
			
		||||
            '{% trans "Select Stock Items" %}',
 | 
			
		||||
            '{% trans "Select one or more stock items" %}'
 | 
			
		||||
        );
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let id_values = [];
 | 
			
		||||
 | 
			
		||||
    items.forEach(function(item) {
 | 
			
		||||
        id_values.push(item.pk)
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let html = `
 | 
			
		||||
    <div class='alert alert-info alert-block>
 | 
			
		||||
    {% trans "Selected stock items" %}: ${items.length}
 | 
			
		||||
    </div>`;
 | 
			
		||||
 | 
			
		||||
    constructForm('{% url "api-stock-change-status" %}', {
 | 
			
		||||
        title: '{% trans "Change Stock Status" %}',
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        preFormContent: html,
 | 
			
		||||
        fields: {
 | 
			
		||||
            status: {},
 | 
			
		||||
            note: {},
 | 
			
		||||
        },
 | 
			
		||||
        processBeforeUpload: function(data) {
 | 
			
		||||
            data.items = items;
 | 
			
		||||
            return data;
 | 
			
		||||
        },
 | 
			
		||||
        onSuccess: function() {
 | 
			
		||||
            $(options.table).bootstrapTable('refresh');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,12 @@ function constructHasProjectCodeFilter() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Reset a dictionary of filters for the attachment table
 | 
			
		||||
function getAttachmentFilters() {
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Return a dictionary of filters for the return order table
 | 
			
		||||
function getReturnOrderFilters() {
 | 
			
		||||
    var filters = {
 | 
			
		||||
@@ -487,6 +493,11 @@ function getBuildTableFilters() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getBuildItemTableFilters() {
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Return a dictionary of filters for the "build lines" table
 | 
			
		||||
function getBuildLineTableFilters() {
 | 
			
		||||
    return {
 | 
			
		||||
@@ -770,6 +781,16 @@ function getAvailableTableFilters(tableKey) {
 | 
			
		||||
    tableKey = tableKey.toLowerCase();
 | 
			
		||||
 | 
			
		||||
    switch (tableKey) {
 | 
			
		||||
    case 'attachments':
 | 
			
		||||
        return getAttachmentFilters();
 | 
			
		||||
    case 'build':
 | 
			
		||||
        return getBuildTableFilters();
 | 
			
		||||
    case 'builditems':
 | 
			
		||||
        return getBuildItemTableFilters();
 | 
			
		||||
    case 'buildlines':
 | 
			
		||||
        return getBuildLineTableFilters();
 | 
			
		||||
    case 'bom':
 | 
			
		||||
        return getBOMTableFilters();
 | 
			
		||||
    case 'category':
 | 
			
		||||
        return getPartCategoryFilters();
 | 
			
		||||
    case 'company':
 | 
			
		||||
@@ -778,12 +799,6 @@ function getAvailableTableFilters(tableKey) {
 | 
			
		||||
        return getContactFilters();
 | 
			
		||||
    case 'customerstock':
 | 
			
		||||
        return getCustomerStockFilters();
 | 
			
		||||
    case 'bom':
 | 
			
		||||
        return getBOMTableFilters();
 | 
			
		||||
    case 'build':
 | 
			
		||||
        return getBuildTableFilters();
 | 
			
		||||
    case 'buildlines':
 | 
			
		||||
        return getBuildLineTableFilters();
 | 
			
		||||
    case 'location':
 | 
			
		||||
        return getStockLocationFilters();
 | 
			
		||||
    case 'parameters':
 | 
			
		||||
 
 | 
			
		||||
@@ -38,10 +38,10 @@ function reloadBootstrapTable(table) {
 | 
			
		||||
        if (tbl.exists()) {
 | 
			
		||||
            tbl.bootstrapTable('refresh');
 | 
			
		||||
        } else {
 | 
			
		||||
            console.error(`Invalid table name passed to reloadTable(): ${table}`);
 | 
			
		||||
            console.error(`Invalid table name passed to reloadBootstrapTable(): ${table}`);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        console.error(`Null value passed to reloadTable()`);
 | 
			
		||||
        console.error(`Null value passed to reloadBootstrapTable()`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -220,25 +220,6 @@ function enableButtons(elements, enabled) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Link a bootstrap-table object to one or more buttons.
 | 
			
		||||
 * The buttons will only be enabled if there is at least one row selected
 | 
			
		||||
 */
 | 
			
		||||
function linkButtonsToSelection(table, buttons) {
 | 
			
		||||
 | 
			
		||||
    if (typeof table === 'string') {
 | 
			
		||||
        table = $(table);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initially set the enable state of the buttons
 | 
			
		||||
    enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
 | 
			
		||||
 | 
			
		||||
    // Add a callback
 | 
			
		||||
    table.on('check.bs.table uncheck.bs.table check-some.bs.table uncheck-some.bs.table check-all.bs.table uncheck-all.bs.table', function() {
 | 
			
		||||
        enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns true if the input looks like a valid number
 | 
			
		||||
 * @param {String} n
 | 
			
		||||
@@ -474,11 +455,6 @@ $.fn.inventreeTable = function(options) {
 | 
			
		||||
            console.error(`Could not get list of visible columns for table '${tableName}'`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Optionally, link buttons to the table selection
 | 
			
		||||
    if (options.buttons) {
 | 
			
		||||
        linkButtonsToSelection(table, options.buttons);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,46 +9,7 @@
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
<div id='{{ prefix }}button-toolbar'>
 | 
			
		||||
    <div class='button-toolbar container-fluid' style='float: right;'>
 | 
			
		||||
        <div class='btn-group' role='group'>
 | 
			
		||||
            {% if barcodes %}
 | 
			
		||||
            <!-- Barcode actions menu -->
 | 
			
		||||
            <div class='btn-group' role='group'>
 | 
			
		||||
                <button id='stock-barcode-options' class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle='dropdown' title='{% trans "Barcode Actions" %}'>
 | 
			
		||||
                    <span class='fas fa-qrcode'></span> <span class='caret'></span>
 | 
			
		||||
                </button>
 | 
			
		||||
                <ul class='dropdown-menu'>
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-item-barcode-scan-into-location' title='{% trans "Scan to Location" %}'><span class='fas fa-sitemap'></span> {% trans "Scan to Location" %}</a></li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% if not read_only %}
 | 
			
		||||
            {% if roles.stock.change or roles.stock.delete %}
 | 
			
		||||
            <div class="btn-group" role="group">
 | 
			
		||||
                <button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" title='{% trans "Stock Options" %}'>
 | 
			
		||||
                    <span class='fas fa-boxes'></span> <span class="caret"></span>
 | 
			
		||||
                </button>
 | 
			
		||||
                <ul class="dropdown-menu">
 | 
			
		||||
                    {% if roles.stock.change %}
 | 
			
		||||
                    <li><a class='dropdown-item' href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li>
 | 
			
		||||
                    <li><a class='dropdown-item' href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'><span class='fas fa-minus-circle icon-red'></span> {% trans "Remove stock" %}</a></li>
 | 
			
		||||
                    <li><a class='dropdown-item' href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle icon-green'></span> {% trans "Count stock" %}</a></li>
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt icon-blue'></span> {% trans "Transfer stock" %}</a></li>
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-item-status' title='{% trans "Change stock status" %}'><span class='fas fa-info-circle icon-blue'></span> {% trans "Change stock status" %}</a></li>
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-item-merge' title='{% trans "Merge selected stock items" %}'><span class='fas fa-object-group'></span> {% trans "Merge stock" %}</a></li>
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li>
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-item-assign' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    {% if roles.stock.delete %}
 | 
			
		||||
                    <li><a class='dropdown-item' href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete stock" %}</a></li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% include "filter_list.html" with prefix=prefix id="stock" %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% include "filter_list.html" with prefix=prefix id="stock" %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<table class='table table-striped table-condensed' data-toolbar='#{{ prefix }}button-toolbar' id='{{ prefix }}stock-table'>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user