Merge commit '2c8ef258941d1da2e5d8e0310c4f465c5fa72fe9' into notify-context
This commit is contained in:
commit
c258390de3
|
|
@ -206,11 +206,11 @@ jobs:
|
|||
images: |
|
||||
inventree/inventree
|
||||
ghcr.io/${{ github.repository }}
|
||||
- uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 # pin@v1
|
||||
- uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # pin@v1
|
||||
- name: Push Docker Images
|
||||
id: push-docker
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: depot/build-push-action@9785b135c3c76c33db102e45be96a25ab55cd507 # pin@v1
|
||||
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # pin@v1
|
||||
with:
|
||||
project: jczzbjkk68
|
||||
context: .
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ env:
|
|||
INVENTREE_SITE_URL: http://localhost:8000
|
||||
INVENTREE_DEBUG: true
|
||||
|
||||
use_performance: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
|
@ -42,6 +40,8 @@ jobs:
|
|||
cicd: ${{ steps.filter.outputs.cicd }}
|
||||
requirements: ${{ steps.filter.outputs.requirements }}
|
||||
runner-perf: ${{ steps.runner-perf.outputs.runner }}
|
||||
performance: ${{ steps.performance.outputs.force-performance }}
|
||||
submit-performance: ${{ steps.runner-perf.outputs.submit-performance }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2
|
||||
|
|
@ -77,10 +77,29 @@ jobs:
|
|||
if: |
|
||||
contains(github.event.pull_request.labels.*.name, 'dependency') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'full-run')
|
||||
- name: Is performance testing being forced?
|
||||
run: echo "force-performance=true" >> $GITHUB_OUTPUT
|
||||
id: performance
|
||||
if: |
|
||||
contains(github.event.pull_request.labels.*.name, 'performance-run')
|
||||
- name: Which runner to use?
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
PERFORMANCE: ${{ steps.performance.outputs.force-performance }}
|
||||
id: runner-perf
|
||||
# decide if we are running in inventree/inventree -> use codspeed-macro runner else ubuntu-24.04
|
||||
run: echo "runner=$([[ '${{ github.repository }}' == 'inventree/InvenTree' && '${{ env.use_performance }}' == 'true' ]] && echo 'codspeed-macro' || echo 'ubuntu-24.04')" >> $GITHUB_OUTPUT
|
||||
run: |
|
||||
is_main_push=false
|
||||
if [[ '${{ github.event_name }}' == 'push' && "$GITHUB_REF" == 'refs/heads/master' ]]; then
|
||||
is_main_push=true
|
||||
fi
|
||||
if [[ '${{ github.repository }}' == 'inventree/InvenTree' && ( "$is_main_push" == 'true' || "$PERFORMANCE" == 'true' ) ]]; then
|
||||
echo "runner=codspeed-macro" >> "$GITHUB_OUTPUT"
|
||||
echo "submit-performance=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "runner=ubuntu-24.04" >> "$GITHUB_OUTPUT"
|
||||
echo "submit-performance=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
pre-commit:
|
||||
name: Style [pre-commit]
|
||||
|
|
@ -183,7 +202,7 @@ jobs:
|
|||
- name: Export API Documentation
|
||||
run: invoke dev.schema --ignore-warnings --filename src/backend/InvenTree/schema.yml
|
||||
- name: Upload schema
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
with:
|
||||
name: schema.yml
|
||||
path: src/backend/InvenTree/schema.yml
|
||||
|
|
@ -232,17 +251,17 @@ jobs:
|
|||
- name: Extract settings / tags
|
||||
run: invoke int.export-definitions --basedir docs
|
||||
- name: Upload settings
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
with:
|
||||
name: inventree_settings.json
|
||||
path: docs/generated/inventree_settings.json
|
||||
- name: Upload tags
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
with:
|
||||
name: inventree_tags.yml
|
||||
path: docs/generated/inventree_tags.yml
|
||||
- name: Upload filters
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
with:
|
||||
name: inventree_filters.yml
|
||||
path: docs/generated/inventree_filters.yml
|
||||
|
|
@ -265,7 +284,7 @@ jobs:
|
|||
- name: Create artifact directory
|
||||
run: mkdir -p artifact
|
||||
- name: Download schema artifact
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # pin@v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # pin@v8.0.0
|
||||
with:
|
||||
path: artifact
|
||||
merge-multiple: true
|
||||
|
|
@ -292,7 +311,7 @@ jobs:
|
|||
runs-on: ${{ needs.paths-filter.outputs.runner-perf }}
|
||||
|
||||
needs: ["pre-commit", "paths-filter"]
|
||||
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
|
||||
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' || needs.paths-filter.outputs.performance == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
|
@ -341,10 +360,11 @@ jobs:
|
|||
pip uninstall pytest-django -y
|
||||
cd ${WRAPPER_NAME}
|
||||
pip install .
|
||||
if: needs.paths-filter.outputs.submit-performance == 'true'
|
||||
- name: Performance Reporting
|
||||
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # pin@v4
|
||||
# check if we are in inventree/inventree - reporting only works in that OIDC context
|
||||
if: github.repository == 'inventree/InvenTree'
|
||||
if: github.repository == 'inventree/InvenTree' && needs.paths-filter.outputs.submit-performance == 'true'
|
||||
with:
|
||||
mode: walltime
|
||||
run: pytest ./src/performance --codspeed
|
||||
|
|
@ -387,7 +407,7 @@ jobs:
|
|||
- name: Coverage Tests
|
||||
run: invoke dev.test --check --coverage --translations
|
||||
- name: Upload raw coverage to artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
with:
|
||||
name: coverage
|
||||
path: .coverage
|
||||
|
|
@ -406,7 +426,7 @@ jobs:
|
|||
|
||||
needs: ["pre-commit", "paths-filter"]
|
||||
# check if we are in inventree/inventree - reporting only works in that OIDC context
|
||||
if: (needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true') && github.repository == 'inventree/InvenTree'
|
||||
if: (needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true') && github.repository == 'inventree/InvenTree' && needs.paths-filter.outputs.submit-performance == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
|
@ -701,7 +721,7 @@ jobs:
|
|||
invoke static
|
||||
env INVENTREE_CUSTOM_SPLASH="img/playwright_custom_splash.png" INVENTREE_CUSTOM_LOGO="img/playwright_custom_logo.png" npx nyc playwright test --project=customization
|
||||
npx nyc playwright test --project=chromium --project=firefox
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
|
||||
with:
|
||||
name: playwright-report
|
||||
|
|
@ -746,7 +766,7 @@ jobs:
|
|||
run: |
|
||||
cd src/backend/InvenTree/web/static
|
||||
zip -r frontend-build.zip web/ web/.vite
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
with:
|
||||
name: frontend-build
|
||||
path: src/backend/InvenTree/web/static/web
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ jobs:
|
|||
- name: Build frontend
|
||||
run: cd src/frontend && npm run compile && npm run build
|
||||
- name: Create SBOM for frontend
|
||||
uses: anchore/sbom-action@0b82b0b1a22399a1c542d4d656f70cd903571b5c # pin@v0
|
||||
uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # pin@v0
|
||||
with:
|
||||
artifact-name: frontend-build.spdx
|
||||
path: src/frontend
|
||||
|
|
@ -73,12 +73,12 @@ jobs:
|
|||
zip -r ../frontend-build.zip * .vite
|
||||
- name: Attest Build Provenance
|
||||
id: attest
|
||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # pin@v1
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # pin@v1
|
||||
with:
|
||||
subject-path: "${{ github.workspace }}/src/backend/InvenTree/web/static/frontend-build.zip"
|
||||
|
||||
- name: Upload frontend
|
||||
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
|
||||
uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # pin@2.11.4
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: src/backend/InvenTree/web/static/frontend-build.zip
|
||||
|
|
@ -86,12 +86,12 @@ jobs:
|
|||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
- name: Upload frontend to artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
with:
|
||||
name: frontend-build
|
||||
path: src/backend/InvenTree/web/static/frontend-build.zip
|
||||
- name: Upload Attestation
|
||||
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
|
||||
uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # pin@2.11.4
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
asset_name: frontend-build.intoto.jsonl
|
||||
|
|
@ -134,7 +134,7 @@ jobs:
|
|||
cd docs/site
|
||||
zip -r docs-html.zip *
|
||||
- name: Publish documentation
|
||||
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
|
||||
uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # pin@2.11.4
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: docs/site/docs-html.zip
|
||||
|
|
@ -163,7 +163,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Get frontend artifact
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # pin@v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # pin@v8.0.0
|
||||
with:
|
||||
name: frontend-build
|
||||
- name: Setup
|
||||
|
|
@ -244,7 +244,7 @@ jobs:
|
|||
channel: ${{ env.pkg_channel }}
|
||||
file: ${{ steps.package.outputs.package_path }}
|
||||
- name: Publish to artifact
|
||||
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
|
||||
uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # pin@2.11.4
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.package.outputs.package_path }}
|
||||
|
|
|
|||
|
|
@ -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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # pin@v10.1.1
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # pin@v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue seems stale. Please react to show this is still important."
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
echo "Resetting to HEAD~"
|
||||
git reset HEAD~ || true
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@b4b468cffefb50bdd99dd83e5d2eaeb63c880380 # pin@v2
|
||||
uses: crowdin/github-action@8818ff65bfc4322384f983ea37e3926948c11745 # pin@v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ repos:
|
|||
files: docs/requirements\.(in|txt)$
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements.txt
|
||||
args: [contrib/container/requirements.in, -o, contrib/container/requirements.txt, --python-version=3.11, -c, src/backend/requirements.txt]
|
||||
args: [contrib/container/requirements.in, -o, contrib/container/requirements.txt, --python-version=3.14, -c, src/backend/requirements.txt]
|
||||
files: contrib/container/requirements\.(in|txt)$
|
||||
- repo: https://github.com/Riverside-Healthcare/djLint
|
||||
rev: v1.36.4
|
||||
|
|
|
|||
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -13,11 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
[#11222](https://github.com/inventree/InvenTree/pull/11222) adds support for data import using natural keys, allowing for easier association of related objects without needing to know their internal database IDs.
|
||||
|
||||
[#11383](https://github.com/inventree/InvenTree/pull/11383) adds "exists_for_model_id", "exists_for_related_model", and "exists_for_related_model_id" filters to the ParameterTemplate API endpoint. These filters allow users to check for the existence of parameters associated with specific models or related models, improving the flexibility and usability of the API.
|
||||
|
||||
[#10887](https://github.com/inventree/InvenTree/pull/10887) adds the ability to auto-allocate tracked items against specific build outputs. Currently, this will only allocate items where the serial number of the tracked item matches the serial number of the build output, but in future this may be extended to allow for more flexible allocation rules.
|
||||
- [#11405](https://github.com/inventree/InvenTree/pull/11405) adds default table filters, which hide inactive items by default. The default table filters are overridden by user filter selection, and only apply to the table view initially presented to the user. This means that users can still view inactive items if they choose to, but they will not be shown by default.
|
||||
- [#11222](https://github.com/inventree/InvenTree/pull/11222) adds support for data import using natural keys, allowing for easier association of related objects without needing to know their internal database IDs.
|
||||
- [#11383](https://github.com/inventree/InvenTree/pull/11383) adds "exists_for_model_id", "exists_for_related_model", and "exists_for_related_model_id" filters to the ParameterTemplate API endpoint. These filters allow users to check for the existence of parameters associated with specific models or related models, improving the flexibility and usability of the API.
|
||||
- [#10887](https://github.com/inventree/InvenTree/pull/10887) adds the ability to auto-allocate tracked items against specific build outputs. Currently, this will only allocate items where the serial number of the tracked item matches the serial number of the build output, but in future this may be extended to allow for more flexible allocation rules.
|
||||
- [#11372](https://github.com/inventree/InvenTree/pull/11372) adds backup metadata setter and restore metadata validator functions to ensure common footguns are harder to trigger when using the backup and restore functionality.
|
||||
- [#11374](https://github.com/inventree/InvenTree/pull/11374) adds `updated_at` field on purchase, sales and return orders.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ InvenTree/
|
|||
├─ CONTRIBUTING.md # Contribution guidelines and overview
|
||||
├─ Procfile # Process definition for Debian/Ubuntu packages
|
||||
├─ README.md # General project information and overview
|
||||
├─ runtime.txt # Python runtime settings for Debian/Ubuntu packages build
|
||||
├─ SECURITY.md # Project security policy
|
||||
├─ tasks.py # Action definitions for development, testing and deployment
|
||||
```
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
# - Runs InvenTree web server under django development server
|
||||
# - Monitors source files for any changes, and live-reloads server
|
||||
|
||||
# Base image last bumped 2026-02-12
|
||||
FROM python:3.11-slim-trixie@sha256:0b23cfb7425d065008b778022a17b1551c82f8b4866ee5a7a200084b7e2eafbf AS inventree_base
|
||||
# Base image last bumped 2026-02-23
|
||||
FROM python:3.14-slim-trixie@sha256:486b8092bfb12997e10d4920897213a06563449c951c5506c2a2cfaf591c599f AS inventree_base
|
||||
|
||||
# Build arguments for this image
|
||||
ARG commit_tag=""
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile contrib/container/requirements.in -o contrib/container/requirements.txt --python-version=3.11 -c src/backend/requirements.txt
|
||||
# uv pip compile contrib/container/requirements.in -o contrib/container/requirements.txt --python-version=3.14 -c src/backend/requirements.txt
|
||||
asgiref==3.11.1 \
|
||||
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
||||
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django
|
||||
django==5.2.11 \
|
||||
--hash=sha256:7f2d292ad8b9ee35e405d965fbbad293758b858c34bbf7f3df551aeeac6f02d3 \
|
||||
--hash=sha256:e7130df33ada9ab5e5e929bc19346a20fe383f5454acb2cc004508f242ee92c0
|
||||
django==5.2.12 \
|
||||
--hash=sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7 \
|
||||
--hash=sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r contrib/container/requirements.in
|
||||
|
|
@ -236,7 +236,6 @@ typing-extensions==4.15.0 \
|
|||
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# psycopg
|
||||
# psycopg-pool
|
||||
uv==0.9.22 \
|
||||
--hash=sha256:012bdc5285a9cdb091ac514b7eb8a707e3b649af5355fe4afb4920bfe1958c00 \
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt -c src/backend/requirements.txt
|
||||
certifi==2026.1.4 \
|
||||
--hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \
|
||||
--hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120
|
||||
certifi==2026.2.25 \
|
||||
--hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
|
||||
--hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
|
|
|
|||
|
|
@ -291,3 +291,11 @@ Users may opt to disable the spotlight search functionality if they do not find
|
|||
Many aspects of the user interface are controlled by user permissions, which determine what actions and features are available to each user based on their assigned roles and permissions within the system. This allows for a highly customizable user experience, where different users can have access to different features and functionality based on their specific needs and responsibilities within the organization.
|
||||
|
||||
If a user does not have permission to access a particular feature or section of the system, that feature will be hidden from their view in the user interface. This helps to ensure that users only see the features and information that are relevant to their role, reducing clutter and improving usability.
|
||||
|
||||
## Language Support
|
||||
|
||||
The InvenTree user interface supports multiple languages, allowing users to interact with the system in their preferred language.
|
||||
|
||||
The default system language can be configured by the system administrator in the [server configuration options](../start/config.md#basic-options).
|
||||
|
||||
Additionally, users can select their preferred language in their [user settings](../settings/user.md), allowing them to override the system default language with their own choice. This provides a personalized experience for each user, ensuring that they can interact with the system in the language they are most comfortable with.
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ InvenTree roughly follow the [GitLab flow](https://about.gitlab.com/topics/versi
|
|||
There are nominally 5 active branches:
|
||||
- `master` - The main development branch
|
||||
- `stable` - The latest stable release
|
||||
- `next-breaking` - The next breaking release (e.g. 2.0, 3.0) with all deprecated features removed
|
||||
- `l10n` - Translation branch: Source to Crowdin
|
||||
- `l10_crowdin` - Translation branch: Source from Crowdin
|
||||
- `y.y.x` - Release branch for the currently supported version (e.g. `0.5.x`)
|
||||
|
|
@ -115,6 +116,23 @@ The translation process is as follows:
|
|||
4. Translations made in Crowdin are automatically pushed back to the `l10_crowdin` branch by Crowdin once they are approved
|
||||
5. The `l10_crowdin` branch is merged back into `master` by a maintainer periodically
|
||||
|
||||
### `next-breaking` Branch
|
||||
|
||||
Used for easier testing of plugins and integrations against the next major release. It is branched from master when a major release is cut and updated on minor release. The branch is not build into docker images or packages and not meant to be run in production.
|
||||
|
||||
|
||||
All deprecated features (REST or python API endpoints mostly) are removed from this branch after each minor release. This allows plugin developers to test their plugins against the next major release early and identify any extensive changes before the major release is cut.
|
||||
|
||||
Only breaking changes are added to this branch. No new features should be added at any point to this branch, only breaking removals / changes.
|
||||
|
||||
Before a major release is cut (1.12.5 > 2.0.0), this branch is merged back into `master`.
|
||||
|
||||
|
||||
During the life-time of a major release line (1.0.1, 1.1.x, 1.2.x, 1.3.x, ..., 1.12.5) all deprecation removals are collected in this branch.
|
||||
On every minor release (1.11.8 > 1.12.0) the `master` is rebased onto the `next-breaking` branch.
|
||||
|
||||
Every time a change with depreations is merged into `master`, a follow up PR that removes the newly-introduced deprecation is created targeting the `next-breaking` branch. After the next minor is released and `master` was rebased into `next-breaking` all the PRs from the previous minor release line can be merged into the `next-breaking` branch. Deprecation removals for the - possibly - long running major release line can be collected this way without having a large number of deprecation removals PRs open.
|
||||
|
||||
## API versioning
|
||||
|
||||
The [API version]({{ sourcefile("src/backend/InvenTree/InvenTree/api_version.py") }}) needs to be bumped every time when the API is changed.
|
||||
|
|
|
|||
|
|
@ -84,23 +84,27 @@ This is a security measure to prevent plugins from changing the core functionali
|
|||
An error occurred when discovering or initializing a machine type from a plugin. This likely indicates a faulty or incompatible plugin.
|
||||
|
||||
#### INVE-E13
|
||||
|
||||
**Error reading InvenTree configuration file**
|
||||
|
||||
An error occurred while reading the InvenTree configuration file. This might be caused by a syntax error or invalid value in the configuration file.
|
||||
|
||||
#### INVE-E14
|
||||
|
||||
**Could not import Django**
|
||||
|
||||
Django is not installed in the current Python environment. This means that the InvenTree backend is not running within the correct [python virtual environment](../start/index.md#virtual-environment) or that the required Python packages were not installed correctly.
|
||||
|
||||
#### INVE-E15
|
||||
|
||||
**Python version not supported**
|
||||
|
||||
This error occurs attempting to run InvenTree on a version of Python which is older than the minimum required version. We [require Python {{ config.extra.min_python_version }} or newer](../start/index.md#python-requirements)
|
||||
|
||||
#### INVE-E16
|
||||
**Restore was stopped due to critical issues with backup environment - Backend**
|
||||
|
||||
A potentially critical mismatch between the backup environment and the current restore environment was detected during the restore process. The restore was stopped to prevent potential data loss or corruption. Check the logs for more information about the detected issues.
|
||||
|
||||
While using [invoke](../start/invoke.md), this can be overridden with the `--restore-allow-newer-version` flag.
|
||||
|
||||
### INVE-W (InvenTree Warning)
|
||||
Warnings - These are non-critical errors which should be addressed when possible.
|
||||
|
||||
|
|
@ -199,6 +203,14 @@ A user attempted to sign up but registration is currently disabled via the syste
|
|||
|
||||
To enable registration, adjust the relevant settings (for regular or SSO registration) to allow user signups.
|
||||
|
||||
#### INVE-W13
|
||||
**Current environment inconsistent with backup environment - Backend**
|
||||
|
||||
The environment in which the backup was taken does not match the current environment. This might lead to issues with restoring the backup, as the backup might contain data that is not compatible with the current environment. Plugins for example might be missing or are present in a different version - this can lead to issues with restoring the backup.
|
||||
|
||||
This warning will not prevent you from restoring the backup but it is recommended to ensure the mentioned issues are resolved before restoring the backup to prevent issues with the restored instance.
|
||||
|
||||
|
||||
### INVE-I (InvenTree Information)
|
||||
Information — These are not errors but information messages. They might point out potential issues or just provide information.
|
||||
|
||||
|
|
@ -215,5 +227,9 @@ An issue was detected with the application of a filtering serializer or decorato
|
|||
|
||||
This warning should only be raised during development and not in production, if you recently installed a plugin you might want to contact the plugin author.
|
||||
|
||||
#### INVE-I3
|
||||
**Backup and restore information - Backend**
|
||||
Information about the metadata of a backup being restored or the environment in which a backup is being restored to. This information can be helpful to understand the backup and restore process and to identify potential issues with the backup or the restore process.
|
||||
|
||||
### INVE-M (InvenTree Miscellaneous)
|
||||
Miscellaneous — These are information messages that might be used to mark debug information or other messages helpful for the InvenTree team to understand behaviour.
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ Configuration of label printing:
|
|||
{{ globalsetting("PART_SALABLE") }}
|
||||
{{ globalsetting("PART_VIRTUAL") }}
|
||||
{{ globalsetting("PART_COPY_BOM") }}
|
||||
{{ globalsetting("PART_BOM_ALLOW_ZERO_QUANTITY") }}
|
||||
{{ globalsetting("PART_COPY_PARAMETERS") }}
|
||||
{{ globalsetting("PART_COPY_TESTS") }}
|
||||
{{ globalsetting("PART_CATEGORY_PARAMETERS") }}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ The following configuration options are available for backup:
|
|||
| INVENTREE_BACKUP_DATE_FORMAT | backup_date_format | Date format string used to format timestamps in backup filenames. | `%Y-%m-%d-%H%M%S` |
|
||||
| INVENTREE_BACKUP_DATABASE_FILENAME_TEMPLATE | backup_database_filename_template | Template string used to generate database backup filenames. | `InvenTree-db-{datetime}.{extension}` |
|
||||
| INVENTREE_BACKUP_MEDIA_FILENAME_TEMPLATE | backup_media_filename_template | Template string used to generate media backup filenames. | `InvenTree-media-{datetime}.{extension}` |
|
||||
| INVENTREE_BACKUP_RESTORE_ALLOW_NEWER_VERSION | backup_restore_allow_newer_version | If True, allows restoring a backup created with a newer version of InvenTree. This is dangerous as it can lead to hard-to-debug data loss. | False |
|
||||
|
||||
### Storage Backend
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ bracex==2.6 \
|
|||
--hash=sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 \
|
||||
--hash=sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7
|
||||
# via wcmatch
|
||||
certifi==2026.1.4 \
|
||||
--hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \
|
||||
--hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120
|
||||
certifi==2026.2.25 \
|
||||
--hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
|
||||
--hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# httpcore
|
||||
|
|
@ -391,9 +391,9 @@ mkdocs-macros-plugin==1.5.0 \
|
|||
--hash=sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f \
|
||||
--hash=sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material==9.7.1 \
|
||||
--hash=sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c \
|
||||
--hash=sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8
|
||||
mkdocs-material==9.7.3 \
|
||||
--hash=sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d \
|
||||
--hash=sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material-extensions==1.3.1 \
|
||||
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
|
||||
|
|
@ -562,9 +562,9 @@ requests==2.32.5 \
|
|||
# mkdocs-macros-plugin
|
||||
# mkdocs-material
|
||||
# mkdocs-mermaid2-plugin
|
||||
rich==14.3.2 \
|
||||
--hash=sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69 \
|
||||
--hash=sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8
|
||||
rich==14.3.3 \
|
||||
--hash=sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d \
|
||||
--hash=sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b
|
||||
# via neoteroi-mkdocs
|
||||
setuptools==82.0.0 \
|
||||
--hash=sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb \
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 457
|
||||
INVENTREE_API_VERSION = 460
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v460 -> 2026-02-25 : https://github.com/inventree/InvenTree/pull/11374
|
||||
- Adds "updated_at" field to PurchaseOrder, SalesOrder and ReturnOrder API endpoints
|
||||
- Adds "updated_before" and "updated_after" date filters to all three order list endpoints
|
||||
- Adds "updated_at" ordering option to all three order list endpoints
|
||||
|
||||
v459 -> 2026-02-23 : https://github.com/inventree/InvenTree/pull/11411
|
||||
- Changed PurchaseOrderLine "auto_pricing" default value from true to false
|
||||
|
||||
v458 -> 2026-02-22 : https://github.com/inventree/InvenTree/pull/11401
|
||||
- Switches token refresh endpoint to use POST instead of GET (upstream allauth change)
|
||||
|
||||
v457 -> 2026-02-11 : https://github.com/inventree/InvenTree/pull/10887
|
||||
- Extend the "auto allocate" wizard API to include tracked items
|
||||
|
||||
|
|
|
|||
|
|
@ -65,10 +65,12 @@ class InvenTreeConfig(AppConfig):
|
|||
self.start_background_tasks()
|
||||
|
||||
if not InvenTree.ready.isInTestMode(): # pragma: no cover
|
||||
# Update exchange rates
|
||||
InvenTree.tasks.offload_task(InvenTree.tasks.update_exchange_rates)
|
||||
# Let the background worker check for migrations
|
||||
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations)
|
||||
# Update exchange rates
|
||||
InvenTree.tasks.offload_task(
|
||||
InvenTree.tasks.update_exchange_rates, force_async=True
|
||||
)
|
||||
|
||||
self.update_site_url()
|
||||
self.load_unit_registry()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,16 @@ We use the django-dbbackup library to handle backup and restore operations.
|
|||
Ref: https://archmonger.github.io/django-dbbackup/latest/configuration/
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import structlog
|
||||
|
||||
import InvenTree.config
|
||||
import InvenTree.version
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
|
||||
def get_backup_connector_options() -> dict:
|
||||
|
|
@ -131,3 +140,131 @@ def backup_media_filename_template() -> str:
|
|||
default_value='InvenTree-media-{datetime}.{extension}',
|
||||
typecast=str,
|
||||
)
|
||||
|
||||
|
||||
# schema for backup metadata
|
||||
InvenTreeBackupMetadata = dict[str, str | int | bool | None]
|
||||
|
||||
|
||||
def _gather_environment_metadata() -> InvenTreeBackupMetadata:
|
||||
"""Gather metadata about the current environment to be stored with the backup."""
|
||||
import plugin.installer
|
||||
|
||||
new_data: InvenTreeBackupMetadata = {}
|
||||
|
||||
new_data['ivt_1_debug'] = settings.DEBUG
|
||||
new_data['ivt_1_version'] = InvenTree.version.inventreeVersion()
|
||||
new_data['ivt_1_version_api'] = InvenTree.version.inventreeApiVersion()
|
||||
new_data['ivt_1_plugins_enabled'] = settings.PLUGINS_ENABLED
|
||||
new_data['ivt_1_plugins_file_hash'] = plugin.installer.plugins_file_hash()
|
||||
new_data['ivt_1_installer'] = InvenTree.config.inventreeInstaller()
|
||||
new_data['ivt_1_backup_time'] = datetime.now().isoformat()
|
||||
|
||||
return new_data
|
||||
|
||||
|
||||
def _parse_environment_metadata(metadata: InvenTreeBackupMetadata) -> dict[str, str]:
|
||||
"""Parse backup metadata to extract environment information."""
|
||||
data = {}
|
||||
|
||||
data['debug'] = metadata.get('ivt_1_debug', False)
|
||||
data['version'] = metadata.get('ivt_1_version', 'unknown')
|
||||
data['version_api'] = metadata.get('ivt_1_version_api', 'unknown')
|
||||
data['plugins_enabled'] = metadata.get('ivt_1_plugins_enabled', False)
|
||||
data['plugins_file_hash'] = metadata.get('ivt_1_plugins_file_hash', 'unknown')
|
||||
data['installer'] = metadata.get('ivt_1_installer', 'unknown')
|
||||
data['backup_time'] = metadata.get('ivt_1_backup_time', 'unknown')
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def metadata_set(metadata) -> InvenTreeBackupMetadata:
|
||||
"""Set backup metadata for the current backup operation."""
|
||||
return _gather_environment_metadata()
|
||||
|
||||
|
||||
def validate_restore(metadata: InvenTreeBackupMetadata) -> bool | None:
|
||||
"""Validate whether a backup restore operation should proceed, based on the provided metadata."""
|
||||
if metadata.get('ivt_1_version') is None:
|
||||
logger.warning(
|
||||
'INVE-W13: Backup metadata does not contain version information',
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
return True
|
||||
|
||||
current_environment = _parse_environment_metadata(_gather_environment_metadata())
|
||||
backup_environment = _parse_environment_metadata(metadata)
|
||||
|
||||
# Version mismatch
|
||||
if backup_environment['version'] != current_environment['version']:
|
||||
logger.warning(
|
||||
f'INVE-W13: Backup being restored was created with InvenTree version {backup_environment["version"]}, but current version is {current_environment["version"]}',
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
|
||||
# Backup is from newer version - fail
|
||||
try:
|
||||
if int(backup_environment['version_api']) > int(
|
||||
str(current_environment['version_api'])
|
||||
):
|
||||
logger.error(
|
||||
'INVE-E16: Backup being restored was created with a newer version of InvenTree - restore cannot proceed. If you are using the invoke task for your restore this warning might be overridden once with `--restore-allow-newer-version`',
|
||||
error_code='INVE-E16',
|
||||
)
|
||||
# Check for pass flag to allow restore
|
||||
if not settings.BACKUP_RESTORE_ALLOW_NEWER_VERSION: # defaults to False
|
||||
return False
|
||||
else:
|
||||
logger.warning(
|
||||
'INVE-W13: Backup restore is allowing a restore from a newer version of InvenTree - this can lead to data loss or corruption',
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
except ValueError: # pragma: no cover
|
||||
logger.warning(
|
||||
'INVE-W13: Could not parse API version from backup metadata - cannot determine if backup is from newer version',
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
|
||||
# Plugins enabled on backup but not restore environment - warn
|
||||
if (
|
||||
backup_environment['plugins_enabled']
|
||||
and not current_environment['plugins_enabled']
|
||||
):
|
||||
logger.warning(
|
||||
'INVE-W13: Backup being restored was created with plugins enabled, but current environment has plugins disabled - this can lead to data loss',
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
|
||||
# Plugins file hash mismatch - warn
|
||||
if pg_hash := backup_environment['plugins_file_hash']:
|
||||
if pg_hash != current_environment['plugins_file_hash']:
|
||||
logger.warning(
|
||||
'INVE-W13: Backup being restored has a different plugins file hash to the current environment - this can lead to data loss or corruption',
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
|
||||
# Installer mismatch - warn
|
||||
if installer := backup_environment['installer']:
|
||||
if installer != current_environment['installer']:
|
||||
logger.warning(
|
||||
f"INVE-W13: Backup being restored was created with installer '{installer}', but current environment has installer '{current_environment['installer']}'",
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
|
||||
# Age of backup
|
||||
last_backup_time = backup_environment.get('backup_time')
|
||||
if datetime.now() - datetime.fromisoformat(last_backup_time) > timedelta(days=120):
|
||||
logger.warning(
|
||||
f'INVE-W13: Backup being restored is over 120 days old (last backup time: {last_backup_time})',
|
||||
error_code='INVE-W13',
|
||||
)
|
||||
|
||||
if settings.DEBUG: # pragma: no cover
|
||||
logger.info(
|
||||
f'INVE-I3: Backup environment: {backup_environment}', error_code='INVE-I3'
|
||||
)
|
||||
logger.info(
|
||||
f'INVE-I3: Current environment: {current_environment}', error_code='INVE-I3'
|
||||
)
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -111,9 +111,11 @@ class InvenTreeOrderingFilter(filters.OrderingFilter):
|
|||
|
||||
def get_ordering(self, request, queryset, view):
|
||||
"""Override ordering for supporting aliases."""
|
||||
ordering = super().get_ordering(request, queryset, view)
|
||||
ordering = list(super().get_ordering(request, queryset, view) or [])
|
||||
|
||||
aliases = getattr(view, 'ordering_field_aliases', None)
|
||||
lookup_field = getattr(view, 'lookup_field', 'pk')
|
||||
lookup_reversed = len(ordering) > 0 and ordering[-1].startswith('-')
|
||||
|
||||
# Attempt to map ordering fields based on provided aliases
|
||||
if ordering is not None and aliases is not None:
|
||||
|
|
@ -123,9 +125,8 @@ class InvenTreeOrderingFilter(filters.OrderingFilter):
|
|||
ordering = []
|
||||
|
||||
for field in ordering_initial:
|
||||
reverse = field.startswith('-')
|
||||
|
||||
if reverse:
|
||||
field_reversed = field.startswith('-')
|
||||
if field_reversed:
|
||||
field = field[1:]
|
||||
|
||||
# Are aliases defined for this field?
|
||||
|
|
@ -153,11 +154,22 @@ class InvenTreeOrderingFilter(filters.OrderingFilter):
|
|||
continue
|
||||
|
||||
for a in alias:
|
||||
if reverse:
|
||||
if field_reversed:
|
||||
a = '-' + a
|
||||
|
||||
ordering.append(a)
|
||||
|
||||
# Ensure that any API filtering appends the primary-key field
|
||||
# This is to prevent "ambiguous ordering" errors across pagination boundaries
|
||||
# Ref: https://github.com/inventree/InvenTree/issues/11442
|
||||
if lookup_field and not any(
|
||||
field in ordering for field in [lookup_field, f'-{lookup_field}']
|
||||
):
|
||||
if lookup_reversed:
|
||||
ordering.append(f'-{lookup_field}')
|
||||
else:
|
||||
ordering.append(lookup_field)
|
||||
|
||||
return ordering
|
||||
|
||||
|
||||
|
|
@ -220,18 +232,10 @@ class NumericInFilter(rest_filters.BaseInFilter):
|
|||
return super().filter(qs, numeric_values)
|
||||
|
||||
|
||||
SEARCH_ORDER_FILTER = [
|
||||
drf_backend.DjangoFilterBackend,
|
||||
InvenTreeSearchFilter,
|
||||
filters.OrderingFilter,
|
||||
]
|
||||
ORDER_FILTER = [drf_backend.DjangoFilterBackend, InvenTreeOrderingFilter]
|
||||
|
||||
SEARCH_ORDER_FILTER_ALIAS = [
|
||||
SEARCH_ORDER_FILTER = [
|
||||
drf_backend.DjangoFilterBackend,
|
||||
InvenTreeSearchFilter,
|
||||
InvenTreeOrderingFilter,
|
||||
]
|
||||
|
||||
ORDER_FILTER = [drf_backend.DjangoFilterBackend, filters.OrderingFilter]
|
||||
|
||||
ORDER_FILTER_ALIAS = [drf_backend.DjangoFilterBackend, InvenTreeOrderingFilter]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from corsheaders.defaults import default_headers as default_cors_headers
|
|||
import InvenTree.backup
|
||||
from InvenTree.cache import get_cache_config, is_global_cache_enabled
|
||||
from InvenTree.config import get_boolean_setting, get_oidc_private_key, get_setting
|
||||
from InvenTree.ready import isInMainThread
|
||||
from InvenTree.ready import isInMainThread, isRunningBackup
|
||||
from InvenTree.sentry import default_sentry_dsn, init_sentry
|
||||
from InvenTree.version import checkMinPythonVersion, inventreeCommitHash
|
||||
from users.oauth2_scopes import oauth2_scopes
|
||||
|
|
@ -258,12 +258,22 @@ DBBACKUP_EMAIL_SUBJECT_PREFIX = InvenTree.backup.backup_email_prefix()
|
|||
|
||||
DBBACKUP_CONNECTORS = {'default': InvenTree.backup.get_backup_connector_options()}
|
||||
|
||||
DBBACKUP_BACKUP_METADATA_SETTER = InvenTree.backup.metadata_set
|
||||
DBBACKUP_RESTORE_METADATA_VALIDATOR = InvenTree.backup.validate_restore
|
||||
|
||||
# Data storage options
|
||||
DBBACKUP_STORAGE_CONFIG = {
|
||||
'BACKEND': InvenTree.backup.get_backup_storage_backend(),
|
||||
'OPTIONS': InvenTree.backup.get_backup_storage_options(),
|
||||
}
|
||||
|
||||
# This can also be overridden with a command line flag --restore-allow-newer-version when running the restore command
|
||||
BACKUP_RESTORE_ALLOW_NEWER_VERSION = get_boolean_setting(
|
||||
'INVENTREE_BACKUP_RESTORE_ALLOW_NEWER_VERSION',
|
||||
'backup_restore_allow_newer_version',
|
||||
False,
|
||||
)
|
||||
|
||||
# Enable django admin interface?
|
||||
INVENTREE_ADMIN_ENABLED = get_boolean_setting(
|
||||
'INVENTREE_ADMIN_ENABLED', config_key='admin_enabled', default_value=True
|
||||
|
|
@ -862,7 +872,7 @@ TRACING_ENABLED = get_boolean_setting(
|
|||
)
|
||||
TRACING_DETAILS: Optional[dict] = None
|
||||
|
||||
if TRACING_ENABLED: # pragma: no cover
|
||||
if TRACING_ENABLED and not isRunningBackup(): # pragma: no cover
|
||||
from InvenTree.tracing import setup_instruments, setup_tracing
|
||||
|
||||
_t_endpoint = get_setting('INVENTREE_TRACING_ENDPOINT', 'tracing.endpoint', None)
|
||||
|
|
|
|||
|
|
@ -587,7 +587,10 @@ def update_exchange_rates(force: bool = False):
|
|||
Arguments:
|
||||
force: If True, force the update to run regardless of the last update time
|
||||
"""
|
||||
from InvenTree.ready import canAppAccessDatabase
|
||||
from InvenTree.ready import canAppAccessDatabase, isRunningMigrations
|
||||
|
||||
if isRunningMigrations():
|
||||
return
|
||||
|
||||
# Do not update exchange rates if we cannot access the database
|
||||
if not canAppAccessDatabase(allow_test=True, allow_shell=True):
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
from opentelemetry.instrumentation.sqlite3 import SQLite3Instrumentor
|
||||
|
||||
from InvenTree.config import get_testfolder_dir
|
||||
|
||||
|
||||
class CommandTestCase(TestCase):
|
||||
"""Test case for custom management commands."""
|
||||
|
|
@ -66,3 +71,144 @@ class CommandTestCase(TestCase):
|
|||
output = call_command('remove_mfa', username=my_admin3.username, verbosity=0)
|
||||
self.assertEqual(output, 'done')
|
||||
self.assertEqual(my_admin3.authenticator_set.all().count(), 0)
|
||||
|
||||
def test_backup_metadata(self):
|
||||
"""Test the backup metadata functions."""
|
||||
from InvenTree.backup import (
|
||||
_gather_environment_metadata,
|
||||
_parse_environment_metadata,
|
||||
)
|
||||
|
||||
metadata = _gather_environment_metadata()
|
||||
self.assertIn('ivt_1_version', metadata)
|
||||
self.assertIn('ivt_1_plugins_enabled', metadata)
|
||||
|
||||
parsed = _parse_environment_metadata(metadata)
|
||||
self.assertIn('version', parsed)
|
||||
self.assertIn('plugins_enabled', parsed)
|
||||
|
||||
def test_restore_validation(self):
|
||||
"""Test the restore validation functions."""
|
||||
from InvenTree.backup import _gather_environment_metadata, validate_restore
|
||||
|
||||
metadata = _gather_environment_metadata()
|
||||
|
||||
self.assertTrue(validate_restore(metadata))
|
||||
|
||||
# Version
|
||||
with self.assertLogs() as cm:
|
||||
self.assertTrue(validate_restore({}))
|
||||
self.assertIn(
|
||||
'INVE-W13: Backup metadata does not contain version information', str(cm[1])
|
||||
)
|
||||
with self.assertLogs() as cm:
|
||||
self.assertTrue(validate_restore({**metadata, 'ivt_1_version': '123xx'}))
|
||||
self.assertIn(
|
||||
'INVE-W13: Backup being restored was created with InvenTree version',
|
||||
str(cm[1]),
|
||||
)
|
||||
with self.assertLogs() as cm:
|
||||
self.assertFalse(
|
||||
validate_restore({
|
||||
**metadata,
|
||||
'ivt_1_version': '9999',
|
||||
'ivt_1_version_api': '9999',
|
||||
})
|
||||
)
|
||||
self.assertIn(
|
||||
'INVE-E16: Backup being restored was created with a newer version',
|
||||
str(cm[1]),
|
||||
)
|
||||
# not with allow flag
|
||||
with self.settings(BACKUP_RESTORE_ALLOW_NEWER_VERSION=True):
|
||||
with self.assertLogs() as cm:
|
||||
self.assertTrue(
|
||||
validate_restore({
|
||||
**metadata,
|
||||
'ivt_1_version': '9999',
|
||||
'ivt_1_version_api': '9999',
|
||||
})
|
||||
)
|
||||
self.assertIn(
|
||||
'INVE-W13: Backup restore is allowing a restore from a newer version of InvenTree',
|
||||
str(cm[1]),
|
||||
)
|
||||
|
||||
# Plugins enabled
|
||||
with self.settings(PLUGINS_ENABLED=False):
|
||||
with self.assertLogs() as cm:
|
||||
self.assertTrue(validate_restore(metadata))
|
||||
self.assertIn(
|
||||
'INVE-W13: Backup being restored was created with plugins enabled',
|
||||
str(cm[1]),
|
||||
)
|
||||
|
||||
# Plugin hash
|
||||
with self.assertLogs() as cm:
|
||||
self.assertTrue(
|
||||
validate_restore({**metadata, 'ivt_1_plugins_file_hash': '123xx'})
|
||||
)
|
||||
self.assertIn(
|
||||
'INVE-W13: Backup being restored has a different plugins file hash',
|
||||
str(cm[1]),
|
||||
)
|
||||
|
||||
# installer
|
||||
with self.assertLogs() as cm:
|
||||
self.assertTrue(validate_restore({**metadata, 'ivt_1_installer': '123xx'}))
|
||||
self.assertIn(
|
||||
'INVE-W13: Backup being restored was created with installer', str(cm[1])
|
||||
)
|
||||
|
||||
# Age
|
||||
with self.assertLogs() as cm:
|
||||
self.assertTrue(
|
||||
validate_restore({
|
||||
**metadata,
|
||||
'ivt_1_backup_time': '2020-02-02T00:00:00',
|
||||
})
|
||||
)
|
||||
self.assertIn(
|
||||
'INVE-W13: Backup being restored is over 120 days old', str(cm[1])
|
||||
)
|
||||
|
||||
def test_backup_command_e2e(self):
|
||||
"""Test the backup command."""
|
||||
# we only test on sqlite, the environment in which we also run coverage
|
||||
if settings.DB_ENGINE != 'django.db.backends.sqlite3':
|
||||
self.skipTest('Backup command test only runs on sqlite database')
|
||||
|
||||
# disable tracing for now
|
||||
if settings.TRACING_ENABLED: # pragma: no cover
|
||||
print('Disabling tracing for backup command test')
|
||||
SQLite3Instrumentor().uninstrument()
|
||||
|
||||
output_path = get_testfolder_dir().joinpath('backup.zip').resolve()
|
||||
|
||||
# Backup
|
||||
with self.assertLogs() as cm:
|
||||
output = call_command(
|
||||
'dbbackup', noinput=True, verbosity=2, output_path=str(output_path)
|
||||
)
|
||||
self.assertIsNone(output)
|
||||
self.assertIn(f'Writing metadata file to {output_path}', str(cm[1]))
|
||||
|
||||
# Restore
|
||||
with self.assertLogs() as cm:
|
||||
output = call_command(
|
||||
'dbrestore',
|
||||
noinput=True,
|
||||
interactive=False,
|
||||
verbosity=2,
|
||||
input_path=str(output_path),
|
||||
)
|
||||
self.assertIsNone(output)
|
||||
self.assertIn('Using connector from metadata', str(cm[1]))
|
||||
|
||||
# Cleanup the generated backup file and metadata file
|
||||
output_path.unlink()
|
||||
Path(str(output_path) + '.metadata').unlink()
|
||||
|
||||
if settings.TRACING_ENABLED: # pragma: no cover
|
||||
print('Re-enabling tracing for backup command test')
|
||||
SQLite3Instrumentor().instrument()
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from generic.states.api import StatusView
|
|||
from InvenTree.api import BulkDeleteMixin, ParameterListMixin, meta_path
|
||||
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
|
||||
from InvenTree.filters import (
|
||||
SEARCH_ORDER_FILTER_ALIAS,
|
||||
SEARCH_ORDER_FILTER,
|
||||
InvenTreeDateFilter,
|
||||
NumberOrNullFilter,
|
||||
)
|
||||
|
|
@ -343,7 +343,7 @@ class BuildList(
|
|||
|
||||
output_options = BuildListOutputOptions
|
||||
filterset_class = BuildFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
ordering_fields = [
|
||||
'reference',
|
||||
'part',
|
||||
|
|
@ -594,7 +594,7 @@ class BuildLineList(
|
|||
"""API endpoint for accessing a list of BuildLine objects."""
|
||||
|
||||
filterset_class = BuildLineFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
output_options = BuildLineOutputOptions
|
||||
ordering_fields = [
|
||||
'part',
|
||||
|
|
@ -951,7 +951,7 @@ class BuildItemList(
|
|||
|
||||
output_options = BuildItemOutputOptions
|
||||
filterset_class = BuildItemFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
def get_queryset(self):
|
||||
"""Override the queryset method, to perform custom prefetch."""
|
||||
|
|
|
|||
|
|
@ -49,11 +49,7 @@ from InvenTree.api import (
|
|||
meta_path,
|
||||
)
|
||||
from InvenTree.config import CONFIG_LOOKUPS
|
||||
from InvenTree.filters import (
|
||||
ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER_ALIAS,
|
||||
)
|
||||
from InvenTree.filters import ORDER_FILTER, SEARCH_ORDER_FILTER
|
||||
from InvenTree.helpers import inheritors, str2bool
|
||||
from InvenTree.helpers_email import send_email
|
||||
from InvenTree.mixins import (
|
||||
|
|
@ -1079,7 +1075,7 @@ class ParameterList(
|
|||
"""List API endpoint for Parameter objects."""
|
||||
|
||||
filterset_class = ParameterFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
ordering_fields = ['name', 'data', 'units', 'template', 'updated', 'updated_by']
|
||||
|
||||
|
|
|
|||
|
|
@ -637,6 +637,14 @@ SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
|
|||
'default': False,
|
||||
'validator': bool,
|
||||
},
|
||||
'PART_BOM_ALLOW_ZERO_QUANTITY': {
|
||||
'name': _('Allow BOM Zero Quantity'),
|
||||
'description': _(
|
||||
'Accept a zero quantity for BOM item for part. Enables using setup quantity to define a quantity required per build, independent of build quantity'
|
||||
),
|
||||
'default': False,
|
||||
'validator': bool,
|
||||
},
|
||||
'LABEL_ENABLE': {
|
||||
'name': _('Enable label printing'),
|
||||
'description': _('Enable label printing from the web interface'),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import part.models
|
|||
from data_exporter.mixins import DataExportViewMixin
|
||||
from InvenTree.api import ListCreateDestroyAPIView, ParameterListMixin, meta_path
|
||||
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
|
||||
from InvenTree.filters import SEARCH_ORDER_FILTER, SEARCH_ORDER_FILTER_ALIAS
|
||||
from InvenTree.filters import SEARCH_ORDER_FILTER
|
||||
from InvenTree.mixins import (
|
||||
ListCreateAPI,
|
||||
OutputOptionsMixin,
|
||||
|
|
@ -197,7 +197,7 @@ class ManufacturerPartList(
|
|||
"""
|
||||
|
||||
filterset_class = ManufacturerPartFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
output_options = ManufacturerOutputOptions
|
||||
|
||||
ordering_fields = ['part', 'IPN', 'MPN', 'manufacturer']
|
||||
|
|
@ -360,7 +360,7 @@ class SupplierPartList(
|
|||
"""
|
||||
|
||||
filterset_class = SupplierPartFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
output_options = SupplierPartOutputOptions
|
||||
|
||||
ordering_fields = [
|
||||
|
|
@ -475,7 +475,7 @@ class SupplierPriceBreakList(
|
|||
output_options = SupplierPriceBreakOutputOptions
|
||||
|
||||
filterset_class = SupplierPriceBreakFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
ordering_fields = ['quantity', 'supplier', 'SKU', 'price']
|
||||
|
||||
search_fields = ['part__SKU', 'part__supplier__name']
|
||||
|
|
|
|||
|
|
@ -552,6 +552,12 @@ class DataImportRow(models.Model):
|
|||
self.valid = self.validate()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""Update the session progress when a row is deleted."""
|
||||
session = self.session
|
||||
super().delete(*args, **kwargs)
|
||||
session.check_complete()
|
||||
|
||||
session = models.ForeignKey(
|
||||
DataImportSession,
|
||||
on_delete=models.CASCADE,
|
||||
|
|
|
|||
|
|
@ -35,11 +35,7 @@ from InvenTree.api import (
|
|||
meta_path,
|
||||
)
|
||||
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
|
||||
from InvenTree.filters import (
|
||||
SEARCH_ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER_ALIAS,
|
||||
InvenTreeDateFilter,
|
||||
)
|
||||
from InvenTree.filters import SEARCH_ORDER_FILTER, InvenTreeDateFilter
|
||||
from InvenTree.helpers import str2bool
|
||||
from InvenTree.helpers_model import construct_absolute_url, get_base_url
|
||||
from InvenTree.mixins import (
|
||||
|
|
@ -228,6 +224,14 @@ class OrderFilter(FilterSet):
|
|||
label=_('Target Date After'), field_name='target_date', lookup_expr='gt'
|
||||
)
|
||||
|
||||
updated_before = InvenTreeDateFilter(
|
||||
label=_('Updated Before'), field_name='updated_at', lookup_expr='lt'
|
||||
)
|
||||
|
||||
updated_after = InvenTreeDateFilter(
|
||||
label=_('Updated After'), field_name='updated_at', lookup_expr='gt'
|
||||
)
|
||||
|
||||
min_date = InvenTreeDateFilter(label=_('Min Date'), method='filter_min_date')
|
||||
|
||||
def filter_min_date(self, queryset, name, value):
|
||||
|
|
@ -391,7 +395,7 @@ class PurchaseOrderList(
|
|||
"""
|
||||
|
||||
filterset_class = PurchaseOrderFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
output_options = PurchaseOrderOutputOptions
|
||||
|
||||
ordering_field_aliases = {
|
||||
|
|
@ -420,6 +424,7 @@ class PurchaseOrderList(
|
|||
'responsible',
|
||||
'total_price',
|
||||
'project_code',
|
||||
'updated_at',
|
||||
]
|
||||
|
||||
ordering = '-reference'
|
||||
|
|
@ -700,7 +705,7 @@ class PurchaseOrderLineItemList(
|
|||
serializer.data, status=status.HTTP_201_CREATED, headers=headers
|
||||
)
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
ordering_field_aliases = {
|
||||
'MPN': 'part__manufacturer_part__MPN',
|
||||
|
|
@ -859,7 +864,7 @@ class SalesOrderList(
|
|||
"""
|
||||
|
||||
filterset_class = SalesOrderFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
output_options = SalesOrderOutputOptions
|
||||
|
||||
ordering_field_aliases = {
|
||||
|
|
@ -882,6 +887,7 @@ class SalesOrderList(
|
|||
'shipment_date',
|
||||
'total_price',
|
||||
'project_code',
|
||||
'updated_at',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
|
|
@ -1043,7 +1049,7 @@ class SalesOrderLineItemList(
|
|||
|
||||
filterset_class = SalesOrderLineItemFilter
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
output_options = SalesOrderLineItemOutputOptions
|
||||
|
||||
|
|
@ -1289,7 +1295,7 @@ class SalesOrderAllocationList(
|
|||
"""API endpoint for listing SalesOrderAllocation objects."""
|
||||
|
||||
filterset_class = SalesOrderAllocationFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
output_options = SalesOrderAllocationOutputOptions
|
||||
|
||||
ordering_fields = [
|
||||
|
|
@ -1399,7 +1405,7 @@ class SalesOrderShipmentList(SalesOrderShipmentMixin, ListCreateAPI):
|
|||
"""API list endpoint for SalesOrderShipment model."""
|
||||
|
||||
filterset_class = SalesOrderShipmentFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
ordering_fields = ['reference', 'delivery_date', 'shipment_date', 'allocated_items']
|
||||
|
||||
search_fields = [
|
||||
|
|
@ -1528,7 +1534,7 @@ class ReturnOrderList(
|
|||
"""API endpoint for accessing a list of ReturnOrder objects."""
|
||||
|
||||
filterset_class = ReturnOrderFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
output_options = ReturnOrderOutputOptions
|
||||
|
||||
|
|
@ -1549,6 +1555,7 @@ class ReturnOrderList(
|
|||
'target_date',
|
||||
'complete_date',
|
||||
'project_code',
|
||||
'updated_at',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
|
|
@ -1674,7 +1681,7 @@ class ReturnOrderLineItemList(
|
|||
|
||||
filterset_class = ReturnOrderLineItemFilter
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
output_options = ReturnOrderLineItemOutputOptions
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
# Generated by Django 5.2.11 on 2026-02-19 22:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("order", "0114_purchaseorderextraline_project_code_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="purchaseorder",
|
||||
name="updated_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Timestamp of last update",
|
||||
verbose_name="Updated At",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="returnorder",
|
||||
name="updated_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Timestamp of last update",
|
||||
verbose_name="Updated At",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="salesorder",
|
||||
name="updated_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Timestamp of last update",
|
||||
verbose_name="Updated At",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -9,7 +9,7 @@ from django.core.validators import MinValueValidator
|
|||
from django.db import models, transaction
|
||||
from django.db.models import F, Q, QuerySet, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.dispatch.dispatcher import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
|
@ -331,6 +331,8 @@ class Order(
|
|||
if not self.creation_date:
|
||||
self.creation_date = InvenTree.helpers.current_date()
|
||||
|
||||
self.updated_at = InvenTree.helpers.current_time()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def check_locked(self, db: bool = False) -> bool:
|
||||
|
|
@ -498,6 +500,13 @@ class Order(
|
|||
help_text=_('Date order was issued'),
|
||||
)
|
||||
|
||||
updated_at = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_('Updated At'),
|
||||
help_text=_('Timestamp of last update'),
|
||||
)
|
||||
|
||||
responsible = models.ForeignKey(
|
||||
UserModels.Owner,
|
||||
on_delete=models.SET_NULL,
|
||||
|
|
@ -3073,3 +3082,43 @@ class ReturnOrderExtraLine(OrderExtraLine):
|
|||
verbose_name=_('Order'),
|
||||
help_text=_('Return Order'),
|
||||
)
|
||||
|
||||
|
||||
def _touch_order_updated_at(instance):
|
||||
"""Bump updated_at on the parent order without triggering a full save."""
|
||||
if not InvenTree.ready.canAppAccessDatabase(allow_test=True):
|
||||
return
|
||||
instance.order.__class__.objects.filter(pk=instance.order_id).update(
|
||||
updated_at=InvenTree.helpers.current_time()
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=PurchaseOrderLineItem, dispatch_uid='po_lineitem_post_save')
|
||||
@receiver(
|
||||
post_delete, sender=PurchaseOrderLineItem, dispatch_uid='po_lineitem_post_delete'
|
||||
)
|
||||
@receiver(
|
||||
post_save, sender=PurchaseOrderExtraLine, dispatch_uid='po_extraline_post_save'
|
||||
)
|
||||
@receiver(
|
||||
post_delete, sender=PurchaseOrderExtraLine, dispatch_uid='po_extraline_post_delete'
|
||||
)
|
||||
@receiver(post_save, sender=SalesOrderLineItem, dispatch_uid='so_lineitem_post_save')
|
||||
@receiver(
|
||||
post_delete, sender=SalesOrderLineItem, dispatch_uid='so_lineitem_post_delete'
|
||||
)
|
||||
@receiver(post_save, sender=SalesOrderExtraLine, dispatch_uid='so_extraline_post_save')
|
||||
@receiver(
|
||||
post_delete, sender=SalesOrderExtraLine, dispatch_uid='so_extraline_post_delete'
|
||||
)
|
||||
@receiver(post_save, sender=ReturnOrderLineItem, dispatch_uid='ro_lineitem_post_save')
|
||||
@receiver(
|
||||
post_delete, sender=ReturnOrderLineItem, dispatch_uid='ro_lineitem_post_delete'
|
||||
)
|
||||
@receiver(post_save, sender=ReturnOrderExtraLine, dispatch_uid='ro_extraline_post_save')
|
||||
@receiver(
|
||||
post_delete, sender=ReturnOrderExtraLine, dispatch_uid='ro_extraline_post_delete'
|
||||
)
|
||||
def update_order_on_lineitem_change(sender, instance, **kwargs):
|
||||
"""Update parent order updated_at when any line item is saved or deleted."""
|
||||
_touch_order_updated_at(instance)
|
||||
|
|
|
|||
|
|
@ -373,8 +373,14 @@ class PurchaseOrderSerializer(
|
|||
'total_price',
|
||||
'order_currency',
|
||||
'destination',
|
||||
'updated_at',
|
||||
])
|
||||
read_only_fields = ['issue_date', 'complete_date', 'creation_date']
|
||||
read_only_fields = [
|
||||
'issue_date',
|
||||
'complete_date',
|
||||
'creation_date',
|
||||
'updated_at',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'supplier': {'required': True},
|
||||
'order_currency': {'required': False},
|
||||
|
|
@ -639,7 +645,7 @@ class PurchaseOrderLineItemSerializer(
|
|||
help_text=_(
|
||||
'Automatically calculate purchase price based on supplier part data'
|
||||
),
|
||||
default=True,
|
||||
default=False,
|
||||
)
|
||||
|
||||
destination_detail = enable_filter(
|
||||
|
|
@ -1026,8 +1032,9 @@ class SalesOrderSerializer(
|
|||
'shipments_count',
|
||||
'completed_shipments_count',
|
||||
'allocated_lines',
|
||||
'updated_at',
|
||||
])
|
||||
read_only_fields = ['status', 'creation_date', 'shipment_date']
|
||||
read_only_fields = ['status', 'creation_date', 'shipment_date', 'updated_at']
|
||||
extra_kwargs = {'order_currency': {'required': False}}
|
||||
|
||||
def skip_create_fields(self):
|
||||
|
|
@ -1918,8 +1925,9 @@ class ReturnOrderSerializer(
|
|||
'customer_reference',
|
||||
'order_currency',
|
||||
'total_price',
|
||||
'updated_at',
|
||||
])
|
||||
read_only_fields = ['creation_date']
|
||||
read_only_fields = ['creation_date', 'updated_at']
|
||||
|
||||
def skip_create_fields(self):
|
||||
"""Skip these fields when instantiating a new object."""
|
||||
|
|
|
|||
|
|
@ -25,7 +25,17 @@ from part.models import Part
|
|||
from stock.models import StockItem, StockLocation
|
||||
from users.models import Owner
|
||||
|
||||
from .models import PurchaseOrder, PurchaseOrderExtraLine, PurchaseOrderLineItem
|
||||
from .models import (
|
||||
PurchaseOrder,
|
||||
PurchaseOrderExtraLine,
|
||||
PurchaseOrderLineItem,
|
||||
ReturnOrder,
|
||||
ReturnOrderExtraLine,
|
||||
ReturnOrderLineItem,
|
||||
SalesOrder,
|
||||
SalesOrderExtraLine,
|
||||
SalesOrderLineItem,
|
||||
)
|
||||
|
||||
|
||||
class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase):
|
||||
|
|
@ -369,7 +379,8 @@ class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase):
|
|||
order=po,
|
||||
part=sp_1,
|
||||
quantity=3,
|
||||
purchase_price=Money(1000, 'USD'), # "Unit price" should be $100USD
|
||||
# "Unit price" should be $100USD
|
||||
purchase_price=Money(1000, 'USD'),
|
||||
)
|
||||
|
||||
# 13 x 0.1 = 1.3
|
||||
|
|
@ -569,3 +580,151 @@ class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase):
|
|||
p.set_metadata(k, k)
|
||||
|
||||
self.assertEqual(len(p.metadata.keys()), 4)
|
||||
|
||||
|
||||
class OrderUpdatedAtTest(TestCase):
|
||||
"""Tests to verify that the updated_at field is correctly maintained on all order types."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up objects for all three order types."""
|
||||
self.supplier = Company.objects.filter(is_supplier=True).first()
|
||||
self.customer = Company.objects.filter(is_customer=True).first()
|
||||
|
||||
self.po = PurchaseOrder.objects.create(
|
||||
reference='PO-TEST-001', supplier=self.supplier
|
||||
)
|
||||
self.so = SalesOrder.objects.create(
|
||||
reference='SO-TEST-001', customer=self.customer
|
||||
)
|
||||
self.ro = ReturnOrder.objects.create(
|
||||
reference='RO-TEST-001', customer=self.customer
|
||||
)
|
||||
|
||||
self.part = Part.objects.create(name='Test Part', description='Test Part')
|
||||
self.stock_item = StockItem.objects.create(part=self.part, quantity=10)
|
||||
|
||||
def _refresh(self, instance):
|
||||
"""Return a fresh copy of the instance from the database."""
|
||||
return instance.__class__.objects.get(pk=instance.pk)
|
||||
|
||||
def test_updated_at_set_on_save(self):
|
||||
"""updated_at should be populated after the order is saved."""
|
||||
for instance in [self.po, self.so, self.ro]:
|
||||
self.assertIsNotNone(self._refresh(instance).updated_at)
|
||||
|
||||
def test_updated_at_changes_on_save(self):
|
||||
"""updated_at should advance when the order is saved again."""
|
||||
for instance in [self.po, self.so, self.ro]:
|
||||
original = self._refresh(instance).updated_at
|
||||
|
||||
instance.description = 'Updated description'
|
||||
instance.save()
|
||||
|
||||
refreshed = self._refresh(instance)
|
||||
self.assertGreaterEqual(refreshed.updated_at, original)
|
||||
|
||||
def test_updated_at_on_extra_line_add(self):
|
||||
"""updated_at should advance on the parent order when an extra line is added."""
|
||||
for instance, ExtraLine in [
|
||||
(self.po, PurchaseOrderExtraLine),
|
||||
(self.so, SalesOrderExtraLine),
|
||||
(self.ro, ReturnOrderExtraLine),
|
||||
]:
|
||||
before = self._refresh(instance).updated_at
|
||||
|
||||
ExtraLine.objects.create(order=instance, quantity=1)
|
||||
|
||||
after = self._refresh(instance).updated_at
|
||||
self.assertGreaterEqual(after, before)
|
||||
|
||||
def test_updated_at_on_extra_line_update(self):
|
||||
"""updated_at should advance on the parent order when an extra line is updated."""
|
||||
for instance, ExtraLine in [
|
||||
(self.po, PurchaseOrderExtraLine),
|
||||
(self.so, SalesOrderExtraLine),
|
||||
(self.ro, ReturnOrderExtraLine),
|
||||
]:
|
||||
line = ExtraLine.objects.create(order=instance, quantity=1)
|
||||
|
||||
before = self._refresh(instance).updated_at
|
||||
|
||||
line.quantity = 5
|
||||
line.save()
|
||||
|
||||
after = self._refresh(instance).updated_at
|
||||
self.assertGreaterEqual(after, before)
|
||||
|
||||
def test_updated_at_on_extra_line_delete(self):
|
||||
"""updated_at should advance on the parent order when an extra line is deleted."""
|
||||
for instance, ExtraLine in [
|
||||
(self.po, PurchaseOrderExtraLine),
|
||||
(self.so, SalesOrderExtraLine),
|
||||
(self.ro, ReturnOrderExtraLine),
|
||||
]:
|
||||
line = ExtraLine.objects.create(order=instance, quantity=1)
|
||||
|
||||
before = self._refresh(instance).updated_at
|
||||
|
||||
line.delete()
|
||||
|
||||
after = self._refresh(instance).updated_at
|
||||
self.assertGreaterEqual(after, before)
|
||||
|
||||
def test_updated_at_on_line_item_add(self):
|
||||
"""updated_at should advance on the parent order when a regular line item is added."""
|
||||
before_po = self._refresh(self.po).updated_at
|
||||
PurchaseOrderLineItem.objects.create(order=self.po, part=None, quantity=1)
|
||||
self.assertGreaterEqual(self._refresh(self.po).updated_at, before_po)
|
||||
|
||||
before_so = self._refresh(self.so).updated_at
|
||||
SalesOrderLineItem.objects.create(order=self.so, part=None, quantity=1)
|
||||
self.assertGreaterEqual(self._refresh(self.so).updated_at, before_so)
|
||||
|
||||
before_ro = self._refresh(self.ro).updated_at
|
||||
ReturnOrderLineItem.objects.create(
|
||||
order=self.ro, item=self.stock_item, quantity=1
|
||||
)
|
||||
self.assertGreaterEqual(self._refresh(self.ro).updated_at, before_ro)
|
||||
|
||||
def test_updated_at_on_line_item_update(self):
|
||||
"""updated_at should advance on the parent order when a regular line item is updated."""
|
||||
po_line = PurchaseOrderLineItem.objects.create(
|
||||
order=self.po, part=None, quantity=1
|
||||
)
|
||||
so_line = SalesOrderLineItem.objects.create(
|
||||
order=self.so, part=None, quantity=1
|
||||
)
|
||||
ro_line = ReturnOrderLineItem.objects.create(
|
||||
order=self.ro, item=self.stock_item, quantity=1
|
||||
)
|
||||
|
||||
for instance, line in [
|
||||
(self.po, po_line),
|
||||
(self.so, so_line),
|
||||
(self.ro, ro_line),
|
||||
]:
|
||||
before = self._refresh(instance).updated_at
|
||||
line.quantity = 5
|
||||
line.save()
|
||||
self.assertGreaterEqual(self._refresh(instance).updated_at, before)
|
||||
|
||||
def test_updated_at_on_line_item_delete(self):
|
||||
"""updated_at should advance on the parent order when a regular line item is deleted."""
|
||||
po_line = PurchaseOrderLineItem.objects.create(
|
||||
order=self.po, part=None, quantity=1
|
||||
)
|
||||
so_line = SalesOrderLineItem.objects.create(
|
||||
order=self.so, part=None, quantity=1
|
||||
)
|
||||
ro_line = ReturnOrderLineItem.objects.create(
|
||||
order=self.ro, item=self.stock_item, quantity=1
|
||||
)
|
||||
|
||||
for instance, line in [
|
||||
(self.po, po_line),
|
||||
(self.so, so_line),
|
||||
(self.ro, ro_line),
|
||||
]:
|
||||
before = self._refresh(instance).updated_at
|
||||
line.delete()
|
||||
self.assertGreaterEqual(self._refresh(instance).updated_at, before)
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ from InvenTree.api import (
|
|||
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
|
||||
from InvenTree.filters import (
|
||||
ORDER_FILTER,
|
||||
ORDER_FILTER_ALIAS,
|
||||
SEARCH_ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER_ALIAS,
|
||||
InvenTreeDateFilter,
|
||||
InvenTreeSearchFilter,
|
||||
NumberOrNullFilter,
|
||||
|
|
@ -302,7 +300,7 @@ class CategoryTree(ListAPI):
|
|||
queryset = PartCategory.objects.all()
|
||||
serializer_class = part_serializers.CategoryTree
|
||||
|
||||
filter_backends = ORDER_FILTER_ALIAS
|
||||
filter_backends = ORDER_FILTER
|
||||
|
||||
ordering_fields = ['level', 'name', 'subcategories']
|
||||
|
||||
|
|
@ -1080,7 +1078,7 @@ class PartList(
|
|||
filterset_class = PartFilter
|
||||
is_create = True
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
ordering_fields = [
|
||||
'name',
|
||||
|
|
@ -1442,7 +1440,7 @@ class BomList(
|
|||
|
||||
output_options = BomOutputOptions
|
||||
filterset_class = BomFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
search_fields = [
|
||||
'reference',
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from sql_util.utils import SubqueryCount
|
|||
|
||||
import common.currency
|
||||
import common.filters
|
||||
import common.models
|
||||
import common.serializers
|
||||
import company.models
|
||||
import InvenTree.helpers
|
||||
|
|
@ -1656,8 +1657,20 @@ class BomItemSerializer(
|
|||
|
||||
def validate_quantity(self, quantity):
|
||||
"""Perform validation for the BomItem quantity field."""
|
||||
if quantity <= 0:
|
||||
raise serializers.ValidationError(_('Quantity must be greater than zero'))
|
||||
allow_zero_qty = common.models.InvenTreeSetting.get_setting(
|
||||
'PART_BOM_ALLOW_ZERO_QUANTITY', False
|
||||
)
|
||||
|
||||
if allow_zero_qty:
|
||||
if quantity < 0:
|
||||
raise serializers.ValidationError(
|
||||
_('Quantity must be greater than or equal to zero')
|
||||
)
|
||||
else:
|
||||
if quantity <= 0:
|
||||
raise serializers.ValidationError(
|
||||
_('Quantity must be greater than zero')
|
||||
)
|
||||
|
||||
return quantity
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,8 @@ from InvenTree.api import (
|
|||
)
|
||||
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
|
||||
from InvenTree.filters import (
|
||||
ORDER_FILTER_ALIAS,
|
||||
ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER_ALIAS,
|
||||
InvenTreeDateFilter,
|
||||
NumberOrNullFilter,
|
||||
)
|
||||
|
|
@ -455,7 +454,7 @@ class StockLocationTree(ListAPI):
|
|||
queryset = StockLocation.objects.all()
|
||||
serializer_class = StockSerializers.LocationTreeSerializer
|
||||
|
||||
filter_backends = ORDER_FILTER_ALIAS
|
||||
filter_backends = ORDER_FILTER
|
||||
|
||||
ordering_fields = ['level', 'name', 'sublocations']
|
||||
|
||||
|
|
@ -1260,7 +1259,7 @@ class StockList(
|
|||
headers=self.get_success_headers(serializer.data),
|
||||
)
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
ordering_field_aliases = {
|
||||
'part': 'part__name',
|
||||
|
|
|
|||
|
|
@ -575,6 +575,81 @@ class StockItemListTest(StockAPITestCase):
|
|||
for ordering in ['part', 'location', 'stock', 'status', 'IPN', 'MPN', 'SKU']:
|
||||
self.run_ordering_test(self.list_url, ordering)
|
||||
|
||||
def test_pagination(self):
|
||||
"""Test that pagination boundaries are observed correctly.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/issues/11442
|
||||
"""
|
||||
location = StockLocation.objects.first()
|
||||
part = Part.objects.first()
|
||||
|
||||
items = []
|
||||
|
||||
# Delete all existing stock item objects
|
||||
for item in StockItem.objects.all():
|
||||
item.delete()
|
||||
|
||||
for idx in range(1000):
|
||||
items.append(
|
||||
StockItem(
|
||||
part=part,
|
||||
location=location,
|
||||
quantity=idx % 10,
|
||||
level=0,
|
||||
lft=0,
|
||||
rght=0,
|
||||
tree_id=0,
|
||||
)
|
||||
)
|
||||
|
||||
if len(items) >= 100:
|
||||
StockItem.objects.bulk_create(items)
|
||||
items = []
|
||||
|
||||
self.assertEqual(StockItem.objects.count(), 1000)
|
||||
|
||||
url = reverse('api-stock-list')
|
||||
|
||||
# Keep track of the unique PKs we have seen in the results
|
||||
unique_pks = set()
|
||||
|
||||
for idx in range(0, 100, 10):
|
||||
data = self.get(url, {'limit': 10, 'offset': idx}).data
|
||||
self.assertEqual(data['count'], 1000)
|
||||
self.assertEqual(len(data['results']), 10)
|
||||
|
||||
for item in data['results']:
|
||||
self.assertNotIn(
|
||||
item['pk'],
|
||||
unique_pks,
|
||||
f'Duplicate PK {item["pk"]} found in paginated results @ page {idx // 10}',
|
||||
)
|
||||
unique_pks.add(item['pk'])
|
||||
|
||||
self.assertEqual(
|
||||
len(unique_pks), 100, 'Expected to see 100 unique PKs in paginated results'
|
||||
)
|
||||
|
||||
# Run same test again, with reverse ordering on part IPN
|
||||
unique_pks = set()
|
||||
|
||||
for idx in range(0, 100, 10):
|
||||
data = self.get(url, {'limit': 10, 'offset': idx, 'ordering': '-IPN'}).data
|
||||
self.assertEqual(data['count'], 1000)
|
||||
self.assertEqual(len(data['results']), 10)
|
||||
|
||||
for item in data['results']:
|
||||
self.assertNotIn(
|
||||
item['pk'],
|
||||
unique_pks,
|
||||
f'Duplicate PK {item["pk"]} found in paginated results @ page {idx // 10} with reverse ordering',
|
||||
)
|
||||
unique_pks.add(item['pk'])
|
||||
|
||||
self.assertEqual(
|
||||
len(unique_pks), 100, 'Expected to see 100 unique PKs in paginated results'
|
||||
)
|
||||
|
||||
def test_top_level_filtering(self):
|
||||
"""Test filtering against "top level" stock location."""
|
||||
# No filters, should return *all* items
|
||||
|
|
|
|||
|
|
@ -101,16 +101,16 @@ blessed==1.30.0 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
boto3==1.42.54 \
|
||||
--hash=sha256:71194e855bfc81a21872cbe29c41f52ffdbe67e0a184a52c13346ef00b328939 \
|
||||
--hash=sha256:fe3d8ec586c39a0c96327fd317c77ca601ec5f991e9ba7211cacae8db4c07a73
|
||||
boto3==1.42.58 \
|
||||
--hash=sha256:1bc5ff0b7a1a3f42b115481e269e1aada1d68bbfa80a989ac2882d51072907a3 \
|
||||
--hash=sha256:3a21b5bbc8bf8d6472a7ae7bdc77819b1f86f35d127f428f4603bed1b98122c0
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django-anymail
|
||||
# django-storages
|
||||
botocore==1.42.54 \
|
||||
--hash=sha256:853a0822de66d060aeebafa07ca13a03799f7958313d1b29f8dc7e2e1be8f527 \
|
||||
--hash=sha256:ab203d4e57d22913c8386a695d048e003b7508a8a4a7a46c9ddf4ebd67a20b69
|
||||
botocore==1.42.58 \
|
||||
--hash=sha256:3098178f4404cf85c8997ebb7948b3f267cff1dd191b08fc4ebb614ac1013a20 \
|
||||
--hash=sha256:55224d6a91afae0997e8bee62d1ef1ae2dcbc6c210516939b32a774b0b35bec5
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# boto3
|
||||
|
|
@ -219,9 +219,9 @@ brotli==1.2.0 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# fonttools
|
||||
certifi==2026.1.4 \
|
||||
--hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \
|
||||
--hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120
|
||||
certifi==2026.2.25 \
|
||||
--hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
|
||||
--hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
|
|
@ -504,9 +504,9 @@ defusedxml==0.7.1 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# python3-openid
|
||||
django==5.2.11 \
|
||||
--hash=sha256:7f2d292ad8b9ee35e405d965fbbad293758b858c34bbf7f3df551aeeac6f02d3 \
|
||||
--hash=sha256:e7130df33ada9ab5e5e929bc19346a20fe383f5454acb2cc004508f242ee92c0
|
||||
django==5.2.12 \
|
||||
--hash=sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7 \
|
||||
--hash=sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
|
|
@ -538,9 +538,9 @@ django==5.2.11 \
|
|||
# djangorestframework
|
||||
# djangorestframework-simplejwt
|
||||
# drf-spectacular
|
||||
django-allauth[headless, mfa, openid, saml, socialaccount]==65.13.1 \
|
||||
--hash=sha256:2887294beedfd108b4b52ebd182e0ed373deaeb927fc5a22f77bbde3174704a6 \
|
||||
--hash=sha256:2af0d07812f8c1a8e3732feaabe6a9db5ecf3fad6b45b6a0f7fd825f656c5a15
|
||||
django-allauth[headless, mfa, openid, saml, socialaccount]==65.14.3 \
|
||||
--hash=sha256:1d8e1127bdffceb8001bdd9bafbf97661f81e92f4b7bd4f6e799167b0311286d \
|
||||
--hash=sha256:548eef76ab85f6e48f46f98437abf22acf0e834f73e9915fb6cc3f31a0dcdf4d
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
|
|
@ -877,68 +877,68 @@ googleapis-common-protos==1.72.0 \
|
|||
# -c src/backend/requirements.txt
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
grpcio==1.78.1 \
|
||||
--hash=sha256:02b82dcd2fa580f5e82b4cf62ecde1b3c7cc9ba27b946421200706a6e5acaf85 \
|
||||
--hash=sha256:07eb016ea7444a22bef465cce045512756956433f54450aeaa0b443b8563b9ca \
|
||||
--hash=sha256:09fbd4bcaadb6d8604ed1504b0bdf7ac18e48467e83a9d930a70a7fefa27e862 \
|
||||
--hash=sha256:0fa9943d4c7f4a14a9a876153a4e8ee2bb20a410b65c09f31510b2a42271f41b \
|
||||
--hash=sha256:13937b28986f45fee342806b07c6344db785ad74a549ebcb00c659142973556f \
|
||||
--hash=sha256:15f6e636d1152667ddb4022b37534c161c8477274edb26a0b65b215dd0a81e97 \
|
||||
--hash=sha256:1a56bf3ee99af5cf32d469de91bf5de79bdac2e18082b495fc1063ea33f4f2d0 \
|
||||
--hash=sha256:263307118791bc350f4642749a9c8c2d13fec496228ab11070973e568c256bfd \
|
||||
--hash=sha256:27b5cb669603efb7883a882275db88b6b5d6b6c9f0267d5846ba8699b7ace338 \
|
||||
--hash=sha256:27c625532d33ace45d57e775edf1982e183ff8641c72e4e91ef7ba667a149d72 \
|
||||
--hash=sha256:2b7ad2981550ce999e25ce3f10c8863f718a352a2fd655068d29ea3fd37b4907 \
|
||||
--hash=sha256:2c473b54ef1618f4fb85e82ff4994de18143b74efc088b91b5a935a3a45042ba \
|
||||
--hash=sha256:34b6cb16f4b67eeb5206250dc5b4d5e8e3db939535e58efc330e4c61341554bd \
|
||||
--hash=sha256:36aeff5ba8aaf70ceb2cbf6cbba9ad6beef715ad744841f3e0cd977ec02e5966 \
|
||||
--hash=sha256:389b77484959bdaad6a2b7dda44d7d1228381dd669a03f5660392aa0e9385b22 \
|
||||
--hash=sha256:39d21fd30d38a5afb93f0e2e71e2ec2bd894605fb75d41d5a40060c2f98f8d11 \
|
||||
--hash=sha256:39da1680d260c0c619c3b5fa2dc47480ca24d5704c7a548098bca7de7f5dd17f \
|
||||
--hash=sha256:3a8aa79bc6e004394c0abefd4b034c14affda7b66480085d87f5fbadf43b593b \
|
||||
--hash=sha256:409bfe22220889b9906739910a0ee4c197a967c21b8dd14b4b06dd477f8819ce \
|
||||
--hash=sha256:41e4605c923e0e9a84a2718e4948a53a530172bfaf1a6d1ded16ef9c5849fca2 \
|
||||
--hash=sha256:4393bef64cf26dc07cd6f18eaa5170ae4eebaafd4418e7e3a59ca9526a6fa30b \
|
||||
--hash=sha256:43b930cf4f9c4a2262bb3e5d5bc40df426a72538b4f98e46f158b7eb112d2d70 \
|
||||
--hash=sha256:4b8d7fda614cf2af0f73bbb042f3b7fee2ecd4aea69ec98dbd903590a1083529 \
|
||||
--hash=sha256:4d50329b081c223d444751076bb5b389d4f06c2b32d51b31a1e98172e6cecfb9 \
|
||||
--hash=sha256:5380268ab8513445740f1f77bd966d13043d07e2793487e61fd5b5d0935071eb \
|
||||
--hash=sha256:5572c5dd1e43dbb452b466be9794f77e3502bdb6aa6a1a7feca72c98c5085ca7 \
|
||||
--hash=sha256:559f58b6823e1abc38f82e157800aff649146f8906f7998c356cd48ae274d512 \
|
||||
--hash=sha256:5ce1855e8cfc217cdf6bcfe0cf046d7cf81ddcc3e6894d6cfd075f87a2d8f460 \
|
||||
--hash=sha256:656a5bd142caeb8b1efe1fe0b4434ecc7781f44c97cfc7927f6608627cf178c0 \
|
||||
--hash=sha256:716a544969660ed609164aff27b2effd3ff84e54ac81aa4ce77b1607ca917d22 \
|
||||
--hash=sha256:75fa92c47d048d696f12b81a775316fca68385ffc6e6cb1ed1d76c8562579f74 \
|
||||
--hash=sha256:7e836778c13ff70edada16567e8da0c431e8818eaae85b80d11c1ba5782eccbb \
|
||||
--hash=sha256:849cc62eb989bc3be5629d4f3acef79be0d0ff15622201ed251a86d17fef6494 \
|
||||
--hash=sha256:86edb3966778fa05bfdb333688fde5dc9079f9e2a9aa6a5c42e9564b7656ba04 \
|
||||
--hash=sha256:888ceb7821acd925b1c90f0cdceaed1386e69cfe25e496e0771f6c35a156132f \
|
||||
--hash=sha256:8942bdfc143b467c264b048862090c4ba9a0223c52ae28c9ae97754361372e42 \
|
||||
--hash=sha256:8991c2add0d8505178ff6c3ae54bd9386279e712be82fa3733c54067aae9eda1 \
|
||||
--hash=sha256:8e1fcb419da5811deb47b7749b8049f7c62b993ba17822e3c7231e3e0ba65b79 \
|
||||
--hash=sha256:8f27683ca68359bd3f0eb4925824d71e538f84338b3ae337ead2ae43977d7541 \
|
||||
--hash=sha256:917047c19cd120b40aab9a4b8a22e9ce3562f4a1343c0d62b3cd2d5199da3d67 \
|
||||
--hash=sha256:99550e344482e3c21950c034f74668fccf8a546d50c1ecb4f717543bbdc071ba \
|
||||
--hash=sha256:9a00992d6fafe19d648b9ccb4952200c50d8e36d0cce8cf026c56ed3fdc28465 \
|
||||
--hash=sha256:9dee66d142f4a8cca36b5b98a38f006419138c3c89e72071747f8fca415a6d8f \
|
||||
--hash=sha256:a40515b69ac50792f9b8ead260f194ba2bb3285375b6c40c7ff938f14c3df17d \
|
||||
--hash=sha256:a6afd191551fd72e632367dfb083e33cd185bf9ead565f2476bba8ab864ae496 \
|
||||
--hash=sha256:b071dccac245c32cd6b1dd96b722283b855881ca0bf1c685cf843185f5d5d51e \
|
||||
--hash=sha256:b2acd83186305c0802dbc4d81ed0ec2f3e8658d7fde97cfba2f78d7372f05b89 \
|
||||
--hash=sha256:b5d5881d72a09b8336a8f874784a8eeffacde44a7bc1a148bce5a0243a265ef0 \
|
||||
--hash=sha256:ca6aebae928383e971d5eace4f1a217fd7aadaf18d5ddd3163d80354105e9068 \
|
||||
--hash=sha256:cd26048d066b51f39fe9206e2bcc2cea869a5e5b2d13c8d523f4179193047ebd \
|
||||
--hash=sha256:d101fe49b1e0fb4a7aa36ed0c3821a0f67a5956ef572745452d2cd790d723a3f \
|
||||
--hash=sha256:d6fb962947e4fe321eeef3be1ba5ba49d32dea9233c825fcbade8e858c14aaf4 \
|
||||
--hash=sha256:db681513a1bdd879c0b24a5a6a70398da5eaaba0e077a306410dc6008426847a \
|
||||
--hash=sha256:e2a6b33d1050dce2c6f563c5caf7f7cbeebf7fba8cde37ffe3803d50526900d1 \
|
||||
--hash=sha256:e49e720cd6b092504ec7bb2f60eb459aaaf4ce0e5fe20521c201b179e93b5d5d \
|
||||
--hash=sha256:e840405a3f1249509892be2399f668c59b9d492068a2cf326d661a8c79e5e747 \
|
||||
--hash=sha256:ebeec1383aed86530a5f39646984e92d6596c050629982ac54eeb4e2f6ead668 \
|
||||
--hash=sha256:f81816faa426da461e9a597a178832a351d6f1078102590a4b32c77d251b71eb \
|
||||
--hash=sha256:f8759a1347f3b4f03d9a9d4ce8f9f31ad5e5d0144ba06ccfb1ffaeb0ba4c1e20 \
|
||||
--hash=sha256:ff7de398bb3528d44d17e6913a7cfe639e3b15c65595a71155322df16978c5e1 \
|
||||
--hash=sha256:ffbb760df1cd49e0989f9826b2fd48930700db6846ac171eaff404f3cfbe5c28
|
||||
grpcio==1.78.0 \
|
||||
--hash=sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e \
|
||||
--hash=sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d \
|
||||
--hash=sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9 \
|
||||
--hash=sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383 \
|
||||
--hash=sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558 \
|
||||
--hash=sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9 \
|
||||
--hash=sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65 \
|
||||
--hash=sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670 \
|
||||
--hash=sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6 \
|
||||
--hash=sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a \
|
||||
--hash=sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127 \
|
||||
--hash=sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec \
|
||||
--hash=sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452 \
|
||||
--hash=sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e \
|
||||
--hash=sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911 \
|
||||
--hash=sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb \
|
||||
--hash=sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6 \
|
||||
--hash=sha256:5361a0630a7fdb58a6a97638ab70e1dae2893c4d08d7aba64ded28bb9e7a29df \
|
||||
--hash=sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec \
|
||||
--hash=sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c \
|
||||
--hash=sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856 \
|
||||
--hash=sha256:684083fd383e9dc04c794adb838d4faea08b291ce81f64ecd08e4577c7398adf \
|
||||
--hash=sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5 \
|
||||
--hash=sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5 \
|
||||
--hash=sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20 \
|
||||
--hash=sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b \
|
||||
--hash=sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5 \
|
||||
--hash=sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996 \
|
||||
--hash=sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303 \
|
||||
--hash=sha256:86ce2371bfd7f212cf60d8517e5e854475c2c43ce14aa910e136ace72c6db6c1 \
|
||||
--hash=sha256:86f85dd7c947baa707078a236288a289044836d4b640962018ceb9cd1f899af5 \
|
||||
--hash=sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724 \
|
||||
--hash=sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84 \
|
||||
--hash=sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68 \
|
||||
--hash=sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf \
|
||||
--hash=sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e \
|
||||
--hash=sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e \
|
||||
--hash=sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702 \
|
||||
--hash=sha256:ab399ef5e3cd2a721b1038a0f3021001f19c5ab279f145e1146bb0b9f1b2b12c \
|
||||
--hash=sha256:b0c689c02947d636bc7fab3e30cc3a3445cca99c834dfb77cd4a6cabfc1c5597 \
|
||||
--hash=sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7 \
|
||||
--hash=sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb \
|
||||
--hash=sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813 \
|
||||
--hash=sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7 \
|
||||
--hash=sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2 \
|
||||
--hash=sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f \
|
||||
--hash=sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b \
|
||||
--hash=sha256:ce7599575eeb25c0f4dc1be59cada6219f3b56176f799627f44088b21381a28a \
|
||||
--hash=sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb \
|
||||
--hash=sha256:de8cb00d1483a412a06394b8303feec5dcb3b55f81d83aa216dbb6a0b86a94f5 \
|
||||
--hash=sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a \
|
||||
--hash=sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e \
|
||||
--hash=sha256:e888474dee2f59ff68130f8a397792d8cb8e17e6b3434339657ba4ee90845a8c \
|
||||
--hash=sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04 \
|
||||
--hash=sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4 \
|
||||
--hash=sha256:f3d6379493e18ad4d39537a82371c5281e153e963cecb13f953ebac155756525 \
|
||||
--hash=sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de \
|
||||
--hash=sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97 \
|
||||
--hash=sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074 \
|
||||
--hash=sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce \
|
||||
--hash=sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
|
|
@ -949,9 +949,9 @@ gunicorn==25.1.0 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
icalendar==7.0.1 \
|
||||
--hash=sha256:1f577af3e4d022a701aa98f9366dd524f0f1134b90c73c28493f06d5371901d5 \
|
||||
--hash=sha256:68a6295f31ed907885ae79f10336b53ba067e0a66075b6a956abbe5e89ac7b39
|
||||
icalendar==7.0.2 \
|
||||
--hash=sha256:ad31a5825b39522a30b073c6ced3ffcdf6c02cbb7dab69ba2e4de32ddbf77cc9 \
|
||||
--hash=sha256:de844ff5cde32f539bea7644e36d8494032a926b933bedb92621f2f239760806
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django-ical
|
||||
|
|
@ -1669,9 +1669,9 @@ pynacl==1.6.2 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# paramiko
|
||||
pypdf==6.7.2 \
|
||||
--hash=sha256:331b63cd66f63138f152a700565b3e0cebdf4ec8bec3b7594b2522418782f1f3 \
|
||||
--hash=sha256:82a1a48de500ceea59a52a7d979f5095927ef802e4e4fac25ab862a73468acbb
|
||||
pypdf==6.7.5 \
|
||||
--hash=sha256:07ba7f1d6e6d9aa2a17f5452e320a84718d4ce863367f7ede2fd72280349ab13 \
|
||||
--hash=sha256:40bb2e2e872078655f12b9b89e2f900888bb505e88a82150b64f9f34fa25651d
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
|
|
@ -1899,9 +1899,9 @@ rapidfuzz==3.14.3 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
redis==7.2.0 \
|
||||
--hash=sha256:01f591f8598e483f1842d429e8ae3a820804566f1c73dca1b80e23af9fba0497 \
|
||||
--hash=sha256:4dd5bf4bd4ae80510267f14185a15cba2a38666b941aff68cccf0256b51c1f26
|
||||
redis==7.2.1 \
|
||||
--hash=sha256:49e231fbc8df2001436ae5252b3f0f3dc930430239bfeb6da4c7ee92b16e5d33 \
|
||||
--hash=sha256:6163c1a47ee2d9d01221d8456bc1c75ab953cbda18cfbc15e7140e9ba16ca3a5
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django-redis
|
||||
|
|
@ -2164,9 +2164,9 @@ webencodings==0.5.1 \
|
|||
# cssselect2
|
||||
# tinycss2
|
||||
# tinyhtml5
|
||||
whitenoise==6.11.0 \
|
||||
--hash=sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f \
|
||||
--hash=sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258
|
||||
whitenoise==6.12.0 \
|
||||
--hash=sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad \
|
||||
--hash=sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements.in
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ build==1.4.0 \
|
|||
--hash=sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 \
|
||||
--hash=sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936
|
||||
# via pip-tools
|
||||
certifi==2026.1.4 \
|
||||
--hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \
|
||||
--hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120
|
||||
certifi==2026.2.25 \
|
||||
--hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
|
||||
--hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
|
||||
# via
|
||||
# -c src/backend/requirements-3.14.txt
|
||||
# -c src/backend/requirements.txt
|
||||
|
|
@ -401,9 +401,9 @@ distlib==0.4.0 \
|
|||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
django==5.2.11 \
|
||||
--hash=sha256:7f2d292ad8b9ee35e405d965fbbad293758b858c34bbf7f3df551aeeac6f02d3 \
|
||||
--hash=sha256:e7130df33ada9ab5e5e929bc19346a20fe383f5454acb2cc004508f242ee92c0
|
||||
django==5.2.12 \
|
||||
--hash=sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7 \
|
||||
--hash=sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb
|
||||
# via
|
||||
# -c src/backend/requirements-3.14.txt
|
||||
# -c src/backend/requirements.txt
|
||||
|
|
@ -437,10 +437,12 @@ django-types==0.23.0 \
|
|||
--hash=sha256:0727b13ae810c4b1f14eeac9872834ac928c99dc76584ea7c23afc4461e049dd \
|
||||
--hash=sha256:f97fb746166fb15a5f40e470a1fd7a58226349aac9e0a9cb8ae81deb14d94fd0
|
||||
# via -r src/backend/requirements-dev.in
|
||||
filelock==3.21.2 \
|
||||
--hash=sha256:cfd218cfccf8b947fce7837da312ec3359d10ef2a47c8602edd59e0bacffb708 \
|
||||
--hash=sha256:d6cd4dbef3e1bb63bc16500fc5aa100f16e405bbff3fb4231711851be50c1560
|
||||
# via virtualenv
|
||||
filelock==3.24.3 \
|
||||
--hash=sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa \
|
||||
--hash=sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
gprof2dot==2025.4.14 \
|
||||
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
|
||||
--hash=sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce
|
||||
|
|
@ -460,9 +462,9 @@ iniconfig==2.3.0 \
|
|||
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
|
||||
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
|
||||
# via pytest
|
||||
isort==7.0.0 \
|
||||
--hash=sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1 \
|
||||
--hash=sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187
|
||||
isort==8.0.0 \
|
||||
--hash=sha256:184916a933041c7cf718787f7e52064f3c06272aff69a5cb4dc46497bd8911d9 \
|
||||
--hash=sha256:fddea59202f231e170e52e71e3510b99c373b6e571b55d9c7b31b679c0fed47c
|
||||
# via -r src/backend/requirements-dev.in
|
||||
markdown-it-py==4.0.0 \
|
||||
--hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \
|
||||
|
|
@ -503,6 +505,7 @@ platformdirs==4.9.2 \
|
|||
# via
|
||||
# -c src/backend/requirements-3.14.txt
|
||||
# -c src/backend/requirements.txt
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
pluggy==1.6.0 \
|
||||
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
|
||||
|
|
@ -555,10 +558,14 @@ pytest-codspeed==4.3.0 \
|
|||
--hash=sha256:df6a36a2a9da1406bc50428437f657f0bd8c842ae54bee5fb3ad30e01d50c0f5 \
|
||||
--hash=sha256:e6584e641cadf27d894ae90b87c50377232a97cbfd76ee0c7ecd0c056fa3f7f4
|
||||
# via -r src/backend/requirements-dev.in
|
||||
pytest-django==4.11.1 \
|
||||
--hash=sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10 \
|
||||
--hash=sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991
|
||||
pytest-django==4.12.0 \
|
||||
--hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \
|
||||
--hash=sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758
|
||||
# via -r src/backend/requirements-dev.in
|
||||
python-discovery==1.1.0 \
|
||||
--hash=sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268 \
|
||||
--hash=sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b
|
||||
# via virtualenv
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
|
||||
|
|
@ -648,9 +655,9 @@ requests-mock==1.12.1 \
|
|||
--hash=sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563 \
|
||||
--hash=sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401
|
||||
# via -r src/backend/requirements-dev.in
|
||||
rich==14.3.2 \
|
||||
--hash=sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69 \
|
||||
--hash=sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8
|
||||
rich==14.3.3 \
|
||||
--hash=sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d \
|
||||
--hash=sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b
|
||||
# via pytest-codspeed
|
||||
setuptools==82.0.0 \
|
||||
--hash=sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb \
|
||||
|
|
@ -688,9 +695,9 @@ ty==0.0.1a21 \
|
|||
--hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \
|
||||
--hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a
|
||||
# via -r src/backend/requirements-dev.in
|
||||
types-psycopg2==2.9.21.20251012 \
|
||||
--hash=sha256:4cdafd38927da0cfde49804f39ab85afd9c6e9c492800e42f1f0c1a1b0312935 \
|
||||
--hash=sha256:712bad5c423fe979e357edbf40a07ca40ef775d74043de72bd4544ca328cc57e
|
||||
types-psycopg2==2.9.21.20260223 \
|
||||
--hash=sha256:78ed70de2e56bc6b5c26c8c1da8e9af54e49fdc3c94d1504609f3519e2b84f02 \
|
||||
--hash=sha256:c6228ade72d813b0624f4c03feeb89471950ac27cd0506b5debed6f053086bc8
|
||||
# via django-types
|
||||
types-pyyaml==6.0.12.20250915 \
|
||||
--hash=sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3 \
|
||||
|
|
@ -712,9 +719,9 @@ urllib3==2.6.3 \
|
|||
# -c src/backend/requirements-3.14.txt
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
virtualenv==20.36.1 \
|
||||
--hash=sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f \
|
||||
--hash=sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba
|
||||
virtualenv==21.1.0 \
|
||||
--hash=sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07 \
|
||||
--hash=sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44
|
||||
# via pre-commit
|
||||
wheel==0.46.3 \
|
||||
--hash=sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d \
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ build==1.4.0 \
|
|||
--hash=sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 \
|
||||
--hash=sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936
|
||||
# via pip-tools
|
||||
certifi==2026.1.4 \
|
||||
--hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \
|
||||
--hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120
|
||||
certifi==2026.2.25 \
|
||||
--hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
|
||||
--hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
|
|
@ -231,99 +231,113 @@ click==8.3.1 \
|
|||
--hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \
|
||||
--hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6
|
||||
# via pip-tools
|
||||
coverage[toml]==7.13.3 \
|
||||
--hash=sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1 \
|
||||
--hash=sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67 \
|
||||
--hash=sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395 \
|
||||
--hash=sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d \
|
||||
--hash=sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14 \
|
||||
--hash=sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8 \
|
||||
--hash=sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f \
|
||||
--hash=sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0 \
|
||||
--hash=sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c \
|
||||
--hash=sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b \
|
||||
--hash=sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051 \
|
||||
--hash=sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8 \
|
||||
--hash=sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea \
|
||||
--hash=sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f \
|
||||
--hash=sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba \
|
||||
--hash=sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3 \
|
||||
--hash=sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627 \
|
||||
--hash=sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10 \
|
||||
--hash=sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508 \
|
||||
--hash=sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75 \
|
||||
--hash=sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56 \
|
||||
--hash=sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3 \
|
||||
--hash=sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a \
|
||||
--hash=sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4 \
|
||||
--hash=sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab \
|
||||
--hash=sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8 \
|
||||
--hash=sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4 \
|
||||
--hash=sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f \
|
||||
--hash=sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be \
|
||||
--hash=sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024 \
|
||||
--hash=sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73 \
|
||||
--hash=sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43 \
|
||||
--hash=sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c \
|
||||
--hash=sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c \
|
||||
--hash=sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107 \
|
||||
--hash=sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e \
|
||||
--hash=sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0 \
|
||||
--hash=sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895 \
|
||||
--hash=sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43 \
|
||||
--hash=sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba \
|
||||
--hash=sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e \
|
||||
--hash=sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2 \
|
||||
--hash=sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f \
|
||||
--hash=sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4 \
|
||||
--hash=sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25 \
|
||||
--hash=sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6 \
|
||||
--hash=sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23 \
|
||||
--hash=sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7 \
|
||||
--hash=sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6 \
|
||||
--hash=sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910 \
|
||||
--hash=sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155 \
|
||||
--hash=sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b \
|
||||
--hash=sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9 \
|
||||
--hash=sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef \
|
||||
--hash=sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39 \
|
||||
--hash=sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0 \
|
||||
--hash=sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587 \
|
||||
--hash=sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00 \
|
||||
--hash=sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b \
|
||||
--hash=sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3 \
|
||||
--hash=sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e \
|
||||
--hash=sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a \
|
||||
--hash=sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34 \
|
||||
--hash=sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a \
|
||||
--hash=sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b \
|
||||
--hash=sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8 \
|
||||
--hash=sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222 \
|
||||
--hash=sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b \
|
||||
--hash=sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3 \
|
||||
--hash=sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9 \
|
||||
--hash=sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0 \
|
||||
--hash=sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301 \
|
||||
--hash=sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e \
|
||||
--hash=sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f \
|
||||
--hash=sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd \
|
||||
--hash=sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8 \
|
||||
--hash=sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96 \
|
||||
--hash=sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a \
|
||||
--hash=sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f \
|
||||
--hash=sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc \
|
||||
--hash=sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca \
|
||||
--hash=sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595 \
|
||||
--hash=sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba \
|
||||
--hash=sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e \
|
||||
--hash=sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a \
|
||||
--hash=sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1 \
|
||||
--hash=sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac \
|
||||
--hash=sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86 \
|
||||
--hash=sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad \
|
||||
--hash=sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4 \
|
||||
--hash=sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0 \
|
||||
--hash=sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb
|
||||
coverage[toml]==7.13.4 \
|
||||
--hash=sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246 \
|
||||
--hash=sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459 \
|
||||
--hash=sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129 \
|
||||
--hash=sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6 \
|
||||
--hash=sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415 \
|
||||
--hash=sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf \
|
||||
--hash=sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80 \
|
||||
--hash=sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11 \
|
||||
--hash=sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0 \
|
||||
--hash=sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b \
|
||||
--hash=sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9 \
|
||||
--hash=sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b \
|
||||
--hash=sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f \
|
||||
--hash=sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505 \
|
||||
--hash=sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47 \
|
||||
--hash=sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55 \
|
||||
--hash=sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def \
|
||||
--hash=sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689 \
|
||||
--hash=sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012 \
|
||||
--hash=sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5 \
|
||||
--hash=sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3 \
|
||||
--hash=sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95 \
|
||||
--hash=sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9 \
|
||||
--hash=sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601 \
|
||||
--hash=sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997 \
|
||||
--hash=sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c \
|
||||
--hash=sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac \
|
||||
--hash=sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c \
|
||||
--hash=sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa \
|
||||
--hash=sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750 \
|
||||
--hash=sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3 \
|
||||
--hash=sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d \
|
||||
--hash=sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12 \
|
||||
--hash=sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a \
|
||||
--hash=sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932 \
|
||||
--hash=sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356 \
|
||||
--hash=sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92 \
|
||||
--hash=sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148 \
|
||||
--hash=sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39 \
|
||||
--hash=sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634 \
|
||||
--hash=sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6 \
|
||||
--hash=sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72 \
|
||||
--hash=sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98 \
|
||||
--hash=sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef \
|
||||
--hash=sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3 \
|
||||
--hash=sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9 \
|
||||
--hash=sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0 \
|
||||
--hash=sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a \
|
||||
--hash=sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9 \
|
||||
--hash=sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552 \
|
||||
--hash=sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc \
|
||||
--hash=sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f \
|
||||
--hash=sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525 \
|
||||
--hash=sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940 \
|
||||
--hash=sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a \
|
||||
--hash=sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23 \
|
||||
--hash=sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f \
|
||||
--hash=sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc \
|
||||
--hash=sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b \
|
||||
--hash=sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056 \
|
||||
--hash=sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7 \
|
||||
--hash=sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb \
|
||||
--hash=sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a \
|
||||
--hash=sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd \
|
||||
--hash=sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea \
|
||||
--hash=sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126 \
|
||||
--hash=sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299 \
|
||||
--hash=sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9 \
|
||||
--hash=sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b \
|
||||
--hash=sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00 \
|
||||
--hash=sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf \
|
||||
--hash=sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda \
|
||||
--hash=sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2 \
|
||||
--hash=sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5 \
|
||||
--hash=sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d \
|
||||
--hash=sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9 \
|
||||
--hash=sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9 \
|
||||
--hash=sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b \
|
||||
--hash=sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa \
|
||||
--hash=sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092 \
|
||||
--hash=sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58 \
|
||||
--hash=sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea \
|
||||
--hash=sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26 \
|
||||
--hash=sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea \
|
||||
--hash=sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9 \
|
||||
--hash=sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053 \
|
||||
--hash=sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f \
|
||||
--hash=sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0 \
|
||||
--hash=sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3 \
|
||||
--hash=sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256 \
|
||||
--hash=sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a \
|
||||
--hash=sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903 \
|
||||
--hash=sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91 \
|
||||
--hash=sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd \
|
||||
--hash=sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505 \
|
||||
--hash=sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7 \
|
||||
--hash=sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0 \
|
||||
--hash=sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2 \
|
||||
--hash=sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a \
|
||||
--hash=sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71 \
|
||||
--hash=sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985 \
|
||||
--hash=sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242 \
|
||||
--hash=sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d \
|
||||
--hash=sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af \
|
||||
--hash=sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c \
|
||||
--hash=sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0
|
||||
# via -r src/backend/requirements-dev.in
|
||||
cryptography==46.0.5 \
|
||||
--hash=sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72 \
|
||||
|
|
@ -382,9 +396,9 @@ distlib==0.4.0 \
|
|||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
django==5.2.11 \
|
||||
--hash=sha256:7f2d292ad8b9ee35e405d965fbbad293758b858c34bbf7f3df551aeeac6f02d3 \
|
||||
--hash=sha256:e7130df33ada9ab5e5e929bc19346a20fe383f5454acb2cc004508f242ee92c0
|
||||
django==5.2.12 \
|
||||
--hash=sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7 \
|
||||
--hash=sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django-silk
|
||||
|
|
@ -417,9 +431,9 @@ django-types==0.23.0 \
|
|||
--hash=sha256:0727b13ae810c4b1f14eeac9872834ac928c99dc76584ea7c23afc4461e049dd \
|
||||
--hash=sha256:f97fb746166fb15a5f40e470a1fd7a58226349aac9e0a9cb8ae81deb14d94fd0
|
||||
# via -r src/backend/requirements-dev.in
|
||||
filelock==3.20.3 \
|
||||
--hash=sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1 \
|
||||
--hash=sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1
|
||||
filelock==3.24.3 \
|
||||
--hash=sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa \
|
||||
--hash=sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d
|
||||
# via virtualenv
|
||||
gprof2dot==2025.4.14 \
|
||||
--hash=sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e \
|
||||
|
|
@ -439,9 +453,9 @@ iniconfig==2.3.0 \
|
|||
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
|
||||
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
|
||||
# via pytest
|
||||
isort==7.0.0 \
|
||||
--hash=sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1 \
|
||||
--hash=sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187
|
||||
isort==8.0.0 \
|
||||
--hash=sha256:184916a933041c7cf718787f7e52064f3c06272aff69a5cb4dc46497bd8911d9 \
|
||||
--hash=sha256:fddea59202f231e170e52e71e3510b99c373b6e571b55d9c7b31b679c0fed47c
|
||||
# via -r src/backend/requirements-dev.in
|
||||
markdown-it-py==4.0.0 \
|
||||
--hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \
|
||||
|
|
@ -471,9 +485,9 @@ pip==26.0.1 \
|
|||
--hash=sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b \
|
||||
--hash=sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8
|
||||
# via pip-tools
|
||||
pip-tools==7.5.2 \
|
||||
--hash=sha256:2d64d72da6a044da1110257d333960563d7a4743637e8617dd2610ae7b82d60f \
|
||||
--hash=sha256:2fe16db727bbe5bf28765aeb581e792e61be51fc275545ef6725374ad720a1ce
|
||||
pip-tools==7.5.3 \
|
||||
--hash=sha256:3aac0c473240ae90db7213c033401f345b05197293ccbdd2704e52e7a783785e \
|
||||
--hash=sha256:8fa364779ebc010cbfe17cb9de404457ac733e100840423f28f6955de7742d41
|
||||
# via -r src/backend/requirements-dev.in
|
||||
platformdirs==4.9.2 \
|
||||
--hash=sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd \
|
||||
|
|
@ -513,27 +527,27 @@ pytest==9.0.2 \
|
|||
# via
|
||||
# pytest-codspeed
|
||||
# pytest-django
|
||||
pytest-codspeed==4.2.0 \
|
||||
--hash=sha256:04b5d0bc5a1851ba1504d46bf9d7dbb355222a69f2cd440d54295db721b331f7 \
|
||||
--hash=sha256:0881a736285f33b9a8894da8fe8e1775aa1a4310226abe5d1f0329228efb680c \
|
||||
--hash=sha256:238e17abe8f08d8747fa6c7acff34fefd3c40f17a56a7847ca13dc8d6e8c6009 \
|
||||
--hash=sha256:23a0c0fbf8bb4de93a3454fd9e5efcdca164c778aaef0a9da4f233d85cb7f5b8 \
|
||||
--hash=sha256:2de87bde9fbc6fd53f0fd21dcf2599c89e0b8948d49f9bad224edce51c47e26b \
|
||||
--hash=sha256:309b4227f57fcbb9df21e889ea1ae191d0d1cd8b903b698fdb9ea0461dbf1dfe \
|
||||
--hash=sha256:50794dabea6ec90d4288904452051e2febace93e7edf4ca9f2bce8019dd8cd37 \
|
||||
--hash=sha256:609828b03972966b75b9b7416fa2570c4a0f6124f67e02d35cd3658e64312a7b \
|
||||
--hash=sha256:684fcd9491d810ded653a8d38de4835daa2d001645f4a23942862950664273f8 \
|
||||
--hash=sha256:72aab8278452a6d020798b9e4f82780966adb00f80d27a25d1274272c54630d5 \
|
||||
--hash=sha256:748411c832147bfc85f805af78a1ab1684f52d08e14aabe22932bbe46c079a5f \
|
||||
--hash=sha256:7d4fefbd4ae401e2c60f6be920a0be50eef0c3e4a1f0a1c83962efd45be38b39 \
|
||||
--hash=sha256:95aeb2479ca383f6b18e2cc9ebcd3b03ab184980a59a232aea6f370bbf59a1e3 \
|
||||
--hash=sha256:a0ebd87f2a99467a1cfd8e83492c4712976e43d353ee0b5f71cbb057f1393aca \
|
||||
--hash=sha256:dbbb2d61b85bef8fc7e2193f723f9ac2db388a48259d981bbce96319043e9830 \
|
||||
--hash=sha256:e81bbb45c130874ef99aca97929d72682733527a49f84239ba575b5cb843bab0
|
||||
pytest-codspeed==4.3.0 \
|
||||
--hash=sha256:05baff2a61dc9f3e92b92b9c2ab5fb45d9b802438f5373073f5766a91319ed7a \
|
||||
--hash=sha256:10d19b48c9dc35b227a7ac863d4b3c58132256c2ba1aae3220aaddbf6f3f5f9a \
|
||||
--hash=sha256:34f2fd8497456eefbd325673f677ea80d93bb1bc08a578c1fa43a09cec3d1879 \
|
||||
--hash=sha256:5230d9d65f39063a313ed1820df775166227ec5c20a1122968f85653d5efee48 \
|
||||
--hash=sha256:527a3a02eaa3e4d4583adc4ba2327eef79628f3e1c682a4b959439551a72588e \
|
||||
--hash=sha256:619120775e92a3f43fb4ff4c256a251b1554c904d95e2154a382484283f0388a \
|
||||
--hash=sha256:6ac31ae566bf047e91c79b6d12d9a31efedad556ff9258294d6ecebeacb92fa4 \
|
||||
--hash=sha256:878aad5e4bb7b401ad8d82f3af5186030cd2bd0d0446782e10dabb9db8827466 \
|
||||
--hash=sha256:9858c2a6e1f391d5696757e7b6e9484749a7376c46f8b4dd9aebf093479a9667 \
|
||||
--hash=sha256:a2f5bb6d8898bea7db45e3c8b916ee48e36905b929477bb511b79c5a3ccacda4 \
|
||||
--hash=sha256:b2acecc4126658abebc683b38121adec405a46e18a619d49d6154c6e60c5deb2 \
|
||||
--hash=sha256:bec30f4fc9c4973143cd80f0d33fa780e9fa3e01e4dbe8cedf229e72f1212c62 \
|
||||
--hash=sha256:dbeff1eb2f2e36df088658b556fa993e6937bf64ffb07406de4db16fd2b26874 \
|
||||
--hash=sha256:df0d1f6ea594f29b745c634d66d5f5f1caa1c3abd2af82fea49d656038e8fc77 \
|
||||
--hash=sha256:df6a36a2a9da1406bc50428437f657f0bd8c842ae54bee5fb3ad30e01d50c0f5 \
|
||||
--hash=sha256:e6584e641cadf27d894ae90b87c50377232a97cbfd76ee0c7ecd0c056fa3f7f4
|
||||
# via -r src/backend/requirements-dev.in
|
||||
pytest-django==4.11.1 \
|
||||
--hash=sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10 \
|
||||
--hash=sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991
|
||||
pytest-django==4.12.0 \
|
||||
--hash=sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85 \
|
||||
--hash=sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758
|
||||
# via -r src/backend/requirements-dev.in
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
|
|
@ -622,9 +636,9 @@ requests-mock==1.12.1 \
|
|||
--hash=sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563 \
|
||||
--hash=sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401
|
||||
# via -r src/backend/requirements-dev.in
|
||||
rich==14.3.2 \
|
||||
--hash=sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69 \
|
||||
--hash=sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8
|
||||
rich==14.3.3 \
|
||||
--hash=sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d \
|
||||
--hash=sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b
|
||||
# via pytest-codspeed
|
||||
setuptools==82.0.0 \
|
||||
--hash=sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb \
|
||||
|
|
@ -731,9 +745,9 @@ urllib3==2.6.3 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
virtualenv==20.36.1 \
|
||||
--hash=sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f \
|
||||
--hash=sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba
|
||||
virtualenv==20.38.0 \
|
||||
--hash=sha256:94f39b1abaea5185bf7ea5a46702b56f1d0c9aa2f41a6c2b8b0af4ddc74c10a7 \
|
||||
--hash=sha256:d6e78e5889de3a4742df2d3d44e779366325a90cf356f15621fddace82431794
|
||||
# via pre-commit
|
||||
wheel==0.46.3 \
|
||||
--hash=sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d \
|
||||
|
|
|
|||
|
|
@ -95,15 +95,15 @@ blessed==1.30.0 \
|
|||
--hash=sha256:4061a9f10dd22798716c2548ba36385af6a29d856c897f367c6ccc927e0b3a5a \
|
||||
--hash=sha256:4d547019d7b40fc5420ea2ba2bc180fdccc31d6715298e2b49ffa7b020d44667
|
||||
# via -r src/backend/requirements.in
|
||||
boto3==1.42.54 \
|
||||
--hash=sha256:71194e855bfc81a21872cbe29c41f52ffdbe67e0a184a52c13346ef00b328939 \
|
||||
--hash=sha256:fe3d8ec586c39a0c96327fd317c77ca601ec5f991e9ba7211cacae8db4c07a73
|
||||
boto3==1.42.58 \
|
||||
--hash=sha256:1bc5ff0b7a1a3f42b115481e269e1aada1d68bbfa80a989ac2882d51072907a3 \
|
||||
--hash=sha256:3a21b5bbc8bf8d6472a7ae7bdc77819b1f86f35d127f428f4603bed1b98122c0
|
||||
# via
|
||||
# django-anymail
|
||||
# django-storages
|
||||
botocore==1.42.54 \
|
||||
--hash=sha256:853a0822de66d060aeebafa07ca13a03799f7958313d1b29f8dc7e2e1be8f527 \
|
||||
--hash=sha256:ab203d4e57d22913c8386a695d048e003b7508a8a4a7a46c9ddf4ebd67a20b69
|
||||
botocore==1.42.58 \
|
||||
--hash=sha256:3098178f4404cf85c8997ebb7948b3f267cff1dd191b08fc4ebb614ac1013a20 \
|
||||
--hash=sha256:55224d6a91afae0997e8bee62d1ef1ae2dcbc6c210516939b32a774b0b35bec5
|
||||
# via
|
||||
# boto3
|
||||
# s3transfer
|
||||
|
|
@ -209,9 +209,9 @@ brotli==1.2.0 \
|
|||
--hash=sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361 \
|
||||
--hash=sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d
|
||||
# via fonttools
|
||||
certifi==2026.1.4 \
|
||||
--hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \
|
||||
--hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120
|
||||
certifi==2026.2.25 \
|
||||
--hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
|
||||
--hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
|
||||
# via
|
||||
# requests
|
||||
# sentry-sdk
|
||||
|
|
@ -485,9 +485,9 @@ defusedxml==0.7.1 \
|
|||
--hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \
|
||||
--hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
|
||||
# via python3-openid
|
||||
django==5.2.11 \
|
||||
--hash=sha256:7f2d292ad8b9ee35e405d965fbbad293758b858c34bbf7f3df551aeeac6f02d3 \
|
||||
--hash=sha256:e7130df33ada9ab5e5e929bc19346a20fe383f5454acb2cc004508f242ee92c0
|
||||
django==5.2.12 \
|
||||
--hash=sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7 \
|
||||
--hash=sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-allauth
|
||||
|
|
@ -518,9 +518,9 @@ django==5.2.11 \
|
|||
# djangorestframework
|
||||
# djangorestframework-simplejwt
|
||||
# drf-spectacular
|
||||
django-allauth[headless, mfa, openid, saml, socialaccount]==65.13.1 \
|
||||
--hash=sha256:2887294beedfd108b4b52ebd182e0ed373deaeb927fc5a22f77bbde3174704a6 \
|
||||
--hash=sha256:2af0d07812f8c1a8e3732feaabe6a9db5ecf3fad6b45b6a0f7fd825f656c5a15
|
||||
django-allauth[headless, mfa, openid, saml, socialaccount]==65.14.3 \
|
||||
--hash=sha256:1d8e1127bdffceb8001bdd9bafbf97661f81e92f4b7bd4f6e799167b0311286d \
|
||||
--hash=sha256:548eef76ab85f6e48f46f98437abf22acf0e834f73e9915fb6cc3f31a0dcdf4d
|
||||
# via -r src/backend/requirements.in
|
||||
django-anymail[amazon-ses, postal]==14.0 \
|
||||
--hash=sha256:98e5575bcff231952a92117b7d0a4dd37fab582333a343de302d415a31dc72b1 \
|
||||
|
|
@ -773,68 +773,68 @@ googleapis-common-protos==1.72.0 \
|
|||
# via
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
grpcio==1.78.1 \
|
||||
--hash=sha256:02b82dcd2fa580f5e82b4cf62ecde1b3c7cc9ba27b946421200706a6e5acaf85 \
|
||||
--hash=sha256:07eb016ea7444a22bef465cce045512756956433f54450aeaa0b443b8563b9ca \
|
||||
--hash=sha256:09fbd4bcaadb6d8604ed1504b0bdf7ac18e48467e83a9d930a70a7fefa27e862 \
|
||||
--hash=sha256:0fa9943d4c7f4a14a9a876153a4e8ee2bb20a410b65c09f31510b2a42271f41b \
|
||||
--hash=sha256:13937b28986f45fee342806b07c6344db785ad74a549ebcb00c659142973556f \
|
||||
--hash=sha256:15f6e636d1152667ddb4022b37534c161c8477274edb26a0b65b215dd0a81e97 \
|
||||
--hash=sha256:1a56bf3ee99af5cf32d469de91bf5de79bdac2e18082b495fc1063ea33f4f2d0 \
|
||||
--hash=sha256:263307118791bc350f4642749a9c8c2d13fec496228ab11070973e568c256bfd \
|
||||
--hash=sha256:27b5cb669603efb7883a882275db88b6b5d6b6c9f0267d5846ba8699b7ace338 \
|
||||
--hash=sha256:27c625532d33ace45d57e775edf1982e183ff8641c72e4e91ef7ba667a149d72 \
|
||||
--hash=sha256:2b7ad2981550ce999e25ce3f10c8863f718a352a2fd655068d29ea3fd37b4907 \
|
||||
--hash=sha256:2c473b54ef1618f4fb85e82ff4994de18143b74efc088b91b5a935a3a45042ba \
|
||||
--hash=sha256:34b6cb16f4b67eeb5206250dc5b4d5e8e3db939535e58efc330e4c61341554bd \
|
||||
--hash=sha256:36aeff5ba8aaf70ceb2cbf6cbba9ad6beef715ad744841f3e0cd977ec02e5966 \
|
||||
--hash=sha256:389b77484959bdaad6a2b7dda44d7d1228381dd669a03f5660392aa0e9385b22 \
|
||||
--hash=sha256:39d21fd30d38a5afb93f0e2e71e2ec2bd894605fb75d41d5a40060c2f98f8d11 \
|
||||
--hash=sha256:39da1680d260c0c619c3b5fa2dc47480ca24d5704c7a548098bca7de7f5dd17f \
|
||||
--hash=sha256:3a8aa79bc6e004394c0abefd4b034c14affda7b66480085d87f5fbadf43b593b \
|
||||
--hash=sha256:409bfe22220889b9906739910a0ee4c197a967c21b8dd14b4b06dd477f8819ce \
|
||||
--hash=sha256:41e4605c923e0e9a84a2718e4948a53a530172bfaf1a6d1ded16ef9c5849fca2 \
|
||||
--hash=sha256:4393bef64cf26dc07cd6f18eaa5170ae4eebaafd4418e7e3a59ca9526a6fa30b \
|
||||
--hash=sha256:43b930cf4f9c4a2262bb3e5d5bc40df426a72538b4f98e46f158b7eb112d2d70 \
|
||||
--hash=sha256:4b8d7fda614cf2af0f73bbb042f3b7fee2ecd4aea69ec98dbd903590a1083529 \
|
||||
--hash=sha256:4d50329b081c223d444751076bb5b389d4f06c2b32d51b31a1e98172e6cecfb9 \
|
||||
--hash=sha256:5380268ab8513445740f1f77bd966d13043d07e2793487e61fd5b5d0935071eb \
|
||||
--hash=sha256:5572c5dd1e43dbb452b466be9794f77e3502bdb6aa6a1a7feca72c98c5085ca7 \
|
||||
--hash=sha256:559f58b6823e1abc38f82e157800aff649146f8906f7998c356cd48ae274d512 \
|
||||
--hash=sha256:5ce1855e8cfc217cdf6bcfe0cf046d7cf81ddcc3e6894d6cfd075f87a2d8f460 \
|
||||
--hash=sha256:656a5bd142caeb8b1efe1fe0b4434ecc7781f44c97cfc7927f6608627cf178c0 \
|
||||
--hash=sha256:716a544969660ed609164aff27b2effd3ff84e54ac81aa4ce77b1607ca917d22 \
|
||||
--hash=sha256:75fa92c47d048d696f12b81a775316fca68385ffc6e6cb1ed1d76c8562579f74 \
|
||||
--hash=sha256:7e836778c13ff70edada16567e8da0c431e8818eaae85b80d11c1ba5782eccbb \
|
||||
--hash=sha256:849cc62eb989bc3be5629d4f3acef79be0d0ff15622201ed251a86d17fef6494 \
|
||||
--hash=sha256:86edb3966778fa05bfdb333688fde5dc9079f9e2a9aa6a5c42e9564b7656ba04 \
|
||||
--hash=sha256:888ceb7821acd925b1c90f0cdceaed1386e69cfe25e496e0771f6c35a156132f \
|
||||
--hash=sha256:8942bdfc143b467c264b048862090c4ba9a0223c52ae28c9ae97754361372e42 \
|
||||
--hash=sha256:8991c2add0d8505178ff6c3ae54bd9386279e712be82fa3733c54067aae9eda1 \
|
||||
--hash=sha256:8e1fcb419da5811deb47b7749b8049f7c62b993ba17822e3c7231e3e0ba65b79 \
|
||||
--hash=sha256:8f27683ca68359bd3f0eb4925824d71e538f84338b3ae337ead2ae43977d7541 \
|
||||
--hash=sha256:917047c19cd120b40aab9a4b8a22e9ce3562f4a1343c0d62b3cd2d5199da3d67 \
|
||||
--hash=sha256:99550e344482e3c21950c034f74668fccf8a546d50c1ecb4f717543bbdc071ba \
|
||||
--hash=sha256:9a00992d6fafe19d648b9ccb4952200c50d8e36d0cce8cf026c56ed3fdc28465 \
|
||||
--hash=sha256:9dee66d142f4a8cca36b5b98a38f006419138c3c89e72071747f8fca415a6d8f \
|
||||
--hash=sha256:a40515b69ac50792f9b8ead260f194ba2bb3285375b6c40c7ff938f14c3df17d \
|
||||
--hash=sha256:a6afd191551fd72e632367dfb083e33cd185bf9ead565f2476bba8ab864ae496 \
|
||||
--hash=sha256:b071dccac245c32cd6b1dd96b722283b855881ca0bf1c685cf843185f5d5d51e \
|
||||
--hash=sha256:b2acd83186305c0802dbc4d81ed0ec2f3e8658d7fde97cfba2f78d7372f05b89 \
|
||||
--hash=sha256:b5d5881d72a09b8336a8f874784a8eeffacde44a7bc1a148bce5a0243a265ef0 \
|
||||
--hash=sha256:ca6aebae928383e971d5eace4f1a217fd7aadaf18d5ddd3163d80354105e9068 \
|
||||
--hash=sha256:cd26048d066b51f39fe9206e2bcc2cea869a5e5b2d13c8d523f4179193047ebd \
|
||||
--hash=sha256:d101fe49b1e0fb4a7aa36ed0c3821a0f67a5956ef572745452d2cd790d723a3f \
|
||||
--hash=sha256:d6fb962947e4fe321eeef3be1ba5ba49d32dea9233c825fcbade8e858c14aaf4 \
|
||||
--hash=sha256:db681513a1bdd879c0b24a5a6a70398da5eaaba0e077a306410dc6008426847a \
|
||||
--hash=sha256:e2a6b33d1050dce2c6f563c5caf7f7cbeebf7fba8cde37ffe3803d50526900d1 \
|
||||
--hash=sha256:e49e720cd6b092504ec7bb2f60eb459aaaf4ce0e5fe20521c201b179e93b5d5d \
|
||||
--hash=sha256:e840405a3f1249509892be2399f668c59b9d492068a2cf326d661a8c79e5e747 \
|
||||
--hash=sha256:ebeec1383aed86530a5f39646984e92d6596c050629982ac54eeb4e2f6ead668 \
|
||||
--hash=sha256:f81816faa426da461e9a597a178832a351d6f1078102590a4b32c77d251b71eb \
|
||||
--hash=sha256:f8759a1347f3b4f03d9a9d4ce8f9f31ad5e5d0144ba06ccfb1ffaeb0ba4c1e20 \
|
||||
--hash=sha256:ff7de398bb3528d44d17e6913a7cfe639e3b15c65595a71155322df16978c5e1 \
|
||||
--hash=sha256:ffbb760df1cd49e0989f9826b2fd48930700db6846ac171eaff404f3cfbe5c28
|
||||
grpcio==1.78.0 \
|
||||
--hash=sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e \
|
||||
--hash=sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d \
|
||||
--hash=sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9 \
|
||||
--hash=sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383 \
|
||||
--hash=sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558 \
|
||||
--hash=sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9 \
|
||||
--hash=sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65 \
|
||||
--hash=sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670 \
|
||||
--hash=sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6 \
|
||||
--hash=sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a \
|
||||
--hash=sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127 \
|
||||
--hash=sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec \
|
||||
--hash=sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452 \
|
||||
--hash=sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e \
|
||||
--hash=sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911 \
|
||||
--hash=sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb \
|
||||
--hash=sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6 \
|
||||
--hash=sha256:5361a0630a7fdb58a6a97638ab70e1dae2893c4d08d7aba64ded28bb9e7a29df \
|
||||
--hash=sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec \
|
||||
--hash=sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c \
|
||||
--hash=sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856 \
|
||||
--hash=sha256:684083fd383e9dc04c794adb838d4faea08b291ce81f64ecd08e4577c7398adf \
|
||||
--hash=sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5 \
|
||||
--hash=sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5 \
|
||||
--hash=sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20 \
|
||||
--hash=sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b \
|
||||
--hash=sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5 \
|
||||
--hash=sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996 \
|
||||
--hash=sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303 \
|
||||
--hash=sha256:86ce2371bfd7f212cf60d8517e5e854475c2c43ce14aa910e136ace72c6db6c1 \
|
||||
--hash=sha256:86f85dd7c947baa707078a236288a289044836d4b640962018ceb9cd1f899af5 \
|
||||
--hash=sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724 \
|
||||
--hash=sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84 \
|
||||
--hash=sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68 \
|
||||
--hash=sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf \
|
||||
--hash=sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e \
|
||||
--hash=sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e \
|
||||
--hash=sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702 \
|
||||
--hash=sha256:ab399ef5e3cd2a721b1038a0f3021001f19c5ab279f145e1146bb0b9f1b2b12c \
|
||||
--hash=sha256:b0c689c02947d636bc7fab3e30cc3a3445cca99c834dfb77cd4a6cabfc1c5597 \
|
||||
--hash=sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7 \
|
||||
--hash=sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb \
|
||||
--hash=sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813 \
|
||||
--hash=sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7 \
|
||||
--hash=sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2 \
|
||||
--hash=sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f \
|
||||
--hash=sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b \
|
||||
--hash=sha256:ce7599575eeb25c0f4dc1be59cada6219f3b56176f799627f44088b21381a28a \
|
||||
--hash=sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb \
|
||||
--hash=sha256:de8cb00d1483a412a06394b8303feec5dcb3b55f81d83aa216dbb6a0b86a94f5 \
|
||||
--hash=sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a \
|
||||
--hash=sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e \
|
||||
--hash=sha256:e888474dee2f59ff68130f8a397792d8cb8e17e6b3434339657ba4ee90845a8c \
|
||||
--hash=sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04 \
|
||||
--hash=sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4 \
|
||||
--hash=sha256:f3d6379493e18ad4d39537a82371c5281e153e963cecb13f953ebac155756525 \
|
||||
--hash=sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de \
|
||||
--hash=sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97 \
|
||||
--hash=sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074 \
|
||||
--hash=sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce \
|
||||
--hash=sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
|
|
@ -842,9 +842,9 @@ gunicorn==25.1.0 \
|
|||
--hash=sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616 \
|
||||
--hash=sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b
|
||||
# via -r src/backend/requirements.in
|
||||
icalendar==7.0.1 \
|
||||
--hash=sha256:1f577af3e4d022a701aa98f9366dd524f0f1134b90c73c28493f06d5371901d5 \
|
||||
--hash=sha256:68a6295f31ed907885ae79f10336b53ba067e0a66075b6a956abbe5e89ac7b39
|
||||
icalendar==7.0.2 \
|
||||
--hash=sha256:ad31a5825b39522a30b073c6ced3ffcdf6c02cbb7dab69ba2e4de32ddbf77cc9 \
|
||||
--hash=sha256:de844ff5cde32f539bea7644e36d8494032a926b933bedb92621f2f239760806
|
||||
# via django-ical
|
||||
idna==3.11 \
|
||||
--hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \
|
||||
|
|
@ -1477,9 +1477,9 @@ pynacl==1.6.2 \
|
|||
--hash=sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2 \
|
||||
--hash=sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9
|
||||
# via paramiko
|
||||
pypdf==6.7.2 \
|
||||
--hash=sha256:331b63cd66f63138f152a700565b3e0cebdf4ec8bec3b7594b2522418782f1f3 \
|
||||
--hash=sha256:82a1a48de500ceea59a52a7d979f5095927ef802e4e4fac25ab862a73468acbb
|
||||
pypdf==6.7.5 \
|
||||
--hash=sha256:07ba7f1d6e6d9aa2a17f5452e320a84718d4ce863367f7ede2fd72280349ab13 \
|
||||
--hash=sha256:40bb2e2e872078655f12b9b89e2f900888bb505e88a82150b64f9f34fa25651d
|
||||
# via -r src/backend/requirements.in
|
||||
pyphen==0.17.2 \
|
||||
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
|
||||
|
|
@ -1686,9 +1686,9 @@ rapidfuzz==3.14.3 \
|
|||
--hash=sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb \
|
||||
--hash=sha256:fce3152f94afcfd12f3dd8cf51e48fa606e3cb56719bccebe3b401f43d0714f9
|
||||
# via -r src/backend/requirements.in
|
||||
redis==7.2.0 \
|
||||
--hash=sha256:01f591f8598e483f1842d429e8ae3a820804566f1c73dca1b80e23af9fba0497 \
|
||||
--hash=sha256:4dd5bf4bd4ae80510267f14185a15cba2a38666b941aff68cccf0256b51c1f26
|
||||
redis==7.2.1 \
|
||||
--hash=sha256:49e231fbc8df2001436ae5252b3f0f3dc930430239bfeb6da4c7ee92b16e5d33 \
|
||||
--hash=sha256:6163c1a47ee2d9d01221d8456bc1c75ab953cbda18cfbc15e7140e9ba16ca3a5
|
||||
# via django-redis
|
||||
referencing==0.37.0 \
|
||||
--hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \
|
||||
|
|
@ -1922,9 +1922,9 @@ webencodings==0.5.1 \
|
|||
# cssselect2
|
||||
# tinycss2
|
||||
# tinyhtml5
|
||||
whitenoise==6.11.0 \
|
||||
--hash=sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f \
|
||||
--hash=sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258
|
||||
whitenoise==6.12.0 \
|
||||
--hash=sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad \
|
||||
--hash=sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2
|
||||
# via -r src/backend/requirements.in
|
||||
wrapt==1.17.3 \
|
||||
--hash=sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56 \
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
This file contains historical changelog information for the InvenTree UI components library.
|
||||
|
||||
### 0.8.0 - March 2026
|
||||
|
||||
Exposes the `monitorDataOutput` hook, which allows plugins to monitor the output of a long-running task and display notifications when the task is complete. This is useful for plugins that need to perform long-running tasks and want to provide feedback to the user when the task is complete.
|
||||
|
||||
### 0.7.0 - October 2025
|
||||
|
||||
Exposes stock adjustment forms to plugins, allowing plugins to adjust stock adjustments using the common InvenTree UI form components.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
import { t } from '@lingui/core/macro';
|
||||
import { useDocumentVisibility } from '@mantine/hooks';
|
||||
import { notifications, showNotification } from '@mantine/notifications';
|
||||
import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ProgressBar } from '../components/ProgressBar';
|
||||
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../functions/Api';
|
||||
|
||||
/**
|
||||
* Hook for monitoring a data output process running on the server
|
||||
*/
|
||||
export default function monitorDataOutput({
|
||||
api,
|
||||
title,
|
||||
hostname,
|
||||
id
|
||||
}: {
|
||||
api: AxiosInstance;
|
||||
title: string;
|
||||
hostname?: string;
|
||||
id?: number;
|
||||
}) {
|
||||
const visibility = useDocumentVisibility();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!id) {
|
||||
setLoading(true);
|
||||
showNotification({
|
||||
id: `data-output-${id}`,
|
||||
title: title,
|
||||
loading: true,
|
||||
autoClose: false,
|
||||
withCloseButton: false,
|
||||
message: <ProgressBar size='lg' value={0} progressLabel />
|
||||
});
|
||||
} else setLoading(false);
|
||||
}, [id, title]);
|
||||
|
||||
useQuery({
|
||||
enabled: !!id && loading && visibility === 'visible',
|
||||
refetchInterval: 500,
|
||||
queryKey: ['data-output', id, title],
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(apiUrl(ApiEndpoints.data_output, id))
|
||||
.then((response) => {
|
||||
const data = response?.data ?? {};
|
||||
|
||||
if (!!data.errors || !!data.error) {
|
||||
setLoading(false);
|
||||
|
||||
const error: string =
|
||||
data?.error ?? data?.errors?.error ?? t`Process failed`;
|
||||
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
icon: <IconExclamationCircle />,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: error,
|
||||
color: 'red'
|
||||
});
|
||||
} else if (data.complete) {
|
||||
setLoading(false);
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: t`Process completed successfully`,
|
||||
color: 'green',
|
||||
icon: <IconCircleCheck />
|
||||
});
|
||||
|
||||
if (data.output) {
|
||||
const url = data.output;
|
||||
const base = hostname ?? window.location.hostname;
|
||||
|
||||
const downloadUrl = new URL(url, base);
|
||||
|
||||
window.open(downloadUrl.toString(), '_blank');
|
||||
}
|
||||
} else {
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: true,
|
||||
autoClose: false,
|
||||
withCloseButton: false,
|
||||
message: (
|
||||
<ProgressBar
|
||||
size='lg'
|
||||
maximum={data.total}
|
||||
value={data.progress}
|
||||
progressLabel={data.total > 0}
|
||||
animated
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: t`Process failed`,
|
||||
color: 'red'
|
||||
});
|
||||
return {};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
@ -71,3 +71,6 @@ export {
|
|||
RowCancelAction,
|
||||
RowActions
|
||||
} from './components/RowActions';
|
||||
|
||||
// Shared hooks
|
||||
export { default as monitorDataOutput } from './hooks/MonitorDataOutput';
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export type TableFilterType = 'boolean' | 'choice' | 'date' | 'text' | 'api';
|
|||
*/
|
||||
export type TableFilter = {
|
||||
name: string;
|
||||
label: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
type?: TableFilterType;
|
||||
choices?: TableFilterChoice[];
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ export type TableState = {
|
|||
* @param cellsStyle - The style of the cells in the column
|
||||
* @param extra - Extra data to pass to the render function
|
||||
* @param noContext - Disable context menu for this column
|
||||
* @param copyable - Enable copy button on hover (uses accessor to get value, or custom function)
|
||||
* @param copyAccessor - Custom accessor path for copy value (defaults to column accessor)
|
||||
*/
|
||||
export type TableColumnProps<T = any> = {
|
||||
accessor?: string;
|
||||
|
|
@ -123,6 +125,8 @@ export type TableColumnProps<T = any> = {
|
|||
extra?: any;
|
||||
noContext?: boolean;
|
||||
style?: MantineStyleProp;
|
||||
copyable?: boolean | ((record: T) => string);
|
||||
copyAccessor?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@inventreedb/ui",
|
||||
"description": "UI components for the InvenTree project",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.0",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
ActionIcon,
|
||||
type ActionIconVariant,
|
||||
Button,
|
||||
type DefaultMantineColor,
|
||||
type FloatingPosition,
|
||||
|
|
@ -22,7 +23,8 @@ export function CopyButton({
|
|||
tooltipPosition,
|
||||
content,
|
||||
size,
|
||||
color = 'gray'
|
||||
color = 'gray',
|
||||
variant = 'transparent'
|
||||
}: Readonly<{
|
||||
value: any;
|
||||
label?: string;
|
||||
|
|
@ -32,9 +34,15 @@ export function CopyButton({
|
|||
content?: JSX.Element;
|
||||
size?: MantineSize;
|
||||
color?: DefaultMantineColor;
|
||||
variant?: ActionIconVariant;
|
||||
}>) {
|
||||
const ButtonComponent = label ? Button : ActionIcon;
|
||||
|
||||
// Disable the copy button if we are not in a secure context, as the Clipboard API is not available
|
||||
if (!window.isSecureContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MantineCopyButton value={value}>
|
||||
{({ copied, copy }) => (
|
||||
|
|
@ -46,8 +54,12 @@ export function CopyButton({
|
|||
<ButtonComponent
|
||||
disabled={disabled}
|
||||
color={copied ? 'teal' : color}
|
||||
onClick={copy}
|
||||
variant='transparent'
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
copy();
|
||||
}}
|
||||
variant={copied ? 'transparent' : (variant ?? 'transparent')}
|
||||
size={size ?? 'sm'}
|
||||
>
|
||||
{copied ? (
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ import {
|
|||
} from '@tabler/icons-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import {
|
||||
defaultLocale,
|
||||
getPriorityLocale
|
||||
} from '../../contexts/LanguageContext';
|
||||
import type { CalendarState } from '../../hooks/UseCalendar';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { FilterSelectDrawer } from '../../tables/FilterSelectDrawer';
|
||||
|
|
@ -60,7 +64,19 @@ export default function Calendar({
|
|||
const [locale] = useLocalState(useShallow((s) => [s.language]));
|
||||
|
||||
// Ensure underscore is replaced with dash
|
||||
const calendarLocale = useMemo(() => locale.replace('_', '-'), [locale]);
|
||||
const calendarLocale = useMemo(() => {
|
||||
let _locale: string | null = locale;
|
||||
|
||||
if (!_locale) {
|
||||
_locale = getPriorityLocale();
|
||||
}
|
||||
|
||||
_locale = _locale || defaultLocale;
|
||||
|
||||
_locale = _locale.replace('_', '-');
|
||||
|
||||
return _locale;
|
||||
}, [locale]);
|
||||
|
||||
const selectMonth = useCallback(
|
||||
(date: DateValue) => {
|
||||
|
|
|
|||
|
|
@ -54,10 +54,16 @@ export type DetailsField = {
|
|||
type BadgeType = 'owner' | 'user' | 'group';
|
||||
type ValueFormatterReturn = string | number | null | React.ReactNode;
|
||||
|
||||
type StringDetailField = {
|
||||
type: 'string' | 'text' | 'date';
|
||||
unit?: boolean;
|
||||
};
|
||||
type StringDetailField =
|
||||
| {
|
||||
type: 'string' | 'text';
|
||||
unit?: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'date';
|
||||
unit?: boolean;
|
||||
showTime?: boolean;
|
||||
};
|
||||
|
||||
type NumberDetailField = {
|
||||
type: 'number';
|
||||
|
|
@ -260,7 +266,13 @@ function NameBadge({
|
|||
}
|
||||
|
||||
function DateValue(props: Readonly<FieldProps>) {
|
||||
return <Text size='sm'>{formatDate(props.field_value?.toString())}</Text>;
|
||||
return (
|
||||
<Text size='sm'>
|
||||
{formatDate(props.field_value?.toString(), {
|
||||
showTime: props.field_data?.showTime
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
// Return a formatted "number" value, with optional unit
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { Select } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { getSupportedLanguages } from '../../contexts/LanguageContext';
|
||||
import {
|
||||
activateLocale,
|
||||
getSupportedLanguages
|
||||
} from '../../contexts/LanguageContext';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
|
||||
export function LanguageSelect({ width = 80 }: Readonly<{ width?: number }>) {
|
||||
|
|
@ -28,13 +32,21 @@ export function LanguageSelect({ width = 80 }: Readonly<{ width?: number }>) {
|
|||
}));
|
||||
setLangOptions(newLangOptions);
|
||||
setValue(locale);
|
||||
activateLocale(locale); // Ensure the locale is activated on component load
|
||||
}, [locale]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
w={width}
|
||||
data={langOptions}
|
||||
data={[
|
||||
{
|
||||
value: '',
|
||||
label: t`Default Language`
|
||||
},
|
||||
...langOptions
|
||||
]}
|
||||
value={value}
|
||||
defaultValue={''}
|
||||
onChange={setValue}
|
||||
searchable
|
||||
aria-label='Select language'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type InvenTreePluginContext
|
||||
} from '@lib/types/Plugins';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { defaultLocale } from '../../contexts/LanguageContext';
|
||||
import {
|
||||
useAddStockItem,
|
||||
useAssignStockItem,
|
||||
|
|
@ -35,10 +36,12 @@ import {
|
|||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useServerApiState } from '../../states/ServerApiState';
|
||||
import { RenderInstance } from '../render/Instance';
|
||||
|
||||
export const useInvenTreeContext = () => {
|
||||
const [locale, host] = useLocalState(useShallow((s) => [s.language, s.host]));
|
||||
const [server] = useServerApiState(useShallow((s) => [s.server]));
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
|
|
@ -57,7 +60,7 @@ export const useInvenTreeContext = () => {
|
|||
user: user,
|
||||
host: host,
|
||||
i18n: i18n,
|
||||
locale: locale,
|
||||
locale: locale || server.default_locale || defaultLocale,
|
||||
api: api,
|
||||
queryClient: queryClient,
|
||||
navigate: navigate,
|
||||
|
|
|
|||
|
|
@ -65,9 +65,29 @@ export function LanguageContext({
|
|||
const [language] = useLocalState(useShallow((state) => [state.language]));
|
||||
const [server] = useServerApiState(useShallow((state) => [state.server]));
|
||||
|
||||
const [activeLocale, setActiveLocale] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
activateLocale(defaultLocale);
|
||||
}, []);
|
||||
// Update the locale based on prioritization:
|
||||
// 1. Locally selected locale
|
||||
// 2. Server default locale
|
||||
// 3. English (fallback)
|
||||
|
||||
let locale: string | null = activeLocale;
|
||||
|
||||
if (!!language) {
|
||||
locale = language;
|
||||
} else if (!!server.default_locale) {
|
||||
locale = server.default_locale;
|
||||
} else {
|
||||
locale = defaultLocale;
|
||||
}
|
||||
|
||||
if (locale != activeLocale) {
|
||||
setActiveLocale(locale);
|
||||
activateLocale(locale);
|
||||
}
|
||||
}, [activeLocale, language, server.default_locale, defaultLocale]);
|
||||
|
||||
const [loadedState, setLoadedState] = useState<
|
||||
'loading' | 'loaded' | 'error'
|
||||
|
|
@ -77,7 +97,7 @@ export function LanguageContext({
|
|||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
let lang = language;
|
||||
let lang: string = language || defaultLocale;
|
||||
|
||||
// Ensure that the selected language is supported
|
||||
if (!Object.keys(getSupportedLanguages()).includes(lang)) {
|
||||
|
|
@ -96,7 +116,7 @@ export function LanguageContext({
|
|||
*/
|
||||
const locales: (string | undefined)[] = [];
|
||||
|
||||
if (lang != 'pseudo-LOCALE') {
|
||||
if (!!lang && lang != 'pseudo-LOCALE') {
|
||||
locales.push(lang);
|
||||
}
|
||||
|
||||
|
|
@ -156,8 +176,26 @@ export function LanguageContext({
|
|||
return <I18nProvider i18n={i18n}>{children}</I18nProvider>;
|
||||
}
|
||||
|
||||
export async function activateLocale(locale: string) {
|
||||
const { messages } = await import(`../locales/${locale}/messages.ts`);
|
||||
i18n.load(locale, messages);
|
||||
i18n.activate(locale);
|
||||
// This function is used to determine the locale to activate based on the prioritization rules.
|
||||
export function getPriorityLocale(): string {
|
||||
const serverDefault = useServerApiState.getState().server.default_locale;
|
||||
const userDefault = useLocalState.getState().language;
|
||||
|
||||
return userDefault || serverDefault || defaultLocale;
|
||||
}
|
||||
|
||||
export async function activateLocale(locale: string | null) {
|
||||
if (!locale) {
|
||||
locale = getPriorityLocale();
|
||||
}
|
||||
|
||||
const localeDir = locale.split('-')[0]; // Extract the base locale (e.g., 'en' from 'en-US')
|
||||
|
||||
try {
|
||||
const { messages } = await import(`../locales/${localeDir}/messages.ts`);
|
||||
i18n.load(locale, messages);
|
||||
i18n.activate(locale);
|
||||
} catch (err) {
|
||||
console.error(`Failed to load locale ${locale}:`, err);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react';
|
||||
import { MantineProvider, createTheme } from '@mantine/core';
|
||||
import {
|
||||
MantineProvider,
|
||||
type MantineThemeOverride,
|
||||
createTheme
|
||||
} from '@mantine/core';
|
||||
import { ModalsProvider } from '@mantine/modals';
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
import { ContextMenuProvider } from 'mantine-contextmenu';
|
||||
|
|
@ -20,23 +24,31 @@ export function ThemeContext({
|
|||
}: Readonly<{ children: JSX.Element }>) {
|
||||
const [userTheme] = useLocalState(useShallow((state) => [state.userTheme]));
|
||||
|
||||
let customUserTheme: MantineThemeOverride | undefined = undefined;
|
||||
|
||||
// Theme
|
||||
const myTheme = createTheme({
|
||||
primaryColor: userTheme.primaryColor,
|
||||
white: userTheme.whiteColor,
|
||||
black: userTheme.blackColor,
|
||||
defaultRadius: userTheme.radius,
|
||||
breakpoints: {
|
||||
xs: '30em',
|
||||
sm: '48em',
|
||||
md: '64em',
|
||||
lg: '74em',
|
||||
xl: '90em'
|
||||
}
|
||||
});
|
||||
try {
|
||||
customUserTheme = createTheme({
|
||||
primaryColor: userTheme.primaryColor,
|
||||
white: userTheme.whiteColor,
|
||||
black: userTheme.blackColor,
|
||||
defaultRadius: userTheme.radius,
|
||||
breakpoints: {
|
||||
xs: '30em',
|
||||
sm: '48em',
|
||||
md: '64em',
|
||||
lg: '74em',
|
||||
xl: '90em'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating theme with user settings:', error);
|
||||
// Fallback to default theme if there's an error
|
||||
customUserTheme = undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<MantineProvider theme={myTheme} colorSchemeManager={colorSchema}>
|
||||
<MantineProvider theme={customUserTheme} colorSchemeManager={colorSchema}>
|
||||
<ContextMenuProvider>
|
||||
<LanguageContext>
|
||||
<ModalsProvider
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export function useSupplierPartFields({
|
|||
},
|
||||
manufacturer_part: {
|
||||
value: manufacturerPartId,
|
||||
autoFill: true,
|
||||
filters: {
|
||||
manufacturer: manufacturerId,
|
||||
part_detail: true,
|
||||
|
|
|
|||
|
|
@ -113,16 +113,6 @@ export function usePurchaseOrderLineItemFields({
|
|||
}
|
||||
}, [create, part, quantity, priceBreaks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoPricing) {
|
||||
setPurchasePrice('');
|
||||
}
|
||||
}, [autoPricing]);
|
||||
|
||||
useEffect(() => {
|
||||
setAutoPricing(purchasePrice === '');
|
||||
}, [purchasePrice]);
|
||||
|
||||
const fields = useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
order: {
|
||||
|
|
@ -159,6 +149,7 @@ export function usePurchaseOrderLineItemFields({
|
|||
purchase_price: {
|
||||
icon: <IconCurrencyDollar />,
|
||||
value: purchasePrice,
|
||||
disabled: autoPricing,
|
||||
placeholder: suggestedPurchasePrice,
|
||||
placeholderAutofill: true,
|
||||
onValueChange: setPurchasePrice
|
||||
|
|
@ -169,6 +160,7 @@ export function usePurchaseOrderLineItemFields({
|
|||
onValueChange: setPurchasePriceCurrency
|
||||
},
|
||||
auto_pricing: {
|
||||
default: create !== false,
|
||||
value: autoPricing,
|
||||
onValueChange: setAutoPricing
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,6 @@
|
|||
import { ProgressBar } from '@lib/components/ProgressBar';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useDocumentVisibility } from '@mantine/hooks';
|
||||
import { notifications, showNotification } from '@mantine/notifications';
|
||||
import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import monitorDataOutput from '@lib/hooks/MonitorDataOutput';
|
||||
import { useApi } from '../contexts/ApiContext';
|
||||
import { generateUrl } from '../functions/urls';
|
||||
import { useLocalState } from '../states/LocalState';
|
||||
|
||||
/**
|
||||
* Hook for monitoring a data output process running on the server
|
||||
|
|
@ -21,97 +13,12 @@ export default function useDataOutput({
|
|||
id?: number;
|
||||
}) {
|
||||
const api = useApi();
|
||||
const { getHost } = useLocalState.getState();
|
||||
|
||||
const visibility = useDocumentVisibility();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!id) {
|
||||
setLoading(true);
|
||||
showNotification({
|
||||
id: `data-output-${id}`,
|
||||
title: title,
|
||||
loading: true,
|
||||
autoClose: false,
|
||||
withCloseButton: false,
|
||||
message: <ProgressBar size='lg' value={0} progressLabel />
|
||||
});
|
||||
} else setLoading(false);
|
||||
}, [id, title]);
|
||||
|
||||
useQuery({
|
||||
enabled: !!id && loading && visibility === 'visible',
|
||||
refetchInterval: 500,
|
||||
queryKey: ['data-output', id, title],
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(apiUrl(ApiEndpoints.data_output, id))
|
||||
.then((response) => {
|
||||
const data = response?.data ?? {};
|
||||
|
||||
if (!!data.errors || !!data.error) {
|
||||
setLoading(false);
|
||||
|
||||
const error: string =
|
||||
data?.error ?? data?.errors?.error ?? t`Process failed`;
|
||||
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
icon: <IconExclamationCircle />,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: error,
|
||||
color: 'red'
|
||||
});
|
||||
} else if (data.complete) {
|
||||
setLoading(false);
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: t`Process completed successfully`,
|
||||
color: 'green',
|
||||
icon: <IconCircleCheck />
|
||||
});
|
||||
|
||||
if (data.output) {
|
||||
const url = generateUrl(data.output);
|
||||
window.open(url.toString(), '_blank');
|
||||
}
|
||||
} else {
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: true,
|
||||
autoClose: false,
|
||||
withCloseButton: false,
|
||||
message: (
|
||||
<ProgressBar
|
||||
size='lg'
|
||||
maximum={data.total}
|
||||
value={data.progress}
|
||||
progressLabel={data.total > 0}
|
||||
animated
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: t`Process failed`,
|
||||
color: 'red'
|
||||
});
|
||||
return {};
|
||||
})
|
||||
return monitorDataOutput({
|
||||
api: api,
|
||||
title: title,
|
||||
id: id,
|
||||
hostname: getHost()
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,43 @@
|
|||
import type { FilterSetState, TableFilter } from '@lib/types/Filters';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
export function useFilterSet(filterKey: string): FilterSetState {
|
||||
export function useFilterSet(
|
||||
filterKey: string,
|
||||
initialFilters?: TableFilter[]
|
||||
): FilterSetState {
|
||||
// Array of active filters (saved to local storage)
|
||||
const [activeFilters, setActiveFilters] = useLocalStorage<TableFilter[]>({
|
||||
const [storedFilters, setStoredFilters] = useLocalStorage<
|
||||
TableFilter[] | null
|
||||
>({
|
||||
key: `inventree-filterset-${filterKey}`,
|
||||
defaultValue: [],
|
||||
defaultValue: null,
|
||||
sync: false,
|
||||
getInitialValueInEffect: false
|
||||
});
|
||||
|
||||
const activeFilters: TableFilter[] = useMemo(() => {
|
||||
if (storedFilters == null) {
|
||||
// If there are no stored filters, set initial values
|
||||
const filters = initialFilters || [];
|
||||
setStoredFilters(filters);
|
||||
return filters;
|
||||
}
|
||||
return storedFilters || [];
|
||||
}, [storedFilters]);
|
||||
|
||||
// Callback to clear all active filters from the table
|
||||
const clearActiveFilters = useCallback(() => {
|
||||
setActiveFilters([]);
|
||||
setStoredFilters([]);
|
||||
}, []);
|
||||
|
||||
const setActiveFilters = useCallback(
|
||||
(filters: TableFilter[]) => {
|
||||
setStoredFilters(filters);
|
||||
},
|
||||
[setStoredFilters]
|
||||
);
|
||||
|
||||
return {
|
||||
filterKey,
|
||||
activeFilters,
|
||||
|
|
|
|||
|
|
@ -2,17 +2,28 @@ import { randomId } from '@mantine/hooks';
|
|||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import type { FilterSetState } from '@lib/types/Filters';
|
||||
import type { FilterSetState, TableFilter } from '@lib/types/Filters';
|
||||
import type { TableState } from '@lib/types/Tables';
|
||||
import { useFilterSet } from './UseFilterSet';
|
||||
|
||||
export type TableStateExtraProps = {
|
||||
idAccessor?: string;
|
||||
initialFilters?: TableFilter[];
|
||||
};
|
||||
|
||||
/**
|
||||
* A custom hook for managing the state of an <InvenTreeTable> component.
|
||||
*
|
||||
* Refer to the TableState type definition for more information.
|
||||
*/
|
||||
|
||||
export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
||||
export function useTable(
|
||||
tableName: string,
|
||||
tableProps: TableStateExtraProps = {
|
||||
idAccessor: 'pk',
|
||||
initialFilters: []
|
||||
}
|
||||
): TableState {
|
||||
// Function to generate a new ID (to refresh the table)
|
||||
function generateTableName() {
|
||||
return `${tableName.replaceAll('-', '')}-${randomId()}`;
|
||||
|
|
@ -38,7 +49,10 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
|||
[generateTableName]
|
||||
);
|
||||
|
||||
const filterSet: FilterSetState = useFilterSet(`table-${tableName}`);
|
||||
const filterSet: FilterSetState = useFilterSet(
|
||||
`table-${tableName}`,
|
||||
tableProps.initialFilters
|
||||
);
|
||||
|
||||
// Array of expanded records
|
||||
const [expandedRecords, setExpandedRecords] = useState<any[]>([]);
|
||||
|
|
@ -59,7 +73,7 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
|||
|
||||
// Array of selected primary key values
|
||||
const selectedIds = useMemo(
|
||||
() => selectedRecords.map((r) => r[idAccessor || 'pk']),
|
||||
() => selectedRecords.map((r) => r[tableProps.idAccessor || 'pk']),
|
||||
[selectedRecords]
|
||||
);
|
||||
|
||||
|
|
@ -89,7 +103,7 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
|||
|
||||
// Find the matching record in the table
|
||||
const index = _records.findIndex(
|
||||
(r) => r[idAccessor || 'pk'] === record.pk
|
||||
(r) => r[tableProps.idAccessor || 'pk'] === record.pk
|
||||
);
|
||||
|
||||
if (index >= 0) {
|
||||
|
|
@ -106,6 +120,11 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
|||
[records]
|
||||
);
|
||||
|
||||
const idAccessor = useMemo(
|
||||
() => tableProps.idAccessor || 'pk',
|
||||
[tableProps.idAccessor]
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { InvenTreeTable } from '../../../../tables/InvenTreeTable';
|
|||
export function CurrencyTable({
|
||||
setInfo
|
||||
}: Readonly<{ setInfo: (info: any) => void }>) {
|
||||
const table = useTable('currency', 'currency');
|
||||
const table = useTable('currency', { idAccessor: 'currency' });
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { InvenTreeTable } from '../../../../tables/InvenTreeTable';
|
|||
import CustomUnitsTable from '../../../../tables/settings/CustomUnitsTable';
|
||||
|
||||
function AllUnitTable() {
|
||||
const table = useTable('all-units', 'name');
|
||||
const table = useTable('all-units', { idAccessor: 'name' });
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ export default function SystemSettings() {
|
|||
'PART_SALABLE',
|
||||
'PART_VIRTUAL',
|
||||
'PART_COPY_BOM',
|
||||
'PART_BOM_ALLOW_ZERO_QUANTITY',
|
||||
'PART_COPY_PARAMETERS',
|
||||
'PART_COPY_TESTS',
|
||||
'PART_CATEGORY_PARAMETERS',
|
||||
|
|
|
|||
|
|
@ -469,6 +469,7 @@ export default function BuildDetail() {
|
|||
tableName='build-consumed'
|
||||
showLocation={false}
|
||||
allowReturn
|
||||
defaultInStock={null}
|
||||
params={{
|
||||
consumed_by: id
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
|||
tableName='assigned-stock'
|
||||
showLocation={false}
|
||||
allowReturn
|
||||
defaultInStock={null}
|
||||
params={{ customer: company.pk }}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -304,6 +304,15 @@ export default function PurchaseOrderDetail() {
|
|||
label: t`Completion Date`,
|
||||
copy: true,
|
||||
hidden: !order.complete_date
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'updated_at',
|
||||
label: t`Last Updated`,
|
||||
icon: 'calendar',
|
||||
copy: true,
|
||||
showTime: true,
|
||||
hidden: !order.updated_at
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export default function PurchasingIndex() {
|
|||
icon: <IconTable />,
|
||||
content: (
|
||||
<CompanyTable
|
||||
companyType='supplier'
|
||||
path='purchasing/supplier'
|
||||
params={{ is_supplier: true }}
|
||||
/>
|
||||
|
|
@ -157,6 +158,7 @@ export default function PurchasingIndex() {
|
|||
icon: <IconTable />,
|
||||
content: (
|
||||
<CompanyTable
|
||||
companyType='manufacturer'
|
||||
path='purchasing/manufacturer'
|
||||
params={{ is_manufacturer: true }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -282,6 +282,15 @@ export default function ReturnOrderDetail() {
|
|||
label: t`Completion Date`,
|
||||
copy: true,
|
||||
hidden: !order.complete_date
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'updated_at',
|
||||
label: t`Last Updated`,
|
||||
icon: 'calendar',
|
||||
copy: true,
|
||||
showTime: true,
|
||||
hidden: !order.updated_at
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ export default function SalesIndex() {
|
|||
icon: <IconTable />,
|
||||
content: (
|
||||
<CompanyTable
|
||||
companyType='customer'
|
||||
path='sales/customer'
|
||||
params={{ is_customer: true }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -273,6 +273,15 @@ export default function SalesOrderDetail() {
|
|||
label: t`Completion Date`,
|
||||
hidden: !order.shipment_date,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'updated_at',
|
||||
label: t`Last Updated`,
|
||||
icon: 'calendar',
|
||||
copy: true,
|
||||
showTime: true,
|
||||
hidden: !order.updated_at
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ interface LocalStateProps {
|
|||
hostKey: string;
|
||||
hostList: HostList;
|
||||
setHostList: (newHostList: HostList) => void;
|
||||
language: string;
|
||||
setLanguage: (newLanguage: string, noPatch?: boolean) => void;
|
||||
language: string | null;
|
||||
setLanguage: (newLanguage: string | null, noPatch?: boolean) => void;
|
||||
userTheme: UserTheme;
|
||||
setTheme: (
|
||||
newValues: {
|
||||
|
|
@ -78,7 +78,7 @@ export const useLocalState = create<LocalStateProps>()(
|
|||
hostKey: '',
|
||||
hostList: {},
|
||||
setHostList: (newHostList) => set({ hostList: newHostList }),
|
||||
language: 'en',
|
||||
language: null,
|
||||
setLanguage: (newLanguage, noPatch = false) => {
|
||||
set({ language: newLanguage });
|
||||
if (!noPatch) patchUser('language', newLanguage);
|
||||
|
|
|
|||
|
|
@ -107,6 +107,18 @@ export function PartColumn(props: PartColumnProps): TableColumn {
|
|||
};
|
||||
}
|
||||
|
||||
export function IPNColumn(props: TableColumnProps): TableColumn {
|
||||
return {
|
||||
accessor: 'part_detail.IPN',
|
||||
sortable: true,
|
||||
ordering: 'IPN',
|
||||
switchable: true,
|
||||
title: t`IPN`,
|
||||
copyable: true,
|
||||
...props
|
||||
};
|
||||
}
|
||||
|
||||
export type StockColumnProps = TableColumnProps & {
|
||||
nullMessage?: string | ReactNode;
|
||||
};
|
||||
|
|
@ -448,6 +460,7 @@ export function DescriptionColumn(props: TableColumnProps): TableColumn {
|
|||
sortable: false,
|
||||
switchable: true,
|
||||
minWidth: '200px',
|
||||
copyable: true,
|
||||
...props
|
||||
};
|
||||
}
|
||||
|
|
@ -457,6 +470,8 @@ export function LinkColumn(props: TableColumnProps): TableColumn {
|
|||
accessor: 'link',
|
||||
sortable: false,
|
||||
defaultVisible: false,
|
||||
copyable: true,
|
||||
copyAccessor: props.accessor ?? 'link',
|
||||
render: (record: any) => {
|
||||
const url = resolveItem(record, props.accessor ?? 'link');
|
||||
|
||||
|
|
@ -490,6 +505,7 @@ export function ReferenceColumn(props: TableColumnProps): TableColumn {
|
|||
title: t`Reference`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
copyable: true,
|
||||
...props
|
||||
};
|
||||
}
|
||||
|
|
@ -664,6 +680,7 @@ export function DateColumn(props: TableColumnProps): TableColumn {
|
|||
formatDate(resolveItem(record, props.accessor ?? 'date'), {
|
||||
showTime: props.extra?.showTime
|
||||
}),
|
||||
copyable: true,
|
||||
...props
|
||||
};
|
||||
}
|
||||
|
|
@ -708,6 +725,16 @@ export function ShipmentDateColumn(props: TableColumnProps): TableColumn {
|
|||
});
|
||||
}
|
||||
|
||||
export function UpdatedAtColumn(props: TableColumnProps): TableColumn {
|
||||
return DateColumn({
|
||||
accessor: 'updated_at',
|
||||
title: t`Updated`,
|
||||
defaultVisible: false,
|
||||
extra: { showTime: true },
|
||||
...props
|
||||
});
|
||||
}
|
||||
|
||||
export function CurrencyColumn({
|
||||
accessor,
|
||||
title,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import { Group } from '@mantine/core';
|
||||
import { useState } from 'react';
|
||||
import { CopyButton } from '../components/buttons/CopyButton';
|
||||
|
||||
/**
|
||||
* A wrapper component that adds a copy button to cell content on hover
|
||||
* This component is used to make table cells copyable without adding visual clutter
|
||||
*
|
||||
* @param children - The cell content to render
|
||||
* @param value - The value to copy when the copy button is clicked
|
||||
*/
|
||||
export function CopyableCell({
|
||||
children,
|
||||
value
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
value: string;
|
||||
}>) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<Group
|
||||
gap={0}
|
||||
p={0}
|
||||
wrap='nowrap'
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
justify='space-between'
|
||||
align='center'
|
||||
>
|
||||
{children}
|
||||
{window.isSecureContext && isHovered && value != null && (
|
||||
<span
|
||||
style={{ position: 'relative' }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
transform: 'translateY(-50%)'
|
||||
}}
|
||||
>
|
||||
<CopyButton value={value} variant={'default'} />
|
||||
</div>
|
||||
</span>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { t } from '@lingui/core/macro';
|
|||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { isTrue } from '@lib/functions/Conversion';
|
||||
import type { TableFilter, TableFilterChoice } from '@lib/types/Filters';
|
||||
import type {
|
||||
StatusCodeInterface,
|
||||
|
|
@ -14,6 +15,47 @@ import {
|
|||
} from '../states/GlobalStatusState';
|
||||
import { useGlobalSettingsState } from '../states/SettingsStates';
|
||||
|
||||
// Determine the appropriate display label for a given filter, based on its name and the list of available filters
|
||||
export function filterDisplayLabel(
|
||||
name: string,
|
||||
filters?: TableFilter[]
|
||||
): string {
|
||||
const filter = filters?.find((f) => f.name === name);
|
||||
return filter?.label ?? name;
|
||||
}
|
||||
|
||||
// Determine the appropriate display value for a filter, based on its type and value
|
||||
// This is useful for recreating a display value if we only have a name:value pair
|
||||
export function filterDisplayValue(
|
||||
name: string,
|
||||
value: any,
|
||||
filters?: TableFilter[]
|
||||
) {
|
||||
const filterDef = filters?.find((f) => f.name === name);
|
||||
|
||||
if (!filterDef) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!filterDef.type || filterDef.type == 'boolean') {
|
||||
return isTrue(value) ? t`Yes` : t`No`;
|
||||
}
|
||||
|
||||
if (filterDef.type === 'choice' && filterDef.choices) {
|
||||
const choice = filterDef.choices.find((c) => c.value === value);
|
||||
return choice ? choice.label : value;
|
||||
}
|
||||
|
||||
if (filterDef.type === 'choice' && filterDef.choiceFunction) {
|
||||
const choices = filterDef.choiceFunction();
|
||||
const choice = choices.find((c) => c.value === value);
|
||||
return choice ? choice.label : value;
|
||||
}
|
||||
|
||||
// No obvious match - return the raw value
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of available filter options for a given filter
|
||||
* @param filter - TableFilter object
|
||||
|
|
@ -244,6 +286,24 @@ export function CompletedAfterFilter(): TableFilter {
|
|||
};
|
||||
}
|
||||
|
||||
export function UpdatedAfterFilter(): TableFilter {
|
||||
return {
|
||||
name: 'updated_after',
|
||||
label: t`Updated After`,
|
||||
description: t`Show orders updated after this date`,
|
||||
type: 'date'
|
||||
};
|
||||
}
|
||||
|
||||
export function UpdatedBeforeFilter(): TableFilter {
|
||||
return {
|
||||
name: 'updated_before',
|
||||
label: t`Updated Before`,
|
||||
description: t`Show orders updated before this date`,
|
||||
type: 'date'
|
||||
};
|
||||
}
|
||||
|
||||
export function HasProjectCodeFilter(): TableFilter {
|
||||
const globalSettings = useGlobalSettingsState.getState();
|
||||
const enabled = globalSettings.isSet('PROJECT_CODES_ENABLED', true);
|
||||
|
|
|
|||
|
|
@ -28,17 +28,46 @@ import type {
|
|||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||
import { StylishText } from '../components/items/StylishText';
|
||||
import { getTableFilterOptions } from './Filter';
|
||||
import {
|
||||
filterDisplayLabel,
|
||||
filterDisplayValue,
|
||||
getTableFilterOptions
|
||||
} from './Filter';
|
||||
|
||||
/*
|
||||
* Render a preview of a single filter
|
||||
*/
|
||||
export function FilterPreview({
|
||||
filter,
|
||||
filters
|
||||
}: Readonly<{
|
||||
filter: TableFilter;
|
||||
filters?: TableFilter[];
|
||||
}>) {
|
||||
return (
|
||||
<Group key={filter.name} justify='space-between' gap='xl' wrap='nowrap'>
|
||||
<Text size='sm'>
|
||||
{filter.label ?? filterDisplayLabel(filter.name, filters)}
|
||||
</Text>
|
||||
<Text size='xs'>
|
||||
{filter.displayValue ??
|
||||
filterDisplayValue(filter.name, filter.value, filters)}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Render a single table filter item
|
||||
*/
|
||||
function FilterItem({
|
||||
flt,
|
||||
filterSet
|
||||
filterSet,
|
||||
availableFilters
|
||||
}: Readonly<{
|
||||
flt: TableFilter;
|
||||
filterSet: FilterSetState;
|
||||
availableFilters?: TableFilter[];
|
||||
}>) {
|
||||
const removeFilter = useCallback(() => {
|
||||
const newFilters = filterSet.activeFilters.filter(
|
||||
|
|
@ -47,17 +76,29 @@ function FilterItem({
|
|||
filterSet.setActiveFilters(newFilters);
|
||||
}, [flt]);
|
||||
|
||||
// Find the matching filter definition
|
||||
const filterProps: TableFilter | undefined = useMemo(() => {
|
||||
return availableFilters?.find((f) => f.name === flt.name);
|
||||
}, [availableFilters, flt]);
|
||||
|
||||
return (
|
||||
<Paper p='sm' shadow='sm' radius='xs'>
|
||||
<Group justify='space-between' key={flt.name} wrap='nowrap'>
|
||||
<Stack gap='xs'>
|
||||
<Text size='sm'>{flt.label}</Text>
|
||||
<Text size='xs'>{flt.description}</Text>
|
||||
<Text size='sm'>{flt.label ?? filterProps?.label ?? flt.name}</Text>
|
||||
<Text size='xs'>{flt.description ?? filterProps?.description}</Text>
|
||||
</Stack>
|
||||
<Group justify='right'>
|
||||
<Badge>{flt.displayValue ?? flt.value}</Badge>
|
||||
<Tooltip label={t`Remove filter`} withinPortal={true}>
|
||||
<CloseButton size='md' color='red' onClick={removeFilter} />
|
||||
<Badge>
|
||||
{flt.displayValue ??
|
||||
filterDisplayValue(flt.name, flt.value, availableFilters)}
|
||||
</Badge>
|
||||
<Tooltip
|
||||
label={t`Remove filter`}
|
||||
withinPortal={true}
|
||||
position='top-end'
|
||||
>
|
||||
<CloseButton size='md' c='red' onClick={removeFilter} />
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Group>
|
||||
|
|
@ -174,10 +215,10 @@ function FilterAddGroup({
|
|||
return (
|
||||
availableFilters
|
||||
?.filter((flt) => !activeFilterNames.includes(flt.name))
|
||||
?.sort((a, b) => a.label.localeCompare(b.label))
|
||||
?.sort((a, b) => (a.label ?? a.name).localeCompare(b.label ?? b.name))
|
||||
?.map((flt) => ({
|
||||
value: flt.name,
|
||||
label: flt.label,
|
||||
label: flt.label ?? flt.name,
|
||||
description: flt.description
|
||||
})) ?? []
|
||||
);
|
||||
|
|
@ -317,7 +358,12 @@ export function FilterSelectDrawer({
|
|||
<Stack gap='xs'>
|
||||
{hasFilters &&
|
||||
filterSet.activeFilters?.map((f) => (
|
||||
<FilterItem key={f.name} flt={f} filterSet={filterSet} />
|
||||
<FilterItem
|
||||
key={f.name}
|
||||
flt={f}
|
||||
filterSet={filterSet}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
))}
|
||||
{addFilter && (
|
||||
<Stack gap='xs'>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { hashString } from '../functions/tables';
|
|||
import { useLocalState } from '../states/LocalState';
|
||||
import { useUserSettingsState } from '../states/SettingsStates';
|
||||
import { useStoredTableState } from '../states/StoredTableState';
|
||||
import { CopyableCell } from './CopyableCell';
|
||||
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
||||
|
||||
const ACTIONS_COLUMN_ACCESSOR: string = '--actions--';
|
||||
|
|
@ -264,11 +265,40 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||
? false
|
||||
: (tableState.hiddenColumns?.includes(col.accessor) ?? false);
|
||||
|
||||
// Wrap the render function with CopyableCell if copyable is enabled
|
||||
const originalRender = col.render;
|
||||
let wrappedRender = originalRender;
|
||||
|
||||
if (col.copyable) {
|
||||
wrappedRender = (record: any, index?: number) => {
|
||||
const content =
|
||||
originalRender?.(record, index) ??
|
||||
resolveItem(record, col.accessor);
|
||||
|
||||
// Determine the value to copy, ensuring it is always a string
|
||||
let rawCopyValue: unknown;
|
||||
if (typeof col.copyable === 'function') {
|
||||
rawCopyValue = col.copyable(record);
|
||||
} else {
|
||||
const accessor = col.copyAccessor ?? col.accessor;
|
||||
rawCopyValue = resolveItem(record, accessor);
|
||||
}
|
||||
const copyValue = rawCopyValue == null ? '' : String(rawCopyValue);
|
||||
|
||||
if (window.isSecureContext && !!copyValue) {
|
||||
return <CopyableCell value={copyValue}>{content}</CopyableCell>;
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...col,
|
||||
hidden: hidden,
|
||||
resizable: col.resizable ?? true,
|
||||
title: col.title ?? fieldNames[col.accessor] ?? `${col.accessor}`,
|
||||
render: wrappedRender,
|
||||
cellsStyle: (record: any, index: number) => {
|
||||
const width = (col as any).minWidth ?? 100;
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
Paper,
|
||||
Space,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
|
|
@ -37,7 +36,7 @@ import { StylishText } from '../components/items/StylishText';
|
|||
import useDataExport from '../hooks/UseDataExport';
|
||||
import { useDeleteApiFormModal } from '../hooks/UseForm';
|
||||
import { TableColumnSelect } from './ColumnSelect';
|
||||
import { FilterSelectDrawer } from './FilterSelectDrawer';
|
||||
import { FilterPreview, FilterSelectDrawer } from './FilterSelectDrawer';
|
||||
|
||||
/**
|
||||
* Render a composite header for an InvenTree table
|
||||
|
|
@ -272,15 +271,10 @@ export default function InvenTreeTableHeader({
|
|||
<StylishText size='md'>{t`Active Filters`}</StylishText>
|
||||
<Divider />
|
||||
{tableState.filterSet.activeFilters?.map((filter) => (
|
||||
<Group
|
||||
key={filter.name}
|
||||
justify='space-between'
|
||||
gap='xl'
|
||||
wrap='nowrap'
|
||||
>
|
||||
<Text size='sm'>{filter.label}</Text>
|
||||
<Text size='xs'>{filter.displayValue}</Text>
|
||||
</Group>
|
||||
<FilterPreview
|
||||
filter={filter}
|
||||
filters={tableProps.tableFilters}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import {
|
|||
BooleanColumn,
|
||||
CategoryColumn,
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
NoteColumn,
|
||||
ReferenceColumn
|
||||
} from '../ColumnRenderers';
|
||||
|
|
@ -129,12 +130,9 @@ export function BomTable({
|
|||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'sub_part_detail.IPN',
|
||||
title: t`IPN`,
|
||||
sortable: true,
|
||||
ordering: 'IPN'
|
||||
},
|
||||
IPNColumn({
|
||||
accessor: 'sub_part_detail.IPN'
|
||||
}),
|
||||
CategoryColumn({
|
||||
accessor: 'category_detail',
|
||||
defaultVisible: false,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { useTable } from '../../hooks/UseTable';
|
|||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
PartColumn,
|
||||
ReferenceColumn
|
||||
} from '../ColumnRenderers';
|
||||
|
|
@ -40,11 +41,9 @@ export function UsedInTable({
|
|||
title: t`Assembly`,
|
||||
part: 'part_detail'
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
sortable: false,
|
||||
title: t`IPN`
|
||||
},
|
||||
IPNColumn({
|
||||
sortable: false
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.revision',
|
||||
title: t`Revision`,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { useTable } from '../../hooks/UseTable';
|
|||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
DecimalColumn,
|
||||
IPNColumn,
|
||||
LocationColumn,
|
||||
PartColumn,
|
||||
ReferenceColumn,
|
||||
|
|
@ -99,14 +100,9 @@ export default function BuildAllocatedStockTable({
|
|||
hidden: !showPartInfo,
|
||||
switchable: false
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
ordering: 'IPN',
|
||||
hidden: !showPartInfo,
|
||||
title: t`IPN`,
|
||||
sortable: true,
|
||||
switchable: true
|
||||
},
|
||||
IPNColumn({
|
||||
hidden: !showPartInfo
|
||||
}),
|
||||
{
|
||||
hidden: !showPartInfo,
|
||||
accessor: 'bom_reference',
|
||||
|
|
@ -119,7 +115,9 @@ export default function BuildAllocatedStockTable({
|
|||
title: t`Batch Code`,
|
||||
sortable: false,
|
||||
switchable: true,
|
||||
render: (record: any) => record?.stock_item_detail?.batch
|
||||
render: (record: any) => record?.stock_item_detail?.batch,
|
||||
copyable: true,
|
||||
copyAccessor: 'stock_item_detail.batch'
|
||||
},
|
||||
DecimalColumn({
|
||||
accessor: 'stock_item_detail.quantity',
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import {
|
|||
CategoryColumn,
|
||||
DecimalColumn,
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
LocationColumn,
|
||||
PartColumn,
|
||||
RenderPartColumn
|
||||
|
|
@ -91,7 +92,9 @@ export function BuildLineSubTable({
|
|||
},
|
||||
{
|
||||
accessor: 'stock_item_detail.batch',
|
||||
title: t`Batch`
|
||||
title: t`Batch`,
|
||||
copyable: true,
|
||||
copyAccessor: 'stock_item_detail.batch'
|
||||
},
|
||||
LocationColumn({
|
||||
accessor: 'location_detail'
|
||||
|
|
@ -332,12 +335,7 @@ export default function BuildLineTable({
|
|||
);
|
||||
}
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
sortable: true,
|
||||
ordering: 'IPN',
|
||||
title: t`IPN`
|
||||
},
|
||||
IPNColumn({}),
|
||||
CategoryColumn({
|
||||
accessor: 'category_detail',
|
||||
defaultVisible: false,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import {
|
|||
CreationDateColumn,
|
||||
DateColumn,
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
LinkColumn,
|
||||
PartColumn,
|
||||
ProjectCodeColumn,
|
||||
ReferenceColumn,
|
||||
|
|
@ -64,7 +66,14 @@ export function BuildOrderTable({
|
|||
salesOrderId?: number;
|
||||
}>) {
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
const table = useTable(!!partId ? 'buildorder-part' : 'buildorder-index');
|
||||
const table = useTable(!!partId ? 'buildorder-part' : 'buildorder-index', {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'outstanding',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
return [
|
||||
|
|
@ -72,13 +81,7 @@ export function BuildOrderTable({
|
|||
PartColumn({
|
||||
switchable: false
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
sortable: true,
|
||||
ordering: 'IPN',
|
||||
switchable: true,
|
||||
title: t`IPN`
|
||||
},
|
||||
IPNColumn({}),
|
||||
{
|
||||
accessor: 'part_detail.revision',
|
||||
title: t`Revision`,
|
||||
|
|
@ -143,7 +146,8 @@ export function BuildOrderTable({
|
|||
ordering: 'issued_by',
|
||||
title: t`Issued By`
|
||||
}),
|
||||
ResponsibleColumn({})
|
||||
ResponsibleColumn({}),
|
||||
LinkColumn({})
|
||||
];
|
||||
}, [parentBuildId, globalSettings]);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,22 @@ import { InvenTreeTable } from '../InvenTreeTable';
|
|||
* based on the provided filter parameters
|
||||
*/
|
||||
export function CompanyTable({
|
||||
companyType,
|
||||
params,
|
||||
path
|
||||
}: Readonly<{
|
||||
companyType?: string;
|
||||
params?: any;
|
||||
path?: string;
|
||||
}>) {
|
||||
const table = useTable('company');
|
||||
const table = useTable(`company-${companyType ?? 'index'}`, {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'active',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default function BarcodeScanTable({
|
|||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
||||
const table = useTable('barcode-scan-results', 'id');
|
||||
const table = useTable('barcode-scan-results', { idAccessor: 'id' });
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useTable } from '../../hooks/UseTable';
|
|||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
PartColumn,
|
||||
ProjectCodeColumn,
|
||||
StatusColumn
|
||||
|
|
@ -56,10 +57,7 @@ export default function PartSalesAllocationsTable({
|
|||
PartColumn({
|
||||
part: 'part_detail'
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
title: t`IPN`
|
||||
},
|
||||
IPNColumn({}),
|
||||
ProjectCodeColumn({
|
||||
accessor: 'order_detail.project_code_detail'
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import {
|
|||
CategoryColumn,
|
||||
DefaultLocationColumn,
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
LinkColumn,
|
||||
PartColumn
|
||||
} from '../ColumnRenderers';
|
||||
|
|
@ -56,17 +57,17 @@ function partTableColumns(): TableColumn[] {
|
|||
part: '',
|
||||
accessor: 'name'
|
||||
}),
|
||||
{
|
||||
accessor: 'IPN',
|
||||
sortable: true
|
||||
},
|
||||
IPNColumn({
|
||||
accessor: 'IPN'
|
||||
}),
|
||||
{
|
||||
accessor: 'revision',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'units',
|
||||
sortable: true
|
||||
sortable: true,
|
||||
copyable: true
|
||||
},
|
||||
DescriptionColumn({}),
|
||||
CategoryColumn({
|
||||
|
|
@ -338,17 +339,26 @@ export function PartListTable({
|
|||
enableImport = true,
|
||||
basePartInstance,
|
||||
props,
|
||||
tableName = 'part-list',
|
||||
defaultPartData
|
||||
}: Readonly<{
|
||||
enableImport?: boolean;
|
||||
props?: InvenTreeTableProps;
|
||||
basePartInstance?: any;
|
||||
tableName?: string;
|
||||
defaultPartData?: any;
|
||||
}>) {
|
||||
const tableColumns = useMemo(() => partTableColumns(), []);
|
||||
const tableFilters = useMemo(() => partTableFilters(), []);
|
||||
|
||||
const table = useTable('part-list');
|
||||
const table = useTable(tableName ?? 'part-list', {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'active',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
const user = useUserState();
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,8 @@ export default function PartTestResultTable({
|
|||
accessor: 'batch',
|
||||
title: t`Batch Code`,
|
||||
sortable: true,
|
||||
switchable: true
|
||||
switchable: true,
|
||||
copyable: true
|
||||
},
|
||||
LocationColumn({
|
||||
accessor: 'location_detail'
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export function PartVariantTable({ part }: Readonly<{ part: any }>) {
|
|||
ancestor: part.pk
|
||||
}
|
||||
}}
|
||||
tableName='part-variants'
|
||||
basePartInstance={part}
|
||||
defaultPartData={{
|
||||
...part,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ export interface PluginRegistryErrorI {
|
|||
* Table displaying list of plugin registry errors
|
||||
*/
|
||||
export default function PluginErrorTable() {
|
||||
const table = useTable('registryErrors', 'id');
|
||||
const table = useTable('registryErrors', {
|
||||
idAccessor: 'id'
|
||||
});
|
||||
|
||||
const registryErrorTableColumns: TableColumn<PluginRegistryErrorI>[] =
|
||||
useMemo(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { TableFilter } from '@lib/types/Filters';
|
|||
import type { TableColumn } from '@lib/types/Tables';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
import { CompanyColumn, PartColumn } from '../ColumnRenderers';
|
||||
import { CompanyColumn, IPNColumn, PartColumn } from '../ColumnRenderers';
|
||||
import ParametricDataTable from '../general/ParametricDataTable';
|
||||
|
||||
export default function ManufacturerPartParametricTable({
|
||||
|
|
@ -16,12 +16,9 @@ export default function ManufacturerPartParametricTable({
|
|||
PartColumn({
|
||||
switchable: false
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
title: t`IPN`,
|
||||
sortable: false,
|
||||
switchable: true
|
||||
},
|
||||
IPNColumn({
|
||||
sortable: false
|
||||
}),
|
||||
{
|
||||
accessor: 'manufacturer',
|
||||
sortable: true,
|
||||
|
|
@ -32,7 +29,8 @@ export default function ManufacturerPartParametricTable({
|
|||
{
|
||||
accessor: 'MPN',
|
||||
title: t`MPN`,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
copyable: true
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { useUserState } from '../../states/UserState';
|
|||
import {
|
||||
CompanyColumn,
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
LinkColumn,
|
||||
PartColumn
|
||||
} from '../ColumnRenderers';
|
||||
|
|
@ -54,7 +55,29 @@ export function ManufacturerPartTable({
|
|||
return tId;
|
||||
}, [manufacturerId, partId]);
|
||||
|
||||
const table = useTable(tableId);
|
||||
const initialFilters = useMemo(() => {
|
||||
const filters: TableFilter[] = [];
|
||||
|
||||
if (!manufacturerId) {
|
||||
filters.push({
|
||||
name: 'manufacturer_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
if (!partId) {
|
||||
filters.push({
|
||||
name: 'part_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [manufacturerId, partId]);
|
||||
|
||||
const table = useTable(tableId, {
|
||||
initialFilters: initialFilters
|
||||
});
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
|
|
@ -64,13 +87,7 @@ export function ManufacturerPartTable({
|
|||
PartColumn({
|
||||
switchable: !!partId
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
title: t`IPN`,
|
||||
sortable: true,
|
||||
ordering: 'IPN',
|
||||
switchable: true
|
||||
},
|
||||
IPNColumn({}),
|
||||
{
|
||||
accessor: 'manufacturer',
|
||||
sortable: true,
|
||||
|
|
@ -81,7 +98,8 @@ export function ManufacturerPartTable({
|
|||
{
|
||||
accessor: 'MPN',
|
||||
title: t`MPN`,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
copyable: true
|
||||
},
|
||||
DescriptionColumn({}),
|
||||
LinkColumn({})
|
||||
|
|
|
|||
|
|
@ -237,7 +237,8 @@ export function PurchaseOrderLineItemTable({
|
|||
title: t`Supplier Code`,
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
ordering: 'SKU'
|
||||
ordering: 'SKU',
|
||||
copyable: true
|
||||
},
|
||||
LinkColumn({
|
||||
accessor: 'supplier_part_detail.link',
|
||||
|
|
@ -250,7 +251,8 @@ export function PurchaseOrderLineItemTable({
|
|||
ordering: 'MPN',
|
||||
title: t`Manufacturer Code`,
|
||||
sortable: true,
|
||||
defaultVisible: false
|
||||
defaultVisible: false,
|
||||
copyable: true
|
||||
},
|
||||
CurrencyColumn({
|
||||
accessor: 'purchase_price',
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ import {
|
|||
CreationDateColumn,
|
||||
DescriptionColumn,
|
||||
LineItemsProgressColumn,
|
||||
LinkColumn,
|
||||
ProjectCodeColumn,
|
||||
ReferenceColumn,
|
||||
ResponsibleColumn,
|
||||
StartDateColumn,
|
||||
StatusColumn,
|
||||
TargetDateColumn
|
||||
TargetDateColumn,
|
||||
UpdatedAtColumn
|
||||
} from '../ColumnRenderers';
|
||||
import {
|
||||
AssignedToMeFilter,
|
||||
|
|
@ -44,7 +46,9 @@ import {
|
|||
StartDateAfterFilter,
|
||||
StartDateBeforeFilter,
|
||||
TargetDateAfterFilter,
|
||||
TargetDateBeforeFilter
|
||||
TargetDateBeforeFilter,
|
||||
UpdatedAfterFilter,
|
||||
UpdatedBeforeFilter
|
||||
} from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
||||
|
|
@ -60,7 +64,14 @@ export function PurchaseOrderTable({
|
|||
supplierPartId?: number;
|
||||
externalBuildId?: number;
|
||||
}>) {
|
||||
const table = useTable('purchase-order');
|
||||
const table = useTable('purchase-order', {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'outstanding',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
const user = useUserState();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
|
|
@ -91,6 +102,8 @@ export function PurchaseOrderTable({
|
|||
},
|
||||
CompletedBeforeFilter(),
|
||||
CompletedAfterFilter(),
|
||||
UpdatedBeforeFilter(),
|
||||
UpdatedAfterFilter(),
|
||||
ProjectCodeFilter(),
|
||||
HasProjectCodeFilter(),
|
||||
ResponsibleFilter(),
|
||||
|
|
@ -113,7 +126,8 @@ export function PurchaseOrderTable({
|
|||
)
|
||||
},
|
||||
{
|
||||
accessor: 'supplier_reference'
|
||||
accessor: 'supplier_reference',
|
||||
copyable: true
|
||||
},
|
||||
LineItemsProgressColumn({}),
|
||||
StatusColumn({ model: ModelType.purchaseorder }),
|
||||
|
|
@ -133,6 +147,9 @@ export function PurchaseOrderTable({
|
|||
CompletionDateColumn({
|
||||
accessor: 'complete_date'
|
||||
}),
|
||||
UpdatedAtColumn({
|
||||
defaultVisible: false
|
||||
}),
|
||||
{
|
||||
accessor: 'total_price',
|
||||
title: t`Total Price`,
|
||||
|
|
@ -143,7 +160,8 @@ export function PurchaseOrderTable({
|
|||
});
|
||||
}
|
||||
},
|
||||
ResponsibleColumn({})
|
||||
ResponsibleColumn({}),
|
||||
LinkColumn({})
|
||||
];
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ export default function SupplierPartParametricTable({
|
|||
{
|
||||
accessor: 'SKU',
|
||||
title: t`Supplier Part`,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
copyable: true
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
CompanyColumn,
|
||||
DecimalColumn,
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
LinkColumn,
|
||||
NoteColumn,
|
||||
PartColumn
|
||||
|
|
@ -54,7 +55,34 @@ export function SupplierPartTable({
|
|||
partId?: number;
|
||||
supplierId?: number;
|
||||
}>): ReactNode {
|
||||
const table = useTable('supplierparts');
|
||||
const initialFilters = useMemo(() => {
|
||||
const filters: TableFilter[] = [
|
||||
{
|
||||
name: 'active',
|
||||
value: 'true'
|
||||
}
|
||||
];
|
||||
|
||||
if (!supplierId) {
|
||||
filters.push({
|
||||
name: 'supplier_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
if (!partId) {
|
||||
filters.push({
|
||||
name: 'part_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [supplierId, partId]);
|
||||
|
||||
const table = useTable('supplierparts', {
|
||||
initialFilters: initialFilters
|
||||
});
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
|
|
@ -65,13 +93,7 @@ export function SupplierPartTable({
|
|||
switchable: !!partId,
|
||||
part: 'part_detail'
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
title: t`IPN`,
|
||||
sortable: true,
|
||||
ordering: 'IPN',
|
||||
switchable: true
|
||||
},
|
||||
IPNColumn({}),
|
||||
{
|
||||
accessor: 'supplier',
|
||||
sortable: true,
|
||||
|
|
@ -82,7 +104,8 @@ export function SupplierPartTable({
|
|||
{
|
||||
accessor: 'SKU',
|
||||
title: t`Supplier Part`,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
copyable: true
|
||||
},
|
||||
DescriptionColumn({}),
|
||||
{
|
||||
|
|
@ -97,7 +120,9 @@ export function SupplierPartTable({
|
|||
accessor: 'MPN',
|
||||
sortable: true,
|
||||
title: t`MPN`,
|
||||
render: (record: any) => record?.manufacturer_part_detail?.MPN
|
||||
render: (record: any) => record?.manufacturer_part_detail?.MPN,
|
||||
copyable: true,
|
||||
copyAccessor: 'manufacturer_part_detail.MPN'
|
||||
},
|
||||
BooleanColumn({
|
||||
accessor: 'primary',
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ import {
|
|||
CreationDateColumn,
|
||||
DescriptionColumn,
|
||||
LineItemsProgressColumn,
|
||||
LinkColumn,
|
||||
ProjectCodeColumn,
|
||||
ReferenceColumn,
|
||||
ResponsibleColumn,
|
||||
StartDateColumn,
|
||||
StatusColumn,
|
||||
TargetDateColumn
|
||||
TargetDateColumn,
|
||||
UpdatedAtColumn
|
||||
} from '../ColumnRenderers';
|
||||
import {
|
||||
AssignedToMeFilter,
|
||||
|
|
@ -45,7 +47,9 @@ import {
|
|||
StartDateAfterFilter,
|
||||
StartDateBeforeFilter,
|
||||
TargetDateAfterFilter,
|
||||
TargetDateBeforeFilter
|
||||
TargetDateBeforeFilter,
|
||||
UpdatedAfterFilter,
|
||||
UpdatedBeforeFilter
|
||||
} from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
||||
|
|
@ -56,7 +60,18 @@ export function ReturnOrderTable({
|
|||
partId?: number;
|
||||
customerId?: number;
|
||||
}>) {
|
||||
const table = useTable(!!partId ? 'returnorders-part' : 'returnorders-index');
|
||||
const table = useTable(
|
||||
!!partId ? 'returnorders-part' : 'returnorders-index',
|
||||
{
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'outstanding',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
|
|
@ -87,6 +102,8 @@ export function ReturnOrderTable({
|
|||
},
|
||||
CompletedBeforeFilter(),
|
||||
CompletedAfterFilter(),
|
||||
UpdatedBeforeFilter(),
|
||||
UpdatedAfterFilter(),
|
||||
HasProjectCodeFilter(),
|
||||
ProjectCodeFilter(),
|
||||
ResponsibleFilter(),
|
||||
|
|
@ -112,7 +129,8 @@ export function ReturnOrderTable({
|
|||
)
|
||||
},
|
||||
{
|
||||
accessor: 'customer_reference'
|
||||
accessor: 'customer_reference',
|
||||
copyable: true
|
||||
},
|
||||
DescriptionColumn({}),
|
||||
LineItemsProgressColumn({}),
|
||||
|
|
@ -133,6 +151,9 @@ export function ReturnOrderTable({
|
|||
CompletionDateColumn({
|
||||
accessor: 'complete_date'
|
||||
}),
|
||||
UpdatedAtColumn({
|
||||
defaultVisible: false
|
||||
}),
|
||||
ResponsibleColumn({}),
|
||||
{
|
||||
accessor: 'total_price',
|
||||
|
|
@ -143,7 +164,8 @@ export function ReturnOrderTable({
|
|||
currency: record.order_currency || record.customer_detail?.currency
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
LinkColumn({})
|
||||
];
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { useTable } from '../../hooks/UseTable';
|
|||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
LocationColumn,
|
||||
PartColumn,
|
||||
ReferenceColumn,
|
||||
|
|
@ -130,13 +131,9 @@ export default function SalesOrderAllocationTable({
|
|||
accessor: 'part_detail.description',
|
||||
hidden: showPartInfo != true
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
title: t`IPN`,
|
||||
hidden: showPartInfo != true,
|
||||
sortable: true,
|
||||
ordering: 'IPN'
|
||||
},
|
||||
IPNColumn({
|
||||
hidden: showPartInfo != true
|
||||
}),
|
||||
{
|
||||
accessor: 'serial',
|
||||
title: t`Serial Number`,
|
||||
|
|
@ -149,7 +146,9 @@ export default function SalesOrderAllocationTable({
|
|||
title: t`Batch Code`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
render: (record: any) => record?.item_detail?.batch
|
||||
render: (record: any) => record?.item_detail?.batch,
|
||||
copyable: true,
|
||||
copyAccessor: 'item_detail.batch'
|
||||
},
|
||||
{
|
||||
accessor: 'available',
|
||||
|
|
|
|||
|
|
@ -47,8 +47,10 @@ import {
|
|||
DateColumn,
|
||||
DecimalColumn,
|
||||
DescriptionColumn,
|
||||
IPNColumn,
|
||||
LinkColumn,
|
||||
ProjectCodeColumn,
|
||||
ReferenceColumn,
|
||||
RenderPartColumn
|
||||
} from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
|
@ -94,21 +96,11 @@ export default function SalesOrderLineItemTable({
|
|||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
title: t`IPN`,
|
||||
sortable: true,
|
||||
ordering: 'IPN',
|
||||
switchable: true
|
||||
},
|
||||
IPNColumn({}),
|
||||
DescriptionColumn({
|
||||
accessor: 'part_detail.description'
|
||||
}),
|
||||
{
|
||||
accessor: 'reference',
|
||||
sortable: false,
|
||||
switchable: true
|
||||
},
|
||||
ReferenceColumn({}),
|
||||
ProjectCodeColumn({}),
|
||||
DecimalColumn({
|
||||
accessor: 'quantity',
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@ export default function SalesOrderShipmentTable({
|
|||
accessor: 'order_detail.reference',
|
||||
title: t`Sales Order`,
|
||||
hidden: !showOrderInfo,
|
||||
sortable: false
|
||||
sortable: false,
|
||||
copyable: true
|
||||
},
|
||||
StatusColumn({
|
||||
switchable: true,
|
||||
|
|
@ -155,7 +156,8 @@ export default function SalesOrderShipmentTable({
|
|||
accessor: 'reference',
|
||||
title: t`Shipment Reference`,
|
||||
switchable: false,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
copyable: true
|
||||
},
|
||||
{
|
||||
accessor: 'allocated_items',
|
||||
|
|
@ -193,14 +195,14 @@ export default function SalesOrderShipmentTable({
|
|||
title: t`Delivery Date`
|
||||
}),
|
||||
{
|
||||
accessor: 'tracking_number'
|
||||
accessor: 'tracking_number',
|
||||
copyable: true
|
||||
},
|
||||
{
|
||||
accessor: 'invoice_number'
|
||||
accessor: 'invoice_number',
|
||||
copyable: true
|
||||
},
|
||||
LinkColumn({
|
||||
accessor: 'link'
|
||||
})
|
||||
LinkColumn({})
|
||||
];
|
||||
}, [showOrderInfo]);
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue