From 4a0ed4b2a1c69dcc17238855a226f95f5a8bed43 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 19 Oct 2021 22:49:48 +1100 Subject: [PATCH 001/177] Start of API forms for stock item --- InvenTree/stock/serializers.py | 9 +- .../stock/templates/stock/item_base.html | 5 ++ InvenTree/templates/js/translated/forms.js | 12 +++ InvenTree/templates/js/translated/stock.js | 89 +++++++++++++++++++ 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index c44dffe94f..e2781af12f 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -134,7 +134,7 @@ class StockItemSerializer(InvenTreeModelSerializer): tracking_items = serializers.IntegerField(source='tracking_info_count', read_only=True, required=False) - quantity = serializers.FloatField() + # quantity = serializers.FloatField() allocated = serializers.FloatField(source='allocation_count', required=False) @@ -142,20 +142,22 @@ class StockItemSerializer(InvenTreeModelSerializer): stale = serializers.BooleanField(required=False, read_only=True) - serial = serializers.CharField(required=False) + # serial = serializers.CharField(required=False) required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False) purchase_price = InvenTreeMoneySerializer( label=_('Purchase Price'), max_digits=19, decimal_places=4, - allow_null=True + allow_null=True, + help_text=_('Purchase price of this stock item'), ) purchase_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), default=currency_code_default, label=_('Currency'), + help_text=_('Purchase currency of this stock item'), ) purchase_price_string = serializers.SerializerMethodField() @@ -197,6 +199,7 @@ class StockItemSerializer(InvenTreeModelSerializer): 'belongs_to', 'build', 'customer', + 'delete_on_deplete', 'expired', 'expiry_date', 'in_stock', diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index b7019ff887..97b94bdd70 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -150,6 +150,7 @@
  • {% trans "Duplicate stock item" %}
  • {% endif %}
  • {% trans "Edit stock item" %}
  • +
  • {% trans "Edit stock item" %}
  • {% if user.is_staff or roles.stock.delete %} {% if item.can_delete %}
  • {% trans "Delete stock item" %}
  • @@ -520,6 +521,10 @@ $("#stock-edit").click(function () { ); }); +$('#stock-edit-2').click(function() { + editStockItem({{ item.pk }}); +}); + $('#stock-edit-status').click(function () { constructForm('{% url "api-stock-detail" item.pk %}', { diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 1bfe196286..07e38565cd 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -179,6 +179,7 @@ function constructChangeForm(fields, options) { // Request existing data from the API endpoint $.ajax({ url: options.url, + data: options.params || {}, type: 'GET', contentType: 'application/json', dataType: 'json', @@ -194,6 +195,17 @@ function constructChangeForm(fields, options) { fields[field].value = data[field]; } } + + // An optional function can be provided to process the returned results, + // before they are rendered to the form + if (options.processResults) { + var processed = options.processResults(data, fields, options); + + // If the processResults function returns data, it will be stored + if (processed) { + data = processed; + } + } // Store the entire data object options.instance = data; diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 67c50bffef..2f885477a5 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -68,6 +68,95 @@ function locationFields() { } +function stockItemFields(options={}) { + var fields = { + part: {}, + supplier_part: { + filters: { + part_detail: true, + supplier_detail: true, + }, + adjustFilters: function(query, opts) { + var part = getFormFieldValue('part', {}, opts); + + if (part) { + query.part = part; + } + + return query; + } + }, + serial: {}, + status: {}, + expiry_date: {}, + batch: {}, + purchase_price: {}, + purchase_price_currency: {}, + packaging: {}, + link: {}, + delete_on_deplete: {}, + // owner: {}, + }; + + // Remove stock expiry fields if feature is not enabled + if (!global_settings.STOCK_ENABLE_EXPIRY) { + delete fields['expiry_date']; + } + + return fields; +} + + +function stockItemGroups(options={}) { + return { + + }; +} + + +/* + * Launch a modal form to edit a given StockItem + */ +function editStockItem(pk, options={}) { + + var url = `/api/stock/${pk}/`; + + var fields = stockItemFields(options); + + // Prevent editing of the "part" + fields.part.hidden = true; + + var groups = stockItemGroups(options); + + constructForm(url, { + fields: fields, + groups: groups, + title: '{% trans "Edit Stock Item" %}', + params: { + part_detail: true, + supplier_part_detail: true, + }, + processResults: function(data, fields, options) { + // Callback when StockItem data is received from server + + if (data.part_detail.trackable) { + delete options.fields.delete_on_deplete; + } else { + // Remove serial number field if part is not trackable + delete options.fields.serial; + } + + // Remove pricing fields if part is not purchaseable + if (!data.part_detail.purchaseable) { + delete options.fields.supplier_part; + delete options.fields.purchase_price; + delete options.fields.purchase_price_currency; + } + } + }); +} + + /* Stock API functions * Requires api.js to be loaded first */ From d3b1ecd65e2a0611de9363a1cf8e6af03c6963c2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 20 Oct 2021 23:44:01 +1100 Subject: [PATCH 002/177] Add "owner" field --- InvenTree/stock/serializers.py | 7 ++++--- InvenTree/templates/js/translated/stock.js | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index e2781af12f..0b99ed9c02 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -58,15 +58,15 @@ class StockItemSerializerBrief(InvenTreeModelSerializer): class Meta: model = StockItem fields = [ - 'pk', - 'uid', 'part', 'part_name', - 'supplier_part', + 'pk', 'location', 'location_name', 'quantity', 'serial', + 'supplier_part', + 'uid', ] @@ -208,6 +208,7 @@ class StockItemSerializer(InvenTreeModelSerializer): 'location', 'location_detail', 'notes', + 'owner', 'packaging', 'part', 'part_detail', diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 2f885477a5..2bc562cb5a 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -94,8 +94,8 @@ function stockItemFields(options={}) { purchase_price_currency: {}, packaging: {}, link: {}, + owner: {}, delete_on_deplete: {}, - // owner: {}, }; // Remove stock expiry fields if feature is not enabled @@ -103,6 +103,11 @@ function stockItemFields(options={}) { delete fields['expiry_date']; } + // Remove ownership field if feature is not enanbled + if (!global_settings.STOCK_OWNERSHIP_CONTROL) { + delete fields['owner']; + } + return fields; } From da9d2f7467fd81d9aa3c64e05e3316aa482b84c1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Oct 2021 22:49:06 +0200 Subject: [PATCH 003/177] Added missing fields Fixes #2181 --- InvenTree/part/views.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 5a4167ea05..330e7ae49c 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -245,6 +245,7 @@ class PartImport(FileManagementFormView): 'Category', 'default_location', 'default_supplier', + 'variant_of', ] OPTIONAL_HEADERS = [ @@ -256,6 +257,16 @@ class PartImport(FileManagementFormView): 'minimum_stock', 'Units', 'Notes', + 'Active', + 'base_cost', + 'Multiple', + 'assembly', + 'component', + 'is_template', + 'purchaseable', + 'salable', + 'trackable', + 'virtual', ] name = 'part' @@ -284,6 +295,17 @@ class PartImport(FileManagementFormView): 'category': 'category', 'default_location': 'default_location', 'default_supplier': 'default_supplier', + 'variant_of': 'variant_of', + 'active': 'active', + 'base_cost': 'base_cost', + 'multiple': 'multiple', + 'assembly': 'assembly', + 'component': 'component', + 'is_template': 'is_template', + 'purchaseable': 'purchaseable', + 'salable': 'salable', + 'trackable': 'trackable', + 'virtual': 'virtual', } file_manager_class = PartFileManager @@ -299,6 +321,8 @@ class PartImport(FileManagementFormView): self.matches['default_location'] = ['name__contains'] self.allowed_items['default_supplier'] = SupplierPart.objects.all() self.matches['default_supplier'] = ['SKU__contains'] + self.allowed_items['variant_of'] = Part.objects.all() + self.matches['variant_of'] = ['name__contains'] # setup self.file_manager.setup() From 71cc155dc99474f7e0c17066ee1eed46f5531436 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Oct 2021 22:50:01 +0200 Subject: [PATCH 004/177] Capitalize name --- InvenTree/part/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 330e7ae49c..66a101ba5a 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -260,13 +260,13 @@ class PartImport(FileManagementFormView): 'Active', 'base_cost', 'Multiple', - 'assembly', - 'component', + 'Assembly', + 'Component', 'is_template', - 'purchaseable', - 'salable', - 'trackable', - 'virtual', + 'Purchaseable', + 'Salable', + 'Trackable', + 'Virtual', ] name = 'part' From 15566632540cf0f1e521ddabd1f202af6aa30d07 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Oct 2021 23:40:29 +0200 Subject: [PATCH 005/177] added fields to save step --- InvenTree/part/views.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 66a101ba5a..55147c933b 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -388,6 +388,17 @@ class PartImport(FileManagementFormView): category=optional_matches['Category'], default_location=optional_matches['default_location'], default_supplier=optional_matches['default_supplier'], + variant_of=optional_matches['variant_of'], + active=optional_matches['active'], + base_cost=optional_matches['base_cost'], + multiple=optional_matches['multiple'], + assembly=optional_matches['assembly'], + component=optional_matches['component'], + is_template=optional_matches['is_template'], + purchaseable=optional_matches['purchaseable'], + salable=optional_matches['salable'], + trackable=optional_matches['trackable'], + virtual=optional_matches['virtual'], ) try: new_part.save() From 8e6aaa89f91f94ada7bce7f5660dbe783d63cae0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Oct 2021 23:40:57 +0200 Subject: [PATCH 006/177] calculate true / false for fields --- InvenTree/part/views.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 55147c933b..f9bd57e767 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -389,16 +389,16 @@ class PartImport(FileManagementFormView): default_location=optional_matches['default_location'], default_supplier=optional_matches['default_supplier'], variant_of=optional_matches['variant_of'], - active=optional_matches['active'], - base_cost=optional_matches['base_cost'], - multiple=optional_matches['multiple'], - assembly=optional_matches['assembly'], - component=optional_matches['component'], - is_template=optional_matches['is_template'], - purchaseable=optional_matches['purchaseable'], - salable=optional_matches['salable'], - trackable=optional_matches['trackable'], - virtual=optional_matches['virtual'], + active=str2bool(part_data.get('active', None)), + base_cost=part_data.get('base_cost', None), + multiple=part_data.get('multiple', None), + assembly=str2bool(part_data.get('assembly', None)), + component=str2bool(part_data.get('component', None)), + is_template=str2bool(part_data.get('is_template', None)), + purchaseable=str2bool(part_data.get('purchaseable', None)), + salable=str2bool(part_data.get('salable', None)), + trackable=str2bool(part_data.get('trackable', None)), + virtual=str2bool(part_data.get('virtual', None)), ) try: new_part.save() From 612832c3e771b3847abf948cd8cc1459962f10c2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Oct 2021 23:48:42 +0200 Subject: [PATCH 007/177] respect defaults --- InvenTree/part/views.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index f9bd57e767..8deec0750f 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -47,6 +47,7 @@ from stock.models import StockLocation import common.settings as inventree_settings from . import forms as part_forms +from . import settings as part_settings from .bom import MakeBomTemplate, ExportBom, IsValidBOMFormat from order.models import PurchaseOrderLineItem @@ -389,16 +390,16 @@ class PartImport(FileManagementFormView): default_location=optional_matches['default_location'], default_supplier=optional_matches['default_supplier'], variant_of=optional_matches['variant_of'], - active=str2bool(part_data.get('active', None)), + active=str2bool(part_data.get('active', True)), base_cost=part_data.get('base_cost', None), multiple=part_data.get('multiple', None), - assembly=str2bool(part_data.get('assembly', None)), - component=str2bool(part_data.get('component', None)), - is_template=str2bool(part_data.get('is_template', None)), - purchaseable=str2bool(part_data.get('purchaseable', None)), - salable=str2bool(part_data.get('salable', None)), - trackable=str2bool(part_data.get('trackable', None)), - virtual=str2bool(part_data.get('virtual', None)), + assembly=str2bool(part_data.get('assembly', part_settings.part_assembly_default())), + component=str2bool(part_data.get('component', part_settings.part_component_default())), + is_template=str2bool(part_data.get('is_template', part_settings.part_template_default())), + purchaseable=str2bool(part_data.get('purchaseable', part_settings.part_purchaseable_default())), + salable=str2bool(part_data.get('salable', part_settings.part_salable_default())), + trackable=str2bool(part_data.get('trackable', part_settings.part_trackable_default())), + virtual=str2bool(part_data.get('virtual', part_settings.part_virtual_default())), ) try: new_part.save() From bec845003d76bb93c50181c1f096cfc1fc939fac Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Oct 2021 23:57:10 +0200 Subject: [PATCH 008/177] fix defaults --- InvenTree/part/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 8deec0750f..918c015f94 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -391,8 +391,8 @@ class PartImport(FileManagementFormView): default_supplier=optional_matches['default_supplier'], variant_of=optional_matches['variant_of'], active=str2bool(part_data.get('active', True)), - base_cost=part_data.get('base_cost', None), - multiple=part_data.get('multiple', None), + base_cost=part_data.get('base_cost', 0), + multiple=part_data.get('multiple', 1), assembly=str2bool(part_data.get('assembly', part_settings.part_assembly_default())), component=str2bool(part_data.get('component', part_settings.part_component_default())), is_template=str2bool(part_data.get('is_template', part_settings.part_template_default())), From d97e3cd4e5d85e0ebf71ad0f653e43358f872d21 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Oct 2021 00:19:17 +0200 Subject: [PATCH 009/177] create stock on import --- InvenTree/part/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 918c015f94..5456f4e049 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -42,7 +42,7 @@ from common.files import FileManager from common.views import FileManagementFormView, FileManagementAjaxView from common.forms import UploadFileForm, MatchFieldForm -from stock.models import StockLocation +from stock.models import StockItem, StockLocation import common.settings as inventree_settings @@ -268,6 +268,7 @@ class PartImport(FileManagementFormView): 'Salable', 'Trackable', 'Virtual', + 'Stock', ] name = 'part' @@ -307,6 +308,7 @@ class PartImport(FileManagementFormView): 'salable': 'salable', 'trackable': 'trackable', 'virtual': 'virtual', + 'stock': 'stock', } file_manager_class = PartFileManager @@ -403,6 +405,15 @@ class PartImport(FileManagementFormView): ) try: new_part.save() + + # add stock item if set + if part_data.get('stock', None): + stock = StockItem( + part=new_part, + location=new_part.default_location, + quantity=int(part_data.get('stock', 1)), + ) + stock.save() import_done += 1 except ValidationError as _e: import_error.append(', '.join(set(_e.messages))) From d8796f95356730784b31ddd078c618780e3c3918 Mon Sep 17 00:00:00 2001 From: rocheparadox Date: Fri, 29 Oct 2021 16:03:41 +0530 Subject: [PATCH 010/177] Notify users who have starred a part when that part's stock quantity falls below the minimum quanitity/threshold through email. --- InvenTree/InvenTree/tasks.py | 36 +++++++++++++++++-- InvenTree/stock/models.py | 14 +++++++- .../stock/low_stock_notification.html | 27 ++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 InvenTree/stock/templates/stock/low_stock_notification.html diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index aa17ef8603..9987a2593d 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -11,6 +11,7 @@ from django.utils import timezone from django.core.exceptions import AppRegistryNotReady from django.db.utils import OperationalError, ProgrammingError +from django.template.loader import render_to_string logger = logging.getLogger("inventree") @@ -52,7 +53,7 @@ def schedule_task(taskname, **kwargs): pass -def offload_task(taskname, force_sync=False, *args, **kwargs): +def offload_task(taskname, *args, force_sync=False, **kwargs): """ Create an AsyncTask if workers are running. This is different to a 'scheduled' task, @@ -290,7 +291,7 @@ def update_exchange_rates(): Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete() -def send_email(subject, body, recipients, from_email=None): +def send_email(subject, body, recipients, from_email=None, html_message=None): """ Send an email with the specified subject and body, to the specified recipients list. @@ -306,4 +307,35 @@ def send_email(subject, body, recipients, from_email=None): from_email, recipients, fail_silently=False, + html_message=html_message ) + + +def notify_low_stock(stock_item): + """ + Notify users who have starred a part when its stock quantity falls below the minimum threshold + """ + + from allauth.account.models import EmailAddress + starred_users = EmailAddress.objects.filter(user__starred_parts__part=stock_item.part) + + if len(starred_users) > 0: + logger.info(f"Notify users regarding low stock of {stock_item.part.name}") + body = f'Hi, {stock_item.part.name} is low on stock. Kindly do the needful.' + context = { + 'part_name': stock_item.part.name, + # Part url can be used to open the page of part in application from the email. + # It can be facilitated when the application base url is accessible programmatically. + # 'part_url': f'{application_base_url}/part/{stock_item.part.id}', + + 'message': body, + + # quantity is in decimal field datatype. Since the same datatype is used in models, + # it is not converted to number/integer, + 'part_quantity': stock_item.quantity, + 'minimum_quantity': stock_item.part.minimum_stock + } + subject = f'Attention! {stock_item.part.name} is low on stock' + html_message = render_to_string('stock/low_stock_notification.html', context) + recipients = starred_users.values_list('email', flat=True) + send_email(subject, body, recipients, html_message=html_message) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 1372e63406..ff8b91b105 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -17,7 +17,7 @@ from django.db.models import Sum, Q from django.db.models.functions import Coalesce from django.core.validators import MinValueValidator from django.contrib.auth.models import User -from django.db.models.signals import pre_delete +from django.db.models.signals import pre_delete, post_save from django.dispatch import receiver from markdownx.models import MarkdownxField @@ -36,6 +36,7 @@ import label.models from InvenTree.status_codes import StockStatus, StockHistoryCode from InvenTree.models import InvenTreeTree, InvenTreeAttachment from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField +from InvenTree import tasks as inventree_tasks from users.models import Owner @@ -1651,6 +1652,17 @@ def before_delete_stock_item(sender, instance, using, **kwargs): child.save() +@receiver(post_save, sender=StockItem) +def after_save_stock_item(sender, instance: StockItem, **kwargs): + """ + Check if the stock quantity has fallen below the minimum threshold of part. If yes, notify the users who have + starred the part + """ + + if instance.quantity <= instance.part.minimum_stock: + inventree_tasks.notify_low_stock(instance) + + class StockItemAttachment(InvenTreeAttachment): """ Model for storing file attachments against a StockItem object. diff --git a/InvenTree/stock/templates/stock/low_stock_notification.html b/InvenTree/stock/templates/stock/low_stock_notification.html new file mode 100644 index 0000000000..fa3799f6dd --- /dev/null +++ b/InvenTree/stock/templates/stock/low_stock_notification.html @@ -0,0 +1,27 @@ +

    {{ message }}

    + + + + + + + + + + + + + + + + + + + + + + + +
    Part low on stock
    Part NameAvailable QuantityMinimum Quantity
    {{ part_name }}{{ part_quantity }}{{ minimum_quantity }}
    You are receiving this mail because you have starred the part {{ part_name }} in + Inventree application
    + From 83309fd054f0aa9cfab16e57a23a7eeecb4468e8 Mon Sep 17 00:00:00 2001 From: rocheparadox Date: Sat, 30 Oct 2021 08:16:42 +0530 Subject: [PATCH 011/177] Fixed the order of fixtures installation for testing --- InvenTree/InvenTree/test_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index dfe94c034e..6ace21b576 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -102,9 +102,9 @@ class APITests(InvenTreeAPITestCase): fixtures = [ 'location', - 'stock', - 'part', 'category', + 'part', + 'stock' ] token = None From e0cd02ee60a5ea42cd2735d4700a8ab34d9af950 Mon Sep 17 00:00:00 2001 From: rocheparadox Date: Sat, 30 Oct 2021 08:30:39 +0530 Subject: [PATCH 012/177] added dispatch_uid to post_save signal of StockItem --- InvenTree/stock/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index ff8b91b105..b4746e0879 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1652,7 +1652,7 @@ def before_delete_stock_item(sender, instance, using, **kwargs): child.save() -@receiver(post_save, sender=StockItem) +@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log') def after_save_stock_item(sender, instance: StockItem, **kwargs): """ Check if the stock quantity has fallen below the minimum threshold of part. If yes, notify the users who have From ceb457a8373d1983e3631500e5cf46c59e8d614f Mon Sep 17 00:00:00 2001 From: Matthias Mair <66015116+matmair@users.noreply.github.com> Date: Sat, 30 Oct 2021 14:16:11 +0200 Subject: [PATCH 013/177] Update README.md added a button in REadme to open up a env in gitpod directly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dccc03183..f0ccdb88b9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ![SQLite](https://github.com/inventree/inventree/actions/workflows/coverage.yaml/badge.svg) ![MySQL](https://github.com/inventree/inventree/actions/workflows/mysql.yaml/badge.svg) ![PostgreSQL](https://github.com/inventree/inventree/actions/workflows/postgresql.yaml/badge.svg) - +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/InvenTree/InvenTree) InvenTree is an open-source Inventory Management System which provides powerful low-level stock control and part tracking. The core of the InvenTree system is a Python/Django database backend which provides an admin interface (web-based) and a JSON API for interaction with external interfaces and applications. From 6af866557d052d7048ab2b0acb7406df3b0f7cf1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sat, 30 Oct 2021 12:34:31 +0000 Subject: [PATCH 014/177] fixes missing wraping on iPdas and iPhones Fixes #2210 --- InvenTree/templates/page_base.html | 2 +- inventree-data | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 inventree-data diff --git a/InvenTree/templates/page_base.html b/InvenTree/templates/page_base.html index fc00fb1f7f..2a9704a728 100644 --- a/InvenTree/templates/page_base.html +++ b/InvenTree/templates/page_base.html @@ -7,7 +7,7 @@
    -
    +

    {% block heading %} -- page header goes here -- diff --git a/inventree-data b/inventree-data new file mode 160000 index 0000000000..5a5efd9dac --- /dev/null +++ b/inventree-data @@ -0,0 +1 @@ +Subproject commit 5a5efd9dac4983a56b3141abdcc9e115466da253 From a151a68e589e8cb6adcc5b84819106c60c0188ad Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 31 Oct 2021 00:05:20 +1100 Subject: [PATCH 015/177] convert sale_price to string before exporting --- InvenTree/order/admin.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/InvenTree/order/admin.py b/InvenTree/order/admin.py index 25b0922291..502c63c084 100644 --- a/InvenTree/order/admin.py +++ b/InvenTree/order/admin.py @@ -83,7 +83,9 @@ class POLineItemResource(ModelResource): class SOLineItemResource(ModelResource): - """ Class for managing import / export of SOLineItem data """ + """ + Class for managing import / export of SOLineItem data + """ part_name = Field(attribute='part__name', readonly=True) @@ -93,6 +95,17 @@ class SOLineItemResource(ModelResource): fulfilled = Field(attribute='fulfilled_quantity', readonly=True) + def dehydrate_sale_price(self, item): + """ + Return a string value of the 'sale_price' field, rather than the 'Money' object. + Ref: https://github.com/inventree/InvenTree/issues/2207 + """ + + if item.sale_price: + return str(item.sale_price) + else: + return '' + class Meta: model = SalesOrderLineItem skip_unchanged = True From 14680531f0cb019662bbec12a73715f35ad7cbd8 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sat, 30 Oct 2021 14:05:40 +0000 Subject: [PATCH 016/177] [BUG] Auth screens broken Fixes #2213 --- InvenTree/InvenTree/static/css/inventree.css | 16 +++++++--------- InvenTree/templates/account/base.html | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 34d3685a7e..22513b2d85 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -15,25 +15,23 @@ } .login-screen { - background-image: url("/static/img/paper_splash.jpg"); + background: url(/static/img/paper_splash.jpg) no-repeat center fixed; background-size: cover; - background-repeat: no-repeat; - height: 100%; + height: 100vh; font-family: 'Numans', sans-serif; color: #eee; } .login-container { - left: 50%; - position: fixed; - top: 50%; - transform: translate(-50%, -50%); - width: 30%; - align-content: center; + align-self: center; border-radius: 15px; padding: 20px; padding-bottom: 35px; background-color: rgba(50, 50, 50, 0.75); + + width: 100%; + max-width: 330px; + margin: auto; } .login-header { diff --git a/InvenTree/templates/account/base.html b/InvenTree/templates/account/base.html index 7ad0447cd5..048496c4a5 100644 --- a/InvenTree/templates/account/base.html +++ b/InvenTree/templates/account/base.html @@ -13,7 +13,7 @@ - + @@ -34,7 +34,7 @@ Background Image Attribution: https://unsplash.com/photos/Ixvv3YZkd7w --> -