diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 3f9c2d6cec..a3b23dd07d 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -88,6 +88,11 @@ DEBUG = _is_true(get_setting( CONFIG.get('debug', True) )) +DOCKER = _is_true(get_setting( + 'INVENTREE_DOCKER', + False +)) + # Configure logging settings log_level = get_setting( 'INVENTREE_LOG_LEVEL', diff --git a/InvenTree/InvenTree/static/script/inventree/inventree.js b/InvenTree/InvenTree/static/script/inventree/inventree.js index 4b43b342af..1a6fdec47a 100644 --- a/InvenTree/InvenTree/static/script/inventree/inventree.js +++ b/InvenTree/InvenTree/static/script/inventree/inventree.js @@ -12,7 +12,7 @@ function attachClipboard(selector, containerselector, textElement) { return document.getElementById(textElement).textContent; } } else { - text = function() { + text = function(trigger) { var content = trigger.parentElement.parentElement.textContent;return content.trim(); } } @@ -22,7 +22,6 @@ function attachClipboard(selector, containerselector, textElement) { text: text, container: containerselector }); - console.log(cis); } @@ -81,7 +80,6 @@ function inventreeDocReady() { attachClipboard('.clip-btn'); attachClipboard('.clip-btn', 'modal-about'); // modals attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text'); // version-text - } function isFileTransfer(transfer) { diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index 6018f64cef..669a5e8212 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -459,8 +459,8 @@ class FileManagementFormView(MultiStepFormView): if guess: n = list(self.column_selections.values()).count(self.column_selections[col]) - if n > 1: - duplicates.append(col) + if n > 1 and self.column_selections[col] not in duplicates: + duplicates.append(self.column_selections[col]) # Store extra context data self.extra_context_data = { diff --git a/InvenTree/order/admin.py b/InvenTree/order/admin.py index 4519f8a2a9..1e7b20e5a1 100644 --- a/InvenTree/order/admin.py +++ b/InvenTree/order/admin.py @@ -13,6 +13,10 @@ from .models import SalesOrder, SalesOrderLineItem from .models import SalesOrderAllocation +class PurchaseOrderLineItemInlineAdmin(admin.StackedInline): + model = PurchaseOrderLineItem + + class PurchaseOrderAdmin(ImportExportModelAdmin): list_display = ( @@ -29,6 +33,10 @@ class PurchaseOrderAdmin(ImportExportModelAdmin): 'description', ] + inlines = [ + PurchaseOrderLineItemInlineAdmin + ] + class SalesOrderAdmin(ImportExportModelAdmin): diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 5305038b4f..fb169e0536 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -826,6 +826,9 @@ class SalesOrderAllocation(models.Model): else: return "" + def get_po(self): + return self.item.purchase_order + def complete_allocation(self, user): """ Complete this allocation (called when the parent SalesOrder is marked as "shipped"): diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 062e8986b2..6091140313 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -235,6 +235,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): location_path = serializers.CharField(source='get_location_path') location_id = serializers.IntegerField(source='get_location') serial = serializers.CharField(source='get_serial') + po = serializers.CharField(source='get_po') quantity = serializers.FloatField() class Meta: @@ -247,6 +248,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): 'quantity', 'location_id', 'location_path', + 'po', 'item', ] diff --git a/InvenTree/order/templates/order/order_wizard/match_fields.html b/InvenTree/order/templates/order/order_wizard/match_fields.html index 4ff7b6a963..cd81142341 100644 --- a/InvenTree/order/templates/order/order_wizard/match_fields.html +++ b/InvenTree/order/templates/order/order_wizard/match_fields.html @@ -55,7 +55,7 @@ {{ col }} {% for duplicate in duplicates %} - {% if duplicate == col.name %} + {% if duplicate == col.value %} diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index 72b7d63911..a90e61bff9 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -87,6 +87,9 @@ function showAllocationSubTable(index, row, element) { return renderLink(row.location_path, `/stock/location/${row.location_id}/`); }, }, + { + field: 'po' + }, { field: 'buttons', title: '{% trans "Actions" %}', @@ -159,9 +162,12 @@ function showFulfilledSubTable(index, row, element) { text = `{% trans "Quantity" %}: ${row.quantity}`; } - return renderLink(text, `/stock/item/${row.pk}/`); + return renderLink(text, `/stock/item/${row.pk}/`); }, - } + }, + { + field: 'po' + }, ], }); } @@ -271,6 +277,25 @@ $("#so-lines-table").inventreeTable({ field: 'notes', title: '{% trans "Notes" %}', }, + { + field: 'po', + title: '{% trans "PO" %}', + formatter: function(value, row, index, field) { + var po_name = ""; + if (row.allocated) { + row.allocations.forEach(function(allocation) { + if (allocation.po != po_name) { + if (po_name) { + po_name = "-"; + } else { + po_name = allocation.po + } + } + }) + } + return `
` + po_name + `
`; + } + }, {% if order.status == SalesOrderStatus.PENDING %} { field: 'buttons', diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index cf79746f0d..c8ec42d3e7 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -90,7 +90,7 @@ class SalesOrderDetail(InvenTreeRoleMixin, DetailView): """ Detail view for a SalesOrder object """ context_object_name = 'order' - queryset = SalesOrder.objects.all().prefetch_related('lines') + queryset = SalesOrder.objects.all().prefetch_related('lines__allocations__item__purchase_order') template_name = 'order/sales_order_detail.html' diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 6ac34f9d5e..e73dfe88c4 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -1,8 +1,14 @@ +# -*- coding: utf-8 -*- + """ This module provides template tags for extra functionality over and above the built-in Django tags. """ + import os +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings as djangosettings + from django import template from django.urls import reverse from django.utils.safestring import mark_safe @@ -68,6 +74,33 @@ def part_allocation_count(build, part, *args, **kwargs): return InvenTree.helpers.decimal2string(build.getAllocatedQuantity(part)) +@register.simple_tag() +def inventree_in_debug_mode(*args, **kwargs): + """ Return True if the server is running in DEBUG mode """ + + return djangosettings.DEBUG + + +@register.simple_tag() +def inventree_docker_mode(*args, **kwargs): + """ Return True if the server is running as a Docker image """ + + return djangosettings.DOCKER + + +@register.simple_tag() +def inventree_db_engine(*args, **kwargs): + """ Return the InvenTree database backend e.g. 'postgresql' """ + + db = djangosettings.DATABASES['default'] + + engine = db.get('ENGINE', _('Unknown database')) + + engine = engine.replace('django.db.backends.', '') + + return engine + + @register.simple_tag() def inventree_instance_name(*args, **kwargs): """ Return the InstanceName associated with the current database """ diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 28123ebc41..88f8dd081c 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1370,6 +1370,12 @@ class StockItem(MPTTModel): if self.location: s += ' @ {loc}'.format(loc=self.location.name) + if self.purchase_order: + s += " ({pre}{po})".format( + pre=helpers.getSetting("PURCHASEORDER_REFERENCE_PREFIX"), + po=self.purchase_order, + ) + return s @transaction.atomic diff --git a/InvenTree/templates/js/modals.js b/InvenTree/templates/js/modals.js index b621caab80..f447fdce72 100644 --- a/InvenTree/templates/js/modals.js +++ b/InvenTree/templates/js/modals.js @@ -89,6 +89,15 @@ function setFieldOptions(fieldName, optionList, options={}) { } +function clearFieldOptions(fieldName) { + /** + * Clear (emtpy) the options list for a particular field + */ + + setFieldOptions(fieldName, []); +} + + function reloadFieldOptions(fieldName, options) { /* Reload the options for a given field, * using an AJAX request. diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index a0601aeb13..8f06d403ee 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -1218,6 +1218,17 @@ function createNewStockItem(options) { field: 'part', action: function(value) { + if (!value) { + // No part chosen + + clearFieldOptions('supplier_part'); + enableField('serial_numbers', false); + enableField('purchase_price_0', false); + enableField('purchase_price_1', false); + + return; + } + // Reload options for supplier part reloadFieldOptions( 'supplier_part', @@ -1243,6 +1254,9 @@ function createNewStockItem(options) { enableField('serial_numbers', response.trackable); clearField('serial_numbers'); + enableField('purchase_price_0', response.purchaseable); + enableField('purchase_price_1', response.purchaseable); + // Populate the expiry date if (response.default_expiry <= 0) { // No expiry date diff --git a/InvenTree/templates/stats.html b/InvenTree/templates/stats.html index eff9c4504f..403030644a 100644 --- a/InvenTree/templates/stats.html +++ b/InvenTree/templates/stats.html @@ -13,6 +13,27 @@ {% trans "Instance Name" %} {% inventree_instance_name %} + + + {% trans "Database" %} + {% inventree_db_engine %} + + {% inventree_in_debug_mode as debug_mode %} + {% if debug_mode %} + + + {% trans "Debug Mode" %} + {% trans "Server is running in debug mode" %} + + {% endif %} + {% inventree_docker_mode as docker_mode %} + {% if docker_mode %} + + + {% trans "Docker Mode" %} + {% trans "Server is deployed using docker" %} + + {% endif %} {% if user.is_staff %} diff --git a/InvenTree/templates/version.html b/InvenTree/templates/version.html index c8ec6862b6..b702fd85f5 100644 --- a/InvenTree/templates/version.html +++ b/InvenTree/templates/version.html @@ -2,4 +2,7 @@ InvenTree-Version: {% inventree_version %} Django Version: {% django_version %} {% inventree_commit_hash as hash %}{% if hash %}Commit Hash: {{ hash }}{% endif %} -{% inventree_commit_date as commit_date %}{% if commit_date %}Commit Date: {{ commit_date }}{% endif %} \ No newline at end of file +{% inventree_commit_date as commit_date %}{% if commit_date %}Commit Date: {{ commit_date }}{% endif %} +Database: {% inventree_db_engine %} +Debug-Mode: {% inventree_in_debug_mode %} +Deployed using Docker: {% inventree_docker_mode %} \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 3e0a7e1230..ea70d9f994 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -14,6 +14,7 @@ ENV INVENTREE_REPO="${repository}" ENV INVENTREE_BRANCH="${branch}" ENV INVENTREE_LOG_LEVEL="INFO" +ENV INVENTREE_DOCKER="true" # InvenTree paths ENV INVENTREE_SRC_DIR="${INVENTREE_HOME}/src"