diff --git a/.gitignore b/.gitignore
index 4bc362f653..8541ce79a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -111,3 +111,4 @@ InvenTree/web/static
docs/schema.yml
docs/docs/api/*.yml
docs/docs/api/schema/*.yml
+inventree_settings.json
diff --git a/docs/docs/assets/images/settings/user_notifications.png b/docs/docs/assets/images/settings/user_notifications.png
deleted file mode 100644
index ef1b8294f0..0000000000
Binary files a/docs/docs/assets/images/settings/user_notifications.png and /dev/null differ
diff --git a/docs/docs/assets/images/settings/user_reporting.png b/docs/docs/assets/images/settings/user_reporting.png
deleted file mode 100644
index 0b08c7914b..0000000000
Binary files a/docs/docs/assets/images/settings/user_reporting.png and /dev/null differ
diff --git a/docs/docs/build/build.md b/docs/docs/build/build.md
index 6c2f4c31df..09c0add44f 100644
--- a/docs/docs/build/build.md
+++ b/docs/docs/build/build.md
@@ -265,18 +265,16 @@ Build orders may (optionally) have a target complete date specified. If this dat
- Builds can be filtered by overdue status in the build list
- Overdue builds will be displayed on the home page
-## Build Order Restrictions
+## Build Order Settings
-There are a number of optional restrictions which can be applied to build orders, which may be enabled or disabled in the system settings:
+The following [global settings](../settings/global.md) are available for adjusting the behavior of build orders:
-### Require Active Part
-
-If this option is enabled, build orders can only be created for parts which are marked as [Active](../part/part.md#active-parts).
-
-### Require Locked Part
-
-If this option is enabled, build orders can only be created for parts which are marked as [Locked](../part/part.md#locked-parts).
-
-### Require Valid BOM
-
-If this option is enabled, build orders can only be created for parts which have a valid [Bill of Materials](./bom.md) defined.
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("BUILDORDER_REFERENCE_PATTERN") }}
+{{ globalsetting("BUILDORDER_REQUIRE_RESPONSIBLE") }}
+{{ globalsetting("BUILDORDER_REQUIRE_ACTIVE_PART") }}
+{{ globalsetting("BUILDORDER_REQUIRE_LOCKED_PART") }}
+{{ globalsetting("BUILDORDER_REQUIRE_VALID_BOM") }}
+{{ globalsetting("BUILDORDER_REQUIRE_CLOSED_CHILDS") }}
+{{ globalsetting("PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS") }}
diff --git a/docs/docs/order/purchase_order.md b/docs/docs/order/purchase_order.md
index fe39c2ab8f..19169c2298 100644
--- a/docs/docs/order/purchase_order.md
+++ b/docs/docs/order/purchase_order.md
@@ -139,3 +139,14 @@ This view can be accessed externally as an ICS calendar using a URL like the fol
`http://inventree.example.org/api/order/calendar/purchase-order/calendar.ics`
by default, completed orders are not exported. These can be included by appending `?include_completed=True` to the URL.
+
+## Purchase Order Settings
+
+The following [global settings](../settings/global.md) are available for purchase orders:
+
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("PURCHASEORDER_REFERENCE_PATTERN") }}
+{{ globalsetting("PURCHASEORDER_REQUIRE_RESPONSIBLE") }}
+{{ globalsetting("PURCHASEORDER_EDIT_COMPLETED_ORDERS") }}
+{{ globalsetting("PURCHASEORDER_AUTO_COMPLETE") }}
diff --git a/docs/docs/order/return_order.md b/docs/docs/order/return_order.md
index ad219c099b..cdbfba88b1 100644
--- a/docs/docs/order/return_order.md
+++ b/docs/docs/order/return_order.md
@@ -121,3 +121,14 @@ This view can be accessed externally as an ICS calendar using a URL like the fol
`http://inventree.example.org/api/order/calendar/return-order/calendar.ics`
by default, completed orders are not exported. These can be included by appending `?include_completed=True` to the URL.
+
+## Return Order Settings
+
+The following [global settings](../settings/global.md) are available for return orders:
+
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("RETURNORDER_ENABLED") }}
+{{ globalsetting("RETURNORDER_REFERENCE_PATTERN") }}
+{{ globalsetting("RETURNORDER_REQUIRE_RESPONSIBLE") }}
+{{ globalsetting("RETURNORDER_EDIT_COMPLETED_ORDERS") }}
diff --git a/docs/docs/order/sales_order.md b/docs/docs/order/sales_order.md
index 3d6f77dec0..e44666fb02 100644
--- a/docs/docs/order/sales_order.md
+++ b/docs/docs/order/sales_order.md
@@ -183,3 +183,15 @@ All these fields can be edited by the user:
{% with id="edit-shipment", url="order/edit_shipment.png", description="Edit shipment" %}
{% include "img.html" %}
{% endwith %}
+
+## Sales Order Settings
+
+The following [global settings](../settings/global.md) are available for sales orders:
+
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("SALESORDER_REFERENCE_PATTERN") }}
+{{ globalsetting("SALESORDER_REQUIRE_RESPONSIBLE") }}
+{{ globalsetting("SALESORDER_DEFAULT_SHIPMENT") }}
+{{ globalsetting("SALESORDER_EDIT_COMPLETED_ORDERS") }}
+{{ globalsetting("SALESORDER_SHIP_COMPLETE") }}
diff --git a/docs/docs/settings/currency.md b/docs/docs/settings/currency.md
index 4a2e7834f5..7368213afa 100644
--- a/docs/docs/settings/currency.md
+++ b/docs/docs/settings/currency.md
@@ -24,12 +24,7 @@ If a different currency exchange backend is needed, or a custom implementation i
### Currency Settings
-In the [settings screen](./global.md), under the *Pricing* section, the following currency settings are available:
-
-| Setting | Description | Default Value |
-| --- | --- |
-| Default Currency | The selected *default* currency for the system. | USD |
-| Supported Currencies | The list of supported currencies for the system. | AUD, CAD, CNY, EUR, GBP, JPY, NZD, USD |
+Refer to the [global settings](./global.md#pricing-and-currency) documentation for more information on available currency settings.
#### Supported Currencies
diff --git a/docs/docs/settings/global.md b/docs/docs/settings/global.md
index 967651e46d..7de1186167 100644
--- a/docs/docs/settings/global.md
+++ b/docs/docs/settings/global.md
@@ -17,120 +17,139 @@ Global settings are arranged in the following categories:
### Server Settings
-Configuration of basic server settings.
+Configuration of basic server settings:
+
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("INVENTREE_BASE_URL") }}
+{{ globalsetting("INVENTREE_COMPANY_NAME") }}
+{{ globalsetting("INVENTREE_INSTANCE") }}
+{{ globalsetting("INVENTREE_INSTANCE_TITLE") }}
+{{ globalsetting("INVENTREE_RESTRICT_ABOUT") }}
+{{ globalsetting("DISPLAY_FULL_NAMES") }}
+{{ globalsetting("INVENTREE_UPDATE_CHECK_INTERVAL") }}
+{{ globalsetting("INVENTREE_DOWNLOAD_FROM_URL") }}
+{{ globalsetting("INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE") }}
+{{ globalsetting("INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT") }}
+{{ globalsetting("INVENTREE_REQUIRE_CONFIRM") }}
+{{ globalsetting("INVENTREE_STRICT_URLS") }}
+{{ globalsetting("INVENTREE_TREE_DEPTH") }}
+{{ globalsetting("INVENTREE_BACKUP_ENABLE") }}
+{{ globalsetting("INVENTREE_BACKUP_DAYS") }}
+{{ globalsetting("INVENTREE_DELETE_TASKS_DAYS") }}
+{{ globalsetting("INVENTREE_DELETE_ERRORS_DAYS") }}
+{{ globalsetting("INVENTREE_DELETE_NOTIFICATIONS_DAYS") }}
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| InvenTree Instance Name | String | String descriptor for the InvenTree server instance | InvenTree Server |
-| Use Instance Name | Boolean | Use instance name in title bars | False |
-| Restrict showing `about` | Boolean | Show the `about` modal only to superusers | False |
-| Base URL | String | Base URL for server instance | *blank* |
-| Company Name | String | Company name | My company name |
-| Download from URL | Boolean | Allow downloading of images from remote URLs | False |
### Login Settings
-Change how logins, password-forgot, signups are handled.
+Change how logins, password-forgot, signups are handled:
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Enable registration | Boolean | Enable self-registration for users on the login-pages | False |
-| Enable SSO | Boolean | Enable SSO on the login-pages | False |
-| Enable SSO registration | Boolean | Enable self-registration for users via SSO on the login-pages | False |
-| Enable SSO group sync | Boolean | Enable synchronizing InvenTree groups directly from the IdP | False |
-| SSO group key | String | The name of the groups claim attribute provided by the IdP | |
-| SSO group map | String (JSON) | A mapping from SSO groups to local InvenTree groups | {} |
-| Remove groups outside of SSO | Boolean | Whether groups assigned to the user should be removed if they are not backend by the IdP. Disabling this setting might cause security issues | True |
-| Enable password forgot | Boolean | Enable password forgot function on the login-pages.
This will let users reset their passwords on their own. For this feature to work you need to configure E-mail | True |
-| E-Mail required | Boolean | Require user to supply e-mail on signup.
Without a way (e-mail) to contact the user notifications and security features might not work! | False |
-| Enforce MFA | Boolean | Users must use multifactor security.
This forces each user to setup MFA and use it on each authentication | False |
-| Mail twice | Boolean | On signup ask users twice for their mail | False |
-| Password twice | Boolean | On signup ask users twice for their password | True |
-| Auto-fill SSO users | Boolean | Automatically fill out user-details from SSO account-data.
If this feature is enabled the user is only asked for their username, first- and surname if those values can not be gathered from their SSO profile. This might lead to unwanted usernames bleeding over. | True |
-| Allowed domains | String | Restrict signup to certain domains (comma-separated, starting with @) | |
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("LOGIN_ENABLE_PWD_FORGOT") }}
+{{ globalsetting("LOGIN_MAIL_REQUIRED") }}
+{{ globalsetting("LOGIN_ENFORCE_MFA") }}
+{{ globalsetting("LOGIN_ENABLE_REG") }}
+{{ globalsetting("LOGIN_SIGNUP_MAIL_TWICE") }}
+{{ globalsetting("LOGIN_SIGNUP_PWD_TWICE") }}
+{{ globalsetting("SIGNUP_GROUP") }}
+{{ globalsetting("LOGIN_SIGNUP_MAIL_RESTRICTION") }}
+{{ globalsetting("LOGIN_ENABLE_SSO") }}
+{{ globalsetting("LOGIN_ENABLE_SSO_REG") }}
+{{ globalsetting("LOGIN_SIGNUP_SSO_AUTO") }}
+{{ globalsetting("LOGIN_ENABLE_SSO_GROUP_SYNC") }}
+{{ globalsetting("SSO_GROUP_MAP") }}
+{{ globalsetting("SSO_GROUP_KEY") }}
+{{ globalsetting("SSO_REMOVE_GROUPS") }}
+#### Require User Email
+
+If this setting is enabled, users must provide an email address when signing up. Note that some notification and security features require a valid email address.
+
+#### Forgot Password
+
+If this setting is enabled, users can reset their password via email. This requires a valid email address to be associated with the user account.
+
+#### Enforce Multi-Factor Authentication
+
+If this setting is enabled, users must have multi-factor authentication enabled to log in.
+
+#### Auto Fil SSO Users
+
+Automatically fill out user-details from SSO account-data. If this feature is enabled the user is only asked for their username, first- and surname if those values can not be gathered from their SSO profile. This might lead to unwanted usernames bleeding over.
### Barcodes
-Configuration of barcode functionality
+Configuration of barcode functionality:
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Barcode Support | Boolean | Enable barcode functionality in web interface | True |
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("BARCODE_ENABLE") }}
+{{ globalsetting("BARCODE_INPUT_DELAY") }}
+{{ globalsetting("BARCODE_WEBCAM_SUPPORT") }}
+{{ globalsetting("BARCODE_SHOW_TEXT") }}
+{{ globalsetting("BARCODE_GENERATION_PLUGIN") }}
-### Currencies
+### Pricing and Currency
-Configuration of currency support
+Configuration of pricing data and currency support:
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Default Currency | Currency | Default currency | USD |
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("INVENTREE_DEFAULT_CURRENCY") }}
+{{ globalsetting("CURRENCY_CODES") }}
+{{ globalsetting("PART_INTERNAL_PRICE") }}
+{{ globalsetting("PART_BOM_USE_INTERNAL_PRICE") }}
+{{ globalsetting("PRICING_DECIMAL_PLACES_MIN") }}
+{{ globalsetting("PRICING_DECIMAL_PLACES") }}
+{{ globalsetting("PRICING_UPDATE_DAYS") }}
+{{ globalsetting("PRICING_USE_SUPPLIER_PRICING") }}
+{{ globalsetting("PRICING_PURCHASE_HISTORY_OVERRIDES_SUPPLIER") }}
+{{ globalsetting("PRICING_USE_STOCK_PRICING") }}
+{{ globalsetting("PRICING_STOCK_ITEM_AGE_DAYS") }}
+{{ globalsetting("PRICING_USE_VARIANT_PRICING") }}
+{{ globalsetting("PRICING_ACTIVE_VARIANTS") }}
### Reporting
-Configuration of report generation
+Configuration of report generation:
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Enable Reports | Boolean | Enable report generation | False |
-| Page Size | String | Default page size | A4 |
-| Debug Mode | Boolean | Generate reports in debug mode (HTML output) | False |
-| Test Reports | Boolean | Enable generation of test reports | False |
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("REPORT_ENABLE") }}
+{{ globalsetting("REPORT_DEFAULT_PAGE_SIZE") }}
+{{ globalsetting("REPORT_DEBUG_MODE") }}
+{{ globalsetting("REPORT_LOG_ERRORS") }}
+{{ globalsetting("REPORT_ENABLE_TEST_REPORT") }}
+{{ globalsetting("REPORT_ATTACH_TEST_REPORT") }}
### Parts
-#### Main Settings
-
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| IPN Regex | String | Regular expression pattern for matching Part IPN | *blank* |
-| Allow Duplicate IPN | Boolean | Allow multiple parts to share the same IPN | True |
-| Allow Editing IPN | Boolean | Allow changing the IPN value while editing a part | True |
-| Part Name Display Format | String | Format to display the part name | {% raw %}`{{ part.id if part.id }}{{ ' | ' if part.id }}{{ part.name }}{{ ' | ' if part.revision }}{{ part.revision if part.revision }}`{% endraw %} |
-| Show Price History | Boolean | Display historical pricing for Part | False |
-| Show Price in Forms | Boolean | Display part price in some forms | True |
-| Show Price in BOM | Boolean | Include pricing information in BOM tables | True |
-| Show related parts | Boolean | Display related parts for a part | True |
-| Create initial stock | Boolean | Create initial stock on part creation | True |
-
-#### Creation Settings
-
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Template | Boolean | Parts are templates by default | False |
-| Assembly | Boolean | Parts can be assembled from other components by default | False |
-| Component | Boolean | Parts can be used as sub-components by default | True |
-| Trackable | Boolean | Parts are trackable by default | False |
-| Purchaseable | Boolean | Parts are purchaseable by default | True |
-| Salable | Boolean | Parts are salable by default | False |
-| Virtual | Boolean | Parts are virtual by default | False |
-
-#### Copy Settings
-
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Copy Part BOM Data | Boolean | Copy BOM data by default when duplicating a part | True |
-| Copy Part Parameter Data | Boolean | Copy parameter data by default when duplicating a part | True |
-| Copy Part Test Data | Boolean | Copy test data by default when duplicating a part | True |
-| Copy Category Parameter Templates | Boolean | Copy category parameter templates when creating a part | True |
-
-#### Internal Price Settings
-
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Internal Prices | Boolean | Enable internal prices for parts | False |
-| Internal Price as BOM-Price | Boolean | Use the internal price (if set) in BOM-price calculations | False |
-
-#### Part Import Setting
-
-This section of the part settings allows staff users to:
-
-- import parts to InvenTree clicking the Import Part button
-- enable the ["Import Parts" tab in the part category view](../part/part.md#part-import).
-
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Show Import in Views | Boolean | Display the import wizard in some part views | True |
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("PART_IPN_REGEX") }}
+{{ globalsetting("PART_ALLOW_DUPLICATE_IPN") }}
+{{ globalsetting("PART_ALLOW_EDIT_IPN") }}
+{{ globalsetting("PART_ALLOW_DELETE_FROM_ASSEMBLY") }}
+{{ globalsetting("PART_ENABLE_REVISION") }}
+{{ globalsetting("PART_REVISION_ASSEMBLY_ONLY") }}
+{{ globalsetting("PART_NAME_FORMAT") }}
+{{ globalsetting("PART_SHOW_RELATED") }}
+{{ globalsetting("PART_CREATE_INITIAL") }}
+{{ globalsetting("PART_CREATE_SUPPLIER") }}
+{{ globalsetting("PART_TEMPLATE") }}
+{{ globalsetting("PART_ASSEMBLY") }}
+{{ globalsetting("PART_COMPONENT") }}
+{{ globalsetting("PART_TRACKABLE") }}
+{{ globalsetting("PART_PURCHASEABLE") }}
+{{ globalsetting("PART_SALABLE") }}
+{{ globalsetting("PART_VIRTUAL") }}
+{{ globalsetting("PART_COPY_BOM") }}
+{{ globalsetting("PART_COPY_PARAMETERS") }}
+{{ globalsetting("PART_COPY_TESTS") }}
+{{ globalsetting("PART_CATEGORY_PARAMETERS") }}
+{{ globalsetting("PART_CATEGORY_DEFAULT_ICON") }}
#### Part Parameter Templates
@@ -153,45 +172,48 @@ After a list of parameters is added to a part category and upon creation of a ne
Configuration of stock item options
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Stock Expiry | Boolean | Enable stock expiry functionality | False |
-| Stock Stale Time | Days | Number of days stock items are considered stale before expiring | 90 |
-| Sell Expired Stock | Boolean | Allow sale of expired stock | False |
-| Build Expired Stock | Boolean | Allow building with expired stock | False |
-| Stock Ownership Control | Boolean | Enable ownership control functionality | False |
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("SERIAL_NUMBER_GLOBALLY_UNIQUE") }}
+{{ globalsetting("SERIAL_NUMBER_AUTOFILL") }}
+{{ globalsetting("STOCK_DELETE_DEPLETED_DEFAULT") }}
+{{ globalsetting("STOCK_BATCH_CODE_TEMPLATE") }}
+{{ globalsetting("STOCK_ENABLE_EXPIRY") }}
+{{ globalsetting("STOCK_STALE_DAYS") }}
+{{ globalsetting("STOCK_ALLOW_EXPIRED_SALE") }}
+{{ globalsetting("STOCK_ALLOW_EXPIRED_BUILD") }}
+{{ globalsetting("STOCK_OWNERSHIP_CONTROL") }}
+{{ globalsetting("STOCK_LOCATION_DEFAULT_ICON") }}
+{{ globalsetting("STOCK_SHOW_INSTALLED_ITEMS") }}
+{{ globalsetting("STOCK_ENFORCE_BOM_INSTALLATION") }}
+{{ globalsetting("STOCK_ALLOW_OUT_OF_STOCK_TRANSFER") }}
+{{ globalsetting("TEST_STATION_DATA") }}
### Build Orders
-Options for build orders
-
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Reference Pattern | String | Pattern for defining Build Order reference values | {% raw %}BO-{ref:04d}{% endraw %} |
+Refer to the [build order settings](../build/build.md#build-order-settings).
### Purchase Orders
-Options for purchase orders
+Refer to the [purchase order settings](../order/purchase_order.md#purchase-order-settings).
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Reference Pattern | String | Pattern for defining Purchase Order reference values | {% raw %}PO-{ref:04d}{% endraw %} |
+### Sales Orders
-### Sales orders
+Refer to the [sales order settings](../order/sales_order.md#sales-order-settings).
-Options for sales orders
+### Return Orders
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Reference Pattern | String | Pattern for defining Sales Order reference values | {% raw %}SO-{ref:04d}{% endraw %} |
+Refer to the [return order settings](../order/return_order.md#return-order-settings).
### Plugin Settings
-Change into what parts plugins can integrate into.
-| Setting | Type | Description | Default |
-| --- | --- | --- | --- |
-| Enable URL integration | Boolean | Enable plugins to add URL routes | False |
-| Enable navigation integration | Boolean | Enable plugins to integrate into navigation | False |
-| Enable setting integration | Boolean | Enable plugins to integrate into inventree settings | False |
-| Enable app integration | Boolean | Enable plugins to add apps | False |
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ globalsetting("PLUGIN_ON_STARTUP") }}
+{{ globalsetting("PLUGIN_UPDATE_CHECK") }}
+{{ globalsetting("ENABLE_PLUGINS_URL") }}
+{{ globalsetting("ENABLE_PLUGINS_NAVIGATION") }}
+{{ globalsetting("ENABLE_PLUGINS_APP") }}
+{{ globalsetting("ENABLE_PLUGINS_SCHEDULE") }}
+{{ globalsetting("ENABLE_PLUGINS_EVENTS") }}
diff --git a/docs/docs/settings/user.md b/docs/docs/settings/user.md
index c9f74b97ed..fdb6cb0aca 100644
--- a/docs/docs/settings/user.md
+++ b/docs/docs/settings/user.md
@@ -32,24 +32,44 @@ This screen allows the user to customize display of items on the InvenTree home
### Search Settings
-Customize settings for search results
+Customize settings for search results:
-{% with id="user-search", url="settings/user_search.png", description="User Search Settings" %}
-{% include 'img.html' %}
-{% endwith %}
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ usersetting("SEARCH_WHOLE") }}
+{{ usersetting("SEARCH_REGEX") }}
+{{ usersetting("SEARCH_PREVIEW_RESULTS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_PARTS") }}
+{{ usersetting("SEARCH_HIDE_INACTIVE_PARTS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_CATEGORIES") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_STOCK") }}
+{{ usersetting("SEARCH_PREVIEW_HIDE_UNAVAILABLE_STOCK") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_LOCATIONS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_COMPANIES") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_BUILD_ORDERS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS") }}
+{{ usersetting("SEARCH_PREVIEW_EXCLUDE_INACTIVE_PURCHASE_ORDERS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_SALES_ORDERS") }}
+{{ usersetting("SEARCH_PREVIEW_EXCLUDE_INACTIVE_SALES_ORDERS") }}
+{{ usersetting("SEARCH_PREVIEW_SHOW_RETURN_ORDERS") }}
+{{ usersetting("SEARCH_PREVIEW_EXCLUDE_INACTIVE_RETURN_ORDERS") }}
### Notifications
-Settings related to notification messages
+Settings related to notification messages:
-{% with id="user-notification", url="settings/user_notifications.png", description="User Notification Settings" %}
-{% include 'img.html' %}
-{% endwith %}
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ usersetting("NOTIFICATION_ERROR_REPORT") }}
### Reporting
-Settings for label printing and report generation
+Settings for label printing and report generation:
-{% with id="user-reporting", url="settings/user_reporting.png", description="User Reporting Settings" %}
-{% include 'img.html' %}
-{% endwith %}
+| Name | Description | Default | Units |
+| ---- | ----------- | ------- | ----- |
+{{ usersetting("REPORT_INLINE") }}
+{{ usersetting("LABEL_INLINE") }}
+{{ usersetting("LABEL_DEFAULT_PRINTER") }}
diff --git a/docs/main.py b/docs/main.py
index 11066c4057..b09b8b0da2 100644
--- a/docs/main.py
+++ b/docs/main.py
@@ -1,5 +1,6 @@
"""Main entry point for the documentation build process."""
+import json
import os
import subprocess
import textwrap
@@ -7,6 +8,20 @@ import textwrap
import requests
import yaml
+# Cached settings dict values
+global GLOBAL_SETTINGS
+global USER_SETTINGS
+
+# Read in the InvenTree settings file
+here = os.path.dirname(__file__)
+settings_file = os.path.join(here, 'inventree_settings.json')
+
+with open(settings_file, 'r') as sf:
+ settings = json.load(sf)
+
+ GLOBAL_SETTINGS = settings['global']
+ USER_SETTINGS = settings['user']
+
def get_repo_url(raw=False):
"""Return the repository URL for the current project."""
@@ -219,3 +234,37 @@ def define_env(env):
)
return includefile(fn, f'Template: {base}', format='html')
+
+ @env.macro
+ def rendersetting(setting: dict):
+ """Render a provided setting object into a table row."""
+ name = setting['name']
+ description = setting['description']
+ default = setting.get('default', None)
+ units = setting.get('units', None)
+
+ return f'| {name} | {description} | {default if default is not None else ""} | {units if units is not None else ""} |'
+
+ @env.macro
+ def globalsetting(key: str):
+ """Extract information on a particular global setting.
+
+ Arguments:
+ - key: The name of the global setting to extract information for.
+ """
+ global GLOBAL_SETTINGS
+ setting = GLOBAL_SETTINGS[key]
+
+ return rendersetting(setting)
+
+ @env.macro
+ def usersetting(key: str):
+ """Extract information on a particular user setting.
+
+ Arguments:
+ - key: The name of the user setting to extract information for.
+ """
+ global USER_SETTINGS
+ setting = USER_SETTINGS[key]
+
+ return rendersetting(setting)
diff --git a/readthedocs.yml b/readthedocs.yml
index 003ca95426..f9a08ce2a9 100644
--- a/readthedocs.yml
+++ b/readthedocs.yml
@@ -17,5 +17,6 @@ build:
- echo "Generating API schema file"
- pip install -U invoke
- invoke migrate
+ - invoke export-settings-definitions --filename docs/inventree_settings.json --overwrite
- invoke schema --filename docs/schema.yml --ignore-warnings
- python docs/extract_schema.py docs/schema.yml
diff --git a/src/backend/InvenTree/InvenTree/management/commands/export_settings_definitions.py b/src/backend/InvenTree/InvenTree/management/commands/export_settings_definitions.py
new file mode 100644
index 0000000000..8587e936d0
--- /dev/null
+++ b/src/backend/InvenTree/InvenTree/management/commands/export_settings_definitions.py
@@ -0,0 +1,53 @@
+"""Custom management command to export settings definitions.
+
+This is used to generate a JSON file which contains all of the settings,
+so that they can be introspected by the InvenTree documentation system.
+
+This in turn allows settings to be documented in the InvenTree documentation,
+without having to manually duplicate the information in multiple places.
+"""
+
+import json
+
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+ """Extract settings information, and export to a JSON file."""
+
+ def add_arguments(self, parser):
+ """Add custom arguments for this command."""
+ parser.add_argument(
+ 'filename', type=str, help='Output filename for settings definitions'
+ )
+
+ def handle(self, *args, **kwargs):
+ """Export settings information to a JSON file."""
+ from common.models import InvenTreeSetting, InvenTreeUserSetting
+
+ settings = {'global': {}, 'user': {}}
+
+ # Global settings
+ for key, setting in InvenTreeSetting.SETTINGS.items():
+ settings['global'][key] = {
+ 'name': str(setting['name']),
+ 'description': str(setting['description']),
+ 'default': str(InvenTreeSetting.get_setting_default(key)),
+ 'units': str(setting.get('units', '')),
+ }
+
+ # User settings
+ for key, setting in InvenTreeUserSetting.SETTINGS.items():
+ settings['user'][key] = {
+ 'name': str(setting['name']),
+ 'description': str(setting['description']),
+ 'default': str(InvenTreeUserSetting.get_setting_default(key)),
+ 'units': str(setting.get('units', '')),
+ }
+
+ filename = kwargs.get('filename', 'inventree_settings.json')
+
+ with open(filename, 'w') as f:
+ json.dump(settings, f, indent=4)
+
+ print(f"Exported InvenTree settings definitions to '{filename}'")
diff --git a/src/backend/InvenTree/build/models.py b/src/backend/InvenTree/build/models.py
index eb0449ea94..adde7080da 100644
--- a/src/backend/InvenTree/build/models.py
+++ b/src/backend/InvenTree/build/models.py
@@ -395,9 +395,9 @@ class Build(
def sub_builds(self, cascade=True):
"""Return all Build Order objects under this one."""
if cascade:
- return Build.objects.filter(parent=self.pk)
- descendants = self.get_descendants(include_self=True)
- Build.objects.filter(parent__pk__in=[d.pk for d in descendants])
+ return self.get_descendants(include_self=False)
+ else:
+ return self.get_children()
def sub_build_count(self, cascade=True):
"""Return the number of sub builds under this one.
@@ -407,6 +407,11 @@ class Build(
"""
return self.sub_builds(cascade=cascade).count()
+ @property
+ def has_open_child_builds(self):
+ """Return True if this build order has any open child builds."""
+ return self.sub_builds().filter(status__in=BuildStatusGroups.ACTIVE_CODES).exists()
+
@property
def is_overdue(self):
"""Returns true if this build is "overdue".
@@ -576,6 +581,9 @@ class Build(
- Untracked parts must be allocated
"""
+ if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and self.has_open_child_builds:
+ return False
+
if self.status != BuildStatus.PRODUCTION.value:
return False
@@ -619,6 +627,10 @@ class Build(
trim_allocated_stock = kwargs.pop('trim_allocated_stock', False)
user = kwargs.pop('user', None)
+ # Prevent completion if there are open child builds
+ if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and self.has_open_child_builds:
+ return
+
if self.incomplete_count > 0:
return
@@ -974,7 +986,10 @@ class Build(
items_to_save = []
items_to_delete = []
- for build_line in self.untracked_line_items:
+ lines = self.untracked_line_items
+ lines = lines.prefetch_related('allocations')
+
+ for build_line in lines:
reduce_by = build_line.allocated_quantity() - build_line.quantity
diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py
index 38f6b43adf..ef73686a9b 100644
--- a/src/backend/InvenTree/build/serializers.py
+++ b/src/backend/InvenTree/build/serializers.py
@@ -27,6 +27,7 @@ from stock.serializers import StockItemSerializerBrief, LocationBriefSerializer
import common.models
from common.serializers import ProjectCodeSerializer
+from common.settings import get_global_setting
from importer.mixins import DataImportExportSerializerMixin
import company.serializers
import part.filters
@@ -765,6 +766,9 @@ class BuildCompleteSerializer(serializers.Serializer):
"""Perform validation of this serializer prior to saving"""
build = self.context['build']
+ if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and build.has_open_child_builds:
+ raise ValidationError(_("Build order has open child build orders"))
+
if build.status != BuildStatus.PRODUCTION.value:
raise ValidationError(_("Build order must be in production state"))
diff --git a/src/backend/InvenTree/build/test_api.py b/src/backend/InvenTree/build/test_api.py
index acb75c25f3..518d4c4098 100644
--- a/src/backend/InvenTree/build/test_api.py
+++ b/src/backend/InvenTree/build/test_api.py
@@ -1015,7 +1015,7 @@ class BuildOverallocationTest(BuildAPITest):
'accept_overallocated': 'trim',
},
expected_code=201,
- max_query_count=555, # TODO: Come back and refactor this
+ max_query_count=600, # TODO: Come back and refactor this
)
self.build.refresh_from_db()
diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py
index 46e38b03d8..9b34a4c37b 100644
--- a/src/backend/InvenTree/common/models.py
+++ b/src/backend/InvenTree/common/models.py
@@ -1838,6 +1838,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False,
'validator': bool,
},
+ 'BUILDORDER_REQUIRE_CLOSED_CHILDS': {
+ 'name': _('Require Closed Child Orders'),
+ 'description': _(
+ 'Prevent build order completion until all child orders are closed'
+ ),
+ 'default': False,
+ 'validator': bool,
+ },
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
'name': _('Block Until Tests Pass'),
'description': _(
diff --git a/src/backend/InvenTree/templates/InvenTree/settings/build.html b/src/backend/InvenTree/templates/InvenTree/settings/build.html
index 9eb1ce47bb..cce783b372 100644
--- a/src/backend/InvenTree/templates/InvenTree/settings/build.html
+++ b/src/backend/InvenTree/templates/InvenTree/settings/build.html
@@ -17,6 +17,7 @@
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_ACTIVE_PART" %}
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_LOCKED_PART" %}
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_VALID_BOM" %}
+ {% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_CLOSED_CHILDS" %}
{% include "InvenTree/settings/setting.html" with key="PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS" %}
diff --git a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx
index 0db6114592..fe4d7a49e4 100644
--- a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx
+++ b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx
@@ -244,6 +244,7 @@ export default function SystemSettings() {
'BUILDORDER_REQUIRE_ACTIVE_PART',
'BUILDORDER_REQUIRE_LOCKED_PART',
'BUILDORDER_REQUIRE_VALID_BOM',
+ 'BUILDORDER_REQUIRE_CLOSED_CHILDS',
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
]}
/>
diff --git a/tasks.py b/tasks.py
index 57c2ceed30..b665daec7a 100644
--- a/tasks.py
+++ b/tasks.py
@@ -1074,6 +1074,16 @@ def schema(
print('Schema export completed:', filename)
+@task
+def export_settings_definitions(c, filename='inventree_settings.json', overwrite=False):
+ """Export settings definition to a JSON file."""
+ filename = Path(filename).resolve()
+ check_file_existance(filename, overwrite)
+
+ print(f"Exporting settings definition to '{filename}'...")
+ manage(c, f'export_settings_definitions {filename}', pty=True)
+
+
@task(default=True)
def version(c):
"""Show the current version of InvenTree."""
@@ -1406,6 +1416,11 @@ via your signed in browser, or consider using a point release download via invok
)
def docs_server(c, address='localhost:8080', compile_schema=False):
"""Start a local mkdocs server to view the documentation."""
+ # Extract settings definitions
+ export_settings_definitions(
+ c, filename='docs/inventree_settings.json', overwrite=True
+ )
+
if compile_schema:
# Build the schema docs first
schema(c, ignore_warnings=True, overwrite=True, filename='docs/schema.yml')