mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' into add-changelog
This commit is contained in:
commit
c965637dd3
@ -2,7 +2,7 @@ version: "3"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:14
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 5432/tcp
|
||||
|
3
.github/scripts/version_check.py
vendored
3
.github/scripts/version_check.py
vendored
@ -97,6 +97,9 @@ if __name__ == '__main__':
|
||||
)
|
||||
text = version_file.read_text()
|
||||
results = re.findall(r"""INVENTREE_API_VERSION = (.*)""", text)
|
||||
# If 2. args is true lower the version number by 1
|
||||
if len(sys.argv) > 2 and sys.argv[2] == 'true':
|
||||
results[0] = str(int(results[0]) - 1)
|
||||
print(results[0])
|
||||
exit(0)
|
||||
|
||||
|
22
.github/workflows/qc_checks.yaml
vendored
22
.github/workflows/qc_checks.yaml
vendored
@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
python_version: 3.9
|
||||
node_version: 18
|
||||
node_version: 20
|
||||
# The OS version must be set per job
|
||||
server_start_sleep: 60
|
||||
|
||||
@ -164,15 +164,27 @@ jobs:
|
||||
name: schema.yml
|
||||
path: src/backend/InvenTree/schema.yml
|
||||
- name: Download public schema
|
||||
if: needs.paths-filter.outputs.api == 'false'
|
||||
run: |
|
||||
pip install --require-hashes -r contrib/dev_reqs/requirements.txt >/dev/null 2>&1
|
||||
version="$(python3 .github/scripts/version_check.py only_version 2>&1)"
|
||||
version="$(python3 .github/scripts/version_check.py only_version ${{ needs.paths-filter.outputs.api }} 2>&1)"
|
||||
echo "Version: $version"
|
||||
url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml"
|
||||
echo "URL: $url"
|
||||
curl -s -o api.yaml $url
|
||||
code=$(curl -s -o api.yaml $url --write-out '%{http_code}' --silent)
|
||||
if [ "$code" != "200" ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Downloaded api.yaml"
|
||||
- name: Running OpenAPI Spec diff action
|
||||
id: breaking_changes
|
||||
uses: oasdiff/oasdiff-action/diff@main
|
||||
with:
|
||||
base: 'api.yaml'
|
||||
revision: 'src/backend/InvenTree/schema.yml'
|
||||
format: 'html'
|
||||
- name: Echoing diff to step
|
||||
run: echo "${{ steps.breaking_changes.outputs.diff }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check for differences in API Schema
|
||||
if: needs.paths-filter.outputs.api == 'false'
|
||||
run: |
|
||||
@ -555,6 +567,8 @@ jobs:
|
||||
run: cd src/frontend && yarn install
|
||||
- name: Build frontend
|
||||
run: cd src/frontend && yarn run compile && yarn run build
|
||||
- name: Write version file - SHA
|
||||
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
|
||||
- name: Zip frontend
|
||||
run: |
|
||||
cd src/backend/InvenTree/web/static
|
||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@ -43,6 +43,10 @@ jobs:
|
||||
run: cd src/frontend && yarn install
|
||||
- name: Build frontend
|
||||
run: cd src/frontend && npm run compile && npm run build
|
||||
- name: Write version file - SHA
|
||||
run: cd src/backend/InvenTree/web/static/web/.vite && echo "$GITHUB_SHA" > sha.txt
|
||||
- name: Write version file - TAG
|
||||
run: cd src/backend/InvenTree/web/static/web/.vite && echo "${{ github.ref_name }}" > tag.txt
|
||||
- name: Zip frontend
|
||||
run: |
|
||||
cd src/backend/InvenTree/web/static/web
|
||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@ -7,7 +7,7 @@ on:
|
||||
|
||||
env:
|
||||
python_version: 3.9
|
||||
node_version: 18
|
||||
node_version: 20
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -14,8 +14,10 @@ env:
|
||||
- INVENTREE_BACKUP_DIR=/opt/inventree/backup
|
||||
- INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt
|
||||
- INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml
|
||||
- APP_REPO=inventree/InvenTree
|
||||
before_install: contrib/packager.io/preinstall.sh
|
||||
after_install: contrib/packager.io/postinstall.sh
|
||||
before_remove: contrib/packager.io/preinstall.sh
|
||||
before:
|
||||
- contrib/packager.io/before.sh
|
||||
dependencies:
|
||||
|
@ -17,7 +17,7 @@ repos:
|
||||
- id: check-yaml
|
||||
- id: mixed-line-ending
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.1
|
||||
rev: v0.5.1
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
args: [--preview]
|
||||
@ -27,23 +27,23 @@ repos:
|
||||
--preview
|
||||
]
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.1.35
|
||||
rev: 0.2.13
|
||||
hooks:
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements-dev.in
|
||||
args: [src/backend/requirements-dev.in, -o, src/backend/requirements-dev.txt]
|
||||
args: [src/backend/requirements-dev.in, -o, src/backend/requirements-dev.txt, --no-strip-extras, --generate-hashes]
|
||||
files: src/backend/requirements-dev\.(in|txt)$
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements.txt
|
||||
args: [src/backend/requirements.in, -o, src/backend/requirements.txt]
|
||||
args: [src/backend/requirements.in, -o, src/backend/requirements.txt, --no-strip-extras, --generate-hashes]
|
||||
files: src/backend/requirements\.(in|txt)$
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements.txt
|
||||
args: [contrib/dev_reqs/requirements.in, -o, contrib/dev_reqs/requirements.txt]
|
||||
args: [contrib/dev_reqs/requirements.in, -o, contrib/dev_reqs/requirements.txt, --no-strip-extras, --generate-hashes]
|
||||
files: contrib/dev_reqs/requirements\.(in|txt)$
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements.txt
|
||||
args: [docs/requirements.in, -o, docs/requirements.txt]
|
||||
args: [docs/requirements.in, -o, docs/requirements.txt, --no-strip-extras, --generate-hashes]
|
||||
files: docs/requirements\.(in|txt)$
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements.txt
|
||||
@ -54,9 +54,11 @@ repos:
|
||||
hooks:
|
||||
- id: djlint-django
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
exclude: >
|
||||
(?x)^(
|
||||
docs/docs/stylesheets/.*|
|
||||
@ -75,7 +77,7 @@ repos:
|
||||
- "prettier@^2.4.1"
|
||||
- "@trivago/prettier-plugin-sort-imports"
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: "v9.4.0"
|
||||
rev: "v9.6.0"
|
||||
hooks:
|
||||
- id: eslint
|
||||
additional_dependencies:
|
||||
@ -87,7 +89,7 @@ repos:
|
||||
- "@typescript-eslint/parser"
|
||||
files: ^src/frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.18.3
|
||||
rev: v8.18.4
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
#- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||
|
@ -9,7 +9,7 @@
|
||||
# - Runs InvenTree web server under django development server
|
||||
# - Monitors source files for any changes, and live-reloads server
|
||||
|
||||
ARG base_image=python:3.11-alpine3.18
|
||||
ARG base_image=python:3.11-alpine3.20
|
||||
FROM ${base_image} AS inventree_base
|
||||
|
||||
# Build arguments for this image
|
||||
@ -64,7 +64,7 @@ RUN apk add --no-cache \
|
||||
# Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-12
|
||||
py3-pip py3-pillow py3-cffi py3-brotli pango poppler-utils openldap \
|
||||
# Postgres client
|
||||
postgresql13-client \
|
||||
postgresql14-client \
|
||||
# MySQL / MariaDB client
|
||||
mariadb-client mariadb-connector-c \
|
||||
&& \
|
||||
|
@ -20,7 +20,7 @@ services:
|
||||
# Use PostgreSQL as the database backend
|
||||
# Note: This can be changed to a different backend if required
|
||||
inventree-dev-db:
|
||||
image: postgres:13
|
||||
image: postgres:14
|
||||
expose:
|
||||
- 5432/tcp
|
||||
environment:
|
||||
|
@ -38,7 +38,7 @@ services:
|
||||
# Database service
|
||||
# Use PostgreSQL as the database backend
|
||||
inventree-db:
|
||||
image: postgres:13
|
||||
image: postgres:14
|
||||
container_name: inventree-db
|
||||
expose:
|
||||
- ${INVENTREE_DB_PORT:-5432}/tcp
|
||||
|
@ -1,12 +1,12 @@
|
||||
#!/bin/ash
|
||||
|
||||
# Install system packages required for building InvenTree python libraries
|
||||
# Note that for postgreslql, we use the 13 version, which matches the version used in the InvenTree docker image
|
||||
# Note that for postgreslql, we use the 14 version, which matches the version used in the InvenTree docker image
|
||||
|
||||
apk add gcc g++ musl-dev openssl-dev libffi-dev cargo python3-dev openldap-dev \
|
||||
libstdc++ build-base linux-headers py3-grpcio \
|
||||
jpeg-dev openjpeg-dev libwebp-dev zlib-dev \
|
||||
sqlite sqlite-dev \
|
||||
mariadb-connector-c-dev mariadb-client mariadb-dev \
|
||||
postgresql13-dev postgresql-libs \
|
||||
postgresql14-dev postgresql-libs \
|
||||
$@
|
||||
|
@ -11,12 +11,15 @@ django==4.2.14 \
|
||||
django-auth-ldap==4.8.0 \
|
||||
--hash=sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce \
|
||||
--hash=sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738
|
||||
# via -r contrib/container/requirements.in
|
||||
gunicorn==22.0.0 \
|
||||
--hash=sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9 \
|
||||
--hash=sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63
|
||||
# via -r contrib/container/requirements.in
|
||||
invoke==2.2.0 \
|
||||
--hash=sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820 \
|
||||
--hash=sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5
|
||||
# via -r contrib/container/requirements.in
|
||||
mariadb==1.1.10 \
|
||||
--hash=sha256:03d6284ef713d1cad40146576a4cc2d6cbc1662060f2a0e59b174e1694521698 \
|
||||
--hash=sha256:1ce87971c02375236ff8933e6c593c748e7b2f2950b86eabfab4289fd250ea63 \
|
||||
@ -29,6 +32,7 @@ mariadb==1.1.10 \
|
||||
--hash=sha256:a332893e3ef7ceb7970ab4bd7c844bcb4bd68a051ca51313566f9808d7411f2d \
|
||||
--hash=sha256:d7b09ec4abd02ed235257feb769f90cd4066e8f536b55b92f5166103d5b66a63 \
|
||||
--hash=sha256:dff8b28ce4044574870d7bdd2d9f9f5da8e5f95a7ff6d226185db733060d1a93
|
||||
# via -r contrib/container/requirements.in
|
||||
mysqlclient==2.2.4 \
|
||||
--hash=sha256:329e4eec086a2336fe3541f1ce095d87a6f169d1cc8ba7b04ac68bcb234c9711 \
|
||||
--hash=sha256:33bc9fb3464e7d7c10b1eaf7336c5ff8f2a3d3b88bab432116ad2490beb3bf41 \
|
||||
@ -39,6 +43,7 @@ mysqlclient==2.2.4 \
|
||||
--hash=sha256:ac44777eab0a66c14cb0d38965572f762e193ec2e5c0723bcd11319cc5b693c5 \
|
||||
--hash=sha256:d43987bb9626096a302ca6ddcdd81feaeca65ced1d5fe892a6a66b808326aa54 \
|
||||
--hash=sha256:e1ebe3f41d152d7cb7c265349fdb7f1eca86ccb0ca24a90036cde48e00ceb2ab
|
||||
# via -r contrib/container/requirements.in
|
||||
packaging==24.0 \
|
||||
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
|
||||
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
|
||||
@ -48,6 +53,7 @@ packaging==24.0 \
|
||||
psycopg[binary, pool]==3.1.18 \
|
||||
--hash=sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b \
|
||||
--hash=sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e
|
||||
# via -r contrib/container/requirements.in
|
||||
psycopg-binary==3.1.18 \
|
||||
--hash=sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7 \
|
||||
--hash=sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c \
|
||||
@ -131,7 +137,9 @@ pyasn1-modules==0.4.0 \
|
||||
# via python-ldap
|
||||
python-ldap==3.4.4 \
|
||||
--hash=sha256:7edb0accec4e037797705f3a05cbf36a9fde50d08c8f67f2aef99a2628fab828
|
||||
# via django-auth-ldap
|
||||
# via
|
||||
# -r contrib/container/requirements.in
|
||||
# django-auth-ldap
|
||||
pyyaml==6.0.1 \
|
||||
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
|
||||
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
|
||||
@ -184,9 +192,11 @@ pyyaml==6.0.1 \
|
||||
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
|
||||
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
|
||||
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
|
||||
# via -r contrib/container/requirements.in
|
||||
setuptools==70.3.0 \
|
||||
--hash=sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5 \
|
||||
--hash=sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc
|
||||
# via -r contrib/container/requirements.in
|
||||
sqlparse==0.5.0 \
|
||||
--hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \
|
||||
--hash=sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663
|
||||
@ -215,6 +225,8 @@ uv==0.1.38 \
|
||||
--hash=sha256:b0b15e51a0f8240969bc412ed0dd60cfe3f664b30173139ef263d71c596d631f \
|
||||
--hash=sha256:ea44c07605d1359a7d82bf42706dd86d341f15f4ca2e1f36e51626a7111c2ad5 \
|
||||
--hash=sha256:f87c9711493c53d32012a96b49c4d53aabdf7ed666cbf2c3fb55dd402a6b31a8
|
||||
# via -r contrib/container/requirements.in
|
||||
wheel==0.43.0 \
|
||||
--hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \
|
||||
--hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81
|
||||
# via -r contrib/container/requirements.in
|
||||
|
@ -1,5 +1,5 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt
|
||||
# uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt --no-strip-extras --generate-hashes
|
||||
certifi==2024.7.4 \
|
||||
--hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
|
||||
--hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90
|
||||
@ -103,6 +103,7 @@ idna==3.7 \
|
||||
jc==1.25.2 \
|
||||
--hash=sha256:26e412a65a478f9da3097653db6277f915cfae5c0f0a3f42026b405936abd358 \
|
||||
--hash=sha256:97ada193495f79550f06fe0cbfb119ff470bcca57c1cc593a5cdb0008720e0b3
|
||||
# via -r contrib/dev_reqs/requirements.in
|
||||
pygments==2.17.2 \
|
||||
--hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
|
||||
--hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
|
||||
@ -159,9 +160,11 @@ pyyaml==6.0.1 \
|
||||
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
|
||||
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
|
||||
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
|
||||
# via -r contrib/dev_reqs/requirements.in
|
||||
requests==2.32.2 \
|
||||
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
|
||||
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
|
||||
# via -r contrib/dev_reqs/requirements.in
|
||||
ruamel-yaml==0.18.6 \
|
||||
--hash=sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636 \
|
||||
--hash=sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b
|
||||
|
@ -5,33 +5,40 @@
|
||||
|
||||
set -eu
|
||||
|
||||
VERSION="$APP_PKG_VERSION-$APP_PKG_ITERATION"
|
||||
echo "Setting VERSION information to $VERSION"
|
||||
echo "$VERSION" > VERSION
|
||||
|
||||
# The sha is the second element in APP_PKG_ITERATION
|
||||
VERSION="$APP_PKG_VERSION-$APP_PKG_ITERATION"
|
||||
SHA=$(echo $APP_PKG_ITERATION | cut -d'.' -f2)
|
||||
|
||||
# Download info
|
||||
echo "Getting info from github for commit $SHA"
|
||||
curl -L \
|
||||
echo "INFO collection | Getting info from github for commit $SHA"
|
||||
curl -L -s -f \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/InvenTree/InvenTree/commits/$SHA > commit.json
|
||||
curl -L \
|
||||
https://api.github.com/repos/$APP_REPO/commits/$SHA > commit.json
|
||||
echo "INFO collection | Got commit.json with size $(wc -c commit.json)"
|
||||
curl -L -s -f \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/InvenTree/InvenTree/commits/$SHA/branches-where-head > branches.json
|
||||
https://api.github.com/repos/$APP_REPO/commits/$SHA/branches-where-head > branches.json
|
||||
echo "INFO collection | Got branches.json with size $(wc -c branches.json)"
|
||||
curl -L -s -f \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/$APP_REPO/commits/$APP_PKG_VERSION > tag.json
|
||||
echo "INFO collection | Got tag.json with size $(wc -c tag.json)"
|
||||
|
||||
# Extract info
|
||||
echo "Extracting info from github"
|
||||
echo "INFO extract | Extracting info from github"
|
||||
DATE=$(jq -r '.commit.committer.date' commit.json)
|
||||
BRANCH=$(jq -r '.[].name' branches.json)
|
||||
NODE_ID=$(jq -r '.node_id' commit.json)
|
||||
SIGNATURE=$(jq -r '.commit.verification.signature' commit.json)
|
||||
FULL_SHA=$(jq -r '.sha' commit.json)
|
||||
|
||||
echo "Write VERSION information"
|
||||
echo "INFO write | Write VERSION information"
|
||||
echo "$VERSION" > VERSION
|
||||
echo "INVENTREE_COMMIT_HASH='$SHA'" >> VERSION
|
||||
echo "INVENTREE_COMMIT_SHA='$FULL_SHA'" >> VERSION
|
||||
echo "INVENTREE_COMMIT_DATE='$DATE'" >> VERSION
|
||||
echo "INVENTREE_PKG_INSTALLER='PKG'" >> VERSION
|
||||
echo "INVENTREE_PKG_BRANCH='$BRANCH'" >> VERSION
|
||||
@ -39,5 +46,22 @@ echo "INVENTREE_PKG_TARGET='$TARGET'" >> VERSION
|
||||
echo "NODE_ID='$NODE_ID'" >> VERSION
|
||||
echo "SIGNATURE='$SIGNATURE'" >> VERSION
|
||||
|
||||
echo "Written VERSION information"
|
||||
echo "INFO write | Written VERSION information"
|
||||
echo "### VERSION ###"
|
||||
cat VERSION
|
||||
echo "### VERSION ###"
|
||||
|
||||
# Try to get frontend
|
||||
echo "INFO frontend | Trying to get frontend"
|
||||
# Check if tag sha is the same as the commit sha
|
||||
TAG_SHA=$(jq -r '.sha' tag.json)
|
||||
if [ "$TAG_SHA" != "$FULL_SHA" ]; then
|
||||
echo "INFO frontend | Tag sha '$TAG_SHA' is not the same as commit sha $FULL_SHA, can not download frontend"
|
||||
else
|
||||
echo "INFO frontend | Getting frontend from github via tag"
|
||||
curl https://github.com/$APP_REPO/releases/download/$APP_PKG_VERSION/frontend-build.zip -L -O -f
|
||||
mkdir -p src/backend/InvenTree/web/static
|
||||
echo "INFO frontend | Unzipping frontend"
|
||||
unzip -qq frontend-build.zip -d src/backend/InvenTree/web/static/web
|
||||
echo "INFO frontend | Unzipped frontend"
|
||||
fi
|
||||
|
@ -60,7 +60,7 @@ function detect_python() {
|
||||
fi
|
||||
|
||||
# Try to detect a python between 3.9 and 3.12 in reverse order
|
||||
if [ -z "${SETUP_PYTHON}" ]; then
|
||||
if [ -z "$(which ${SETUP_PYTHON})" ]; then
|
||||
echo "# Trying to detecting python3.${PYTHON_FROM} to python3.${PYTHON_TO} - using newest version"
|
||||
for i in $(seq $PYTHON_TO -1 $PYTHON_FROM); do
|
||||
echo "# Checking for python3.${i}"
|
||||
@ -318,17 +318,17 @@ function set_env() {
|
||||
sed -i s=debug:\ True=debug:\ False=g ${INVENTREE_CONFIG_FILE}
|
||||
|
||||
# Database engine
|
||||
sed -i s=#ENGINE:\ sampleengine=ENGINE:\ ${INVENTREE_DB_ENGINE}=g ${INVENTREE_CONFIG_FILE}
|
||||
sed -i s=#\ ENGINE:\ Database\ engine.\ Selection\ from:=ENGINE:\ ${INVENTREE_DB_ENGINE}=g ${INVENTREE_CONFIG_FILE}
|
||||
# Database name
|
||||
sed -i s=#NAME:\ \'/path/to/database\'=NAME:\ \'${INVENTREE_DB_NAME}\'=g ${INVENTREE_CONFIG_FILE}
|
||||
sed -i s=#\ NAME:\ Database\ name=NAME:\ \'${INVENTREE_DB_NAME}\'=g ${INVENTREE_CONFIG_FILE}
|
||||
# Database user
|
||||
sed -i s=#USER:\ sampleuser=USER:\ ${INVENTREE_DB_USER}=g ${INVENTREE_CONFIG_FILE}
|
||||
sed -i s=#\ USER:\ Database\ username\ \(if\ required\)=USER:\ ${INVENTREE_DB_USER}=g ${INVENTREE_CONFIG_FILE}
|
||||
# Database password
|
||||
sed -i s=#PASSWORD:\ samplepassword=PASSWORD:\ ${INVENTREE_DB_PASSWORD}=g ${INVENTREE_CONFIG_FILE}
|
||||
sed -i s=#\ PASSWORD:\ Database\ password\ \(if\ required\)=PASSWORD:\ ${INVENTREE_DB_PASSWORD}=g ${INVENTREE_CONFIG_FILE}
|
||||
# Database host
|
||||
sed -i s=#HOST:\ samplehost=HOST:\ ${INVENTREE_DB_HOST}=g ${INVENTREE_CONFIG_FILE}
|
||||
sed -i s=#\ HOST:\ Database\ host\ address\ \(if\ required\)=HOST:\ ${INVENTREE_DB_HOST}=g ${INVENTREE_CONFIG_FILE}
|
||||
# Database port
|
||||
sed -i s=#PORT:\ 123456=PORT:\ ${INVENTREE_DB_PORT}=g ${INVENTREE_CONFIG_FILE}
|
||||
sed -i s=#\ PORT:\ Database\ host\ port\ \(if\ required\)=PORT:\ ${INVENTREE_DB_PORT}=g ${INVENTREE_CONFIG_FILE}
|
||||
|
||||
# Fixing the permissions
|
||||
chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} ${INVENTREE_CONFIG_FILE}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# packager.io preinstall script
|
||||
# packager.io preinstall/preremove script
|
||||
#
|
||||
PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
|
||||
|
||||
|
@ -125,7 +125,7 @@ The core software modules are targeting the following versions:
|
||||
| Python | {{ config.extra.min_python_version }} | Minimum required version |
|
||||
| Invoke | {{ config.extra.min_invoke_version }} | Minimum required version |
|
||||
| Django | {{ config.extra.django_version }} | Pinned version |
|
||||
| Node | 18 | Only needed for frontend development |
|
||||
| Node | 20 | Only needed for frontend development |
|
||||
|
||||
Any other software dependencies are handled by the project package config.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile docs/requirements.in -o docs/requirements.txt
|
||||
# uv pip compile docs/requirements.in -o docs/requirements.txt --no-strip-extras --generate-hashes
|
||||
anyio==4.3.0 \
|
||||
--hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \
|
||||
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6
|
||||
@ -284,6 +284,7 @@ mkdocs==1.6.0 \
|
||||
--hash=sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7 \
|
||||
--hash=sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocs-autorefs
|
||||
# mkdocs-git-revision-date-localized-plugin
|
||||
# mkdocs-include-markdown-plugin
|
||||
@ -303,15 +304,19 @@ mkdocs-get-deps==0.2.0 \
|
||||
mkdocs-git-revision-date-localized-plugin==1.2.5 \
|
||||
--hash=sha256:0c439816d9d0dba48e027d9d074b2b9f1d7cd179f74ba46b51e4da7bb3dc4b9b \
|
||||
--hash=sha256:d796a18b07cfcdb154c133e3ec099d2bb5f38389e4fd54d3eb516a8a736815b8
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-include-markdown-plugin==6.0.6 \
|
||||
--hash=sha256:7c80258b2928563c75cc057a7b9a0014701c40804b1b6aa290f3b4032518b43c \
|
||||
--hash=sha256:7ccafbaa412c1e5d3510c4aff46d1fe64c7a810c01dace4c636253d1aa5bc193
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-macros-plugin==1.0.5 \
|
||||
--hash=sha256:f60e26f711f5a830ddf1e7980865bf5c0f1180db56109803cdd280073c1a050a \
|
||||
--hash=sha256:fe348d75f01c911f362b6d998c57b3d85b505876dde69db924f2c512c395c328
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material==9.5.24 \
|
||||
--hash=sha256:02d5aaba0ee755e707c3ef6e748f9acb7b3011187c0ea766db31af8905078a34 \
|
||||
--hash=sha256:e12cd75954c535b61e716f359cf2a5056bf4514889d17161fdebd5df4b0153c6
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material-extensions==1.3.1 \
|
||||
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
|
||||
--hash=sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31
|
||||
@ -319,10 +324,13 @@ mkdocs-material-extensions==1.3.1 \
|
||||
mkdocs-simple-hooks==0.1.5 \
|
||||
--hash=sha256:dddbdf151a18723c9302a133e5cf79538be8eb9d274e8e07d2ac3ac34890837c \
|
||||
--hash=sha256:efeabdbb98b0850a909adee285f3404535117159d5cb3a34f541d6eaa644d50a
|
||||
# via -r docs/requirements.in
|
||||
mkdocstrings[python]==0.25.1 \
|
||||
--hash=sha256:c3a2515f31577f311a9ee58d089e4c51fc6046dbd9e9b4c3de4c3194667fe9bf \
|
||||
--hash=sha256:da01fcc2670ad61888e8fe5b60afe9fee5781017d67431996832d63e887c2e51
|
||||
# via mkdocstrings-python
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocstrings-python
|
||||
mkdocstrings-python==1.10.2 \
|
||||
--hash=sha256:38a4fd41953defb458a107033440c229c7e9f98f35a24e84d888789c97da5a63 \
|
||||
--hash=sha256:e8e596b37f45c09b67bec253e035fe18988af5bbbbf44e0ccd711742eed750e5
|
||||
@ -330,6 +338,7 @@ mkdocstrings-python==1.10.2 \
|
||||
neoteroi-mkdocs==1.0.5 \
|
||||
--hash=sha256:1f3b372dee79269157361733c0f45b3a89189077078e0e3224d829a144ef3579 \
|
||||
--hash=sha256:29875ef444b08aec5619a384142e16f1b4e851465cab4e380fb2b8ae730fe046
|
||||
# via -r docs/requirements.in
|
||||
packaging==24.0 \
|
||||
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
|
||||
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
|
||||
|
@ -1,12 +1,21 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 222
|
||||
INVENTREE_API_VERSION = 225
|
||||
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
v225 - 2024-07-17 : https://github.com/inventree/InvenTree/pull/7671
|
||||
- Adds "filters" field to DataImportSession API
|
||||
|
||||
v224 - 2024-07-14 : https://github.com/inventree/InvenTree/pull/7667
|
||||
- Add notes field to ManufacturerPart and SupplierPart API endpoints
|
||||
|
||||
v223 - 2024-07-14 : https://github.com/inventree/InvenTree/pull/7649
|
||||
- Allow adjustment of "packaging" field when receiving items against a purchase order
|
||||
|
||||
v222 - 2024-07-14 : https://github.com/inventree/InvenTree/pull/7635
|
||||
- Adjust the BomItem API endpoint to improve data import process
|
||||
|
||||
|
@ -120,26 +120,28 @@ class Build(
|
||||
self.validate_reference_field(self.reference)
|
||||
self.reference_int = self.rebuild_reference_field(self.reference)
|
||||
|
||||
if get_global_setting('BUILDORDER_REQUIRE_VALID_BOM'):
|
||||
# Check that the BOM is valid
|
||||
if not self.part.is_bom_valid():
|
||||
raise ValidationError({
|
||||
'part': _('Assembly BOM has not been validated')
|
||||
})
|
||||
# Check part when initially creating the build order
|
||||
if not self.pk or self.has_field_changed('part'):
|
||||
if get_global_setting('BUILDORDER_REQUIRE_VALID_BOM'):
|
||||
# Check that the BOM is valid
|
||||
if not self.part.is_bom_valid():
|
||||
raise ValidationError({
|
||||
'part': _('Assembly BOM has not been validated')
|
||||
})
|
||||
|
||||
if get_global_setting('BUILDORDER_REQUIRE_ACTIVE_PART'):
|
||||
# Check that the part is active
|
||||
if not self.part.active:
|
||||
raise ValidationError({
|
||||
'part': _('Build order cannot be created for an inactive part')
|
||||
})
|
||||
if get_global_setting('BUILDORDER_REQUIRE_ACTIVE_PART'):
|
||||
# Check that the part is active
|
||||
if not self.part.active:
|
||||
raise ValidationError({
|
||||
'part': _('Build order cannot be created for an inactive part')
|
||||
})
|
||||
|
||||
if get_global_setting('BUILDORDER_REQUIRE_LOCKED_PART'):
|
||||
# Check that the part is locked
|
||||
if not self.part.locked:
|
||||
raise ValidationError({
|
||||
'part': _('Build order cannot be created for an unlocked part')
|
||||
})
|
||||
if get_global_setting('BUILDORDER_REQUIRE_LOCKED_PART'):
|
||||
# Check that the part is locked
|
||||
if not self.part.locked:
|
||||
raise ValidationError({
|
||||
'part': _('Build order cannot be created for an unlocked part')
|
||||
})
|
||||
|
||||
# On first save (i.e. creation), run some extra checks
|
||||
if self.pk is None:
|
||||
|
@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.2.11 on 2024-07-16 12:58
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('company', '0070_remove_manufacturerpartattachment_manufacturer_part_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='manufacturerpart',
|
||||
name='notes',
|
||||
field=InvenTree.fields.InvenTreeNotesField(blank=True, help_text='Markdown notes (optional)', max_length=50000, null=True, verbose_name='Notes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierpart',
|
||||
name='notes',
|
||||
field=InvenTree.fields.InvenTreeNotesField(blank=True, help_text='Markdown notes (optional)', max_length=50000, null=True, verbose_name='Notes'),
|
||||
),
|
||||
]
|
@ -451,6 +451,7 @@ class Address(InvenTree.models.InvenTreeModel):
|
||||
class ManufacturerPart(
|
||||
InvenTree.models.InvenTreeAttachmentMixin,
|
||||
InvenTree.models.InvenTreeBarcodeMixin,
|
||||
InvenTree.models.InvenTreeNotesMixin,
|
||||
InvenTree.models.InvenTreeMetadataModel,
|
||||
):
|
||||
"""Represents a unique part as provided by a Manufacturer Each ManufacturerPart is identified by a MPN (Manufacturer Part Number) Each ManufacturerPart is also linked to a Part object. A Part may be available from multiple manufacturers.
|
||||
@ -624,6 +625,7 @@ class SupplierPartManager(models.Manager):
|
||||
class SupplierPart(
|
||||
InvenTree.models.MetadataMixin,
|
||||
InvenTree.models.InvenTreeBarcodeMixin,
|
||||
InvenTree.models.InvenTreeNotesMixin,
|
||||
common.models.MetaMixin,
|
||||
InvenTree.models.InvenTreeModel,
|
||||
):
|
||||
|
@ -213,7 +213,7 @@ class ContactSerializer(DataImportExportSerializerMixin, InvenTreeModelSerialize
|
||||
|
||||
@register_importer()
|
||||
class ManufacturerPartSerializer(
|
||||
DataImportExportSerializerMixin, InvenTreeTagModelSerializer
|
||||
DataImportExportSerializerMixin, InvenTreeTagModelSerializer, NotesFieldMixin
|
||||
):
|
||||
"""Serializer for ManufacturerPart object."""
|
||||
|
||||
@ -232,6 +232,7 @@ class ManufacturerPartSerializer(
|
||||
'MPN',
|
||||
'link',
|
||||
'barcode_hash',
|
||||
'notes',
|
||||
'tags',
|
||||
]
|
||||
|
||||
@ -305,7 +306,7 @@ class ManufacturerPartParameterSerializer(
|
||||
|
||||
@register_importer()
|
||||
class SupplierPartSerializer(
|
||||
DataImportExportSerializerMixin, InvenTreeTagModelSerializer
|
||||
DataImportExportSerializerMixin, InvenTreeTagModelSerializer, NotesFieldMixin
|
||||
):
|
||||
"""Serializer for SupplierPart object."""
|
||||
|
||||
@ -340,6 +341,7 @@ class SupplierPartSerializer(
|
||||
'supplier_detail',
|
||||
'url',
|
||||
'updated',
|
||||
'notes',
|
||||
'tags',
|
||||
]
|
||||
|
||||
|
@ -171,11 +171,40 @@ src="{% static 'img/blank_image.png' %}"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='panel panel-hidden' id='panel-manufacturer-part-notes'>
|
||||
<div class='panel-heading'>
|
||||
<div class='d-flex flex-wrap'>
|
||||
<h4>{% trans "Manufacturer Part Notes" %}</h4>
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% include "notes_buttons.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
<textarea id='manufacturer-part-notes'></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock page_content %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
// Load the "notes" tab
|
||||
onPanelLoad('manufacturer-part-notes', function() {
|
||||
|
||||
setupNotesField(
|
||||
'manufacturer-part-notes',
|
||||
'{% url "api-manufacturer-part-detail" part.pk %}',
|
||||
{
|
||||
model_type: "manufacturerpart",
|
||||
model_id: {{ part.pk }},
|
||||
editable: {% js_bool roles.purchase_order.change %},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
onPanelLoad("attachments", function() {
|
||||
loadAttachmentTable('manufacturerpart', {{ part.pk }});
|
||||
});
|
||||
|
@ -8,3 +8,5 @@
|
||||
{% include "sidebar_item.html" with label='supplier-parts' text=text icon="fa-building" %}
|
||||
{% trans "Attachments" as text %}
|
||||
{% include "sidebar_item.html" with label='attachments' text=text icon="fa-paperclip" %}
|
||||
{% trans "Notes" as text %}
|
||||
{% include "sidebar_item.html" with label="manufacturer-part-notes" text=text icon="fa-clipboard" %}
|
||||
|
@ -264,11 +264,40 @@ src="{% static 'img/blank_image.png' %}"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='panel panel-hidden' id='panel-supplier-part-notes'>
|
||||
<div class='panel-heading'>
|
||||
<div class='d-flex flex-wrap'>
|
||||
<h4>{% trans "Supplier Part Notes" %}</h4>
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% include "notes_buttons.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
<textarea id='supplier-part-notes'></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock page_content %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
// Load the "notes" tab
|
||||
onPanelLoad('supplier-part-notes', function() {
|
||||
|
||||
setupNotesField(
|
||||
'supplier-part-notes',
|
||||
'{% url "api-supplier-part-detail" part.pk %}',
|
||||
{
|
||||
model_type: "supplierpart",
|
||||
model_id: {{ part.pk }},
|
||||
editable: {% js_bool roles.purchase_order.change %},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
{% if barcodes %}
|
||||
|
||||
$("#show-qr-code").click(function() {
|
||||
|
@ -8,3 +8,5 @@
|
||||
{% include "sidebar_item.html" with label='purchase-orders' text=text icon="fa-shopping-cart" %}
|
||||
{% trans "Supplier Part Pricing" as text %}
|
||||
{% include "sidebar_item.html" with label='pricing' text=text icon="fa-dollar-sign" %}
|
||||
{% trans "Notes" as text %}
|
||||
{% include "sidebar_item.html" with label="supplier-part-notes" text=text icon="fa-clipboard" %}
|
||||
|
@ -11,6 +11,8 @@
|
||||
# Note: Database configuration options can also be specified from environmental variables,
|
||||
# with the prefix INVENTREE_DB_
|
||||
# e.g INVENTREE_DB_NAME / INVENTREE_DB_USER / INVENTREE_DB_PASSWORD
|
||||
# Do not change this section if you are using the package - use `inventree config` instead
|
||||
# TO MAINTAINERS: Do not change database strings
|
||||
database:
|
||||
# --- Available options: ---
|
||||
# ENGINE: Database engine. Selection from:
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.14 on 2024-07-16 03:04
|
||||
|
||||
from django.db import migrations, models
|
||||
import importer.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('importer', '0002_dataimportsession_field_overrides'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dataimportsession',
|
||||
name='field_filters',
|
||||
field=models.JSONField(blank=True, null=True, validators=[importer.validators.validate_field_defaults], verbose_name='Field Filters'),
|
||||
),
|
||||
]
|
@ -32,8 +32,9 @@ class DataImportSession(models.Model):
|
||||
data_file: FileField for the data file to import
|
||||
status: IntegerField for the status of the import session
|
||||
user: ForeignKey to the User who initiated the import
|
||||
field_defaults: JSONField for field default values
|
||||
field_overrides: JSONField for field override values
|
||||
field_defaults: JSONField for field default values - provides a backup value for a field
|
||||
field_overrides: JSONField for field override values - used to force a value for a field
|
||||
field_filters: JSONField for field filter values - optional field API filters
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@ -101,6 +102,13 @@ class DataImportSession(models.Model):
|
||||
validators=[importer.validators.validate_field_defaults],
|
||||
)
|
||||
|
||||
field_filters = models.JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Field Filters'),
|
||||
validators=[importer.validators.validate_field_defaults],
|
||||
)
|
||||
|
||||
@property
|
||||
def field_mapping(self):
|
||||
"""Construct a dict of field mappings for this import session.
|
||||
|
@ -50,6 +50,7 @@ class DataImportSessionSerializer(InvenTreeModelSerializer):
|
||||
'column_mappings',
|
||||
'field_defaults',
|
||||
'field_overrides',
|
||||
'field_filters',
|
||||
'row_count',
|
||||
'completed_row_count',
|
||||
]
|
||||
@ -104,6 +105,19 @@ class DataImportSessionSerializer(InvenTreeModelSerializer):
|
||||
|
||||
return overrides
|
||||
|
||||
def validate_field_filters(self, filters):
|
||||
"""De-stringify the field filters."""
|
||||
if filters is None:
|
||||
return None
|
||||
|
||||
if type(filters) is not dict:
|
||||
try:
|
||||
filters = json.loads(str(filters))
|
||||
except:
|
||||
raise ValidationError(_('Invalid field filters'))
|
||||
|
||||
return filters
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Override create method for this serializer.
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -742,6 +742,14 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
# Extract optional notes field
|
||||
notes = kwargs.get('notes', '')
|
||||
|
||||
# Extract optional packaging field
|
||||
packaging = kwargs.get('packaging', None)
|
||||
|
||||
if not packaging:
|
||||
# Default to the packaging field for the linked supplier part
|
||||
if line.part:
|
||||
packaging = line.part.packaging
|
||||
|
||||
# Extract optional barcode field
|
||||
barcode = kwargs.get('barcode', None)
|
||||
|
||||
@ -791,6 +799,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
purchase_order=self,
|
||||
status=status,
|
||||
batch=batch_code,
|
||||
packaging=packaging,
|
||||
serial=sn,
|
||||
purchase_price=unit_purchase_price,
|
||||
)
|
||||
|
@ -373,13 +373,13 @@ class PurchaseOrderLineItemSerializer(
|
||||
|
||||
fields = [
|
||||
'pk',
|
||||
'part',
|
||||
'quantity',
|
||||
'reference',
|
||||
'notes',
|
||||
'order',
|
||||
'order_detail',
|
||||
'overdue',
|
||||
'part',
|
||||
'part_detail',
|
||||
'supplier_part_detail',
|
||||
'received',
|
||||
@ -454,6 +454,14 @@ class PurchaseOrderLineItemSerializer(
|
||||
|
||||
return queryset
|
||||
|
||||
part = serializers.PrimaryKeyRelatedField(
|
||||
queryset=part_models.SupplierPart.objects.all(),
|
||||
many=False,
|
||||
required=True,
|
||||
allow_null=True,
|
||||
label=_('Supplier Part'),
|
||||
)
|
||||
|
||||
quantity = serializers.FloatField(min_value=0, required=True)
|
||||
|
||||
def validate_quantity(self, quantity):
|
||||
@ -588,7 +596,10 @@ class PurchaseOrderLineItemReceiveSerializer(serializers.Serializer):
|
||||
'location',
|
||||
'quantity',
|
||||
'status',
|
||||
'batch_code' 'serial_numbers',
|
||||
'batch_code',
|
||||
'serial_numbers',
|
||||
'packaging',
|
||||
'note',
|
||||
]
|
||||
|
||||
line_item = serializers.PrimaryKeyRelatedField(
|
||||
@ -646,6 +657,22 @@ class PurchaseOrderLineItemReceiveSerializer(serializers.Serializer):
|
||||
choices=StockStatus.items(), default=StockStatus.OK.value, label=_('Status')
|
||||
)
|
||||
|
||||
packaging = serializers.CharField(
|
||||
label=_('Packaging'),
|
||||
help_text=_('Override packaging information for incoming stock items'),
|
||||
required=False,
|
||||
default='',
|
||||
allow_blank=True,
|
||||
)
|
||||
|
||||
note = serializers.CharField(
|
||||
label=_('Note'),
|
||||
help_text=_('Additional note for incoming stock items'),
|
||||
required=False,
|
||||
default='',
|
||||
allow_blank=True,
|
||||
)
|
||||
|
||||
barcode = serializers.CharField(
|
||||
label=_('Barcode'),
|
||||
help_text=_('Scanned barcode'),
|
||||
@ -798,7 +825,9 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer):
|
||||
status=item['status'],
|
||||
barcode=item.get('barcode', ''),
|
||||
batch_code=item.get('batch_code', ''),
|
||||
packaging=item.get('packaging', ''),
|
||||
serials=item.get('serials', None),
|
||||
notes=item.get('note', None),
|
||||
)
|
||||
except (ValidationError, DjangoValidationError) as exc:
|
||||
# Catch model errors and re-throw as DRF errors
|
||||
|
@ -1137,6 +1137,56 @@ class PurchaseOrderReceiveTest(OrderTest):
|
||||
self.assertEqual(item.quantity, 10)
|
||||
self.assertEqual(item.batch, 'B-xyz-789')
|
||||
|
||||
def test_packaging(self):
|
||||
"""Test that we can supply a 'packaging' value when receiving items."""
|
||||
line_1 = models.PurchaseOrderLineItem.objects.get(pk=1)
|
||||
line_2 = models.PurchaseOrderLineItem.objects.get(pk=2)
|
||||
|
||||
line_1.part.packaging = 'Reel'
|
||||
line_1.part.save()
|
||||
|
||||
line_2.part.packaging = 'Tube'
|
||||
line_2.part.save()
|
||||
|
||||
# Receive items without packaging data
|
||||
data = {
|
||||
'items': [
|
||||
{'line_item': line_1.pk, 'quantity': 1},
|
||||
{'line_item': line_2.pk, 'quantity': 1},
|
||||
],
|
||||
'location': 1,
|
||||
}
|
||||
|
||||
n = StockItem.objects.count()
|
||||
|
||||
self.post(self.url, data, expected_code=201)
|
||||
|
||||
item_1 = StockItem.objects.filter(supplier_part=line_1.part).first()
|
||||
self.assertEqual(item_1.packaging, 'Reel')
|
||||
|
||||
item_2 = StockItem.objects.filter(supplier_part=line_2.part).first()
|
||||
self.assertEqual(item_2.packaging, 'Tube')
|
||||
|
||||
# Receive items and override packaging data
|
||||
data = {
|
||||
'items': [
|
||||
{'line_item': line_1.pk, 'quantity': 1, 'packaging': 'Bag'},
|
||||
{'line_item': line_2.pk, 'quantity': 1, 'packaging': 'Box'},
|
||||
],
|
||||
'location': 1,
|
||||
}
|
||||
|
||||
self.post(self.url, data, expected_code=201)
|
||||
|
||||
item_1 = StockItem.objects.filter(supplier_part=line_1.part).last()
|
||||
self.assertEqual(item_1.packaging, 'Bag')
|
||||
|
||||
item_2 = StockItem.objects.filter(supplier_part=line_2.part).last()
|
||||
self.assertEqual(item_2.packaging, 'Box')
|
||||
|
||||
# Check that the expected number of stock items has been created
|
||||
self.assertEqual(n + 4, StockItem.objects.count())
|
||||
|
||||
|
||||
class SalesOrderTest(OrderTest):
|
||||
"""Tests for the SalesOrder API."""
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""API for the plugin app."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.urls import include, path, re_path
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -266,7 +268,9 @@ class PluginSettingList(ListAPI):
|
||||
filterset_fields = ['plugin__active', 'plugin__key']
|
||||
|
||||
|
||||
def check_plugin(plugin_slug: str, plugin_pk: int) -> InvenTreePlugin:
|
||||
def check_plugin(
|
||||
plugin_slug: Optional[str], plugin_pk: Optional[int]
|
||||
) -> InvenTreePlugin:
|
||||
"""Check that a plugin for the provided slug exists and get the config.
|
||||
|
||||
Args:
|
||||
@ -286,16 +290,16 @@ def check_plugin(plugin_slug: str, plugin_pk: int) -> InvenTreePlugin:
|
||||
raise NotFound(detail='Plugin not specified')
|
||||
|
||||
# Define filter
|
||||
filter = {}
|
||||
filters = {}
|
||||
if plugin_slug:
|
||||
filter['key'] = plugin_slug
|
||||
filters['key'] = plugin_slug
|
||||
elif plugin_pk:
|
||||
filter['pk'] = plugin_pk
|
||||
filters['pk'] = plugin_pk
|
||||
ref = plugin_slug or plugin_pk
|
||||
|
||||
# Check that the 'plugin' specified is valid
|
||||
try:
|
||||
plugin_cgf = PluginConfig.objects.filter(**filter).first()
|
||||
plugin_cgf = PluginConfig.objects.filter(**filters).first()
|
||||
except PluginConfig.DoesNotExist:
|
||||
raise NotFound(detail=f"Plugin '{ref}' not installed")
|
||||
|
||||
|
@ -39,7 +39,7 @@ def qrcode(data, **kwargs):
|
||||
fill_color = kwargs.pop('fill_color', 'black')
|
||||
back_color = kwargs.pop('back_color', 'white')
|
||||
|
||||
format = kwargs.pop('format', 'PNG')
|
||||
img_format = kwargs.pop('format', 'PNG')
|
||||
|
||||
params.update(**kwargs)
|
||||
|
||||
@ -51,7 +51,7 @@ def qrcode(data, **kwargs):
|
||||
qri = qr.make_image(fill_color=fill_color, back_color=back_color)
|
||||
|
||||
# Render to byte-encoded image
|
||||
return image_data(qri, fmt=format)
|
||||
return image_data(qri, fmt=img_format)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
@ -59,7 +59,7 @@ def barcode(data, barcode_class='code128', **kwargs):
|
||||
"""Render a barcode."""
|
||||
constructor = python_barcode.get_barcode_class(barcode_class)
|
||||
|
||||
format = kwargs.pop('format', 'PNG')
|
||||
img_format = kwargs.pop('format', 'PNG')
|
||||
|
||||
data = str(data).zfill(constructor.digits)
|
||||
|
||||
@ -70,4 +70,4 @@ def barcode(data, barcode_class='code128', **kwargs):
|
||||
image = barcode_image.render(writer_options=kwargs)
|
||||
|
||||
# Render to byte-encoded image
|
||||
return image_data(image, fmt=format)
|
||||
return image_data(image, fmt=img_format)
|
||||
|
@ -170,6 +170,18 @@ def uploaded_image(
|
||||
width = kwargs.get('width', None)
|
||||
height = kwargs.get('height', None)
|
||||
|
||||
if width is not None:
|
||||
try:
|
||||
width = int(width)
|
||||
except ValueError:
|
||||
width = None
|
||||
|
||||
if height is not None:
|
||||
try:
|
||||
height = int(height)
|
||||
except ValueError:
|
||||
height = None
|
||||
|
||||
if width is not None and height is not None:
|
||||
# Resize the image, width *and* height are provided
|
||||
img = img.resize((width, height))
|
||||
@ -185,10 +197,12 @@ def uploaded_image(
|
||||
img = img.resize((wsize, height))
|
||||
|
||||
# Optionally rotate the image
|
||||
rotate = kwargs.get('rotate', None)
|
||||
|
||||
if rotate is not None:
|
||||
img = img.rotate(rotate)
|
||||
if rotate := kwargs.get('rotate', None):
|
||||
try:
|
||||
rotate = int(rotate)
|
||||
img = img.rotate(rotate)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Return a base-64 encoded image
|
||||
img_data = report.helpers.encode_image_base64(img)
|
||||
|
@ -163,7 +163,13 @@ function generateTreeStructure(data, options) {
|
||||
const nodes = {};
|
||||
const roots = [];
|
||||
|
||||
for (let node of data) {
|
||||
if (!data || !Array.isArray(data) || data.length == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (let ii = 0; ii < data.length; ii++) {
|
||||
let node = data[ii];
|
||||
|
||||
nodes[node.pk] = node;
|
||||
node.selectable = false;
|
||||
|
||||
@ -174,11 +180,15 @@ function generateTreeStructure(data, options) {
|
||||
|
||||
if (options.processNode) {
|
||||
node = options.processNode(node);
|
||||
data[ii] = node;
|
||||
}
|
||||
}
|
||||
|
||||
for (let node of data) {
|
||||
if (node.parent != null) {
|
||||
for (let ii = 0; ii < data.length; ii++) {
|
||||
|
||||
let node = data[ii];
|
||||
|
||||
if (!!node.parent) {
|
||||
if (nodes[node.parent].nodes) {
|
||||
nodes[node.parent].nodes.push(node);
|
||||
} else {
|
||||
@ -186,7 +196,7 @@ function generateTreeStructure(data, options) {
|
||||
}
|
||||
|
||||
if (node.state.expanded) {
|
||||
while (node.parent != null) {
|
||||
while (!!node.parent) {
|
||||
nodes[node.parent].state.expanded = true;
|
||||
node = nodes[node.parent];
|
||||
}
|
||||
|
@ -298,7 +298,8 @@ function constructDeleteForm(fields, options) {
|
||||
* - closeText: Text for the "close" button
|
||||
* - fields: list of fields to display, with the following options
|
||||
* - filters: API query filters
|
||||
* - onEdit: callback or array of callbacks which get fired when field is edited
|
||||
* - onEdit: callback or array of callbacks which get fired when field is edited - does not get triggered until the field loses focus, ref: https://api.jquery.com/change/
|
||||
* - onInput: callback or array of callbacks which get fired when an input is detected in the field
|
||||
* - secondary: Define a secondary modal form for this field
|
||||
* - label: Specify custom label
|
||||
* - help_text: Specify custom help_text
|
||||
@ -1501,8 +1502,23 @@ function handleFormErrors(errors, fields={}, options={}) {
|
||||
|
||||
for (var field_name in errors) {
|
||||
|
||||
var field = fields[field_name] || {};
|
||||
var field_errors = errors[field_name];
|
||||
let field = fields[field_name] || null;
|
||||
let field_errors = errors[field_name];
|
||||
|
||||
// No matching field - append to non_field_errors
|
||||
if (!field || field.hidden) {
|
||||
|
||||
if (Array.isArray(field_errors)) {
|
||||
field_errors.forEach((err) => {
|
||||
non_field_errors.append(`<div class='alert alert-block alert-danger'>${err}</div>`);
|
||||
});
|
||||
} else {
|
||||
non_field_errors.append(`<div class='alert alert-block alert-danger'>${field_errors.toString()}</div>`);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// for nested objects with children and dependent fields with a child defined, extract nested errors
|
||||
if (((field.type == 'nested object') && ('children' in field)) || ((field.type == 'dependent field') && ('child' in field))) {
|
||||
@ -1646,6 +1662,23 @@ function addFieldCallback(name, field, options) {
|
||||
});
|
||||
}
|
||||
|
||||
if(field.onInput){
|
||||
|
||||
el.on('input', function(){
|
||||
var value = getFormFieldValue(name, field, options);
|
||||
let onInputHandlers = field.onInput;
|
||||
|
||||
if (!Array.isArray(onInputHandlers)) {
|
||||
onInputHandlers = [onInputHandlers];
|
||||
}
|
||||
|
||||
for (const onInput of onInputHandlers) {
|
||||
onInput(value, name, field, options);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// attach field callback for nested fields
|
||||
if(field.type === "nested object") {
|
||||
for (const [c_name, c_field] of Object.entries(field.children)) {
|
||||
|
@ -343,7 +343,7 @@ function poLineItemFields(options={}) {
|
||||
reference: {},
|
||||
purchase_price: {
|
||||
icon: 'fa-dollar-sign',
|
||||
onEdit: function(value, name, field, opts) {
|
||||
onInput: function(value, name, field, opts) {
|
||||
updateFieldValue('auto_pricing', value === '', {}, opts);
|
||||
}
|
||||
},
|
||||
@ -1136,7 +1136,7 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
);
|
||||
|
||||
// Hidden barcode input
|
||||
var barcode_input = constructField(
|
||||
const barcode_input = constructField(
|
||||
`items_barcode_${pk}`,
|
||||
{
|
||||
type: 'string',
|
||||
@ -1145,7 +1145,8 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
}
|
||||
);
|
||||
|
||||
var sn_input = constructField(
|
||||
// Hidden serial number input
|
||||
const sn_input = constructField(
|
||||
`items_serial_numbers_${pk}`,
|
||||
{
|
||||
type: 'string',
|
||||
@ -1159,6 +1160,37 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
}
|
||||
);
|
||||
|
||||
// Hidden packaging input
|
||||
const packaging_input = constructField(
|
||||
`items_packaging_${pk}`,
|
||||
{
|
||||
type: 'string',
|
||||
required: false,
|
||||
label: '{% trans "Packaging" %}',
|
||||
help_text: '{% trans "Specify packaging for incoming stock items" %}',
|
||||
icon: 'fa-boxes',
|
||||
value: line_item.supplier_part_detail.packaging,
|
||||
},
|
||||
{
|
||||
hideLabels: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Hidden note input
|
||||
const note_input = constructField(
|
||||
`items_note_${pk}`,
|
||||
{
|
||||
type: 'string',
|
||||
required: false,
|
||||
label: '{% trans "Note" %}',
|
||||
icon: 'fa-sticky-note',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
hideLabels: true,
|
||||
}
|
||||
);
|
||||
|
||||
var quantity_input_group = `${quantity_input}${pack_size_div}`;
|
||||
|
||||
// Construct list of StockItem status codes
|
||||
@ -1220,6 +1252,16 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
}
|
||||
);
|
||||
|
||||
buttons += makeIconButton(
|
||||
'fa-boxes',
|
||||
'button-row-add-packaging',
|
||||
pk,
|
||||
'{% trans "Specify packaging" %}',
|
||||
{
|
||||
collapseTarget: `row-packaging-${pk}`
|
||||
}
|
||||
);
|
||||
|
||||
if (line_item.part_detail.trackable) {
|
||||
buttons += makeIconButton(
|
||||
'fa-hashtag',
|
||||
@ -1232,6 +1274,16 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
);
|
||||
}
|
||||
|
||||
buttons += makeIconButton(
|
||||
'fa-sticky-note',
|
||||
'button-row-add-note',
|
||||
pk,
|
||||
'{% trans "Add note" %}',
|
||||
{
|
||||
collapseTarget: `row-note-${pk}`,
|
||||
}
|
||||
);
|
||||
|
||||
if (line_items.length > 1) {
|
||||
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
}
|
||||
@ -1275,12 +1327,23 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
<td colspan='2'>${batch_input}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr id='row-packaging-${pk}' class='collapse'>
|
||||
<td colspan='2'></td>
|
||||
<th>{% trans "Packaging" %}</th>
|
||||
<td colspan='2'>${packaging_input}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr id='row-serials-${pk}' class='collapse'>
|
||||
<td colspan='2'></td>
|
||||
<th>{% trans "Serials" %}</th>
|
||||
<td colspan=2'>${sn_input}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr id='row-note-${pk}' class='collapse'>
|
||||
<td colspan='2'></td>
|
||||
<th>{% trans "Note" %}</th>
|
||||
<td colspan='2'>${note_input}</td>
|
||||
<td></td>
|
||||
`;
|
||||
|
||||
return html;
|
||||
@ -1472,6 +1535,14 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
line.batch_code = getFormFieldValue(`items_batch_code_${pk}`);
|
||||
}
|
||||
|
||||
if (getFormFieldElement(`items_packaging_${pk}`).exists()) {
|
||||
line.packaging = getFormFieldValue(`items_packaging_${pk}`);
|
||||
}
|
||||
|
||||
if (getFormFieldElement(`items_note_${pk}`).exists()) {
|
||||
line.note = getFormFieldValue(`items_note_${pk}`);
|
||||
}
|
||||
|
||||
if (getFormFieldElement(`items_serial_numbers_${pk}`).exists()) {
|
||||
line.serial_numbers = getFormFieldValue(`items_serial_numbers_${pk}`);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
formatDecimal,
|
||||
formatPriceRange,
|
||||
getCurrencyConversionRates,
|
||||
getFormFieldElement,
|
||||
getFormFieldValue,
|
||||
getTableData,
|
||||
global_settings,
|
||||
@ -1010,14 +1011,16 @@ function mergeStockItems(items, options={}) {
|
||||
*/
|
||||
function adjustStock(action, items, options={}) {
|
||||
|
||||
var formTitle = 'Form Title Here';
|
||||
var actionTitle = null;
|
||||
let formTitle = 'Form Title Here';
|
||||
let actionTitle = null;
|
||||
|
||||
const allowExtraFields = action == 'move';
|
||||
|
||||
// API url
|
||||
var url = null;
|
||||
|
||||
var specifyLocation = false;
|
||||
var allowSerializedStock = false;
|
||||
let specifyLocation = false;
|
||||
let allowSerializedStock = false;
|
||||
|
||||
switch (action) {
|
||||
case 'move':
|
||||
@ -1069,7 +1072,7 @@ function adjustStock(action, items, options={}) {
|
||||
|
||||
for (var idx = 0; idx < items.length; idx++) {
|
||||
|
||||
var item = items[idx];
|
||||
const item = items[idx];
|
||||
|
||||
if ((item.serial != null) && (item.serial != '') && !allowSerializedStock) {
|
||||
continue;
|
||||
@ -1112,7 +1115,6 @@ function adjustStock(action, items, options={}) {
|
||||
|
||||
let quantityString = '';
|
||||
|
||||
|
||||
var location = locationDetail(item, false);
|
||||
|
||||
if (item.location_detail) {
|
||||
@ -1152,11 +1154,68 @@ function adjustStock(action, items, options={}) {
|
||||
);
|
||||
}
|
||||
|
||||
let buttons = wrapButtons(makeRemoveButton(
|
||||
let buttons = '';
|
||||
|
||||
if (allowExtraFields) {
|
||||
buttons += makeIconButton(
|
||||
'fa-layer-group',
|
||||
'button-row-add-batch',
|
||||
pk,
|
||||
'{% trans "Adjust batch code" %}',
|
||||
{
|
||||
collapseTarget: `row-batch-${pk}`
|
||||
}
|
||||
);
|
||||
|
||||
buttons += makeIconButton(
|
||||
'fa-boxes',
|
||||
'button-row-add-packaging',
|
||||
pk,
|
||||
'{% trans "Adjust packaging" %}',
|
||||
{
|
||||
collapseTarget: `row-packaging-${pk}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
buttons += makeRemoveButton(
|
||||
'button-stock-item-remove',
|
||||
pk,
|
||||
'{% trans "Remove stock item" %}',
|
||||
));
|
||||
);
|
||||
|
||||
buttons = wrapButtons(buttons);
|
||||
|
||||
// Add in options for "batch code" and "serial numbers"
|
||||
const batch_input = constructField(
|
||||
`items_batch_code_${pk}`,
|
||||
{
|
||||
type: 'string',
|
||||
required: false,
|
||||
label: '{% trans "Batch Code" %}',
|
||||
help_text: '{% trans "Enter batch code for incoming stock items" %}',
|
||||
icon: 'fa-layer-group',
|
||||
value: item.batch,
|
||||
},
|
||||
{
|
||||
hideLabels: true,
|
||||
}
|
||||
);
|
||||
|
||||
const packaging_input = constructField(
|
||||
`items_packaging_${pk}`,
|
||||
{
|
||||
type: 'string',
|
||||
required: false,
|
||||
label: '{% trans "Packaging" %}',
|
||||
help_text: '{% trans "Specify packaging for incoming stock items" %}',
|
||||
icon: 'fa-boxes',
|
||||
value: item.packaging,
|
||||
},
|
||||
{
|
||||
hideLabels: true,
|
||||
}
|
||||
);
|
||||
|
||||
html += `
|
||||
<tr id='stock_item_${pk}' class='stock-item-row'>
|
||||
@ -1170,6 +1229,19 @@ function adjustStock(action, items, options={}) {
|
||||
</div>
|
||||
</td>
|
||||
<td id='buttons_${pk}'>${buttons}</td>
|
||||
</tr>
|
||||
<!-- Hidden row for extra data entry -->
|
||||
<tr id='row-batch-${pk}' class='collapse'>
|
||||
<td colspan='2'></td>
|
||||
<th>{% trans "Batch" %}</th>
|
||||
<td colspan='2'>${batch_input}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr id='row-packaging-${pk}' class='collapse'>
|
||||
<td colspan='2'></td>
|
||||
<th>{% trans "Packaging" %}</th>
|
||||
<td colspan='2'>${packaging_input}</td>
|
||||
<td></td>
|
||||
</tr>`;
|
||||
|
||||
itemCount += 1;
|
||||
@ -1266,21 +1338,30 @@ function adjustStock(action, items, options={}) {
|
||||
var item_pk_values = [];
|
||||
|
||||
items.forEach(function(item) {
|
||||
var pk = item.pk;
|
||||
let pk = item.pk;
|
||||
|
||||
// Does the row exist in the form?
|
||||
var row = $(opts.modal).find(`#stock_item_${pk}`);
|
||||
let row = $(opts.modal).find(`#stock_item_${pk}`);
|
||||
|
||||
if (row.exists()) {
|
||||
|
||||
item_pk_values.push(pk);
|
||||
|
||||
var quantity = getFormFieldValue(`items_quantity_${pk}`, {}, opts);
|
||||
|
||||
data.items.push({
|
||||
let quantity = getFormFieldValue(`items_quantity_${pk}`, {}, opts);
|
||||
let line = {
|
||||
pk: pk,
|
||||
quantity: quantity,
|
||||
});
|
||||
quantity: quantity
|
||||
};
|
||||
|
||||
if (getFormFieldElement(`items_batch_code_${pk}`).exists()) {
|
||||
line.batch = getFormFieldValue(`items_batch_code_${pk}`);
|
||||
}
|
||||
|
||||
if (getFormFieldElement(`items_packaging_${pk}`).exists()) {
|
||||
line.packaging = getFormFieldValue(`items_packaging_${pk}`);
|
||||
}
|
||||
|
||||
data.items.push(line);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile src/backend/requirements-dev.in -o src/backend/requirements-dev.txt
|
||||
# uv pip compile src/backend/requirements-dev.in -o src/backend/requirements-dev.txt --no-strip-extras --generate-hashes
|
||||
asgiref==3.8.1 \
|
||||
--hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \
|
||||
--hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590
|
||||
# via django
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django
|
||||
build==1.2.1 \
|
||||
--hash=sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d \
|
||||
--hash=sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4
|
||||
@ -61,7 +63,9 @@ cffi==1.16.0 \
|
||||
--hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \
|
||||
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
|
||||
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
|
||||
# via cryptography
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# cryptography
|
||||
cfgv==3.4.0 \
|
||||
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
||||
--hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560
|
||||
@ -157,7 +161,9 @@ charset-normalizer==3.3.2 \
|
||||
--hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \
|
||||
--hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \
|
||||
--hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561
|
||||
# via pdfminer-six
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# pdfminer-six
|
||||
click==8.1.7 \
|
||||
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
|
||||
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
|
||||
@ -215,6 +221,7 @@ coverage[toml]==7.5.4 \
|
||||
--hash=sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633 \
|
||||
--hash=sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9 \
|
||||
--hash=sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c
|
||||
# via -r src/backend/requirements-dev.in
|
||||
cryptography==42.0.8 \
|
||||
--hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \
|
||||
--hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \
|
||||
@ -248,7 +255,9 @@ cryptography==42.0.8 \
|
||||
--hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \
|
||||
--hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \
|
||||
--hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e
|
||||
# via pdfminer-six
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# pdfminer-six
|
||||
distlib==0.3.8 \
|
||||
--hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \
|
||||
--hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64
|
||||
@ -257,18 +266,23 @@ django==4.2.14 \
|
||||
--hash=sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240 \
|
||||
--hash=sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django-admin-shell
|
||||
# django-slowtests
|
||||
django-admin-shell==2.0.1 \
|
||||
--hash=sha256:334a651e53ae4f59d0d279d7ede7dc5ed7a7733d4d093765b447dca5274c7b30 \
|
||||
--hash=sha256:b129e282ebd581c2099c0504edf081259728b3a504b40c5784d0457b8cb41470
|
||||
# via -r src/backend/requirements-dev.in
|
||||
django-querycount==0.8.3 \
|
||||
--hash=sha256:0782484e8a1bd29498fa0195a67106e47cdcc98fafe80cebb1991964077cb694
|
||||
# via -r src/backend/requirements-dev.in
|
||||
django-slowtests==1.1.1 \
|
||||
--hash=sha256:3c6936d420c9df444ac03625b41d97de043c662bbde61fbcd33e4cd407d0c247
|
||||
# via -r src/backend/requirements-dev.in
|
||||
django-test-migrations==1.3.0 \
|
||||
--hash=sha256:b42edb1af481e08c9d91c95aa9b373e76e905a931bc19c086ec00a6cb936876e \
|
||||
--hash=sha256:b52b29475f9a1bcaa4512f2ec8fad08b5f470cf1cf522e86b7d950252fb6fbf1
|
||||
# via -r src/backend/requirements-dev.in
|
||||
filelock==3.15.4 \
|
||||
--hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \
|
||||
--hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7
|
||||
@ -280,10 +294,13 @@ identify==2.5.36 \
|
||||
importlib-metadata==7.1.0 \
|
||||
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \
|
||||
--hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2
|
||||
# via build
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# build
|
||||
isort==5.13.2 \
|
||||
--hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \
|
||||
--hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6
|
||||
# via -r src/backend/requirements-dev.in
|
||||
nodeenv==1.9.1 \
|
||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||
@ -291,10 +308,13 @@ nodeenv==1.9.1 \
|
||||
packaging==24.1 \
|
||||
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
|
||||
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
|
||||
# via build
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# build
|
||||
pdfminer-six==20231228 \
|
||||
--hash=sha256:6004da3ad1a7a4d45930cb950393df89b068e73be365a6ff64a838d37bcb08c4 \
|
||||
--hash=sha256:e8d3c3310e6fbc1fe414090123ab01351634b4ecb021232206c4c9a8ca3e3b8f
|
||||
# via -r src/backend/requirements-dev.in
|
||||
pip==24.1 \
|
||||
--hash=sha256:a775837439bf5da2c1a0c2fa43d5744854497c689ddbd9344cf3ea6d00598540 \
|
||||
--hash=sha256:bdae551038c0ce6a83030b4aedef27fc95f0daa683593fea22fa05e55ed8e317
|
||||
@ -302,6 +322,7 @@ pip==24.1 \
|
||||
pip-tools==7.4.1 \
|
||||
--hash=sha256:4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9 \
|
||||
--hash=sha256:864826f5073864450e24dbeeb85ce3920cdfb09848a3d69ebf537b521f14bcc9
|
||||
# via -r src/backend/requirements-dev.in
|
||||
platformdirs==4.2.2 \
|
||||
--hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \
|
||||
--hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3
|
||||
@ -309,10 +330,13 @@ platformdirs==4.2.2 \
|
||||
pre-commit==3.7.1 \
|
||||
--hash=sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a \
|
||||
--hash=sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5
|
||||
# via -r src/backend/requirements-dev.in
|
||||
pycparser==2.22 \
|
||||
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
|
||||
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
|
||||
# via cffi
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# cffi
|
||||
pyproject-hooks==1.1.0 \
|
||||
--hash=sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965 \
|
||||
--hash=sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2
|
||||
@ -371,15 +395,22 @@ pyyaml==6.0.1 \
|
||||
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
|
||||
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
|
||||
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
|
||||
# via pre-commit
|
||||
setuptools==70.3.0 \
|
||||
--hash=sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5 \
|
||||
--hash=sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc
|
||||
# via pip-tools
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# pre-commit
|
||||
setuptools==71.0.3 \
|
||||
--hash=sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d \
|
||||
--hash=sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r src/backend/requirements-dev.in
|
||||
# pip-tools
|
||||
sqlparse==0.5.0 \
|
||||
--hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \
|
||||
--hash=sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663
|
||||
# via django
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# django
|
||||
tomli==2.0.1 \
|
||||
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
|
||||
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
|
||||
@ -391,6 +422,7 @@ typing-extensions==4.12.2 \
|
||||
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# asgiref
|
||||
# django-test-migrations
|
||||
virtualenv==20.26.3 \
|
||||
@ -404,4 +436,6 @@ wheel==0.43.0 \
|
||||
zipp==3.19.2 \
|
||||
--hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \
|
||||
--hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c
|
||||
# via importlib-metadata
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# importlib-metadata
|
||||
|
@ -1,5 +1,5 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile src/backend/requirements.in -o src/backend/requirements.txt
|
||||
# uv pip compile src/backend/requirements.in -o src/backend/requirements.txt --no-strip-extras --generate-hashes
|
||||
asgiref==3.8.1 \
|
||||
--hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \
|
||||
--hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590
|
||||
@ -266,6 +266,7 @@ charset-normalizer==3.3.2 \
|
||||
coreapi==2.3.3 \
|
||||
--hash=sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb \
|
||||
--hash=sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3
|
||||
# via -r src/backend/requirements.in
|
||||
coreschema==0.0.4 \
|
||||
--hash=sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f \
|
||||
--hash=sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607
|
||||
@ -303,7 +304,9 @@ cryptography==42.0.8 \
|
||||
--hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \
|
||||
--hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \
|
||||
--hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e
|
||||
# via djangorestframework-simplejwt
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# djangorestframework-simplejwt
|
||||
cssselect2==0.7.0 \
|
||||
--hash=sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a \
|
||||
--hash=sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969
|
||||
@ -327,10 +330,12 @@ diff-match-patch==20230430 \
|
||||
# via django-import-export
|
||||
dj-rest-auth==6.0.0 \
|
||||
--hash=sha256:760b45f3a07cd6182e6a20fe07d0c55230c5f950167df724d7914d0dd8c50133
|
||||
# via -r src/backend/requirements.in
|
||||
django==4.2.14 \
|
||||
--hash=sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240 \
|
||||
--hash=sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# dj-rest-auth
|
||||
# django-allauth
|
||||
# django-allauth-2fa
|
||||
@ -363,40 +368,53 @@ django==4.2.14 \
|
||||
# drf-spectacular
|
||||
django-allauth[openid, saml]==0.63.3 \
|
||||
--hash=sha256:2374164c468a309e6badf70bc3405136df6036f24a20a13387f2a063066bdaa9
|
||||
# via django-allauth-2fa
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-allauth-2fa
|
||||
django-allauth-2fa==0.11.1 \
|
||||
--hash=sha256:02ffdf1025836f072c2f6ec0964494589cf1d52362f663f9ff6d9ca61a7b6962 \
|
||||
--hash=sha256:2f2d61dd488f66ad45e59780b061f5abe96caea9c3466e3ee4ea50ea1faebef6
|
||||
# via -r src/backend/requirements.in
|
||||
django-cleanup==8.1.0 \
|
||||
--hash=sha256:70df905076a44e7a111b31198199af633dee08876e199e6dce36ca8dd6b8b10f \
|
||||
--hash=sha256:7903873ea73b3f7e61e055340d27dba49b70634f60c87a573ad748e172836458
|
||||
# via -r src/backend/requirements.in
|
||||
django-cors-headers==4.4.0 \
|
||||
--hash=sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6 \
|
||||
--hash=sha256:92cf4633e22af67a230a1456cb1b7a02bb213d6536d2dcb2a4a24092ea9cebc2
|
||||
# via -r src/backend/requirements.in
|
||||
django-crispy-forms==1.14.0 \
|
||||
--hash=sha256:35887b8851a931374dd697207a8f56c57a9c5cb9dbf0b9fa54314da5666cea5b \
|
||||
--hash=sha256:bc4d2037f6de602d39c0bc452ac3029d1f5d65e88458872cc4dbc01c3a400604
|
||||
# via -r src/backend/requirements.in
|
||||
django-dbbackup==4.1.0 \
|
||||
--hash=sha256:c411d38d0f8e60ab3254017278c14ebd75d4001b5634fc73be7fbe8a5260583b \
|
||||
--hash=sha256:c539b5246b429a22a8efadbab3719ee6b8eda45c66c4ff6592056c590d51c782
|
||||
# via -r src/backend/requirements.in
|
||||
django-error-report-2==0.4.2 \
|
||||
--hash=sha256:1dd99c497af09b7ea99f5fbaf910501838150a9d5390796ea00e187bc62f6c1b \
|
||||
--hash=sha256:603e1e3b24d01bbfeab6379af948893b2b034031c80fa8b45cf1c4735341c04b
|
||||
# via -r src/backend/requirements.in
|
||||
django-filter==24.2 \
|
||||
--hash=sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e \
|
||||
--hash=sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48
|
||||
# via -r src/backend/requirements.in
|
||||
django-flags==5.0.13 \
|
||||
--hash=sha256:52df74b86d93f5cb402190ad26b68a5ba0f127e9e016189f1a6f2e8ba3c06a42 \
|
||||
--hash=sha256:ff6940cf37e07d6d0c4ac28c5420c8cfc478b62541473dba4aa02d600f7db9fc
|
||||
# via -r src/backend/requirements.in
|
||||
django-formtools==2.5.1 \
|
||||
--hash=sha256:47cb34552c6efca088863d693284d04fc36eaaf350eb21e1a1d935e0df523c93 \
|
||||
--hash=sha256:bce9b64eda52cc1eef6961cc649cf75aacd1a707c2fff08d6c3efcbc8e7e761a
|
||||
# via -r src/backend/requirements.in
|
||||
django-ical==1.9.2 \
|
||||
--hash=sha256:44c9b6fa90d09f25e9ebaa91ed9eb007f079afbc23d6aac909cfc18188a8e90c \
|
||||
--hash=sha256:74a16bca05735f91a00120cad7250f3c3aa292a9f698a6cfdc544a922c11de70
|
||||
# via -r src/backend/requirements.in
|
||||
django-import-export==3.3.9 \
|
||||
--hash=sha256:16797965e93a8001fe812c61e3b71fb858c57c1bd16da195fe276d6de685348e \
|
||||
--hash=sha256:dd6cabc08ed6d1bd37a392e7fb542bd7d196b615c800168f5c69f0f55f49b103
|
||||
# via -r src/backend/requirements.in
|
||||
django-js-asset==2.2.0 \
|
||||
--hash=sha256:0c57a82cae2317e83951d956110ce847f58ff0cdc24e314dbc18b35033917e94 \
|
||||
--hash=sha256:7ef3e858e13d06f10799b56eea62b1e76706f42cf4e709be4e13356bc0ae30d8
|
||||
@ -404,15 +422,19 @@ django-js-asset==2.2.0 \
|
||||
django-maintenance-mode==0.21.1 \
|
||||
--hash=sha256:b79afddb671c59972ae542e4fafbc99117d2d37991843eaaa837e328eed12b1b \
|
||||
--hash=sha256:c02fff0e386b7f8b2ab54479d3a0d336ae34014da22a7a2365ca96d5a2c1db94
|
||||
# via -r src/backend/requirements.in
|
||||
django-markdownify==0.9.5 \
|
||||
--hash=sha256:2c4ae44e386c209453caf5e9ea1b74f64535985d338ad2d5ad5e7089cc94be86 \
|
||||
--hash=sha256:34c34eba4a797282a5c5bd97b13cec84d6a4c0673ad47ce1c1d000d74dd8d4ab
|
||||
# via -r src/backend/requirements.in
|
||||
django-money==3.2.0 \
|
||||
--hash=sha256:2e4174b47993780bf4b61ad3fa0a66ebe140da42fdbe68b628c7ba9788287214 \
|
||||
--hash=sha256:3099f906407175af06b56ef3ff5c250e2fc525ff00f50d42f77b98597e625459
|
||||
# via -r src/backend/requirements.in
|
||||
django-mptt==0.16.0 \
|
||||
--hash=sha256:56c9606bf0b329b5f5afd55dd8bfd073612ea1d5999b10903b09de62bee84c8e \
|
||||
--hash=sha256:8716849ba3318d94e2e100ed0923a05c1ffdf8195f8472b690dbaf737d2af3b5
|
||||
# via -r src/backend/requirements.in
|
||||
django-otp==1.5.0 \
|
||||
--hash=sha256:e7142139f1e9686be5f396669a3d3d61178cd9b3e9de9de5933888668908b46b \
|
||||
--hash=sha256:e88871d2d3b333a86c2cd0cb721be8098d4d6344cb220315a500e5a5c8254295
|
||||
@ -423,9 +445,11 @@ django-picklefield==3.2 \
|
||||
# via django-q2
|
||||
django-q-sentry==0.1.6 \
|
||||
--hash=sha256:9b8b4d7fad253a7d9a47f2c2ab0d9dea83078b7ef45c8849dbb1e4176ef8d050
|
||||
# via -r src/backend/requirements.in
|
||||
django-q2==1.6.2 \
|
||||
--hash=sha256:c2d75552c80b83ca0d8c0b0db7db4f17e9f43ee131a46d0ddd514c5f5fc603cb \
|
||||
--hash=sha256:cd83c16b5791cd99f83a8d106d2447305d73c6c8ed8ec22c7cb954fe0e814284
|
||||
# via -r src/backend/requirements.in
|
||||
django-recurrence==1.11.1 \
|
||||
--hash=sha256:0c65f30872599b5813a9bab6952dada23c55894f28674490a753ada559f14bc5 \
|
||||
--hash=sha256:9c89444e651a78c587f352c5f63eda48ab2f53996347b9fcdff2d248f4fcff70
|
||||
@ -433,41 +457,53 @@ django-recurrence==1.11.1 \
|
||||
django-redis==5.4.0 \
|
||||
--hash=sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42 \
|
||||
--hash=sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b
|
||||
# via -r src/backend/requirements.in
|
||||
django-sesame==3.2.2 \
|
||||
--hash=sha256:523ebd4d04e28c897c262f25b78b6fd8f37e11cdca6e277fdc8bf496bd686cf5 \
|
||||
--hash=sha256:5d753a309166356b6a0d7fc047690943b9e80b4aa7952f1a6400fe6ce60d573c
|
||||
# via -r src/backend/requirements.in
|
||||
django-sql-utils==0.7.0 \
|
||||
--hash=sha256:9371ff28eaf326836a7c52887259123cdd3fbffb7b738e42ae1a21258be0feb6 \
|
||||
--hash=sha256:fefc40c826896b60fcf33e35b6e30b523fc958955a16006438cd3ba6d795a532
|
||||
# via -r src/backend/requirements.in
|
||||
django-sslserver==0.22 \
|
||||
--hash=sha256:c598a363d2ccdc2421c08ddb3d8b0973f80e8e47a3a5b74e4a2896f21c2947c5
|
||||
# via -r src/backend/requirements.in
|
||||
django-stdimage==6.0.2 \
|
||||
--hash=sha256:880ab14828be56b53f711c3afae83c219ddd5d9af00850626736feb48382bf7f \
|
||||
--hash=sha256:9a73f7da48c48074580e2b032d5bdb7164935dbe4b9dc4fb88a7e112f3d521c8
|
||||
# via -r src/backend/requirements.in
|
||||
django-taggit==5.0.1 \
|
||||
--hash=sha256:a0ca8a28b03c4b26c2630fd762cb76ec39b5e41abf727a7b66f897a625c5e647 \
|
||||
--hash=sha256:edcd7db1e0f35c304e082a2f631ddac2e16ef5296029524eb792af7430cab4cc
|
||||
# via -r src/backend/requirements.in
|
||||
django-user-sessions==2.0.0 \
|
||||
--hash=sha256:0965554279f556b47062965609fa08b3ae45bbc581001dbe84b2ea599cc67748 \
|
||||
--hash=sha256:41b8b1ebeb4736065efbc96437c9cfbf491c39e10fd547a76b98f2312e11fa3e
|
||||
# via -r src/backend/requirements.in
|
||||
django-weasyprint==2.3.0 \
|
||||
--hash=sha256:2f849e15bfd6c1b2a58512097b9042eddf3533651d37d2e096cd6f7d8be6442b \
|
||||
--hash=sha256:807cb3b16332123d97c8bbe2ac9c70286103fe353235351803ffd33b67284735
|
||||
# via -r src/backend/requirements.in
|
||||
django-xforwardedfor-middleware==2.0 \
|
||||
--hash=sha256:16fd1cb27f33a5541b6f3e0b43afb1b7334a76f27a1255b69e14ec5c440f0b24
|
||||
# via -r src/backend/requirements.in
|
||||
djangorestframework==3.14.0 \
|
||||
--hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \
|
||||
--hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# dj-rest-auth
|
||||
# djangorestframework-simplejwt
|
||||
# drf-spectacular
|
||||
djangorestframework-simplejwt[crypto]==5.3.1 \
|
||||
--hash=sha256:381bc966aa46913905629d472cd72ad45faa265509764e20ffd440164c88d220 \
|
||||
--hash=sha256:6c4bd37537440bc439564ebf7d6085e74c5411485197073f508ebdfa34bc9fae
|
||||
# via -r src/backend/requirements.in
|
||||
drf-spectacular==0.27.2 \
|
||||
--hash=sha256:a199492f2163c4101055075ebdbb037d59c6e0030692fc83a1a8c0fc65929981 \
|
||||
--hash=sha256:b1c04bf8b2fbbeaf6f59414b4ea448c8787aba4d32f76055c3b13335cf7ec37b
|
||||
# via -r src/backend/requirements.in
|
||||
dulwich==0.22.1 \
|
||||
--hash=sha256:0d72a88c7af8fafa14c8743e8923c8d46bd0b850a0b7f5e34eb49201f1ead88e \
|
||||
--hash=sha256:0ea4c5feedd35e8bde175a9ab91ef6705c3cef5ee209eeb2f67dd0b59ff1825f \
|
||||
@ -516,6 +552,7 @@ dulwich==0.22.1 \
|
||||
--hash=sha256:e90b8a2f24149c5803b733a24f1a016a2943b1f5a9ab2360db545e4638354c35 \
|
||||
--hash=sha256:f9e10678fe0692c5167553981d97cbe342ed055c49016aef10da336e2962b1f2 \
|
||||
--hash=sha256:fd51e77ff1b4ca08bc9b09b85646a3e77f275827b7b30180d76d769ce608e64d
|
||||
# via -r src/backend/requirements.in
|
||||
et-xmlfile==1.1.0 \
|
||||
--hash=sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c \
|
||||
--hash=sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada
|
||||
@ -523,6 +560,7 @@ et-xmlfile==1.1.0 \
|
||||
feedparser==6.0.11 \
|
||||
--hash=sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45 \
|
||||
--hash=sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5
|
||||
# via -r src/backend/requirements.in
|
||||
fonttools[woff]==4.53.0 \
|
||||
--hash=sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d \
|
||||
--hash=sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64 \
|
||||
@ -620,10 +658,13 @@ grpcio==1.64.1 \
|
||||
--hash=sha256:ed6091fa0adcc7e4ff944090cf203a52da35c37a130efa564ded02b7aff63bcd \
|
||||
--hash=sha256:ee73a2f5ca4ba44fa33b4d7d2c71e2c8a9e9f78d53f6507ad68e7d2ad5f64a22 \
|
||||
--hash=sha256:f10193c69fc9d3d726e83bbf0f3d316f1847c3071c8c93d8090cf5f326b14309
|
||||
# via opentelemetry-exporter-otlp-proto-grpc
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
gunicorn==22.0.0 \
|
||||
--hash=sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9 \
|
||||
--hash=sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63
|
||||
# via -r src/backend/requirements.in
|
||||
html5lib==1.1 \
|
||||
--hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \
|
||||
--hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f
|
||||
@ -893,6 +934,7 @@ opentelemetry-api==1.25.0 \
|
||||
--hash=sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737 \
|
||||
--hash=sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
# opentelemetry-instrumentation
|
||||
@ -905,6 +947,7 @@ opentelemetry-api==1.25.0 \
|
||||
opentelemetry-exporter-otlp==1.25.0 \
|
||||
--hash=sha256:ce03199c1680a845f82e12c0a6a8f61036048c07ec7a0bd943142aca8fa6ced0 \
|
||||
--hash=sha256:d67a831757014a3bc3174e4cd629ae1493b7ba8d189e8a007003cacb9f1a6b60
|
||||
# via -r src/backend/requirements.in
|
||||
opentelemetry-exporter-otlp-proto-common==1.25.0 \
|
||||
--hash=sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693 \
|
||||
--hash=sha256:c93f4e30da4eee02bacd1e004eb82ce4da143a2f8e15b987a9f603e0a85407d3
|
||||
@ -930,12 +973,15 @@ opentelemetry-instrumentation==0.46b0 \
|
||||
opentelemetry-instrumentation-django==0.46b0 \
|
||||
--hash=sha256:cc11b2e24f9bdd20759570390ed8619d9c5acbf788b4a5401e36e280dfc20feb \
|
||||
--hash=sha256:ecc85941263122f99dbd96463a981b2d1eeea618ca287a58abe0af9fd67631ee
|
||||
# via -r src/backend/requirements.in
|
||||
opentelemetry-instrumentation-redis==0.46b0 \
|
||||
--hash=sha256:8b4639fe52edb6ccdc633c54c01630005ab63faeffd97754cddbf6bdc1f04c5e \
|
||||
--hash=sha256:e796530808829a9c32f19eaf470f0b01caef13bd89f1d964f536198de881e460
|
||||
# via -r src/backend/requirements.in
|
||||
opentelemetry-instrumentation-requests==0.46b0 \
|
||||
--hash=sha256:a8c2472800d8686f3f286cd524b8746b386154092e85a791ba14110d1acc9b81 \
|
||||
--hash=sha256:ef0ad63bfd0d52631daaf7d687e763dbd89b465f5cb052f12a4e67e5e3d181e4
|
||||
# via -r src/backend/requirements.in
|
||||
opentelemetry-instrumentation-wsgi==0.46b0 \
|
||||
--hash=sha256:2386014b026f5307c802417eeab74265785ae3dd6eee8c5581a830e3b2d3435b \
|
||||
--hash=sha256:f4e1001e8477eb546cac7c13cff0b0cf127812b1188a37bcaa3e43eb741451e2
|
||||
@ -951,6 +997,7 @@ opentelemetry-sdk==1.25.0 \
|
||||
--hash=sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7 \
|
||||
--hash=sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
opentelemetry-semantic-conventions==0.46b0 \
|
||||
@ -976,6 +1023,7 @@ packaging==24.1 \
|
||||
pdf2image==1.17.0 \
|
||||
--hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \
|
||||
--hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2
|
||||
# via -r src/backend/requirements.in
|
||||
pillow==10.3.0 \
|
||||
--hash=sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c \
|
||||
--hash=sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2 \
|
||||
@ -1047,6 +1095,7 @@ pillow==10.3.0 \
|
||||
--hash=sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27 \
|
||||
--hash=sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-stdimage
|
||||
# pdf2image
|
||||
# python-barcode
|
||||
@ -1055,9 +1104,11 @@ pillow==10.3.0 \
|
||||
pint==0.21 \
|
||||
--hash=sha256:3e98bdf01f4dcf840cc0207c0b6f7510d4e0c6288efc1bf470626e875c831172 \
|
||||
--hash=sha256:998b695e84a34d11702da4a8b9457a39bb5c7ab5ec68db90e948e30878e421f1
|
||||
# via -r src/backend/requirements.in
|
||||
pip-licenses==4.4.0 \
|
||||
--hash=sha256:996817118375445243a34faafe23c06f6b2d250247c4046571b5a6722d45be69 \
|
||||
--hash=sha256:dbad2ac5a25f574cabe2716f2f031a0c5fa359bed9b3ef615301f4e546893b46
|
||||
# via -r src/backend/requirements.in
|
||||
prettytable==3.10.0 \
|
||||
--hash=sha256:6536efaf0757fdaa7d22e78b3aac3b69ea1b7200538c2c6995d649365bddab92 \
|
||||
--hash=sha256:9665594d137fb08a1117518c25551e0ede1687197cf353a4fdc78d27e1073568
|
||||
@ -1104,6 +1155,7 @@ pypng==0.20220715.0 \
|
||||
python-barcode[images]==0.15.1 \
|
||||
--hash=sha256:057636fba37369c22852410c8535b36adfbeb965ddfd4e5b6924455d692e0886 \
|
||||
--hash=sha256:3b1825fbdb11e597466dff4286b4ea9b1e86a57717b59e563ae679726fc854de
|
||||
# via -r src/backend/requirements.in
|
||||
python-dateutil==2.9.0.post0 \
|
||||
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
|
||||
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
|
||||
@ -1113,6 +1165,7 @@ python-dateutil==2.9.0.post0 \
|
||||
python-dotenv==1.0.1 \
|
||||
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
|
||||
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
|
||||
# via -r src/backend/requirements.in
|
||||
python-fsutil==0.14.1 \
|
||||
--hash=sha256:0d45e623f0f4403f674bdd8ae7aa7d24a4b3132ea45c65416bd2865e6b20b035 \
|
||||
--hash=sha256:8fb204fa8059f37bdeee8a1dc0fff010170202ea47c4225ee71bb3c26f3997be
|
||||
@ -1186,12 +1239,15 @@ pyyaml==6.0.1 \
|
||||
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
|
||||
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# drf-spectacular
|
||||
# tablib
|
||||
qrcode[pil]==7.4.2 \
|
||||
--hash=sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a \
|
||||
--hash=sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845
|
||||
# via django-allauth-2fa
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-allauth-2fa
|
||||
rapidfuzz==3.9.3 \
|
||||
--hash=sha256:05ee0696ebf0dfe8f7c17f364d70617616afc7dafe366532730ca34056065b8a \
|
||||
--hash=sha256:0c34139df09a61b1b557ab65782ada971b4a3bce7081d1b2bee45b0a52231adb \
|
||||
@ -1286,6 +1342,7 @@ rapidfuzz==3.9.3 \
|
||||
--hash=sha256:f50fed4a9b0c9825ff37cf0bccafd51ff5792090618f7846a7650f21f85579c9 \
|
||||
--hash=sha256:f57e8305c281e8c8bc720515540e0580355100c0a7a541105c6cafc5de71daae \
|
||||
--hash=sha256:fd84b7f652a5610733400307dc732f57c4a907080bef9520412e6d9b55bc9adc
|
||||
# via -r src/backend/requirements.in
|
||||
redis==5.0.7 \
|
||||
--hash=sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db \
|
||||
--hash=sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b
|
||||
@ -1376,6 +1433,7 @@ regex==2024.4.28 \
|
||||
--hash=sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2 \
|
||||
--hash=sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e \
|
||||
--hash=sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb
|
||||
# via -r src/backend/requirements.in
|
||||
requests==2.32.3 \
|
||||
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
|
||||
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
|
||||
@ -1485,14 +1543,17 @@ rpds-py==0.18.1 \
|
||||
# via
|
||||
# jsonschema
|
||||
# referencing
|
||||
sentry-sdk==2.7.0 \
|
||||
--hash=sha256:d846a211d4a0378b289ced3c434480945f110d0ede00450ba631fc2852e7a0d4 \
|
||||
--hash=sha256:db9594c27a4d21c1ebad09908b1f0dc808ef65c2b89c1c8e7e455143262e37c1
|
||||
# via django-q-sentry
|
||||
setuptools==70.3.0 \
|
||||
--hash=sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5 \
|
||||
--hash=sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc
|
||||
sentry-sdk==2.8.0 \
|
||||
--hash=sha256:6051562d2cfa8087bb8b4b8b79dc44690f8a054762a29c07e22588b1f619bfb5 \
|
||||
--hash=sha256:aa4314f877d9cd9add5a0c9ba18e3f27f99f7de835ce36bd150e48a41c7c646f
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-q-sentry
|
||||
setuptools==71.0.3 \
|
||||
--hash=sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d \
|
||||
--hash=sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-money
|
||||
# opentelemetry-instrumentation
|
||||
sgmllib3k==1.0.0 \
|
||||
@ -1515,7 +1576,9 @@ sqlparse==0.5.0 \
|
||||
tablib[html, ods, xls, xlsx, yaml]==3.5.0 \
|
||||
--hash=sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9 \
|
||||
--hash=sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33
|
||||
# via django-import-export
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-import-export
|
||||
tinycss2==1.2.1 \
|
||||
--hash=sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847 \
|
||||
--hash=sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627
|
||||
@ -1552,7 +1615,9 @@ wcwidth==0.2.13 \
|
||||
weasyprint==61.2 \
|
||||
--hash=sha256:47df6cfeeff8c6c28cf2e4caf837cde17715efe462708ada74baa2eb391b6059 \
|
||||
--hash=sha256:76c6dc0e75e09182d5645d92c66ddf86b1b992c9420235b723fb374b584e5bf4
|
||||
# via django-weasyprint
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-weasyprint
|
||||
webencodings==0.5.1 \
|
||||
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
|
||||
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
|
||||
@ -1564,6 +1629,7 @@ webencodings==0.5.1 \
|
||||
whitenoise==6.7.0 \
|
||||
--hash=sha256:58c7a6cd811e275a6c91af22e96e87da0b1109e9a53bb7464116ef4c963bf636 \
|
||||
--hash=sha256:a1ae85e01fdc9815d12fa33f17765bc132ed2c54fa76daf9e39e879dd93566f6
|
||||
# via -r src/backend/requirements.in
|
||||
wrapt==1.16.0 \
|
||||
--hash=sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc \
|
||||
--hash=sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81 \
|
||||
|
@ -52,7 +52,7 @@
|
||||
"dayjs": "^1.11.10",
|
||||
"embla-carousel-react": "^8.1.6",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"mantine-datatable": "^7.11.1",
|
||||
"mantine-datatable": "^7.11.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-grid-layout": "^1.4.4",
|
||||
|
@ -31,7 +31,7 @@ export function DashboardItemProxy({
|
||||
queryFn: fetchData,
|
||||
refetchOnWindowFocus: autoupdate
|
||||
});
|
||||
const [dashdata, setDashData] = useState({ title: t`Title`, value: '000' });
|
||||
const [dashData, setDashData] = useState({ title: t`Title`, value: '000' });
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
@ -44,7 +44,7 @@ export function DashboardItemProxy({
|
||||
<div key={id}>
|
||||
<StatisticItem
|
||||
id={id}
|
||||
data={dashdata}
|
||||
data={dashData}
|
||||
isLoading={isLoading || isFetching}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { IconUserStar } from '@tabler/icons-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { base_url } from '../../main';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconPrinter, IconReport, IconTags } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
|
@ -85,7 +85,7 @@ function UploadModal({
|
||||
apiPath: string;
|
||||
setImage: (image: string) => void;
|
||||
}) {
|
||||
const [file1, setFile] = useState<FileWithPath | null>(null);
|
||||
const [currentFile, setCurrentFile] = useState<FileWithPath | null>(null);
|
||||
let uploading = false;
|
||||
|
||||
// Components to show in the Dropzone when no file is selected
|
||||
@ -168,7 +168,7 @@ function UploadModal({
|
||||
return (
|
||||
<Paper style={{ height: '220px' }}>
|
||||
<Dropzone
|
||||
onDrop={(files) => setFile(files[0])}
|
||||
onDrop={(files) => setCurrentFile(files[0])}
|
||||
maxFiles={1}
|
||||
accept={IMAGE_MIME_TYPE}
|
||||
loading={uploading}
|
||||
@ -198,7 +198,9 @@ function UploadModal({
|
||||
}}
|
||||
/>
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>{file1 ? fileInfo(file1) : noFileIdle}</Dropzone.Idle>
|
||||
<Dropzone.Idle>
|
||||
{currentFile ? fileInfo(currentFile) : noFileIdle}
|
||||
</Dropzone.Idle>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
<Paper
|
||||
@ -218,12 +220,15 @@ function UploadModal({
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={!file1}
|
||||
onClick={() => setFile(null)}
|
||||
disabled={!currentFile}
|
||||
onClick={() => setCurrentFile(null)}
|
||||
>
|
||||
<Trans>Clear</Trans>
|
||||
</Button>
|
||||
<Button disabled={!file1} onClick={() => uploadImage(file1)}>
|
||||
<Button
|
||||
disabled={!currentFile}
|
||||
onClick={() => uploadImage(currentFile)}
|
||||
>
|
||||
<Trans>Submit</Trans>
|
||||
</Button>
|
||||
</Paper>
|
||||
@ -354,31 +359,27 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos="relative">
|
||||
<>
|
||||
<ApiImage
|
||||
src={img}
|
||||
mah={IMAGE_DIMENSION}
|
||||
maw={IMAGE_DIMENSION}
|
||||
onClick={expandImage}
|
||||
/>
|
||||
{permissions.hasChangeRole(props.appRole) &&
|
||||
hasOverlay &&
|
||||
hovered && (
|
||||
<Overlay color="black" opacity={0.8} onClick={expandImage}>
|
||||
<ImageActionButtons
|
||||
visible={hovered}
|
||||
actions={props.imageActions}
|
||||
apiPath={props.apiPath}
|
||||
hasImage={props.src ? true : false}
|
||||
pk={props.pk}
|
||||
setImage={setAndRefresh}
|
||||
/>
|
||||
</Overlay>
|
||||
)}
|
||||
</>
|
||||
</AspectRatio>
|
||||
</>
|
||||
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos="relative">
|
||||
<>
|
||||
<ApiImage
|
||||
src={img}
|
||||
mah={IMAGE_DIMENSION}
|
||||
maw={IMAGE_DIMENSION}
|
||||
onClick={expandImage}
|
||||
/>
|
||||
{permissions.hasChangeRole(props.appRole) && hasOverlay && hovered && (
|
||||
<Overlay color="black" opacity={0.8} onClick={expandImage}>
|
||||
<ImageActionButtons
|
||||
visible={hovered}
|
||||
actions={props.imageActions}
|
||||
apiPath={props.apiPath}
|
||||
hasImage={props.src ? true : false}
|
||||
pk={props.pk}
|
||||
setImage={setAndRefresh}
|
||||
/>
|
||||
</Overlay>
|
||||
)}
|
||||
</>
|
||||
</AspectRatio>
|
||||
);
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ export default function NotesEditor({
|
||||
id: 'notes'
|
||||
});
|
||||
});
|
||||
}, [noteUrl, ref.current]);
|
||||
}, [api, noteUrl, ref.current]);
|
||||
|
||||
const plugins: any[] = useMemo(() => {
|
||||
let plg = [
|
||||
|
@ -81,7 +81,9 @@ export const PdfPreviewComponent: PreviewAreaComponent = forwardRef(
|
||||
<Trans>Preview not available, click "Reload Preview".</Trans>
|
||||
</div>
|
||||
)}
|
||||
{pdfUrl && <iframe src={pdfUrl} width="100%" height="100%" />}
|
||||
{pdfUrl && (
|
||||
<iframe src={pdfUrl} width="100%" height="100%" title="PDF Preview" />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Center,
|
||||
|
@ -139,6 +139,7 @@ export function OptionsApiForm({
|
||||
if (!props.ignorePermissionCheck) {
|
||||
fields = extractAvailableFields(response, props.method);
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
throwOnError: (error: any) => {
|
||||
@ -183,7 +184,7 @@ export function OptionsApiForm({
|
||||
<ApiForm
|
||||
id={id}
|
||||
props={formProps}
|
||||
optionsLoading={optionsQuery.isFetching}
|
||||
optionsLoading={optionsQuery.isFetching || !optionsQuery.data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -206,9 +207,6 @@ export function ApiForm({
|
||||
const [fields, setFields] = useState<ApiFormFieldSet>(
|
||||
() => props.fields ?? {}
|
||||
);
|
||||
useEffect(() => {
|
||||
setFields(props.fields ?? {});
|
||||
}, [props.fields]);
|
||||
|
||||
const defaultValues: FieldValues = useMemo(() => {
|
||||
let defaultValuesMap = mapFields(fields ?? {}, (_path, field) => {
|
||||
@ -251,6 +249,31 @@ export function ApiForm({
|
||||
[props.url, props.pk, props.pathParams]
|
||||
);
|
||||
|
||||
// Define function to process API response
|
||||
const processFields = (fields: ApiFormFieldSet, data: NestedDict) => {
|
||||
const res: NestedDict = {};
|
||||
|
||||
for (const [k, field] of Object.entries(fields)) {
|
||||
const dataValue = data[k];
|
||||
|
||||
if (
|
||||
field.field_type === 'nested object' &&
|
||||
field.children &&
|
||||
typeof dataValue === 'object'
|
||||
) {
|
||||
res[k] = processFields(field.children, dataValue);
|
||||
} else {
|
||||
res[k] = dataValue;
|
||||
|
||||
if (field.onValueChange) {
|
||||
field.onValueChange(dataValue, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
// Query manager for retrieving initial data from the server
|
||||
const initialDataQuery = useQuery({
|
||||
enabled: false,
|
||||
@ -263,66 +286,51 @@ export function ApiForm({
|
||||
props.pathParams
|
||||
],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
// Await API call
|
||||
let response = await api.get(url);
|
||||
return await api
|
||||
.get(url)
|
||||
.then((response: any) => {
|
||||
// Process API response
|
||||
const fetchedData: any = processFields(fields, response.data);
|
||||
|
||||
// Define function to process API response
|
||||
const processFields = (fields: ApiFormFieldSet, data: NestedDict) => {
|
||||
const res: NestedDict = {};
|
||||
|
||||
// TODO: replace with .map()
|
||||
for (const [k, field] of Object.entries(fields)) {
|
||||
const dataValue = data[k];
|
||||
|
||||
if (
|
||||
field.field_type === 'nested object' &&
|
||||
field.children &&
|
||||
typeof dataValue === 'object'
|
||||
) {
|
||||
res[k] = processFields(field.children, dataValue);
|
||||
} else {
|
||||
res[k] = dataValue;
|
||||
|
||||
if (field.onValueChange) {
|
||||
field.onValueChange(dataValue, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
// Process API response
|
||||
const initialData: any = processFields(fields, response.data);
|
||||
|
||||
// Update form values, but only for the fields specified for this form
|
||||
form.reset(initialData);
|
||||
|
||||
// Update the field references, too
|
||||
Object.keys(fields).forEach((fieldName) => {
|
||||
if (fieldName in initialData) {
|
||||
let field = fields[fieldName] ?? {};
|
||||
fields[fieldName] = {
|
||||
...field,
|
||||
value: initialData[fieldName]
|
||||
};
|
||||
}
|
||||
// Update form values, but only for the fields specified for this form
|
||||
form.reset(fetchedData);
|
||||
return fetchedData;
|
||||
})
|
||||
.catch(() => {
|
||||
return {};
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('ERR: Error fetching initial data:', error);
|
||||
// Re-throw error to allow react-query to handle error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let _fields: any = props.fields || {};
|
||||
let _initialData: any = props.initialData || {};
|
||||
let _fetchedData: any = initialDataQuery.data || {};
|
||||
|
||||
for (const k of Object.keys(_fields)) {
|
||||
// Ensure default values override initial field spec
|
||||
if (defaultValues[k]) {
|
||||
_fields[k].value = defaultValues[k];
|
||||
}
|
||||
|
||||
// Ensure initial data overrides default values
|
||||
if (_initialData && _initialData[k]) {
|
||||
_fields[k].value = _initialData[k];
|
||||
}
|
||||
|
||||
// Ensure fetched data overrides also
|
||||
if (_fetchedData && _fetchedData[k]) {
|
||||
_fields[k].value = _fetchedData[k];
|
||||
}
|
||||
}
|
||||
|
||||
setFields(_fields);
|
||||
}, [props.fields, props.initialData, defaultValues, initialDataQuery.data]);
|
||||
|
||||
// Fetch initial data on form load
|
||||
useEffect(() => {
|
||||
// Fetch initial data if the fetchInitialData property is set
|
||||
if (props.fetchInitialData) {
|
||||
if (!optionsLoading && props.fetchInitialData) {
|
||||
queryClient.removeQueries({
|
||||
queryKey: [
|
||||
'form-initial-data',
|
||||
@ -335,22 +343,16 @@ export function ApiForm({
|
||||
});
|
||||
initialDataQuery.refetch();
|
||||
}
|
||||
}, [props.fetchInitialData]);
|
||||
}, [props.fetchInitialData, optionsLoading]);
|
||||
|
||||
const isLoading = useMemo(
|
||||
const isLoading: boolean = useMemo(
|
||||
() =>
|
||||
isFormLoading ||
|
||||
initialDataQuery.isFetching ||
|
||||
optionsLoading ||
|
||||
isSubmitting ||
|
||||
!fields,
|
||||
[
|
||||
isFormLoading,
|
||||
initialDataQuery.isFetching,
|
||||
isSubmitting,
|
||||
fields,
|
||||
optionsLoading
|
||||
]
|
||||
[isFormLoading, initialDataQuery, isSubmitting, fields, optionsLoading]
|
||||
);
|
||||
|
||||
const [initialFocus, setInitialFocus] = useState<string>('');
|
||||
@ -370,7 +372,7 @@ export function ApiForm({
|
||||
});
|
||||
}
|
||||
|
||||
if (isLoading || initialFocus == focusField) {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -522,6 +524,14 @@ export function ApiForm({
|
||||
props.onFormError?.();
|
||||
}, [props.onFormError]);
|
||||
|
||||
if (optionsLoading || initialDataQuery.isFetching) {
|
||||
return (
|
||||
<Paper mah={'65vh'}>
|
||||
<LoadingOverlay visible zIndex={1010} />
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Boundary label={`ApiForm-${id}`}>
|
||||
@ -535,13 +545,15 @@ export function ApiForm({
|
||||
{/* Form Fields */}
|
||||
<Stack gap="sm">
|
||||
{(!isValid || nonFieldErrors.length > 0) && (
|
||||
<Alert radius="sm" color="red" title={t`Error`}>
|
||||
{nonFieldErrors.length > 0 && (
|
||||
<Alert radius="sm" color="red" title={t`Form Error`}>
|
||||
{nonFieldErrors.length > 0 ? (
|
||||
<Stack gap="xs">
|
||||
{nonFieldErrors.map((message) => (
|
||||
<Text key={message}>{message}</Text>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Text>{t`Errors exist for one or more form fields`}</Text>
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
@ -561,8 +573,8 @@ export function ApiForm({
|
||||
<Boundary label={`ApiForm-${id}-FormContent`}>
|
||||
<FormProvider {...form}>
|
||||
<Stack gap="xs">
|
||||
{!optionsLoading &&
|
||||
Object.entries(fields).map(([fieldName, field]) => (
|
||||
{Object.entries(fields).map(([fieldName, field]) => {
|
||||
return (
|
||||
<ApiFormField
|
||||
key={fieldName}
|
||||
fieldName={fieldName}
|
||||
@ -571,7 +583,8 @@ export function ApiForm({
|
||||
url={url}
|
||||
setFields={setFields}
|
||||
/>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
</Boundary>
|
||||
|
@ -1,15 +1,7 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
Alert,
|
||||
FileInput,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Switch,
|
||||
TextInput
|
||||
} from '@mantine/core';
|
||||
import { Alert, FileInput, NumberInput, Stack, Switch } from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { IconX } from '@tabler/icons-react';
|
||||
import { ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import { Control, FieldValues, useController } from 'react-hook-form';
|
||||
|
||||
@ -149,7 +141,7 @@ export function ApiFormField({
|
||||
label: hideLabels ? undefined : definition.label,
|
||||
description: hideLabels ? undefined : definition.description
|
||||
};
|
||||
}, [definition]);
|
||||
}, [hideLabels, definition]);
|
||||
|
||||
// pull out onValueChange as this can cause strange errors when passing the
|
||||
// definition to the input components via spread syntax
|
||||
@ -202,7 +194,7 @@ export function ApiFormField({
|
||||
}
|
||||
|
||||
return val;
|
||||
}, [value]);
|
||||
}, [definition.field_type, value]);
|
||||
|
||||
// Coerce the value to a (stringified) boolean value
|
||||
const booleanValue: boolean = useMemo(() => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Select } from '@mantine/core';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import { ApiFormFieldType } from './ApiFormField';
|
||||
@ -10,7 +10,8 @@ import { ApiFormFieldType } from './ApiFormField';
|
||||
*/
|
||||
export function ChoiceField({
|
||||
controller,
|
||||
definition
|
||||
definition,
|
||||
fieldName
|
||||
}: {
|
||||
controller: UseControllerReturn<FieldValues, any>;
|
||||
definition: ApiFormFieldType;
|
||||
@ -23,6 +24,8 @@ export function ChoiceField({
|
||||
fieldState: { error }
|
||||
} = controller;
|
||||
|
||||
const { value } = field;
|
||||
|
||||
// Build a set of choices for the field
|
||||
const choices: any[] = useMemo(() => {
|
||||
let choices = definition.choices ?? [];
|
||||
@ -48,6 +51,14 @@ export function ChoiceField({
|
||||
[field.onChange, definition]
|
||||
);
|
||||
|
||||
const choiceValue = useMemo(() => {
|
||||
if (!value) {
|
||||
return '';
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
id={fieldId}
|
||||
@ -57,7 +68,7 @@ export function ChoiceField({
|
||||
{...field}
|
||||
onChange={onChange}
|
||||
data={choices}
|
||||
value={field.value}
|
||||
value={choiceValue}
|
||||
label={definition.label}
|
||||
description={definition.description}
|
||||
placeholder={definition.placeholder}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { Container, Flex, Group, Table } from '@mantine/core';
|
||||
import { Container, Group, Table } from '@mantine/core';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
import { StandaloneField } from '../StandaloneField';
|
||||
import { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
export function TableField({
|
||||
@ -83,23 +85,51 @@ export function TableField({
|
||||
|
||||
/*
|
||||
* Display an "extra" row below the main table row, for additional information.
|
||||
* - Each "row" can display an extra row of information below the main row
|
||||
*/
|
||||
export function TableFieldExtraRow({
|
||||
visible,
|
||||
content,
|
||||
colSpan
|
||||
fieldDefinition,
|
||||
defaultValue,
|
||||
emptyValue,
|
||||
onValueChange
|
||||
}: {
|
||||
visible: boolean;
|
||||
content: React.ReactNode;
|
||||
colSpan?: number;
|
||||
fieldDefinition: ApiFormFieldType;
|
||||
defaultValue?: any;
|
||||
emptyValue?: any;
|
||||
onValueChange: (value: any) => void;
|
||||
}) {
|
||||
// Callback whenever the visibility of the sub-field changes
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
// If the sub-field is hidden, reset the value to the "empty" value
|
||||
onValueChange(emptyValue);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const field: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
...fieldDefinition,
|
||||
default: defaultValue,
|
||||
onValueChange: (value: any) => {
|
||||
onValueChange(value);
|
||||
}
|
||||
};
|
||||
}, [fieldDefinition]);
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={colSpan ?? 3}>
|
||||
<Group justify="flex-start" grow>
|
||||
<InvenTreeIcon icon="downright" />
|
||||
{content}
|
||||
<Table.Td colSpan={10}>
|
||||
<Group grow preventGrowOverflow={false} justify="flex-apart" p="xs">
|
||||
<Container flex={0} p="xs">
|
||||
<InvenTreeIcon icon="downright" />
|
||||
</Container>
|
||||
<StandaloneField
|
||||
fieldDefinition={field}
|
||||
defaultValue={defaultValue}
|
||||
/>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
|
@ -56,6 +56,9 @@ export default function TextField({
|
||||
error={error?.message}
|
||||
radius="sm"
|
||||
onChange={(event) => onTextChange(event.currentTarget.value)}
|
||||
onBlur={(event) => {
|
||||
onChange(event.currentTarget.value);
|
||||
}}
|
||||
rightSection={
|
||||
value && !definition.required ? (
|
||||
<IconX size="1rem" color="red" onClick={() => onTextChange('')} />
|
||||
|
@ -138,16 +138,27 @@ export default function ImporterDataSelector({
|
||||
// Find the field definition in session.availableFields
|
||||
let fieldDef = session.availableFields[field];
|
||||
if (fieldDef) {
|
||||
// Construct field filters based on session field filters
|
||||
let filters = fieldDef.filters ?? {};
|
||||
|
||||
if (session.fieldFilters[field]) {
|
||||
filters = {
|
||||
...filters,
|
||||
...session.fieldFilters[field]
|
||||
};
|
||||
}
|
||||
|
||||
fields[field] = {
|
||||
...fieldDef,
|
||||
field_type: fieldDef.type,
|
||||
description: fieldDef.help_text
|
||||
description: fieldDef.help_text,
|
||||
filters: filters
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}, [selectedFieldNames, session.availableFields]);
|
||||
}, [selectedFieldNames, session.availableFields, session.fieldFilters]);
|
||||
|
||||
const importData = useCallback(
|
||||
(rows: number[]) => {
|
||||
|
@ -29,18 +29,16 @@ export default function ImporterImportProgress({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center>
|
||||
<Container>
|
||||
<Stack gap="xs">
|
||||
<StylishText size="lg">{t`Importing Records`}</StylishText>
|
||||
<Loader />
|
||||
<Text size="lg">
|
||||
{t`Imported rows`}: {session.sessionData.row_count}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Center>
|
||||
</>
|
||||
<Center>
|
||||
<Container>
|
||||
<Stack gap="xs">
|
||||
<StylishText size="lg">{t`Importing Records`}</StylishText>
|
||||
<Loader />
|
||||
<Text size="lg">
|
||||
{t`Imported rows`}: {session.sessionData.row_count}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Carousel } from '@mantine/carousel';
|
||||
import { Anchor, Button, Paper, Text, Title, rem } from '@mantine/core';
|
||||
import { Anchor, Button, Paper, Text, Title } from '@mantine/core';
|
||||
|
||||
import { DocumentationLinkItem } from './DocumentationLinks';
|
||||
import * as classes from './GettingStartedCarousel.css';
|
||||
|
@ -35,7 +35,7 @@ export function QrCodeModal({
|
||||
key: 'camId',
|
||||
defaultValue: null
|
||||
});
|
||||
const [ScanningEnabled, setIsScanning] = useState<boolean>(false);
|
||||
const [scanningEnabled, setScanningEnabled] = useState<boolean>(false);
|
||||
const [wasAutoPaused, setWasAutoPaused] = useState<boolean>(false);
|
||||
const documentState = useDocumentVisibility();
|
||||
|
||||
@ -48,7 +48,7 @@ export function QrCodeModal({
|
||||
|
||||
// Stop/star when leaving or reentering page
|
||||
useEffect(() => {
|
||||
if (ScanningEnabled && documentState === 'hidden') {
|
||||
if (scanningEnabled && documentState === 'hidden') {
|
||||
stopScanning();
|
||||
setWasAutoPaused(true);
|
||||
} else if (wasAutoPaused && documentState === 'visible') {
|
||||
@ -128,12 +128,12 @@ export function QrCodeModal({
|
||||
icon: <IconX />
|
||||
});
|
||||
});
|
||||
setIsScanning(true);
|
||||
setScanningEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
function stopScanning() {
|
||||
if (qrCodeScanner && ScanningEnabled) {
|
||||
if (qrCodeScanner && scanningEnabled) {
|
||||
qrCodeScanner.stop().catch((err: string) => {
|
||||
showNotification({
|
||||
title: t`Error while stopping`,
|
||||
@ -142,7 +142,7 @@ export function QrCodeModal({
|
||||
icon: <IconX />
|
||||
});
|
||||
});
|
||||
setIsScanning(false);
|
||||
setScanningEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ export function QrCodeModal({
|
||||
<Group>
|
||||
<Text size="sm">{camId?.label}</Text>
|
||||
<Space style={{ flex: 1 }} />
|
||||
<Badge>{ScanningEnabled ? t`Scanning` : t`Not scanning`}</Badge>
|
||||
<Badge>{scanningEnabled ? t`Scanning` : t`Not scanning`}</Badge>
|
||||
</Group>
|
||||
<Container px={0} id="reader" w={'100%'} mih="300px" />
|
||||
{!camId ? (
|
||||
@ -164,14 +164,14 @@ export function QrCodeModal({
|
||||
<Button
|
||||
style={{ flex: 1 }}
|
||||
onClick={() => startScanning()}
|
||||
disabled={camId != undefined && ScanningEnabled}
|
||||
disabled={camId != undefined && scanningEnabled}
|
||||
>
|
||||
<Trans>Start scanning</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
style={{ flex: 1 }}
|
||||
onClick={() => stopScanning()}
|
||||
disabled={!ScanningEnabled}
|
||||
disabled={!scanningEnabled}
|
||||
>
|
||||
<Trans>Stop scanning</Trans>
|
||||
</Button>
|
||||
|
@ -2,7 +2,7 @@ import { ActionIcon, Container, Group, Indicator, Tabs } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { IconBell, IconSearch } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import { useMatch, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
@ -132,10 +132,35 @@ export function Header() {
|
||||
}
|
||||
|
||||
function NavTabs() {
|
||||
const user = useUserState();
|
||||
const navigate = useNavigate();
|
||||
const match = useMatch(':tabName/*');
|
||||
const tabValue = match?.params.tabName;
|
||||
|
||||
const tabs: ReactNode[] = useMemo(() => {
|
||||
let _tabs: ReactNode[] = [];
|
||||
|
||||
mainNavTabs.forEach((tab) => {
|
||||
if (tab.role && !user.hasViewRole(tab.role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_tabs.push(
|
||||
<Tabs.Tab
|
||||
value={tab.name}
|
||||
key={tab.name}
|
||||
onClick={(event: any) =>
|
||||
navigateToLink(`/${tab.name}`, navigate, event)
|
||||
}
|
||||
>
|
||||
{tab.text}
|
||||
</Tabs.Tab>
|
||||
);
|
||||
});
|
||||
|
||||
return _tabs;
|
||||
}, [mainNavTabs, user]);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue="home"
|
||||
@ -146,19 +171,7 @@ function NavTabs() {
|
||||
}}
|
||||
value={tabValue}
|
||||
>
|
||||
<Tabs.List>
|
||||
{mainNavTabs.map((tab) => (
|
||||
<Tabs.Tab
|
||||
value={tab.name}
|
||||
key={tab.name}
|
||||
onClick={(event: any) =>
|
||||
navigateToLink(`/${tab.name}`, navigate, event)
|
||||
}
|
||||
>
|
||||
{tab.text}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs.List>
|
||||
<Tabs.List>{tabs.map((tab) => tab)}</Tabs.List>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
Drawer,
|
||||
Group,
|
||||
Loader,
|
||||
LoadingOverlay,
|
||||
Space,
|
||||
Stack,
|
||||
Text,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Alert, Anchor, Group, Skeleton, Space, Text } from '@mantine/core';
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
|
||||
import { api } from '../../App';
|
||||
@ -71,7 +71,7 @@ const RendererLookup: EnumDictionary<
|
||||
[ModelType.parttesttemplate]: RenderPartTestTemplate,
|
||||
[ModelType.projectcode]: RenderProjectCode,
|
||||
[ModelType.purchaseorder]: RenderPurchaseOrder,
|
||||
[ModelType.purchaseorderline]: RenderPurchaseOrder,
|
||||
[ModelType.purchaseorderlineitem]: RenderPurchaseOrder,
|
||||
[ModelType.returnorder]: RenderReturnOrder,
|
||||
[ModelType.salesorder]: RenderSalesOrder,
|
||||
[ModelType.salesordershipment]: RenderSalesOrderShipment,
|
||||
@ -180,7 +180,7 @@ export function RenderInlineModel({
|
||||
return (
|
||||
<Group gap="xs" justify="space-between" wrap="nowrap">
|
||||
<Group gap="xs" justify="left" wrap="nowrap">
|
||||
{image && Thumbnail({ src: image, size: 18 })}
|
||||
{image && <Thumbnail src={image} size={18} />}
|
||||
{url ? (
|
||||
<Anchor href={url} onClick={(event: any) => onClick(event)}>
|
||||
<Text size="sm">{primary}</Text>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user