{% load i18n %} /* globals constructForm, imageHoverIcon, inventreeGet, inventreePut, launchModalForm, loadTableFilters, makePartIcons, renderLink, setupFilterList, yesNoLabel, */ /* exported newPartFromBomWizard, loadBomTable, removeRowFromBomWizard, removeColFromBomWizard, */ /* BOM management functions. * Requires follwing files to be loaded first: * - api.js * - part.js * - modals.js */ function bomItemFields() { return { part: { hidden: true, }, sub_part: { }, quantity: {}, reference: {}, overage: {}, note: {}, allow_variants: {}, inherited: {}, optional: {}, }; } function reloadBomTable(table) { table.bootstrapTable('refresh'); } function removeRowFromBomWizard(e) { /* Remove a row from BOM upload wizard */ e = e || window.event; var src = e.target || e.srcElement; var table = $(src).closest('table'); // Which column was clicked? var row = $(src).closest('tr'); row.remove(); var rowNum = 1; var colNum = 0; table.find('tr').each(function() { colNum++; if (colNum >= 3) { var cell = $(this).find('td:eq(1)'); cell.text(rowNum++); } }); } function removeColFromBomWizard(e) { /* Remove a column from BOM upload wizard */ e = e || window.event; var src = e.target || e.srcElement; // Which column was clicked? var col = $(src).closest('th').index(); var table = $(src).closest('table'); table.find('tr').each(function() { this.removeChild(this.cells[col]); }); } function newPartFromBomWizard(e) { /* Create a new part directly from the BOM wizard. */ e = e || window.event; var src = e.target || e.srcElement; var row = $(src).closest('tr'); launchModalForm('/part/new/', { data: { 'description': row.attr('part-description'), 'name': row.attr('part-name'), }, success: function(response) { /* A new part has been created! Push it as an option. */ var select = row.attr('part-select'); var option = new Option(response.text, response.pk, true, true); $(select).append(option).trigger('change'); } }); } function loadBomTable(table, options) { /* Load a BOM table with some configurable options. * * Following options are available: * editable - Should the BOM table be editable? * bom_url - Address to request BOM data from * part_url - Address to request Part data from * parent_id - Parent ID of the owning part * * BOM data are retrieved from the server via AJAX query */ var params = { part: options.parent_id, ordering: 'name', }; // Do we show part pricing in the BOM table? var show_pricing = global_settings.PART_SHOW_PRICE_IN_BOM; if (!show_pricing) { params.include_pricing = false; } if (options.part_detail) { params.part_detail = true; } params.sub_part_detail = true; var filters = {}; if (!options.disableFilters) { filters = loadTableFilters('bom'); } for (var key in params) { filters[key] = params[key]; } setupFilterList('bom', $(table)); // Construct the table columns var cols = []; if (options.editable) { cols.push({ field: 'ID', title: '', checkbox: true, visible: true, switchable: false, formatter: function(value, row) { // Disable checkbox if the row is defined for a *different* part! if (row.part != options.parent_id) { return { disabled: true, }; } else { return value; } } }); } // Set the parent ID of the multi-level table. // We prepend this with the literal string value 'top-level-', // because otherwise the unfortunate situation where BomItem.pk == BomItem.part.pk // AND THIS BREAKS EVERYTHING var parent_id = `top-level-${options.parent_id}`; // Part column cols.push( { field: 'sub_part', title: '{% trans "Part" %}', sortable: true, formatter: function(value, row) { var url = `/part/${row.sub_part}/`; var html = imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, url); var sub_part = row.sub_part_detail; html += makePartIcons(row.sub_part_detail); // Display an extra icon if this part is an assembly if (sub_part.assembly) { var text = ``; html += renderLink(text, `/part/${row.sub_part}/bom/`); } return html; } } ); // Part description cols.push( { field: 'sub_part_detail.description', title: '{% trans "Description" %}', } ); // Part reference cols.push({ field: 'reference', title: '{% trans "Reference" %}', searchable: true, sortable: true, }); // Part quantity cols.push({ field: 'quantity', title: '{% trans "Quantity" %}', searchable: false, sortable: true, formatter: function(value, row) { var text = value; // The 'value' is a text string with (potentially) multiple trailing zeros // Let's make it a bit more pretty text = parseFloat(text); if (row.optional) { text += ' ({% trans "Optional" %})'; } if (row.overage) { text += ` (${row.overage}) `; } return text; }, }); cols.push({ field: 'sub_part_detail.stock', title: '{% trans "Available" %}', searchable: false, sortable: true, formatter: function(value, row) { var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`; var text = value; if (value == null || value <= 0) { text = `{% trans "No Stock" %}`; } return renderLink(text, url); } }); if (show_pricing) { cols.push({ field: 'purchase_price_range', title: '{% trans "Purchase Price Range" %}', searchable: false, sortable: true, }); cols.push({ field: 'purchase_price_avg', title: '{% trans "Purchase Price Average" %}', searchable: false, sortable: true, }); cols.push({ field: 'price_range', title: '{% trans "Supplier Cost" %}', sortable: true, formatter: function(value) { if (value) { return value; } else { return `{% trans 'No supplier pricing available' %}`; } } }); } cols.push({ field: 'optional', title: '{% trans "Optional" %}', searchable: false, formatter: function(value) { return yesNoLabel(value); } }); cols.push({ field: 'allow_variants', title: '{% trans "Allow Variants" %}', formatter: function(value) { return yesNoLabel(value); } }); cols.push({ field: 'inherited', title: '{% trans "Inherited" %}', searchable: false, formatter: function(value, row) { // This BOM item *is* inheritable, but is defined for this BOM if (!row.inherited) { return yesNoLabel(false); } else if (row.part == options.parent_id) { return '{% trans "Inherited" %}'; } else { // If this BOM item is inherited from a parent part return renderLink( '{% trans "View BOM" %}', `/part/${row.part}/bom/`, ); } } }); cols.push( { field: 'can_build', title: '{% trans "Can Build" %}', formatter: function(value, row) { var can_build = 0; if (row.quantity > 0) { can_build = row.sub_part_detail.stock / row.quantity; } return +can_build.toFixed(2); }, sorter: function(valA, valB, rowA, rowB) { // Function to sort the "can build" quantity var cb_a = 0; var cb_b = 0; if (rowA.quantity > 0) { cb_a = rowA.sub_part_detail.stock / rowA.quantity; } if (rowB.quantity > 0) { cb_b = rowB.sub_part_detail.stock / rowB.quantity; } return (cb_a > cb_b) ? 1 : -1; }, sortable: true, } ); // Part notes cols.push( { field: 'note', title: '{% trans "Notes" %}', searchable: true, sortable: true, } ); if (options.editable) { cols.push({ title: '{% trans "Actions" %}', switchable: false, field: 'pk', visible: true, formatter: function(value, row) { if (row.part == options.parent_id) { var bValidate = ``; var bValid = ``; var bEdit = ``; var bDelt = ``; var html = `