mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	Merge branch 'master' into feature-non-int-serial
This commit is contained in:
		| @@ -1,7 +1,136 @@ | ||||
| function makeOption(id, title) { | ||||
| function makeOption(text, value, title) { | ||||
|     /* Format an option for a select element | ||||
|      */ | ||||
|     return "<option value='" + id + "'>" + title + "</option>"; | ||||
|  | ||||
|     var html = `<option value='${value || text}'`; | ||||
|  | ||||
|     if (title) { | ||||
|         html += ` title='${title}'`; | ||||
|     } | ||||
|  | ||||
|     html += `>${text}</option>`; | ||||
|  | ||||
|     return html; | ||||
| } | ||||
|  | ||||
| function makeOptionsList(elements, textFunc, valueFunc, titleFunc) { | ||||
|     /* | ||||
|      * Programatically generate a list of <option> elements, | ||||
|      * from the (assumed array) of elements. | ||||
|      * For each element, we pass the element to the supplied functions, | ||||
|      * which (in turn) generate display / value / title values. | ||||
|      *  | ||||
|      * Args: | ||||
|      * - elements: List of elements | ||||
|      * - textFunc: Function which takes an element and generates the text to be displayed  | ||||
|      * - valueFunc: optional function which takes an element and generates the value | ||||
|      * - titleFunc: optional function which takes an element and generates a title | ||||
|      */ | ||||
|      | ||||
|     var options = []; | ||||
|  | ||||
|     elements.forEach(function(element) { | ||||
|          | ||||
|         var text = textFunc(element); | ||||
|         var value = null; | ||||
|         var title = null; | ||||
|  | ||||
|         if (valueFunc) { | ||||
|             value = valueFunc(element); | ||||
|         } else { | ||||
|             value = text; | ||||
|         } | ||||
|  | ||||
|         if (titleFunc) { | ||||
|             title = titleFunc(element); | ||||
|         } | ||||
|  | ||||
|         options.push(makeOption(text, value, title)); | ||||
|     }); | ||||
|  | ||||
|     return options; | ||||
| } | ||||
|  | ||||
|  | ||||
| function setFieldOptions(fieldName, optionList, options={}) { | ||||
|     /* Set the options for a <select> field. | ||||
|      * | ||||
|      * Args: | ||||
|      * - fieldName: The name of the target field | ||||
|      * - Options: List of formatted <option> strings | ||||
|      * - append: If true, options will be appended, otherwise will replace existing options. | ||||
|      */ | ||||
|  | ||||
|      | ||||
|     var append = options.append || false; | ||||
|      | ||||
|     var modal = options.modal || '#modal-form'; | ||||
|  | ||||
|     var field = getFieldByName(modal, fieldName); | ||||
|  | ||||
|     var addEmptyOption = options.addEmptyOption || true; | ||||
|  | ||||
|     // If not appending, clear out the field...  | ||||
|     if (!append) { | ||||
|         field.find('option').remove(); | ||||
|     } | ||||
|  | ||||
|     if (addEmptyOption) { | ||||
|         // Add an 'empty' option at the top of the list | ||||
|         field.append(`<option value="">---------</option>`); | ||||
|     } | ||||
|  | ||||
|     optionList.forEach(function(option) { | ||||
|         field.append(option); | ||||
|     }); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| function reloadFieldOptions(fieldName, options) { | ||||
|     /* Reload the options for a given field, | ||||
|      * using an AJAX request. | ||||
|      * | ||||
|      * Args: | ||||
|      * - fieldName: The name of the field | ||||
|      * - options:  | ||||
|      * -- url: Query url | ||||
|      * -- params: Query params | ||||
|      * -- value: A function which takes a returned option and returns the 'value' (if not specified, the `pk` field is used) | ||||
|      * -- text: A function which takes a returned option and returns the 'text' | ||||
|      * -- title: A function which takes a returned option and returns the 'title' (optional!) | ||||
|      */ | ||||
|  | ||||
|     inventreeGet(options.url, options.params, { | ||||
|         success: function(response) { | ||||
|             var opts = makeOptionsList(response, | ||||
|                 function(item) { | ||||
|                     return options.text(item); | ||||
|                 }, | ||||
|                 function(item) { | ||||
|                     if (options.value) { | ||||
|                         return options.value(item); | ||||
|                     } else { | ||||
|                         // Fallback is to use the 'pk' field | ||||
|                         return item.pk; | ||||
|                     } | ||||
|                 }, | ||||
|                 function(item) { | ||||
|                     if (options.title) { | ||||
|                         return options.title(item); | ||||
|                     } else { | ||||
|                         return null; | ||||
|                     } | ||||
|                 } | ||||
|             ); | ||||
|  | ||||
|             // Update the target field with the new options | ||||
|             setFieldOptions(fieldName, opts); | ||||
|         }, | ||||
|         error: function(response) { | ||||
|             console.log("Error GETting field options"); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -397,6 +526,13 @@ function injectModalForm(modal, form_html) { | ||||
| } | ||||
|  | ||||
|  | ||||
| function getFieldByName(modal, name) { | ||||
|     /* Find the field (with the given name) within the modal */ | ||||
|  | ||||
|     return $(modal).find(`#id_${name}`); | ||||
| } | ||||
|  | ||||
|  | ||||
| function insertNewItemButton(modal, options) { | ||||
|     /* Insert a button into a modal form, after a field label. | ||||
|      * Looks for a <label> tag inside the form with the attribute "for='id_<field>'" | ||||
| @@ -476,6 +612,39 @@ function attachSecondaries(modal, secondaries) { | ||||
| } | ||||
|  | ||||
|  | ||||
| function attachFieldCallback(modal, callback) { | ||||
|     /* Attach a 'callback' function to a given field in the modal form. | ||||
|      * When the value of that field is changed, the callback function is performed. | ||||
|      *  | ||||
|      * options: | ||||
|      * - field: The name of the field to attach to | ||||
|      * - action: A function to perform | ||||
|      */ | ||||
|  | ||||
|      // Find the field input in the form | ||||
|      var field = getFieldByName(modal, callback.field); | ||||
|  | ||||
|     field.change(function() { | ||||
|  | ||||
|         if (callback.action) { | ||||
|             // Run the callback function with the new value of the field! | ||||
|             callback.action(field.val(), field); | ||||
|         } else { | ||||
|             console.log(`Value changed for field ${callback.field} - ${field.val()}`); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| function attachCallbacks(modal, callbacks) { | ||||
|     /* Attach a provided list of callback functions */ | ||||
|  | ||||
|     for (var i = 0; i < callbacks.length; i++) { | ||||
|         attachFieldCallback(modal, callbacks[i]); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| function handleModalForm(url, options) { | ||||
|     /* Update a modal form after data are received from the server. | ||||
|      * Manages POST requests until the form is successfully submitted. | ||||
| @@ -575,6 +744,7 @@ function launchModalForm(url, options = {}) { | ||||
|      * no_post - If true, only display form data, hide submit button, and disallow POST | ||||
|      * after_render - Callback function to run after form is rendered | ||||
|      * secondary - List of secondary modals to attach | ||||
|      * callback - List of callback functions to attach to inputs | ||||
|      */ | ||||
|  | ||||
|     var modal = options.modal || '#modal-form'; | ||||
| @@ -615,6 +785,10 @@ function launchModalForm(url, options = {}) { | ||||
|                     attachSecondaries(modal, options.secondary); | ||||
|                 } | ||||
|  | ||||
|                 if (options.callback) { | ||||
|                     attachCallbacks(modal, options.callback); | ||||
|                 } | ||||
|  | ||||
|                 if (options.no_post) { | ||||
|                     modalShowSubmitButton(modal, false); | ||||
|                 } else { | ||||
|   | ||||
| @@ -104,12 +104,41 @@ class SupplierPartList(generics.ListCreateAPIView): | ||||
|  | ||||
|         queryset = super().get_queryset() | ||||
|  | ||||
|         return queryset | ||||
|  | ||||
|     def filter_queryset(self, queryset): | ||||
|         """ | ||||
|         Custom filtering for the queryset. | ||||
|         """ | ||||
|  | ||||
|         queryset = super().filter_queryset(queryset) | ||||
|  | ||||
|         params = self.request.query_params | ||||
|  | ||||
|         # Filter by manufacturer | ||||
|         manufacturer = params.get('manufacturer', None) | ||||
|  | ||||
|         if manufacturer is not None: | ||||
|             queryset = queryset.filter(manufacturer=manufacturer) | ||||
|  | ||||
|         # Filter by supplier | ||||
|         supplier = params.get('supplier', None) | ||||
|  | ||||
|         if supplier is not None: | ||||
|             queryset = queryset.filter(supplier=supplier) | ||||
|  | ||||
|         # Filter by EITHER manufacturer or supplier | ||||
|         company = self.request.query_params.get('company', None) | ||||
|         company = params.get('company', None) | ||||
|  | ||||
|         if company is not None: | ||||
|             queryset = queryset.filter(Q(manufacturer=company) | Q(supplier=company)) | ||||
|  | ||||
|         # Filter by parent part? | ||||
|         part = params.get('part', None) | ||||
|  | ||||
|         if part is not None: | ||||
|             queryset = queryset.filter(part=part) | ||||
|  | ||||
|         return queryset | ||||
|  | ||||
|     def get_serializer(self, *args, **kwargs): | ||||
| @@ -130,6 +159,11 @@ class SupplierPartList(generics.ListCreateAPIView): | ||||
|         except AttributeError: | ||||
|             pass | ||||
|  | ||||
|         try: | ||||
|             kwargs['pretty'] = str2bool(self.request.query_params.get('pretty', None)) | ||||
|         except AttributeError: | ||||
|             pass | ||||
|          | ||||
|         kwargs['context'] = self.get_serializer_context() | ||||
|  | ||||
|         return self.serializer_class(*args, **kwargs) | ||||
| @@ -147,9 +181,6 @@ class SupplierPartList(generics.ListCreateAPIView): | ||||
|     ] | ||||
|  | ||||
|     filter_fields = [ | ||||
|         'part', | ||||
|         'supplier', | ||||
|         'manufacturer', | ||||
|     ] | ||||
|  | ||||
|     search_fields = [ | ||||
|   | ||||
| @@ -418,6 +418,10 @@ class SupplierPart(models.Model): | ||||
|  | ||||
|         return [line.order for line in self.purchase_order_line_items.all().prefetch_related('order')] | ||||
|  | ||||
|     @property | ||||
|     def pretty_name(self): | ||||
|         return str(self) | ||||
|  | ||||
|     def __str__(self): | ||||
|         s = "{supplier} ({sku})".format( | ||||
|             sku=self.SKU, | ||||
|   | ||||
| @@ -80,13 +80,17 @@ class SupplierPartSerializer(InvenTreeModelSerializer): | ||||
|     part_detail = PartBriefSerializer(source='part', many=False, read_only=True) | ||||
|  | ||||
|     supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) | ||||
|  | ||||
|     manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True) | ||||
|  | ||||
|     pretty_name = serializers.CharField(read_only=True) | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|  | ||||
|         part_detail = kwargs.pop('part_detail', False) | ||||
|         supplier_detail = kwargs.pop('supplier_detail', False) | ||||
|         manufacturer_detail = kwargs.pop('manufacturer_detail', False) | ||||
|         prettify = kwargs.pop('pretty', False) | ||||
|  | ||||
|         super(SupplierPartSerializer, self).__init__(*args, **kwargs) | ||||
|  | ||||
| @@ -99,6 +103,9 @@ class SupplierPartSerializer(InvenTreeModelSerializer): | ||||
|         if manufacturer_detail is not True: | ||||
|             self.fields.pop('manufacturer_detail') | ||||
|  | ||||
|         if prettify is not True: | ||||
|             self.fields.pop('pretty_name') | ||||
|  | ||||
|     supplier = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_supplier=True)) | ||||
|  | ||||
|     manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True)) | ||||
| @@ -109,6 +116,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer): | ||||
|             'pk', | ||||
|             'part', | ||||
|             'part_detail', | ||||
|             'pretty_name', | ||||
|             'supplier', | ||||
|             'supplier_detail', | ||||
|             'SKU', | ||||
|   | ||||
| @@ -44,23 +44,13 @@ | ||||
|     }); | ||||
|  | ||||
|     $("#item-create").click(function() { | ||||
|         launchModalForm("{% url 'stock-item-create' %}", { | ||||
|             reload: true, | ||||
|         createNewStockItem({ | ||||
|             data: { | ||||
|                 part: {{ part.part.id }}, | ||||
|                 supplier_part: {{ part.id }}, | ||||
|             }, | ||||
|             secondary: [ | ||||
|                 { | ||||
|                     field: 'location', | ||||
|                     label: '{% trans "New Location" %}', | ||||
|                     title: '{% trans "Create New Location" %}', | ||||
|                     url: "{% url 'stock-location-create' %}", | ||||
|                 } | ||||
|             ] | ||||
|             reload: true, | ||||
|         }); | ||||
|  | ||||
|         return false; | ||||
|     }); | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,8 @@ from rapidfuzz import fuzz | ||||
| import tablib | ||||
| import os | ||||
|  | ||||
| from collections import OrderedDict | ||||
|  | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.core.exceptions import ValidationError | ||||
|  | ||||
| @@ -47,7 +49,7 @@ def MakeBomTemplate(fmt): | ||||
|     return DownloadFile(data, filename) | ||||
|  | ||||
|  | ||||
| def ExportBom(part, fmt='csv', cascade=False, max_levels=None, supplier_data=False): | ||||
| def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=False, stock_data=False, supplier_data=False): | ||||
|     """ Export a BOM (Bill of Materials) for a given part. | ||||
|  | ||||
|     Args: | ||||
| @@ -92,9 +94,75 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, supplier_data=Fal | ||||
|  | ||||
|     dataset = BomItemResource().export(queryset=bom_items, cascade=cascade) | ||||
|  | ||||
|     def add_columns_to_dataset(columns, column_size): | ||||
|         try: | ||||
|             for header, column_dict in columns.items(): | ||||
|                 # Construct column tuple | ||||
|                 col = tuple(column_dict.get(c_idx, '') for c_idx in range(column_size)) | ||||
|                 # Add column to dataset | ||||
|                 dataset.append_col(col, header=header) | ||||
|         except AttributeError: | ||||
|             pass | ||||
|  | ||||
|     if parameter_data: | ||||
|         """ | ||||
|         If requested, add extra columns for each PartParameter associated with each line item | ||||
|         """ | ||||
|  | ||||
|         parameter_cols = {} | ||||
|  | ||||
|         for b_idx, bom_item in enumerate(bom_items): | ||||
|             # Get part parameters | ||||
|             parameters = bom_item.sub_part.get_parameters() | ||||
|             # Add parameters to columns | ||||
|             if parameters: | ||||
|                 for parameter in parameters: | ||||
|                     name = parameter.template.name | ||||
|                     value = parameter.data | ||||
|  | ||||
|                     try: | ||||
|                         parameter_cols[name].update({b_idx: value}) | ||||
|                     except KeyError: | ||||
|                         parameter_cols[name] = {b_idx: value} | ||||
|                  | ||||
|         # Add parameter columns to dataset | ||||
|         parameter_cols_ordered = OrderedDict(sorted(parameter_cols.items(), key=lambda x: x[0])) | ||||
|         add_columns_to_dataset(parameter_cols_ordered, len(bom_items)) | ||||
|  | ||||
|     if stock_data: | ||||
|         """ | ||||
|         If requested, add extra columns for stock data associated with each line item | ||||
|         """ | ||||
|  | ||||
|         stock_headers = [ | ||||
|             _('Default Location'), | ||||
|             _('Available Stock'), | ||||
|         ] | ||||
|  | ||||
|         stock_cols = {} | ||||
|  | ||||
|         for b_idx, bom_item in enumerate(bom_items): | ||||
|             stock_data = [] | ||||
|             # Get part default location | ||||
|             try: | ||||
|                 stock_data.append(bom_item.sub_part.get_default_location().name) | ||||
|             except AttributeError: | ||||
|                 stock_data.append('') | ||||
|             # Get part current stock | ||||
|             stock_data.append(bom_item.sub_part.available_stock) | ||||
|  | ||||
|             for s_idx, header in enumerate(stock_headers): | ||||
|                 try: | ||||
|                     stock_cols[header].update({b_idx: stock_data[s_idx]}) | ||||
|                 except KeyError: | ||||
|                     stock_cols[header] = {b_idx: stock_data[s_idx]} | ||||
|  | ||||
|         # Add stock columns to dataset | ||||
|         add_columns_to_dataset(stock_cols, len(bom_items)) | ||||
|  | ||||
|     if supplier_data: | ||||
|         """ | ||||
|         If requested, add extra columns for each SupplierPart associated with the each line item | ||||
|         If requested, add extra columns for each SupplierPart associated with each line item | ||||
|         """ | ||||
|  | ||||
|         # Expand dataset with manufacturer parts | ||||
| @@ -150,11 +218,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, supplier_data=Fal | ||||
|                     manufacturer_cols[k_mpn] = {b_idx: manufacturer_mpn} | ||||
|  | ||||
|         # Add manufacturer columns to dataset | ||||
|         for header, col_dict in manufacturer_cols.items(): | ||||
|             # Construct column tuple | ||||
|             col = tuple(col_dict.get(c_idx, '') for c_idx in range(len(bom_items))) | ||||
|             # Add column to dataset | ||||
|             dataset.append_col(col, header=header) | ||||
|         add_columns_to_dataset(manufacturer_cols, len(bom_items)) | ||||
|  | ||||
|     data = dataset.export(fmt) | ||||
|  | ||||
|   | ||||
| @@ -58,7 +58,11 @@ class BomExportForm(forms.Form): | ||||
|  | ||||
|     levels = forms.IntegerField(label=_("Levels"), required=True, initial=0, help_text=_("Select maximum number of BOM levels to export (0 = all levels)")) | ||||
|  | ||||
|     supplier_data = forms.BooleanField(label=_("Include Supplier Data"), required=False, initial=True, help_text=_("Include supplier part data in exported BOM")) | ||||
|     parameter_data = forms.BooleanField(label=_("Include Parameter Data"), required=False, initial=False, help_text=_("Include part parameters data in exported BOM")) | ||||
|  | ||||
|     stock_data = forms.BooleanField(label=_("Include Stock Data"), required=False, initial=False, help_text=_("Include part stock data in exported BOM")) | ||||
|  | ||||
|     supplier_data = forms.BooleanField(label=_("Include Supplier Data"), required=False, initial=True, help_text=_("Include part supplier data in exported BOM")) | ||||
|  | ||||
|     def get_choices(self): | ||||
|         """ BOM export format choices """ | ||||
| @@ -196,11 +200,19 @@ class EditCategoryForm(HelperForm): | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class PartModelChoiceField(forms.ModelChoiceField): | ||||
|     """ Extending string representation of Part instance with available stock """ | ||||
|     def label_from_instance(self, part): | ||||
|         return f'{part} - {part.available_stock}' | ||||
|  | ||||
|  | ||||
| class EditBomItemForm(HelperForm): | ||||
|     """ Form for editing a BomItem object """ | ||||
|  | ||||
|     quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) | ||||
|  | ||||
|     sub_part = PartModelChoiceField(queryset=Part.objects.all()) | ||||
|  | ||||
|     class Meta: | ||||
|         model = BomItem | ||||
|         fields = [ | ||||
|   | ||||
| @@ -268,7 +268,7 @@ class Part(MPTTModel): | ||||
|         super().save(*args, **kwargs) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "{n} - {d}".format(n=self.full_name, d=self.description) | ||||
|         return f"{self.full_name} - {self.description}" | ||||
|  | ||||
|     def checkAddToBOM(self, parent): | ||||
|         """ | ||||
|   | ||||
| @@ -63,7 +63,7 @@ | ||||
|                         <option value=''>--- {% trans "Select Part" %} ---</option> | ||||
|                         {% for part in row.part_options %} | ||||
|                         <option value='{{ part.id }}' {% if part.id == row.part.id %} selected='selected' {% elif part.id == row.part_match.id %} selected='selected' {% endif %}> | ||||
|                             {{ part.full_name }} - {{ part.description }} | ||||
|                             {{ part }} - {{ part.available_stock }} | ||||
|                         </option> | ||||
|                         {% endfor %} | ||||
|                     </select> | ||||
|   | ||||
| @@ -25,12 +25,10 @@ | ||||
| {{ block.super }} | ||||
|  | ||||
|     $('#add-stock-item').click(function () { | ||||
|         launchModalForm( | ||||
|                         "{% url 'stock-item-create' %}", | ||||
|                         { | ||||
|         createNewStockItem({ | ||||
|             reload: true, | ||||
|             data: { | ||||
|                                 part: {{ part.id }} | ||||
|                 part: {{ part.id }}, | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| @@ -64,37 +62,12 @@ | ||||
|     }); | ||||
|  | ||||
|     $('#item-create').click(function () { | ||||
|         launchModalForm("{% url 'stock-item-create' %}", { | ||||
|         createNewStockItem({ | ||||
|             reload: true, | ||||
|             data: { | ||||
|                 part: {{ part.id }} | ||||
|             }, | ||||
|             secondary: [ | ||||
|                 { | ||||
|                     field: 'part', | ||||
|                     label: '{% trans "New Part" %}', | ||||
|                     title: '{% trans "Create New Part" %}', | ||||
|                     url: "{% url 'part-create' %}", | ||||
|                 }, | ||||
|                 { | ||||
|                     field: 'supplier_part', | ||||
|                     label: '{% trans "New Supplier Part" %}', | ||||
|                     title: '{% trans "Create new Supplier Part" %}', | ||||
|                     url: "{% url 'supplier-part-create' %}", | ||||
|                     data: { | ||||
|                         part: {{ part.id }} | ||||
|                     }, | ||||
|                 }, | ||||
|                 { | ||||
|                     field: 'location', | ||||
|                     label: '{% trans "New Location" %}', | ||||
|                     title: '{% trans "Create New Location" %}', | ||||
|                     url: "{% url 'stock-location-create' %}", | ||||
|                 part: {{ part.id }}, | ||||
|             } | ||||
|             ] | ||||
|         }); | ||||
|  | ||||
|         return false; | ||||
|     }); | ||||
|  | ||||
| {% endblock %} | ||||
| @@ -1499,6 +1499,10 @@ class BomDownload(AjaxView): | ||||
|  | ||||
|         cascade = str2bool(request.GET.get('cascade', False)) | ||||
|  | ||||
|         parameter_data = str2bool(request.GET.get('parameter_data', False)) | ||||
|  | ||||
|         stock_data = str2bool(request.GET.get('stock_data', False)) | ||||
|  | ||||
|         supplier_data = str2bool(request.GET.get('supplier_data', False)) | ||||
|  | ||||
|         levels = request.GET.get('levels', None) | ||||
| @@ -1516,7 +1520,13 @@ class BomDownload(AjaxView): | ||||
|         if not IsValidBOMFormat(export_format): | ||||
|             export_format = 'csv' | ||||
|  | ||||
|         return ExportBom(part, fmt=export_format, cascade=cascade, max_levels=levels, supplier_data=supplier_data) | ||||
|         return ExportBom(part, | ||||
|                          fmt=export_format, | ||||
|                          cascade=cascade, | ||||
|                          max_levels=levels, | ||||
|                          parameter_data=parameter_data, | ||||
|                          stock_data=stock_data, | ||||
|                          supplier_data=supplier_data) | ||||
|  | ||||
|     def get_data(self): | ||||
|         return { | ||||
| @@ -1541,6 +1551,8 @@ class BomExport(AjaxView): | ||||
|         fmt = request.POST.get('file_format', 'csv').lower() | ||||
|         cascade = str2bool(request.POST.get('cascading', False)) | ||||
|         levels = request.POST.get('levels', None) | ||||
|         parameter_data = str2bool(request.POST.get('parameter_data', False)) | ||||
|         stock_data = str2bool(request.POST.get('stock_data', False)) | ||||
|         supplier_data = str2bool(request.POST.get('supplier_data', False)) | ||||
|  | ||||
|         try: | ||||
| @@ -1556,6 +1568,8 @@ class BomExport(AjaxView): | ||||
|  | ||||
|         url += '?file_format=' + fmt | ||||
|         url += '&cascade=' + str(cascade) | ||||
|         url += '¶meter_data=' + str(parameter_data) | ||||
|         url += '&stock_data=' + str(stock_data) | ||||
|         url += '&supplier_data=' + str(supplier_data) | ||||
|  | ||||
|         if levels: | ||||
|   | ||||
| @@ -180,7 +180,7 @@ class TestReport(ReportTemplateBase): | ||||
|         Test if this report template matches a given StockItem objects | ||||
|         """ | ||||
|  | ||||
|         filters = validateFilterString(self.part_filters) | ||||
|         filters = validateFilterString(self.filters) | ||||
|  | ||||
|         items = StockItem.objects.filter(**filters) | ||||
|  | ||||
|   | ||||
| @@ -237,7 +237,7 @@ class TestReportFormatForm(HelperForm): | ||||
|  | ||||
|         for template in templates: | ||||
|             if template.matches_stock_item(self.stock_item): | ||||
|                 choices.append(template) | ||||
|                 choices.append((template.pk, template)) | ||||
|  | ||||
|         return choices | ||||
|  | ||||
|   | ||||
| @@ -320,15 +320,12 @@ $("#print-label").click(function() { | ||||
| }); | ||||
|  | ||||
| $("#stock-duplicate").click(function() { | ||||
|     launchModalForm( | ||||
|         "{% url 'stock-item-create' %}", | ||||
|         { | ||||
|     createNewStockItem({ | ||||
|         follow: true, | ||||
|         data: { | ||||
|             copy: {{ item.id }}, | ||||
|             }, | ||||
|         } | ||||
|     ); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| $("#stock-edit").click(function () { | ||||
|   | ||||
| @@ -204,38 +204,14 @@ | ||||
|     {% endif %} | ||||
|  | ||||
|     $('#item-create').click(function () { | ||||
|         launchModalForm("{% url 'stock-item-create' %}", | ||||
|                         { | ||||
|         createNewStockItem({ | ||||
|             follow: true, | ||||
|             data: { | ||||
|                 {% if location %} | ||||
|                 location: {{ location.id }} | ||||
|                 {% endif %} | ||||
|                             }, | ||||
|                             secondary: [ | ||||
|                                 { | ||||
|                                     field: 'part', | ||||
|                                     label: 'New Part', | ||||
|                                     title: 'Create New Part', | ||||
|                                     url: "{% url 'part-create' %}", | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     field: 'supplier_part', | ||||
|                                     label: 'New Supplier Part', | ||||
|                                     title: 'Create new Supplier Part', | ||||
|                                     url: "{% url 'supplier-part-create' %}" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     field: 'location', | ||||
|                                     label: 'New Location', | ||||
|                                     title: 'Create New Location', | ||||
|                                     url: "{% url 'stock-location-create' %}", | ||||
|             } | ||||
|                             ] | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         return false; | ||||
|     }); | ||||
|      | ||||
|     loadStockTable($("#stock-table"), { | ||||
|   | ||||
| @@ -1240,7 +1240,7 @@ class StockItemCreate(AjaxCreateView): | ||||
|             form.rebuild_layout() | ||||
|  | ||||
|             # Hide the 'part' field (as a valid part is selected) | ||||
|             form.fields['part'].widget = HiddenInput() | ||||
|             # form.fields['part'].widget = HiddenInput() | ||||
|  | ||||
|             # trackable parts get special consideration | ||||
|             if part.trackable: | ||||
| @@ -1267,6 +1267,11 @@ class StockItemCreate(AjaxCreateView): | ||||
|                     # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate | ||||
|                     form.fields['supplier_part'].initial = all_parts[0].id | ||||
|  | ||||
|         else: | ||||
|             # No Part has been selected! | ||||
|             # We must not provide *any* options for SupplierPart | ||||
|             form.fields['supplier_part'].queryset = SupplierPart.objects.none() | ||||
|  | ||||
|         # Otherwise if the user has selected a SupplierPart, we know what Part they meant! | ||||
|         if form['supplier_part'].value() is not None: | ||||
|             pass | ||||
|   | ||||
| @@ -735,3 +735,59 @@ function loadStockTrackingTable(table, options) { | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| function createNewStockItem(options) { | ||||
|     /* Launch a modal form to create a new stock item. | ||||
|      *  | ||||
|      * This is really just a helper function which calls launchModalForm, | ||||
|      * but it does get called a lot, so here we are ... | ||||
|      */ | ||||
|  | ||||
|     // Add in some funky options | ||||
|  | ||||
|     options.callback = [ | ||||
|         {    | ||||
|             field: 'part', | ||||
|             action: function(value) { | ||||
|  | ||||
|                 reloadFieldOptions( | ||||
|                     'supplier_part', | ||||
|                     { | ||||
|                         url: "{% url 'api-supplier-part-list' %}", | ||||
|                         params: { | ||||
|                             part: value, | ||||
|                             pretty: true, | ||||
|                         }, | ||||
|                         text: function(item) { | ||||
|                             return item.pretty_name; | ||||
|                         } | ||||
|                     } | ||||
|                 ); | ||||
|             } | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     options.secondary = [ | ||||
|         { | ||||
|             field: 'part', | ||||
|             label: '{% trans "New Part" %}', | ||||
|             title: '{% trans "Create New Part" %}', | ||||
|             url: "{% url 'part-create' %}", | ||||
|         }, | ||||
|         { | ||||
|             field: 'supplier_part', | ||||
|             label: '{% trans "New Supplier Part" %}', | ||||
|             title: '{% trans "Create new Supplier Part" %}', | ||||
|             url: "{% url 'supplier-part-create' %}" | ||||
|         }, | ||||
|         { | ||||
|             field: 'location', | ||||
|             label: '{% trans "New Location" %}', | ||||
|             title: '{% trans "Create New Location" %}', | ||||
|             url: "{% url 'stock-location-create' %}", | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     launchModalForm("{% url 'stock-item-create' %}", options); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user