From e1fb7e5d98a59ca179d88cc2ab9acf5f83550955 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 1 Dec 2020 13:45:01 -0500 Subject: [PATCH 01/44] Added owner field to both stock item and location tables and forms --- InvenTree/stock/forms.py | 5 +++- .../migrations/0056_auto_20201201_1844.py | 27 +++++++++++++++++++ InvenTree/stock/models.py | 8 +++++- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 InvenTree/stock/migrations/0056_auto_20201201_1844.py diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 8ab88155e2..9cd5435423 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -89,7 +89,8 @@ class EditStockLocationForm(HelperForm): fields = [ 'name', 'parent', - 'description' + 'description', + 'owner', ] @@ -132,6 +133,7 @@ class CreateStockItemForm(HelperForm): 'link', 'delete_on_deplete', 'status', + 'owner', ] # Custom clean to prevent complex StockItem.clean() logic from running (yet) @@ -403,6 +405,7 @@ class EditStockItemForm(HelperForm): 'purchase_price', 'link', 'delete_on_deplete', + 'owner', ] diff --git a/InvenTree/stock/migrations/0056_auto_20201201_1844.py b/InvenTree/stock/migrations/0056_auto_20201201_1844.py new file mode 100644 index 0000000000..bc5f8bc4db --- /dev/null +++ b/InvenTree/stock/migrations/0056_auto_20201201_1844.py @@ -0,0 +1,27 @@ +# Generated by Django 3.0.7 on 2020-12-01 18:44 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0011_update_proxy_permissions'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('stock', '0055_auto_20201117_1453'), + ] + + operations = [ + migrations.AddField( + model_name='stockitem', + name='owner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner_stockitems', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='stocklocation', + name='owner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner_stocklocations', to='auth.Group'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index d1e46c53a7..d42ba88e28 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -16,7 +16,7 @@ from django.db import models, transaction 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.contrib.auth.models import User, Group from django.db.models.signals import pre_delete from django.dispatch import receiver @@ -44,6 +44,9 @@ class StockLocation(InvenTreeTree): Stock locations can be heirarchical as required """ + owner = models.ForeignKey(Group, on_delete=models.SET_NULL, blank=True, null=True, + related_name='owner_stocklocations') + def get_absolute_url(self): return reverse('stock-location-detail', kwargs={'pk': self.id}) @@ -459,6 +462,9 @@ class StockItem(MPTTModel): help_text=_('Single unit purchase price at time of purchase'), ) + owner = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, + related_name='owner_stockitems') + def clearAllocations(self): """ Clear all order allocations for this StockItem: From 2c38be2d135212d133c7d76979793aac7f476a08 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 1 Dec 2020 15:54:05 -0500 Subject: [PATCH 02/44] Added global setting and updated stock item templates --- InvenTree/common/models.py | 7 +++++++ InvenTree/stock/templates/stock/item.html | 4 +++- InvenTree/stock/templates/stock/item_base.html | 7 ++++++- InvenTree/templates/InvenTree/settings/stock.html | 10 ++++++++++ InvenTree/templates/attachment_table.html | 2 -- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 2f27dd602d..7441614ec5 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -139,6 +139,13 @@ class InvenTreeSetting(models.Model): 'validator': bool, }, + 'STOCK_OWNER': { + 'name': _('Stock Owner Changes'), + 'description': _('Allow only owner of stock location and item to make changes'), + 'default': False, + 'validator': bool, + }, + 'BUILDORDER_REFERENCE_PREFIX': { 'name': _('Build Order Reference Prefix'), 'description': _('Prefix value for build order reference'), diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 8f8502af2a..f9cbf472bc 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -11,7 +11,9 @@

{% trans "Stock Tracking Information" %}


-{% if roles.stock.change %} + +{% setting_object 'STOCK_OWNER' as owner_enable %} +{% if user.is_superuser or roles.stock.change and item.owner == user and owner_enable.value == "True" %}
- {% if roles.stock.change and not item.is_building %} + + {% setting_object 'STOCK_OWNER' as owner_enable %} + {% if user.is_superuser or roles.stock.change and item.owner == user and owner_enable.value == "True" %} + {% if not item.is_building %}
{% endif %} + + {% endif %} diff --git a/InvenTree/templates/InvenTree/settings/stock.html b/InvenTree/templates/InvenTree/settings/stock.html index c3c40087ff..f80a1c0926 100644 --- a/InvenTree/templates/InvenTree/settings/stock.html +++ b/InvenTree/templates/InvenTree/settings/stock.html @@ -10,4 +10,14 @@ {% endblock %} {% block settings %} + +

{% trans "Stock Options" %}

+ + + + + {% include "InvenTree/settings/setting.html" with key="STOCK_OWNER" %} + +
+ {% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/attachment_table.html b/InvenTree/templates/attachment_table.html index d13b7b33b1..35b114cc05 100644 --- a/InvenTree/templates/attachment_table.html +++ b/InvenTree/templates/attachment_table.html @@ -1,6 +1,5 @@ {% load i18n %} -{% if roles.stock.change %}
-{% endif %}
From 4104e7df8e8c17ac813e94aca29fa26745cdecab Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 1 Dec 2020 16:08:27 -0500 Subject: [PATCH 03/44] Fixed template logic --- InvenTree/stock/templates/stock/item.html | 4 +- .../stock/templates/stock/item_base.html | 99 +++++++++---------- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index f9cbf472bc..51eae668f4 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -13,7 +13,8 @@ {% setting_object 'STOCK_OWNER' as owner_enable %} -{% if user.is_superuser or roles.stock.change and item.owner == user and owner_enable.value == "True" %} +{% if owner_enable.value == "False" or owner_enable.value == "True" and item.owner == user %} +{% if roles.stock.change and not item.is_building %}
{% endif %} +{% endif %}
diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 876f8605d9..c05a1c30df 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -114,57 +114,56 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% setting_object 'STOCK_OWNER' as owner_enable %} - {% if user.is_superuser or roles.stock.change and item.owner == user and owner_enable.value == "True" %} - {% if not item.is_building %} -
- - -
- {% endif %} - - {% if roles.stock.change and not item.is_building %} -
- - +
+ {% endif %} + + {% if roles.stock.change and not item.is_building %} +
+ + +
+ {% endif %} {% endif %} + {% if roles.stock.change and not item.is_building %} +
+
+ +
-
-{% endif %} + {% endif %} {% endif %}
diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index c05a1c30df..a8d775ef1a 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -15,6 +15,8 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% block pre_content %} {% include 'stock/loc_link.html' with location=item.location %} +{% setting_object 'STOCK_OWNER' as owner_enable %} + {% if item.is_building %}
{% trans "This stock item is in production and cannot be edited." %}
@@ -29,6 +31,12 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
{% endif %} +{% if owner_enable.value == "True" and not item.owner == user and not user.is_superuser %} +
+ {% trans "You are not the owner of this item. This stock item cannot be edited." %}
+
+{% endif %} + {% if item.hasRequiredTests and not item.passedAllRequiredTests %}
{% trans "This stock item has not passed all required tests" %} @@ -68,6 +76,9 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% endblock %} {% block page_data %} + +{% setting_object 'STOCK_OWNER' as owner_enable %} +

{% trans "Stock Item" %} {% stock_status_label item.status large=True %} @@ -113,8 +124,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}

- {% setting_object 'STOCK_OWNER' as owner_enable %} - {% if owner_enable.value == "False" or owner_enable.value == "True" and item.owner == user %} + {% if owner_enable.value == "False" or owner_enable.value == "True" and item.owner == user or user.is_superuser %} {% if roles.stock.change and not item.is_building %}
diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index fef3428373..d1fead76c3 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -1,8 +1,17 @@ {% extends "stock/stock_app_base.html" %} {% load static %} +{% load inventree_extras %} {% load i18n %} {% block content %} +{% setting_object 'STOCK_OWNER' as owner_enable %} + +{% if location and owner_enable.value == "True" and not location.owner in user.groups.all and not user.is_superuser %} +
+ {% trans "You are not in the list of owners of this location. This stock location cannot be edited." %}
+
+{% endif %} +
{% if location %} @@ -18,11 +27,13 @@

{% trans "All stock items" %}

{% endif %}
- {% if roles.stock.add %} - - {% endif %} + {% if owner_enable.value == "False" or owner_enable.value == "True" and location.owner in user.groups.all or user.is_superuser or not location %} + {% if roles.stock.add %} + + {% endif %} + {% endif %} {% if location %}
@@ -33,23 +44,26 @@
  • {% trans "Check-in Items" %}
  • - {% if roles.stock.change %} - -
    - - -
    + + {% if owner_enable.value == "False" or owner_enable.value == "True" and location.owner in user.groups.all or user.is_superuser %} + {% if roles.stock.change %} + +
    + + +
    + {% endif %} {% endif %} {% endif %}
    diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index 51f7c277db..fc5e7f506d 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -1,4 +1,7 @@ {% load i18n %} +{% load inventree_extras %} + +{% setting_object 'STOCK_OWNER' as owner_enable %}
    @@ -8,28 +11,31 @@ {% if read_only %} {% else %} - {% if roles.stock.add %} - - {% endif %} - {% if roles.stock.change or roles.stock.delete %} -
    - - -
    - {% endif %} + {% endif %} {% endif %}
    From c9b3c16c6f127287a733d2fc924e95c314dbb252 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 1 Dec 2020 17:46:11 -0500 Subject: [PATCH 05/44] Added help text on owner fields --- InvenTree/stock/migrations/0056_auto_20201201_1844.py | 4 ++-- InvenTree/stock/models.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/InvenTree/stock/migrations/0056_auto_20201201_1844.py b/InvenTree/stock/migrations/0056_auto_20201201_1844.py index bc5f8bc4db..cc6d94848a 100644 --- a/InvenTree/stock/migrations/0056_auto_20201201_1844.py +++ b/InvenTree/stock/migrations/0056_auto_20201201_1844.py @@ -17,11 +17,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='stockitem', name='owner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner_stockitems', to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(blank=True, help_text='Owner (User)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner_stockitems', to=settings.AUTH_USER_MODEL), ), migrations.AddField( model_name='stocklocation', name='owner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner_stocklocations', to='auth.Group'), + field=models.ForeignKey(blank=True, help_text='Owner (Group)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner_stocklocations', to='auth.Group'), ), ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index d42ba88e28..04db5a81de 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -45,6 +45,7 @@ class StockLocation(InvenTreeTree): """ owner = models.ForeignKey(Group, on_delete=models.SET_NULL, blank=True, null=True, + help_text='Owner (Group)', related_name='owner_stocklocations') def get_absolute_url(self): @@ -463,6 +464,7 @@ class StockItem(MPTTModel): ) owner = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, + help_text='Owner (User)', related_name='owner_stockitems') def clearAllocations(self): From 2d7461f6097f310815795a5b105e9b8c4ec7ec49 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 2 Dec 2020 12:05:00 -0500 Subject: [PATCH 06/44] Updated StockLocation create/edit view with ownership control --- InvenTree/stock/views.py | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 175a06ae80..8e6d747e96 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -35,6 +35,7 @@ from label.models import StockItemLabel from .models import StockItem, StockLocation, StockItemTracking, StockItemAttachment, StockItemTestResult import common.settings +from common.models import InvenTreeSetting from .admin import StockItemResource @@ -127,6 +128,7 @@ class StockLocationEdit(AjaxUpdateView): """ Customize form data for StockLocation editing. Limit the choices for 'parent' field to those which make sense. + If ownership control is enabled and location has parent, disable owner field. """ form = super(AjaxUpdateView, self).get_form() @@ -139,8 +141,31 @@ class StockLocationEdit(AjaxUpdateView): form.fields['parent'].queryset = parent_choices + if location.parent: + form.fields['owner'].initial = location.parent.owner + + # Disable selection if stock ownership control is enabled + stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + if stock_owner_setting_enable: + form.fields['owner'].disabled = True + return form + def save(self, object, form, **kwargs): + """ If location has children and ownership control is enabled: + - update all children's owners with location's owner + """ + + self.object = form.save() + + stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + if self.object.get_children() and stock_owner_setting_enable: + for child in self.object.get_children(): + child.owner = self.object.owner + child.save() + + return self.object + class StockLocationQRCode(QRCodeView): """ View for displaying a QR code for a StockLocation object """ @@ -1355,6 +1380,41 @@ class StockLocationCreate(AjaxCreateView): return initials + def get_form(self): + """ Disable owner field when: + - creating child location + - and stock ownership control is enable + """ + + form = super().get_form() + + try: + parent = self.get_initial()['parent'] + if parent: + form.fields['owner'].initial = parent.owner + + # Disable selection if stock ownership control is enabled + stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + if stock_owner_setting_enable: + form.fields['owner'].disabled = True + except KeyError: + pass + + return form + + def save(self, form): + """ If parent location exists then use it to set the owner """ + + self.object = form.save(commit=False) + + parent = form.cleaned_data.get('parent', None) + if parent: + self.object.owner = parent.owner + + self.object.save() + + return self.object + class StockItemSerialize(AjaxUpdateView): """ View for manually serializing a StockItem """ From c66ac2579e47dcad4c377e7cdf65a3e1e0941835 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 2 Dec 2020 13:25:33 -0500 Subject: [PATCH 07/44] Updated StockItem create/edit view with ownership control --- InvenTree/stock/views.py | 56 +++++++++++++++++++++++++++- InvenTree/templates/stock_table.html | 2 +- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 8e6d747e96..1749fa5064 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -11,6 +11,7 @@ from django.views.generic import DetailView, ListView, UpdateView from django.forms.models import model_to_dict from django.forms import HiddenInput from django.urls import reverse +from django.contrib.auth.models import User from django.utils.translation import ugettext as _ @@ -1326,8 +1327,28 @@ class StockItemEdit(AjaxUpdateView): if not item.part.trackable and not item.serialized: form.fields['serial'].widget = HiddenInput() + location = item.location + + # Is ownership control enabled? + stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + if stock_owner_setting_enable and location: + # Check if location has owner + if location.owner: + form.fields['owner'].queryset = User.objects.filter(groups=location.owner) + return form + def validate(self, item, form): + """ Check that owner is set if stock ownership control is enabled """ + + owner = form.cleaned_data.get('owner', None) + + # Is ownership control enabled? + stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + + if not owner and stock_owner_setting_enable: + form.add_error('owner', _('Owner is required (ownership control is enabled)')) + class StockItemConvert(AjaxUpdateView): """ @@ -1602,6 +1623,23 @@ class StockItemCreate(AjaxCreateView): # Otherwise if the user has selected a SupplierPart, we know what Part they meant! if form['supplier_part'].value() is not None: pass + + location = None + try: + loc_id = form['location'].value() + location = StockLocation.objects.get(pk=loc_id) + except ValueError: + pass + + # Is ownership control enabled? + stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + if stock_owner_setting_enable and location: + # Check if location has owner + if location.owner: + queryset = User.objects.filter(groups=location.owner) + if self.request.user in queryset: + form.fields['owner'].initial = self.request.user + form.fields['owner'].queryset = queryset return form @@ -1674,10 +1712,17 @@ class StockItemCreate(AjaxCreateView): data = form.cleaned_data - part = data['part'] + part = data.get('part', None) quantity = data.get('quantity', None) + location = data.get('location', None) + + owner = data.get('owner', None) + + if not part: + return + if not quantity: return @@ -1708,6 +1753,15 @@ class StockItemCreate(AjaxCreateView): _('Serial numbers already exist') + ': ' + exists ) + # Is ownership control enabled? + stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + + if stock_owner_setting_enable: + # Check if owner is set + if not owner: + form.add_error('owner', _('Owner is required (ownership control is enabled)')) + return + def save(self, form, **kwargs): """ Create a new StockItem based on the provided form data. diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index fc5e7f506d..f954b1c97c 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -12,7 +12,7 @@ {% if read_only %} {% else %} - {% if owner_enable.value == "False" or owner_enable.value == "True" and location.owner in user.groups.all %} + {% if owner_enable.value == "False" or owner_enable.value == "True" and location.owner in user.groups.all or user.is_superuser %} {% if roles.stock.add %} diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index d1fead76c3..6c47a697ca 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -4,9 +4,9 @@ {% load i18n %} {% block content %} -{% setting_object 'STOCK_OWNER' as owner_enable %} +{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %} -{% if location and owner_enable.value == "True" and not location.owner in user.groups.all and not user.is_superuser %} +{% if location and owner_control.value == "True" and not location.owner in user.groups.all and not user.is_superuser %}
    {% trans "You are not in the list of owners of this location. This stock location cannot be edited." %}
    @@ -27,7 +27,7 @@

    {% trans "All stock items" %}

    {% endif %}
    - {% if owner_enable.value == "False" or owner_enable.value == "True" and location.owner in user.groups.all or user.is_superuser or not location %} + {% if owner_control.value == "False" or owner_control.value == "True" and location.owner in user.groups.all or user.is_superuser or not location %} {% if roles.stock.add %}
    - {% if owner_enable.value == "False" or owner_enable.value == "True" and location.owner in user.groups.all or user.is_superuser %} + {% if owner_control.value == "False" or owner_control.value == "True" and location.owner in user.groups.all or user.is_superuser %} {% if roles.stock.change %}
    diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index da2b5ea15b..56b37c15d9 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -146,8 +146,8 @@ class StockLocationEdit(AjaxUpdateView): form.fields['owner'].initial = location.parent.owner # Disable selection if stock ownership control is enabled - stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') - if stock_owner_setting_enable: + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') + if stock_ownership_control: form.fields['owner'].disabled = True return form @@ -159,8 +159,8 @@ class StockLocationEdit(AjaxUpdateView): self.object = form.save() - stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') - if self.object.get_children() and stock_owner_setting_enable: + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') + if self.object.get_children() and stock_ownership_control: for child in self.object.get_children(): child.owner = self.object.owner child.save() @@ -1330,8 +1330,8 @@ class StockItemEdit(AjaxUpdateView): location = item.location # Is ownership control enabled? - stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') - if stock_owner_setting_enable and location: + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') + if stock_ownership_control and location: # Check if location has owner if location.owner: form.fields['owner'].queryset = User.objects.filter(groups=location.owner) @@ -1344,9 +1344,9 @@ class StockItemEdit(AjaxUpdateView): owner = form.cleaned_data.get('owner', None) # Is ownership control enabled? - stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - if not owner and stock_owner_setting_enable: + if not owner and stock_ownership_control: form.add_error('owner', _('Owner is required (ownership control is enabled)')) @@ -1415,8 +1415,8 @@ class StockLocationCreate(AjaxCreateView): form.fields['owner'].initial = parent.owner # Disable selection if stock ownership control is enabled - stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') - if stock_owner_setting_enable: + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') + if stock_ownership_control: form.fields['owner'].disabled = True except KeyError: pass @@ -1632,8 +1632,8 @@ class StockItemCreate(AjaxCreateView): pass # Is ownership control enabled? - stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') - if stock_owner_setting_enable and location: + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') + if stock_ownership_control and location: # Check if location has owner if location.owner: queryset = User.objects.filter(groups=location.owner) @@ -1752,9 +1752,9 @@ class StockItemCreate(AjaxCreateView): ) # Is ownership control enabled? - stock_owner_setting_enable = InvenTreeSetting.get_setting('STOCK_OWNER') + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - if stock_owner_setting_enable: + if stock_ownership_control: # Check if owner is set if not owner: form.add_error('owner', _('Owner is required (ownership control is enabled)')) diff --git a/InvenTree/templates/InvenTree/settings/stock.html b/InvenTree/templates/InvenTree/settings/stock.html index f80a1c0926..ae5e17bf1a 100644 --- a/InvenTree/templates/InvenTree/settings/stock.html +++ b/InvenTree/templates/InvenTree/settings/stock.html @@ -16,7 +16,7 @@ - {% include "InvenTree/settings/setting.html" with key="STOCK_OWNER" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_OWNERSHIP_CONTROL" %}
    diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index f954b1c97c..9746ff0aaa 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -1,7 +1,7 @@ {% load i18n %} {% load inventree_extras %} -{% setting_object 'STOCK_OWNER' as owner_enable %} +{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
    @@ -12,7 +12,7 @@ {% if read_only %} {% else %} - {% if owner_enable.value == "False" or owner_enable.value == "True" and location.owner in user.groups.all or user.is_superuser %} + {% if owner_control.value == "False" or owner_control.value == "True" and location.owner in user.groups.all or user.is_superuser %} {% if roles.stock.add %} diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index 6c47a697ca..5c03b365ab 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -5,11 +5,14 @@ {% block content %} {% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %} +{% if owner_control.value == "True" %} + {% authorized_owners location.owner as owners %} -{% if location and owner_control.value == "True" and not location.owner in user.groups.all and not user.is_superuser %} -
    - {% trans "You are not in the list of owners of this location. This stock location cannot be edited." %}
    -
    + {% if location and not user in owners and not user.is_superuser %} +
    + {% trans "You are not in the list of owners of this location. This stock location cannot be edited." %}
    +
    + {% endif %} {% endif %}
    @@ -27,7 +30,7 @@

    {% trans "All stock items" %}

    {% endif %}
    - {% if owner_control.value == "False" or owner_control.value == "True" and location.owner in user.groups.all or user.is_superuser or not location %} + {% if owner_control.value == "False" or owner_control.value == "True" and user in owners or user.is_superuser or not location %} {% if roles.stock.add %}
    - {% if owner_control.value == "False" or owner_control.value == "True" and location.owner in user.groups.all or user.is_superuser %} + {% if owner_control.value == "False" or owner_control.value == "True" and user in owners or user.is_superuser %} {% if roles.stock.change %}
    diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 7b3cedb5b4..0eab69661f 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -166,13 +166,59 @@ class StockLocationEdit(AjaxUpdateView): stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') if stock_ownership_control: - if self.object.get_children(): - for child in self.object.get_children(): - child.owner = self.object.owner - child.save() + authorized_owners = self.object.owner.get_users() + print(f'{authorized_owners=}') + + # Update children locations + children_locations = self.object.get_children() + for child in children_locations: + # Check if current owner is subset of new owner + if child.owner and authorized_owners: + if child.owner in authorized_owners: + continue + + child.owner = self.object.owner + child.save() + + # Update stock items + stock_items = self.object.get_stock_items() + print(f'{stock_items=}') + for stock_item in stock_items: + # Check if current owner is subset of new owner + if stock_item.owner and authorized_owners: + if stock_item.owner in authorized_owners: + print(f'{stock_item.owner} is authorized') + continue + + print(f'Updating stock item {stock_item} owner') + stock_item.owner = self.object.owner + stock_item.save() return self.object + def validate(self, item, form): + """ Check that owner is set if stock ownership control is enabled """ + + parent = form.cleaned_data.get('parent', None) + + owner = form.cleaned_data.get('owner', None) + + # Is ownership control enabled? + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') + + if stock_ownership_control: + if not owner and not self.request.user.is_superuser: + form.add_error('owner', _('Owner is required (ownership control is enabled)')) + else: + try: + if parent.owner: + if parent.owner != owner: + error = f'Owner requires to be equivalent to parent\'s owner ({parent.owner})' + form.add_error('owner', error) + except AttributeError: + # No parent + pass + class StockLocationQRCode(QRCodeView): """ View for displaying a QR code for a StockLocation object """ @@ -1288,6 +1334,18 @@ class StockAdjust(AjaxView, FormMixin): count += 1 + # Is ownership control enabled? + stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') + + if stock_ownership_control: + # Fetch destination owner + destination_owner = destination.owner + + if destination_owner: + # Update owner + item.owner = destination_owner + item.save() + if count == 0: return _('No items were moved') @@ -1361,12 +1419,18 @@ class StockItemEdit(AjaxUpdateView): if not stock_ownership_control: form.fields['owner'].widget = HiddenInput() else: - location_owner = location.owner + try: + location_owner = location.owner + except AttributeError: + location_owner = None + # Check if location has owner if location_owner: + form.fields['owner'].initial = location_owner + # Check location owner type and filter if type(location_owner.owner) is Group: - queryset = location_owner.get_users() + queryset = location_owner.get_users(include_group=True) if self.request.user in queryset: form.fields['owner'].initial = self.request.user form.fields['owner'].queryset = queryset @@ -1374,6 +1438,25 @@ class StockItemEdit(AjaxUpdateView): form.fields['owner'].disabled = True form.fields['owner'].initial = location_owner + try: + item_owner = item.owner + except AttributeError: + item_owner = None + + # Check if item has owner + if item_owner: + form.fields['owner'].initial = item_owner + + # Check location owner type and filter + if type(item_owner.owner) is Group: + queryset = item_owner.get_users(include_group=True) + if self.request.user in queryset: + form.fields['owner'].initial = self.request.user + form.fields['owner'].queryset = queryset + elif type(item_owner.owner) is User: + form.fields['owner'].disabled = True + form.fields['owner'].initial = item_owner + return form def validate(self, item, form): @@ -1384,8 +1467,9 @@ class StockItemEdit(AjaxUpdateView): # Is ownership control enabled? stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - if not owner and stock_ownership_control: - form.add_error('owner', _('Owner is required (ownership control is enabled)')) + if stock_ownership_control: + if not owner and not self.request.user.is_superuser: + form.add_error('owner', _('Owner is required (ownership control is enabled)')) class StockItemConvert(AjaxUpdateView): @@ -1495,7 +1579,7 @@ class StockLocationCreate(AjaxCreateView): stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') if stock_ownership_control: - if not owner: + if not owner and not self.request.user.is_superuser: form.add_error('owner', _('Owner is required (ownership control is enabled)')) else: try: @@ -1716,7 +1800,11 @@ class StockItemCreate(AjaxCreateView): if not stock_ownership_control: form.fields['owner'].widget = HiddenInput() else: - location_owner = location.owner + try: + location_owner = location.owner + except AttributeError: + location_owner = None + if location_owner: # Check location owner type and filter if type(location_owner.owner) is Group: @@ -1848,7 +1936,7 @@ class StockItemCreate(AjaxCreateView): if stock_ownership_control: # Check if owner is set - if not owner: + if not owner and not self.request.user.is_superuser: form.add_error('owner', _('Owner is required (ownership control is enabled)')) return diff --git a/InvenTree/templates/stock_table.html b/InvenTree/templates/stock_table.html index 9746ff0aaa..2e13721af1 100644 --- a/InvenTree/templates/stock_table.html +++ b/InvenTree/templates/stock_table.html @@ -2,6 +2,9 @@ {% load inventree_extras %} {% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %} +{% if owner_control.value == "True" %} + {% authorized_owners location.owner as owners %} +{% endif %}
    @@ -12,7 +15,7 @@ {% if read_only %} {% else %} - {% if owner_control.value == "False" or owner_control.value == "True" and location.owner in user.groups.all or user.is_superuser %} + {% if owner_control.value == "False" or owner_control.value == "True" and user in owners or user.is_superuser %} {% if roles.stock.add %}