mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	Merge remote-tracking branch 'inventree/master' into scheduling
# Conflicts: # InvenTree/InvenTree/version.py (Update API version)
This commit is contained in:
		| @@ -20,6 +20,7 @@ | ||||
|  | ||||
| /* exported | ||||
|     allocateStockToBuild, | ||||
|     autoAllocateStockToBuild, | ||||
|     completeBuildOrder, | ||||
|     createBuildOutput, | ||||
|     editBuildOrder, | ||||
| @@ -754,7 +755,7 @@ function loadBuildOutputTable(build_info, options={}) { | ||||
|         filters[key] = params[key]; | ||||
|     } | ||||
|  | ||||
|     // TODO: Initialize filter list | ||||
|     setupFilterList('builditems', $(table), options.filterTarget || '#filter-list-incompletebuilditems'); | ||||
|  | ||||
|     function setupBuildOutputButtonCallbacks() { | ||||
|          | ||||
| @@ -999,7 +1000,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { | ||||
|         filters[key] = params[key]; | ||||
|     } | ||||
|  | ||||
|     setupFilterList('builditems', $(table), options.filterTarget || null); | ||||
|     setupFilterList('builditems', $(table), options.filterTarget); | ||||
|  | ||||
|     // If an "output" is specified, then only "trackable" parts are allocated | ||||
|     // Otherwise, only "untrackable" parts are allowed | ||||
| @@ -1512,6 +1513,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { | ||||
|  */ | ||||
| function allocateStockToBuild(build_id, part_id, bom_items, options={}) { | ||||
|  | ||||
|     if (bom_items.length == 0) { | ||||
|  | ||||
|         showAlertDialog( | ||||
|             '{% trans "Select Parts" %}', | ||||
|             '{% trans "You must select at least one part to allocate" %}', | ||||
|         ); | ||||
|  | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // ID of the associated "build output" (or null) | ||||
|     var output_id = options.output || null; | ||||
|  | ||||
| @@ -1626,8 +1637,8 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { | ||||
|     if (table_entries.length == 0) { | ||||
|  | ||||
|         showAlertDialog( | ||||
|             '{% trans "Select Parts" %}', | ||||
|             '{% trans "You must select at least one part to allocate" %}', | ||||
|             '{% trans "All Parts Allocated" %}', | ||||
|             '{% trans "All selected parts have been fully allocated" %}', | ||||
|         ); | ||||
|  | ||||
|         return; | ||||
| @@ -1844,6 +1855,48 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Automatically allocate stock items to a build | ||||
|  */ | ||||
| function autoAllocateStockToBuild(build_id, bom_items=[], options={}) { | ||||
|  | ||||
|     var html = ` | ||||
|     <div class='alert alert-block alert-info'> | ||||
|     <strong>{% trans "Automatic Stock Allocation" %}</strong><br> | ||||
|     {% trans "Stock items will be automatically allocated to this build order, according to the provided guidelines" %}: | ||||
|     <ul> | ||||
|         <li>{% trans "If a location is specifed, stock will only be allocated from that location" %}</li> | ||||
|         <li>{% trans "If stock is considered interchangeable, it will be allocated from the first location it is found" %}</li> | ||||
|         <li>{% trans "If substitute stock is allowed, it will be used where stock of the primary part cannot be found" %}</li> | ||||
|     </ul> | ||||
|     </div> | ||||
|     `; | ||||
|  | ||||
|     var fields = { | ||||
|         location: { | ||||
|             value: options.location, | ||||
|         }, | ||||
|         interchangeable: { | ||||
|             value: true, | ||||
|         }, | ||||
|         substitutes: { | ||||
|             value: true, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     constructForm(`/api/build/${build_id}/auto-allocate/`, { | ||||
|         method: 'POST', | ||||
|         fields: fields, | ||||
|         title: '{% trans "Allocate Stock Items" %}', | ||||
|         confirm: true, | ||||
|         preFormContent: html, | ||||
|         onSuccess: function(response) { | ||||
|             $('#allocation-table-untracked').bootstrapTable('refresh'); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Display a table of Build orders | ||||
|  */ | ||||
|   | ||||
| @@ -256,7 +256,7 @@ function generateFilterInput(tableKey, filterKey) { | ||||
|  * @param {*} table - bootstrapTable element to update | ||||
|  * @param {*} target - name of target element on page | ||||
|  */ | ||||
| function setupFilterList(tableKey, table, target) { | ||||
| function setupFilterList(tableKey, table, target, options={}) { | ||||
|  | ||||
|     var addClicked = false; | ||||
|  | ||||
| @@ -283,6 +283,11 @@ function setupFilterList(tableKey, table, target) { | ||||
|  | ||||
|     var buttons = ''; | ||||
|  | ||||
|     // Add download button | ||||
|     if (options.download) { | ||||
|         buttons += `<button id='download-${tableKey}' title='{% trans "Download data" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-download'></span></button>`; | ||||
|     } | ||||
|  | ||||
|     buttons += `<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-redo-alt'></span></button>`; | ||||
|  | ||||
|     // If there are filters defined for this table, add more buttons | ||||
| @@ -295,7 +300,7 @@ function setupFilterList(tableKey, table, target) { | ||||
|     } | ||||
|  | ||||
|     element.html(` | ||||
|     <div class='btn-group' role='group'> | ||||
|     <div class='btn-group filter-group' role='group'> | ||||
|         ${buttons} | ||||
|     </div> | ||||
|     `); | ||||
| @@ -322,6 +327,13 @@ function setupFilterList(tableKey, table, target) { | ||||
|         $(table).bootstrapTable('refresh'); | ||||
|     }); | ||||
|  | ||||
|     // Add a callback for downloading table data | ||||
|     if (options.download) { | ||||
|         element.find(`#download-${tableKey}`).click(function() { | ||||
|             downloadTableData($(table)); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // Add a callback for adding a new filter | ||||
|     element.find(`#${add}`).click(function clicked() { | ||||
|  | ||||
| @@ -358,14 +370,14 @@ function setupFilterList(tableKey, table, target) { | ||||
|                     reloadTableFilters(table, filters); | ||||
|  | ||||
|                     // Run this function again | ||||
|                     setupFilterList(tableKey, table, target); | ||||
|                     setupFilterList(tableKey, table, target, options); | ||||
|                 } | ||||
|  | ||||
|             }); | ||||
|         } else { | ||||
|             addClicked = false; | ||||
|  | ||||
|             setupFilterList(tableKey, table, target); | ||||
|             setupFilterList(tableKey, table, target, options); | ||||
|         } | ||||
|  | ||||
|     }); | ||||
| @@ -376,7 +388,7 @@ function setupFilterList(tableKey, table, target) { | ||||
|  | ||||
|         reloadTableFilters(table, filters); | ||||
|  | ||||
|         setupFilterList(tableKey, table, target); | ||||
|         setupFilterList(tableKey, table, target, options); | ||||
|     }); | ||||
|  | ||||
|     // Add callback for deleting each filter | ||||
| @@ -390,7 +402,7 @@ function setupFilterList(tableKey, table, target) { | ||||
|         reloadTableFilters(table, filters); | ||||
|  | ||||
|         // Run this function again! | ||||
|         setupFilterList(tableKey, table, target); | ||||
|         setupFilterList(tableKey, table, target, options); | ||||
|     }); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -281,6 +281,65 @@ function createPurchaseOrder(options={}) { | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Construct a set of fields for the SalesOrderLineItem form */ | ||||
| function soLineItemFields(options={}) { | ||||
|  | ||||
|     var fields = { | ||||
|         order: { | ||||
|             hidden: true, | ||||
|         }, | ||||
|         part: {}, | ||||
|         quantity: {}, | ||||
|         reference: {}, | ||||
|         sale_price: {}, | ||||
|         sale_price_currency: {}, | ||||
|         target_date: {}, | ||||
|         notes: {}, | ||||
|     }; | ||||
|  | ||||
|     if (options.order) { | ||||
|         fields.order.value = options.order; | ||||
|     } | ||||
|  | ||||
|     return fields; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Construct a set of fields for the PurchaseOrderLineItem form */ | ||||
| function poLineItemFields(options={}) { | ||||
|  | ||||
|     var fields = { | ||||
|         order: { | ||||
|             hidden: true, | ||||
|         }, | ||||
|         part: { | ||||
|             filters: { | ||||
|                 part_detail: true, | ||||
|                 supplier_detail: true, | ||||
|                 supplier: options.supplier, | ||||
|             } | ||||
|         }, | ||||
|         quantity: {}, | ||||
|         reference: {}, | ||||
|         purchase_price: {}, | ||||
|         purchase_price_currency: {}, | ||||
|         target_date: {}, | ||||
|         destination: {}, | ||||
|         notes: {}, | ||||
|     }; | ||||
|  | ||||
|     if (options.order) { | ||||
|         fields.order.value = options.order; | ||||
|     } | ||||
|  | ||||
|     if (options.currency) { | ||||
|         fields.purchase_price_currency.value = options.currency; | ||||
|     } | ||||
|  | ||||
|     return fields; | ||||
| } | ||||
|  | ||||
|  | ||||
| function removeOrderRowFromOrderWizard(e) { | ||||
|     /* Remove a part selection from an order form. */ | ||||
|  | ||||
| @@ -293,6 +352,7 @@ function removeOrderRowFromOrderWizard(e) { | ||||
|     $('#' + row).remove(); | ||||
| } | ||||
|  | ||||
|  | ||||
| function newSupplierPartFromOrderWizard(e) { | ||||
|     /* Create a new supplier part directly from an order form. | ||||
|      * Launches a secondary modal and (if successful), | ||||
| @@ -899,7 +959,7 @@ function loadPurchaseOrderTable(table, options) { | ||||
|                 sortable: true, | ||||
|                 sortName: 'supplier__name', | ||||
|                 formatter: function(value, row) { | ||||
|                     return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/purchase-orders/`); | ||||
|                     return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/?display=purchase-orders`); | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
| @@ -991,10 +1051,36 @@ function loadPurchaseOrderLineItemTable(table, options={}) { | ||||
|      | ||||
|     var target = options.filter_target || '#filter-list-purchase-order-lines'; | ||||
|  | ||||
|     setupFilterList('purchaseorderlineitem', $(table), target); | ||||
|     setupFilterList('purchaseorderlineitem', $(table), target, {download: true}); | ||||
|  | ||||
|     function setupCallbacks() { | ||||
|         if (options.allow_edit) { | ||||
|  | ||||
|             // Callback for "duplicate" button | ||||
|             $(table).find('.button-line-duplicate').click(function() { | ||||
|                 var pk = $(this).attr('pk'); | ||||
|  | ||||
|                 inventreeGet(`/api/order/po-line/${pk}/`, {}, { | ||||
|                     success: function(data) { | ||||
|  | ||||
|                         var fields = poLineItemFields({ | ||||
|                             supplier: options.supplier, | ||||
|                         }); | ||||
|  | ||||
|                         constructForm('{% url "api-po-line-list" %}', { | ||||
|                             method: 'POST', | ||||
|                             fields: fields, | ||||
|                             data: data, | ||||
|                             title: '{% trans "Duplicate Line Item" %}', | ||||
|                             onSuccess: function(response) { | ||||
|                                 $(table).bootstrapTable('refresh'); | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // Callback for "edit" button | ||||
|             $(table).find('.button-line-edit').click(function() { | ||||
|                 var pk = $(this).attr('pk'); | ||||
|  | ||||
| @@ -1022,6 +1108,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) { | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // Callback for "delete" button | ||||
|             $(table).find('.button-line-delete').click(function() { | ||||
|                 var pk = $(this).attr('pk'); | ||||
|  | ||||
| @@ -1270,6 +1357,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) { | ||||
|                     } | ||||
|  | ||||
|                     if (options.allow_edit) { | ||||
|                         html += makeIconButton('fa-clone', 'button-line-duplicate', pk, '{% trans "Duplicate line item" %}'); | ||||
|                         html += makeIconButton('fa-edit icon-blue', 'button-line-edit', pk, '{% trans "Edit line item" %}'); | ||||
|                         html += makeIconButton('fa-trash-alt icon-red', 'button-line-delete', pk, '{% trans "Delete line item" %}'); | ||||
|                     } | ||||
| @@ -2449,6 +2537,7 @@ function loadSalesOrderLineItemTable(table, options={}) { | ||||
|                     html += makeIconButton('fa-dollar-sign icon-green', 'button-price', pk, '{% trans "Calculate price" %}'); | ||||
|                 } | ||||
|  | ||||
|                 html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line item" %}'); | ||||
|                 html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}'); | ||||
|  | ||||
|                 var delete_disabled = false; | ||||
| @@ -2480,6 +2569,28 @@ function loadSalesOrderLineItemTable(table, options={}) { | ||||
|     // Configure callback functions once the table is loaded | ||||
|     function setupCallbacks() { | ||||
|  | ||||
|         // Callback for duplicating line items | ||||
|         $(table).find('.button-duplicate').click(function() { | ||||
|             var pk = $(this).attr('pk'); | ||||
|  | ||||
|             inventreeGet(`/api/order/so-line/${pk}/`, {}, { | ||||
|                 success: function(data) { | ||||
|  | ||||
|                     var fields = soLineItemFields(); | ||||
|  | ||||
|                     constructForm('{% url "api-so-line-list" %}', { | ||||
|                         method: 'POST', | ||||
|                         fields: fields, | ||||
|                         data: data, | ||||
|                         title: '{% trans "Duplicate Line Item" %}', | ||||
|                         onSuccess: function(response) { | ||||
|                             $(table).bootstrapTable('refresh'); | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         // Callback for editing line items | ||||
|         $(table).find('.button-edit').click(function() { | ||||
|             var pk = $(this).attr('pk'); | ||||
|   | ||||
| @@ -1219,7 +1219,7 @@ function loadPartTable(table, url, options={}) { | ||||
|         filters[key] = params[key]; | ||||
|     } | ||||
|  | ||||
|     setupFilterList('parts', $(table), options.filterTarget || null); | ||||
|     setupFilterList('parts', $(table), options.filterTarget, {download: true}); | ||||
|  | ||||
|     var columns = [ | ||||
|         { | ||||
|   | ||||
| @@ -43,7 +43,6 @@ | ||||
|     duplicateStockItem, | ||||
|     editStockItem, | ||||
|     editStockLocation, | ||||
|     exportStock, | ||||
|     findStockItemBySerialNumber, | ||||
|     installStockItem, | ||||
|     loadInstalledInTable, | ||||
| @@ -506,49 +505,6 @@ function stockStatusCodes() { | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Export stock table | ||||
|  */ | ||||
| function exportStock(params={}) { | ||||
|  | ||||
|     constructFormBody({}, { | ||||
|         title: '{% trans "Export Stock" %}', | ||||
|         fields: { | ||||
|             format: { | ||||
|                 label: '{% trans "Format" %}', | ||||
|                 help_text: '{% trans "Select file format" %}', | ||||
|                 required: true, | ||||
|                 type: 'choice', | ||||
|                 value: 'csv', | ||||
|                 choices: exportFormatOptions(), | ||||
|             }, | ||||
|             sublocations: { | ||||
|                 label: '{% trans "Include Sublocations" %}', | ||||
|                 help_text: '{% trans "Include stock items in sublocations" %}', | ||||
|                 type: 'boolean', | ||||
|                 value: 'true', | ||||
|             } | ||||
|         }, | ||||
|         onSubmit: function(fields, form_options) { | ||||
|  | ||||
|             var format = getFormFieldValue('format', fields['format'], form_options); | ||||
|             var cascade = getFormFieldValue('sublocations', fields['sublocations'], form_options); | ||||
|  | ||||
|             // Hide the modal | ||||
|             $(form_options.modal).modal('hide'); | ||||
|  | ||||
|             var url = `{% url "stock-export" %}?format=${format}&cascade=${cascade}`; | ||||
|  | ||||
|             for (var key in params) { | ||||
|                 url += `&${key}=${params[key]}`; | ||||
|             } | ||||
|  | ||||
|             location.href = url; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Assign multiple stock items to a customer | ||||
|  */ | ||||
| @@ -1615,7 +1571,7 @@ function loadStockTable(table, options) { | ||||
|         original[k] = params[k]; | ||||
|     } | ||||
|  | ||||
|     setupFilterList(filterKey, table, filterTarget); | ||||
|     setupFilterList(filterKey, table, filterTarget, {download: true}); | ||||
|  | ||||
|     // Override the default values, or add new ones | ||||
|     for (var key in params) { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
|  | ||||
| /* exported | ||||
|     customGroupSorter, | ||||
|     downloadTableData, | ||||
|     reloadtable, | ||||
|     renderLink, | ||||
|     reloadTableFilters, | ||||
| @@ -21,6 +22,62 @@ function reloadtable(table) { | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Download data from a table, via the API. | ||||
|  * This requires a number of conditions to be met: | ||||
|  *  | ||||
|  * - The API endpoint supports data download (on the server side) | ||||
|  * - The table is "flat" (does not support multi-level loading, etc) | ||||
|  * - The table has been loaded using the inventreeTable() function, not bootstrapTable() | ||||
|  *   (Refer to the "reloadTableFilters" function to see why!) | ||||
|  */ | ||||
| function downloadTableData(table, opts={}) { | ||||
|  | ||||
|     // Extract table configuration options | ||||
|     var table_options = table.bootstrapTable('getOptions'); | ||||
|  | ||||
|     var url = table_options.url; | ||||
|  | ||||
|     if (!url) { | ||||
|         console.log('Error: downloadTableData could not find "url" parameter.'); | ||||
|     } | ||||
|  | ||||
|     var query_params = table_options.query_params || {}; | ||||
|  | ||||
|     url += '?'; | ||||
|  | ||||
|     constructFormBody({}, { | ||||
|         title: opts.title || '{% trans "Export Table Data" %}', | ||||
|         fields: { | ||||
|             format: { | ||||
|                 label: '{% trans "Format" %}', | ||||
|                 help_text: '{% trans "Select File Format" %}', | ||||
|                 required: true, | ||||
|                 type: 'choice', | ||||
|                 value: 'csv', | ||||
|                 choices: exportFormatOptions(), | ||||
|             } | ||||
|         }, | ||||
|         onSubmit: function(fields, form_options) { | ||||
|             var format = getFormFieldValue('format', fields['format'], form_options); | ||||
|              | ||||
|             // Hide the modal | ||||
|             $(form_options.modal).modal('hide'); | ||||
|  | ||||
|             for (const [key, value] of Object.entries(query_params)) { | ||||
|                 url += `${key}=${value}&`; | ||||
|             } | ||||
|          | ||||
|             url += `export=${format}`; | ||||
|          | ||||
|             location.href = url; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Render a URL for display | ||||
|  * @param {String} text  | ||||
| @@ -114,6 +171,10 @@ function reloadTableFilters(table, filters) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Store the total set of query params | ||||
|     // This is necessary for the "downloadTableData" function to work | ||||
|     options.query_params = params; | ||||
|  | ||||
|     options.queryParams = function(tableParams) { | ||||
|         return convertQueryParameters(tableParams, params); | ||||
|     }; | ||||
| @@ -221,7 +282,11 @@ $.fn.inventreeTable = function(options) { | ||||
|     // Extract query params | ||||
|     var filters = options.queryParams || options.filters || {}; | ||||
|  | ||||
|     // Store the total set of query params | ||||
|     options.query_params = filters; | ||||
|  | ||||
|     options.queryParams = function(params) { | ||||
|         // Update the query parameters callback with the *new* filters | ||||
|         return convertQueryParameters(params, filters); | ||||
|     }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user