diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82cfa4e9e2..c36c11b62b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ The HEAD of the "main" or "master" branch of InvenTree represents the current "l **No pushing to master:** New featues must be submitted as a pull request from a separate branch (one branch per feature). -#### Feature Branches +### Feature Branches Feature branches should be branched *from* the *master* branch. @@ -45,7 +45,7 @@ The HEAD of the "stable" branch represents the latest stable release code. - The bugfix *must* also be cherry picked into the *master* branch. ## Environment -#### Target version +### Target version We are currently targeting: | Name | Minimum version | |---|---| @@ -65,7 +65,7 @@ pyupgrade `find . -name "*.py"` django-upgrade --target-version 3.2 `find . -name "*.py"` ``` -### Credits +## Credits If you add any new dependencies / libraries, they need to be added to [the docs](https://github.com/inventree/inventree-docs/blob/master/docs/credits.md). Please try to do that as timely as possible. @@ -124,4 +124,41 @@ HTML and javascript files are passed through the django templating engine. Trans {% load i18n %} {% trans "This string will be translated" %} - this string will not! -``` \ No newline at end of file +``` + +## Github use +### Tags +The tags describe issues and PRs in multiple areas: +| Area | Name | Description | +|---|---|---| +| Type Labels | | | +| | bug | Identifies a bug which needs to be addressed | +| | dependency | Relates to a project dependency | +| | duplicate | Duplicate of another issue or PR | +| | enhancement | This is an suggested enhancement or new feature | +| | help wanted | Assistance required | +| | invalid | This issue or PR is considered invalid | +| | inactive | Indicates lack of activity | +| | question | This is a question | +| | roadmap | This is a roadmap feature with no immediate plans for implementation | +| | security | Relates to a security issue | +| | starter | Good issue for a developer new to the project | +| | wontfix | No work will be done against this issue or PR | +| Feature Labels | | | +| | API | Relates to the API | +| | barcode | Barcode scanning and integration | +| | build | Build orders | +| | importer | Data importing and processing | +| | order | Purchase order and sales orders | +| | part | Parts | +| | plugin | Plugin ecosystem | +| | pricing | Pricing functionality | +| | report | Report generation | +| | stock | Stock item management | +| | user interface | User interface | +| Ecosystem Labels | | | +| | demo | Relates to the InvenTree demo server or dataset | +| | docker | Docker / docker-compose | +| | CI | CI / unit testing ecosystem | +| | setup | Relates to the InvenTree setup / installation process | + diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index d9dfaa395d..36cd288232 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -537,7 +537,7 @@ def extract_serial_numbers(serials, expected_quantity, next_number: int): # The number of extracted serial numbers must match the expected quantity if not expected_quantity == len(numbers): - raise ValidationError([_("Number of unique serial number ({s}) must match quantity ({q})").format(s=len(numbers), q=expected_quantity)]) + raise ValidationError([_("Number of unique serial numbers ({s}) must match quantity ({q})").format(s=len(numbers), q=expected_quantity)]) return numbers diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 1fdf613b68..43bca0e238 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -1177,7 +1177,7 @@ class BuildItem(models.Model): a = normalize(self.stock_item.quantity) raise ValidationError({ - 'quantity': _(f'Allocated quantity ({q}) must not execed available stock quantity ({a})') + 'quantity': _(f'Allocated quantity ({q}) must not exceed available stock quantity ({a})') }) # Allocated quantity cannot cause the stock item to be over-allocated diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 07a0bcc29a..bed4b59203 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -387,7 +387,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer): default=False, required=False, label=_('Accept Incomplete Allocation'), - help_text=_('Complete ouputs if stock has not been fully allocated'), + help_text=_('Complete outputs if stock has not been fully allocated'), ) notes = serializers.CharField( diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 42bc51bb2f..2fb96e88e3 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -546,14 +546,6 @@ $('#allocate-selected-items').click(function() { ); }); -$("#btn-order-parts").click(function() { - launchModalForm("/order/purchase-order/order-parts/", { - data: { - build: {{ build.id }}, - }, - }); -}); - {% endif %} enableSidebar('buildorder'); diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index e4a589d9e5..b99fbd01fb 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -312,7 +312,7 @@ class SupplierPartList(generics.ListCreateAPIView): try: params = self.request.query_params kwargs['part_detail'] = str2bool(params.get('part_detail', None)) - kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', None)) + kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', True)) kwargs['manufacturer_detail'] = str2bool(params.get('manufacturer_detail', None)) kwargs['pretty'] = str2bool(params.get('pretty', None)) except AttributeError: diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index fff38a8e99..9b881c227e 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -290,9 +290,6 @@ class Contact(models.Model): role = models.CharField(max_length=100, blank=True) - company = models.ForeignKey(Company, related_name='contacts', - on_delete=models.CASCADE) - class ManufacturerPart(models.Model): """ Represents a unique part as provided by a Manufacturer diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html index 489493fd06..c58ea63791 100644 --- a/InvenTree/company/templates/company/company_base.html +++ b/InvenTree/company/templates/company/company_base.html @@ -23,6 +23,8 @@ {% endif %} +{% define perms.company.change_company or perms.company.delete_company as has_permission %} +{% if has_permission %} @@ -38,6 +40,7 @@ {% endif %} +{% endif %} {% endblock actions %} {% block thumbnail %} diff --git a/InvenTree/company/templates/company/detail.html b/InvenTree/company/templates/company/detail.html index 3d715e288c..4474278613 100644 --- a/InvenTree/company/templates/company/detail.html +++ b/InvenTree/company/templates/company/detail.html @@ -325,14 +325,14 @@ var parts = []; selections.forEach(function(item) { - parts.push(item.part); + var part = item.part_detail; + part.manufacturer_part = item.pk; + parts.push(part); }); - launchModalForm("/order/purchase-order/order-parts/", { - data: { - parts: parts, - }, - }); + orderParts( + parts, + ); }); {% endif %} @@ -396,14 +396,16 @@ var parts = []; selections.forEach(function(item) { - parts.push(item.part); + var part = item.part_detail; + parts.push(part); }); - launchModalForm("/order/purchase-order/order-parts/", { - data: { - parts: parts, - }, - }); + orderParts( + parts, + { + supplier: {{ company.pk }}, + } + ); }); {% endif %} diff --git a/InvenTree/company/templates/company/manufacturer_part.html b/InvenTree/company/templates/company/manufacturer_part.html index fb33128a77..5a0e741c1a 100644 --- a/InvenTree/company/templates/company/manufacturer_part.html +++ b/InvenTree/company/templates/company/manufacturer_part.html @@ -31,13 +31,11 @@ {% include "admin_button.html" with url=url %} {% endif %} {% if roles.purchase_order.change %} -{% comment "for later" %} -{% if roles.purchase_order.add %} +{% if roles.purchase_order.add and part.part.purchaseable %} {% endif %} -{% endcomment %} @@ -90,7 +88,14 @@ src="{% static 'img/blank_image.png' %}"
{% trans "Supplier" %} | -{{ order.supplier.name }}{% include "clip.html"%} | ++ {% if order.supplier %} + {{ order.supplier.name }}{% include "clip.html"%} + {% else %} + {% trans "No suppplier information available" %} + {% endif %} + | {% if order.supplier_reference %}||||||||||||
{% trans "Supplier" %} | -{{ item.supplier_part.supplier.name }} | ++ {% if item.supplier_part.supplier %} + {{ item.supplier_part.supplier.name }} + {% endif %} + | ||||||||||||
diff --git a/InvenTree/templates/js/translated/api.js b/InvenTree/templates/js/translated/api.js index fccd6bf5ef..119376c310 100644 --- a/InvenTree/templates/js/translated/api.js +++ b/InvenTree/templates/js/translated/api.js @@ -105,7 +105,7 @@ function inventreeFormDataUpload(url, data, options={}) { } }, error: function(xhr, status, error) { - console.log('Form data upload failure: ' + status); + console.error('Form data upload failure: ' + status); if (options.error) { options.error(xhr, status, error); diff --git a/InvenTree/templates/js/translated/barcode.js b/InvenTree/templates/js/translated/barcode.js index eb8a7f98b7..a6305eb1df 100644 --- a/InvenTree/templates/js/translated/barcode.js +++ b/InvenTree/templates/js/translated/barcode.js @@ -86,7 +86,6 @@ function onCameraAvailable(hasCamera, options) { function onBarcodeScanCompleted(result, options) { if (result.data == '') return; - console.log('decoded qr code:', result.data); stopQrScanner(); postBarcodeData(result.data, options); } diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index e4674d5989..9bd66877da 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -129,7 +129,7 @@ function constructBomUploadTable(data, options={}) { var modal = createNewModal({ title: '{% trans "Row Data" %}', - cancelText: '{% trans "Close" %}', + closeText: '{% trans "Close" %}', hideSubmitButton: true }); @@ -617,7 +617,7 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) { }, }, preFormContent: html, - cancelText: '{% trans "Close" %}', + closeText: '{% trans "Close" %}', submitText: '{% trans "Add Substitute" %}', title: '{% trans "Edit BOM Item Substitutes" %}', afterRender: function(fields, opts) { @@ -1061,7 +1061,7 @@ function loadBomTable(table, options={}) { table.bootstrapTable('append', response); }, error: function(xhr) { - console.log('Error requesting BOM for part=' + part_pk); + console.error('Error requesting BOM for part=' + part_pk); showApiError(xhr); } } diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index c9ebbe0e22..d68b319a25 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -1532,13 +1532,18 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { var pk = $(this).attr('pk'); - launchModalForm('{% url "order-parts" %}', { - data: { - parts: [ - pk, - ] + inventreeGet( + `/api/part/${pk}/`, + {}, + { + success: function(part) { + orderParts( + [part], + {} + ); + } } - }); + ); }); // Callback for 'build' button diff --git a/InvenTree/templates/js/translated/company.js b/InvenTree/templates/js/translated/company.js index c92bb75d6f..d7b98841e4 100644 --- a/InvenTree/templates/js/translated/company.js +++ b/InvenTree/templates/js/translated/company.js @@ -115,10 +115,6 @@ function supplierPartFields() { return { part: {}, - supplier: {}, - SKU: { - icon: 'fa-hashtag', - }, manufacturer_part: { filters: { part_detail: true, @@ -126,6 +122,10 @@ function supplierPartFields() { }, auto_fill: true, }, + supplier: {}, + SKU: { + icon: 'fa-hashtag', + }, description: {}, link: { icon: 'fa-link', diff --git a/InvenTree/templates/js/translated/filters.js b/InvenTree/templates/js/translated/filters.js index ceef79f66d..dd45aa6628 100644 --- a/InvenTree/templates/js/translated/filters.js +++ b/InvenTree/templates/js/translated/filters.js @@ -62,7 +62,7 @@ function loadTableFilters(tableKey) { if (f.length == 2) { filters[f[0]] = f[1]; } else { - console.log(`Improperly formatted filter: ${item}`); + console.warn(`Improperly formatted filter: ${item}`); } } }); @@ -274,7 +274,7 @@ function setupFilterList(tableKey, table, target, options={}) { var element = $(target); if (!element || !element.exists()) { - console.log(`WARNING: setupFilterList could not find target '${target}'`); + console.warn(`setupFilterList could not find target '${target}'`); return; } diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 88c9c5badb..cc138052ef 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -135,7 +135,7 @@ function getApiEndpointOptions(url, callback) { success: callback, error: function(xhr) { // TODO: Handle error - console.log(`ERROR in getApiEndpointOptions at '${url}'`); + console.error(`Error in getApiEndpointOptions at '${url}'`); showApiError(xhr, url); } }); @@ -227,7 +227,7 @@ function constructChangeForm(fields, options) { }, error: function(xhr) { // TODO: Handle error here - console.log(`ERROR in constructChangeForm at '${options.url}'`); + console.error(`Error in constructChangeForm at '${options.url}'`); showApiError(xhr, options.url); } @@ -268,7 +268,7 @@ function constructDeleteForm(fields, options) { }, error: function(xhr) { // TODO: Handle error here - console.log(`ERROR in constructDeleteForm at '${options.url}`); + console.error(`Error in constructDeleteForm at '${options.url}`); showApiError(xhr, options.url); } @@ -354,7 +354,7 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`'POST action unavailable at ${url}`); + console.warn(`'POST action unavailable at ${url}`); } break; case 'PUT': @@ -369,7 +369,7 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`${options.method} action unavailable at ${url}`); + console.warn(`${options.method} action unavailable at ${url}`); } break; case 'DELETE': @@ -383,7 +383,7 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`DELETE action unavailable at ${url}`); + console.warn(`DELETE action unavailable at ${url}`); } break; case 'GET': @@ -397,11 +397,11 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`GET action unavailable at ${url}`); + console.warn(`GET action unavailable at ${url}`); } break; default: - console.log(`constructForm() called with invalid method '${options.method}'`); + console.warn(`constructForm() called with invalid method '${options.method}'`); break; } }); @@ -731,7 +731,7 @@ function submitFormData(fields, options) { data[name] = value; } } else { - console.log(`WARNING: Could not find field matching '${name}'`); + console.warn(`Could not find field matching '${name}'`); } } @@ -776,7 +776,7 @@ function submitFormData(fields, options) { default: $(options.modal).modal('hide'); - console.log(`upload error at ${options.url}`); + console.error(`Upload error at ${options.url}`); showApiError(xhr, options.url); break; } @@ -827,7 +827,7 @@ function updateFieldValue(name, value, field, options) { var el = getFormFieldElement(name, options); if (!el) { - console.log(`WARNING: updateFieldValue could not find field '${name}'`); + console.warn(`updateFieldValue could not find field '${name}'`); return; } @@ -870,7 +870,7 @@ function getFormFieldElement(name, options) { } if (!el.exists) { - console.log(`ERROR: Could not find form element for field '${name}'`); + console.error(`Could not find form element for field '${name}'`); } return el; @@ -918,7 +918,7 @@ function getFormFieldValue(name, field={}, options={}) { var el = getFormFieldElement(name, options); if (!el.exists()) { - console.log(`ERROR: getFormFieldValue could not locate field '${name}'`); + console.error(`getFormFieldValue could not locate field '${name}'`); return null; } @@ -1104,7 +1104,7 @@ function handleNestedErrors(errors, field_name, options={}) { // Nest list must be provided! if (!nest_list) { - console.log(`WARNING: handleNestedErrors missing nesting options for field '${fieldName}'`); + console.warn(`handleNestedErrors missing nesting options for field '${fieldName}'`); return; } @@ -1113,7 +1113,7 @@ function handleNestedErrors(errors, field_name, options={}) { var error_item = error_list[idx]; if (idx >= nest_list.length) { - console.log(`WARNING: handleNestedErrors returned greater number of errors (${error_list.length}) than could be handled (${nest_list.length})`); + console.warn(`handleNestedErrors returned greater number of errors (${error_list.length}) than could be handled (${nest_list.length})`); break; } @@ -1218,29 +1218,26 @@ function handleFormErrors(errors, fields={}, options={}) { for (var field_name in errors) { - if (field_name in fields) { + var field = fields[field_name] || {}; - var field = fields[field_name]; + if ((field.type == 'field') && ('child' in field)) { + // This is a "nested" field + handleNestedErrors(errors, field_name, options); + } else { + // This is a "simple" field - if ((field.type == 'field') && ('child' in field)) { - // This is a "nested" field - handleNestedErrors(errors, field_name, options); - } else { - // This is a "simple" field + var field_errors = errors[field_name]; - var field_errors = errors[field_name]; + if (field_errors && !first_error_field && isFieldVisible(field_name, options)) { + first_error_field = field_name; + } - if (field_errors && !first_error_field && isFieldVisible(field_name, options)) { - first_error_field = field_name; - } + // Add an entry for each returned error message + for (var ii = field_errors.length-1; ii >= 0; ii--) { - // Add an entry for each returned error message - for (var ii = field_errors.length-1; ii >= 0; ii--) { + var error_text = field_errors[ii]; - var error_text = field_errors[ii]; - - addFieldErrorMessage(field_name, error_text, ii, options); - } + addFieldErrorMessage(field_name, error_text, ii, options); } } } @@ -1285,7 +1282,7 @@ function addFieldErrorMessage(name, error_text, error_idx=0, options={}) { field_dom.append(error_html); } else { - console.log(`WARNING: addFieldErrorMessage could not locate field '${field_name}'`); + console.warn(`addFieldErrorMessage could not locate field '${field_name}'`); } } @@ -1358,7 +1355,7 @@ function addClearCallback(name, field, options={}) { } if (!el) { - console.log(`WARNING: addClearCallback could not find field '${name}'`); + console.warn(`addClearCallback could not find field '${name}'`); return; } @@ -1582,7 +1579,7 @@ function initializeRelatedField(field, fields, options={}) { var name = field.name; if (!field.api_url) { - console.log(`WARNING: Related field '${name}' missing 'api_url' parameter.`); + console.warn(`Related field '${name}' missing 'api_url' parameter.`); return; } @@ -1712,7 +1709,7 @@ function initializeRelatedField(field, fields, options={}) { return $(html); } else { // Return a simple renderering - console.log(`WARNING: templateResult() missing 'field.model' for '${name}'`); + console.warn(`templateResult() missing 'field.model' for '${name}'`); return `${name} - ${item.id}`; } }, @@ -1742,7 +1739,7 @@ function initializeRelatedField(field, fields, options={}) { return $(html); } else { // Return a simple renderering - console.log(`WARNING: templateSelection() missing 'field.model' for '${name}'`); + console.warn(`templateSelection() missing 'field.model' for '${name}'`); return `${name} - ${item.id}`; } } @@ -1780,6 +1777,11 @@ function initializeRelatedField(field, fields, options={}) { // Only a single result is available, given the provided filters if (data.count == 1) { setRelatedFieldData(name, data.results[0], options); + + // Run "callback" function (if supplied) + if (field.onEdit) { + field.onEdit(data.results[0], name, field, options); + } } } }); @@ -1911,7 +1913,7 @@ function renderModelData(name, model, data, parameters, options) { if (html != null) { return html; } else { - console.log(`ERROR: Rendering not implemented for model '${model}'`); + console.error(`Rendering not implemented for model '${model}'`); // Simple text rendering return `${model} - ID ${data.id}`; } @@ -1924,6 +1926,10 @@ function renderModelData(name, model, data, parameters, options) { function getFieldName(name, options={}) { var field_name = name; + if (options.field_suffix) { + field_name += options.field_suffix; + } + if (options && options.depth) { field_name += `_${options.depth}`; } @@ -2196,7 +2202,7 @@ function constructInput(name, parameters, options={}) { if (func != null) { html = func(name, parameters, options); } else { - console.log(`WARNING: Unhandled form field type: '${parameters.type}'`); + console.warn(`Unhandled form field type: '${parameters.type}'`); } return html; @@ -2499,12 +2505,12 @@ function constructHelpText(name, parameters) { function selectImportFields(url, data={}, options={}) { if (!data.model_fields) { - console.log(`WARNING: selectImportFields is missing 'model_fields'`); + console.warn(`selectImportFields is missing 'model_fields'`); return; } if (!data.file_fields) { - console.log(`WARNING: selectImportFields is missing 'file_fields'`); + console.warn(`selectImportFields is missing 'file_fields'`); return; } @@ -2595,7 +2601,7 @@ function selectImportFields(url, data={}, options={}) { default: $(opts.modal).modal('hide'); - console.log(`upload error at ${opts.url}`); + console.error(`upload error at ${opts.url}`); showApiError(xhr, opts.url); break; } diff --git a/InvenTree/templates/js/translated/modals.js b/InvenTree/templates/js/translated/modals.js index f114f6f419..85f503682e 100644 --- a/InvenTree/templates/js/translated/modals.js +++ b/InvenTree/templates/js/translated/modals.js @@ -85,12 +85,25 @@ function createNewModal(options={}) { var modal_name = `#modal-form-${id}`; + // Callback *after* the modal has been rendered $(modal_name).on('shown.bs.modal', function() { $(modal_name + ' .modal-form-content').scrollTop(0); if (options.focus) { getFieldByName(modal_name, options.focus).focus(); } + + // Steal keyboard focus + $(modal_name).focus(); + + if (options.hideCloseButton) { + $(modal_name).find('#modal-form-cancel').hide(); + } + + if (options.preventSubmit || options.hideSubmitButton) { + $(modal_name).find('#modal-form-submit').hide(); + } + }); // Automatically remove the modal when it is deleted! @@ -102,8 +115,11 @@ function createNewModal(options={}) { $(modal_name).on('keydown', 'input', function(event) { if (event.keyCode == 13) { event.preventDefault(); - // Simulate a click on the 'Submit' button - $(modal_name).find('#modal-form-submit').click(); + + if (!options.preventSubmit) { + // Simulate a click on the 'Submit' button + $(modal_name).find('#modal-form-submit').click(); + } return false; } @@ -117,18 +133,7 @@ function createNewModal(options={}) { // Set labels based on supplied options modalSetTitle(modal_name, options.title || '{% trans "Form Title" %}'); modalSetSubmitText(modal_name, options.submitText || '{% trans "Submit" %}'); - modalSetCloseText(modal_name, options.cancelText || '{% trans "Cancel" %}'); - - if (options.hideSubmitButton) { - $(modal_name).find('#modal-form-submit').hide(); - } - - if (options.hideCloseButton) { - $(modal_name).find('#modal-form-cancel').hide(); - } - - // Steal keyboard focus - $(modal_name).focus(); + modalSetCloseText(modal_name, options.closeText || '{% trans "Cancel" %}'); // Return the "name" of the modal return modal_name; @@ -274,7 +279,7 @@ function reloadFieldOptions(fieldName, options) { setFieldOptions(fieldName, opts); }, error: function() { - console.log('Error GETting field options'); + console.error('Error GETting field options'); } }); } @@ -581,7 +586,7 @@ function showAlertDialog(title, content, options={}) { var modal = createNewModal({ title: title, - cancelText: '{% trans "Close" %}', + closeText: '{% trans "Close" %}', hideSubmitButton: true, }); @@ -607,7 +612,7 @@ function showQuestionDialog(title, content, options={}) { var modal = createNewModal({ title: title, submitText: options.accept_text || '{% trans "Accept" %}', - cancelText: options.cancel_text || '{% trans "Cancel" %}', + closeText: options.cancel_text || '{% trans "Cancel" %}', }); modalSetContent(modal, content); @@ -842,7 +847,7 @@ function attachFieldCallback(modal, callback) { // 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()}`); + console.info(`Value changed for field ${callback.field} - ${field.val()} (no callback attached)`); } }); } @@ -1085,8 +1090,8 @@ function launchModalForm(url, options = {}) { showAlertDialog('{% trans "Error requesting form data" %}', renderErrorMessage(xhr)); } - console.log('Modal form error: ' + xhr.status); - console.log('Message: ' + xhr.responseText); + console.error('Modal form error: ' + xhr.status); + console.info('Message: ' + xhr.responseText); } }; diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index b88de5af35..d55d93e531 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -34,7 +34,7 @@ // Should the ID be rendered for this string function renderId(title, pk, parameters={}) { - // Default = do not display + // Default = do not render var render = false; if ('render_pk' in parameters) { @@ -297,7 +297,12 @@ function renderSalesOrderShipment(name, data, parameters={}, options={}) { var so_prefix = global_settings.SALESORDER_REFERENCE_PREFIX; - var html = `${so_prefix}${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference}`; + var html = ` + ${so_prefix}${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference} + + {% trans "Shipment ID" %}: ${data.pk} + + `; html += renderId('{% trans "Shipment ID" %}', data.pk, parameters); @@ -384,10 +389,18 @@ function renderSupplierPart(name, data, parameters={}, options={}) { var html = ''; html += select2Thumbnail(supplier_image); - html += select2Thumbnail(part_image); - html += ` ${data.supplier_detail.name} - ${data.SKU}`; - html += ` - ${data.part_detail.full_name}`; + if (data.part_detail) { + html += select2Thumbnail(part_image); + } + + if (data.supplier_detail) { + html += ` ${data.supplier_detail.name} - ${data.SKU}`; + } + + if (data.part_detail) { + html += ` - ${data.part_detail.full_name}`; + } html += renderId('{% trans "Supplier Part ID" %}', data.pk, parameters); diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 4aad54a65a..d5ca7caf42 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -35,6 +35,7 @@ loadSalesOrderTable, newPurchaseOrderFromOrderWizard, newSupplierPartFromOrderWizard, + orderParts, removeOrderRowFromOrderWizard, removePurchaseOrderLineItem, loadOrderTotal, @@ -259,8 +260,8 @@ function createPurchaseOrder(options={}) { } } }, - supplier_reference: {}, description: {}, + supplier_reference: {}, target_date: { icon: 'fa-calendar-alt', }, @@ -476,6 +477,328 @@ function exportOrder(redirect_url, options={}) { }); } + +/* + * Create a new form to order parts based on the list of provided parts. + */ +function orderParts(parts_list, options={}) { + + var parts = []; + + var parts_seen = {}; + + parts_list.forEach(function(part) { + if (part.purchaseable) { + + // Prevent duplicates + if (!(part.pk in parts_seen)) { + parts_seen[part.pk] = true; + parts.push(part); + } + } + }); + + if (parts.length == 0) { + showAlertDialog( + '{% trans "Select Parts" %}', + '{% trans "At least one purchaseable part must be selected" %}', + ); + return; + } + + // Render a single part within the dialog + function renderPart(part, opts={}) { + + var pk = part.pk; + + var thumb = thumbnailImage(part.thumbnail || part.image); + + // The "quantity" field should have been provided for each part + var quantity = part.quantity || 1; + + if (quantity < 0) { + quantity = 0; + } + + var quantity_input = constructField( + `quantity_${pk}`, + { + type: 'decimal', + min_value: 0, + value: quantity, + title: '{% trans "Quantity to order" %}', + required: true, + }, + { + hideLabels: true, + } + ); + + var supplier_part_prefix = ` + + `; + + var supplier_part_input = constructField( + `part_${pk}`, + { + type: 'related field', + required: true, + prefixRaw: supplier_part_prefix, + }, + { + hideLabels: true, + } + ); + + var purchase_order_prefix = ` + + `; + + var purchase_order_input = constructField( + `order_${pk}`, + { + type: 'related field', + required: true, + prefixRaw: purchase_order_prefix, + }, + { + hideLabels: 'true', + } + ); + + var buttons = ` | ||||||||||||||
${thumb} ${part.full_name} | +${supplier_part_input} | +${purchase_order_input} | +${quantity_input} | +${buttons} | +
{% trans "Part" %} | +{% trans "Supplier Part" %} | +{% trans "Purchase Order" %} | +{% trans "Quantity" %} | ++ |
---|
Open Source Inventory Management System
- - -# InvenTree - - - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Coverage Status](https://coveralls.io/repos/github/inventree/InvenTree/badge.svg)](https://coveralls.io/github/inventree/InvenTree) -[![Crowdin](https://badges.crowdin.net/inventree/localized.svg)](https://crowdin.com/project/inventree) + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/inventree/inventree) ![CI](https://github.com/inventree/inventree/actions/workflows/qc_checks.yaml/badge.svg) +![Docker Build](https://github.com/inventree/inventree/actions/workflows/docker_latest.yaml/badge.svg) + +![Coveralls](https://img.shields.io/coveralls/github/inventree/InvenTree) +[![Crowdin](https://badges.crowdin.net/inventree/localized.svg)](https://crowdin.com/project/inventree) +![Lines of code](https://img.shields.io/tokei/lines/github/inventree/InvenTree) +![GitHub commit activity](https://img.shields.io/github/commit-activity/m/inventree/inventree) +![PyPI - Downloads](https://img.shields.io/pypi/dm/inventree) +[![Docker Pulls](https://img.shields.io/docker/pulls/inventree/inventree)](https://hub.docker.com/r/inventree/inventree) + +![GitHub Org's stars](https://img.shields.io/github/stars/inventree?style=social) +![Twitter Follow](https://img.shields.io/twitter/follow/inventreedb?style=social) +![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/inventree?style=social) + + +