mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' into docs-screenshots
This commit is contained in:
commit
be83802b95
6
.github/scripts/version_check.py
vendored
6
.github/scripts/version_check.py
vendored
@ -10,6 +10,7 @@ tagged branch:
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@ -198,10 +199,13 @@ if __name__ == '__main__':
|
||||
print(f"Version check passed for '{version}'!")
|
||||
print(f"Docker tags: '{docker_tags}'")
|
||||
|
||||
target_repos = [REPO.lower(), f'ghcr.io/{REPO.lower()}']
|
||||
|
||||
# Ref: https://getridbug.com/python/how-to-set-environment-variables-in-github-actions-using-python/
|
||||
with open(os.getenv('GITHUB_ENV'), 'a') as env_file:
|
||||
# Construct tag string
|
||||
tags = ','.join([f'{REPO.lower()}:{tag}' for tag in docker_tags])
|
||||
tag_list = [[f'{r}:{t}' for t in docker_tags] for r in target_repos]
|
||||
tags = ','.join(itertools.chain(*tag_list))
|
||||
|
||||
env_file.write(f'docker_tags={tags}\n')
|
||||
|
||||
|
8
.github/workflows/docker.yaml
vendored
8
.github/workflows/docker.yaml
vendored
@ -130,14 +130,14 @@ jobs:
|
||||
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # pin@v3.6.1
|
||||
- name: Set up cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # pin@v3.5.0
|
||||
uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # pin@v3.6.0
|
||||
- name: Check if Dockerhub login is required
|
||||
id: docker_login
|
||||
run: |
|
||||
if [ -z "${{ secrets.DOCKER_USERNAME }}" ]; then
|
||||
echo "skip_dockerhub_login=true" >> $GITHUB_ENV
|
||||
echo "skip_dockerhub_login=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "skip_dockerhub_login=false" >> $GITHUB_ENV
|
||||
echo "skip_dockerhub_login=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Login to Dockerhub
|
||||
if: github.event_name != 'pull_request' && steps.docker_login.outputs.skip_dockerhub_login != 'true'
|
||||
@ -166,7 +166,7 @@ jobs:
|
||||
- name: Push Docker Images
|
||||
id: push-docker
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # pin@v6.5.0
|
||||
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # pin@v6.6.1
|
||||
with:
|
||||
context: .
|
||||
file: ./contrib/container/Dockerfile
|
||||
|
6
.github/workflows/qc_checks.yaml
vendored
6
.github/workflows/qc_checks.yaml
vendored
@ -159,7 +159,7 @@ jobs:
|
||||
- name: Export API Documentation
|
||||
run: invoke schema --ignore-warnings --filename src/backend/InvenTree/schema.yml
|
||||
- name: Upload schema
|
||||
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # pin@v4.3.5
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4.3.6
|
||||
with:
|
||||
name: schema.yml
|
||||
path: src/backend/InvenTree/schema.yml
|
||||
@ -535,7 +535,7 @@ jobs:
|
||||
- name: Run Playwright tests
|
||||
id: tests
|
||||
run: cd src/frontend && npx nyc playwright test --project desktop_*
|
||||
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # pin@v4
|
||||
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4
|
||||
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
|
||||
with:
|
||||
name: playwright-report
|
||||
@ -573,7 +573,7 @@ jobs:
|
||||
run: |
|
||||
cd src/backend/InvenTree/web/static
|
||||
zip -r frontend-build.zip web/ web/.vite
|
||||
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # pin@v4.3.5
|
||||
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # pin@v4.3.6
|
||||
with:
|
||||
name: frontend-build
|
||||
path: src/backend/InvenTree/web/static/web
|
||||
|
4
.github/workflows/scorecard.yaml
vendored
4
.github/workflows/scorecard.yaml
vendored
@ -59,7 +59,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@ -67,6 +67,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
|
||||
uses: github/codeql-action/upload-sarif@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
ARG base_image=python:3.11-alpine3.18
|
||||
FROM ${base_image} AS inventree_base
|
||||
ARG base_image
|
||||
|
||||
# Build arguments for this image
|
||||
ARG commit_tag=""
|
||||
@ -48,13 +49,18 @@ ENV INVENTREE_BACKGROUND_WORKERS="4"
|
||||
ENV INVENTREE_WEB_ADDR=0.0.0.0
|
||||
ENV INVENTREE_WEB_PORT=8000
|
||||
|
||||
LABEL org.label-schema.schema-version="1.0" \
|
||||
org.label-schema.build-date=${DATE} \
|
||||
org.label-schema.vendor="inventree" \
|
||||
org.label-schema.name="inventree/inventree" \
|
||||
org.label-schema.url="https://hub.docker.com/r/inventree/inventree" \
|
||||
org.label-schema.vcs-url="https://github.com/inventree/InvenTree.git" \
|
||||
org.label-schema.vcs-ref=${commit_tag}
|
||||
LABEL org.opencontainers.image.created=${DATE} \
|
||||
org.opencontainers.image.vendor="inventree" \
|
||||
org.opencontainers.image.title="InvenTree backend server" \
|
||||
org.opencontainers.image.description="InvenTree is the open-source inventory management system" \
|
||||
org.opencontainers.image.url="https://inventree.org" \
|
||||
org.opencontainers.image.documentation="https://docs.inventree.org" \
|
||||
org.opencontainers.image.source="https://github.com/inventree/InvenTree" \
|
||||
org.opencontainers.image.revision=${commit_hash} \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.base.name="docker.io/library/${base_image}" \
|
||||
org.opencontainers.image.version=${commit_tag}
|
||||
|
||||
|
||||
# Install required system level packages
|
||||
RUN apk add --no-cache \
|
||||
|
@ -1,13 +1,20 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 236
|
||||
INVENTREE_API_VERSION = 238
|
||||
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v238 - 2024-08-14 : https://github.com/inventree/InvenTree/pull/7874
|
||||
- Add "assembly" filter to BuildLine API endpoint
|
||||
|
||||
v237 - 2024-08-13 : https://github.com/inventree/InvenTree/pull/7863
|
||||
- Reimplement "bulk delete" operation for Attachment model
|
||||
- Fix permission checks for Attachment API endpoints
|
||||
|
||||
v236 - 2024-08-10 : https://github.com/inventree/InvenTree/pull/7844
|
||||
- Adds "supplier_name" to the PurchaseOrder API serializer
|
||||
|
||||
|
@ -290,6 +290,7 @@ class BuildLineFilter(rest_filters.FilterSet):
|
||||
# Fields on related models
|
||||
consumable = rest_filters.BooleanFilter(label=_('Consumable'), field_name='bom_item__consumable')
|
||||
optional = rest_filters.BooleanFilter(label=_('Optional'), field_name='bom_item__optional')
|
||||
assembly = rest_filters.BooleanFilter(label=_('Assembly'), field_name='bom_item__sub_part__assembly')
|
||||
tracked = rest_filters.BooleanFilter(label=_('Tracked'), field_name='bom_item__sub_part__trackable')
|
||||
|
||||
allocated = rest_filters.BooleanFilter(label=_('Allocated'), method='filter_allocated')
|
||||
|
@ -4,6 +4,7 @@ import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.http.response import HttpResponse
|
||||
from django.urls import include, path, re_path
|
||||
@ -706,7 +707,7 @@ class AttachmentFilter(rest_filters.FilterSet):
|
||||
return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct()
|
||||
|
||||
|
||||
class AttachmentList(ListCreateAPI):
|
||||
class AttachmentList(BulkDeleteMixin, ListCreateAPI):
|
||||
"""List API endpoint for Attachment objects."""
|
||||
|
||||
queryset = common.models.Attachment.objects.all()
|
||||
@ -725,6 +726,24 @@ class AttachmentList(ListCreateAPI):
|
||||
attachment.upload_user = self.request.user
|
||||
attachment.save()
|
||||
|
||||
def validate_delete(self, queryset, request) -> None:
|
||||
"""Ensure that the user has correct permissions for a bulk-delete.
|
||||
|
||||
- Extract all model types from the provided queryset
|
||||
- Ensure that the user has correct 'delete' permissions for each model
|
||||
"""
|
||||
from common.validators import attachment_model_class_from_label
|
||||
from users.models import check_user_permission
|
||||
|
||||
model_types = queryset.values_list('model_type', flat=True).distinct()
|
||||
|
||||
for model_type in model_types:
|
||||
if model_class := attachment_model_class_from_label(model_type):
|
||||
if not check_user_permission(request.user, model_class, 'delete'):
|
||||
raise ValidationError(
|
||||
_('User does not have permission to delete these attachments')
|
||||
)
|
||||
|
||||
|
||||
class AttachmentDetail(RetrieveUpdateDestroyAPI):
|
||||
"""Detail API endpoint for Attachment objects."""
|
||||
|
@ -540,12 +540,17 @@ class AttachmentSerializer(InvenTreeModelSerializer):
|
||||
allow_null=False,
|
||||
)
|
||||
|
||||
def save(self):
|
||||
def save(self, **kwargs):
|
||||
"""Override the save method to handle the model_type field."""
|
||||
from InvenTree.models import InvenTreeAttachmentMixin
|
||||
from users.models import check_user_permission
|
||||
|
||||
model_type = self.validated_data.get('model_type', None)
|
||||
|
||||
if model_type is None:
|
||||
if self.instance:
|
||||
model_type = self.instance.model_type
|
||||
|
||||
# Ensure that the user has permission to attach files to the specified model
|
||||
user = self.context.get('request').user
|
||||
|
||||
@ -556,15 +561,18 @@ class AttachmentSerializer(InvenTreeModelSerializer):
|
||||
if not issubclass(target_model_class, InvenTreeAttachmentMixin):
|
||||
raise PermissionDenied(_('Invalid model type specified for attachment'))
|
||||
|
||||
permission_error_msg = _(
|
||||
'User does not have permission to create or edit attachments for this model'
|
||||
)
|
||||
|
||||
if not check_user_permission(user, target_model_class, 'change'):
|
||||
raise PermissionDenied(permission_error_msg)
|
||||
|
||||
# Check that the user has the required permissions to attach files to the target model
|
||||
if not target_model_class.check_attachment_permission('change', user):
|
||||
raise PermissionDenied(
|
||||
_(
|
||||
'User does not have permission to create or edit attachments for this model'
|
||||
)
|
||||
)
|
||||
raise PermissionDenied(_(permission_error_msg))
|
||||
|
||||
return super().save()
|
||||
return super().save(**kwargs)
|
||||
|
||||
|
||||
class IconSerializer(serializers.Serializer):
|
||||
|
@ -157,6 +157,29 @@ class AttachmentTest(InvenTreeAPITestCase):
|
||||
# Upload should now work!
|
||||
response = self.post(url, data, expected_code=201)
|
||||
|
||||
pk = response.data['pk']
|
||||
|
||||
# Edit the attachment via API
|
||||
response = self.patch(
|
||||
reverse('api-attachment-detail', kwargs={'pk': pk}),
|
||||
{'comment': 'New comment'},
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
self.assertEqual(response.data['comment'], 'New comment')
|
||||
|
||||
attachment = Attachment.objects.get(pk=pk)
|
||||
self.assertEqual(attachment.comment, 'New comment')
|
||||
|
||||
# And check that we cannot edit the attachment without the correct permissions
|
||||
self.clearRoles()
|
||||
|
||||
self.patch(
|
||||
reverse('api-attachment-detail', kwargs={'pk': pk}),
|
||||
{'comment': 'New comment 2'},
|
||||
expected_code=403,
|
||||
)
|
||||
|
||||
# Try to delete the attachment via API (should fail)
|
||||
attachment = part.attachments.first()
|
||||
url = reverse('api-attachment-detail', kwargs={'pk': attachment.pk})
|
||||
|
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
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
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
File diff suppressed because it is too large
Load Diff
@ -1161,7 +1161,8 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
|
||||
# Schedule pricing update for any referenced parts
|
||||
for line in self.lines.all():
|
||||
line.part.schedule_pricing_update(create=True)
|
||||
if line.part:
|
||||
line.part.schedule_pricing_update(create=True)
|
||||
|
||||
trigger_event('salesorder.completed', id=self.pk)
|
||||
|
||||
|
@ -4509,7 +4509,8 @@ def update_pricing_after_edit(sender, instance, created, **kwargs):
|
||||
"""Callback function when a part price break is created or updated."""
|
||||
# Update part pricing *unless* we are importing data
|
||||
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
|
||||
instance.part.schedule_pricing_update(create=True)
|
||||
if instance.part:
|
||||
instance.part.schedule_pricing_update(create=True)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=BomItem, dispatch_uid='post_delete_bom_item')
|
||||
@ -4525,7 +4526,8 @@ def update_pricing_after_delete(sender, instance, **kwargs):
|
||||
"""Callback function when a part price break is deleted."""
|
||||
# Update part pricing *unless* we are importing data
|
||||
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
|
||||
instance.part.schedule_pricing_update(create=False)
|
||||
if instance.part:
|
||||
instance.part.schedule_pricing_update(create=False)
|
||||
|
||||
|
||||
class BomItemSubstitute(InvenTree.models.InvenTreeMetadataModel):
|
||||
|
@ -2293,7 +2293,8 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs):
|
||||
)
|
||||
|
||||
# Schedule an update on parent part pricing
|
||||
instance.part.schedule_pricing_update(create=False)
|
||||
if instance.part:
|
||||
instance.part.schedule_pricing_update(create=False)
|
||||
|
||||
|
||||
@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')
|
||||
@ -2312,7 +2313,8 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs):
|
||||
)
|
||||
|
||||
# Schedule an update on parent part pricing
|
||||
instance.part.schedule_pricing_update(create=True)
|
||||
if instance.part:
|
||||
instance.part.schedule_pricing_update(create=True)
|
||||
|
||||
|
||||
class StockItemTracking(InvenTree.models.InvenTreeModel):
|
||||
|
@ -682,6 +682,18 @@ def clear_user_role_cache(user: User):
|
||||
cache.delete(key)
|
||||
|
||||
|
||||
def check_user_permission(user: User, model, permission):
|
||||
"""Check if the user has a particular permission against a given model type.
|
||||
|
||||
Arguments:
|
||||
user: The user object to check
|
||||
model: The model class to check (e.g. Part)
|
||||
permission: The permission to check (e.g. 'view' / 'delete')
|
||||
"""
|
||||
permission_name = f'{model._meta.app_label}.{permission}_{model._meta.model_name}'
|
||||
return user.has_perm(permission_name)
|
||||
|
||||
|
||||
def check_user_role(user: User, role, permission):
|
||||
"""Check if a user has a particular role:permission combination.
|
||||
|
||||
|
26
src/backend/package-lock.json
generated
26
src/backend/package-lock.json
generated
@ -5,7 +5,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"eslint": "^9.7.0",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-config-google": "^0.14.0"
|
||||
}
|
||||
},
|
||||
@ -86,9 +86,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz",
|
||||
"integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==",
|
||||
"version": "9.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz",
|
||||
"integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
@ -322,15 +322,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.7.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.7.0.tgz",
|
||||
"integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==",
|
||||
"version": "9.9.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.0.tgz",
|
||||
"integrity": "sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.11.0",
|
||||
"@eslint/config-array": "^0.17.0",
|
||||
"@eslint/config-array": "^0.17.1",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "9.7.0",
|
||||
"@eslint/js": "9.9.0",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.3.0",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@ -369,6 +369,14 @@
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://eslint.org/donate"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jiti": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"jiti": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-google": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"eslint": "^9.7.0",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-config-google": "^0.14.0"
|
||||
},
|
||||
"type": "module"
|
||||
|
@ -11,82 +11,82 @@
|
||||
"compile": "lingui compile --typescript"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": ">=6.16.3",
|
||||
"@codemirror/autocomplete": ">=6.18.0",
|
||||
"@codemirror/lang-liquid": "^6.2.1",
|
||||
"@codemirror/language": ">=6.10.2",
|
||||
"@codemirror/lint": ">=6.8.1",
|
||||
"@codemirror/search": ">=6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/theme-one-dark": ">=6.0.0",
|
||||
"@codemirror/view": ">=6.28.3",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||
"@codemirror/view": ">=6.32.0",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@lingui/core": "^4.11.1",
|
||||
"@lingui/react": "^4.11.1",
|
||||
"@mantine/carousel": "^7.11.0",
|
||||
"@mantine/charts": "^7.11.0",
|
||||
"@mantine/core": "^7.11.0",
|
||||
"@mantine/dates": "^7.11.0",
|
||||
"@mantine/dropzone": "^7.11.0",
|
||||
"@mantine/form": "^7.11.0",
|
||||
"@mantine/hooks": "^7.11.0",
|
||||
"@mantine/modals": "^7.11.0",
|
||||
"@mantine/notifications": "^7.11.0",
|
||||
"@mantine/spotlight": "^7.11.0",
|
||||
"@mantine/vanilla-extract": "^7.11.0",
|
||||
"@mdxeditor/editor": "^3.6.1",
|
||||
"@sentry/react": "^8.13.0",
|
||||
"@tabler/icons-react": "^3.7.0",
|
||||
"@tanstack/react-query": "^5.49.2",
|
||||
"@uiw/codemirror-theme-vscode": "^4.22.2",
|
||||
"@uiw/react-codemirror": "^4.22.2",
|
||||
"@lingui/core": "^4.11.3",
|
||||
"@lingui/react": "^4.11.3",
|
||||
"@mantine/carousel": "^7.12.1",
|
||||
"@mantine/charts": "^7.12.1",
|
||||
"@mantine/core": "^7.12.1",
|
||||
"@mantine/dates": "^7.12.1",
|
||||
"@mantine/dropzone": "^7.12.1",
|
||||
"@mantine/form": "^7.12.1",
|
||||
"@mantine/hooks": "^7.12.1",
|
||||
"@mantine/modals": "^7.12.1",
|
||||
"@mantine/notifications": "^7.12.1",
|
||||
"@mantine/spotlight": "^7.12.1",
|
||||
"@mantine/vanilla-extract": "^7.12.1",
|
||||
"@mdxeditor/editor": "^3.11.0",
|
||||
"@sentry/react": "^8.25.0",
|
||||
"@tabler/icons-react": "^3.12.0",
|
||||
"@tanstack/react-query": "^5.51.23",
|
||||
"@uiw/codemirror-theme-vscode": "^4.23.0",
|
||||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"@uiw/react-split": "^5.9.3",
|
||||
"@vanilla-extract/css": "^1.15.3",
|
||||
"axios": "^1.7.2",
|
||||
"axios": "^1.7.3",
|
||||
"clsx": "^2.1.0",
|
||||
"codemirror": ">=6.0.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"embla-carousel-react": "^8.1.6",
|
||||
"dayjs": "^1.11.12",
|
||||
"embla-carousel-react": "^8.1.8",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"mantine-datatable": "^7.11.2",
|
||||
"qrcode": "^1.5.3",
|
||||
"mantine-datatable": "^7.11.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-grid-layout": "^1.4.4",
|
||||
"react-hook-form": "^7.51.3",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"react-is": "^18.3.1",
|
||||
"react-router-dom": "^6.24.0",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-select": "^5.8.0",
|
||||
"react-window": "^1.8.10",
|
||||
"recharts": "^2.12.4",
|
||||
"styled-components": "^6.1.11",
|
||||
"styled-components": "^6.1.12",
|
||||
"zustand": "^4.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@lingui/cli": "^4.11.1",
|
||||
"@lingui/macro": "^4.11.1",
|
||||
"@playwright/test": "^1.45.0",
|
||||
"@types/node": "^20.14.9",
|
||||
"@lingui/cli": "^4.11.3",
|
||||
"@lingui/macro": "^4.11.3",
|
||||
"@playwright/test": "^1.46.0",
|
||||
"@types/node": "^22.2.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-grid-layout": "^1.3.5",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@vanilla-extract/vite-plugin": "^4.0.12",
|
||||
"@vanilla-extract/vite-plugin": "^4.0.13",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"nyc": "^17.0.0",
|
||||
"rollup-plugin-license": "^3.5.1",
|
||||
"typescript": "^5.5.2",
|
||||
"vite": "^5.3.2",
|
||||
"rollup-plugin-license": "^3.5.2",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.0",
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-plugin-istanbul": "^6.0.2"
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { Boundary } from '../Boundary';
|
||||
@ -78,12 +79,12 @@ function BasePanelGroup({
|
||||
const handlePanelChange = useCallback(
|
||||
(panel: string | null, event?: any) => {
|
||||
if (activePanels.findIndex((p) => p.name === panel) === -1) {
|
||||
setLastUsedPanel('');
|
||||
return navigate('../');
|
||||
panel = '';
|
||||
}
|
||||
|
||||
if (event && (event?.ctrlKey || event?.shiftKey)) {
|
||||
const url = `${location.pathname}/../${panel}`;
|
||||
cancelEvent(event);
|
||||
navigateToLink(url, navigate, event);
|
||||
} else {
|
||||
navigate(`../${panel}`);
|
||||
@ -117,12 +118,7 @@ function BasePanelGroup({
|
||||
return (
|
||||
<Boundary label={`PanelGroup-${pageKey}`}>
|
||||
<Paper p="sm" radius="xs" shadow="xs">
|
||||
<Tabs
|
||||
value={panel}
|
||||
orientation="vertical"
|
||||
onChange={handlePanelChange}
|
||||
keepMounted={false}
|
||||
>
|
||||
<Tabs value={panel} orientation="vertical" keepMounted={false}>
|
||||
<Tabs.List justify="left">
|
||||
{panels.map(
|
||||
(panel) =>
|
||||
@ -136,7 +132,6 @@ function BasePanelGroup({
|
||||
<Tabs.Tab
|
||||
p="xs"
|
||||
value={panel.name}
|
||||
// icon={(<InvenTreeIcon icon={panel.name}/>)} // Enable when implementing Icon manager everywhere
|
||||
leftSection={panel.icon}
|
||||
hidden={panel.hidden}
|
||||
disabled={panel.disabled}
|
||||
|
@ -133,7 +133,11 @@ export function SearchDrawer({
|
||||
return [
|
||||
{
|
||||
model: ModelType.part,
|
||||
parameters: {},
|
||||
parameters: {
|
||||
active: userSettings.isSet('SEARCH_HIDE_INACTIVE_PARTS')
|
||||
? true
|
||||
: undefined
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.part) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_PARTS')
|
||||
@ -173,7 +177,10 @@ export function SearchDrawer({
|
||||
model: ModelType.stockitem,
|
||||
parameters: {
|
||||
part_detail: true,
|
||||
location_detail: true
|
||||
location_detail: true,
|
||||
in_stock: userSettings.isSet('SEARCH_PREVIEW_HIDE_UNAVAILABLE_STOCK')
|
||||
? true
|
||||
: undefined
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.stock) &&
|
||||
@ -206,7 +213,12 @@ export function SearchDrawer({
|
||||
{
|
||||
model: ModelType.purchaseorder,
|
||||
parameters: {
|
||||
supplier_detail: true
|
||||
supplier_detail: true,
|
||||
outstanding: userSettings.isSet(
|
||||
'SEARCH_PREVIEW_EXCLUDE_INACTIVE_PURCHASE_ORDERS'
|
||||
)
|
||||
? true
|
||||
: undefined
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.purchase_order) &&
|
||||
@ -215,7 +227,12 @@ export function SearchDrawer({
|
||||
{
|
||||
model: ModelType.salesorder,
|
||||
parameters: {
|
||||
customer_detail: true
|
||||
customer_detail: true,
|
||||
outstanding: userSettings.isSet(
|
||||
'SEARCH_PREVIEW_EXCLUDE_INACTIVE_SALES_ORDERS'
|
||||
)
|
||||
? true
|
||||
: undefined
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.sales_order) &&
|
||||
@ -224,7 +241,12 @@ export function SearchDrawer({
|
||||
{
|
||||
model: ModelType.returnorder,
|
||||
parameters: {
|
||||
customer_detail: true
|
||||
customer_detail: true,
|
||||
outstanding: userSettings.isSet(
|
||||
'SEARCH_PREVIEW_EXCLUDE_INACTIVE_RETURN_ORDERS'
|
||||
)
|
||||
? true
|
||||
: undefined
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.return_order) &&
|
||||
@ -250,7 +272,7 @@ export function SearchDrawer({
|
||||
|
||||
let params: any = {
|
||||
offset: 0,
|
||||
limit: 10, // TODO: Make this configurable (based on settings)
|
||||
limit: userSettings.getSetting('SEARCH_PREVIEW_RESULTS', '10'),
|
||||
search: searchText,
|
||||
search_regex: searchRegex,
|
||||
search_whole: searchWhole
|
||||
|
@ -13,6 +13,8 @@ export function usePartFields({
|
||||
}: {
|
||||
create?: boolean;
|
||||
}): ApiFormFieldSet {
|
||||
const settings = useGlobalSettingsState.getState();
|
||||
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
category: {
|
||||
@ -93,8 +95,6 @@ export function usePartFields({
|
||||
};
|
||||
}
|
||||
|
||||
const settings = useGlobalSettingsState.getState();
|
||||
|
||||
if (settings.isSet('PART_REVISION_ASSEMBLY_ONLY')) {
|
||||
fields.revision_of.filters['assembly'] = true;
|
||||
}
|
||||
@ -111,7 +111,7 @@ export function usePartFields({
|
||||
}
|
||||
|
||||
return fields;
|
||||
}, [create]);
|
||||
}, [create, settings]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
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
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
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
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
@ -95,6 +95,14 @@ export default function BuildDetail() {
|
||||
label: t`Part`,
|
||||
model: ModelType.part
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'part_detail.IPN',
|
||||
icon: 'part',
|
||||
label: t`IPN`,
|
||||
hidden: !build.part_detail?.IPN,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'status',
|
||||
name: 'status',
|
||||
@ -104,13 +112,15 @@ export default function BuildDetail() {
|
||||
{
|
||||
type: 'text',
|
||||
name: 'reference',
|
||||
label: t`Reference`
|
||||
label: t`Reference`,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
label: t`Description`,
|
||||
icon: 'description'
|
||||
icon: 'description',
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
|
@ -307,7 +307,7 @@ export function BomTable({
|
||||
{
|
||||
name: 'sub_part_assembly',
|
||||
label: t`Assembled Part`,
|
||||
description: t`Show asssmbled items`
|
||||
description: t`Show assembled items`
|
||||
},
|
||||
{
|
||||
name: 'available_stock',
|
||||
|
@ -56,6 +56,11 @@ export default function BuildLineTable({
|
||||
label: t`Optional`,
|
||||
description: t`Show optional lines`
|
||||
},
|
||||
{
|
||||
name: 'assembly',
|
||||
label: t`Assembly`,
|
||||
description: t`Show assembled items`
|
||||
},
|
||||
{
|
||||
name: 'tracked',
|
||||
label: t`Tracked`,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user