{% load i18n %} {% load inventree_extras %} /* globals Chart, constructForm, global_settings, imageHoverIcon, inventreeGet, inventreePut, launchModalForm, linkButtonsToSelection, loadTableFilters, makeIconBadge, makeIconButton, printPartLabels, renderLink, setFormGroupVisibility, setupFilterList, yesNoLabel, */ /* exported duplicatePart, editCategory, editPart, initPriceBreakSet, loadBomChart, loadParametricPartTable, loadPartCategoryTable, loadPartParameterTable, loadPartTable, loadPartTestTemplateTable, loadPartVariantTable, loadRelatedPartsTable, loadSellPricingChart, loadSimplePartTable, loadStockPricingChart, partStockLabel, toggleStar, */ /* Part API functions * Requires api.js to be loaded first */ function partGroups() { return { attributes: { title: '{% trans "Part Attributes" %}', collapsible: true, }, create: { title: '{% trans "Part Creation Options" %}', collapsible: true, }, duplicate: { title: '{% trans "Part Duplication Options" %}', collapsible: true, }, supplier: { title: '{% trans "Supplier Options" %}', collapsible: true, hidden: !global_settings.PART_PURCHASEABLE, } }; } // Construct fieldset for part forms function partFields(options={}) { var fields = { category: { secondary: { title: '{% trans "Add Part Category" %}', fields: function() { var fields = categoryFields(); return fields; } } }, name: {}, IPN: {}, revision: {}, description: {}, variant_of: {}, keywords: { icon: 'fa-key', }, units: {}, link: { icon: 'fa-link', }, default_location: { }, default_supplier: { filters: { part_detail: true, supplier_detail: true, } }, default_expiry: { icon: 'fa-calendar-alt', }, minimum_stock: { icon: 'fa-boxes', }, component: { default: global_settings.PART_COMPONENT, group: 'attributes', }, assembly: { default: global_settings.PART_ASSEMBLY, group: 'attributes', }, is_template: { default: global_settings.PART_TEMPLATE, group: 'attributes', }, trackable: { default: global_settings.PART_TRACKABLE, group: 'attributes', }, purchaseable: { default: global_settings.PART_PURCHASEABLE, group: 'attributes', onEdit: function(value, name, field, options) { setFormGroupVisibility('supplier', value, options); } }, salable: { default: global_settings.PART_SALABLE, group: 'attributes', }, virtual: { default: global_settings.PART_VIRTUAL, group: 'attributes', }, }; // If editing a part, we can set the "active" status if (options.edit) { fields.active = { group: 'attributes' }; } // Pop expiry field if (!global_settings.STOCK_ENABLE_EXPIRY) { delete fields['default_expiry']; } if (options.create || options.duplicate) { if (global_settings.PART_CREATE_INITIAL) { fields.initial_stock = { type: 'boolean', label: '{% trans "Create Initial Stock" %}', help_text: '{% trans "Create an initial stock item for this part" %}', group: 'create', }; fields.initial_stock_quantity = { type: 'decimal', value: 1, label: '{% trans "Initial Stock Quantity" %}', help_text: '{% trans "Specify initial stock quantity for this part" %}', group: 'create', }; // TODO - Allow initial location of stock to be specified fields.initial_stock_location = { label: '{% trans "Location" %}', help_text: '{% trans "Select destination stock location" %}', type: 'related field', required: true, api_url: `/api/stock/location/`, model: 'stocklocation', group: 'create', }; } } // Additional fields when "creating" a new part if (options.create) { // No supplier parts available yet delete fields['default_supplier']; fields.copy_category_parameters = { type: 'boolean', label: '{% trans "Copy Category Parameters" %}', help_text: '{% trans "Copy parameter templates from selected part category" %}', value: global_settings.PART_CATEGORY_PARAMETERS, group: 'create', }; // Supplier options fields.add_supplier_info = { type: 'boolean', label: '{% trans "Add Supplier Data" %}', help_text: '{% trans "Create initial supplier data for this part" %}', group: 'supplier', }; fields.supplier = { type: 'related field', model: 'company', label: '{% trans "Supplier" %}', help_text: '{% trans "Select supplier" %}', filters: { 'is_supplier': true, }, api_url: '{% url "api-company-list" %}', group: 'supplier', }; fields.SKU = { type: 'string', label: '{% trans "SKU" %}', help_text: '{% trans "Supplier stock keeping unit" %}', group: 'supplier', }; fields.manufacturer = { type: 'related field', model: 'company', label: '{% trans "Manufacturer" %}', help_text: '{% trans "Select manufacturer" %}', filters: { 'is_manufacturer': true, }, api_url: '{% url "api-company-list" %}', group: 'supplier', }; fields.MPN = { type: 'string', label: '{% trans "MPN" %}', help_text: '{% trans "Manufacturer Part Number" %}', group: 'supplier', }; } // Additional fields when "duplicating" a part if (options.duplicate) { fields.copy_from = { type: 'integer', hidden: true, value: options.duplicate, group: 'duplicate', }, fields.copy_image = { type: 'boolean', label: '{% trans "Copy Image" %}', help_text: '{% trans "Copy image from original part" %}', value: true, group: 'duplicate', }, fields.copy_bom = { type: 'boolean', label: '{% trans "Copy BOM" %}', help_text: '{% trans "Copy bill of materials from original part" %}', value: global_settings.PART_COPY_BOM, group: 'duplicate', }; fields.copy_parameters = { type: 'boolean', label: '{% trans "Copy Parameters" %}', help_text: '{% trans "Copy parameter data from original part" %}', value: global_settings.PART_COPY_PARAMETERS, group: 'duplicate', }; } return fields; } function categoryFields() { return { parent: { help_text: '{% trans "Parent part category" %}', }, name: {}, description: {}, default_location: {}, default_keywords: { icon: 'fa-key', } }; } // Edit a PartCategory via the API function editCategory(pk) { var url = `/api/part/category/${pk}/`; var fields = categoryFields(); constructForm(url, { fields: fields, title: '{% trans "Edit Part Category" %}', reload: true, }); } function editPart(pk) { var url = `/api/part/${pk}/`; var fields = partFields({ edit: true }); // Filter supplied parts by the Part ID fields.default_supplier.filters.part = pk; var groups = partGroups({}); constructForm(url, { fields: fields, groups: groups, title: '{% trans "Edit Part" %}', reload: true, successMessage: '{% trans "Part edited" %}', }); } // Launch form to duplicate a part function duplicatePart(pk, options={}) { // First we need all the part information inventreeGet(`/api/part/${pk}/`, {}, { success: function(data) { var fields = partFields({ duplicate: pk, }); if (fields.initial_stock_location) { fields.initial_stock_location.value = data.default_location; } // Remove "default_supplier" field delete fields['default_supplier']; // If we are making a "variant" part if (options.variant) { // Override the "variant_of" field data.variant_of = pk; } constructForm('{% url "api-part-list" %}', { method: 'POST', fields: fields, groups: partGroups(), title: '{% trans "Duplicate Part" %}', data: data, onSuccess: function(data) { // Follow the new part location.href = `/part/${data.pk}/`; } }); } }); } /* Toggle the 'starred' status of a part. * Performs AJAX queries and updates the display on the button. * * options: * - button: ID of the button (default = '#part-star-icon') * - URL: API url of the object * - user: pk of the user */ function toggleStar(options) { inventreeGet(options.url, {}, { success: function(response) { var starred = response.starred; inventreePut( options.url, { starred: !starred, }, { method: 'PATCH', success: function(response) { if (response.starred) { $(options.button).removeClass('fa fa-bell-slash').addClass('fas fa-bell icon-green'); $(options.button).attr('title', '{% trans "You are subscribed to notifications for this item" %}'); showMessage('{% trans "You have subscribed to notifications for this item" %}', { style: 'success', }); } else { $(options.button).removeClass('fas fa-bell icon-green').addClass('fa fa-bell-slash'); $(options.button).attr('title', '{% trans "Subscribe to notifications for this item" %}'); showMessage('{% trans "You have unsubscribed to notifications for this item" %}', { style: 'warning', }); } } } ); } }); } function partStockLabel(part, options={}) { if (part.in_stock) { return `{% trans "Inactive" %} `; } return html; } function loadPartVariantTable(table, partId, options={}) { /* Load part variant table */ var params = options.params || {}; params.ancestor = partId; // Load filters var filters = loadTableFilters('variants'); for (var key in params) { filters[key] = params[key]; } setupFilterList('variants', $(table)); var cols = [ { field: 'pk', title: 'ID', visible: false, switchable: false, }, { field: 'name', title: '{% trans "Name" %}', switchable: false, formatter: function(value, row) { var html = ''; var name = ''; if (row.IPN) { name += row.IPN; name += ' | '; } name += value; if (row.revision) { name += ' | '; name += row.revision; } if (row.is_template) { name = '' + name + ''; } html += imageHoverIcon(row.thumbnail); html += renderLink(name, `/part/${row.pk}/`); if (row.trackable) { html += makeIconBadge('fa-directions', '{% trans "Trackable part" %}'); } if (row.virtual) { html += makeIconBadge('fa-ghost', '{% trans "Virtual part" %}'); } if (row.is_template) { html += makeIconBadge('fa-clone', '{% trans "Template part" %}'); } if (row.assembly) { html += makeIconBadge('fa-tools', '{% trans "Assembled part" %}'); } if (!row.active) { html += `{% trans "Inactive" %}`; } return html; }, }, { field: 'IPN', title: '{% trans "IPN" %}', }, { field: 'revision', title: '{% trans "Revision" %}', }, { field: 'description', title: '{% trans "Description" %}', }, { field: 'in_stock', title: '{% trans "Stock" %}', formatter: function(value, row) { return renderLink(value, `/part/${row.pk}/?display=part-stock`); } } ]; table.inventreeTable({ url: '{% url "api-part-list" %}', name: 'partvariants', showColumns: true, original: params, queryParams: filters, formatNoMatches: function() { return '{% trans "No variants found" %}'; }, columns: cols, treeEnable: true, rootParentId: partId, parentIdField: 'variant_of', idField: 'pk', uniqueId: 'pk', treeShowField: 'name', sortable: true, search: true, onPostBody: function() { table.treegrid({ treeColumn: 0, }); table.treegrid('collapseAll'); } }); } function loadSimplePartTable(table, url, options={}) { options.disableFilters = true; loadPartTable(table, url, options); } function loadPartParameterTable(table, url, options) { var params = options.params || {}; // Load filters var filters = loadTableFilters('part-parameters'); for (var key in params) { filters[key] = params[key]; } // setupFilterList("#part-parameters", $(table)); $(table).inventreeTable({ url: url, original: params, queryParams: filters, name: 'partparameters', groupBy: false, formatNoMatches: function() { return '{% trans "No parameters found" %}'; }, columns: [ { checkbox: true, switchable: false, visible: true, }, { field: 'name', title: '{% trans "Name" %}', switchable: false, sortable: true, formatter: function(value, row) { return row.template_detail.name; } }, { field: 'data', title: '{% trans "Value" %}', switchable: false, sortable: true, }, { field: 'units', title: '{% trans "Units" %}', switchable: true, sortable: true, formatter: function(value, row) { return row.template_detail.units; } }, { field: 'actions', title: '', switchable: false, sortable: false, formatter: function(value, row) { var pk = row.pk; var html = `
`; } else { return ` `; } } function makePartIcons(part) { /* Render a set of icons for the given part. */ var html = ''; if (part.trackable) { html += makeIconBadge('fa-directions', '{% trans "Trackable part" %}'); } if (part.virtual) { html += makeIconBadge('fa-ghost', '{% trans "Virtual part" %}'); } if (part.is_template) { html += makeIconBadge('fa-clone', '{% trans "Template part" %}'); } if (part.assembly) { html += makeIconBadge('fa-tools', '{% trans "Assembled part" %}'); } if (part.starred) { html += makeIconBadge('fa-bell icon-green', '{% trans "Subscribed part" %}'); } if (part.salable) { html += makeIconBadge('fa-dollar-sign', '{% trans "Salable part" %}'); } if (!part.active) { html += `