diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 4be8a28acb..56a1a116da 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -28,6 +28,7 @@ class HelperForm(forms.ModelForm): self.helper = FormHelper() self.helper.form_tag = False + self.helper.form_show_errors = True """ Create a default 'layout' for this form. @@ -43,11 +44,6 @@ class HelperForm(forms.ModelForm): valid = super(HelperForm, self).is_valid() - # Check for errors from model validation - # If none, disable crispy form errors - if not self.errors: - self.helper.form_show_errors = False - return valid def rebuild_layout(self): diff --git a/InvenTree/company/templates/company/supplier_part_create.html b/InvenTree/company/templates/company/supplier_part_create.html new file mode 100644 index 0000000000..21c23f9075 --- /dev/null +++ b/InvenTree/company/templates/company/supplier_part_create.html @@ -0,0 +1,17 @@ +{% extends "modal_form.html" %} + +{% load i18n %} + +{% block pre_form_content %} +{{ block.super }} + +{% if part %} +
+ {% include "hover_image.html" with image=part.image %} + {{ part.full_name}} +
+ {{ part.description }} +
+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index 2720f4ccac..e863fa1d72 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -291,7 +291,7 @@ class SupplierPartCreate(AjaxCreateView): model = SupplierPart form_class = EditSupplierPartForm - ajax_template_name = 'modal_form.html' + ajax_template_name = 'company/supplier_part_create.html' ajax_form_title = _('Create new Supplier Part') context_object_name = 'part' role_required = 'purchase_order.add' @@ -304,6 +304,27 @@ class SupplierPartCreate(AjaxCreateView): # TODO - What validation steps can be performed on the single_pricing field? pass + def get_context_data(self): + """ + Supply context data to the form + """ + + ctx = super().get_context_data() + + # Add 'part' object + form = self.get_form() + + part = form['part'].value() + + try: + part = Part.objects.get(pk=part) + except (ValueError, Part.DoesNotExist): + part = None + + ctx['part'] = part + + return ctx + def save(self, form): """ If single_pricing is defined, add a price break for quantity=1 diff --git a/InvenTree/order/templates/order/order_wizard/select_parts.html b/InvenTree/order/templates/order/order_wizard/select_parts.html index 9c7c90ac26..c93e26e363 100644 --- a/InvenTree/order/templates/order/order_wizard/select_parts.html +++ b/InvenTree/order/templates/order/order_wizard/select_parts.html @@ -11,11 +11,11 @@ {% if parts|length > 0 %} {% else %} {% endif %} @@ -39,8 +39,8 @@ {{ part.full_name }} {{ part.description }} - diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 7bec904ce0..0eebe6617d 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -336,7 +336,7 @@ class PartStarSerializer(InvenTreeModelSerializer): class BomItemSerializer(InvenTreeModelSerializer): """ Serializer for BomItem object """ - price_range = serializers.CharField(read_only=True) + # price_range = serializers.CharField(read_only=True) quantity = serializers.FloatField() @@ -387,7 +387,7 @@ class BomItemSerializer(InvenTreeModelSerializer): 'sub_part_detail', 'quantity', 'reference', - 'price_range', + # 'price_range', 'optional', 'overage', 'note', diff --git a/InvenTree/part/templates/part/bom_upload/select_fields.html b/InvenTree/part/templates/part/bom_upload/select_fields.html index bd0c222881..60f0efcfad 100644 --- a/InvenTree/part/templates/part/bom_upload/select_fields.html +++ b/InvenTree/part/templates/part/bom_upload/select_fields.html @@ -59,7 +59,7 @@ {% endfor %} {% if col.duplicate %} -

Duplicate column selection

+

{% trans "Duplicate column selection" %}

{% endif %} {% endfor %} diff --git a/InvenTree/part/templates/part/bom_upload/select_parts.html b/InvenTree/part/templates/part/bom_upload/select_parts.html index ede92c6c30..1ee5e8821b 100644 --- a/InvenTree/part/templates/part/bom_upload/select_parts.html +++ b/InvenTree/part/templates/part/bom_upload/select_parts.html @@ -63,7 +63,7 @@ {% for part in row.part_options %} {% endfor %} diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/bom.js index c55429faba..299045cfa5 100644 --- a/InvenTree/templates/js/bom.js +++ b/InvenTree/templates/js/bom.js @@ -140,6 +140,12 @@ function loadBomTable(table, options) { }); } + // 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( { @@ -197,11 +203,11 @@ function loadBomTable(table, options) { text = parseFloat(text); if (row.optional) { - text += " ({% trans "Optional" %})"; + text += ' ({% trans "Optional" %})'; } if (row.overage) { - text += " (+" + row.overage + ") "; + text += ` (${row.overage}) `; } return text; @@ -228,6 +234,12 @@ function loadBomTable(table, options) { } }); + /* + + // TODO - Re-introduce the pricing column at a later stage, + // once the pricing has been "fixed" + // O.W. 2020-11-24 + cols.push( { field: 'price_range', @@ -241,6 +253,7 @@ function loadBomTable(table, options) { } } }); + */ } // Part notes @@ -327,9 +340,8 @@ function loadBomTable(table, options) { table.inventreeTable({ treeEnable: !options.editable, - rootParentId: options.parent_id, + rootParentId: parent_id, idField: 'pk', - //uniqueId: 'pk', parentIdField: 'parentId', treeShowField: 'sub_part', showColumns: true, @@ -338,12 +350,18 @@ function loadBomTable(table, options) { search: true, rowStyle: function(row, index) { if (row.validated) { - return {classes: 'rowvalid'}; + return { + classes: 'rowvalid' + }; } else { - return {classes: 'rowinvalid'}; + return { + classes: 'rowinvalid' + }; } }, - formatNoMatches: function() { return '{% trans "No BOM items found" %}'; }, + formatNoMatches: function() { + return '{% trans "No BOM items found" %}'; + }, clickToSelect: true, queryParams: filters, original: params, @@ -376,7 +394,7 @@ function loadBomTable(table, options) { } // Set the parent ID of the top-level rows - row.parentId = options.parent_id; + row.parentId = parent_id; table.bootstrapTable('updateRow', idx, row, true); diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js index ab6f0e4e0a..a3c7bd5186 100644 --- a/InvenTree/templates/js/build.js +++ b/InvenTree/templates/js/build.js @@ -243,6 +243,22 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { }); }); + // Callback for 'buy' button + $(table).find('.button-buy').click(function() { + var pk = $(this).attr('pk'); + + var idx = $(this).closest('tr').attr('data-index'); + var row = $(table).bootstrapTable('getData')[idx]; + + launchModalForm('{% url "order-parts" %}', { + data: { + parts: [ + pk, + ] + } + }); + }); + // Callback for 'build' button $(table).find('.button-build').click(function() { var pk = $(this).attr('pk'); @@ -563,7 +579,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { } if (row.sub_part_detail.purchaseable) { - html += makeIconButton('fa-shopping-cart icon-blue', 'button-buy', row.sub_part, '{% trans "Order stock" %}', {disabled: true}); + html += makeIconButton('fa-shopping-cart icon-blue', 'button-buy', row.sub_part, '{% trans "Order stock" %}'); } html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', row.sub_part, '{% trans "Allocate stock" %}'); diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index 41a1b4c046..69d4f584d9 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -21,9 +21,16 @@ function newSupplierPartFromOrderWizard(e) { e = e || window.event; - var src = e.target || e.srcElement; + var src = e.srcElement || e.target; - var part = $(src).attr('part-id'); + var part = $(src).attr('part'); + + console.log('part: ' + part); + + if (!part) { + part = $(src).closest('button').attr('part'); + console.log('parent: ' + part); + } launchModalForm("/supplier-part/new/", { modal: '#modal-form-secondary', @@ -125,7 +132,7 @@ function loadPurchaseOrderTable(table, options) { name: 'purchaseorder', groupBy: false, original: options.params, - formatNoMatches: function() { return "{% trans "No purchase orders found" %}"; }, + formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; }, columns: [ { field: 'pk', @@ -208,7 +215,7 @@ function loadSalesOrderTable(table, options) { name: 'salesorder', groupBy: false, original: options.params, - formatNoMatches: function() { return "{% trans "No sales orders found" %}"; }, + formatNoMatches: function() { return '{% trans "No sales orders found" %}'; }, columns: [ { field: 'pk', @@ -265,7 +272,7 @@ function loadSalesOrderTable(table, options) { { sortable: true, field: 'shipment_date', - title: "{% trans "Shipment Date" %}", + title: '{% trans "Shipment Date" %}', }, { sortable: true, diff --git a/InvenTree/templates/modal_form.html b/InvenTree/templates/modal_form.html index 0a5ae59916..94bf032579 100644 --- a/InvenTree/templates/modal_form.html +++ b/InvenTree/templates/modal_form.html @@ -1,3 +1,5 @@ +{% load i18n %} +
{% if form.pre_form_info %} {% endif %} -{% block non_field_error %} -{% if form.non_field_errors %} - -{% endif %} -{% endblock %}
{% block pre_form_content %} @@ -41,4 +31,4 @@ {% endblock %} {% block post_form_content %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/tasks.py b/tasks.py index 30641c7c81..69c53b83d4 100644 --- a/tasks.py +++ b/tasks.py @@ -102,6 +102,16 @@ def install(c): print("Config file 'config.yaml' does not exist - copying from template.") copyfile(CONFIG_TEMPLATE_FILE, CONFIG_FILE) + +@task +def shell(c): + """ + Open a python shell with access to the InvenTree database models. + """ + + manage(c, 'shell', pty=True) + + @task def superuser(c): """