Order responsible requirement (#6866)

* Add BUILDORDER_REQUIRE_RESPONSIBLE setting

- If set, build orders must specify a responsible owner

* Add responsible required setting to other order models:

- PurchaseOrder
- SalesOrder
- ReturnOrder

* Add unit test

* Adjust unit tests

* Settings updates:

- Only check settings for global and user settings
- Plugin settings are not defined at run-time

* typo fix

* More spelling fixes

* Specify responsible owner pk
This commit is contained in:
Oliver 2024-03-27 15:25:56 +11:00 committed by GitHub
parent 785b3b0e68
commit cd0d35047d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 152 additions and 41 deletions

View File

@ -121,6 +121,12 @@ class Build(InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeNo
super().clean() super().clean()
if common.models.InvenTreeSetting.get_setting('BUILDORDER_REQUIRE_RESPONSIBLE'):
if not self.responsible:
raise ValidationError({
'responsible': _('Responsible user or group must be specified')
})
# Prevent changing target part after creation # Prevent changing target part after creation
if self.has_field_changed('part'): if self.has_field_changed('part'):
raise ValidationError({ raise ValidationError({

View File

@ -148,7 +148,7 @@ class CurrencyExchangeView(APIView):
response = { response = {
'base_currency': common.models.InvenTreeSetting.get_setting( 'base_currency': common.models.InvenTreeSetting.get_setting(
'INVENTREE_DEFAULT_CURRENCY', 'USD' 'INVENTREE_DEFAULT_CURRENCY', backup_value='USD'
), ),
'exchange_rates': {}, 'exchange_rates': {},
'updated': updated, 'updated': updated,

View File

@ -190,6 +190,8 @@ class BaseInvenTreeSetting(models.Model):
SETTINGS: dict[str, SettingsKeyType] = {} SETTINGS: dict[str, SettingsKeyType] = {}
CHECK_SETTING_KEY = False
extra_unique_fields: list[str] = [] extra_unique_fields: list[str] = []
class Meta: class Meta:
@ -286,16 +288,16 @@ class BaseInvenTreeSetting(models.Model):
def save_to_cache(self): def save_to_cache(self):
"""Save this setting object to cache.""" """Save this setting object to cache."""
ckey = self.cache_key key = self.cache_key
# skip saving to cache if no pk is set # skip saving to cache if no pk is set
if self.pk is None: if self.pk is None:
return return
logger.debug("Saving setting '%s' to cache", ckey) logger.debug("Saving setting '%s' to cache", key)
try: try:
cache.set(ckey, self, timeout=3600) cache.set(key, self, timeout=3600)
except Exception: except Exception:
pass pass
@ -559,8 +561,8 @@ class BaseInvenTreeSetting(models.Model):
# Unless otherwise specified, attempt to create the setting # Unless otherwise specified, attempt to create the setting
create = kwargs.pop('create', True) create = kwargs.pop('create', True)
# Perform cache lookup by default # Specify if cache lookup should be performed
do_cache = kwargs.pop('cache', True) do_cache = kwargs.pop('cache', False)
# Prevent saving to the database during data import # Prevent saving to the database during data import
if InvenTree.ready.isImportingData(): if InvenTree.ready.isImportingData():
@ -572,12 +574,12 @@ class BaseInvenTreeSetting(models.Model):
create = False create = False
do_cache = False do_cache = False
ckey = cls.create_cache_key(key, **kwargs) cache_key = cls.create_cache_key(key, **kwargs)
if do_cache: if do_cache:
try: try:
# First attempt to find the setting object in the cache # First attempt to find the setting object in the cache
cached_setting = cache.get(ckey) cached_setting = cache.get(cache_key)
if cached_setting is not None: if cached_setting is not None:
return cached_setting return cached_setting
@ -635,6 +637,17 @@ class BaseInvenTreeSetting(models.Model):
If it does not exist, return the backup value (default = None) If it does not exist, return the backup value (default = None)
""" """
if (
cls.CHECK_SETTING_KEY
and key not in cls.SETTINGS
and not key.startswith('_')
):
logger.warning(
"get_setting: Setting key '%s' is not defined for class %s",
key,
str(cls),
)
# If no backup value is specified, attempt to retrieve a "default" value # If no backup value is specified, attempt to retrieve a "default" value
if backup_value is None: if backup_value is None:
backup_value = cls.get_setting_default(key, **kwargs) backup_value = cls.get_setting_default(key, **kwargs)
@ -670,6 +683,17 @@ class BaseInvenTreeSetting(models.Model):
change_user: User object (must be staff member to update a core setting) change_user: User object (must be staff member to update a core setting)
create: If True, create a new setting if the specified key does not exist. create: If True, create a new setting if the specified key does not exist.
""" """
if (
cls.CHECK_SETTING_KEY
and key not in cls.SETTINGS
and not key.startswith('_')
):
logger.warning(
"set_setting: Setting key '%s' is not defined for class %s",
key,
str(cls),
)
if change_user is not None and not change_user.is_staff: if change_user is not None and not change_user.is_staff:
return return
@ -1199,6 +1223,8 @@ class InvenTreeSetting(BaseInvenTreeSetting):
SETTINGS: dict[str, InvenTreeSettingsKeyType] SETTINGS: dict[str, InvenTreeSettingsKeyType]
CHECK_SETTING_KEY = True
class Meta: class Meta:
"""Meta options for InvenTreeSetting.""" """Meta options for InvenTreeSetting."""
@ -1694,7 +1720,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'STOCK_DELETE_DEPLETED_DEFAULT': { 'STOCK_DELETE_DEPLETED_DEFAULT': {
'name': _('Delete Depleted Stock'), 'name': _('Delete Depleted Stock'),
'description': _( 'description': _(
'Determines default behaviour when a stock item is depleted' 'Determines default behavior when a stock item is depleted'
), ),
'default': True, 'default': True,
'validator': bool, 'validator': bool,
@ -1766,6 +1792,20 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': 'BO-{ref:04d}', 'default': 'BO-{ref:04d}',
'validator': build.validators.validate_build_order_reference_pattern, 'validator': build.validators.validate_build_order_reference_pattern,
}, },
'BUILDORDER_REQUIRE_RESPONSIBLE': {
'name': _('Require Responsible Owner'),
'description': _('A responsible owner must be assigned to each order'),
'default': False,
'validator': bool,
},
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
'name': _('Block Until Tests Pass'),
'description': _(
'Prevent build outputs from being completed until all required tests pass'
),
'default': False,
'validator': bool,
},
'RETURNORDER_ENABLED': { 'RETURNORDER_ENABLED': {
'name': _('Enable Return Orders'), 'name': _('Enable Return Orders'),
'description': _('Enable return order functionality in the user interface'), 'description': _('Enable return order functionality in the user interface'),
@ -1780,6 +1820,12 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': 'RMA-{ref:04d}', 'default': 'RMA-{ref:04d}',
'validator': order.validators.validate_return_order_reference_pattern, 'validator': order.validators.validate_return_order_reference_pattern,
}, },
'RETURNORDER_REQUIRE_RESPONSIBLE': {
'name': _('Require Responsible Owner'),
'description': _('A responsible owner must be assigned to each order'),
'default': False,
'validator': bool,
},
'RETURNORDER_EDIT_COMPLETED_ORDERS': { 'RETURNORDER_EDIT_COMPLETED_ORDERS': {
'name': _('Edit Completed Return Orders'), 'name': _('Edit Completed Return Orders'),
'description': _( 'description': _(
@ -1796,6 +1842,12 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': 'SO-{ref:04d}', 'default': 'SO-{ref:04d}',
'validator': order.validators.validate_sales_order_reference_pattern, 'validator': order.validators.validate_sales_order_reference_pattern,
}, },
'SALESORDER_REQUIRE_RESPONSIBLE': {
'name': _('Require Responsible Owner'),
'description': _('A responsible owner must be assigned to each order'),
'default': False,
'validator': bool,
},
'SALESORDER_DEFAULT_SHIPMENT': { 'SALESORDER_DEFAULT_SHIPMENT': {
'name': _('Sales Order Default Shipment'), 'name': _('Sales Order Default Shipment'),
'description': _('Enable creation of default shipment with sales orders'), 'description': _('Enable creation of default shipment with sales orders'),
@ -1818,6 +1870,12 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': 'PO-{ref:04d}', 'default': 'PO-{ref:04d}',
'validator': order.validators.validate_purchase_order_reference_pattern, 'validator': order.validators.validate_purchase_order_reference_pattern,
}, },
'PURCHASEORDER_REQUIRE_RESPONSIBLE': {
'name': _('Require Responsible Owner'),
'description': _('A responsible owner must be assigned to each order'),
'default': False,
'validator': bool,
},
'PURCHASEORDER_EDIT_COMPLETED_ORDERS': { 'PURCHASEORDER_EDIT_COMPLETED_ORDERS': {
'name': _('Edit Completed Purchase Orders'), 'name': _('Edit Completed Purchase Orders'),
'description': _( 'description': _(
@ -2004,14 +2062,6 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False, 'default': False,
'validator': bool, 'validator': bool,
}, },
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
'name': _('Block Until Tests Pass'),
'description': _(
'Prevent build outputs from being completed until all required tests pass'
),
'default': False,
'validator': bool,
},
'TEST_STATION_DATA': { 'TEST_STATION_DATA': {
'name': _('Enable Test Station Data'), 'name': _('Enable Test Station Data'),
'description': _('Enable test station data collection for test results'), 'description': _('Enable test station data collection for test results'),
@ -2054,7 +2104,9 @@ def label_printer_options():
class InvenTreeUserSetting(BaseInvenTreeSetting): class InvenTreeUserSetting(BaseInvenTreeSetting):
"""An InvenTreeSetting object with a usercontext.""" """An InvenTreeSetting object with a user context."""
CHECK_SETTING_KEY = True
class Meta: class Meta:
"""Meta options for InvenTreeUserSetting.""" """Meta options for InvenTreeUserSetting."""
@ -2093,7 +2145,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
'validator': bool, 'validator': bool,
}, },
'HOMEPAGE_BOM_REQUIRES_VALIDATION': { 'HOMEPAGE_BOM_REQUIRES_VALIDATION': {
'name': _('Show unvalidated BOMs'), 'name': _('Show invalid BOMs'),
'description': _('Show BOMs that await validation on the homepage'), 'description': _('Show BOMs that await validation on the homepage'),
'default': False, 'default': False,
'validator': bool, 'validator': bool,
@ -2406,6 +2458,14 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
'validator': [int], 'validator': [int],
'default': '', 'default': '',
}, },
'DEFAULT_LINE_LABEL_TEMPLATE': {
'name': _('Default build line label template'),
'description': _(
'The build line label template to be automatically selected'
),
'validator': [int],
'default': '',
},
'NOTIFICATION_ERROR_REPORT': { 'NOTIFICATION_ERROR_REPORT': {
'name': _('Receive error reports'), 'name': _('Receive error reports'),
'description': _('Receive notifications for system errors'), 'description': _('Receive notifications for system errors'),
@ -2616,7 +2676,7 @@ class VerificationMethod(Enum):
class WebhookEndpoint(models.Model): class WebhookEndpoint(models.Model):
"""Defines a Webhook entdpoint. """Defines a Webhook endpoint.
Attributes: Attributes:
endpoint_id: Path to the webhook, endpoint_id: Path to the webhook,
@ -2951,7 +3011,7 @@ class NewsFeedEntry(models.Model):
- published: Date of publishing of the news item - published: Date of publishing of the news item
- author: Author of news item - author: Author of news item
- summary: Summary of the news items content - summary: Summary of the news items content
- read: Was this iteam already by a superuser? - read: Was this item already by a superuser?
""" """
feed_id = models.CharField(verbose_name=_('Id'), unique=True, max_length=250) feed_id = models.CharField(verbose_name=_('Id'), unique=True, max_length=250)

View File

@ -207,6 +207,8 @@ class Order(
responsible: User (or group) responsible for managing the order responsible: User (or group) responsible for managing the order
""" """
REQUIRE_RESPONSIBLE_SETTING = None
class Meta: class Meta:
"""Metaclass options. Abstract ensures no database table is created.""" """Metaclass options. Abstract ensures no database table is created."""
@ -227,6 +229,16 @@ class Order(
"""Custom clean method for the generic order class.""" """Custom clean method for the generic order class."""
super().clean() super().clean()
# Check if a responsible owner is required for this order type
if self.REQUIRE_RESPONSIBLE_SETTING:
if common_models.InvenTreeSetting.get_setting(
self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False
):
if not self.responsible:
raise ValidationError({
'responsible': _('Responsible user or group must be specified')
})
# Check that the referenced 'contact' matches the correct 'company' # Check that the referenced 'contact' matches the correct 'company'
if self.company and self.contact: if self.company and self.contact:
if self.contact.company != self.company: if self.contact.company != self.company:
@ -347,6 +359,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
target_date: Expected delivery target date for PurchaseOrder completion (optional) target_date: Expected delivery target date for PurchaseOrder completion (optional)
""" """
REFERENCE_PATTERN_SETTING = 'PURCHASEORDER_REFERENCE_PATTERN'
REQUIRE_RESPONSIBLE_SETTING = 'PURCHASEORDER_REQUIRE_RESPONSIBLE'
def get_absolute_url(self): def get_absolute_url(self):
"""Get the 'web' URL for this order.""" """Get the 'web' URL for this order."""
if settings.ENABLE_CLASSIC_FRONTEND: if settings.ENABLE_CLASSIC_FRONTEND:
@ -372,9 +387,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
return defaults return defaults
# Global setting for specifying reference pattern
REFERENCE_PATTERN_SETTING = 'PURCHASEORDER_REFERENCE_PATTERN'
@staticmethod @staticmethod
def filterByDate(queryset, min_date, max_date): def filterByDate(queryset, min_date, max_date):
"""Filter by 'minimum and maximum date range'. """Filter by 'minimum and maximum date range'.
@ -805,6 +817,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
class SalesOrder(TotalPriceMixin, Order): class SalesOrder(TotalPriceMixin, Order):
"""A SalesOrder represents a list of goods shipped outwards to a customer.""" """A SalesOrder represents a list of goods shipped outwards to a customer."""
REFERENCE_PATTERN_SETTING = 'SALESORDER_REFERENCE_PATTERN'
REQUIRE_RESPONSIBLE_SETTING = 'SALESORDER_REQUIRE_RESPONSIBLE'
def get_absolute_url(self): def get_absolute_url(self):
"""Get the 'web' URL for this order.""" """Get the 'web' URL for this order."""
if settings.ENABLE_CLASSIC_FRONTEND: if settings.ENABLE_CLASSIC_FRONTEND:
@ -828,9 +843,6 @@ class SalesOrder(TotalPriceMixin, Order):
return defaults return defaults
# Global setting for specifying reference pattern
REFERENCE_PATTERN_SETTING = 'SALESORDER_REFERENCE_PATTERN'
@staticmethod @staticmethod
def filterByDate(queryset, min_date, max_date): def filterByDate(queryset, min_date, max_date):
"""Filter by "minimum and maximum date range". """Filter by "minimum and maximum date range".
@ -1943,6 +1955,9 @@ class ReturnOrder(TotalPriceMixin, Order):
status: The status of the order (refer to status_codes.ReturnOrderStatus) status: The status of the order (refer to status_codes.ReturnOrderStatus)
""" """
REFERENCE_PATTERN_SETTING = 'RETURNORDER_REFERENCE_PATTERN'
REQUIRE_RESPONSIBLE_SETTING = 'RETURNORDER_REQUIRE_RESPONSIBLE'
def get_absolute_url(self): def get_absolute_url(self):
"""Get the 'web' URL for this order.""" """Get the 'web' URL for this order."""
if settings.ENABLE_CLASSIC_FRONTEND: if settings.ENABLE_CLASSIC_FRONTEND:
@ -1968,8 +1983,6 @@ class ReturnOrder(TotalPriceMixin, Order):
return defaults return defaults
REFERENCE_PATTERN_SETTING = 'RETURNORDER_REFERENCE_PATTERN'
def __str__(self): def __str__(self):
"""Render a string representation of this ReturnOrder.""" """Render a string representation of this ReturnOrder."""
return f"{self.reference} - {self.customer.name if self.customer else _('no customer')}" return f"{self.reference} - {self.customer.name if self.customer else _('no customer')}"

View File

@ -13,6 +13,7 @@ from djmoney.money import Money
from icalendar import Calendar from icalendar import Calendar
from rest_framework import status from rest_framework import status
from common.models import InvenTreeSetting
from common.settings import currency_codes from common.settings import currency_codes
from company.models import Company, SupplierPart, SupplierPriceBreak from company.models import Company, SupplierPart, SupplierPriceBreak
from InvenTree.status_codes import ( from InvenTree.status_codes import (
@ -27,6 +28,7 @@ from InvenTree.unit_test import InvenTreeAPITestCase
from order import models from order import models
from part.models import Part from part.models import Part
from stock.models import StockItem from stock.models import StockItem
from users.models import Owner
class OrderTest(InvenTreeAPITestCase): class OrderTest(InvenTreeAPITestCase):
@ -347,15 +349,35 @@ class PurchaseOrderTest(OrderTest):
"""Test that we can create a new PurchaseOrder via the API.""" """Test that we can create a new PurchaseOrder via the API."""
self.assignRole('purchase_order.add') self.assignRole('purchase_order.add')
self.post( setting = 'PURCHASEORDER_REQUIRE_RESPONSIBLE'
reverse('api-po-list'), url = reverse('api-po-list')
{
'reference': 'PO-12345678', InvenTreeSetting.set_setting(setting, False)
'supplier': 1,
'description': 'A test purchase order', data = {
}, 'reference': 'PO-12345678',
expected_code=201, 'supplier': 1,
) 'description': 'A test purchase order',
}
self.post(url, data, expected_code=201)
# Check the 'responsible required' field
InvenTreeSetting.set_setting(setting, True)
data['reference'] = 'PO-12345679'
data['responsible'] = None
response = self.post(url, data, expected_code=400)
self.assertIn('Responsible user or group must be specified', str(response.data))
data['responsible'] = Owner.objects.first().pk
response = self.post(url, data, expected_code=201)
# Revert the setting to previous value
InvenTreeSetting.set_setting(setting, False)
def test_po_creation_date(self): def test_po_creation_date(self):
"""Test that we can create set the creation_date field of PurchaseOrder via the API.""" """Test that we can create set the creation_date field of PurchaseOrder via the API."""

View File

@ -25,7 +25,9 @@ def compile_full_name_template(*args, **kwargs):
global _part_full_name_template global _part_full_name_template
global _part_full_name_template_string global _part_full_name_template_string
template_string = InvenTreeSetting.get_setting('PART_NAME_FORMAT', '') template_string = InvenTreeSetting.get_setting(
'PART_NAME_FORMAT', backup_value='', cache=True
)
# Skip if the template string has not changed # Skip if the template string has not changed
if ( if (

View File

@ -491,7 +491,7 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
PartCategory.objects.rebuild() PartCategory.objects.rebuild()
with self.assertNumQueriesLessThan(10): with self.assertNumQueriesLessThan(12):
response = self.get(reverse('api-part-category-tree'), expected_code=200) response = self.get(reverse('api-part-category-tree'), expected_code=200)
self.assertEqual(len(response.data), PartCategory.objects.count()) self.assertEqual(len(response.data), PartCategory.objects.count())

View File

@ -446,7 +446,7 @@ class StockLocationTest(StockAPITestCase):
StockLocation.objects.rebuild() StockLocation.objects.rebuild()
with self.assertNumQueriesLessThan(10): with self.assertNumQueriesLessThan(12):
response = self.get(reverse('api-location-tree'), expected_code=200) response = self.get(reverse('api-location-tree'), expected_code=200)
self.assertEqual(len(response.data), StockLocation.objects.count()) self.assertEqual(len(response.data), StockLocation.objects.count())

View File

@ -13,6 +13,7 @@
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PATTERN" %} {% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PATTERN" %}
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_RESPONSIBLE" %}
{% include "InvenTree/settings/setting.html" with key="PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS" %} {% include "InvenTree/settings/setting.html" with key="PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS" %}
</tbody> </tbody>
</table> </table>

View File

@ -11,6 +11,7 @@
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PATTERN" %} {% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PATTERN" %}
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REQUIRE_RESPONSIBLE" %}
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_EDIT_COMPLETED_ORDERS" icon='fa-edit' %} {% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_EDIT_COMPLETED_ORDERS" icon='fa-edit' %}
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_AUTO_COMPLETE" icon='fa-check-circle' %} {% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_AUTO_COMPLETE" icon='fa-check-circle' %}
</tbody> </tbody>

View File

@ -13,6 +13,7 @@
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_ENABLED" icon="fa-check-circle" %} {% include "InvenTree/settings/setting.html" with key="RETURNORDER_ENABLED" icon="fa-check-circle" %}
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_REFERENCE_PATTERN" %} {% include "InvenTree/settings/setting.html" with key="RETURNORDER_REFERENCE_PATTERN" %}
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_REQUIRE_RESPONSIBLE" %}
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_EDIT_COMPLETED_ORDERS" icon="fa-edit" %} {% include "InvenTree/settings/setting.html" with key="RETURNORDER_EDIT_COMPLETED_ORDERS" icon="fa-edit" %}
</tbody> </tbody>
</table> </table>

View File

@ -12,6 +12,7 @@
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PATTERN" %} {% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PATTERN" %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_REQUIRE_RESPONSIBLE" %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_DEFAULT_SHIPMENT" icon="fa-truck-loading" %} {% include "InvenTree/settings/setting.html" with key="SALESORDER_DEFAULT_SHIPMENT" icon="fa-truck-loading" %}
{% include "InvenTree/settings/setting.html" with key="SALESORDER_EDIT_COMPLETED_ORDERS" icon='fa-edit' %} {% include "InvenTree/settings/setting.html" with key="SALESORDER_EDIT_COMPLETED_ORDERS" icon='fa-edit' %}
</tbody> </tbody>

View File

@ -231,6 +231,7 @@ export default function SystemSettings() {
<GlobalSettingList <GlobalSettingList
keys={[ keys={[
'BUILDORDER_REFERENCE_PATTERN', 'BUILDORDER_REFERENCE_PATTERN',
'BUILDORDER_REQUIRE_RESPONSIBLE',
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS' 'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
]} ]}
/> />
@ -244,6 +245,7 @@ export default function SystemSettings() {
<GlobalSettingList <GlobalSettingList
keys={[ keys={[
'PURCHASEORDER_REFERENCE_PATTERN', 'PURCHASEORDER_REFERENCE_PATTERN',
'PURCHASEORDER_REQUIRE_RESPONSIBLE',
'PURCHASEORDER_EDIT_COMPLETED_ORDERS', 'PURCHASEORDER_EDIT_COMPLETED_ORDERS',
'PURCHASEORDER_AUTO_COMPLETE' 'PURCHASEORDER_AUTO_COMPLETE'
]} ]}
@ -258,6 +260,7 @@ export default function SystemSettings() {
<GlobalSettingList <GlobalSettingList
keys={[ keys={[
'SALESORDER_REFERENCE_PATTERN', 'SALESORDER_REFERENCE_PATTERN',
'SALESORDER_REQUIRE_RESPONSIBLE',
'SALESORDER_DEFAULT_SHIPMENT', 'SALESORDER_DEFAULT_SHIPMENT',
'SALESORDER_EDIT_COMPLETED_ORDERS' 'SALESORDER_EDIT_COMPLETED_ORDERS'
]} ]}
@ -273,6 +276,7 @@ export default function SystemSettings() {
keys={[ keys={[
'RETURNORDER_ENABLED', 'RETURNORDER_ENABLED',
'RETURNORDER_REFERENCE_PATTERN', 'RETURNORDER_REFERENCE_PATTERN',
'RETURNORDER_REQUIRE_RESPONSIBLE',
'RETURNORDER_EDIT_COMPLETED_ORDERS' 'RETURNORDER_EDIT_COMPLETED_ORDERS'
]} ]}
/> />