Coverage for src/backend/InvenTree/common/setting/system.py: 42%
92 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""System settings definition."""
3import json
4import os
5import re
6import uuid
8from django.conf import settings as django_settings
9from django.contrib.auth.models import Group
10from django.core.exceptions import FieldDoesNotExist, ValidationError
11from django.core.validators import MaxValueValidator, MinValueValidator, URLValidator
12from django.utils.translation import gettext_lazy as _
14from jinja2.sandbox import SandboxedEnvironment
16import build.validators
17import common.currency
18import common.validators
19import order.validators
20import report.helpers
21from common.setting.type import InvenTreeSettingsKeyType
24def validate_part_name_format(value):
25 """Validate part name format.
27 Make sure that each template container has a field of Part Model
28 """
29 # Make sure that the field_name exists in Part model
30 from part.models import Part
32 jinja_template_regex = re.compile(r'{{.*?}}')
33 field_name_regex = re.compile(r'(?<=part\.)[A-z]+')
35 for jinja_template in jinja_template_regex.findall(str(value)):
36 # make sure at least one and only one field is present inside the parser
37 field_names = field_name_regex.findall(jinja_template)
38 if len(field_names) < 1:
39 raise ValidationError({
40 'value': 'At least one field must be present inside a jinja template container i.e {{}}'
41 })
43 for field_name in field_names:
44 try:
45 Part._meta.get_field(field_name)
46 except FieldDoesNotExist:
47 raise ValidationError({
48 'value': f'{field_name} does not exist in Part Model'
49 })
51 # Attempt to render the template with a dummy Part instance
52 # Use pk=1 to ensure conditional checks like {% if part.pk %} are evaluated
53 p = Part(pk=1, name='test part', description='some test part')
55 try:
56 SandboxedEnvironment().from_string(value).render({'part': p})
57 except Exception as exc:
58 raise ValidationError({'value': str(exc)})
60 return True
63def update_instance_name(setting):
64 """Update the first site objects name to instance name."""
65 if not django_settings.SITE_MULTI: 65 ↛ 68line 65 didn't jump to line 68 because the condition on line 65 was always true
66 return
68 try:
69 from django.contrib.sites.models import Site
70 except (ImportError, RuntimeError):
71 # Multi-site support not enabled
72 return
74 site_obj = Site.objects.all().order_by('id').first()
75 site_obj.name = setting.value
76 site_obj.save()
79def update_instance_url(setting):
80 """Update the first site objects domain to url."""
81 if not django_settings.SITE_MULTI:
82 return
84 try:
85 from django.contrib.sites.models import Site
86 except (ImportError, RuntimeError):
87 # Multi-site support not enabled
88 return
90 site_obj = Site.objects.all().order_by('id').first()
91 site_obj.domain = setting.value
92 site_obj.save()
95def settings_group_options():
96 """Build up group tuple for settings based on your choices."""
97 return [('', _('No group')), *[(str(a.id), str(a)) for a in Group.objects.all()]]
100def reload_plugin_registry(setting):
101 """When a core plugin setting is changed, reload the plugin registry."""
102 from common.models import logger
103 from plugin import registry
105 logger.info("Reloading plugin registry due to change in setting '%s'", setting.key)
107 registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
110def enforce_mfa(setting):
111 """Enforce multifactor authentication for all users."""
112 from allauth.usersessions.models import UserSession
114 from common.models import logger
116 logger.info(
117 'Enforcing multifactor authentication for all users by signing out all sessions.'
118 )
119 for session in UserSession.objects.all():
120 session.end()
121 logger.info('All user sessions have been ended.')
124def barcode_plugins() -> list:
125 """Return a list of plugin choices which can be used for barcode generation."""
126 try:
127 from plugin import PluginMixinEnum, registry
129 plugins = registry.with_mixin(PluginMixinEnum.BARCODE, active=True)
130 except Exception: # pragma: no cover
131 plugins = []
133 return [
134 (plug.slug, plug.human_name) for plug in plugins if plug.has_barcode_generation
135 ]
138def default_uuid4() -> str:
139 """Return a default UUID4 value."""
140 return str(uuid.uuid4())
143class BaseURLValidator(URLValidator):
144 """Validator for the InvenTree base URL.
146 Rules:
147 - Allow empty value
148 - Allow value without specified TLD (top level domain)
149 """
151 def __init__(self, schemes=None, **kwargs):
152 """Custom init routine."""
153 super().__init__(schemes, **kwargs)
155 # Override default host_re value - allow optional tld regex
156 self.host_re = (
157 '('
158 + self.hostname_re
159 + self.domain_re
160 + f'({self.tld_re})?'
161 + '|localhost)'
162 )
164 def __call__(self, value):
165 """Make sure empty values pass."""
166 value = str(value).strip()
168 # If a configuration level value has been specified, prevent change
169 if django_settings.SITE_URL and value != django_settings.SITE_URL:
170 raise ValidationError(_('Site URL is locked by configuration'))
172 if len(value) == 0:
173 pass
175 else:
176 super().__call__(value)
179class SystemSetId:
180 """Shared system settings identifiers."""
182 GLOBAL_WARNING = '_GLOBAL_WARNING'
185SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
186 'SERVER_RESTART_REQUIRED': {
187 'name': _('Restart required'),
188 'description': _('A setting has been changed which requires a server restart'),
189 'default': False,
190 'validator': bool,
191 'hidden': True,
192 },
193 '_PENDING_MIGRATIONS': {
194 'name': _('Pending migrations'),
195 'description': _('Number of pending database migrations'),
196 'default': 0,
197 'validator': int,
198 },
199 SystemSetId.GLOBAL_WARNING: {
200 'name': _('Active warning codes'),
201 'description': _('A dict of active warning codes'),
202 'validator': json.loads,
203 'default': '{}',
204 'hidden': True,
205 },
206 'INVENTREE_INSTANCE_ID': {
207 'name': _('Instance ID'),
208 'description': _('Unique identifier for this InvenTree instance'),
209 'default': default_uuid4,
210 'hidden': True,
211 },
212 'INVENTREE_ANNOUNCE_ID': {
213 'name': _('Announce ID'),
214 'description': _(
215 'Announce the instance ID of the server in the server status info (unauthenticated)'
216 ),
217 'default': False,
218 'validator': bool,
219 },
220 'INVENTREE_INSTANCE': {
221 'name': _('Server Instance Name'),
222 'default': 'InvenTree',
223 'description': _('String descriptor for the server instance'),
224 'after_save': update_instance_name,
225 },
226 'INVENTREE_INSTANCE_TITLE': {
227 'name': _('Use instance name'),
228 'description': _('Use the instance name in the title-bar'),
229 'validator': bool,
230 'default': False,
231 },
232 'INVENTREE_RESTRICT_ABOUT': {
233 'name': _('Restrict showing `about`'),
234 'description': _('Show the `about` modal only to superusers'),
235 'validator': bool,
236 'default': False,
237 },
238 'INVENTREE_SHOW_SUPERUSER_BANNER': {
239 'name': _('Show superuser banner'),
240 'description': _('Show a warning banner in the UI when logged in as superuser'),
241 'validator': bool,
242 'default': True,
243 },
244 'INVENTREE_SHOW_ADMIN_BANNER': {
245 'name': _('Show admin banner'),
246 'description': _('Show a warning banner in the UI when logged in as admin'),
247 'validator': bool,
248 'default': False,
249 },
250 'INVENTREE_COMPANY_NAME': {
251 'name': _('Company name'),
252 'description': _('Internal company name'),
253 'default': 'My company name',
254 },
255 'INVENTREE_BASE_URL': {
256 'name': _('Base URL'),
257 'description': _('Base URL for server instance'),
258 'validator': BaseURLValidator(),
259 'default': '',
260 'after_save': update_instance_url,
261 },
262 'INVENTREE_DEFAULT_CURRENCY': {
263 'name': _('Default Currency'),
264 'description': _('Select base currency for pricing calculations'),
265 'default': 'USD',
266 'choices': common.currency.currency_code_mappings,
267 'after_save': common.currency.after_change_currency,
268 },
269 'CURRENCY_CODES': {
270 'name': _('Supported Currencies'),
271 'description': _('List of supported currency codes'),
272 'default': common.currency.currency_codes_default_list(),
273 'validator': common.currency.validate_currency_codes,
274 'after_save': common.currency.after_change_currency,
275 },
276 'CURRENCY_UPDATE_INTERVAL': {
277 'name': _('Currency Update Interval'),
278 'description': _('How often to update exchange rates (set to zero to disable)'),
279 'default': 1,
280 'units': _('days'),
281 'validator': [int, MinValueValidator(0)],
282 },
283 'CURRENCY_UPDATE_PLUGIN': {
284 'name': _('Currency Update Plugin'),
285 'description': _('Currency update plugin to use'),
286 'choices': common.currency.currency_exchange_plugins,
287 'default': 'inventreecurrencyexchange',
288 },
289 'INVENTREE_UPLOAD_MAX_SIZE': {
290 'name': _('Upload Size Limit'),
291 'description': _('Maximum allowable upload size for images and files'),
292 'units': 'MB',
293 'default': 10,
294 'validator': [int, MinValueValidator(1)],
295 },
296 'INVENTREE_STRICT_URLS': {
297 'name': _('Strict URL Validation'),
298 'description': _('Require schema specification when validating URLs'),
299 'validator': bool,
300 'default': True,
301 },
302 'INVENTREE_UPDATE_CHECK_INTERVAL': {
303 'name': _('Update Check Interval'),
304 'description': _('How often to check for updates (set to zero to disable)'),
305 'validator': [int, MinValueValidator(0)],
306 'default': 7,
307 'units': _('days'),
308 },
309 'INVENTREE_BACKUP_ENABLE': {
310 'name': _('Automatic Backup'),
311 'description': _('Enable automatic backup of database and media files'),
312 'validator': bool,
313 'default': False,
314 },
315 'INVENTREE_BACKUP_DAYS': {
316 'name': _('Auto Backup Interval'),
317 'description': _('Specify number of days between automated backup events'),
318 'validator': [int, MinValueValidator(1)],
319 'default': 1,
320 'units': _('days'),
321 },
322 'INVENTREE_DELETE_TASKS_DAYS': {
323 'name': _('Task Deletion Interval'),
324 'description': _(
325 'Background task results will be deleted after specified number of days'
326 ),
327 'default': 30,
328 'units': _('days'),
329 'validator': [int, MinValueValidator(7)],
330 },
331 'INVENTREE_DELETE_ERRORS_DAYS': {
332 'name': _('Error Log Deletion Interval'),
333 'description': _('Error logs will be deleted after specified number of days'),
334 'default': 30,
335 'units': _('days'),
336 'validator': [int, MinValueValidator(7)],
337 },
338 'INVENTREE_DELETE_NOTIFICATIONS_DAYS': {
339 'name': _('Notification Deletion Interval'),
340 'description': _(
341 'User notifications will be deleted after specified number of days'
342 ),
343 'default': 30,
344 'units': _('days'),
345 'validator': [int, MinValueValidator(7)],
346 },
347 'INVENTREE_DELETE_EMAIL_DAYS': {
348 'name': _('Email Deletion Interval'),
349 'description': _(
350 'Email messages will be deleted after specified number of days'
351 ),
352 'default': 30,
353 'units': _('days'),
354 'validator': [int, MinValueValidator(7)],
355 },
356 'INVENTREE_PROTECT_EMAIL_LOG': {
357 'name': _('Protect Email Log'),
358 'description': _('Prevent deletion of email log entries'),
359 'default': False,
360 'validator': bool,
361 },
362 'BARCODE_ENABLE': {
363 'name': _('Barcode Support'),
364 'description': _('Enable barcode scanner support in the web interface'),
365 'default': True,
366 'validator': bool,
367 },
368 'BARCODE_STORE_RESULTS': {
369 'name': _('Store Barcode Results'),
370 'description': _('Store barcode scan results in the database'),
371 'default': False,
372 'validator': bool,
373 },
374 'BARCODE_RESULTS_MAX_NUM': {
375 'name': _('Barcode Scans Maximum Count'),
376 'description': _('Maximum number of barcode scan results to store'),
377 'default': 100,
378 'validator': [int, MinValueValidator(1)],
379 },
380 'BARCODE_INPUT_DELAY': {
381 'name': _('Barcode Input Delay'),
382 'description': _('Barcode input processing delay time'),
383 'default': 50,
384 'validator': [int, MinValueValidator(1)],
385 'units': 'ms',
386 },
387 'BARCODE_WEBCAM_SUPPORT': {
388 'name': _('Barcode Webcam Support'),
389 'description': _('Allow barcode scanning via webcam in browser'),
390 'default': True,
391 'validator': bool,
392 },
393 'BARCODE_SHOW_TEXT': {
394 'name': _('Barcode Show Data'),
395 'description': _('Display barcode data in browser as text'),
396 'default': False,
397 'validator': bool,
398 },
399 'BARCODE_GENERATION_PLUGIN': {
400 'name': _('Barcode Generation Plugin'),
401 'description': _('Plugin to use for internal barcode data generation'),
402 'choices': barcode_plugins,
403 'default': 'inventreebarcode',
404 },
405 'PART_ENABLE_LOCKING': {
406 'name': _('Part Locking'),
407 'description': _('Enable locking of parts to prevent modification'),
408 'validator': bool,
409 'default': True,
410 },
411 'PART_ENABLE_REVISION': {
412 'name': _('Part Revisions'),
413 'description': _('Enable revision field for Part'),
414 'validator': bool,
415 'default': True,
416 },
417 'PART_REVISION_ASSEMBLY_ONLY': {
418 'name': _('Assembly Revision Only'),
419 'description': _('Only allow revisions for assembly parts'),
420 'validator': bool,
421 'default': False,
422 },
423 'PART_ALLOW_DELETE_FROM_ASSEMBLY': {
424 'name': _('Allow Deletion from Assembly'),
425 'description': _('Allow deletion of parts which are used in an assembly'),
426 'validator': bool,
427 'default': False,
428 },
429 'PART_IPN_REGEX': {
430 'name': _('IPN Regex'),
431 'description': _('Regular expression pattern for matching Part IPN'),
432 },
433 'PART_ALLOW_DUPLICATE_IPN': {
434 'name': _('Allow Duplicate IPN'),
435 'description': _('Allow multiple parts to share the same IPN'),
436 'default': True,
437 'validator': bool,
438 },
439 'PART_ALLOW_EDIT_IPN': {
440 'name': _('Allow Editing IPN'),
441 'description': _('Allow changing the IPN value while editing a part'),
442 'default': True,
443 'validator': bool,
444 },
445 'PART_COPY_BOM': {
446 'name': _('Copy Part BOM Data'),
447 'description': _('Copy BOM data by default when duplicating a part'),
448 'default': True,
449 'validator': bool,
450 },
451 'PART_COPY_PARAMETERS': {
452 'name': _('Copy Part Parameter Data'),
453 'description': _('Copy parameter data by default when duplicating a part'),
454 'default': True,
455 'validator': bool,
456 },
457 'PART_COPY_TESTS': {
458 'name': _('Copy Part Test Data'),
459 'description': _('Copy test data by default when duplicating a part'),
460 'default': True,
461 'validator': bool,
462 },
463 'PART_CATEGORY_PARAMETERS': {
464 'name': _('Copy Category Parameter Templates'),
465 'description': _('Copy category parameter templates when creating a part'),
466 'default': True,
467 'validator': bool,
468 },
469 'PART_TEMPLATE': {
470 'name': _('Template'),
471 'description': _('Parts are templates by default'),
472 'default': False,
473 'validator': bool,
474 },
475 'PART_ASSEMBLY': {
476 'name': _('Assembly'),
477 'description': _('Parts can be assembled from other components by default'),
478 'default': False,
479 'validator': bool,
480 },
481 'PART_COMPONENT': {
482 'name': _('Component'),
483 'description': _('Parts can be used as sub-components by default'),
484 'default': True,
485 'validator': bool,
486 },
487 'PART_PURCHASEABLE': {
488 'name': _('Purchaseable'),
489 'description': _('Parts are purchaseable by default'),
490 'default': True,
491 'validator': bool,
492 },
493 'PART_SALABLE': {
494 'name': _('Salable'),
495 'description': _('Parts are salable by default'),
496 'default': False,
497 'validator': bool,
498 },
499 'PART_TRACKABLE': {
500 'name': _('Trackable'),
501 'description': _('Parts are trackable by default'),
502 'default': False,
503 'validator': bool,
504 },
505 'PART_VIRTUAL': {
506 'name': _('Virtual'),
507 'description': _('Parts are virtual by default'),
508 'default': False,
509 'validator': bool,
510 },
511 'PART_SHOW_RELATED': {
512 'name': _('Show related parts'),
513 'description': _('Display related parts for a part'),
514 'default': True,
515 'validator': bool,
516 },
517 'PART_CREATE_INITIAL': {
518 'name': _('Initial Stock Data'),
519 'description': _('Allow creation of initial stock when adding a new part'),
520 'default': False,
521 'validator': bool,
522 },
523 'PART_CREATE_SUPPLIER': {
524 'name': _('Initial Supplier Data'),
525 'description': _(
526 'Allow creation of initial supplier data when adding a new part'
527 ),
528 'default': True,
529 'validator': bool,
530 },
531 'PART_NAME_FORMAT': {
532 'name': _('Part Name Display Format'),
533 'description': _('Format to display the part name'),
534 'default': "{{ part.IPN if part.IPN }}{{ ' | ' if part.IPN }}{{ part.name }}{{ ' | ' if part.revision }}"
535 '{{ part.revision if part.revision }}',
536 'validator': validate_part_name_format,
537 },
538 'PART_CATEGORY_DEFAULT_ICON': {
539 'name': _('Part Category Default Icon'),
540 'description': _('Part category default icon (empty means no icon)'),
541 'default': '',
542 'validator': common.validators.validate_icon,
543 },
544 'PRICING_DECIMAL_PLACES_MIN': {
545 'name': _('Minimum Pricing Decimal Places'),
546 'description': _(
547 'Minimum number of decimal places to display when rendering pricing data'
548 ),
549 'default': 0,
550 'validator': [
551 int,
552 MinValueValidator(0),
553 MaxValueValidator(4),
554 common.validators.validate_decimal_places_min,
555 ],
556 },
557 'PRICING_DECIMAL_PLACES': {
558 'name': _('Maximum Pricing Decimal Places'),
559 'description': _(
560 'Maximum number of decimal places to display when rendering pricing data'
561 ),
562 'default': 6,
563 'validator': [
564 int,
565 MinValueValidator(2),
566 MaxValueValidator(6),
567 common.validators.validate_decimal_places_max,
568 ],
569 },
570 'PRICING_USE_SUPPLIER_PRICING': {
571 'name': _('Use Supplier Pricing'),
572 'description': _(
573 'Include supplier price breaks in overall pricing calculations'
574 ),
575 'default': True,
576 'validator': bool,
577 },
578 'PRICING_PURCHASE_HISTORY_OVERRIDES_SUPPLIER': {
579 'name': _('Purchase History Override'),
580 'description': _(
581 'Historical purchase order pricing overrides supplier price breaks'
582 ),
583 'default': False,
584 'validator': bool,
585 },
586 'PRICING_USE_STOCK_PRICING': {
587 'name': _('Use Stock Item Pricing'),
588 'description': _(
589 'Use pricing from manually entered stock data for pricing calculations'
590 ),
591 'default': True,
592 'validator': bool,
593 },
594 'PRICING_STOCK_ITEM_AGE_DAYS': {
595 'name': _('Stock Item Pricing Age'),
596 'description': _(
597 'Exclude stock items older than this number of days from pricing calculations'
598 ),
599 'default': 0,
600 'units': _('days'),
601 'validator': [int, MinValueValidator(0)],
602 },
603 'PRICING_USE_VARIANT_PRICING': {
604 'name': _('Use Variant Pricing'),
605 'description': _('Include variant pricing in overall pricing calculations'),
606 'default': True,
607 'validator': bool,
608 },
609 'PRICING_ACTIVE_VARIANTS': {
610 'name': _('Active Variants Only'),
611 'description': _(
612 'Only use active variant parts for calculating variant pricing'
613 ),
614 'default': False,
615 'validator': bool,
616 },
617 'PRICING_AUTO_UPDATE': {
618 'name': _('Auto Update Pricing'),
619 'description': _(
620 'Automatically update part pricing when internal data changes'
621 ),
622 'default': True,
623 'validator': bool,
624 },
625 'PRICING_UPDATE_DAYS': {
626 'name': _('Pricing Rebuild Interval'),
627 'description': _('Number of days before part pricing is automatically updated'),
628 'units': _('days'),
629 'default': 30,
630 'validator': [int, MinValueValidator(0)],
631 },
632 'PART_INTERNAL_PRICE': {
633 'name': _('Internal Prices'),
634 'description': _('Enable internal prices for parts'),
635 'default': False,
636 'validator': bool,
637 },
638 'PART_BOM_USE_INTERNAL_PRICE': {
639 'name': _('Internal Price Override'),
640 'description': _(
641 'If available, internal prices override price range calculations'
642 ),
643 'default': False,
644 'validator': bool,
645 },
646 'PART_BOM_ALLOW_ZERO_QUANTITY': {
647 'name': _('Allow BOM Zero Quantity'),
648 'description': _(
649 'Accept a zero quantity for BOM item for part. Enables using setup quantity to define a quantity required per build, independent of build quantity'
650 ),
651 'default': False,
652 'validator': bool,
653 },
654 'LABEL_ENABLE': {
655 'name': _('Enable label printing'),
656 'description': _('Enable label printing from the web interface'),
657 'default': True,
658 'validator': bool,
659 },
660 'LABEL_DPI': {
661 'name': _('Label Image DPI'),
662 'description': _(
663 'DPI resolution when generating image files to supply to label printing plugins'
664 ),
665 'default': 300,
666 'validator': [int, MinValueValidator(100)],
667 },
668 'REPORT_ENABLE': {
669 'name': _('Enable Reports'),
670 'description': _('Enable generation of reports'),
671 'default': False,
672 'validator': bool,
673 },
674 'REPORT_DEBUG_MODE': {
675 'name': _('Debug Mode'),
676 'description': _('Generate reports in debug mode (HTML output)'),
677 'default': False,
678 'validator': bool,
679 },
680 'REPORT_FETCH_URLS': {
681 'name': _('Report URL Fetching'),
682 'description': _('Allow fetching of remote URLs when generating reports'),
683 'default': False,
684 'validator': bool,
685 },
686 'REPORT_LOG_ERRORS': {
687 'name': _('Log Report Errors'),
688 'description': _('Log errors which occur when generating reports'),
689 'default': False,
690 'validator': bool,
691 },
692 'REPORT_DEFAULT_PAGE_SIZE': {
693 'name': _('Page Size'),
694 'description': _('Default page size for PDF reports'),
695 'default': 'A4',
696 'choices': report.helpers.report_page_size_options,
697 },
698 'PARAMETER_ENFORCE_UNITS': {
699 'name': _('Enforce Parameter Units'),
700 'description': _(
701 'If units are provided, parameter values must match the specified units'
702 ),
703 'default': True,
704 'validator': bool,
705 },
706 'SERIAL_NUMBER_GLOBALLY_UNIQUE': {
707 'name': _('Globally Unique Serials'),
708 'description': _('Serial numbers for stock items must be globally unique'),
709 'default': False,
710 'validator': bool,
711 },
712 'STOCK_DELETE_DEPLETED_DEFAULT': {
713 'name': _('Delete Depleted Stock'),
714 'description': _('Determines default behavior when a stock item is depleted'),
715 'default': True,
716 'validator': bool,
717 },
718 'STOCK_ALLOW_EDIT_SERIAL': {
719 'name': _('Allow Edit Serial Number'),
720 'description': _('Allow editing of serial number for stock items'),
721 'default': True,
722 'validator': bool,
723 },
724 'STOCK_ALLOW_DELETE_SERIALIZED': {
725 'name': _('Delete Serialized Stock'),
726 'description': _('Allow deletion of stock items which have a serial number'),
727 'default': True,
728 'validator': bool,
729 },
730 'STOCK_BATCH_CODE_TEMPLATE': {
731 'name': _('Batch Code Template'),
732 'description': _('Template for generating default batch codes for stock items'),
733 'default': '',
734 },
735 'STOCK_ENABLE_EXPIRY': {
736 'name': _('Stock Expiry'),
737 'description': _('Enable stock expiry functionality'),
738 'default': False,
739 'validator': bool,
740 },
741 'STOCK_ALLOW_EXPIRED_SALE': {
742 'name': _('Sell Expired Stock'),
743 'description': _('Allow sale of expired stock'),
744 'default': False,
745 'validator': bool,
746 },
747 'STOCK_STALE_DAYS': {
748 'name': _('Stock Stale Time'),
749 'description': _(
750 'Number of days stock items are considered stale before expiring'
751 ),
752 'default': 0,
753 'units': _('days'),
754 'validator': [int],
755 },
756 'STOCK_ALLOW_EXPIRED_BUILD': {
757 'name': _('Build Expired Stock'),
758 'description': _('Allow building with expired stock'),
759 'default': False,
760 'validator': bool,
761 },
762 'STOCK_OWNERSHIP_CONTROL': {
763 'name': _('Stock Ownership Control'),
764 'description': _('Enable ownership control over stock locations and items'),
765 'default': False,
766 'validator': bool,
767 },
768 'STOCK_LOCATION_DEFAULT_ICON': {
769 'name': _('Stock Location Default Icon'),
770 'description': _('Stock location default icon (empty means no icon)'),
771 'default': '',
772 'validator': common.validators.validate_icon,
773 },
774 'STOCK_SHOW_INSTALLED_ITEMS': {
775 'name': _('Show Installed Stock Items'),
776 'description': _('Display installed stock items in stock tables'),
777 'default': False,
778 'validator': bool,
779 },
780 'STOCK_ENFORCE_BOM_INSTALLATION': {
781 'name': _('Check BOM when installing items'),
782 'description': _(
783 'Installed stock items must exist in the BOM for the parent part'
784 ),
785 'default': True,
786 'validator': bool,
787 },
788 'STOCK_ALLOW_OUT_OF_STOCK_TRANSFER': {
789 'name': _('Allow Out of Stock Transfer'),
790 'description': _(
791 'Allow stock items which are not in stock to be transferred between stock locations'
792 ),
793 'default': False,
794 'validator': bool,
795 },
796 'BUILDORDER_REFERENCE_PATTERN': {
797 'name': _('Build Order Reference Pattern'),
798 'description': _('Required pattern for generating Build Order reference field'),
799 'default': 'BO-{ref:04d}',
800 'validator': build.validators.validate_build_order_reference_pattern,
801 },
802 'BUILDORDER_REQUIRE_RESPONSIBLE': {
803 'name': _('Require Responsible Owner'),
804 'description': _('A responsible owner must be assigned to each order'),
805 'default': False,
806 'validator': bool,
807 },
808 'BUILDORDER_REQUIRE_ACTIVE_PART': {
809 'name': _('Require Active Part'),
810 'description': _('Prevent build order creation for inactive parts'),
811 'default': False,
812 'validator': bool,
813 },
814 'BUILDORDER_REQUIRE_LOCKED_PART': {
815 'name': _('Require Locked Part'),
816 'description': _('Prevent build order creation for unlocked parts'),
817 'default': False,
818 'validator': bool,
819 },
820 'BUILDORDER_REQUIRE_VALID_BOM': {
821 'name': _('Require Valid BOM'),
822 'description': _('Prevent build order creation unless BOM has been validated'),
823 'default': False,
824 'validator': bool,
825 },
826 'BUILDORDER_REQUIRE_CLOSED_CHILDS': {
827 'name': _('Require Closed Child Orders'),
828 'description': _(
829 'Prevent build order completion until all child orders are closed'
830 ),
831 'default': False,
832 'validator': bool,
833 },
834 'BUILDORDER_EXTERNAL_BUILDS': {
835 'name': _('External Build Orders'),
836 'description': _('Enable external build order functionality'),
837 'default': False,
838 'validator': bool,
839 },
840 'BUILDORDER_EXTERNAL_REQUIRED': {
841 'name': _('Require External Build Orders'),
842 'description': _(
843 'Require an external build order when ordering assembled parts from an external supplier'
844 ),
845 'default': False,
846 'validator': bool,
847 },
848 'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
849 'name': _('Block Until Tests Pass'),
850 'description': _(
851 'Prevent build outputs from being completed until all required tests pass'
852 ),
853 'default': False,
854 'validator': bool,
855 },
856 'RETURNORDER_ENABLED': {
857 'name': _('Enable Return Orders'),
858 'description': _('Enable return order functionality in the user interface'),
859 'validator': bool,
860 'default': False,
861 },
862 'RETURNORDER_REFERENCE_PATTERN': {
863 'name': _('Return Order Reference Pattern'),
864 'description': _(
865 'Required pattern for generating Return Order reference field'
866 ),
867 'default': 'RMA-{ref:04d}',
868 'validator': order.validators.validate_return_order_reference_pattern,
869 },
870 'RETURNORDER_REQUIRE_RESPONSIBLE': {
871 'name': _('Require Responsible Owner'),
872 'description': _('A responsible owner must be assigned to each order'),
873 'default': False,
874 'validator': bool,
875 },
876 'RETURNORDER_EDIT_COMPLETED_ORDERS': {
877 'name': _('Edit Completed Return Orders'),
878 'description': _(
879 'Allow editing of return orders after they have been completed'
880 ),
881 'default': False,
882 'validator': bool,
883 },
884 'SALESORDER_REFERENCE_PATTERN': {
885 'name': _('Sales Order Reference Pattern'),
886 'description': _('Required pattern for generating Sales Order reference field'),
887 'default': 'SO-{ref:04d}',
888 'validator': order.validators.validate_sales_order_reference_pattern,
889 },
890 'SALESORDER_REQUIRE_RESPONSIBLE': {
891 'name': _('Require Responsible Owner'),
892 'description': _('A responsible owner must be assigned to each order'),
893 'default': False,
894 'validator': bool,
895 },
896 'SALESORDER_DEFAULT_SHIPMENT': {
897 'name': _('Sales Order Default Shipment'),
898 'description': _('Enable creation of default shipment with sales orders'),
899 'default': False,
900 'validator': bool,
901 },
902 'SALESORDER_EDIT_COMPLETED_ORDERS': {
903 'name': _('Edit Completed Sales Orders'),
904 'description': _(
905 'Allow editing of sales orders after they have been shipped or completed'
906 ),
907 'default': False,
908 'validator': bool,
909 },
910 'SALESORDER_SHIPMENT_REQUIRES_CHECK': {
911 'name': _('Shipment Requires Checking'),
912 'description': _(
913 'Prevent completion of shipments until items have been checked'
914 ),
915 'default': False,
916 'validator': bool,
917 },
918 'SALESORDER_SHIP_COMPLETE': {
919 'name': _('Mark Shipped Orders as Complete'),
920 'description': _(
921 'Sales orders marked as shipped will automatically be completed, bypassing the "shipped" status'
922 ),
923 'default': False,
924 'validator': bool,
925 },
926 'TRANSFERORDER_ENABLED': {
927 'name': _('Enable Transfer Orders'),
928 'description': _('Enable transfer order functionality in the user interface'),
929 'validator': bool,
930 'default': False,
931 },
932 'TRANSFERORDER_REFERENCE_PATTERN': {
933 'name': _('Transfer Order Reference Pattern'),
934 'description': _(
935 'Required pattern for generating Transfer Order reference field'
936 ),
937 'default': 'TO-{ref:04d}',
938 'validator': order.validators.validate_transfer_order_reference_pattern,
939 },
940 'TRANSFERORDER_REQUIRE_RESPONSIBLE': {
941 'name': _('Require Responsible Owner'),
942 'description': _('A responsible owner must be assigned to each order'),
943 'default': False,
944 'validator': bool,
945 },
946 'SALESORDER_BLOCK_INCOMPLETE_ITEM_TESTS': {
947 'name': _('Block Incomplete Item Tests'),
948 'description': _(
949 'Prevent allocation of stock items to sales orders if required item tests are incomplete'
950 ),
951 'default': False,
952 'validator': bool,
953 },
954 'PURCHASEORDER_REFERENCE_PATTERN': {
955 'name': _('Purchase Order Reference Pattern'),
956 'description': _(
957 'Required pattern for generating Purchase Order reference field'
958 ),
959 'default': 'PO-{ref:04d}',
960 'validator': order.validators.validate_purchase_order_reference_pattern,
961 },
962 'PURCHASEORDER_REQUIRE_RESPONSIBLE': {
963 'name': _('Require Responsible Owner'),
964 'description': _('A responsible owner must be assigned to each order'),
965 'default': False,
966 'validator': bool,
967 },
968 'PURCHASEORDER_EDIT_COMPLETED_ORDERS': {
969 'name': _('Edit Completed Purchase Orders'),
970 'description': _(
971 'Allow editing of purchase orders after they have been shipped or completed'
972 ),
973 'default': False,
974 'validator': bool,
975 },
976 'PURCHASEORDER_CONVERT_CURRENCY': {
977 'name': _('Convert Currency'),
978 'description': _('Convert item value to base currency when receiving stock'),
979 'default': False,
980 'validator': bool,
981 },
982 'PURCHASEORDER_AUTO_COMPLETE': {
983 'name': _('Auto Complete Purchase Orders'),
984 'description': _(
985 'Automatically mark purchase orders as complete when all line items are received'
986 ),
987 'default': True,
988 'validator': bool,
989 },
990 # login / SSO
991 'LOGIN_ENABLE_PWD_FORGOT': {
992 'name': _('Enable password forgot'),
993 'description': _('Enable password forgot function on the login pages'),
994 'default': True,
995 'validator': bool,
996 },
997 'LOGIN_ENABLE_REG': {
998 'name': _('Enable registration'),
999 'description': _('Enable self-registration for users on the login pages'),
1000 'default': False,
1001 'validator': bool,
1002 },
1003 'LOGIN_ENABLE_SSO': {
1004 'name': _('Enable SSO'),
1005 'description': _('Enable SSO on the login pages'),
1006 'default': False,
1007 'validator': bool,
1008 },
1009 'LOGIN_ENABLE_SSO_REG': {
1010 'name': _('Enable SSO registration'),
1011 'description': _(
1012 'Enable self-registration via SSO for users on the login pages'
1013 ),
1014 'default': False,
1015 'validator': bool,
1016 },
1017 'LOGIN_ENABLE_SSO_GROUP_SYNC': {
1018 'name': _('Enable SSO group sync'),
1019 'description': _(
1020 'Enable synchronizing InvenTree groups with groups provided by the IdP'
1021 ),
1022 'default': False,
1023 'validator': bool,
1024 },
1025 'SSO_GROUP_KEY': {
1026 'name': _('SSO group key'),
1027 'description': _('The name of the groups claim attribute provided by the IdP'),
1028 'default': 'groups',
1029 'validator': str,
1030 },
1031 'SSO_GROUP_MAP': {
1032 'name': _('SSO group map'),
1033 'description': _(
1034 'A mapping from SSO groups to local InvenTree groups. If the local group does not exist, it will be created.'
1035 ),
1036 'validator': json.loads,
1037 'default': '{}',
1038 },
1039 'SSO_REMOVE_GROUPS': {
1040 'name': _('Remove groups outside of SSO'),
1041 'description': _(
1042 'Whether groups assigned to the user should be removed if they are not backend by the IdP. Disabling this setting might cause security issues'
1043 ),
1044 'default': True,
1045 'validator': bool,
1046 },
1047 'LOGIN_MAIL_REQUIRED': {
1048 'name': _('Email required'),
1049 'description': _('Require user to supply mail on signup'),
1050 'default': False,
1051 'validator': bool,
1052 },
1053 'LOGIN_SIGNUP_SSO_AUTO': {
1054 'name': _('Auto-fill SSO users'),
1055 'description': _('Automatically fill out user-details from SSO account-data'),
1056 'default': True,
1057 'validator': bool,
1058 },
1059 'LOGIN_SIGNUP_MAIL_TWICE': {
1060 'name': _('Mail twice'),
1061 'description': _('On signup ask users twice for their mail'),
1062 'default': False,
1063 'validator': bool,
1064 },
1065 'LOGIN_SIGNUP_PWD_TWICE': {
1066 'name': _('Password twice'),
1067 'description': _('On signup ask users twice for their password'),
1068 'default': True,
1069 'validator': bool,
1070 },
1071 'LOGIN_SIGNUP_MAIL_RESTRICTION': {
1072 'name': _('Allowed domains'),
1073 'description': _(
1074 'Restrict signup to certain domains (comma-separated, starting with @)'
1075 ),
1076 'default': '',
1077 'before_save': common.validators.validate_email_domains,
1078 },
1079 'SIGNUP_GROUP': {
1080 'name': _('Group on signup'),
1081 'description': _(
1082 'Group to which new users are assigned on registration. If SSO group sync is enabled, this group is only set if no group can be assigned from the IdP.'
1083 ),
1084 'default': '',
1085 'choices': settings_group_options,
1086 },
1087 'LOGIN_ENFORCE_MFA': {
1088 'name': _('Enforce MFA'),
1089 'description': _('Users must use multifactor security.'),
1090 'default': False,
1091 'validator': bool,
1092 'confirm': True,
1093 'confirm_text': _(
1094 'Enabling this setting will require all users to set up multifactor authentication. All sessions will be disconnected immediately.'
1095 ),
1096 'after_save': enforce_mfa,
1097 },
1098 'PLUGIN_ON_STARTUP': {
1099 'name': _('Check plugins on startup'),
1100 'description': _(
1101 'Check that all plugins are installed on startup - enable in container environments'
1102 ),
1103 'default': str(os.getenv('INVENTREE_DOCKER', 'False')).lower() in ['1', 'true'],
1104 'validator': bool,
1105 'requires_restart': True,
1106 },
1107 'PLUGIN_UPDATE_CHECK': {
1108 'name': _('Check for plugin updates'),
1109 'description': _('Enable periodic checks for updates to installed plugins'),
1110 'default': True,
1111 'validator': bool,
1112 },
1113 # Settings for plugin mixin features
1114 'ENABLE_PLUGINS_URL': {
1115 'name': _('Enable URL integration'),
1116 'description': _('Enable plugins to add URL routes'),
1117 'default': False,
1118 'validator': bool,
1119 'after_save': reload_plugin_registry,
1120 },
1121 'ENABLE_PLUGINS_NAVIGATION': {
1122 'name': _('Enable navigation integration'),
1123 'description': _('Enable plugins to integrate into navigation'),
1124 'default': False,
1125 'validator': bool,
1126 'after_save': reload_plugin_registry,
1127 },
1128 'ENABLE_PLUGINS_APP': {
1129 'name': _('Enable app integration'),
1130 'description': _('Enable plugins to add apps'),
1131 'default': False,
1132 'validator': bool,
1133 'after_save': reload_plugin_registry,
1134 },
1135 'ENABLE_PLUGINS_SCHEDULE': {
1136 'name': _('Enable schedule integration'),
1137 'description': _('Enable plugins to run scheduled tasks'),
1138 'default': False,
1139 'validator': bool,
1140 'after_save': reload_plugin_registry,
1141 },
1142 'ENABLE_PLUGINS_EVENTS': {
1143 'name': _('Enable event integration'),
1144 'description': _('Enable plugins to respond to internal events'),
1145 'default': False,
1146 'validator': bool,
1147 'after_save': reload_plugin_registry,
1148 },
1149 'ENABLE_PLUGINS_INTERFACE': {
1150 'name': _('Enable interface integration'),
1151 'description': _('Enable plugins to integrate into the user interface'),
1152 'default': False,
1153 'validator': bool,
1154 'after_save': reload_plugin_registry,
1155 },
1156 'ENABLE_PLUGINS_MAILS': {
1157 'name': _('Enable mail integration'),
1158 'description': _('Enable plugins to process outgoing/incoming mails'),
1159 'default': False,
1160 'validator': bool,
1161 'after_save': reload_plugin_registry,
1162 },
1163 'PROJECT_CODES_ENABLED': {
1164 'name': _('Enable project codes'),
1165 'description': _('Enable project codes for tracking projects'),
1166 'default': False,
1167 'validator': bool,
1168 },
1169 'STOCKTAKE_ENABLE': {
1170 'name': _('Enable Stocktake'),
1171 'description': _(
1172 'Enable functionality for recording historical stock levels and value'
1173 ),
1174 'validator': bool,
1175 'default': False,
1176 },
1177 'STOCKTAKE_EXCLUDE_EXTERNAL': {
1178 'name': _('Exclude External Locations'),
1179 'description': _(
1180 'Exclude stock items in external locations from stocktake calculations'
1181 ),
1182 'validator': bool,
1183 'default': False,
1184 },
1185 'STOCKTAKE_AUTO_DAYS': {
1186 'name': _('Automatic Stocktake Period'),
1187 'description': _('Number of days between automatic stocktake recording'),
1188 'validator': [int, MinValueValidator(1)],
1189 'default': 7,
1190 'units': _('days'),
1191 },
1192 'STOCKTAKE_DELETE_OLD_ENTRIES': {
1193 'name': _('Delete Old Stocktake Entries'),
1194 'description': _(
1195 'Delete stocktake entries older than the specified number of days'
1196 ),
1197 'default': False,
1198 'validator': bool,
1199 },
1200 'STOCKTAKE_DELETE_DAYS': {
1201 'name': _('Stocktake Deletion Interval'),
1202 'description': _(
1203 'Stocktake entries will be deleted after specified number of days'
1204 ),
1205 'default': 365,
1206 'units': _('days'),
1207 'validator': [int, MinValueValidator(30)],
1208 },
1209 'STOCK_TRACKING_DELETE_OLD_ENTRIES': {
1210 'name': _('Delete Old Stock Tracking Entries'),
1211 'description': _(
1212 'Delete stock tracking entries older than the specified number of days'
1213 ),
1214 'default': False,
1215 'validator': bool,
1216 },
1217 'STOCK_TRACKING_DELETE_DAYS': {
1218 'name': _('Stock Tracking Deletion Interval'),
1219 'description': _(
1220 'Stock tracking entries will be deleted after specified number of days'
1221 ),
1222 'default': 365,
1223 'units': _('days'),
1224 'validator': [int, MinValueValidator(30)],
1225 },
1226 'DISPLAY_FULL_NAMES': {
1227 'name': _('Display Users full names'),
1228 'description': _('Display Users full names instead of usernames'),
1229 'default': False,
1230 'validator': bool,
1231 },
1232 'DISPLAY_PROFILE_INFO': {
1233 'name': _('Display User Profiles'),
1234 'description': _('Display Users Profiles on their profile page'),
1235 'default': True,
1236 'validator': bool,
1237 },
1238 'WEEK_STARTS_ON': {
1239 'name': _('Week Starts On'),
1240 'description': _('Starting day of the week, for display in calendar views'),
1241 'default': '1',
1242 'choices': [
1243 ('0', _('Sunday')),
1244 ('1', _('Monday')),
1245 ('2', _('Tuesday')),
1246 ('3', _('Wednesday')),
1247 ('4', _('Thursday')),
1248 ('5', _('Friday')),
1249 ('6', _('Saturday')),
1250 ],
1251 },
1252 'CALENDAR_HORIZON_MONTHS': {
1253 'name': _('Calendar Horizon'),
1254 'description': _(
1255 'Number of months into the future to display in calendar views'
1256 ),
1257 'default': 12,
1258 'validator': [int, MinValueValidator(1)],
1259 'units': _('months'),
1260 },
1261 'TEST_STATION_DATA': {
1262 'name': _('Enable Test Station Data'),
1263 'description': _('Enable test station data collection for test results'),
1264 'default': False,
1265 'validator': bool,
1266 },
1267 'MACHINE_PING_ENABLED': {
1268 'name': _('Enable Machine Ping'),
1269 'description': _(
1270 'Enable periodic ping task of registered machines to check their status'
1271 ),
1272 'default': True,
1273 'validator': bool,
1274 },
1275}