mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-28 15:04:05 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
@@ -134,7 +134,7 @@ class InvenTreeAPITestCase(UserMixin, APITestCase):
|
||||
if expected_code is not None:
|
||||
|
||||
if response.status_code != expected_code:
|
||||
print(f"Unexpected response at '{url}':")
|
||||
print(f"Unexpected response at '{url}': status_code = {response.status_code}")
|
||||
print(response.data)
|
||||
|
||||
self.assertEqual(response.status_code, expected_code)
|
||||
@@ -143,11 +143,13 @@ class InvenTreeAPITestCase(UserMixin, APITestCase):
|
||||
|
||||
def post(self, url, data=None, expected_code=None, format='json'):
|
||||
"""Issue a POST request."""
|
||||
response = self.client.post(url, data=data, format=format)
|
||||
|
||||
# Set default value - see B006
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
response = self.client.post(url, data=data, format=format)
|
||||
|
||||
if expected_code is not None:
|
||||
|
||||
if response.status_code != expected_code:
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 78
|
||||
INVENTREE_API_VERSION = 79
|
||||
|
||||
"""
|
||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||
|
||||
v79 -> 2022-11-03 : https://github.com/inventree/InvenTree/pull/3895
|
||||
- Add metadata to Company
|
||||
|
||||
v78 -> 2022-10-25 : https://github.com/inventree/InvenTree/pull/3854
|
||||
- Make PartCategory to be filtered by name and description
|
||||
|
||||
|
||||
@@ -127,13 +127,6 @@ function inventreeDocReady() {
|
||||
loadBrandIcon($(this), $(this).attr('brand_name'));
|
||||
});
|
||||
|
||||
// Callback for "admin view" button
|
||||
$('#admin-button, .admin-button').click(function() {
|
||||
var url = $(this).attr('url');
|
||||
|
||||
location.href = url;
|
||||
});
|
||||
|
||||
// Display any cached alert messages
|
||||
showCachedAlerts();
|
||||
|
||||
|
||||
@@ -178,11 +178,15 @@ class APITests(InvenTreeAPITestCase):
|
||||
def test_with_roles(self):
|
||||
"""Assign some roles to the user."""
|
||||
self.basicAuth()
|
||||
response = self.get(reverse('api-user-roles'))
|
||||
|
||||
url = reverse('api-user-roles')
|
||||
|
||||
response = self.get(url)
|
||||
|
||||
self.assignRole('part.delete')
|
||||
self.assignRole('build.change')
|
||||
response = self.get(reverse('api-user-roles'))
|
||||
|
||||
response = self.get(url)
|
||||
|
||||
roles = response.data['roles']
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ class BuildTest(BuildAPITest):
|
||||
"location": 1,
|
||||
"status": 50, # Item requires attention
|
||||
},
|
||||
expected_code=201
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
self.assertEqual(self.build.incomplete_outputs.count(), 0)
|
||||
|
||||
@@ -933,6 +933,17 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
||||
'validator': bool,
|
||||
},
|
||||
|
||||
'BARCODE_INPUT_DELAY': {
|
||||
'name': _('Barcode Input Delay'),
|
||||
'description': _('Barcode input processing delay time'),
|
||||
'default': 50,
|
||||
'validator': [
|
||||
int,
|
||||
MinValueValidator(1),
|
||||
],
|
||||
'units': 'ms',
|
||||
},
|
||||
|
||||
'BARCODE_WEBCAM_SUPPORT': {
|
||||
'name': _('Barcode Webcam Support'),
|
||||
'description': _('Allow barcode scanning via webcam in browser'),
|
||||
@@ -1321,7 +1332,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
||||
|
||||
'PLUGIN_ON_STARTUP': {
|
||||
'name': _('Check plugins on startup'),
|
||||
'description': _('Check that all plugins are installed on startup - enable in container enviroments'),
|
||||
'description': _('Check that all plugins are installed on startup - enable in container environments'),
|
||||
'default': False,
|
||||
'validator': bool,
|
||||
'requires_restart': True,
|
||||
|
||||
@@ -10,7 +10,9 @@ from rest_framework import filters
|
||||
from InvenTree.api import AttachmentMixin, ListCreateDestroyAPIView
|
||||
from InvenTree.filters import InvenTreeOrderingFilter
|
||||
from InvenTree.helpers import str2bool
|
||||
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateDestroyAPI
|
||||
from InvenTree.mixins import (ListCreateAPI, RetrieveUpdateAPI,
|
||||
RetrieveUpdateDestroyAPI)
|
||||
from plugin.serializers import MetadataSerializer
|
||||
|
||||
from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
|
||||
ManufacturerPartParameter, SupplierPart,
|
||||
@@ -83,6 +85,16 @@ class CompanyDetail(RetrieveUpdateDestroyAPI):
|
||||
return queryset
|
||||
|
||||
|
||||
class CompanyMetadata(RetrieveUpdateAPI):
|
||||
"""API endpoint for viewing / updating Company metadata."""
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
"""Return MetadataSerializer instance for a Company"""
|
||||
return MetadataSerializer(Company, *args, **kwargs)
|
||||
|
||||
queryset = Company.objects.all()
|
||||
|
||||
|
||||
class ManufacturerPartFilter(rest_filters.FilterSet):
|
||||
"""Custom API filters for the ManufacturerPart list endpoint."""
|
||||
|
||||
@@ -460,7 +472,11 @@ company_api_urls = [
|
||||
re_path(r'^.*$', SupplierPriceBreakList.as_view(), name='api-part-supplier-price-list'),
|
||||
])),
|
||||
|
||||
re_path(r'^(?P<pk>\d+)/?', CompanyDetail.as_view(), name='api-company-detail'),
|
||||
re_path(r'^(?P<pk>\d+)/?', include([
|
||||
re_path(r'^metadata/', CompanyMetadata.as_view(), name='api-company-metadata'),
|
||||
re_path(r'^.*$', CompanyDetail.as_view(), name='api-company-detail'),
|
||||
])),
|
||||
|
||||
re_path(r'^.*$', CompanyList.as_view(), name='api-company-list'),
|
||||
|
||||
]
|
||||
|
||||
18
InvenTree/company/migrations/0049_company_metadata.py
Normal file
18
InvenTree/company/migrations/0049_company_metadata.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.16 on 2022-11-02 17:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('company', '0048_auto_20220913_0312'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='company',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
]
|
||||
@@ -23,6 +23,7 @@ from common.settings import currency_code_default
|
||||
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
|
||||
from InvenTree.models import InvenTreeAttachment, InvenTreeBarcodeMixin
|
||||
from InvenTree.status_codes import PurchaseOrderStatus
|
||||
from plugin.models import MetadataMixin
|
||||
|
||||
|
||||
def rename_company_image(instance, filename):
|
||||
@@ -50,7 +51,7 @@ def rename_company_image(instance, filename):
|
||||
return os.path.join(base, fn)
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
class Company(MetadataMixin, models.Model):
|
||||
"""A Company object represents an external company.
|
||||
|
||||
It may be a supplier or a customer or a manufacturer (or a combination)
|
||||
|
||||
@@ -67,9 +67,7 @@ class LabelPrintMixin:
|
||||
plugin = registry.get_plugin(plugin_key)
|
||||
|
||||
if plugin:
|
||||
config = plugin.plugin_config()
|
||||
|
||||
if config and config.active:
|
||||
if plugin.is_active():
|
||||
# Only return the plugin if it is enabled!
|
||||
return plugin
|
||||
else:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-10-25 10:54+0000\n"
|
||||
"POT-Creation-Date: 2022-10-28 07:18+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -59,35 +59,35 @@ msgstr ""
|
||||
msgid "Provided value does not match required pattern: "
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:133
|
||||
#: InvenTree/forms.py:134
|
||||
msgid "Enter password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:134
|
||||
#: InvenTree/forms.py:135
|
||||
msgid "Enter new password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:143
|
||||
#: InvenTree/forms.py:144
|
||||
msgid "Confirm password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:144
|
||||
#: InvenTree/forms.py:145
|
||||
msgid "Confirm new password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:148
|
||||
#: InvenTree/forms.py:149
|
||||
msgid "Old password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:177
|
||||
#: InvenTree/forms.py:178
|
||||
msgid "Email (again)"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:181
|
||||
#: InvenTree/forms.py:182
|
||||
msgid "Email address confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:202
|
||||
#: InvenTree/forms.py:203
|
||||
msgid "You must type the same email each time."
|
||||
msgstr ""
|
||||
|
||||
@@ -131,30 +131,35 @@ msgstr ""
|
||||
msgid "Empty serial number string"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:633
|
||||
#: InvenTree/helpers.py:640
|
||||
msgid "Duplicate serial"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:662
|
||||
#: InvenTree/helpers.py:673 InvenTree/helpers.py:708
|
||||
#, python-brace-format
|
||||
msgid "Invalid group range: {g}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:714 InvenTree/helpers.py:721 InvenTree/helpers.py:736
|
||||
#: InvenTree/helpers.py:702
|
||||
#, python-brace-format
|
||||
msgid "Group range {g} exceeds allowed quantity ({q})"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:726 InvenTree/helpers.py:733 InvenTree/helpers.py:748
|
||||
#, python-brace-format
|
||||
msgid "Invalid group sequence: {g}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:746
|
||||
#: InvenTree/helpers.py:758
|
||||
msgid "No serial numbers found"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:749
|
||||
#: InvenTree/helpers.py:761
|
||||
#, python-brace-format
|
||||
msgid "Number of unique serial numbers ({s}) must match quantity ({q})"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:948
|
||||
#: InvenTree/helpers.py:960
|
||||
msgid "Remove HTML tags from this value"
|
||||
msgstr ""
|
||||
|
||||
@@ -224,9 +229,9 @@ msgstr ""
|
||||
msgid "File comment"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1726
|
||||
#: common/models.py:1727 common/models.py:1950 common/models.py:1951
|
||||
#: common/models.py:2213 common/models.py:2214 part/models.py:2254
|
||||
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1733
|
||||
#: common/models.py:1734 common/models.py:1957 common/models.py:1958
|
||||
#: common/models.py:2220 common/models.py:2221 part/models.py:2254
|
||||
#: part/models.py:2274 plugin/models.py:260 plugin/models.py:261
|
||||
#: report/templates/report/inventree_test_report_base.html:96
|
||||
#: templates/js/translated/stock.js:2649
|
||||
@@ -266,7 +271,7 @@ msgstr ""
|
||||
msgid "Invalid choice"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1936
|
||||
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1943
|
||||
#: company/models.py:358 label/models.py:101 part/models.py:760
|
||||
#: part/models.py:2432 plugin/models.py:94 report/models.py:152
|
||||
#: templates/InvenTree/settings/mixins/urls.html:13
|
||||
@@ -678,24 +683,24 @@ msgstr ""
|
||||
msgid "Production"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:19
|
||||
#: InvenTree/validators.py:20
|
||||
msgid "Not a valid currency code"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:90
|
||||
#: InvenTree/validators.py:91
|
||||
#, python-brace-format
|
||||
msgid "IPN must match regex pattern {pat}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:132 InvenTree/validators.py:148
|
||||
#: InvenTree/validators.py:133 InvenTree/validators.py:149
|
||||
msgid "Overage value must not be negative"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:150
|
||||
#: InvenTree/validators.py:151
|
||||
msgid "Overage must not exceed 100%"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:157
|
||||
#: InvenTree/validators.py:158
|
||||
msgid "Invalid value for overage"
|
||||
msgstr ""
|
||||
|
||||
@@ -746,7 +751,8 @@ msgstr ""
|
||||
#: order/templates/order/so_sidebar.html:13
|
||||
#: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221
|
||||
#: templates/InvenTree/search.html:141
|
||||
#: templates/InvenTree/settings/sidebar.html:47 users/models.py:41
|
||||
#: templates/InvenTree/settings/sidebar.html:47
|
||||
#: templates/js/translated/search.js:254 users/models.py:41
|
||||
msgid "Build Orders"
|
||||
msgstr ""
|
||||
|
||||
@@ -1010,7 +1016,7 @@ msgstr ""
|
||||
|
||||
#: build/models.py:1359 build/serializers.py:192
|
||||
#: build/templates/build/build_base.html:85
|
||||
#: build/templates/build/detail.html:34 common/models.py:1758
|
||||
#: build/templates/build/detail.html:34 common/models.py:1765
|
||||
#: company/templates/company/supplier_part.html:341 order/models.py:911
|
||||
#: order/models.py:1437 order/serializers.py:1213
|
||||
#: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40
|
||||
@@ -2321,7 +2327,7 @@ msgstr ""
|
||||
msgid "Enable plugins to respond to internal events"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1391 common/models.py:1719
|
||||
#: common/models.py:1391 common/models.py:1726
|
||||
msgid "Settings key (must be unique - case insensitive"
|
||||
msgstr ""
|
||||
|
||||
@@ -2558,120 +2564,128 @@ msgid "Display companies in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1616
|
||||
msgid "Search Purchase Orders"
|
||||
msgid "Search Build Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1617
|
||||
msgid "Display purchase orders in search preview window"
|
||||
msgid "Display build orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1623
|
||||
msgid "Exclude Inactive Purchase Orders"
|
||||
msgid "Search Purchase Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1624
|
||||
msgid "Exclude inactive purchase orders from search preview window"
|
||||
msgid "Display purchase orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1630
|
||||
msgid "Search Sales Orders"
|
||||
msgid "Exclude Inactive Purchase Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1631
|
||||
msgid "Display sales orders in search preview window"
|
||||
msgid "Exclude inactive purchase orders from search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1637
|
||||
msgid "Exclude Inactive Sales Orders"
|
||||
msgid "Search Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1638
|
||||
msgid "Exclude inactive sales orders from search preview window"
|
||||
msgid "Display sales orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1644
|
||||
msgid "Search Preview Results"
|
||||
msgid "Exclude Inactive Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1645
|
||||
msgid "Number of results to show in each section of the search preview window"
|
||||
msgid "Exclude inactive sales orders from search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1651
|
||||
msgid "Show Quantity in Forms"
|
||||
msgid "Search Preview Results"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1652
|
||||
msgid "Display available part quantity in some forms"
|
||||
msgid "Number of results to show in each section of the search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1658
|
||||
msgid "Escape Key Closes Forms"
|
||||
msgid "Show Quantity in Forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1659
|
||||
msgid "Use the escape key to close modal forms"
|
||||
msgid "Display available part quantity in some forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1665
|
||||
msgid "Fixed Navbar"
|
||||
msgid "Escape Key Closes Forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1666
|
||||
msgid "The navbar position is fixed to the top of the screen"
|
||||
msgid "Use the escape key to close modal forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1672
|
||||
msgid "Date Format"
|
||||
msgid "Fixed Navbar"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1673
|
||||
msgid "The navbar position is fixed to the top of the screen"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1679
|
||||
msgid "Date Format"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1680
|
||||
msgid "Preferred format for displaying dates"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1687 part/templates/part/detail.html:41
|
||||
#: common/models.py:1694 part/templates/part/detail.html:41
|
||||
msgid "Part Scheduling"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1688
|
||||
#: common/models.py:1695
|
||||
msgid "Display part scheduling information"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1694
|
||||
#: common/models.py:1701
|
||||
msgid "Table String Length"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1695
|
||||
#: common/models.py:1702
|
||||
msgid "Maximimum length limit for strings displayed in table views"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1759
|
||||
#: common/models.py:1766
|
||||
msgid "Price break quantity"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1766 company/serializers.py:372
|
||||
#: common/models.py:1773 company/serializers.py:372
|
||||
#: company/templates/company/supplier_part.html:346 order/models.py:952
|
||||
#: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1767
|
||||
#: common/models.py:1774
|
||||
msgid "Unit price at specified quantity"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1927 common/models.py:2105
|
||||
#: common/models.py:1934 common/models.py:2112
|
||||
msgid "Endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1928
|
||||
#: common/models.py:1935
|
||||
msgid "Endpoint at which this webhook is received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1937
|
||||
#: common/models.py:1944
|
||||
msgid "Name for this webhook"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1942 part/models.py:935 plugin/models.py:100
|
||||
#: common/models.py:1949 part/models.py:935 plugin/models.py:100
|
||||
#: templates/js/translated/table_filters.js:34
|
||||
#: templates/js/translated/table_filters.js:112
|
||||
#: templates/js/translated/table_filters.js:324
|
||||
@@ -2679,67 +2693,67 @@ msgstr ""
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1943
|
||||
#: common/models.py:1950
|
||||
msgid "Is this webhook active"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1957
|
||||
#: common/models.py:1964
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1958
|
||||
#: common/models.py:1965
|
||||
msgid "Token for access"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1965
|
||||
#: common/models.py:1972
|
||||
msgid "Secret"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1966
|
||||
#: common/models.py:1973
|
||||
msgid "Shared secret for HMAC"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2072
|
||||
#: common/models.py:2079
|
||||
msgid "Message ID"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2073
|
||||
#: common/models.py:2080
|
||||
msgid "Unique identifier for this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2081
|
||||
#: common/models.py:2088
|
||||
msgid "Host"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2082
|
||||
#: common/models.py:2089
|
||||
msgid "Host from which this message was received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2089
|
||||
#: common/models.py:2096
|
||||
msgid "Header"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2090
|
||||
#: common/models.py:2097
|
||||
msgid "Header of this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2096
|
||||
#: common/models.py:2103
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2097
|
||||
#: common/models.py:2104
|
||||
msgid "Body of this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2106
|
||||
#: common/models.py:2113
|
||||
msgid "Endpoint on which this message was received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2111
|
||||
#: common/models.py:2118
|
||||
msgid "Worked on"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2112
|
||||
#: common/models.py:2119
|
||||
msgid "Was the work on this message finished?"
|
||||
msgstr ""
|
||||
|
||||
@@ -3228,7 +3242,7 @@ msgstr ""
|
||||
#: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37
|
||||
#: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200
|
||||
#: templates/InvenTree/settings/sidebar.html:49
|
||||
#: templates/js/translated/search.js:277 templates/navbar.html:50
|
||||
#: templates/js/translated/search.js:293 templates/navbar.html:50
|
||||
#: users/models.py:42
|
||||
msgid "Purchase Orders"
|
||||
msgstr ""
|
||||
@@ -3251,7 +3265,7 @@ msgstr ""
|
||||
#: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41
|
||||
#: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220
|
||||
#: templates/InvenTree/settings/sidebar.html:51
|
||||
#: templates/js/translated/search.js:301 templates/navbar.html:61
|
||||
#: templates/js/translated/search.js:317 templates/navbar.html:61
|
||||
#: users/models.py:43
|
||||
msgid "Sales Orders"
|
||||
msgstr ""
|
||||
@@ -3321,7 +3335,7 @@ msgstr ""
|
||||
#: company/templates/company/manufacturer_part.html:136
|
||||
#: company/templates/company/manufacturer_part.html:183
|
||||
#: part/templates/part/detail.html:371 part/templates/part/detail.html:401
|
||||
#: templates/js/translated/forms.js:458 templates/js/translated/helpers.js:36
|
||||
#: templates/js/translated/forms.js:455 templates/js/translated/helpers.js:36
|
||||
#: users/models.py:222
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
@@ -3570,7 +3584,7 @@ msgstr ""
|
||||
msgid "New Customer"
|
||||
msgstr ""
|
||||
|
||||
#: company/views.py:52 templates/js/translated/search.js:254
|
||||
#: company/views.py:52 templates/js/translated/search.js:270
|
||||
msgid "Companies"
|
||||
msgstr ""
|
||||
|
||||
@@ -4379,47 +4393,47 @@ msgstr ""
|
||||
msgid "Updated {part} unit-price to {price} and quantity to {qty}"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:514
|
||||
#: part/api.py:516
|
||||
msgid "Incoming Purchase Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:534
|
||||
#: part/api.py:536
|
||||
msgid "Outgoing Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:552
|
||||
#: part/api.py:554
|
||||
msgid "Stock produced by Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:638
|
||||
#: part/api.py:640
|
||||
msgid "Stock required for Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:775
|
||||
#: part/api.py:777
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:776
|
||||
#: part/api.py:778
|
||||
msgid "Validate entire Bill of Materials"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:782
|
||||
#: part/api.py:784
|
||||
msgid "This option must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1205
|
||||
#: part/api.py:1207
|
||||
msgid "Must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1209
|
||||
#: part/api.py:1211
|
||||
msgid "Must be a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1224
|
||||
#: part/api.py:1226
|
||||
msgid "Specify location for initial part stock"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1255 part/api.py:1259 part/api.py:1274 part/api.py:1278
|
||||
#: part/api.py:1257 part/api.py:1261 part/api.py:1276 part/api.py:1280
|
||||
msgid "This field is required"
|
||||
msgstr ""
|
||||
|
||||
@@ -5755,23 +5769,23 @@ msgstr ""
|
||||
msgid "No matching action found"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:52 plugin/base/barcodes/api.py:110
|
||||
msgid "Must provide barcode_data parameter"
|
||||
#: plugin/base/barcodes/api.py:54 plugin/base/barcodes/api.py:113
|
||||
msgid "Missing barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:82
|
||||
#: plugin/base/barcodes/api.py:83
|
||||
msgid "No match found for barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:86
|
||||
#: plugin/base/barcodes/api.py:87
|
||||
msgid "Match found for barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:125
|
||||
#: plugin/base/barcodes/api.py:126
|
||||
msgid "Barcode matches existing item"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:222
|
||||
#: plugin/base/barcodes/api.py:223
|
||||
msgid "No match found for provided value"
|
||||
msgstr ""
|
||||
|
||||
@@ -7603,7 +7617,7 @@ msgstr ""
|
||||
msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s."
|
||||
msgstr ""
|
||||
|
||||
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:649
|
||||
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:646
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
@@ -8653,61 +8667,61 @@ msgstr ""
|
||||
msgid "Create filter"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:372 templates/js/translated/forms.js:387
|
||||
#: templates/js/translated/forms.js:401 templates/js/translated/forms.js:415
|
||||
#: templates/js/translated/forms.js:369 templates/js/translated/forms.js:384
|
||||
#: templates/js/translated/forms.js:398 templates/js/translated/forms.js:412
|
||||
msgid "Action Prohibited"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:374
|
||||
#: templates/js/translated/forms.js:371
|
||||
msgid "Create operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:389
|
||||
#: templates/js/translated/forms.js:386
|
||||
msgid "Update operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:403
|
||||
#: templates/js/translated/forms.js:400
|
||||
msgid "Delete operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:417
|
||||
#: templates/js/translated/forms.js:414
|
||||
msgid "View operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:675
|
||||
#: templates/js/translated/forms.js:672
|
||||
msgid "Keep this form open"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:776
|
||||
#: templates/js/translated/forms.js:773
|
||||
msgid "Enter a valid number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1269 templates/modals.html:19
|
||||
#: templates/js/translated/forms.js:1266 templates/modals.html:19
|
||||
#: templates/modals.html:43
|
||||
msgid "Form errors exist"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1706
|
||||
#: templates/js/translated/forms.js:1703
|
||||
msgid "No results found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1922 templates/search.html:29
|
||||
#: templates/js/translated/forms.js:1919 templates/search.html:29
|
||||
msgid "Searching"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2175
|
||||
#: templates/js/translated/forms.js:2172
|
||||
msgid "Clear input"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2641
|
||||
#: templates/js/translated/forms.js:2638
|
||||
msgid "File Column"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2641
|
||||
#: templates/js/translated/forms.js:2638
|
||||
msgid "Field Name"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2653
|
||||
#: templates/js/translated/forms.js:2650
|
||||
msgid "Select Columns"
|
||||
msgstr ""
|
||||
|
||||
@@ -9737,11 +9751,11 @@ msgstr ""
|
||||
msgid "Sales Order(s) must be selected before printing report"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/search.js:394
|
||||
#: templates/js/translated/search.js:410
|
||||
msgid "Minimize results"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/search.js:397
|
||||
#: templates/js/translated/search.js:413
|
||||
msgid "Remove results"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-10-25 10:54+0000\n"
|
||||
"POT-Creation-Date: 2022-10-28 07:18+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -59,35 +59,35 @@ msgstr ""
|
||||
msgid "Provided value does not match required pattern: "
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:133
|
||||
#: InvenTree/forms.py:134
|
||||
msgid "Enter password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:134
|
||||
#: InvenTree/forms.py:135
|
||||
msgid "Enter new password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:143
|
||||
#: InvenTree/forms.py:144
|
||||
msgid "Confirm password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:144
|
||||
#: InvenTree/forms.py:145
|
||||
msgid "Confirm new password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:148
|
||||
#: InvenTree/forms.py:149
|
||||
msgid "Old password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:177
|
||||
#: InvenTree/forms.py:178
|
||||
msgid "Email (again)"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:181
|
||||
#: InvenTree/forms.py:182
|
||||
msgid "Email address confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:202
|
||||
#: InvenTree/forms.py:203
|
||||
msgid "You must type the same email each time."
|
||||
msgstr ""
|
||||
|
||||
@@ -131,30 +131,35 @@ msgstr ""
|
||||
msgid "Empty serial number string"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:633
|
||||
#: InvenTree/helpers.py:640
|
||||
msgid "Duplicate serial"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:662
|
||||
#: InvenTree/helpers.py:673 InvenTree/helpers.py:708
|
||||
#, python-brace-format
|
||||
msgid "Invalid group range: {g}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:714 InvenTree/helpers.py:721 InvenTree/helpers.py:736
|
||||
#: InvenTree/helpers.py:702
|
||||
#, python-brace-format
|
||||
msgid "Group range {g} exceeds allowed quantity ({q})"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:726 InvenTree/helpers.py:733 InvenTree/helpers.py:748
|
||||
#, python-brace-format
|
||||
msgid "Invalid group sequence: {g}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:746
|
||||
#: InvenTree/helpers.py:758
|
||||
msgid "No serial numbers found"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:749
|
||||
#: InvenTree/helpers.py:761
|
||||
#, python-brace-format
|
||||
msgid "Number of unique serial numbers ({s}) must match quantity ({q})"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:948
|
||||
#: InvenTree/helpers.py:960
|
||||
msgid "Remove HTML tags from this value"
|
||||
msgstr ""
|
||||
|
||||
@@ -224,9 +229,9 @@ msgstr ""
|
||||
msgid "File comment"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1726
|
||||
#: common/models.py:1727 common/models.py:1950 common/models.py:1951
|
||||
#: common/models.py:2213 common/models.py:2214 part/models.py:2254
|
||||
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1733
|
||||
#: common/models.py:1734 common/models.py:1957 common/models.py:1958
|
||||
#: common/models.py:2220 common/models.py:2221 part/models.py:2254
|
||||
#: part/models.py:2274 plugin/models.py:260 plugin/models.py:261
|
||||
#: report/templates/report/inventree_test_report_base.html:96
|
||||
#: templates/js/translated/stock.js:2649
|
||||
@@ -266,7 +271,7 @@ msgstr ""
|
||||
msgid "Invalid choice"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1936
|
||||
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1943
|
||||
#: company/models.py:358 label/models.py:101 part/models.py:760
|
||||
#: part/models.py:2432 plugin/models.py:94 report/models.py:152
|
||||
#: templates/InvenTree/settings/mixins/urls.html:13
|
||||
@@ -678,24 +683,24 @@ msgstr ""
|
||||
msgid "Production"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:19
|
||||
#: InvenTree/validators.py:20
|
||||
msgid "Not a valid currency code"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:90
|
||||
#: InvenTree/validators.py:91
|
||||
#, python-brace-format
|
||||
msgid "IPN must match regex pattern {pat}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:132 InvenTree/validators.py:148
|
||||
#: InvenTree/validators.py:133 InvenTree/validators.py:149
|
||||
msgid "Overage value must not be negative"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:150
|
||||
#: InvenTree/validators.py:151
|
||||
msgid "Overage must not exceed 100%"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:157
|
||||
#: InvenTree/validators.py:158
|
||||
msgid "Invalid value for overage"
|
||||
msgstr ""
|
||||
|
||||
@@ -746,7 +751,8 @@ msgstr ""
|
||||
#: order/templates/order/so_sidebar.html:13
|
||||
#: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221
|
||||
#: templates/InvenTree/search.html:141
|
||||
#: templates/InvenTree/settings/sidebar.html:47 users/models.py:41
|
||||
#: templates/InvenTree/settings/sidebar.html:47
|
||||
#: templates/js/translated/search.js:254 users/models.py:41
|
||||
msgid "Build Orders"
|
||||
msgstr ""
|
||||
|
||||
@@ -1010,7 +1016,7 @@ msgstr ""
|
||||
|
||||
#: build/models.py:1359 build/serializers.py:192
|
||||
#: build/templates/build/build_base.html:85
|
||||
#: build/templates/build/detail.html:34 common/models.py:1758
|
||||
#: build/templates/build/detail.html:34 common/models.py:1765
|
||||
#: company/templates/company/supplier_part.html:341 order/models.py:911
|
||||
#: order/models.py:1437 order/serializers.py:1213
|
||||
#: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40
|
||||
@@ -2321,7 +2327,7 @@ msgstr ""
|
||||
msgid "Enable plugins to respond to internal events"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1391 common/models.py:1719
|
||||
#: common/models.py:1391 common/models.py:1726
|
||||
msgid "Settings key (must be unique - case insensitive"
|
||||
msgstr ""
|
||||
|
||||
@@ -2558,120 +2564,128 @@ msgid "Display companies in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1616
|
||||
msgid "Search Purchase Orders"
|
||||
msgid "Search Build Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1617
|
||||
msgid "Display purchase orders in search preview window"
|
||||
msgid "Display build orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1623
|
||||
msgid "Exclude Inactive Purchase Orders"
|
||||
msgid "Search Purchase Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1624
|
||||
msgid "Exclude inactive purchase orders from search preview window"
|
||||
msgid "Display purchase orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1630
|
||||
msgid "Search Sales Orders"
|
||||
msgid "Exclude Inactive Purchase Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1631
|
||||
msgid "Display sales orders in search preview window"
|
||||
msgid "Exclude inactive purchase orders from search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1637
|
||||
msgid "Exclude Inactive Sales Orders"
|
||||
msgid "Search Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1638
|
||||
msgid "Exclude inactive sales orders from search preview window"
|
||||
msgid "Display sales orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1644
|
||||
msgid "Search Preview Results"
|
||||
msgid "Exclude Inactive Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1645
|
||||
msgid "Number of results to show in each section of the search preview window"
|
||||
msgid "Exclude inactive sales orders from search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1651
|
||||
msgid "Show Quantity in Forms"
|
||||
msgid "Search Preview Results"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1652
|
||||
msgid "Display available part quantity in some forms"
|
||||
msgid "Number of results to show in each section of the search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1658
|
||||
msgid "Escape Key Closes Forms"
|
||||
msgid "Show Quantity in Forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1659
|
||||
msgid "Use the escape key to close modal forms"
|
||||
msgid "Display available part quantity in some forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1665
|
||||
msgid "Fixed Navbar"
|
||||
msgid "Escape Key Closes Forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1666
|
||||
msgid "The navbar position is fixed to the top of the screen"
|
||||
msgid "Use the escape key to close modal forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1672
|
||||
msgid "Date Format"
|
||||
msgid "Fixed Navbar"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1673
|
||||
msgid "The navbar position is fixed to the top of the screen"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1679
|
||||
msgid "Date Format"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1680
|
||||
msgid "Preferred format for displaying dates"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1687 part/templates/part/detail.html:41
|
||||
#: common/models.py:1694 part/templates/part/detail.html:41
|
||||
msgid "Part Scheduling"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1688
|
||||
#: common/models.py:1695
|
||||
msgid "Display part scheduling information"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1694
|
||||
#: common/models.py:1701
|
||||
msgid "Table String Length"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1695
|
||||
#: common/models.py:1702
|
||||
msgid "Maximimum length limit for strings displayed in table views"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1759
|
||||
#: common/models.py:1766
|
||||
msgid "Price break quantity"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1766 company/serializers.py:372
|
||||
#: common/models.py:1773 company/serializers.py:372
|
||||
#: company/templates/company/supplier_part.html:346 order/models.py:952
|
||||
#: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1767
|
||||
#: common/models.py:1774
|
||||
msgid "Unit price at specified quantity"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1927 common/models.py:2105
|
||||
#: common/models.py:1934 common/models.py:2112
|
||||
msgid "Endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1928
|
||||
#: common/models.py:1935
|
||||
msgid "Endpoint at which this webhook is received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1937
|
||||
#: common/models.py:1944
|
||||
msgid "Name for this webhook"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1942 part/models.py:935 plugin/models.py:100
|
||||
#: common/models.py:1949 part/models.py:935 plugin/models.py:100
|
||||
#: templates/js/translated/table_filters.js:34
|
||||
#: templates/js/translated/table_filters.js:112
|
||||
#: templates/js/translated/table_filters.js:324
|
||||
@@ -2679,67 +2693,67 @@ msgstr ""
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1943
|
||||
#: common/models.py:1950
|
||||
msgid "Is this webhook active"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1957
|
||||
#: common/models.py:1964
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1958
|
||||
#: common/models.py:1965
|
||||
msgid "Token for access"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1965
|
||||
#: common/models.py:1972
|
||||
msgid "Secret"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1966
|
||||
#: common/models.py:1973
|
||||
msgid "Shared secret for HMAC"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2072
|
||||
#: common/models.py:2079
|
||||
msgid "Message ID"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2073
|
||||
#: common/models.py:2080
|
||||
msgid "Unique identifier for this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2081
|
||||
#: common/models.py:2088
|
||||
msgid "Host"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2082
|
||||
#: common/models.py:2089
|
||||
msgid "Host from which this message was received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2089
|
||||
#: common/models.py:2096
|
||||
msgid "Header"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2090
|
||||
#: common/models.py:2097
|
||||
msgid "Header of this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2096
|
||||
#: common/models.py:2103
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2097
|
||||
#: common/models.py:2104
|
||||
msgid "Body of this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2106
|
||||
#: common/models.py:2113
|
||||
msgid "Endpoint on which this message was received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2111
|
||||
#: common/models.py:2118
|
||||
msgid "Worked on"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2112
|
||||
#: common/models.py:2119
|
||||
msgid "Was the work on this message finished?"
|
||||
msgstr ""
|
||||
|
||||
@@ -3228,7 +3242,7 @@ msgstr ""
|
||||
#: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37
|
||||
#: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200
|
||||
#: templates/InvenTree/settings/sidebar.html:49
|
||||
#: templates/js/translated/search.js:277 templates/navbar.html:50
|
||||
#: templates/js/translated/search.js:293 templates/navbar.html:50
|
||||
#: users/models.py:42
|
||||
msgid "Purchase Orders"
|
||||
msgstr ""
|
||||
@@ -3251,7 +3265,7 @@ msgstr ""
|
||||
#: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41
|
||||
#: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220
|
||||
#: templates/InvenTree/settings/sidebar.html:51
|
||||
#: templates/js/translated/search.js:301 templates/navbar.html:61
|
||||
#: templates/js/translated/search.js:317 templates/navbar.html:61
|
||||
#: users/models.py:43
|
||||
msgid "Sales Orders"
|
||||
msgstr ""
|
||||
@@ -3321,7 +3335,7 @@ msgstr ""
|
||||
#: company/templates/company/manufacturer_part.html:136
|
||||
#: company/templates/company/manufacturer_part.html:183
|
||||
#: part/templates/part/detail.html:371 part/templates/part/detail.html:401
|
||||
#: templates/js/translated/forms.js:458 templates/js/translated/helpers.js:36
|
||||
#: templates/js/translated/forms.js:455 templates/js/translated/helpers.js:36
|
||||
#: users/models.py:222
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
@@ -3570,7 +3584,7 @@ msgstr ""
|
||||
msgid "New Customer"
|
||||
msgstr ""
|
||||
|
||||
#: company/views.py:52 templates/js/translated/search.js:254
|
||||
#: company/views.py:52 templates/js/translated/search.js:270
|
||||
msgid "Companies"
|
||||
msgstr ""
|
||||
|
||||
@@ -4379,47 +4393,47 @@ msgstr ""
|
||||
msgid "Updated {part} unit-price to {price} and quantity to {qty}"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:514
|
||||
#: part/api.py:516
|
||||
msgid "Incoming Purchase Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:534
|
||||
#: part/api.py:536
|
||||
msgid "Outgoing Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:552
|
||||
#: part/api.py:554
|
||||
msgid "Stock produced by Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:638
|
||||
#: part/api.py:640
|
||||
msgid "Stock required for Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:775
|
||||
#: part/api.py:777
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:776
|
||||
#: part/api.py:778
|
||||
msgid "Validate entire Bill of Materials"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:782
|
||||
#: part/api.py:784
|
||||
msgid "This option must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1205
|
||||
#: part/api.py:1207
|
||||
msgid "Must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1209
|
||||
#: part/api.py:1211
|
||||
msgid "Must be a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1224
|
||||
#: part/api.py:1226
|
||||
msgid "Specify location for initial part stock"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1255 part/api.py:1259 part/api.py:1274 part/api.py:1278
|
||||
#: part/api.py:1257 part/api.py:1261 part/api.py:1276 part/api.py:1280
|
||||
msgid "This field is required"
|
||||
msgstr ""
|
||||
|
||||
@@ -5755,23 +5769,23 @@ msgstr ""
|
||||
msgid "No matching action found"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:52 plugin/base/barcodes/api.py:110
|
||||
msgid "Must provide barcode_data parameter"
|
||||
#: plugin/base/barcodes/api.py:54 plugin/base/barcodes/api.py:113
|
||||
msgid "Missing barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:82
|
||||
#: plugin/base/barcodes/api.py:83
|
||||
msgid "No match found for barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:86
|
||||
#: plugin/base/barcodes/api.py:87
|
||||
msgid "Match found for barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:125
|
||||
#: plugin/base/barcodes/api.py:126
|
||||
msgid "Barcode matches existing item"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:222
|
||||
#: plugin/base/barcodes/api.py:223
|
||||
msgid "No match found for provided value"
|
||||
msgstr ""
|
||||
|
||||
@@ -7603,7 +7617,7 @@ msgstr ""
|
||||
msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s."
|
||||
msgstr ""
|
||||
|
||||
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:649
|
||||
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:646
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
@@ -8653,61 +8667,61 @@ msgstr ""
|
||||
msgid "Create filter"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:372 templates/js/translated/forms.js:387
|
||||
#: templates/js/translated/forms.js:401 templates/js/translated/forms.js:415
|
||||
#: templates/js/translated/forms.js:369 templates/js/translated/forms.js:384
|
||||
#: templates/js/translated/forms.js:398 templates/js/translated/forms.js:412
|
||||
msgid "Action Prohibited"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:374
|
||||
#: templates/js/translated/forms.js:371
|
||||
msgid "Create operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:389
|
||||
#: templates/js/translated/forms.js:386
|
||||
msgid "Update operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:403
|
||||
#: templates/js/translated/forms.js:400
|
||||
msgid "Delete operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:417
|
||||
#: templates/js/translated/forms.js:414
|
||||
msgid "View operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:675
|
||||
#: templates/js/translated/forms.js:672
|
||||
msgid "Keep this form open"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:776
|
||||
#: templates/js/translated/forms.js:773
|
||||
msgid "Enter a valid number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1269 templates/modals.html:19
|
||||
#: templates/js/translated/forms.js:1266 templates/modals.html:19
|
||||
#: templates/modals.html:43
|
||||
msgid "Form errors exist"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1706
|
||||
#: templates/js/translated/forms.js:1703
|
||||
msgid "No results found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1922 templates/search.html:29
|
||||
#: templates/js/translated/forms.js:1919 templates/search.html:29
|
||||
msgid "Searching"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2175
|
||||
#: templates/js/translated/forms.js:2172
|
||||
msgid "Clear input"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2641
|
||||
#: templates/js/translated/forms.js:2638
|
||||
msgid "File Column"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2641
|
||||
#: templates/js/translated/forms.js:2638
|
||||
msgid "Field Name"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2653
|
||||
#: templates/js/translated/forms.js:2650
|
||||
msgid "Select Columns"
|
||||
msgstr ""
|
||||
|
||||
@@ -9737,11 +9751,11 @@ msgstr ""
|
||||
msgid "Sales Order(s) must be selected before printing report"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/search.js:394
|
||||
#: templates/js/translated/search.js:410
|
||||
msgid "Minimize results"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/search.js:397
|
||||
#: templates/js/translated/search.js:413
|
||||
msgid "Remove results"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-10-25 10:54+0000\n"
|
||||
"POT-Creation-Date: 2022-10-28 07:18+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -59,35 +59,35 @@ msgstr ""
|
||||
msgid "Provided value does not match required pattern: "
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:133
|
||||
#: InvenTree/forms.py:134
|
||||
msgid "Enter password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:134
|
||||
#: InvenTree/forms.py:135
|
||||
msgid "Enter new password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:143
|
||||
#: InvenTree/forms.py:144
|
||||
msgid "Confirm password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:144
|
||||
#: InvenTree/forms.py:145
|
||||
msgid "Confirm new password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:148
|
||||
#: InvenTree/forms.py:149
|
||||
msgid "Old password"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:177
|
||||
#: InvenTree/forms.py:178
|
||||
msgid "Email (again)"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:181
|
||||
#: InvenTree/forms.py:182
|
||||
msgid "Email address confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/forms.py:202
|
||||
#: InvenTree/forms.py:203
|
||||
msgid "You must type the same email each time."
|
||||
msgstr ""
|
||||
|
||||
@@ -131,30 +131,35 @@ msgstr ""
|
||||
msgid "Empty serial number string"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:633
|
||||
#: InvenTree/helpers.py:640
|
||||
msgid "Duplicate serial"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:662
|
||||
#: InvenTree/helpers.py:673 InvenTree/helpers.py:708
|
||||
#, python-brace-format
|
||||
msgid "Invalid group range: {g}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:714 InvenTree/helpers.py:721 InvenTree/helpers.py:736
|
||||
#: InvenTree/helpers.py:702
|
||||
#, python-brace-format
|
||||
msgid "Group range {g} exceeds allowed quantity ({q})"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:726 InvenTree/helpers.py:733 InvenTree/helpers.py:748
|
||||
#, python-brace-format
|
||||
msgid "Invalid group sequence: {g}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:746
|
||||
#: InvenTree/helpers.py:758
|
||||
msgid "No serial numbers found"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:749
|
||||
#: InvenTree/helpers.py:761
|
||||
#, python-brace-format
|
||||
msgid "Number of unique serial numbers ({s}) must match quantity ({q})"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/helpers.py:948
|
||||
#: InvenTree/helpers.py:960
|
||||
msgid "Remove HTML tags from this value"
|
||||
msgstr ""
|
||||
|
||||
@@ -224,9 +229,9 @@ msgstr ""
|
||||
msgid "File comment"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1726
|
||||
#: common/models.py:1727 common/models.py:1950 common/models.py:1951
|
||||
#: common/models.py:2213 common/models.py:2214 part/models.py:2254
|
||||
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1733
|
||||
#: common/models.py:1734 common/models.py:1957 common/models.py:1958
|
||||
#: common/models.py:2220 common/models.py:2221 part/models.py:2254
|
||||
#: part/models.py:2274 plugin/models.py:260 plugin/models.py:261
|
||||
#: report/templates/report/inventree_test_report_base.html:96
|
||||
#: templates/js/translated/stock.js:2649
|
||||
@@ -266,7 +271,7 @@ msgstr ""
|
||||
msgid "Invalid choice"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1936
|
||||
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1943
|
||||
#: company/models.py:358 label/models.py:101 part/models.py:760
|
||||
#: part/models.py:2432 plugin/models.py:94 report/models.py:152
|
||||
#: templates/InvenTree/settings/mixins/urls.html:13
|
||||
@@ -678,24 +683,24 @@ msgstr ""
|
||||
msgid "Production"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:19
|
||||
#: InvenTree/validators.py:20
|
||||
msgid "Not a valid currency code"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:90
|
||||
#: InvenTree/validators.py:91
|
||||
#, python-brace-format
|
||||
msgid "IPN must match regex pattern {pat}"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:132 InvenTree/validators.py:148
|
||||
#: InvenTree/validators.py:133 InvenTree/validators.py:149
|
||||
msgid "Overage value must not be negative"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:150
|
||||
#: InvenTree/validators.py:151
|
||||
msgid "Overage must not exceed 100%"
|
||||
msgstr ""
|
||||
|
||||
#: InvenTree/validators.py:157
|
||||
#: InvenTree/validators.py:158
|
||||
msgid "Invalid value for overage"
|
||||
msgstr ""
|
||||
|
||||
@@ -746,7 +751,8 @@ msgstr ""
|
||||
#: order/templates/order/so_sidebar.html:13
|
||||
#: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221
|
||||
#: templates/InvenTree/search.html:141
|
||||
#: templates/InvenTree/settings/sidebar.html:47 users/models.py:41
|
||||
#: templates/InvenTree/settings/sidebar.html:47
|
||||
#: templates/js/translated/search.js:254 users/models.py:41
|
||||
msgid "Build Orders"
|
||||
msgstr ""
|
||||
|
||||
@@ -1010,7 +1016,7 @@ msgstr ""
|
||||
|
||||
#: build/models.py:1359 build/serializers.py:192
|
||||
#: build/templates/build/build_base.html:85
|
||||
#: build/templates/build/detail.html:34 common/models.py:1758
|
||||
#: build/templates/build/detail.html:34 common/models.py:1765
|
||||
#: company/templates/company/supplier_part.html:341 order/models.py:911
|
||||
#: order/models.py:1437 order/serializers.py:1213
|
||||
#: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40
|
||||
@@ -2321,7 +2327,7 @@ msgstr ""
|
||||
msgid "Enable plugins to respond to internal events"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1391 common/models.py:1719
|
||||
#: common/models.py:1391 common/models.py:1726
|
||||
msgid "Settings key (must be unique - case insensitive"
|
||||
msgstr ""
|
||||
|
||||
@@ -2558,120 +2564,128 @@ msgid "Display companies in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1616
|
||||
msgid "Search Purchase Orders"
|
||||
msgid "Search Build Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1617
|
||||
msgid "Display purchase orders in search preview window"
|
||||
msgid "Display build orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1623
|
||||
msgid "Exclude Inactive Purchase Orders"
|
||||
msgid "Search Purchase Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1624
|
||||
msgid "Exclude inactive purchase orders from search preview window"
|
||||
msgid "Display purchase orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1630
|
||||
msgid "Search Sales Orders"
|
||||
msgid "Exclude Inactive Purchase Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1631
|
||||
msgid "Display sales orders in search preview window"
|
||||
msgid "Exclude inactive purchase orders from search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1637
|
||||
msgid "Exclude Inactive Sales Orders"
|
||||
msgid "Search Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1638
|
||||
msgid "Exclude inactive sales orders from search preview window"
|
||||
msgid "Display sales orders in search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1644
|
||||
msgid "Search Preview Results"
|
||||
msgid "Exclude Inactive Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1645
|
||||
msgid "Number of results to show in each section of the search preview window"
|
||||
msgid "Exclude inactive sales orders from search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1651
|
||||
msgid "Show Quantity in Forms"
|
||||
msgid "Search Preview Results"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1652
|
||||
msgid "Display available part quantity in some forms"
|
||||
msgid "Number of results to show in each section of the search preview window"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1658
|
||||
msgid "Escape Key Closes Forms"
|
||||
msgid "Show Quantity in Forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1659
|
||||
msgid "Use the escape key to close modal forms"
|
||||
msgid "Display available part quantity in some forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1665
|
||||
msgid "Fixed Navbar"
|
||||
msgid "Escape Key Closes Forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1666
|
||||
msgid "The navbar position is fixed to the top of the screen"
|
||||
msgid "Use the escape key to close modal forms"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1672
|
||||
msgid "Date Format"
|
||||
msgid "Fixed Navbar"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1673
|
||||
msgid "The navbar position is fixed to the top of the screen"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1679
|
||||
msgid "Date Format"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1680
|
||||
msgid "Preferred format for displaying dates"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1687 part/templates/part/detail.html:41
|
||||
#: common/models.py:1694 part/templates/part/detail.html:41
|
||||
msgid "Part Scheduling"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1688
|
||||
#: common/models.py:1695
|
||||
msgid "Display part scheduling information"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1694
|
||||
#: common/models.py:1701
|
||||
msgid "Table String Length"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1695
|
||||
#: common/models.py:1702
|
||||
msgid "Maximimum length limit for strings displayed in table views"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1759
|
||||
#: common/models.py:1766
|
||||
msgid "Price break quantity"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1766 company/serializers.py:372
|
||||
#: common/models.py:1773 company/serializers.py:372
|
||||
#: company/templates/company/supplier_part.html:346 order/models.py:952
|
||||
#: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1767
|
||||
#: common/models.py:1774
|
||||
msgid "Unit price at specified quantity"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1927 common/models.py:2105
|
||||
#: common/models.py:1934 common/models.py:2112
|
||||
msgid "Endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1928
|
||||
#: common/models.py:1935
|
||||
msgid "Endpoint at which this webhook is received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1937
|
||||
#: common/models.py:1944
|
||||
msgid "Name for this webhook"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1942 part/models.py:935 plugin/models.py:100
|
||||
#: common/models.py:1949 part/models.py:935 plugin/models.py:100
|
||||
#: templates/js/translated/table_filters.js:34
|
||||
#: templates/js/translated/table_filters.js:112
|
||||
#: templates/js/translated/table_filters.js:324
|
||||
@@ -2679,67 +2693,67 @@ msgstr ""
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1943
|
||||
#: common/models.py:1950
|
||||
msgid "Is this webhook active"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1957
|
||||
#: common/models.py:1964
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1958
|
||||
#: common/models.py:1965
|
||||
msgid "Token for access"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1965
|
||||
#: common/models.py:1972
|
||||
msgid "Secret"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:1966
|
||||
#: common/models.py:1973
|
||||
msgid "Shared secret for HMAC"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2072
|
||||
#: common/models.py:2079
|
||||
msgid "Message ID"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2073
|
||||
#: common/models.py:2080
|
||||
msgid "Unique identifier for this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2081
|
||||
#: common/models.py:2088
|
||||
msgid "Host"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2082
|
||||
#: common/models.py:2089
|
||||
msgid "Host from which this message was received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2089
|
||||
#: common/models.py:2096
|
||||
msgid "Header"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2090
|
||||
#: common/models.py:2097
|
||||
msgid "Header of this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2096
|
||||
#: common/models.py:2103
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2097
|
||||
#: common/models.py:2104
|
||||
msgid "Body of this message"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2106
|
||||
#: common/models.py:2113
|
||||
msgid "Endpoint on which this message was received"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2111
|
||||
#: common/models.py:2118
|
||||
msgid "Worked on"
|
||||
msgstr ""
|
||||
|
||||
#: common/models.py:2112
|
||||
#: common/models.py:2119
|
||||
msgid "Was the work on this message finished?"
|
||||
msgstr ""
|
||||
|
||||
@@ -3228,7 +3242,7 @@ msgstr ""
|
||||
#: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37
|
||||
#: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200
|
||||
#: templates/InvenTree/settings/sidebar.html:49
|
||||
#: templates/js/translated/search.js:277 templates/navbar.html:50
|
||||
#: templates/js/translated/search.js:293 templates/navbar.html:50
|
||||
#: users/models.py:42
|
||||
msgid "Purchase Orders"
|
||||
msgstr ""
|
||||
@@ -3251,7 +3265,7 @@ msgstr ""
|
||||
#: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41
|
||||
#: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220
|
||||
#: templates/InvenTree/settings/sidebar.html:51
|
||||
#: templates/js/translated/search.js:301 templates/navbar.html:61
|
||||
#: templates/js/translated/search.js:317 templates/navbar.html:61
|
||||
#: users/models.py:43
|
||||
msgid "Sales Orders"
|
||||
msgstr ""
|
||||
@@ -3321,7 +3335,7 @@ msgstr ""
|
||||
#: company/templates/company/manufacturer_part.html:136
|
||||
#: company/templates/company/manufacturer_part.html:183
|
||||
#: part/templates/part/detail.html:371 part/templates/part/detail.html:401
|
||||
#: templates/js/translated/forms.js:458 templates/js/translated/helpers.js:36
|
||||
#: templates/js/translated/forms.js:455 templates/js/translated/helpers.js:36
|
||||
#: users/models.py:222
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
@@ -3570,7 +3584,7 @@ msgstr ""
|
||||
msgid "New Customer"
|
||||
msgstr ""
|
||||
|
||||
#: company/views.py:52 templates/js/translated/search.js:254
|
||||
#: company/views.py:52 templates/js/translated/search.js:270
|
||||
msgid "Companies"
|
||||
msgstr ""
|
||||
|
||||
@@ -4379,47 +4393,47 @@ msgstr ""
|
||||
msgid "Updated {part} unit-price to {price} and quantity to {qty}"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:514
|
||||
#: part/api.py:516
|
||||
msgid "Incoming Purchase Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:534
|
||||
#: part/api.py:536
|
||||
msgid "Outgoing Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:552
|
||||
#: part/api.py:554
|
||||
msgid "Stock produced by Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:638
|
||||
#: part/api.py:640
|
||||
msgid "Stock required for Build Order"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:775
|
||||
#: part/api.py:777
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:776
|
||||
#: part/api.py:778
|
||||
msgid "Validate entire Bill of Materials"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:782
|
||||
#: part/api.py:784
|
||||
msgid "This option must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1205
|
||||
#: part/api.py:1207
|
||||
msgid "Must be greater than zero"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1209
|
||||
#: part/api.py:1211
|
||||
msgid "Must be a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1224
|
||||
#: part/api.py:1226
|
||||
msgid "Specify location for initial part stock"
|
||||
msgstr ""
|
||||
|
||||
#: part/api.py:1255 part/api.py:1259 part/api.py:1274 part/api.py:1278
|
||||
#: part/api.py:1257 part/api.py:1261 part/api.py:1276 part/api.py:1280
|
||||
msgid "This field is required"
|
||||
msgstr ""
|
||||
|
||||
@@ -5755,23 +5769,23 @@ msgstr ""
|
||||
msgid "No matching action found"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:52 plugin/base/barcodes/api.py:110
|
||||
msgid "Must provide barcode_data parameter"
|
||||
#: plugin/base/barcodes/api.py:54 plugin/base/barcodes/api.py:113
|
||||
msgid "Missing barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:82
|
||||
#: plugin/base/barcodes/api.py:83
|
||||
msgid "No match found for barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:86
|
||||
#: plugin/base/barcodes/api.py:87
|
||||
msgid "Match found for barcode data"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:125
|
||||
#: plugin/base/barcodes/api.py:126
|
||||
msgid "Barcode matches existing item"
|
||||
msgstr ""
|
||||
|
||||
#: plugin/base/barcodes/api.py:222
|
||||
#: plugin/base/barcodes/api.py:223
|
||||
msgid "No match found for provided value"
|
||||
msgstr ""
|
||||
|
||||
@@ -7603,7 +7617,7 @@ msgstr ""
|
||||
msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s."
|
||||
msgstr ""
|
||||
|
||||
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:649
|
||||
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:646
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
@@ -8653,61 +8667,61 @@ msgstr ""
|
||||
msgid "Create filter"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:372 templates/js/translated/forms.js:387
|
||||
#: templates/js/translated/forms.js:401 templates/js/translated/forms.js:415
|
||||
#: templates/js/translated/forms.js:369 templates/js/translated/forms.js:384
|
||||
#: templates/js/translated/forms.js:398 templates/js/translated/forms.js:412
|
||||
msgid "Action Prohibited"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:374
|
||||
#: templates/js/translated/forms.js:371
|
||||
msgid "Create operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:389
|
||||
#: templates/js/translated/forms.js:386
|
||||
msgid "Update operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:403
|
||||
#: templates/js/translated/forms.js:400
|
||||
msgid "Delete operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:417
|
||||
#: templates/js/translated/forms.js:414
|
||||
msgid "View operation not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:675
|
||||
#: templates/js/translated/forms.js:672
|
||||
msgid "Keep this form open"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:776
|
||||
#: templates/js/translated/forms.js:773
|
||||
msgid "Enter a valid number"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1269 templates/modals.html:19
|
||||
#: templates/js/translated/forms.js:1266 templates/modals.html:19
|
||||
#: templates/modals.html:43
|
||||
msgid "Form errors exist"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1706
|
||||
#: templates/js/translated/forms.js:1703
|
||||
msgid "No results found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:1922 templates/search.html:29
|
||||
#: templates/js/translated/forms.js:1919 templates/search.html:29
|
||||
msgid "Searching"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2175
|
||||
#: templates/js/translated/forms.js:2172
|
||||
msgid "Clear input"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2641
|
||||
#: templates/js/translated/forms.js:2638
|
||||
msgid "File Column"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2641
|
||||
#: templates/js/translated/forms.js:2638
|
||||
msgid "Field Name"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/forms.js:2653
|
||||
#: templates/js/translated/forms.js:2650
|
||||
msgid "Select Columns"
|
||||
msgstr ""
|
||||
|
||||
@@ -9737,11 +9751,11 @@ msgstr ""
|
||||
msgid "Sales Order(s) must be selected before printing report"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/search.js:394
|
||||
#: templates/js/translated/search.js:410
|
||||
msgid "Minimize results"
|
||||
msgstr ""
|
||||
|
||||
#: templates/js/translated/search.js:397
|
||||
#: templates/js/translated/search.js:413
|
||||
msgid "Remove results"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -48,7 +48,7 @@
|
||||
<h4>{% trans "Extra Lines" %}</h4>
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% if roles.purchase_order.change and order.status == PurchaseOrderStatus.PENDING %}
|
||||
{% if roles.purchase_order.change %}
|
||||
<button type='button' class='btn btn-success' id='new-po-extra-line'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
|
||||
</button>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<h4>{% trans "Extra Lines" %}</h4>
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% if roles.sales_order.change and order.is_pending %}
|
||||
{% if roles.sales_order.change %}
|
||||
<button type='button' class='btn btn-success' id='new-so-extra-line'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.16 on 2022-10-30 23:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0087_bomitem_consumable'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='partparametertemplate',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Parameter Name', max_length=100, unique=True, verbose_name='Name'),
|
||||
),
|
||||
]
|
||||
@@ -2383,10 +2383,7 @@ class PartTestTemplate(models.Model):
|
||||
|
||||
|
||||
def validate_template_name(name):
|
||||
"""Prevent illegal characters in "name" field for PartParameterTemplate."""
|
||||
for c in "\"\'`!?|": # noqa: P103
|
||||
if c in str(name):
|
||||
raise ValidationError(_(f"Illegal character in template name ({c})"))
|
||||
"""Placeholder for legacy function used in migrations."""
|
||||
|
||||
|
||||
class PartParameterTemplate(models.Model):
|
||||
@@ -2431,10 +2428,7 @@ class PartParameterTemplate(models.Model):
|
||||
max_length=100,
|
||||
verbose_name=_('Name'),
|
||||
help_text=_('Parameter Name'),
|
||||
unique=True,
|
||||
validators=[
|
||||
validate_template_name,
|
||||
]
|
||||
unique=True
|
||||
)
|
||||
|
||||
units = models.CharField(max_length=25, verbose_name=_('Units'), help_text=_('Parameter Units'), blank=True)
|
||||
|
||||
@@ -63,11 +63,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if roles.part_category.add %}
|
||||
<button class='btn btn-success' id='cat-create' title='{% trans "Create new part category" %}'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "New Category" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block details_left %}
|
||||
@@ -225,7 +220,17 @@
|
||||
|
||||
<div class='panel panel-hidden' id='panel-subcategories'>
|
||||
<div class='panel-heading'>
|
||||
<h4>{% trans "Subcategories" %}</h4>
|
||||
<div class='d-flex flex-wrap'>
|
||||
<h4>{% trans "Subcategories" %}</h4>
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% if roles.part_category.add %}
|
||||
<button class='btn btn-success' id='cat-create' title='{% trans "Create new part category" %}'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "New Category" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
<div id='subcategory-button-toolbar'>
|
||||
|
||||
@@ -593,7 +593,7 @@ class PartAPITest(InvenTreeAPITestCase):
|
||||
{
|
||||
'convert_from': variant.pk,
|
||||
},
|
||||
expected_code=200
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
# There should be the same number of results for each request
|
||||
@@ -1854,7 +1854,7 @@ class BomItemTest(InvenTreeAPITestCase):
|
||||
data={
|
||||
'validated': True,
|
||||
},
|
||||
expected_code=200
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
# Check that the expected response is returned
|
||||
|
||||
@@ -52,7 +52,7 @@ class PluginConfigAdmin(admin.ModelAdmin):
|
||||
"""Custom admin with restricted id fields."""
|
||||
|
||||
readonly_fields = ["key", "name", ]
|
||||
list_display = ['name', 'key', '__str__', 'active', 'is_sample']
|
||||
list_display = ['name', 'key', '__str__', 'active', 'is_builtin', 'is_sample']
|
||||
list_filter = ['active']
|
||||
actions = [plugin_activate, plugin_deactivate, ]
|
||||
inlines = [PluginSettingInline, ]
|
||||
|
||||
@@ -7,7 +7,6 @@ The main code for plugin special sauce is in the plugin registry in `InvenTree/p
|
||||
import logging
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from maintenance_mode.core import set_maintenance_mode
|
||||
@@ -26,34 +25,34 @@ class PluginAppConfig(AppConfig):
|
||||
|
||||
def ready(self):
|
||||
"""The ready method is extended to initialize plugins."""
|
||||
if settings.PLUGINS_ENABLED:
|
||||
if not canAppAccessDatabase(allow_test=True, allow_plugins=True):
|
||||
logger.info("Skipping plugin loading sequence") # pragma: no cover
|
||||
else:
|
||||
logger.info('Loading InvenTree plugins')
|
||||
if not canAppAccessDatabase(allow_test=True, allow_plugins=True):
|
||||
logger.info("Skipping plugin loading sequence") # pragma: no cover
|
||||
else:
|
||||
logger.info('Loading InvenTree plugins')
|
||||
|
||||
if not registry.is_loading:
|
||||
# this is the first startup
|
||||
try:
|
||||
from common.models import InvenTreeSetting
|
||||
if InvenTreeSetting.get_setting('PLUGIN_ON_STARTUP', create=False, cache=False):
|
||||
# make sure all plugins are installed
|
||||
registry.install_plugin_file()
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
if not registry.is_loading:
|
||||
# this is the first startup
|
||||
try:
|
||||
from common.models import InvenTreeSetting
|
||||
if InvenTreeSetting.get_setting('PLUGIN_ON_STARTUP', create=False, cache=False):
|
||||
# make sure all plugins are installed
|
||||
registry.install_plugin_file()
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
|
||||
# get plugins and init them
|
||||
registry.plugin_modules = registry.collect_plugins()
|
||||
registry.load_plugins()
|
||||
# get plugins and init them
|
||||
registry.plugin_modules = registry.collect_plugins()
|
||||
registry.load_plugins()
|
||||
|
||||
# drop out of maintenance
|
||||
# makes sure we did not have an error in reloading and maintenance is still active
|
||||
set_maintenance_mode(False)
|
||||
# drop out of maintenance
|
||||
# makes sure we did not have an error in reloading and maintenance is still active
|
||||
set_maintenance_mode(False)
|
||||
|
||||
# check git version
|
||||
registry.git_is_modern = check_git_version()
|
||||
if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage
|
||||
log_error(_('Your enviroment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load')
|
||||
# check git version
|
||||
registry.git_is_modern = check_git_version()
|
||||
|
||||
if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage
|
||||
log_error(_('Your environment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load')
|
||||
|
||||
else:
|
||||
logger.info("Plugins not enabled - skipping loading sequence") # pragma: no cover
|
||||
|
||||
@@ -11,8 +11,8 @@ from rest_framework.views import APIView
|
||||
|
||||
from InvenTree.helpers import hash_barcode
|
||||
from plugin import registry
|
||||
from plugin.builtin.barcodes.inventree_barcode import (
|
||||
InvenTreeExternalBarcodePlugin, InvenTreeInternalBarcodePlugin)
|
||||
from plugin.builtin.barcodes.inventree_barcode import \
|
||||
InvenTreeInternalBarcodePlugin
|
||||
from users.models import RuleSet
|
||||
|
||||
|
||||
@@ -53,11 +53,8 @@ class BarcodeScan(APIView):
|
||||
if not barcode_data:
|
||||
raise ValidationError({'barcode': _('Missing barcode data')})
|
||||
|
||||
# Ensure that the default barcode handlers are run first
|
||||
plugins = [
|
||||
InvenTreeInternalBarcodePlugin(),
|
||||
InvenTreeExternalBarcodePlugin(),
|
||||
] + registry.with_mixin('barcode')
|
||||
# Note: the default barcode handlers are loaded (and thus run) first
|
||||
plugins = registry.with_mixin('barcode')
|
||||
|
||||
barcode_hash = hash_barcode(barcode_data)
|
||||
|
||||
@@ -113,10 +110,7 @@ class BarcodeAssign(APIView):
|
||||
raise ValidationError({'barcode': _('Missing barcode data')})
|
||||
|
||||
# Here we only check against 'InvenTree' plugins
|
||||
plugins = [
|
||||
InvenTreeInternalBarcodePlugin(),
|
||||
InvenTreeExternalBarcodePlugin(),
|
||||
]
|
||||
plugins = registry.with_mixin('barcode', builtin=True)
|
||||
|
||||
# First check if the provided barcode matches an existing database entry
|
||||
for plugin in plugins:
|
||||
@@ -133,7 +127,7 @@ class BarcodeAssign(APIView):
|
||||
|
||||
valid_labels = []
|
||||
|
||||
for model in InvenTreeExternalBarcodePlugin.get_supported_barcode_models():
|
||||
for model in InvenTreeInternalBarcodePlugin.get_supported_barcode_models():
|
||||
label = model.barcode_model_type()
|
||||
valid_labels.append(label)
|
||||
|
||||
@@ -188,7 +182,7 @@ class BarcodeUnassign(APIView):
|
||||
"""Respond to a barcode unassign POST request"""
|
||||
|
||||
# The following database models support assignment of third-party barcodes
|
||||
supported_models = InvenTreeExternalBarcodePlugin.get_supported_barcode_models()
|
||||
supported_models = InvenTreeInternalBarcodePlugin.get_supported_barcode_models()
|
||||
|
||||
supported_labels = [model.barcode_model_type() for model in supported_models]
|
||||
model_names = ', '.join(supported_labels)
|
||||
|
||||
@@ -58,9 +58,8 @@ def register_event(event, *args, **kwargs):
|
||||
|
||||
if plugin.mixin_enabled('events'):
|
||||
|
||||
config = plugin.plugin_config()
|
||||
|
||||
if config and config.active:
|
||||
if plugin.is_active():
|
||||
# Only allow event registering for 'active' plugins
|
||||
|
||||
logger.debug(f"Registering callback for plugin '{slug}'")
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ def print_label(plugin_slug: str, pdf_data, filename=None, label_instance=None,
|
||||
"""
|
||||
logger.info(f"Plugin '{plugin_slug}' is printing a label '{filename}'")
|
||||
|
||||
plugin = registry.plugins.get(plugin_slug, None)
|
||||
plugin = registry.get_plugin(plugin_slug)
|
||||
|
||||
if plugin is None: # pragma: no cover
|
||||
logger.error(f"Could not find matching plugin for '{plugin_slug}'")
|
||||
|
||||
@@ -9,6 +9,8 @@ references model objects actually exist in the database.
|
||||
|
||||
import json
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from company.models import SupplierPart
|
||||
from InvenTree.helpers import hash_barcode
|
||||
from part.models import Part
|
||||
@@ -17,8 +19,14 @@ from plugin.mixins import BarcodeMixin
|
||||
from stock.models import StockItem, StockLocation
|
||||
|
||||
|
||||
class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):
|
||||
"""Generic base class for handling InvenTree barcodes"""
|
||||
class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin):
|
||||
"""Builtin BarcodePlugin for matching and generating internal barcodes."""
|
||||
|
||||
NAME = "InvenTreeBarcode"
|
||||
TITLE = _("Inventree Barcodes")
|
||||
DESCRIPTION = _("Provides native support for barcodes")
|
||||
VERSION = "2.0.0"
|
||||
AUTHOR = _("InvenTree contributors")
|
||||
|
||||
@staticmethod
|
||||
def get_supported_barcode_models():
|
||||
@@ -58,57 +66,42 @@ class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class InvenTreeInternalBarcodePlugin(InvenTreeBarcodePlugin):
|
||||
"""Builtin BarcodePlugin for matching and generating internal barcodes."""
|
||||
|
||||
NAME = "InvenTreeInternalBarcode"
|
||||
|
||||
def scan(self, barcode_data):
|
||||
"""Scan a barcode against this plugin.
|
||||
|
||||
Here we are looking for a dict object which contains a reference to a particular InvenTree database object
|
||||
"""
|
||||
|
||||
# Create hash from raw barcode data
|
||||
barcode_hash = hash_barcode(barcode_data)
|
||||
|
||||
# Attempt to coerce the barcode data into a dict object
|
||||
# This is the internal barcode representation that InvenTree uses
|
||||
barcode_dict = None
|
||||
|
||||
if type(barcode_data) is dict:
|
||||
pass
|
||||
barcode_dict = barcode_data
|
||||
elif type(barcode_data) is str:
|
||||
try:
|
||||
barcode_data = json.loads(barcode_data)
|
||||
barcode_dict = json.loads(barcode_data)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
pass
|
||||
|
||||
if type(barcode_data) is not dict:
|
||||
return None
|
||||
if barcode_dict is not None and type(barcode_dict) is dict:
|
||||
# Look for various matches. First good match will be returned
|
||||
for model in self.get_supported_barcode_models():
|
||||
label = model.barcode_model_type()
|
||||
|
||||
# Look for various matches. First good match will be returned
|
||||
if label in barcode_dict:
|
||||
try:
|
||||
instance = model.objects.get(pk=barcode_dict[label])
|
||||
return self.format_matched_response(label, model, instance)
|
||||
except (ValueError, model.DoesNotExist):
|
||||
pass
|
||||
|
||||
# If no "direct" hits are found, look for assigned third-party barcodes
|
||||
for model in self.get_supported_barcode_models():
|
||||
label = model.barcode_model_type()
|
||||
if label in barcode_data:
|
||||
try:
|
||||
instance = model.objects.get(pk=barcode_data[label])
|
||||
return self.format_matched_response(label, model, instance)
|
||||
except (ValueError, model.DoesNotExist):
|
||||
pass
|
||||
|
||||
|
||||
class InvenTreeExternalBarcodePlugin(InvenTreeBarcodePlugin):
|
||||
"""Builtin BarcodePlugin for matching arbitrary external barcodes."""
|
||||
|
||||
NAME = "InvenTreeExternalBarcode"
|
||||
|
||||
def scan(self, barcode_data):
|
||||
"""Scan a barcode against this plugin.
|
||||
|
||||
Here we are looking for a dict object which contains a reference to a particular InvenTree databse object
|
||||
"""
|
||||
|
||||
for model in self.get_supported_barcode_models():
|
||||
label = model.barcode_model_type()
|
||||
|
||||
barcode_hash = hash_barcode(barcode_data)
|
||||
|
||||
instance = model.lookup_barcode(barcode_hash)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
'barcode': barcode_data,
|
||||
'stockitem': 521
|
||||
},
|
||||
expected_code=400
|
||||
expected_code=400,
|
||||
)
|
||||
|
||||
self.assertIn('error', response.data)
|
||||
@@ -250,7 +250,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
)
|
||||
|
||||
self.assertIn('success', response.data)
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeExternalBarcode')
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
|
||||
self.assertEqual(response.data['part']['pk'], 1)
|
||||
|
||||
# Attempting to assign the same barcode to a different part should result in an error
|
||||
@@ -347,7 +347,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
response = self.scan({'barcode': 'blbla=10004'}, expected_code=200)
|
||||
|
||||
self.assertEqual(response.data['barcode_data'], 'blbla=10004')
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeExternalBarcode')
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
|
||||
|
||||
# Scan for a StockItem instance
|
||||
si = stock.models.StockItem.objects.get(pk=1)
|
||||
@@ -402,7 +402,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
self.assertEqual(response.data['stocklocation']['pk'], 5)
|
||||
self.assertEqual(response.data['stocklocation']['api_url'], '/api/stock/location/5/')
|
||||
self.assertEqual(response.data['stocklocation']['web_url'], '/stock/location/5/')
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeInternalBarcode')
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
|
||||
|
||||
# Scan a Part object
|
||||
response = self.scan(
|
||||
@@ -423,7 +423,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(response.data['supplierpart']['pk'], 1)
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeInternalBarcode')
|
||||
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
|
||||
|
||||
self.assertIn('success', response.data)
|
||||
self.assertIn('barcode_data', response.data)
|
||||
|
||||
@@ -27,8 +27,10 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
||||
"""Core notification methods for InvenTree."""
|
||||
|
||||
NAME = "CoreNotificationsPlugin"
|
||||
TITLE = _("InvenTree Notifications")
|
||||
AUTHOR = _('InvenTree contributors')
|
||||
DESCRIPTION = _('Integrated outgoing notificaton methods')
|
||||
VERSION = "1.0.0"
|
||||
|
||||
SETTINGS = {
|
||||
'ENABLE_NOTIFICATION_EMAILS': {
|
||||
|
||||
@@ -158,16 +158,20 @@ class PluginConfig(models.Model):
|
||||
@admin.display(boolean=True, description=_('Sample plugin'))
|
||||
def is_sample(self) -> bool:
|
||||
"""Is this plugin a sample app?"""
|
||||
# Loaded and active plugin
|
||||
if isinstance(self.plugin, InvenTreePlugin):
|
||||
return self.plugin.check_is_sample()
|
||||
|
||||
# If no plugin_class is available it can not be a sample
|
||||
if not self.plugin:
|
||||
return False
|
||||
|
||||
# Not loaded plugin
|
||||
return self.plugin.check_is_sample() # pragma: no cover
|
||||
return self.plugin.check_is_sample()
|
||||
|
||||
@admin.display(boolean=True, description=_('Builtin Plugin'))
|
||||
def is_builtin(self) -> bool:
|
||||
"""Return True if this is a 'builtin' plugin"""
|
||||
|
||||
if not self.plugin:
|
||||
return False
|
||||
|
||||
return self.plugin.check_is_builtin()
|
||||
|
||||
|
||||
class PluginSetting(common.models.BaseInvenTreeSetting):
|
||||
|
||||
@@ -106,10 +106,15 @@ class MetaBase:
|
||||
|
||||
def is_active(self):
|
||||
"""Return True if this plugin is currently active."""
|
||||
cfg = self.plugin_config()
|
||||
|
||||
if cfg:
|
||||
return cfg.active
|
||||
# Builtin plugins are always considered "active"
|
||||
if self.is_builtin:
|
||||
return True
|
||||
|
||||
config = self.plugin_config()
|
||||
|
||||
if config:
|
||||
return config.active
|
||||
else:
|
||||
return False # pragma: no cover
|
||||
|
||||
@@ -300,6 +305,16 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
|
||||
"""Is this plugin part of the samples?"""
|
||||
return self.check_is_sample()
|
||||
|
||||
@classmethod
|
||||
def check_is_builtin(cls) -> bool:
|
||||
"""Determine if a particular plugin class is a 'builtin' plugin"""
|
||||
return str(cls.check_package_path()).startswith('plugin/builtin')
|
||||
|
||||
@property
|
||||
def is_builtin(self) -> bool:
|
||||
"""Is this plugin is builtin"""
|
||||
return self.check_is_builtin()
|
||||
|
||||
@classmethod
|
||||
def check_package_path(cls):
|
||||
"""Path to the plugin."""
|
||||
|
||||
@@ -108,9 +108,6 @@ class PluginsRegistry:
|
||||
Args:
|
||||
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
|
||||
"""
|
||||
if not settings.PLUGINS_ENABLED:
|
||||
# Plugins not enabled, do nothing
|
||||
return # pragma: no cover
|
||||
|
||||
logger.info('Start loading plugins')
|
||||
|
||||
@@ -167,9 +164,6 @@ class PluginsRegistry:
|
||||
|
||||
def unload_plugins(self):
|
||||
"""Unload and deactivate all IntegrationPlugins."""
|
||||
if not settings.PLUGINS_ENABLED:
|
||||
# Plugins not enabled, do nothing
|
||||
return # pragma: no cover
|
||||
|
||||
logger.info('Start unloading plugins')
|
||||
|
||||
@@ -187,6 +181,7 @@ class PluginsRegistry:
|
||||
# remove maintenance
|
||||
if not _maintenance:
|
||||
set_maintenance_mode(False) # pragma: no cover
|
||||
|
||||
logger.info('Finished unloading plugins')
|
||||
|
||||
def reload_plugins(self, full_reload: bool = False):
|
||||
@@ -210,62 +205,63 @@ class PluginsRegistry:
|
||||
def plugin_dirs(self):
|
||||
"""Construct a list of directories from where plugins can be loaded"""
|
||||
|
||||
# Builtin plugins are *always* loaded
|
||||
dirs = ['plugin.builtin', ]
|
||||
|
||||
if settings.TESTING or settings.DEBUG:
|
||||
# If in TEST or DEBUG mode, load plugins from the 'samples' directory
|
||||
dirs.append('plugin.samples')
|
||||
if settings.PLUGINS_ENABLED:
|
||||
# Any 'external' plugins are only loaded if PLUGINS_ENABLED is set to True
|
||||
|
||||
if settings.TESTING:
|
||||
custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None)
|
||||
else: # pragma: no cover
|
||||
custom_dirs = get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
|
||||
if settings.TESTING or settings.DEBUG:
|
||||
# If in TEST or DEBUG mode, load plugins from the 'samples' directory
|
||||
dirs.append('plugin.samples')
|
||||
|
||||
# Load from user specified directories (unless in testing mode)
|
||||
dirs.append('plugins')
|
||||
if settings.TESTING:
|
||||
custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None)
|
||||
else: # pragma: no cover
|
||||
custom_dirs = get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
|
||||
|
||||
if custom_dirs is not None:
|
||||
# Allow multiple plugin directories to be specified
|
||||
for pd_text in custom_dirs.split(','):
|
||||
pd = Path(pd_text.strip()).absolute()
|
||||
# Load from user specified directories (unless in testing mode)
|
||||
dirs.append('plugins')
|
||||
|
||||
# Attempt to create the directory if it does not already exist
|
||||
if not pd.exists():
|
||||
try:
|
||||
pd.mkdir(exist_ok=True)
|
||||
except Exception: # pragma: no cover
|
||||
logger.error(f"Could not create plugin directory '{pd}'")
|
||||
continue
|
||||
if custom_dirs is not None:
|
||||
# Allow multiple plugin directories to be specified
|
||||
for pd_text in custom_dirs.split(','):
|
||||
pd = Path(pd_text.strip()).absolute()
|
||||
|
||||
# Ensure the directory has an __init__.py file
|
||||
init_filename = pd.joinpath('__init__.py')
|
||||
# Attempt to create the directory if it does not already exist
|
||||
if not pd.exists():
|
||||
try:
|
||||
pd.mkdir(exist_ok=True)
|
||||
except Exception: # pragma: no cover
|
||||
logger.error(f"Could not create plugin directory '{pd}'")
|
||||
continue
|
||||
|
||||
if not init_filename.exists():
|
||||
try:
|
||||
init_filename.write_text("# InvenTree plugin directory\n")
|
||||
except Exception: # pragma: no cover
|
||||
logger.error(f"Could not create file '{init_filename}'")
|
||||
continue
|
||||
# Ensure the directory has an __init__.py file
|
||||
init_filename = pd.joinpath('__init__.py')
|
||||
|
||||
# By this point, we have confirmed that the directory at least exists
|
||||
if pd.exists() and pd.is_dir():
|
||||
# Convert to python dot-path
|
||||
if pd.is_relative_to(settings.BASE_DIR):
|
||||
pd_path = '.'.join(pd.relative_to(settings.BASE_DIR).parts)
|
||||
else:
|
||||
pd_path = str(pd)
|
||||
if not init_filename.exists():
|
||||
try:
|
||||
init_filename.write_text("# InvenTree plugin directory\n")
|
||||
except Exception: # pragma: no cover
|
||||
logger.error(f"Could not create file '{init_filename}'")
|
||||
continue
|
||||
|
||||
# Add path
|
||||
dirs.append(pd_path)
|
||||
logger.info(f"Added plugin directory: '{pd}' as '{pd_path}'")
|
||||
# By this point, we have confirmed that the directory at least exists
|
||||
if pd.exists() and pd.is_dir():
|
||||
# Convert to python dot-path
|
||||
if pd.is_relative_to(settings.BASE_DIR):
|
||||
pd_path = '.'.join(pd.relative_to(settings.BASE_DIR).parts)
|
||||
else:
|
||||
pd_path = str(pd)
|
||||
|
||||
# Add path
|
||||
dirs.append(pd_path)
|
||||
logger.info(f"Added plugin directory: '{pd}' as '{pd_path}'")
|
||||
|
||||
return dirs
|
||||
|
||||
def collect_plugins(self):
|
||||
"""Collect plugins from all possible ways of loading. Returned as list."""
|
||||
if not settings.PLUGINS_ENABLED:
|
||||
# Plugins not enabled, do nothing
|
||||
return # pragma: no cover
|
||||
|
||||
collected_plugins = []
|
||||
|
||||
@@ -293,17 +289,20 @@ class PluginsRegistry:
|
||||
if modules:
|
||||
[collected_plugins.append(item) for item in modules]
|
||||
|
||||
# Check if not running in testing mode and apps should be loaded from hooks
|
||||
if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP):
|
||||
# Collect plugins from setup entry points
|
||||
for entry in get_entrypoints():
|
||||
try:
|
||||
plugin = entry.load()
|
||||
plugin.is_package = True
|
||||
plugin._get_package_metadata()
|
||||
collected_plugins.append(plugin)
|
||||
except Exception as error: # pragma: no cover
|
||||
handle_error(error, do_raise=False, log_name='discovery')
|
||||
# From this point any plugins are considered "external" and only loaded if plugins are explicitly enabled
|
||||
if settings.PLUGINS_ENABLED:
|
||||
|
||||
# Check if not running in testing mode and apps should be loaded from hooks
|
||||
if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP):
|
||||
# Collect plugins from setup entry points
|
||||
for entry in get_entrypoints():
|
||||
try:
|
||||
plugin = entry.load()
|
||||
plugin.is_package = True
|
||||
plugin._get_package_metadata()
|
||||
collected_plugins.append(plugin)
|
||||
except Exception as error: # pragma: no cover
|
||||
handle_error(error, do_raise=False, log_name='discovery')
|
||||
|
||||
# Log collected plugins
|
||||
logger.info(f'Collected {len(collected_plugins)} plugins!')
|
||||
@@ -335,7 +334,7 @@ class PluginsRegistry:
|
||||
# endregion
|
||||
|
||||
# region registry functions
|
||||
def with_mixin(self, mixin: str, active=None):
|
||||
def with_mixin(self, mixin: str, active=None, builtin=None):
|
||||
"""Returns reference to all plugins that have a specified mixin enabled."""
|
||||
result = []
|
||||
|
||||
@@ -343,10 +342,13 @@ class PluginsRegistry:
|
||||
if plugin.mixin_enabled(mixin):
|
||||
|
||||
if active is not None:
|
||||
# Filter by 'enabled' status
|
||||
config = plugin.plugin_config()
|
||||
# Filter by 'active' status of plugin
|
||||
if active != plugin.is_active():
|
||||
continue
|
||||
|
||||
if config.active != active:
|
||||
if builtin is not None:
|
||||
# Filter by 'builtin' status of plugin
|
||||
if builtin != plugin.is_builtin:
|
||||
continue
|
||||
|
||||
result.append(plugin)
|
||||
@@ -403,8 +405,14 @@ class PluginsRegistry:
|
||||
# Append reference to plugin
|
||||
plg.db = plg_db
|
||||
|
||||
# Always activate if testing
|
||||
if settings.PLUGIN_TESTING or (plg_db and plg_db.active):
|
||||
# Check if this is a 'builtin' plugin
|
||||
builtin = plg.check_is_builtin()
|
||||
|
||||
# Determine if this plugin should be loaded:
|
||||
# - If PLUGIN_TESTING is enabled
|
||||
# - If this is a 'builtin' plugin
|
||||
# - If this plugin has been explicitly enabled by the user
|
||||
if settings.PLUGIN_TESTING or builtin or (plg_db and plg_db.active):
|
||||
# Check if the plugin was blocked -> threw an error; option1: package, option2: file-based
|
||||
if disabled and ((plg.__name__ == disabled) or (plg.__module__ == disabled)):
|
||||
safe_reference(plugin=plg, key=plg_key, active=False)
|
||||
@@ -498,10 +506,9 @@ class PluginsRegistry:
|
||||
for _key, plugin in plugins:
|
||||
|
||||
if plugin.mixin_enabled('schedule'):
|
||||
config = plugin.plugin_config()
|
||||
|
||||
# Only active tasks for plugins which are enabled
|
||||
if config and config.active:
|
||||
if plugin.is_active():
|
||||
# Only active tasks for plugins which are enabled
|
||||
plugin.register_tasks()
|
||||
task_keys += plugin.get_task_names()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Unit tests for action plugins."""
|
||||
|
||||
from InvenTree.helpers import InvenTreeTestCase
|
||||
from plugin.builtin.action.simpleactionplugin import SimpleActionPlugin
|
||||
from plugin.samples.integration.simpleactionplugin import SimpleActionPlugin
|
||||
|
||||
|
||||
class SimpleActionPluginTests(InvenTreeTestCase):
|
||||
@@ -28,25 +28,38 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
url = reverse('api-plugin-install')
|
||||
|
||||
# valid - Pypi
|
||||
data = self.post(url, {
|
||||
'confirm': True,
|
||||
'packagename': self.PKG_NAME
|
||||
}, expected_code=201).data
|
||||
data = self.post(
|
||||
url,
|
||||
{
|
||||
'confirm': True,
|
||||
'packagename': self.PKG_NAME
|
||||
},
|
||||
expected_code=201,
|
||||
).data
|
||||
|
||||
self.assertEqual(data['success'], True)
|
||||
|
||||
# valid - github url
|
||||
data = self.post(url, {
|
||||
'confirm': True,
|
||||
'url': self.PKG_URL
|
||||
}, expected_code=201).data
|
||||
data = self.post(
|
||||
url,
|
||||
{
|
||||
'confirm': True,
|
||||
'url': self.PKG_URL
|
||||
},
|
||||
expected_code=201,
|
||||
).data
|
||||
self.assertEqual(data['success'], True)
|
||||
|
||||
# valid - github url and packagename
|
||||
data = self.post(url, {
|
||||
'confirm': True,
|
||||
'url': self.PKG_URL,
|
||||
'packagename': 'minimal',
|
||||
}, expected_code=201).data
|
||||
data = self.post(
|
||||
url,
|
||||
{
|
||||
'confirm': True,
|
||||
'url': self.PKG_URL,
|
||||
'packagename': 'minimal',
|
||||
},
|
||||
expected_code=201,
|
||||
).data
|
||||
self.assertEqual(data['success'], True)
|
||||
|
||||
# invalid tries
|
||||
@@ -57,17 +70,20 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
data = self.post(url, {
|
||||
'confirm': True,
|
||||
}, expected_code=400).data
|
||||
|
||||
self.assertEqual(data['url'][0].title().upper(), self.MSG_NO_PKG.upper())
|
||||
self.assertEqual(data['packagename'][0].title().upper(), self.MSG_NO_PKG.upper())
|
||||
|
||||
# not confirmed
|
||||
self.post(url, {
|
||||
'packagename': self.PKG_NAME
|
||||
}, expected_code=400).data
|
||||
}, expected_code=400)
|
||||
|
||||
data = self.post(url, {
|
||||
'packagename': self.PKG_NAME,
|
||||
'confirm': False,
|
||||
}, expected_code=400).data
|
||||
|
||||
self.assertEqual(data['confirm'][0].title().upper(), 'Installation not confirmed'.upper())
|
||||
|
||||
def test_admin_action(self):
|
||||
|
||||
@@ -305,6 +305,7 @@ class TestReportTest(ReportTest):
|
||||
InvenTreeSetting.set_setting('REPORT_ATTACH_TEST_REPORT', True, None)
|
||||
|
||||
response = self.get(url, {'item': item.pk}, expected_code=200)
|
||||
|
||||
headers = response.headers
|
||||
self.assertEqual(headers['Content-Type'], 'application/pdf')
|
||||
|
||||
|
||||
@@ -53,12 +53,21 @@
|
||||
{% else %}
|
||||
<li><a class='dropdown-item' href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li>
|
||||
{% endif %}
|
||||
{% if labels_enabled %}
|
||||
<li><a class='dropdown-item' href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
|
||||
{% endif %}
|
||||
<li><a class='dropdown-item' href='#' id='barcode-check-in'><span class='fas fa-arrow-right'></span> {% trans "Check-in Items" %}</a></li>
|
||||
<li><a class='dropdown-item' href='#' id='barcode-scan-in-items' title='{% trans "Scan stock items into this location" %}'><span class='fas fa-boxes'></span> {% trans "Scan In Stock Items" %}</a></li>
|
||||
<li><a class='dropdown-item' href='#' id='barcode-scan-in-containers' title='{% trans "Scan stock container into this location" %}'><span class='fas fa-sitemap'></span> {% trans "Scan In Container" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Printing action -->
|
||||
{% if labels_enabled %}
|
||||
<div class='btn-group' role='group'>
|
||||
<button id='printing-options' title='{% trans "Printing actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
|
||||
<span class='fas fa-print'></span> <span class='caret'></span>
|
||||
</button>
|
||||
<ul class='dropdown-menu'>
|
||||
<li><a class='dropdown-item' href='#' id='print-label'><span class='fas fa-print'></span> {% trans "Print Label" %}</a>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Check permissions and owner -->
|
||||
{% if user_owns_location %}
|
||||
{% if roles.stock.change %}
|
||||
@@ -96,11 +105,6 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if user_owns_location and roles.stock_location.add %}
|
||||
<button class='btn btn-success' id='location-create' type='button' title='{% trans "Create new stock location" %}'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "New Location" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block details_left %}
|
||||
@@ -203,7 +207,17 @@
|
||||
|
||||
<div class='panel panel-hidden' id='panel-sublocations'>
|
||||
<div class='panel-heading'>
|
||||
<h4>{% trans "Sublocations" %}</h4>
|
||||
<div class='d-flex flex-wrap'>
|
||||
<h4>{% trans "Sublocations" %}</h4>
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% if user_owns_location and roles.stock_location.add %}
|
||||
<button class='btn btn-success' id='location-create' type='button' title='{% trans "Create new stock location" %}'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "New Location" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
<div id='sublocation-button-toolbar'>
|
||||
@@ -284,13 +298,29 @@
|
||||
{% endif %}
|
||||
|
||||
{% if location %}
|
||||
$("#barcode-check-in").click(function() {
|
||||
barcodeCheckIn({{ location.id }});
|
||||
$("#barcode-scan-in-items").click(function() {
|
||||
barcodeCheckInStockItems({{ location.id }});
|
||||
});
|
||||
|
||||
$('#barcode-scan-in-containers').click(function() {
|
||||
barcodeCheckInStockLocations({{ location.id }},
|
||||
{
|
||||
onSuccess: function() {
|
||||
showMessage(
|
||||
'{% trans "Scanned stock container into this location" %}',
|
||||
{
|
||||
style: 'success',
|
||||
}
|
||||
);
|
||||
|
||||
$('#sublocation-table').bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
$('#location-create').click(function () {
|
||||
|
||||
createStockLocation({
|
||||
{% if location %}
|
||||
parent: {{ location.pk }},
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="BARCODE_ENABLE" icon="fa-qrcode" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="BARCODE_INPUT_DELAY" icon="fa-hourglass-half" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="BARCODE_WEBCAM_SUPPORT" icon="fa-video" %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% plugins_enabled as plug %}
|
||||
|
||||
<div class='panel-heading'>
|
||||
<div class='d-flex flex-wrap'>
|
||||
<h4>{% trans "Plugins" %}</h4>
|
||||
@@ -38,78 +40,46 @@
|
||||
<div class='btn-group' role='group'>
|
||||
{% url 'admin:plugin_pluginconfig_changelist' as url %}
|
||||
{% include "admin_button.html" with url=url %}
|
||||
{% if plug %}
|
||||
<button class="btn btn-success" id="install-plugin" title="{% trans 'Install Plugin' %}"><span class='fas fa-plus-circle'></span> {% trans "Install Plugin" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not plug %}
|
||||
<div class='alert alert-warning alert-block'>
|
||||
{% trans "External plugins are not enabled for this InvenTree installation" %}<br>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class='table-responsive'>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Admin" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Key" %}</th>
|
||||
<th>{% trans "Author" %}</th>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "Version" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% plugin_list as pl_list %}
|
||||
{% if pl_list %}
|
||||
<tr><td colspan="6"><h6>{% trans 'Active plugins' %}</h6></td></tr>
|
||||
{% for plugin_key, plugin in pl_list.items %}
|
||||
{% mixin_enabled plugin 'urls' as urls %}
|
||||
{% mixin_enabled plugin 'settings' as settings %}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{% if user.is_staff and perms.plugin.change_pluginconfig %}
|
||||
{% url 'admin:plugin_pluginconfig_change' plugin.pk as url %}
|
||||
{% include "admin_button.html" with url=url %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ plugin.human_name }}<span class="text-muted"> - {{plugin_key}}</span>
|
||||
{% define plugin.registered_mixins as mixin_list %}
|
||||
|
||||
{% if plugin.is_sample %}
|
||||
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
|
||||
<span class='badge bg-info rounded-pill badge-right'>{% trans "Sample" %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if mixin_list %}
|
||||
{% for mixin in mixin_list %}
|
||||
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
|
||||
<span class='badge bg-dark badge-right rounded-pill'>{{ mixin.human_name }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if plugin.website %}
|
||||
<a href="{{ plugin.website }}"><span class="fas fa-globe"></span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ plugin.author }}</td>
|
||||
<td>{% render_date plugin.pub_date %}</td>
|
||||
<td>{% if plugin.version %}{{ plugin.version }}{% endif %}</td>
|
||||
</tr>
|
||||
{% include "InvenTree/settings/plugin_details.html" with plugin=plugin plugin_key=plugin_key %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% inactive_plugin_list as in_pl_list %}
|
||||
{% if in_pl_list %}
|
||||
<tr><td colspan="5"></td></tr>
|
||||
<tr><td colspan="5"><h6>{% trans 'Inactive plugins' %}</h6></td></tr>
|
||||
<tr><td colspan="6"><h6>{% trans 'Inactive plugins' %}</h6></td></tr>
|
||||
{% for plugin_key, plugin in in_pl_list.items %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if user.is_staff and perms.plugin.change_pluginconfig %}
|
||||
{% url 'admin:plugin_pluginconfig_change' plugin.pk as url %}
|
||||
{% include "admin_button.html" with url=url %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{plugin.name}}<span class="text-muted"> - {{plugin.key}}</span></td>
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
{% include "InvenTree/settings/plugin_details.html" with plugin=plugin plugin_key=plugin_key %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
|
||||
75
InvenTree/templates/InvenTree/settings/plugin_details.html
Normal file
75
InvenTree/templates/InvenTree/settings/plugin_details.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{% if plugin.is_active %}
|
||||
<span class='fas fa-check-circle icon-green'></span>
|
||||
{% else %}
|
||||
<span class='fas fa-times-circle icon-red'></span>
|
||||
{% endif %}
|
||||
|
||||
{% if plugin.human_name %}
|
||||
{{ plugin.human_name }}
|
||||
{% elif plugin.title %}
|
||||
{{ plugin.title }}
|
||||
{% elif plugin.name %}
|
||||
{{ plugin.name }}
|
||||
{% endif %}
|
||||
|
||||
{% define plugin.registered_mixins as mixin_list %}
|
||||
|
||||
{% if mixin_list %}
|
||||
{% for mixin in mixin_list %}
|
||||
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
|
||||
<span class='badge bg-dark badge-right rounded-pill'>{{ mixin.human_name }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if plugin.is_builtin %}
|
||||
<a class='sidebar-selector' id='select-plugin-{{ plugin_key }}' data-bs-parent='#sidebar'>
|
||||
<span class='badge bg-success rounded-pill badge-right'>{% trans "Builtin" %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if plugin.is_sample %}
|
||||
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
|
||||
<span class='badge bg-info rounded-pill badge-right'>{% trans "Sample" %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if plugin.website %}
|
||||
<a href="{{ plugin.website }}"><span class="fas fa-globe"></span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ plugin_key }}</td>
|
||||
{% trans "Unvailable" as no_info %}
|
||||
<td>
|
||||
{% if plugin.author %}
|
||||
{{ plugin.author }}
|
||||
{% else %}
|
||||
<em>{{ no_info }}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if plugin.pub_date %}
|
||||
{% render_date plugin.pub_date %}
|
||||
{% else %}
|
||||
<em>{{ no_info }}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if plugin.version %}
|
||||
{{ plugin.version }}
|
||||
{% else %}
|
||||
<em>{{ no_info }}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if user.is_staff and perms.plugin.change_pluginconfig %}
|
||||
{% url 'admin:plugin_pluginconfig_change' plugin.pk as url %}
|
||||
{% include "admin_button.html" with url=url %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -23,16 +23,16 @@
|
||||
<td>{% trans "Name" %}</td>
|
||||
<td>{{ plugin.human_name }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-user'></span></span></td>
|
||||
<td>{% trans "Author" %}</td>
|
||||
<td>{{ plugin.author }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{% trans "Description" %}</td>
|
||||
<td>{{ plugin.description }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-user'></span></span></td>
|
||||
<td>{% trans "Author" %}</td>
|
||||
<td>{{ plugin.author }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Date" %}</td>
|
||||
@@ -94,7 +94,14 @@
|
||||
<td>{% trans "Installation path" %}</td>
|
||||
<td>{{ plugin.package_path }}</td>
|
||||
</tr>
|
||||
{% if plugin.is_package == False %}
|
||||
{% if plugin.is_package %}
|
||||
{% elif plugin.is_builtin %}
|
||||
<tr>
|
||||
<td><span class='fas fa-check-circle icon-green'></span></td>
|
||||
<td>{% trans "Builtin" %}</td>
|
||||
<td>{% trans "This is a builtin plugin which cannot be disabled" %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td><span class='fas fa-user'></span></td>
|
||||
<td>{% trans "Commit Author" %}</td><td>{{ plugin.package.author }} - {{ plugin.package.mail }}{% include "clip.html" %}</td>
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
{% include "InvenTree/settings/po.html" %}
|
||||
{% include "InvenTree/settings/so.html" %}
|
||||
|
||||
{% plugins_enabled as plug %}
|
||||
{% if plug %}
|
||||
{% include "InvenTree/settings/plugin.html" %}
|
||||
{% plugin_list as pl_list %}
|
||||
{% for plugin_key, plugin in pl_list.items %}
|
||||
@@ -51,7 +49,6 @@
|
||||
{% include "InvenTree/settings/plugin_settings.html" %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -51,10 +51,11 @@
|
||||
{% trans "Sales Orders" as text %}
|
||||
{% include "sidebar_item.html" with label='sales-order' text=text icon="fa-truck" %}
|
||||
|
||||
{% plugins_enabled as plug %}
|
||||
{% if plug %}
|
||||
{% include "sidebar_header.html" with text="Plugin Settings" %}
|
||||
{% include "sidebar_item.html" with label='plugin' text="Plugins" icon="fa-plug" %}
|
||||
|
||||
{% trans "Plugin Settings" as text %}
|
||||
{% include "sidebar_header.html" with text=text %}
|
||||
{% trans "Plugins" as text %}
|
||||
{% include "sidebar_item.html" with label='plugin' text=text icon="fa-plug" %}
|
||||
|
||||
{% plugin_list as pl_list %}
|
||||
{% for plugin_key, plugin in pl_list.items %}
|
||||
@@ -62,6 +63,5 @@
|
||||
{% include "sidebar_item.html" with label='plugin-'|add:plugin_key text=plugin.human_name %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
<button id='admin-button' title='{% trans "View in administration panel" %}' type='button' class='btn btn-primary admin-button' url='{{ url }}'>
|
||||
<span class='fas fa-user-shield'></span>
|
||||
</button>
|
||||
|
||||
{% inventree_customize 'hide_admin_link' as hidden %}
|
||||
|
||||
{% if not hidden and user.is_staff %}
|
||||
<a href='{{ url }}'>
|
||||
<button id='admin-button' href='{{ url }}' title='{% trans "View in administration panel" %}' type='button' class='btn btn-primary admin-button'>
|
||||
<span class='fas fa-user-shield'></span>
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
*/
|
||||
|
||||
/* exported
|
||||
barcodeCheckIn,
|
||||
barcodeCheckInStockItems,
|
||||
barcodeCheckInStockLocations,
|
||||
barcodeScanDialog,
|
||||
linkBarcodeDialog,
|
||||
scanItemsIntoLocation,
|
||||
@@ -22,12 +23,14 @@
|
||||
onBarcodeScanClicked,
|
||||
*/
|
||||
|
||||
function makeBarcodeInput(placeholderText='', hintText='') {
|
||||
/*
|
||||
* Generate HTML for a barcode input
|
||||
*/
|
||||
var barcodeInputTimer = null;
|
||||
|
||||
placeholderText = placeholderText || '{% trans "Scan barcode data here using wedge scanner" %}';
|
||||
/*
|
||||
* Generate HTML for a barcode scan input
|
||||
*/
|
||||
function makeBarcodeInput(placeholderText='', hintText='') {
|
||||
|
||||
placeholderText = placeholderText || '{% trans "Scan barcode data here using barcode scanner" %}';
|
||||
|
||||
hintText = hintText || '{% trans "Enter barcode data" %}';
|
||||
|
||||
@@ -43,7 +46,7 @@ function makeBarcodeInput(placeholderText='', hintText='') {
|
||||
<span class='fas fa-qrcode'></span>
|
||||
</span>
|
||||
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
|
||||
<button id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'>
|
||||
<button title='{% trans "Scan barcode using connected webcam" %}' id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'>
|
||||
<span class='fas fa-camera'></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -92,6 +95,9 @@ function onBarcodeScanCompleted(result, options) {
|
||||
postBarcodeData(result.data, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a generic "notes" field for barcode scanning operations
|
||||
*/
|
||||
function makeNotesField(options={}) {
|
||||
|
||||
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}';
|
||||
@@ -199,6 +205,9 @@ function showBarcodeMessage(modal, message, style='danger') {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Display an error message when the server indicates an error
|
||||
*/
|
||||
function showInvalidResponseError(modal, response, status) {
|
||||
showBarcodeMessage(
|
||||
modal,
|
||||
@@ -207,6 +216,9 @@ function showInvalidResponseError(modal, response, status) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Enable (or disable) the barcode scanning input
|
||||
*/
|
||||
function enableBarcodeInput(modal, enabled=true) {
|
||||
|
||||
var barcode = $(modal + ' #barcode');
|
||||
@@ -218,6 +230,10 @@ function enableBarcodeInput(modal, enabled=true) {
|
||||
barcode.focus();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Extract scanned data from the barcode input
|
||||
*/
|
||||
function getBarcodeData(modal) {
|
||||
|
||||
modal = modal || '#modal-form';
|
||||
@@ -233,10 +249,10 @@ function getBarcodeData(modal) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handle a barcode display dialog.
|
||||
*/
|
||||
function barcodeDialog(title, options={}) {
|
||||
/*
|
||||
* Handle a barcode display dialog.
|
||||
*/
|
||||
|
||||
var modal = '#modal-form';
|
||||
|
||||
@@ -244,7 +260,6 @@ function barcodeDialog(title, options={}) {
|
||||
var barcode = getBarcodeData(modal);
|
||||
|
||||
if (barcode && barcode.length > 0) {
|
||||
|
||||
postBarcodeData(barcode, options);
|
||||
}
|
||||
}
|
||||
@@ -264,7 +279,15 @@ function barcodeDialog(title, options={}) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.which == 10 || event.which == 13) {
|
||||
clearTimeout(barcodeInputTimer);
|
||||
sendBarcode();
|
||||
} else {
|
||||
// Start a timer to automatically send barcode after input is complete
|
||||
clearTimeout(barcodeInputTimer);
|
||||
|
||||
barcodeInputTimer = setTimeout(function() {
|
||||
sendBarcode();
|
||||
}, global_settings.BARCODE_INPUT_DELAY);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -305,9 +328,11 @@ function barcodeDialog(title, options={}) {
|
||||
modalShowSubmitButton(modal, false);
|
||||
}
|
||||
|
||||
var details = options.details || '{% trans "Scan barcode data" %}';
|
||||
|
||||
var content = '';
|
||||
|
||||
content += `<div class='alert alert-info alert-block'>{% trans "Scan barcode data below" %}</div>`;
|
||||
content += `<div class='alert alert-info alert-block'>${details}</div>`;
|
||||
|
||||
content += `<div id='barcode-error-message'></div>`;
|
||||
content += `<form class='js-modal-form' method='post'>`;
|
||||
@@ -431,7 +456,7 @@ function unlinkBarcode(data, options={}) {
|
||||
/*
|
||||
* Display dialog to check multiple stock items in to a stock location.
|
||||
*/
|
||||
function barcodeCheckIn(location_id, options={}) {
|
||||
function barcodeCheckInStockItems(location_id, options={}) {
|
||||
|
||||
var modal = '#modal-form';
|
||||
|
||||
@@ -486,6 +511,7 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
|
||||
$(modal + ' #barcode').focus();
|
||||
|
||||
// Callback to remove the scanned item from the table
|
||||
$(modal + ' .button-item-remove').unbind('click').on('mouseup', function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
@@ -514,8 +540,9 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
var extra = makeNotesField();
|
||||
|
||||
barcodeDialog(
|
||||
'{% trans "Check Stock Items into Location" %}',
|
||||
'{% trans "Scan Stock Items Into Location" %}',
|
||||
{
|
||||
details: '{% trans "Scan stock item barcode to check in to this location" %}',
|
||||
headerContent: table,
|
||||
preShow: function() {
|
||||
modalSetSubmitText(modal, '{% trans "Check In" %}');
|
||||
@@ -609,7 +636,7 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
);
|
||||
} else {
|
||||
// Barcode does not match a stock item
|
||||
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', 'warning');
|
||||
showBarcodeMessage(modal, '{% trans "Barcode does not match valid stock item" %}', 'warning');
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -617,6 +644,59 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Display dialog to scan stock locations into the current location
|
||||
*/
|
||||
function barcodeCheckInStockLocations(location_id, options={}) {
|
||||
|
||||
var modal = '#modal-form';
|
||||
var header = '';
|
||||
|
||||
barcodeDialog(
|
||||
'{% trans "Scan Stock Container Into Location" %}',
|
||||
{
|
||||
details: '{% trans "Scan stock container barcode to check in to this location" %}',
|
||||
headerContent: header,
|
||||
preShow: function() {
|
||||
modalEnable(modal, false);
|
||||
},
|
||||
onShow: function() {
|
||||
// TODO
|
||||
},
|
||||
onScan: function(response) {
|
||||
if ('stocklocation' in response) {
|
||||
var pk = response.stocklocation.pk;
|
||||
|
||||
var url = `/api/stock/location/${pk}/`;
|
||||
|
||||
// Move the scanned location into *this* location
|
||||
inventreePut(
|
||||
url,
|
||||
{
|
||||
parent: location_id,
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
success: function(response) {
|
||||
$(modal).modal('hide');
|
||||
handleFormSuccess(response, options);
|
||||
},
|
||||
error: function(xhr) {
|
||||
$(modal).modal('hide');
|
||||
showApiError(xhr, url);
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Barcode does not match a valid stock location
|
||||
showBarcodeMessage(modal, '{% trans "Barcode does not match valid stock location" %}', 'warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Display dialog to check a single stock item into a stock location
|
||||
*/
|
||||
|
||||
@@ -2473,9 +2473,6 @@ function loadPurchaseOrderExtraLineTable(table, options={}) {
|
||||
|
||||
setupFilterList('purchaseorderextraline', $(table), filter_target);
|
||||
|
||||
// Is the order pending?
|
||||
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
||||
|
||||
// Table columns to display
|
||||
var columns = [
|
||||
{
|
||||
@@ -2555,26 +2552,24 @@ function loadPurchaseOrderExtraLineTable(table, options={}) {
|
||||
title: '{% trans "Notes" %}',
|
||||
});
|
||||
|
||||
if (pending) {
|
||||
columns.push({
|
||||
field: 'buttons',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
columns.push({
|
||||
field: 'buttons',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
var pk = row.pk;
|
||||
var pk = row.pk;
|
||||
|
||||
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
|
||||
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
|
||||
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
});
|
||||
}
|
||||
return html;
|
||||
}
|
||||
});
|
||||
|
||||
function reloadTable() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
@@ -4320,9 +4315,6 @@ function loadSalesOrderExtraLineTable(table, options={}) {
|
||||
|
||||
setupFilterList('salesorderextraline', $(table), filter_target);
|
||||
|
||||
// Is the order pending?
|
||||
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
||||
|
||||
// Table columns to display
|
||||
var columns = [
|
||||
{
|
||||
@@ -4402,26 +4394,24 @@ function loadSalesOrderExtraLineTable(table, options={}) {
|
||||
title: '{% trans "Notes" %}',
|
||||
});
|
||||
|
||||
if (pending) {
|
||||
columns.push({
|
||||
field: 'buttons',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
columns.push({
|
||||
field: 'buttons',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
var pk = row.pk;
|
||||
var pk = row.pk;
|
||||
|
||||
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
|
||||
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
|
||||
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
});
|
||||
}
|
||||
return html;
|
||||
}
|
||||
});
|
||||
|
||||
function reloadTable() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
|
||||
@@ -1878,15 +1878,16 @@ function loadPartCategoryTable(table, options) {
|
||||
},
|
||||
event: () => {
|
||||
inventreeSave('category-tree-view', 0);
|
||||
table.bootstrapTable(
|
||||
'refreshOptions',
|
||||
{
|
||||
treeEnable: false,
|
||||
serverSort: true,
|
||||
search: true,
|
||||
pagination: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Adjust table options
|
||||
options.treeEnable = false;
|
||||
options.serverSort = false;
|
||||
options.search = true;
|
||||
options.pagination = true;
|
||||
|
||||
// Destroy and re-create the table
|
||||
table.bootstrapTable('destroy');
|
||||
loadPartCategoryTable(table, options);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1897,15 +1898,16 @@ function loadPartCategoryTable(table, options) {
|
||||
},
|
||||
event: () => {
|
||||
inventreeSave('category-tree-view', 1);
|
||||
table.bootstrapTable(
|
||||
'refreshOptions',
|
||||
{
|
||||
treeEnable: true,
|
||||
serverSort: false,
|
||||
search: false,
|
||||
pagination: false,
|
||||
}
|
||||
);
|
||||
|
||||
// Adjust table options
|
||||
options.treeEnable = true;
|
||||
options.serverSort = false;
|
||||
options.search = false;
|
||||
options.pagination = false;
|
||||
|
||||
// Destroy and re-create the table
|
||||
table.bootstrapTable('destroy');
|
||||
loadPartCategoryTable(table, options);
|
||||
}
|
||||
}
|
||||
] : [],
|
||||
|
||||
@@ -2351,15 +2351,16 @@ function loadStockLocationTable(table, options) {
|
||||
},
|
||||
event: () => {
|
||||
inventreeSave('location-tree-view', 0);
|
||||
table.bootstrapTable(
|
||||
'refreshOptions',
|
||||
{
|
||||
treeEnable: false,
|
||||
serverSort: true,
|
||||
search: true,
|
||||
pagination: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Adjust table options
|
||||
options.treeEnable = false;
|
||||
options.serverSort = true;
|
||||
options.search = true;
|
||||
options.pagination = true;
|
||||
|
||||
// Destroy and re-create the table
|
||||
table.bootstrapTable('destroy');
|
||||
loadStockLocationTable(table, options);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2370,15 +2371,16 @@ function loadStockLocationTable(table, options) {
|
||||
},
|
||||
event: () => {
|
||||
inventreeSave('location-tree-view', 1);
|
||||
table.bootstrapTable(
|
||||
'refreshOptions',
|
||||
{
|
||||
treeEnable: true,
|
||||
serverSort: false,
|
||||
search: false,
|
||||
pagination: false,
|
||||
}
|
||||
);
|
||||
|
||||
// Adjust table options
|
||||
options.treeEnable = true;
|
||||
options.serverSort = false;
|
||||
options.search = false;
|
||||
options.pagination = false;
|
||||
|
||||
// Destroy and re-create the table
|
||||
table.bootstrapTable('destroy');
|
||||
loadStockLocationTable(table, options);
|
||||
}
|
||||
}
|
||||
] : [],
|
||||
|
||||
@@ -14,5 +14,5 @@ INVENTREE_DB_PORT=5432
|
||||
INVENTREE_DB_USER=pguser
|
||||
INVENTREE_DB_PASSWORD=pgpassword
|
||||
|
||||
# Enable plugins?
|
||||
# Enable custom plugins?
|
||||
INVENTREE_PLUGINS_ENABLED=True
|
||||
|
||||
@@ -41,7 +41,7 @@ INVENTREE_DB_PORT=5432
|
||||
#INVENTREE_CACHE_HOST=inventree-cache
|
||||
#INVENTREE_CACHE_PORT=6379
|
||||
|
||||
# Enable plugins?
|
||||
# Enable custom plugins?
|
||||
INVENTREE_PLUGINS_ENABLED=False
|
||||
|
||||
# Image tag that should be used
|
||||
|
||||
Reference in New Issue
Block a user