mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' into pui-add-startpage-to-admin
This commit is contained in:
commit
4e7fca7009
34
.github/workflows/translations.yaml
vendored
34
.github/workflows/translations.yaml
vendored
@ -13,10 +13,11 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
synchronize-with-crowdin:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -39,16 +40,29 @@ jobs:
|
|||||||
apt-dependency: gettext
|
apt-dependency: gettext
|
||||||
- name: Make Translations
|
- name: Make Translations
|
||||||
run: invoke translate
|
run: invoke translate
|
||||||
- name: Commit files
|
- name: Remove compiled static files
|
||||||
|
run: rm -rf src/backend/InvenTree/static
|
||||||
|
- name: Remove all local changes that are not *.po files
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
git config --local user.name "github-actions[bot]"
|
||||||
git checkout -b l10_local
|
git add src/backend/InvenTree/locale/en/LC_MESSAGES/django.po src/frontend/src/locales/en/messages.po
|
||||||
git add "*.po"
|
git commit -m "add translations"
|
||||||
git commit -m "updated translation base"
|
git reset --hard
|
||||||
- name: Push changes
|
git reset HEAD~
|
||||||
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
|
- name: crowdin action
|
||||||
|
uses: crowdin/github-action@6ed209d411599a981ccb978df3be9dc9b8a81699 # pin@v2
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
upload_sources: true
|
||||||
branch: l10
|
upload_translations: false
|
||||||
force: true
|
download_translations: true
|
||||||
|
localization_branch_name: l10_crowdin
|
||||||
|
create_pull_request: true
|
||||||
|
pull_request_title: 'New Crowdin updates'
|
||||||
|
pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)'
|
||||||
|
pull_request_base_branch_name: 'l10'
|
||||||
|
pull_request_labels: 'translations'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||||
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||||
|
@ -17,6 +17,7 @@ gunicorn>=22.0.0
|
|||||||
# LDAP required packages
|
# LDAP required packages
|
||||||
django-auth-ldap # Django integration for ldap auth
|
django-auth-ldap # Django integration for ldap auth
|
||||||
python-ldap # LDAP auth support
|
python-ldap # LDAP auth support
|
||||||
|
django<5.0 # Force lower to match main project
|
||||||
|
|
||||||
# Upgraded python package installer
|
# Upgraded python package installer
|
||||||
uv
|
uv
|
||||||
|
@ -7,14 +7,16 @@ asgiref==3.8.1 \
|
|||||||
django==4.2.15 \
|
django==4.2.15 \
|
||||||
--hash=sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30 \
|
--hash=sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30 \
|
||||||
--hash=sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a
|
--hash=sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a
|
||||||
# via django-auth-ldap
|
# via
|
||||||
|
# -r contrib/container/requirements.in
|
||||||
|
# django-auth-ldap
|
||||||
django-auth-ldap==4.8.0 \
|
django-auth-ldap==4.8.0 \
|
||||||
--hash=sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce \
|
--hash=sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce \
|
||||||
--hash=sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738
|
--hash=sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
gunicorn==22.0.0 \
|
gunicorn==23.0.0 \
|
||||||
--hash=sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9 \
|
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \
|
||||||
--hash=sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63
|
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
invoke==2.2.0 \
|
invoke==2.2.0 \
|
||||||
--hash=sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820 \
|
--hash=sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820 \
|
||||||
@ -44,86 +46,74 @@ mysqlclient==2.2.4 \
|
|||||||
--hash=sha256:d43987bb9626096a302ca6ddcdd81feaeca65ced1d5fe892a6a66b808326aa54 \
|
--hash=sha256:d43987bb9626096a302ca6ddcdd81feaeca65ced1d5fe892a6a66b808326aa54 \
|
||||||
--hash=sha256:e1ebe3f41d152d7cb7c265349fdb7f1eca86ccb0ca24a90036cde48e00ceb2ab
|
--hash=sha256:e1ebe3f41d152d7cb7c265349fdb7f1eca86ccb0ca24a90036cde48e00ceb2ab
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
packaging==24.0 \
|
packaging==24.1 \
|
||||||
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
|
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
|
||||||
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
|
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
|
||||||
# via
|
# via
|
||||||
# gunicorn
|
# gunicorn
|
||||||
# mariadb
|
# mariadb
|
||||||
psycopg[binary, pool]==3.1.18 \
|
psycopg[binary, pool]==3.2.1 \
|
||||||
--hash=sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b \
|
--hash=sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7 \
|
||||||
--hash=sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e
|
--hash=sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
psycopg-binary==3.1.18 \
|
psycopg-binary==3.2.1 \
|
||||||
--hash=sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7 \
|
--hash=sha256:059cbd4e6da2337e17707178fe49464ed01de867dc86c677b30751755ec1dc51 \
|
||||||
--hash=sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c \
|
--hash=sha256:06a7aae34edfe179ddc04da005e083ff6c6b0020000399a2cbf0a7121a8a22ea \
|
||||||
--hash=sha256:13bcd3742112446037d15e360b27a03af4b5afcf767f5ee374ef8f5dd7571b31 \
|
--hash=sha256:0879b5d76b7d48678d31278242aaf951bc2d69ca4e4d7cef117e4bbf7bfefda9 \
|
||||||
--hash=sha256:1729d0e3dfe2546d823841eb7a3d003144189d6f5e138ee63e5227f8b75276a5 \
|
--hash=sha256:0ab58213cc976a1666f66bc1cb2e602315cd753b7981a8e17237ac2a185bd4a1 \
|
||||||
--hash=sha256:1859aeb2133f5ecdd9cbcee155f5e38699afc06a365f903b1512c765fd8d457e \
|
--hash=sha256:0b018631e5c80ce9bc210b71ea885932f9cca6db131e4df505653d7e3873a938 \
|
||||||
--hash=sha256:1c9b6bd7fb5c6638cb32469674707649b526acfe786ba6d5a78ca4293d87bae4 \
|
--hash=sha256:101472468d59c74bb8565fab603e032803fd533d16be4b2d13da1bab8deb32a3 \
|
||||||
--hash=sha256:247474af262bdd5559ee6e669926c4f23e9cf53dae2d34c4d991723c72196404 \
|
--hash=sha256:1d353e028b8f848b9784450fc2abf149d53a738d451eab3ee4c85703438128b9 \
|
||||||
--hash=sha256:258d2f0cb45e4574f8b2fe7c6d0a0e2eb58903a4fd1fbaf60954fba82d595ab7 \
|
--hash=sha256:1d6833f607f3fc7b22226a9e121235d3b84c0eda1d3caab174673ef698f63788 \
|
||||||
--hash=sha256:2e2484ae835dedc80cdc7f1b1a939377dc967fed862262cfd097aa9f50cade46 \
|
--hash=sha256:21927f41c4d722ae8eb30d62a6ce732c398eac230509af5ba1749a337f8a63e2 \
|
||||||
--hash=sha256:320047e3d3554b857e16c2b6b615a85e0db6a02426f4d203a4594a2f125dfe57 \
|
--hash=sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db \
|
||||||
--hash=sha256:39242546383f6b97032de7af30edb483d237a0616f6050512eee7b218a2aa8ee \
|
--hash=sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7 \
|
||||||
--hash=sha256:3c2b039ae0c45eee4cd85300ef802c0f97d0afc78350946a5d0ec77dd2d7e834 \
|
--hash=sha256:302b86f92c0d76e99fe1b5c22c492ae519ce8b98b88d37ef74fda4c9e24c6b46 \
|
||||||
--hash=sha256:3c7afcd6f1d55992f26d9ff7b0bd4ee6b475eb43aa3f054d67d32e09f18b0065 \
|
--hash=sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674 \
|
||||||
--hash=sha256:3e4b0bb91da6f2238dbd4fbb4afc40dfb4f045bb611b92fce4d381b26413c686 \
|
--hash=sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e \
|
||||||
--hash=sha256:3e7ce4d988112ca6c75765c7f24c83bdc476a6a5ce00878df6c140ca32c3e16d \
|
--hash=sha256:3c838806eeb99af39f934b7999e35f947a8e577997cc892c12b5053a97a9057f \
|
||||||
--hash=sha256:4085f56a8d4fc8b455e8f44380705c7795be5317419aa5f8214f315e4205d804 \
|
--hash=sha256:40bb515d042f6a345714ec0403df68ccf13f73b05e567837d80c886c7c9d3805 \
|
||||||
--hash=sha256:4575da95fc441244a0e2ebaf33a2b2f74164603341d2046b5cde0a9aa86aa7e2 \
|
--hash=sha256:413977d18412ff83486eeb5875eb00b185a9391c57febac45b8993bf9c0ff489 \
|
||||||
--hash=sha256:489aa4fe5a0b653b68341e9e44af247dedbbc655326854aa34c163ef1bcb3143 \
|
--hash=sha256:415c3b72ea32119163255c6504085f374e47ae7345f14bc3f0ef1f6e0976a879 \
|
||||||
--hash=sha256:4e4de16a637ec190cbee82e0c2dc4860fed17a23a35f7a1e6dc479a5c6876722 \
|
--hash=sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42 \
|
||||||
--hash=sha256:531381f6647fc267383dca88dbe8a70d0feff433a8e3d0c4939201fea7ae1b82 \
|
--hash=sha256:463d55345f73ff391df8177a185ad57b552915ad33f5cc2b31b930500c068b22 \
|
||||||
--hash=sha256:55ff0948457bfa8c0d35c46e3a75193906d1c275538877ba65907fd67aa059ad \
|
--hash=sha256:4a42b8f9ab39affcd5249b45cac763ac3cf12df962b67e23fd15a2ee2932afe5 \
|
||||||
--hash=sha256:59701118c7d8842e451f1e562d08e8708b3f5d14974eefbce9374badd723c4ae \
|
--hash=sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a \
|
||||||
--hash=sha256:5c323103dfa663b88204cf5f028e83c77d7a715f9b6f51d2bbc8184b99ddd90a \
|
--hash=sha256:592b27d6c46a40f9eeaaeea7c1fef6f3c60b02c634365eb649b2d880669f149f \
|
||||||
--hash=sha256:5d6e860edf877d4413e4a807e837d55e3a7c7df701e9d6943c06e460fa6c058f \
|
--hash=sha256:62b1b7b07e00ee490afb39c0a47d8282a9c2822c7cfed9553a04b0058adf7e7f \
|
||||||
--hash=sha256:639dd78ac09b144b0119076783cb64e1128cc8612243e9701d1503c816750b2e \
|
--hash=sha256:6418712ba63cebb0c88c050b3997185b0ef54173b36568522d5634ac06153040 \
|
||||||
--hash=sha256:6432047b8b24ef97e3fbee1d1593a0faaa9544c7a41a2c67d1f10e7621374c83 \
|
--hash=sha256:6f9e13600647087df5928875559f0eb8f496f53e6278b7da9511b4b3d0aff960 \
|
||||||
--hash=sha256:67284e2e450dc7a9e4d76e78c0bd357dc946334a3d410defaeb2635607f632cd \
|
--hash=sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60 \
|
||||||
--hash=sha256:6ebecbf2406cd6875bdd2453e31067d1bd8efe96705a9489ef37e93b50dc6f09 \
|
--hash=sha256:73f9c9b984be9c322b5ec1515b12df1ee5896029f5e72d46160eb6517438659c \
|
||||||
--hash=sha256:7121acc783c4e86d2d320a7fb803460fab158a7f0a04c5e8c5d49065118c1e73 \
|
--hash=sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707 \
|
||||||
--hash=sha256:74e498586b72fb819ca8ea82107747d0cb6e00ae685ea6d1ab3f929318a8ce2d \
|
--hash=sha256:788ffc43d7517c13e624c83e0e553b7b8823c9655e18296566d36a829bfb373f \
|
||||||
--hash=sha256:780a90bcb69bf27a8b08bc35b958e974cb6ea7a04cdec69e737f66378a344d68 \
|
--hash=sha256:78c2007caf3c90f08685c5378e3ceb142bafd5636be7495f7d86ec8a977eaeef \
|
||||||
--hash=sha256:7ac1785d67241d5074f8086705fa68e046becea27964267ab3abd392481d7773 \
|
--hash=sha256:7a84b5eb194a258116154b2a4ff2962ea60ea52de089508db23a51d3d6b1c7d1 \
|
||||||
--hash=sha256:812726266ab96de681f2c7dbd6b734d327f493a78357fcc16b2ac86ff4f4e080 \
|
--hash=sha256:7ce965caf618061817f66c0906f0452aef966c293ae0933d4fa5a16ea6eaf5bb \
|
||||||
--hash=sha256:824a1bfd0db96cc6bef2d1e52d9e0963f5bf653dd5bc3ab519a38f5e6f21c299 \
|
--hash=sha256:84837e99353d16c6980603b362d0f03302d4b06c71672a6651f38df8a482923d \
|
||||||
--hash=sha256:87dd9154b757a5fbf6d590f6f6ea75f4ad7b764a813ae04b1d91a70713f414a1 \
|
--hash=sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1 \
|
||||||
--hash=sha256:887f8d856c91510148be942c7acd702ccf761a05f59f8abc123c22ab77b5a16c \
|
--hash=sha256:921f0c7f39590763d64a619de84d1b142587acc70fd11cbb5ba8fa39786f3073 \
|
||||||
--hash=sha256:888a72c2aca4316ca6d4a619291b805677bae99bba2f6e31a3c18424a48c7e4d \
|
--hash=sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f \
|
||||||
--hash=sha256:8f54978c4b646dec77fefd8485fa82ec1a87807f334004372af1aaa6de9539a5 \
|
--hash=sha256:9a997efbaadb5e1a294fb5760e2f5643d7b8e4e3fe6cb6f09e6d605fd28e0291 \
|
||||||
--hash=sha256:91074f78a9f890af5f2c786691575b6b93a4967ad6b8c5a90101f7b8c1a91d9c \
|
--hash=sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b \
|
||||||
--hash=sha256:9d684227ef8212e27da5f2aff9d4d303cc30b27ac1702d4f6881935549486dd5 \
|
--hash=sha256:af0469c00f24c4bec18c3d2ede124bf62688d88d1b8a5f3c3edc2f61046fe0d7 \
|
||||||
--hash=sha256:9e24e7b6a68a51cc3b162d0339ae4e1263b253e887987d5c759652f5692b5efe \
|
--hash=sha256:b0104a72a17aa84b3b7dcab6c84826c595355bf54bb6ea6d284dcb06d99c6801 \
|
||||||
--hash=sha256:9ffcbbd389e486d3fd83d30107bbf8b27845a295051ccabde240f235d04ed921 \
|
--hash=sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35 \
|
||||||
--hash=sha256:a87e9eeb80ce8ec8c2783f29bce9a50bbcd2e2342a340f159c3326bf4697afa1 \
|
--hash=sha256:b140182830c76c74d17eba27df3755a46442ce8d4fb299e7f1cf2f74a87c877b \
|
||||||
--hash=sha256:ad35ac7fd989184bf4d38a87decfb5a262b419e8ba8dcaeec97848817412c64a \
|
--hash=sha256:b1f087bd84bdcac78bf9f024ebdbfacd07fc0a23ec8191448a50679e2ac4a19e \
|
||||||
--hash=sha256:b15e3653c82384b043d820fc637199b5c6a36b37fa4a4943e0652785bb2bad5d \
|
--hash=sha256:c1d2b6438fb83376f43ebb798bf0ad5e57bc56c03c9c29c85bc15405c8c0ac5a \
|
||||||
--hash=sha256:b293e01057e63c3ac0002aa132a1071ce0fdb13b9ee2b6b45d3abdb3525c597d \
|
--hash=sha256:cad2de17804c4cfee8640ae2b279d616bb9e4734ac3c17c13db5e40982bd710d \
|
||||||
--hash=sha256:b2f7f95746efd1be2dc240248cc157f4315db3fd09fef2adfcc2a76e24aa5741 \
|
--hash=sha256:cc304a46be1e291031148d9d95c12451ffe783ff0cc72f18e2cc7ec43cdb8c68 \
|
||||||
--hash=sha256:bd27f713f2e5ef3fd6796e66c1a5203a27a30ecb847be27a78e1df8a9a5ae68c \
|
--hash=sha256:dc314a47d44fe1a8069b075a64abffad347a3a1d8652fed1bab5d3baea37acb2 \
|
||||||
--hash=sha256:c38a4796abf7380f83b1653c2711cb2449dd0b2e5aca1caa75447d6fa5179c69 \
|
--hash=sha256:f092114f10f81fb6bae544a0ec027eb720e2d9c74a4fcdaa9dd3899873136935 \
|
||||||
--hash=sha256:c76659ae29a84f2c14f56aad305dd00eb685bd88f8c0a3281a9a4bc6bd7d2aa7 \
|
--hash=sha256:f34e369891f77d0738e5d25727c307d06d5344948771e5379ea29c76c6d84555 \
|
||||||
--hash=sha256:c84a0174109f329eeda169004c7b7ca2e884a6305acab4a39600be67f915ed38 \
|
--hash=sha256:f8a509aeaac364fa965454e80cd110fe6d48ba2c80f56c9b8563423f0b5c3cfd \
|
||||||
--hash=sha256:cd2a9f7f0d4dacc5b9ce7f0e767ae6cc64153264151f50698898c42cabffec0c \
|
--hash=sha256:f8afb07114ea9b924a4a0305ceb15354ccf0ef3c0e14d54b8dbeb03e50182dd7 \
|
||||||
--hash=sha256:d322ba72cde4ca2eefc2196dad9ad7e52451acd2f04e3688d590290625d0c970 \
|
--hash=sha256:f99e59f8a5f4dcd9cbdec445f3d8ac950a492fc0e211032384d6992ed3c17eb7
|
||||||
--hash=sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679 \
|
|
||||||
--hash=sha256:d46ae44d66bf6058a812467f6ae84e4e157dee281bfb1cfaeca07dee07452e85 \
|
|
||||||
--hash=sha256:da917f6df8c6b2002043193cb0d74cc173b3af7eb5800ad69c4e1fbac2a71c30 \
|
|
||||||
--hash=sha256:dea4a59da7850192fdead9da888e6b96166e90608cf39e17b503f45826b16f84 \
|
|
||||||
--hash=sha256:e05f6825f8db4428782135e6986fec79b139210398f3710ed4aa6ef41473c008 \
|
|
||||||
--hash=sha256:e1cf59e0bb12e031a48bb628aae32df3d0c98fd6c759cb89f464b1047f0ca9c8 \
|
|
||||||
--hash=sha256:e252d66276c992319ed6cd69a3ffa17538943954075051e992143ccbf6dc3d3e \
|
|
||||||
--hash=sha256:e262398e5d51563093edf30612cd1e20fedd932ad0994697d7781ca4880cdc3d \
|
|
||||||
--hash=sha256:e28ff8f3de7b56588c2a398dc135fd9f157d12c612bd3daa7e6ba9872337f6f5 \
|
|
||||||
--hash=sha256:eea5f14933177ffe5c40b200f04f814258cc14b14a71024ad109f308e8bad414 \
|
|
||||||
--hash=sha256:f876ebbf92db70125f6375f91ab4bc6b27648aa68f90d661b1fc5affb4c9731c \
|
|
||||||
--hash=sha256:f8ff3bc08b43f36fdc24fedb86d42749298a458c4724fb588c4d76823ac39f54
|
|
||||||
# via psycopg
|
# via psycopg
|
||||||
psycopg-pool==3.2.1 \
|
psycopg-pool==3.2.2 \
|
||||||
--hash=sha256:060b551d1b97a8d358c668be58b637780b884de14d861f4f5ecc48b7563aafb7 \
|
--hash=sha256:273081d0fbfaced4f35e69200c89cb8fbddfe277c38cc86c235b90a2ec2c8153 \
|
||||||
--hash=sha256:6509a75c073590952915eddbba7ce8b8332a440a31e77bba69561483492829ad
|
--hash=sha256:9e22c370045f6d7f2666a5ad1b0caf345f9f1912195b0b25d0d3bcc4f3a7389c
|
||||||
# via psycopg
|
# via psycopg
|
||||||
pyasn1==0.6.0 \
|
pyasn1==0.6.0 \
|
||||||
--hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \
|
--hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \
|
||||||
@ -140,93 +130,96 @@ python-ldap==3.4.4 \
|
|||||||
# via
|
# via
|
||||||
# -r contrib/container/requirements.in
|
# -r contrib/container/requirements.in
|
||||||
# django-auth-ldap
|
# django-auth-ldap
|
||||||
pyyaml==6.0.1 \
|
pyyaml==6.0.2 \
|
||||||
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
|
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||||
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
|
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
|
||||||
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
|
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
|
||||||
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
|
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
|
||||||
--hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
|
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
|
||||||
--hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
|
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
|
||||||
--hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
|
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
|
||||||
--hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
|
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
|
||||||
--hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
|
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
|
||||||
--hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
|
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
|
||||||
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
|
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
|
||||||
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
|
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
|
||||||
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
|
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
|
||||||
--hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
|
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
|
||||||
--hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
|
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
|
||||||
--hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
|
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
|
||||||
--hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
|
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
|
||||||
--hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
|
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
|
||||||
--hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
|
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
|
||||||
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
|
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
|
||||||
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
|
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
|
||||||
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
|
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
|
||||||
--hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
|
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
|
||||||
--hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
|
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
|
||||||
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
|
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
|
||||||
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
|
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
|
||||||
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
|
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
|
||||||
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
|
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
|
||||||
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
|
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
|
||||||
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
|
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
|
||||||
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
|
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
|
||||||
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
|
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
|
||||||
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
|
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
|
||||||
--hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
|
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
|
||||||
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
|
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
|
||||||
--hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
|
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
|
||||||
--hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
|
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
|
||||||
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
|
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
|
||||||
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
|
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
|
||||||
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
|
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
|
||||||
--hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
|
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
|
||||||
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
|
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
|
||||||
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
|
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
|
||||||
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
|
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
|
||||||
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
|
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
|
||||||
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
|
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
|
||||||
--hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
|
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
|
||||||
--hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
|
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
|
||||||
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
|
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
|
||||||
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
|
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
|
||||||
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
|
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
|
||||||
|
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
|
||||||
|
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
setuptools==70.3.0 \
|
setuptools==73.0.1 \
|
||||||
--hash=sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5 \
|
--hash=sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e \
|
||||||
--hash=sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc
|
--hash=sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
sqlparse==0.5.0 \
|
sqlparse==0.5.1 \
|
||||||
--hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \
|
--hash=sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4 \
|
||||||
--hash=sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663
|
--hash=sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e
|
||||||
# via django
|
# via django
|
||||||
typing-extensions==4.11.0 \
|
typing-extensions==4.12.2 \
|
||||||
--hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \
|
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||||
--hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a
|
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
||||||
# via
|
# via
|
||||||
# psycopg
|
# psycopg
|
||||||
# psycopg-pool
|
# psycopg-pool
|
||||||
uv==0.1.38 \
|
uv==0.3.0 \
|
||||||
--hash=sha256:03242a734a572733f2b9a5dbb94517e918fe26fc01114b7c51d12296dfbb8f8b \
|
--hash=sha256:084551ee0743339aa5d0d4c76a94c9f9df16c33030b850f0cd98f316db7b42cc \
|
||||||
--hash=sha256:067af2d986329db4fa3c7373017d49f0e16ddff23e483b7e5bc3a5a18ce08ea6 \
|
--hash=sha256:0da4f060d583325846cde0727a8cc0cb4e8c63b30ac9373dae213a7315056d90 \
|
||||||
--hash=sha256:0937ad16ae0e0b6bb6dd3c386f8fb33141ad08d1762eaacffb4d2b27fb466a17 \
|
--hash=sha256:160a1f3b01298942d6cfe21f95a9b7daa3eb73231ba1fc4689157eb9f23b3438 \
|
||||||
--hash=sha256:0e1d64ac437b0a14fbcec55b1c3f162fa24860711e0d855fcd9c672b149a122a \
|
--hash=sha256:21ebc6ca30df7ff57a8e17e3abeeba8a9d1d4ac79c1adf842fa42d48a5c7f372 \
|
||||||
--hash=sha256:1be7aa46936c0351ccb1400ea95e5381b3f05fef772fa3b9f23af728cc175dea \
|
--hash=sha256:24a1388f5e285058f97576b7dfee79bb5007a712a9e368f3fcdcfeb2dfd9ce92 \
|
||||||
--hash=sha256:309e73a3ec3a5a536a3efaf434270fc94b483069f1425765165c1c9d786c27fd \
|
--hash=sha256:2f937ebdf9976ec1ffe7228fd608ef3e6ce2a61ed68cf7b157ae6900a9c80f41 \
|
||||||
--hash=sha256:4251f9771d392d7badc1e5fb934b397b12ca00fef9d955207ade169cc1f7e872 \
|
--hash=sha256:39a4276afe0808ca6c033e0cd6cb73249f934b4a0c9d7b18a944f3f8ea635e27 \
|
||||||
--hash=sha256:43772e7589f70e954b1ae29230e575ef9e4d8d769138a94dfa5ae7eaf1e26ac5 \
|
--hash=sha256:3b62e44f61a154303fc9f4aa87ae54891957d49769d21dcf2be9c22e640c3e92 \
|
||||||
--hash=sha256:4a6024256d38b77151e32876be9fcb99cf75df7a86b26e0161cc202bed558adf \
|
--hash=sha256:4303364d717b1def58e82b11271259d2ee3bb03da0ca6111819ee254f65b38f4 \
|
||||||
--hash=sha256:5a98d6aacd4b57b7e00daf154919e7c9206fefdf40bd28cfb13efe0e0324d491 \
|
--hash=sha256:503fc619238550be222b41422b415677c9b8045c92a9815f80ff5d7477671fe6 \
|
||||||
--hash=sha256:8de6dbd8f348ee90af044f4cc7b6650521d25ba2d20a813c1e157a3f90069dd9 \
|
--hash=sha256:52b3a6110705ff27462ddc68657fedf8a296ed545619a90fa73354f130ad632e \
|
||||||
--hash=sha256:9133e24db9bdd4f412eab69586d03294419825432a9a27ee1b510a4c01eb7b0b \
|
--hash=sha256:5c826d9daace67d67790503b0c1152093b3cecd35a91de10f5bb9e26afea9de9 \
|
||||||
--hash=sha256:92f65b6e4e5c8126501785af3629dc537d7c82caa56ac9336a86929c73d0e138 \
|
--hash=sha256:6d1025349cbaeba9a974d413795d0ce8d37de5ad7fb7654c0519968b2c083ba1 \
|
||||||
--hash=sha256:afd85029923e712b6b2c45ddc1680c785392220876c766521e45778db3f71f8e \
|
--hash=sha256:a15b2321444f3668bc95863d2b13ce44ea54053189427ea48d112ecd8b3d2f89 \
|
||||||
--hash=sha256:b0b15e51a0f8240969bc412ed0dd60cfe3f664b30173139ef263d71c596d631f \
|
--hash=sha256:a71b7080ee6d7658b22f93aa750cbfd19111cd6c8ac643a73d6778598dd06559 \
|
||||||
--hash=sha256:ea44c07605d1359a7d82bf42706dd86d341f15f4ca2e1f36e51626a7111c2ad5 \
|
--hash=sha256:b44ebf501de5eef33e4f3cf4b6ea9a458d1f1b3cf26737c25ac507ab7914076a \
|
||||||
--hash=sha256:f87c9711493c53d32012a96b49c4d53aabdf7ed666cbf2c3fb55dd402a6b31a8
|
--hash=sha256:d3da56b87ec5aa4f2ae572127c754655bad3820dd41a4d37ed4d5e2f67035990 \
|
||||||
|
--hash=sha256:d87ff76da5128036c05db0291db7510a85cb8efb86538e8f49adc8074bb292f0
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
wheel==0.43.0 \
|
wheel==0.44.0 \
|
||||||
--hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \
|
--hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \
|
||||||
--hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81
|
--hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
|
@ -104,9 +104,9 @@ jc==1.25.3 \
|
|||||||
--hash=sha256:ea17a8578497f2da92f73924d9d403f4563ba59422fbceff7bb4a16cdf84a54f \
|
--hash=sha256:ea17a8578497f2da92f73924d9d403f4563ba59422fbceff7bb4a16cdf84a54f \
|
||||||
--hash=sha256:fa3140ceda6cba1210d1362f363cd79a0514741e8a1dd6167db2b2e2d5f24f7b
|
--hash=sha256:fa3140ceda6cba1210d1362f363cd79a0514741e8a1dd6167db2b2e2d5f24f7b
|
||||||
# via -r contrib/dev_reqs/requirements.in
|
# via -r contrib/dev_reqs/requirements.in
|
||||||
pygments==2.17.2 \
|
pygments==2.18.0 \
|
||||||
--hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
|
--hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \
|
||||||
--hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
|
--hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a
|
||||||
# via jc
|
# via jc
|
||||||
pyyaml==6.0.2 \
|
pyyaml==6.0.2 \
|
||||||
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
|
# Configuration file for Crowdin project integration
|
||||||
|
# See: https://crowdin.com/project/inventree
|
||||||
|
|
||||||
"commit_message": "Fix: New translations %original_file_name% from Crowdin"
|
"commit_message": "Fix: New translations %original_file_name% from Crowdin"
|
||||||
"append_commit_message": false
|
"append_commit_message": false
|
||||||
|
"preserve_hierarchy": true
|
||||||
|
|
||||||
files:
|
files:
|
||||||
- source: /src/backend/InvenTree/locale/en/LC_MESSAGES/django.po
|
- source: /src/backend/InvenTree/locale/en/LC_MESSAGES/django.po
|
||||||
|
dest: /%original_path%/%original_file_name%
|
||||||
translation: /src/backend/InvenTree/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%
|
translation: /src/backend/InvenTree/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%
|
||||||
- source: /src/frontend/src/locales/en/messages.po
|
- source: /src/frontend/src/locales/en/messages.po
|
||||||
|
dest: /%original_path%/%original_file_name%
|
||||||
translation: /src/frontend/src/locales/%two_letters_code%/%original_file_name%
|
translation: /src/frontend/src/locales/%two_letters_code%/%original_file_name%
|
||||||
|
15
docs/docs/concepts/custom_states.md
Normal file
15
docs/docs/concepts/custom_states.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: Custom States
|
||||||
|
---
|
||||||
|
|
||||||
|
## Custom States
|
||||||
|
|
||||||
|
Several models within InvenTree support the use of custom states. The custom states are display only - the business logic is not affected by the state.
|
||||||
|
|
||||||
|
States can be added in the Admin Center under the "Custom States" section. Each state has a name, label and a color that are used to display the state in the user interface. Changes to these settings will only be reflected in the user interface after a full reload of the interface.
|
||||||
|
|
||||||
|
States need to be assigned to a model, state (for example status on a StockItem) and a logical key - that will be used for business logic. These 3 values combined need to be unique throughout the system.
|
||||||
|
|
||||||
|
Custom states can be used in the following models:
|
||||||
|
- StockItem
|
||||||
|
- Orders (PurchaseOrder, SalesOrder, ReturnOrder, ReturnOrderLine)
|
@ -47,6 +47,8 @@ If you want to create your own machine type, please also take a look at the alre
|
|||||||
|
|
||||||
```py
|
```py
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from generic.states import ColorEnum
|
||||||
from plugin.machine import BaseDriver, BaseMachineType, MachineStatus
|
from plugin.machine import BaseDriver, BaseMachineType, MachineStatus
|
||||||
|
|
||||||
class ABCBaseDriver(BaseDriver):
|
class ABCBaseDriver(BaseDriver):
|
||||||
@ -72,9 +74,9 @@ class ABCMachine(BaseMachineType):
|
|||||||
base_driver = ABCBaseDriver
|
base_driver = ABCBaseDriver
|
||||||
|
|
||||||
class ABCStatus(MachineStatus):
|
class ABCStatus(MachineStatus):
|
||||||
CONNECTED = 100, _('Connected'), 'success'
|
CONNECTED = 100, _('Connected'), ColorEnum.success
|
||||||
STANDBY = 101, _('Standby'), 'success'
|
STANDBY = 101, _('Standby'), ColorEnum.success
|
||||||
PRINTING = 110, _('Printing'), 'primary'
|
PRINTING = 110, _('Printing'), ColorEnum.primary
|
||||||
|
|
||||||
MACHINE_STATUS = ABCStatus
|
MACHINE_STATUS = ABCStatus
|
||||||
default_machine_status = ABCStatus.DISCONNECTED
|
default_machine_status = ABCStatus.DISCONNECTED
|
||||||
|
@ -38,6 +38,8 @@ Refer to the source code for the Purchase Order status codes:
|
|||||||
show_source: True
|
show_source: True
|
||||||
members: []
|
members: []
|
||||||
|
|
||||||
|
Purchase Order Status supports [custom states](../concepts/custom_states.md).
|
||||||
|
|
||||||
### Purchase Order Currency
|
### Purchase Order Currency
|
||||||
|
|
||||||
The currency code can be specified for an individual purchase order. If not specified, the default currency specified against the [supplier](./company.md#suppliers) will be used.
|
The currency code can be specified for an individual purchase order. If not specified, the default currency specified against the [supplier](./company.md#suppliers) will be used.
|
||||||
|
@ -61,6 +61,8 @@ Refer to the source code for the Return Order status codes:
|
|||||||
show_source: True
|
show_source: True
|
||||||
members: []
|
members: []
|
||||||
|
|
||||||
|
Return Order Status supports [custom states](../concepts/custom_states.md).
|
||||||
|
|
||||||
## Create a Return Order
|
## Create a Return Order
|
||||||
|
|
||||||
From the Return Order index, click on <span class='badge inventree add'><span class='fas fa-plus-circle'></span> New Return Order</span> which opens the "Create Return Order" form.
|
From the Return Order index, click on <span class='badge inventree add'><span class='fas fa-plus-circle'></span> New Return Order</span> which opens the "Create Return Order" form.
|
||||||
|
@ -39,6 +39,8 @@ Refer to the source code for the Sales Order status codes:
|
|||||||
show_source: True
|
show_source: True
|
||||||
members: []
|
members: []
|
||||||
|
|
||||||
|
Sales Order Status supports [custom states](../concepts/custom_states.md).
|
||||||
|
|
||||||
### Sales Order Currency
|
### Sales Order Currency
|
||||||
|
|
||||||
The currency code can be specified for an individual sales order. If not specified, the default currency specified against the [customer](./company.md#customers) will be used.
|
The currency code can be specified for an individual sales order. If not specified, the default currency specified against the [customer](./company.md#customers) will be used.
|
||||||
|
@ -10,7 +10,7 @@ Certain stock item status codes will restrict the availability of the stock item
|
|||||||
|
|
||||||
Below is the list of available stock status codes and their meaning:
|
Below is the list of available stock status codes and their meaning:
|
||||||
|
|
||||||
| Status | Description | Available |
|
| Status | Description | Available |
|
||||||
| ----------- | ----------- | --- |
|
| ----------- | ----------- | --- |
|
||||||
| <span class='badge inventree success'>OK</span> | Stock item is healthy, nothing wrong to report | <span class='badge inventree success'>Yes</span> |
|
| <span class='badge inventree success'>OK</span> | Stock item is healthy, nothing wrong to report | <span class='badge inventree success'>Yes</span> |
|
||||||
| <span class='badge inventree warning'>Attention needed</span> | Stock item hasn't been checked or tested yet | <span class='badge inventree success'>Yes</span> |
|
| <span class='badge inventree warning'>Attention needed</span> | Stock item hasn't been checked or tested yet | <span class='badge inventree success'>Yes</span> |
|
||||||
@ -38,6 +38,8 @@ Refer to the source code for the Stock status codes:
|
|||||||
show_source: True
|
show_source: True
|
||||||
members: []
|
members: []
|
||||||
|
|
||||||
|
Stock Status supports [custom states](../concepts/custom_states.md).
|
||||||
|
|
||||||
### Default Status Code
|
### Default Status Code
|
||||||
|
|
||||||
The default status code for any newly created Stock Item is <span class='badge inventree success'>OK</span>
|
The default status code for any newly created Stock Item is <span class='badge inventree success'>OK</span>
|
||||||
|
@ -77,6 +77,7 @@ nav:
|
|||||||
- Core Concepts:
|
- Core Concepts:
|
||||||
- Terminology: concepts/terminology.md
|
- Terminology: concepts/terminology.md
|
||||||
- Physical Units: concepts/units.md
|
- Physical Units: concepts/units.md
|
||||||
|
- Custom States: concepts/custom_states.md
|
||||||
- Development:
|
- Development:
|
||||||
- Contributing: develop/contributing.md
|
- Contributing: develop/contributing.md
|
||||||
- Devcontainer: develop/devcontainer.md
|
- Devcontainer: develop/devcontainer.md
|
||||||
|
@ -1,13 +1,27 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 245
|
INVENTREE_API_VERSION = 249
|
||||||
|
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v249 - 2024-08-23 : https://github.com/inventree/InvenTree/pull/7978
|
||||||
|
- Sort status enums
|
||||||
|
|
||||||
|
v248 - 2024-08-23 : https://github.com/inventree/InvenTree/pull/7965
|
||||||
|
- Small adjustments to labels for new custom status fields
|
||||||
|
|
||||||
|
v247 - 2024-08-22 : https://github.com/inventree/InvenTree/pull/7956
|
||||||
|
- Adjust "attachment" field on StockItemTestResult serializer
|
||||||
|
- Allow null values for attachment
|
||||||
|
|
||||||
|
v246 - 2024-08-21 : https://github.com/inventree/InvenTree/pull/7862
|
||||||
|
- Adds custom status fields to various serializers
|
||||||
|
- Adds endpoints to admin custom status fields
|
||||||
|
|
||||||
v245 - 2024-08-21 : https://github.com/inventree/InvenTree/pull/7520
|
v245 - 2024-08-21 : https://github.com/inventree/InvenTree/pull/7520
|
||||||
- Documented pagination fields (no functional changes)
|
- Documented pagination fields (no functional changes)
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
"""Provides extra global data to all templates."""
|
"""Provides extra global data to all templates."""
|
||||||
|
|
||||||
import InvenTree.email
|
import InvenTree.email
|
||||||
|
import InvenTree.ready
|
||||||
import InvenTree.status
|
import InvenTree.status
|
||||||
from generic.states import StatusCode
|
from generic.states.custom import get_custom_classes
|
||||||
from InvenTree.helpers import inheritors
|
|
||||||
from users.models import RuleSet, check_user_role
|
from users.models import RuleSet, check_user_role
|
||||||
|
|
||||||
|
|
||||||
@ -53,7 +53,10 @@ def status_codes(request):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
request._inventree_status_codes = True
|
request._inventree_status_codes = True
|
||||||
return {cls.__name__: cls.template_context() for cls in inheritors(StatusCode)}
|
get_custom = InvenTree.ready.isRebuildingData() is False
|
||||||
|
return {
|
||||||
|
cls.__name__: cls.template_context() for cls in get_custom_classes(get_custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def user_roles(request):
|
def user_roles(request):
|
||||||
|
@ -953,8 +953,15 @@ def get_objectreference(
|
|||||||
Inheritors_T = TypeVar('Inheritors_T')
|
Inheritors_T = TypeVar('Inheritors_T')
|
||||||
|
|
||||||
|
|
||||||
def inheritors(cls: type[Inheritors_T]) -> set[type[Inheritors_T]]:
|
def inheritors(
|
||||||
"""Return all classes that are subclasses from the supplied cls."""
|
cls: type[Inheritors_T], subclasses: bool = True
|
||||||
|
) -> set[type[Inheritors_T]]:
|
||||||
|
"""Return all classes that are subclasses from the supplied cls.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cls: The class to search for subclasses
|
||||||
|
subclasses: Include subclasses of subclasses (default = True)
|
||||||
|
"""
|
||||||
subcls = set()
|
subcls = set()
|
||||||
work = [cls]
|
work = [cls]
|
||||||
|
|
||||||
@ -963,7 +970,8 @@ def inheritors(cls: type[Inheritors_T]) -> set[type[Inheritors_T]]:
|
|||||||
for child in parent.__subclasses__():
|
for child in parent.__subclasses__():
|
||||||
if child not in subcls:
|
if child not in subcls:
|
||||||
subcls.add(child)
|
subcls.add(child)
|
||||||
work.append(child)
|
if subclasses:
|
||||||
|
work.append(child)
|
||||||
return subcls
|
return subcls
|
||||||
|
|
||||||
|
|
||||||
|
@ -363,7 +363,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
field_info['type'] = 'related field'
|
field_info['type'] = 'related field'
|
||||||
field_info['model'] = model._meta.model_name
|
field_info['model'] = model._meta.model_name
|
||||||
|
|
||||||
# Special case for 'user' model
|
# Special case for special models
|
||||||
if field_info['model'] == 'user':
|
if field_info['model'] == 'user':
|
||||||
field_info['api_url'] = '/api/user/'
|
field_info['api_url'] = '/api/user/'
|
||||||
elif field_info['model'] == 'contenttype':
|
elif field_info['model'] == 'contenttype':
|
||||||
@ -381,6 +381,14 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
if field_info['type'] == 'dependent field':
|
if field_info['type'] == 'dependent field':
|
||||||
field_info['depends_on'] = field.depends_on
|
field_info['depends_on'] = field.depends_on
|
||||||
|
|
||||||
|
# Extend field info if the field has a get_field_info method
|
||||||
|
if (
|
||||||
|
not field_info.get('read_only')
|
||||||
|
and hasattr(field, 'get_field_info')
|
||||||
|
and callable(field.get_field_info)
|
||||||
|
):
|
||||||
|
field_info = field.get_field_info(field, field_info)
|
||||||
|
|
||||||
return field_info
|
return field_info
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 4.2.14 on 2024-08-07 22:40
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import generic.states.fields
|
||||||
|
import InvenTree.status_codes
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("build", "0051_delete_buildorderattachment"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="build",
|
||||||
|
name="status_custom_key",
|
||||||
|
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Additional status information for this item",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Custom status key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="build",
|
||||||
|
name="status",
|
||||||
|
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||||
|
choices=InvenTree.status_codes.BuildStatus.items(),
|
||||||
|
default=10,
|
||||||
|
help_text="Build status code",
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Build Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -43,6 +43,7 @@ import part.models
|
|||||||
import report.mixins
|
import report.mixins
|
||||||
import stock.models
|
import stock.models
|
||||||
import users.models
|
import users.models
|
||||||
|
import generic.states
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
@ -315,7 +316,7 @@ class Build(
|
|||||||
help_text=_('Number of stock items which have been completed')
|
help_text=_('Number of stock items which have been completed')
|
||||||
)
|
)
|
||||||
|
|
||||||
status = models.PositiveIntegerField(
|
status = generic.states.fields.InvenTreeCustomStatusModelField(
|
||||||
verbose_name=_('Build Status'),
|
verbose_name=_('Build Status'),
|
||||||
default=BuildStatus.PENDING.value,
|
default=BuildStatus.PENDING.value,
|
||||||
choices=BuildStatus.items(),
|
choices=BuildStatus.items(),
|
||||||
|
@ -2,45 +2,53 @@
|
|||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.db import transaction
|
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.db import models, transaction
|
||||||
|
from django.db.models import (
|
||||||
from django.db import models
|
BooleanField,
|
||||||
from django.db.models import ExpressionWrapper, F, FloatField
|
Case,
|
||||||
from django.db.models import Case, Sum, When, Value
|
ExpressionWrapper,
|
||||||
from django.db.models import BooleanField, Q
|
F,
|
||||||
|
FloatField,
|
||||||
|
Q,
|
||||||
|
Sum,
|
||||||
|
Value,
|
||||||
|
When,
|
||||||
|
)
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
from InvenTree.serializers import InvenTreeModelSerializer, UserSerializer
|
|
||||||
|
|
||||||
import InvenTree.helpers
|
|
||||||
import InvenTree.tasks
|
|
||||||
from InvenTree.serializers import InvenTreeDecimalField, NotesFieldMixin
|
|
||||||
from stock.status_codes import StockStatus
|
|
||||||
|
|
||||||
from stock.generators import generate_batch_code
|
|
||||||
from stock.models import StockItem, StockLocation
|
|
||||||
from stock.serializers import StockItemSerializerBrief, LocationBriefSerializer
|
|
||||||
|
|
||||||
import build.tasks
|
import build.tasks
|
||||||
import common.models
|
import common.models
|
||||||
from common.serializers import ProjectCodeSerializer
|
|
||||||
from common.settings import get_global_setting
|
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
|
||||||
import company.serializers
|
import company.serializers
|
||||||
|
import InvenTree.helpers
|
||||||
|
import InvenTree.tasks
|
||||||
import part.filters
|
import part.filters
|
||||||
import part.serializers as part_serializers
|
import part.serializers as part_serializers
|
||||||
|
from common.serializers import ProjectCodeSerializer
|
||||||
|
from common.settings import get_global_setting
|
||||||
|
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
||||||
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
|
from InvenTree.serializers import (
|
||||||
|
InvenTreeDecimalField,
|
||||||
|
InvenTreeModelSerializer,
|
||||||
|
NotesFieldMixin,
|
||||||
|
UserSerializer,
|
||||||
|
)
|
||||||
|
from stock.generators import generate_batch_code
|
||||||
|
from stock.models import StockItem, StockLocation
|
||||||
|
from stock.serializers import LocationBriefSerializer, StockItemSerializerBrief
|
||||||
|
from stock.status_codes import StockStatus
|
||||||
from users.serializers import OwnerSerializer
|
from users.serializers import OwnerSerializer
|
||||||
|
|
||||||
from .models import Build, BuildLine, BuildItem
|
from .models import Build, BuildItem, BuildLine
|
||||||
from .status_codes import BuildStatus
|
from .status_codes import BuildStatus
|
||||||
|
|
||||||
|
|
||||||
class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer):
|
class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTreeCustomStatusSerializerMixin, InvenTreeModelSerializer):
|
||||||
"""Serializes a Build object."""
|
"""Serializes a Build object."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -69,6 +77,7 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
|
|||||||
'quantity',
|
'quantity',
|
||||||
'status',
|
'status',
|
||||||
'status_text',
|
'status_text',
|
||||||
|
'status_custom_key',
|
||||||
'target_date',
|
'target_date',
|
||||||
'take_from',
|
'take_from',
|
||||||
'notes',
|
'notes',
|
||||||
@ -882,8 +891,8 @@ class BuildUnallocationSerializer(serializers.Serializer):
|
|||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
build.deallocate_stock(
|
build.deallocate_stock(
|
||||||
build_line=data['build_line'],
|
build_line=data.get('build_line', None),
|
||||||
output=data['output']
|
output=data.get('output', None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from generic.states import StatusCode
|
from generic.states import ColorEnum, StatusCode
|
||||||
|
|
||||||
|
|
||||||
class BuildStatus(StatusCode):
|
class BuildStatus(StatusCode):
|
||||||
"""Build status codes."""
|
"""Build status codes."""
|
||||||
|
|
||||||
PENDING = 10, _('Pending'), 'secondary' # Build is pending / active
|
PENDING = 10, _('Pending'), ColorEnum.secondary # Build is pending / active
|
||||||
PRODUCTION = 20, _('Production'), 'primary' # Build is in production
|
PRODUCTION = 20, _('Production'), ColorEnum.primary # Build is in production
|
||||||
ON_HOLD = 25, _('On Hold'), 'warning' # Build is on hold
|
ON_HOLD = 25, _('On Hold'), ColorEnum.warning # Build is on hold
|
||||||
CANCELLED = 30, _('Cancelled'), 'danger' # Build was cancelled
|
CANCELLED = 30, _('Cancelled'), ColorEnum.danger # Build was cancelled
|
||||||
COMPLETE = 40, _('Complete'), 'success' # Build is complete
|
COMPLETE = 40, _('Complete'), ColorEnum.success # Build is complete
|
||||||
|
|
||||||
|
|
||||||
class BuildStatusGroups:
|
class BuildStatusGroups:
|
||||||
|
@ -158,7 +158,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
<td>{% trans "Status" %}</td>
|
<td>{% trans "Status" %}</td>
|
||||||
<td>
|
<td>
|
||||||
{% status_label 'build' build.status %}
|
{% display_status_label 'build' build.status_custom_key build.status %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if build.target_date %}
|
{% if build.target_date %}
|
||||||
@ -225,7 +225,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
|
|
||||||
{% block page_data %}
|
{% block page_data %}
|
||||||
<h3>
|
<h3>
|
||||||
{% status_label 'build' build.status large=True %}
|
{% display_status_label 'build' build.status_custom_key build.status large=True %}
|
||||||
{% if build.is_overdue %}
|
{% if build.is_overdue %}
|
||||||
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
<td>{% trans "Status" %}</td>
|
<td>{% trans "Status" %}</td>
|
||||||
<td>{% status_label 'build' build.status %}</td>
|
<td>{% display_status_label 'build' build.status_custom_key build.status %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-check-circle'></span></td>
|
<td><span class='fas fa-check-circle'></span></td>
|
||||||
|
@ -29,7 +29,7 @@ import common.models
|
|||||||
import common.serializers
|
import common.serializers
|
||||||
from common.icons import get_icon_packs
|
from common.icons import get_icon_packs
|
||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
from generic.states.api import AllStatusViews, StatusView
|
from generic.states.api import urlpattern as generic_states_api_urls
|
||||||
from importer.mixins import DataExportViewMixin
|
from importer.mixins import DataExportViewMixin
|
||||||
from InvenTree.api import BulkDeleteMixin, MetadataView
|
from InvenTree.api import BulkDeleteMixin, MetadataView
|
||||||
from InvenTree.config import CONFIG_LOOKUPS
|
from InvenTree.config import CONFIG_LOOKUPS
|
||||||
@ -655,6 +655,8 @@ class ContentTypeList(ListAPI):
|
|||||||
queryset = ContentType.objects.all()
|
queryset = ContentType.objects.all()
|
||||||
serializer_class = common.serializers.ContentTypeSerializer
|
serializer_class = common.serializers.ContentTypeSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
filter_backends = SEARCH_ORDER_FILTER
|
||||||
|
search_fields = ['app_label', 'model']
|
||||||
|
|
||||||
|
|
||||||
class ContentTypeDetail(RetrieveAPI):
|
class ContentTypeDetail(RetrieveAPI):
|
||||||
@ -965,16 +967,7 @@ common_api_urls = [
|
|||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
# Status
|
# Status
|
||||||
path(
|
path('generic/status/', include(generic_states_api_urls)),
|
||||||
'generic/status/',
|
|
||||||
include([
|
|
||||||
path(
|
|
||||||
f'<str:{StatusView.MODEL_REF}>/',
|
|
||||||
include([path('', StatusView.as_view(), name='api-status')]),
|
|
||||||
),
|
|
||||||
path('', AllStatusViews.as_view(), name='api-status-all'),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
# Contenttype
|
# Contenttype
|
||||||
path(
|
path(
|
||||||
'contenttype/',
|
'contenttype/',
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
# Generated by Django 4.2.14 on 2024-08-07 22:40
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
from common.models import state_color_mappings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
("common", "0028_colortheme_user_obj"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="InvenTreeCustomUserStateModel",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"key",
|
||||||
|
models.IntegerField(
|
||||||
|
help_text="Value that will be saved in the models database",
|
||||||
|
verbose_name="Key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
models.CharField(
|
||||||
|
help_text="Name of the state",
|
||||||
|
max_length=250,
|
||||||
|
verbose_name="Name",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"label",
|
||||||
|
models.CharField(
|
||||||
|
help_text="Label that will be displayed in the frontend",
|
||||||
|
max_length=250,
|
||||||
|
verbose_name="Label",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"color",
|
||||||
|
models.CharField(
|
||||||
|
choices=state_color_mappings(),
|
||||||
|
default="secondary",
|
||||||
|
help_text="Color that will be displayed in the frontend",
|
||||||
|
max_length=10,
|
||||||
|
verbose_name="Color",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"logical_key",
|
||||||
|
models.IntegerField(
|
||||||
|
help_text="State logical key that is equal to this custom state in business logic",
|
||||||
|
verbose_name="Logical Key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"reference_status",
|
||||||
|
models.CharField(
|
||||||
|
help_text="Status set that is extended with this custom state",
|
||||||
|
max_length=250,
|
||||||
|
verbose_name="Reference Status Set",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"model",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="Model this state is associated with",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
verbose_name="Model",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Custom State",
|
||||||
|
"verbose_name_plural": "Custom States",
|
||||||
|
"unique_together": {
|
||||||
|
("model", "reference_status", "key", "logical_key")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -53,6 +53,8 @@ import order.validators
|
|||||||
import plugin.base.barcodes.helper
|
import plugin.base.barcodes.helper
|
||||||
import report.helpers
|
import report.helpers
|
||||||
import users.models
|
import users.models
|
||||||
|
from generic.states import ColorEnum
|
||||||
|
from generic.states.custom import get_custom_classes, state_color_mappings
|
||||||
from InvenTree.sanitizer import sanitize_svg
|
from InvenTree.sanitizer import sanitize_svg
|
||||||
from plugin import registry
|
from plugin import registry
|
||||||
|
|
||||||
@ -3339,3 +3341,109 @@ class Attachment(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel
|
|||||||
raise ValidationError(_('Invalid model type specified for attachment'))
|
raise ValidationError(_('Invalid model type specified for attachment'))
|
||||||
|
|
||||||
return model_class.check_attachment_permission(permission, user)
|
return model_class.check_attachment_permission(permission, user)
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeCustomUserStateModel(models.Model):
|
||||||
|
"""Custom model to extends any registered state with extra custom, user defined states."""
|
||||||
|
|
||||||
|
key = models.IntegerField(
|
||||||
|
verbose_name=_('Key'),
|
||||||
|
help_text=_('Value that will be saved in the models database'),
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=250, verbose_name=_('Name'), help_text=_('Name of the state')
|
||||||
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=250,
|
||||||
|
verbose_name=_('Label'),
|
||||||
|
help_text=_('Label that will be displayed in the frontend'),
|
||||||
|
)
|
||||||
|
color = models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
choices=state_color_mappings(),
|
||||||
|
default=ColorEnum.secondary.value,
|
||||||
|
verbose_name=_('Color'),
|
||||||
|
help_text=_('Color that will be displayed in the frontend'),
|
||||||
|
)
|
||||||
|
logical_key = models.IntegerField(
|
||||||
|
verbose_name=_('Logical Key'),
|
||||||
|
help_text=_(
|
||||||
|
'State logical key that is equal to this custom state in business logic'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
model = models.ForeignKey(
|
||||||
|
ContentType,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('Model'),
|
||||||
|
help_text=_('Model this state is associated with'),
|
||||||
|
)
|
||||||
|
reference_status = models.CharField(
|
||||||
|
max_length=250,
|
||||||
|
verbose_name=_('Reference Status Set'),
|
||||||
|
help_text=_('Status set that is extended with this custom state'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options for this mixin."""
|
||||||
|
|
||||||
|
verbose_name = _('Custom State')
|
||||||
|
verbose_name_plural = _('Custom States')
|
||||||
|
unique_together = [['model', 'reference_status', 'key', 'logical_key']]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""Return string representation of the custom state."""
|
||||||
|
return f'{self.model.name} ({self.reference_status}): {self.name} | {self.key} ({self.logical_key})'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs) -> None:
|
||||||
|
"""Ensure that the custom state is valid before saving."""
|
||||||
|
self.clean()
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self) -> None:
|
||||||
|
"""Validate custom state data."""
|
||||||
|
if self.model is None:
|
||||||
|
raise ValidationError({'model': _('Model must be selected')})
|
||||||
|
|
||||||
|
if self.key is None:
|
||||||
|
raise ValidationError({'key': _('Key must be selected')})
|
||||||
|
|
||||||
|
if self.logical_key is None:
|
||||||
|
raise ValidationError({'logical_key': _('Logical key must be selected')})
|
||||||
|
|
||||||
|
# Ensure that the key is not the same as the logical key
|
||||||
|
if self.key == self.logical_key:
|
||||||
|
raise ValidationError({'key': _('Key must be different from logical key')})
|
||||||
|
|
||||||
|
if self.reference_status is None or self.reference_status == '':
|
||||||
|
raise ValidationError({
|
||||||
|
'reference_status': _('Reference status must be selected')
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ensure that the key is not in the range of the logical keys of the reference status
|
||||||
|
ref_set = list(
|
||||||
|
filter(
|
||||||
|
lambda x: x.__name__ == self.reference_status,
|
||||||
|
get_custom_classes(include_custom=False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if len(ref_set) == 0:
|
||||||
|
raise ValidationError({
|
||||||
|
'reference_status': _('Reference status set not found')
|
||||||
|
})
|
||||||
|
ref_set = ref_set[0]
|
||||||
|
if self.key in ref_set.keys():
|
||||||
|
raise ValidationError({
|
||||||
|
'key': _(
|
||||||
|
'Key must be different from the logical keys of the reference status'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if self.logical_key not in ref_set.keys():
|
||||||
|
raise ValidationError({
|
||||||
|
'logical_key': _(
|
||||||
|
'Logical key must be in the logical keys of the reference status'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return super().clean()
|
||||||
|
@ -14,6 +14,7 @@ from taggit.serializers import TagListSerializerField
|
|||||||
|
|
||||||
import common.models as common_models
|
import common.models as common_models
|
||||||
import common.validators
|
import common.validators
|
||||||
|
import generic.states.custom
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
from importer.registry import register_importer
|
from importer.registry import register_importer
|
||||||
from InvenTree.helpers import get_objectreference
|
from InvenTree.helpers import get_objectreference
|
||||||
@ -308,6 +309,32 @@ class ProjectCodeSerializer(DataImportExportSerializerMixin, InvenTreeModelSeria
|
|||||||
responsible_detail = OwnerSerializer(source='responsible', read_only=True)
|
responsible_detail = OwnerSerializer(source='responsible', read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
@register_importer()
|
||||||
|
class CustomStateSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer):
|
||||||
|
"""Serializer for the custom state model."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta options for CustomStateSerializer."""
|
||||||
|
|
||||||
|
model = common_models.InvenTreeCustomUserStateModel
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'key',
|
||||||
|
'name',
|
||||||
|
'label',
|
||||||
|
'color',
|
||||||
|
'logical_key',
|
||||||
|
'model',
|
||||||
|
'model_name',
|
||||||
|
'reference_status',
|
||||||
|
]
|
||||||
|
|
||||||
|
model_name = serializers.CharField(read_only=True, source='model.name')
|
||||||
|
reference_status = serializers.ChoiceField(
|
||||||
|
choices=generic.states.custom.state_reference_mappings()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FlagSerializer(serializers.Serializer):
|
class FlagSerializer(serializers.Serializer):
|
||||||
"""Serializer for feature flags."""
|
"""Serializer for feature flags."""
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ from .models import (
|
|||||||
Attachment,
|
Attachment,
|
||||||
ColorTheme,
|
ColorTheme,
|
||||||
CustomUnit,
|
CustomUnit,
|
||||||
|
InvenTreeCustomUserStateModel,
|
||||||
InvenTreeSetting,
|
InvenTreeSetting,
|
||||||
InvenTreeUserSetting,
|
InvenTreeUserSetting,
|
||||||
NotesImage,
|
NotesImage,
|
||||||
@ -1586,3 +1587,93 @@ class ValidatorsTest(TestCase):
|
|||||||
common.validators.validate_icon('ti:package:non-existing-variant')
|
common.validators.validate_icon('ti:package:non-existing-variant')
|
||||||
|
|
||||||
common.validators.validate_icon('ti:package:outline')
|
common.validators.validate_icon('ti:package:outline')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomStatusTest(TestCase):
|
||||||
|
"""Unit tests for the custom status model."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Setup for all tests."""
|
||||||
|
self.data = {
|
||||||
|
'key': 11,
|
||||||
|
'name': 'OK - advanced',
|
||||||
|
'label': 'OK - adv.',
|
||||||
|
'color': 'secondary',
|
||||||
|
'logical_key': 10,
|
||||||
|
'model': ContentType.objects.get(model='stockitem'),
|
||||||
|
'reference_status': 'StockStatus',
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_validation_model(self):
|
||||||
|
"""Test that model is present."""
|
||||||
|
data = self.data
|
||||||
|
data.pop('model')
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation_key(self):
|
||||||
|
"""Tests Model must have a key."""
|
||||||
|
data = self.data
|
||||||
|
data.pop('key')
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation_logicalkey(self):
|
||||||
|
"""Tests Logical key must be present."""
|
||||||
|
data = self.data
|
||||||
|
data.pop('logical_key')
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation_reference(self):
|
||||||
|
"""Tests Reference status must be present."""
|
||||||
|
data = self.data
|
||||||
|
data.pop('reference_status')
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation_logical_unique(self):
|
||||||
|
"""Tests Logical key must be unique."""
|
||||||
|
data = self.data
|
||||||
|
data['logical_key'] = data['key']
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation_reference_exsists(self):
|
||||||
|
"""Tests Reference status set not found."""
|
||||||
|
data = self.data
|
||||||
|
data['reference_status'] = 'abcd'
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation_key_unique(self):
|
||||||
|
"""Tests Key must be different from the logical keys of the reference."""
|
||||||
|
data = self.data
|
||||||
|
data['key'] = 50
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation_logical_key_exsists(self):
|
||||||
|
"""Tests Logical key must be in the logical keys of the reference status."""
|
||||||
|
data = self.data
|
||||||
|
data['logical_key'] = 12
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_validation(self):
|
||||||
|
"""Tests Valid run."""
|
||||||
|
data = self.data
|
||||||
|
instance = InvenTreeCustomUserStateModel.objects.create(**data)
|
||||||
|
self.assertEqual(data['key'], instance.key)
|
||||||
|
self.assertEqual(InvenTreeCustomUserStateModel.objects.count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
instance.__str__(), 'Stock Item (StockStatus): OK - advanced | 11 (10)'
|
||||||
|
)
|
||||||
|
@ -6,7 +6,13 @@ There is a rendered state for each state value. The rendered state is used for d
|
|||||||
States can be extended with custom options for each InvenTree instance - those options are stored in the database and need to link back to state values.
|
States can be extended with custom options for each InvenTree instance - those options are stored in the database and need to link back to state values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .states import StatusCode
|
from .states import ColorEnum, StatusCode
|
||||||
from .transition import StateTransitionMixin, TransitionMethod, storage
|
from .transition import StateTransitionMixin, TransitionMethod, storage
|
||||||
|
|
||||||
__all__ = ['StatusCode', 'storage', 'TransitionMethod', 'StateTransitionMixin']
|
__all__ = [
|
||||||
|
'ColorEnum',
|
||||||
|
'StatusCode',
|
||||||
|
'storage',
|
||||||
|
'TransitionMethod',
|
||||||
|
'StateTransitionMixin',
|
||||||
|
]
|
||||||
|
@ -2,12 +2,22 @@
|
|||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||||
from rest_framework import permissions, serializers
|
from rest_framework import permissions, serializers
|
||||||
from rest_framework.generics import GenericAPIView
|
from rest_framework.generics import GenericAPIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
import common.models
|
||||||
|
import common.serializers
|
||||||
|
from generic.states.custom import get_status_api_response
|
||||||
|
from importer.mixins import DataExportViewMixin
|
||||||
|
from InvenTree.filters import SEARCH_ORDER_FILTER
|
||||||
|
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateDestroyAPI
|
||||||
|
from InvenTree.permissions import IsStaffOrReadOnly
|
||||||
from InvenTree.serializers import EmptySerializer
|
from InvenTree.serializers import EmptySerializer
|
||||||
|
from machine.machine_type import MachineStatus
|
||||||
|
|
||||||
from .states import StatusCode
|
from .states import StatusCode
|
||||||
|
|
||||||
@ -73,18 +83,52 @@ class AllStatusViews(StatusView):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Perform a GET request to learn information about status codes."""
|
"""Perform a GET request to learn information about status codes."""
|
||||||
data = {}
|
data = get_status_api_response()
|
||||||
|
# Extend with MachineStatus classes
|
||||||
def discover_status_codes(parent_status_class, prefix=None):
|
data.update(get_status_api_response(MachineStatus, prefix=['MachineStatus']))
|
||||||
"""Recursively discover status classes."""
|
|
||||||
for status_class in parent_status_class.__subclasses__():
|
|
||||||
name = '__'.join([*(prefix or []), status_class.__name__])
|
|
||||||
data[name] = {
|
|
||||||
'class': status_class.__name__,
|
|
||||||
'values': status_class.dict(),
|
|
||||||
}
|
|
||||||
discover_status_codes(status_class, [name])
|
|
||||||
|
|
||||||
discover_status_codes(StatusCode)
|
|
||||||
|
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
|
|
||||||
|
# Custom states
|
||||||
|
class CustomStateList(DataExportViewMixin, ListCreateAPI):
|
||||||
|
"""List view for all custom states."""
|
||||||
|
|
||||||
|
queryset = common.models.InvenTreeCustomUserStateModel.objects.all()
|
||||||
|
serializer_class = common.serializers.CustomStateSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated, IsStaffOrReadOnly]
|
||||||
|
filter_backends = SEARCH_ORDER_FILTER
|
||||||
|
ordering_fields = ['key']
|
||||||
|
search_fields = ['key', 'name', 'label', 'reference_status']
|
||||||
|
|
||||||
|
|
||||||
|
class CustomStateDetail(RetrieveUpdateDestroyAPI):
|
||||||
|
"""Detail view for a particular custom states."""
|
||||||
|
|
||||||
|
queryset = common.models.InvenTreeCustomUserStateModel.objects.all()
|
||||||
|
serializer_class = common.serializers.CustomStateSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated, IsStaffOrReadOnly]
|
||||||
|
|
||||||
|
|
||||||
|
urlpattern = [
|
||||||
|
# Custom state
|
||||||
|
path(
|
||||||
|
'custom/',
|
||||||
|
include([
|
||||||
|
path(
|
||||||
|
'<int:pk>/', CustomStateDetail.as_view(), name='api-custom-state-detail'
|
||||||
|
),
|
||||||
|
path('', CustomStateList.as_view(), name='api-custom-state-list'),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
# Generic status views
|
||||||
|
path(
|
||||||
|
'',
|
||||||
|
include([
|
||||||
|
path(
|
||||||
|
f'<str:{StatusView.MODEL_REF}>/',
|
||||||
|
include([path('', StatusView.as_view(), name='api-status')]),
|
||||||
|
),
|
||||||
|
path('', AllStatusViews.as_view(), name='api-status-all'),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
89
src/backend/InvenTree/generic/states/custom.py
Normal file
89
src/backend/InvenTree/generic/states/custom.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
"""Helper functions for custom status labels."""
|
||||||
|
|
||||||
|
from InvenTree.helpers import inheritors
|
||||||
|
|
||||||
|
from .states import ColorEnum, StatusCode
|
||||||
|
|
||||||
|
|
||||||
|
def get_custom_status_labels(include_custom: bool = True):
|
||||||
|
"""Return a dict of custom status labels."""
|
||||||
|
return {cls.tag(): cls for cls in get_custom_classes(include_custom)}
|
||||||
|
|
||||||
|
|
||||||
|
def get_status_api_response(base_class=StatusCode, prefix=None):
|
||||||
|
"""Return a dict of status classes (custom and class defined).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_class: The base class to search for subclasses.
|
||||||
|
prefix: A list of strings to prefix the class names with.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'__'.join([*(prefix or []), k.__name__]): {
|
||||||
|
'class': k.__name__,
|
||||||
|
'values': k.dict(),
|
||||||
|
}
|
||||||
|
for k in get_custom_classes(base_class=base_class, subclass=False)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def state_color_mappings():
|
||||||
|
"""Return a list of custom user state colors."""
|
||||||
|
return [(a.name, a.value) for a in ColorEnum]
|
||||||
|
|
||||||
|
|
||||||
|
def state_reference_mappings():
|
||||||
|
"""Return a list of custom user state references."""
|
||||||
|
classes = get_custom_classes(include_custom=False)
|
||||||
|
return [(a.__name__, a.__name__) for a in sorted(classes, key=lambda x: x.__name__)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_logical_value(value, model: str):
|
||||||
|
"""Return the state model for the selected value."""
|
||||||
|
from common.models import InvenTreeCustomUserStateModel
|
||||||
|
|
||||||
|
return InvenTreeCustomUserStateModel.objects.get(key=value, model__model=model)
|
||||||
|
|
||||||
|
|
||||||
|
def get_custom_classes(
|
||||||
|
include_custom: bool = True, base_class=StatusCode, subclass=False
|
||||||
|
):
|
||||||
|
"""Return a dict of status classes (custom and class defined)."""
|
||||||
|
discovered_classes = inheritors(base_class, subclass)
|
||||||
|
|
||||||
|
if not include_custom:
|
||||||
|
return discovered_classes
|
||||||
|
|
||||||
|
# Gather DB settings
|
||||||
|
from common.models import InvenTreeCustomUserStateModel
|
||||||
|
|
||||||
|
custom_db_states = {}
|
||||||
|
custom_db_mdls = {}
|
||||||
|
for item in list(InvenTreeCustomUserStateModel.objects.all()):
|
||||||
|
if not custom_db_states.get(item.reference_status):
|
||||||
|
custom_db_states[item.reference_status] = []
|
||||||
|
custom_db_states[item.reference_status].append(item)
|
||||||
|
custom_db_mdls[item.model.app_label] = item.reference_status
|
||||||
|
custom_db_mdls_keys = custom_db_mdls.keys()
|
||||||
|
|
||||||
|
states = {}
|
||||||
|
for cls in discovered_classes:
|
||||||
|
tag = cls.tag()
|
||||||
|
states[tag] = cls
|
||||||
|
if custom_db_mdls and tag in custom_db_mdls_keys:
|
||||||
|
data = [(str(m.name), (m.value, m.label, m.color)) for m in states[tag]]
|
||||||
|
data_keys = [i[0] for i in data]
|
||||||
|
|
||||||
|
# Extent with non present tags
|
||||||
|
for entry in custom_db_states[custom_db_mdls[tag]]:
|
||||||
|
ref_name = str(entry.name.upper().replace(' ', ''))
|
||||||
|
if ref_name not in data_keys:
|
||||||
|
data += [
|
||||||
|
(
|
||||||
|
str(entry.name.upper().replace(' ', '')),
|
||||||
|
(entry.key, entry.label, entry.color),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Re-assemble the enum
|
||||||
|
states[tag] = base_class(f'{tag.capitalize()}Status', data)
|
||||||
|
return states.values()
|
249
src/backend/InvenTree/generic/states/fields.py
Normal file
249
src/backend/InvenTree/generic/states/fields.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
"""Custom model/serializer fields for InvenTree models that support custom states."""
|
||||||
|
|
||||||
|
from typing import Any, Iterable, Optional
|
||||||
|
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.fields import ChoiceField
|
||||||
|
|
||||||
|
from .custom import get_logical_value
|
||||||
|
|
||||||
|
|
||||||
|
class CustomChoiceField(serializers.ChoiceField):
|
||||||
|
"""Custom Choice Field.
|
||||||
|
|
||||||
|
This is not intended to be used directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, choices: Iterable, **kwargs):
|
||||||
|
"""Initialize the field."""
|
||||||
|
choice_mdl = kwargs.pop('choice_mdl', None)
|
||||||
|
choice_field = kwargs.pop('choice_field', None)
|
||||||
|
is_custom = kwargs.pop('is_custom', False)
|
||||||
|
kwargs.pop('max_value', None)
|
||||||
|
kwargs.pop('min_value', None)
|
||||||
|
super().__init__(choices, **kwargs)
|
||||||
|
self.choice_mdl = choice_mdl
|
||||||
|
self.choice_field = choice_field
|
||||||
|
self.is_custom = is_custom
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
"""Map the choice (that might be a custom one) back to the logical value."""
|
||||||
|
try:
|
||||||
|
return super().to_internal_value(data)
|
||||||
|
except serializers.ValidationError:
|
||||||
|
try:
|
||||||
|
logical = get_logical_value(data, self.choice_mdl._meta.model_name)
|
||||||
|
if self.is_custom:
|
||||||
|
return logical.key
|
||||||
|
return logical.logical_key
|
||||||
|
except (ObjectDoesNotExist, Exception):
|
||||||
|
raise serializers.ValidationError('Invalid choice')
|
||||||
|
|
||||||
|
def get_field_info(self, field, field_info):
|
||||||
|
"""Return the field information for the given item."""
|
||||||
|
from common.models import InvenTreeCustomUserStateModel
|
||||||
|
|
||||||
|
# Static choices
|
||||||
|
choices = [
|
||||||
|
{
|
||||||
|
'value': choice_value,
|
||||||
|
'display_name': force_str(choice_name, strings_only=True),
|
||||||
|
}
|
||||||
|
for choice_value, choice_name in field.choices.items()
|
||||||
|
]
|
||||||
|
# Dynamic choices from InvenTreeCustomUserStateModel
|
||||||
|
objs = InvenTreeCustomUserStateModel.objects.filter(
|
||||||
|
model__model=field.choice_mdl._meta.model_name
|
||||||
|
)
|
||||||
|
dyn_choices = [
|
||||||
|
{'value': choice.key, 'display_name': choice.label} for choice in objs.all()
|
||||||
|
]
|
||||||
|
|
||||||
|
if dyn_choices:
|
||||||
|
all_choices = choices + dyn_choices
|
||||||
|
field_info['choices'] = sorted(all_choices, key=lambda kv: kv['value'])
|
||||||
|
else:
|
||||||
|
field_info['choices'] = choices
|
||||||
|
return field_info
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraCustomChoiceField(CustomChoiceField):
|
||||||
|
"""Custom Choice Field that returns value of status if empty.
|
||||||
|
|
||||||
|
This is not intended to be used directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_representation(self, value):
|
||||||
|
"""Return the value of the status if it is empty."""
|
||||||
|
return super().to_representation(value) or value
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeCustomStatusModelField(models.PositiveIntegerField):
|
||||||
|
"""Custom model field for extendable status codes.
|
||||||
|
|
||||||
|
Adds a secondary *_custom_key field to the model which can be used to store additional status information.
|
||||||
|
Models using this model field must also include the InvenTreeCustomStatusSerializerMixin in all serializers that create or update the value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def deconstruct(self):
|
||||||
|
"""Deconstruct the field for migrations."""
|
||||||
|
name, path, args, kwargs = super().deconstruct()
|
||||||
|
|
||||||
|
return name, path, args, kwargs
|
||||||
|
|
||||||
|
def contribute_to_class(self, cls, name):
|
||||||
|
"""Add the _custom_key field to the model."""
|
||||||
|
cls._meta.supports_custom_status = True
|
||||||
|
|
||||||
|
if not hasattr(self, '_custom_key_field'):
|
||||||
|
self.add_field(cls, name)
|
||||||
|
|
||||||
|
super().contribute_to_class(cls, name)
|
||||||
|
|
||||||
|
def clean(self, value: Any, model_instance: Any) -> Any:
|
||||||
|
"""Ensure that the value is not an empty string."""
|
||||||
|
if value == '':
|
||||||
|
value = None
|
||||||
|
return super().clean(value, model_instance)
|
||||||
|
|
||||||
|
def add_field(self, cls, name):
|
||||||
|
"""Adds custom_key_field to the model class to save additional status information."""
|
||||||
|
custom_key_field = ExtraInvenTreeCustomStatusModelField(
|
||||||
|
default=None,
|
||||||
|
verbose_name=_('Custom status key'),
|
||||||
|
help_text=_('Additional status information for this item'),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
cls.add_to_class(f'{name}_custom_key', custom_key_field)
|
||||||
|
self._custom_key_field = custom_key_field
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraInvenTreeCustomStatusModelField(models.PositiveIntegerField):
|
||||||
|
"""Custom field used to detect custom extenteded fields.
|
||||||
|
|
||||||
|
This is not intended to be used directly, if you want to support custom states in your model use InvenTreeCustomStatusModelField.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeCustomStatusSerializerMixin:
|
||||||
|
"""Mixin to ensure custom status fields are set.
|
||||||
|
|
||||||
|
This mixin must be used to ensure that custom status fields are set correctly when updating a model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_custom_fields: Optional[list] = None
|
||||||
|
_custom_fields_leader: Optional[list] = None
|
||||||
|
_custom_fields_follower: Optional[list] = None
|
||||||
|
_is_gathering = False
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
"""Ensure the custom field is updated if the leader was changed."""
|
||||||
|
self.gather_custom_fields()
|
||||||
|
# Mirror values from leader to follower
|
||||||
|
for field in self._custom_fields_leader:
|
||||||
|
follower_field_name = f'{field}_custom_key'
|
||||||
|
if (
|
||||||
|
field in self.initial_data
|
||||||
|
and self.instance
|
||||||
|
and self.initial_data[field]
|
||||||
|
!= getattr(self.instance, follower_field_name, None)
|
||||||
|
):
|
||||||
|
setattr(self.instance, follower_field_name, self.initial_data[field])
|
||||||
|
|
||||||
|
# Mirror values from follower to leader
|
||||||
|
for field in self._custom_fields_follower:
|
||||||
|
leader_field_name = field.replace('_custom_key', '')
|
||||||
|
if field in validated_data and leader_field_name not in self.initial_data:
|
||||||
|
try:
|
||||||
|
reference = get_logical_value(
|
||||||
|
validated_data[field],
|
||||||
|
self.fields[field].choice_mdl._meta.model_name,
|
||||||
|
)
|
||||||
|
validated_data[leader_field_name] = reference.logical_key
|
||||||
|
except (ObjectDoesNotExist, Exception):
|
||||||
|
if validated_data[field] in self.fields[leader_field_name].choices:
|
||||||
|
validated_data[leader_field_name] = validated_data[field]
|
||||||
|
else:
|
||||||
|
raise serializers.ValidationError('Invalid choice')
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
"""Ensure custom state fields are not served empty."""
|
||||||
|
data = super().to_representation(instance)
|
||||||
|
for field in self.gather_custom_fields():
|
||||||
|
if data[field] is None:
|
||||||
|
data[field] = data[
|
||||||
|
field.replace('_custom_key', '')
|
||||||
|
] # Use "normal" status field instead
|
||||||
|
return data
|
||||||
|
|
||||||
|
def gather_custom_fields(self):
|
||||||
|
"""Gather all custom fields on the serializer."""
|
||||||
|
if self._custom_fields_follower:
|
||||||
|
self._is_gathering = False
|
||||||
|
return self._custom_fields_follower
|
||||||
|
|
||||||
|
if self._is_gathering:
|
||||||
|
self._custom_fields = {}
|
||||||
|
else:
|
||||||
|
self._is_gathering = True
|
||||||
|
# Gather fields
|
||||||
|
self._custom_fields = {
|
||||||
|
k: v.is_custom
|
||||||
|
for k, v in self.fields.items()
|
||||||
|
if isinstance(v, CustomChoiceField)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Separate fields for easier/cheaper access
|
||||||
|
self._custom_fields_follower = [k for k, v in self._custom_fields.items() if v]
|
||||||
|
self._custom_fields_leader = [
|
||||||
|
k for k, v in self._custom_fields.items() if not v
|
||||||
|
]
|
||||||
|
|
||||||
|
return self._custom_fields_follower
|
||||||
|
|
||||||
|
def build_standard_field(self, field_name, model_field):
|
||||||
|
"""Use custom field for custom status model.
|
||||||
|
|
||||||
|
This is required because of DRF overwriting all fields with choice sets.
|
||||||
|
"""
|
||||||
|
field_cls, field_kwargs = super().build_standard_field(field_name, model_field)
|
||||||
|
if issubclass(field_cls, ChoiceField) and isinstance(
|
||||||
|
model_field, InvenTreeCustomStatusModelField
|
||||||
|
):
|
||||||
|
field_cls = CustomChoiceField
|
||||||
|
field_kwargs['choice_mdl'] = model_field.model
|
||||||
|
field_kwargs['choice_field'] = model_field.name
|
||||||
|
elif isinstance(model_field, ExtraInvenTreeCustomStatusModelField):
|
||||||
|
field_cls = ExtraCustomChoiceField
|
||||||
|
field_kwargs['choice_mdl'] = model_field.model
|
||||||
|
field_kwargs['choice_field'] = model_field.name
|
||||||
|
field_kwargs['is_custom'] = True
|
||||||
|
|
||||||
|
# Inherit choices from leader
|
||||||
|
self.gather_custom_fields()
|
||||||
|
if field_name in self._custom_fields:
|
||||||
|
leader_field_name = field_name.replace('_custom_key', '')
|
||||||
|
leader_field = self.fields[leader_field_name]
|
||||||
|
if hasattr(leader_field, 'choices'):
|
||||||
|
field_kwargs['choices'] = list(leader_field.choices.items())
|
||||||
|
elif hasattr(model_field.model, leader_field_name):
|
||||||
|
leader_model_field = getattr(
|
||||||
|
model_field.model, leader_field_name
|
||||||
|
).field
|
||||||
|
if hasattr(leader_model_field, 'choices'):
|
||||||
|
field_kwargs['choices'] = leader_model_field.choices
|
||||||
|
|
||||||
|
if getattr(leader_field, 'read_only', False) is True:
|
||||||
|
field_kwargs['read_only'] = True
|
||||||
|
|
||||||
|
if 'choices' not in field_kwargs:
|
||||||
|
field_kwargs['choices'] = []
|
||||||
|
|
||||||
|
return field_cls, field_kwargs
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import enum
|
import enum
|
||||||
import re
|
import re
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class BaseEnum(enum.IntEnum):
|
class BaseEnum(enum.IntEnum):
|
||||||
@ -65,10 +66,23 @@ class StatusCode(BaseEnum):
|
|||||||
# Normal item definition
|
# Normal item definition
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
obj.label = args[0]
|
obj.label = args[0]
|
||||||
obj.color = 'secondary'
|
obj.color = ColorEnum.secondary
|
||||||
else:
|
else:
|
||||||
obj.label = args[1]
|
obj.label = args[1]
|
||||||
obj.color = args[2] if len(args) > 2 else 'secondary'
|
obj.color = args[2] if len(args) > 2 else ColorEnum.secondary
|
||||||
|
|
||||||
|
# Ensure color is a valid value
|
||||||
|
if isinstance(obj.color, str):
|
||||||
|
try:
|
||||||
|
obj.color = ColorEnum(obj.color)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid color value '{obj.color}' for status '{obj.label}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set color value as string
|
||||||
|
obj.color = obj.color.value
|
||||||
|
obj.color_class = obj.color
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@ -181,3 +195,15 @@ class StatusCode(BaseEnum):
|
|||||||
ret['list'] = cls.list()
|
ret['list'] = cls.list()
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class ColorEnum(Enum):
|
||||||
|
"""Enum for color values."""
|
||||||
|
|
||||||
|
primary = 'primary'
|
||||||
|
secondary = 'secondary'
|
||||||
|
success = 'success'
|
||||||
|
danger = 'danger'
|
||||||
|
warning = 'warning'
|
||||||
|
info = 'info'
|
||||||
|
dark = 'dark'
|
||||||
|
@ -3,15 +3,21 @@
|
|||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from generic.templatetags.generic import register
|
from generic.templatetags.generic import register
|
||||||
from InvenTree.helpers import inheritors
|
|
||||||
|
|
||||||
from .states import StatusCode
|
from .custom import get_custom_status_labels
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def status_label(typ: str, key: int, *args, **kwargs):
|
def status_label(typ: str, key: int, include_custom: bool = False, *args, **kwargs):
|
||||||
"""Render a status label."""
|
"""Render a status label."""
|
||||||
state = {cls.tag(): cls for cls in inheritors(StatusCode)}.get(typ, None)
|
state = get_custom_status_labels(include_custom=include_custom).get(typ, None)
|
||||||
if state:
|
if state:
|
||||||
return mark_safe(state.render(key, large=kwargs.get('large', False)))
|
return mark_safe(state.render(key, large=kwargs.get('large', False)))
|
||||||
raise ValueError(f"Unknown status type '{typ}'")
|
raise ValueError(f"Unknown status type '{typ}'")
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def display_status_label(typ: str, key: int, fallback: int, *args, **kwargs):
|
||||||
|
"""Render a status label."""
|
||||||
|
render_key = int(key) if key else fallback
|
||||||
|
return status_label(typ, render_key, *args, include_custom=True, **kwargs)
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
"""Tests for the generic states module."""
|
"""Tests for the generic states module."""
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from rest_framework.test import force_authenticate
|
from rest_framework.test import force_authenticate
|
||||||
|
|
||||||
from InvenTree.unit_test import InvenTreeTestCase
|
from common.models import InvenTreeCustomUserStateModel
|
||||||
|
from generic.states import ColorEnum
|
||||||
|
from InvenTree.unit_test import InvenTreeAPITestCase, InvenTreeTestCase
|
||||||
|
|
||||||
from .api import StatusView
|
from .api import StatusView
|
||||||
from .states import StatusCode
|
from .states import StatusCode
|
||||||
@ -14,9 +18,9 @@ from .states import StatusCode
|
|||||||
class GeneralStatus(StatusCode):
|
class GeneralStatus(StatusCode):
|
||||||
"""Defines a set of status codes for tests."""
|
"""Defines a set of status codes for tests."""
|
||||||
|
|
||||||
PENDING = 10, _('Pending'), 'secondary'
|
PENDING = 10, _('Pending'), ColorEnum.secondary
|
||||||
PLACED = 20, _('Placed'), 'primary'
|
PLACED = 20, _('Placed'), 'primary'
|
||||||
COMPLETE = 30, _('Complete'), 'success'
|
COMPLETE = 30, _('Complete'), ColorEnum.success
|
||||||
ABC = None # This should be ignored
|
ABC = None # This should be ignored
|
||||||
_DEF = None # This should be ignored
|
_DEF = None # This should be ignored
|
||||||
jkl = None # This should be ignored
|
jkl = None # This should be ignored
|
||||||
@ -120,8 +124,19 @@ class GeneralStateTest(InvenTreeTestCase):
|
|||||||
# label
|
# label
|
||||||
self.assertEqual(GeneralStatus.label(10), 'Pending')
|
self.assertEqual(GeneralStatus.label(10), 'Pending')
|
||||||
|
|
||||||
def test_tag_function(self):
|
def test_color(self):
|
||||||
"""Test that the status code tag functions."""
|
"""Test that the color enum validation works."""
|
||||||
|
with self.assertRaises(ValueError) as e:
|
||||||
|
|
||||||
|
class TTTT(StatusCode):
|
||||||
|
PENDING = 10, _('Pending'), 'invalid'
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
str(e.exception), "Invalid color value 'invalid' for status 'Pending'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tag_status_label(self):
|
||||||
|
"""Test that the status_label tag."""
|
||||||
from .tags import status_label
|
from .tags import status_label
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -137,6 +152,21 @@ class GeneralStateTest(InvenTreeTestCase):
|
|||||||
# Test non-existent key
|
# Test non-existent key
|
||||||
self.assertEqual(status_label('general', 100), '100')
|
self.assertEqual(status_label('general', 100), '100')
|
||||||
|
|
||||||
|
def test_tag_display_status_label(self):
|
||||||
|
"""Test that the display_status_label tag (mainly the same as status_label)."""
|
||||||
|
from .tags import display_status_label
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
display_status_label('general', 10, 11),
|
||||||
|
"<span class='badge rounded-pill bg-secondary'>Pending</span>",
|
||||||
|
)
|
||||||
|
# Fallback
|
||||||
|
self.assertEqual(display_status_label('general', None, 11), '11')
|
||||||
|
self.assertEqual(
|
||||||
|
display_status_label('general', None, 10),
|
||||||
|
"<span class='badge rounded-pill bg-secondary'>Pending</span>",
|
||||||
|
)
|
||||||
|
|
||||||
def test_api(self):
|
def test_api(self):
|
||||||
"""Test StatusView API view."""
|
"""Test StatusView API view."""
|
||||||
view = StatusView.as_view()
|
view = StatusView.as_view()
|
||||||
@ -191,3 +221,59 @@ class GeneralStateTest(InvenTreeTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
str(e.exception), '`status_class` not a valid StatusCode class'
|
str(e.exception), '`status_class` not a valid StatusCode class'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiTests(InvenTreeAPITestCase):
|
||||||
|
"""Test the API for the generic states module."""
|
||||||
|
|
||||||
|
def test_all_states(self):
|
||||||
|
"""Test the API endpoint for listing all status models."""
|
||||||
|
response = self.get(reverse('api-status-all'))
|
||||||
|
self.assertEqual(len(response.data), 12)
|
||||||
|
|
||||||
|
# Test the BuildStatus model
|
||||||
|
build_status = response.data['BuildStatus']
|
||||||
|
self.assertEqual(build_status['class'], 'BuildStatus')
|
||||||
|
self.assertEqual(len(build_status['values']), 5)
|
||||||
|
pending = build_status['values']['PENDING']
|
||||||
|
self.assertEqual(pending['key'], 10)
|
||||||
|
self.assertEqual(pending['name'], 'PENDING')
|
||||||
|
self.assertEqual(pending['label'], 'Pending')
|
||||||
|
|
||||||
|
# Test the StockStatus model (static)
|
||||||
|
stock_status = response.data['StockStatus']
|
||||||
|
self.assertEqual(stock_status['class'], 'StockStatus')
|
||||||
|
self.assertEqual(len(stock_status['values']), 8)
|
||||||
|
in_stock = stock_status['values']['OK']
|
||||||
|
self.assertEqual(in_stock['key'], 10)
|
||||||
|
self.assertEqual(in_stock['name'], 'OK')
|
||||||
|
self.assertEqual(in_stock['label'], 'OK')
|
||||||
|
|
||||||
|
# MachineStatus model
|
||||||
|
machine_status = response.data['MachineStatus__LabelPrinterStatus']
|
||||||
|
self.assertEqual(machine_status['class'], 'LabelPrinterStatus')
|
||||||
|
self.assertEqual(len(machine_status['values']), 6)
|
||||||
|
connected = machine_status['values']['CONNECTED']
|
||||||
|
self.assertEqual(connected['key'], 100)
|
||||||
|
self.assertEqual(connected['name'], 'CONNECTED')
|
||||||
|
|
||||||
|
# Add custom status
|
||||||
|
InvenTreeCustomUserStateModel.objects.create(
|
||||||
|
key=11,
|
||||||
|
name='OK - advanced',
|
||||||
|
label='OK - adv.',
|
||||||
|
color='secondary',
|
||||||
|
logical_key=10,
|
||||||
|
model=ContentType.objects.get(model='stockitem'),
|
||||||
|
reference_status='StockStatus',
|
||||||
|
)
|
||||||
|
response = self.get(reverse('api-status-all'))
|
||||||
|
self.assertEqual(len(response.data), 12)
|
||||||
|
|
||||||
|
stock_status_cstm = response.data['StockStatus']
|
||||||
|
self.assertEqual(stock_status_cstm['class'], 'StockStatus')
|
||||||
|
self.assertEqual(len(stock_status_cstm['values']), 9)
|
||||||
|
ok_advanced = stock_status_cstm['values']['OK']
|
||||||
|
self.assertEqual(ok_advanced['key'], 10)
|
||||||
|
self.assertEqual(ok_advanced['name'], 'OK')
|
||||||
|
self.assertEqual(ok_advanced['label'], 'OK')
|
||||||
|
@ -2,18 +2,26 @@
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from generic.states import StatusCode
|
from generic.states import ColorEnum, StatusCode
|
||||||
|
|
||||||
|
|
||||||
class DataImportStatusCode(StatusCode):
|
class DataImportStatusCode(StatusCode):
|
||||||
"""Defines a set of status codes for a DataImportSession."""
|
"""Defines a set of status codes for a DataImportSession."""
|
||||||
|
|
||||||
INITIAL = 0, _('Initializing'), 'secondary' # Import session has been created
|
INITIAL = (
|
||||||
MAPPING = 10, _('Mapping Columns'), 'primary' # Import fields are being mapped
|
0,
|
||||||
IMPORTING = 20, _('Importing Data'), 'primary' # Data is being imported
|
_('Initializing'),
|
||||||
|
ColorEnum.secondary,
|
||||||
|
) # Import session has been created
|
||||||
|
MAPPING = (
|
||||||
|
10,
|
||||||
|
_('Mapping Columns'),
|
||||||
|
ColorEnum.primary,
|
||||||
|
) # Import fields are being mapped
|
||||||
|
IMPORTING = 20, _('Importing Data'), ColorEnum.primary # Data is being imported
|
||||||
PROCESSING = (
|
PROCESSING = (
|
||||||
30,
|
30,
|
||||||
_('Processing Data'),
|
_('Processing Data'),
|
||||||
'primary',
|
ColorEnum.primary,
|
||||||
) # Data is being processed by the user
|
) # Data is being processed by the user
|
||||||
COMPLETE = 40, _('Complete'), 'success' # Import has been completed
|
COMPLETE = 40, _('Complete'), ColorEnum.success # Import has been completed
|
||||||
|
@ -12,6 +12,7 @@ from PIL.Image import Image
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
|
from generic.states import ColorEnum
|
||||||
from machine.machine_type import BaseDriver, BaseMachineType, MachineStatus
|
from machine.machine_type import BaseDriver, BaseMachineType, MachineStatus
|
||||||
from plugin import registry as plg_registry
|
from plugin import registry as plg_registry
|
||||||
from plugin.base.label.mixins import LabelPrintingMixin
|
from plugin.base.label.mixins import LabelPrintingMixin
|
||||||
@ -228,12 +229,12 @@ class LabelPrinterStatus(MachineStatus):
|
|||||||
DISCONNECTED: The driver cannot establish a connection to the printer
|
DISCONNECTED: The driver cannot establish a connection to the printer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CONNECTED = 100, _('Connected'), 'success'
|
CONNECTED = 100, _('Connected'), ColorEnum.success
|
||||||
UNKNOWN = 101, _('Unknown'), 'secondary'
|
UNKNOWN = 101, _('Unknown'), ColorEnum.secondary
|
||||||
PRINTING = 110, _('Printing'), 'primary'
|
PRINTING = 110, _('Printing'), ColorEnum.primary
|
||||||
NO_MEDIA = 301, _('No media'), 'warning'
|
NO_MEDIA = 301, _('No media'), ColorEnum.warning
|
||||||
PAPER_JAM = 302, _('Paper jam'), 'warning'
|
PAPER_JAM = 302, _('Paper jam'), ColorEnum.warning
|
||||||
DISCONNECTED = 400, _('Disconnected'), 'danger'
|
DISCONNECTED = 400, _('Disconnected'), ColorEnum.danger
|
||||||
|
|
||||||
|
|
||||||
class LabelPrinterMachine(BaseMachineType):
|
class LabelPrinterMachine(BaseMachineType):
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
# Generated by Django 4.2.14 on 2024-08-07 22:40
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import generic.states.fields
|
||||||
|
import InvenTree.status_codes
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("order", "0100_remove_returnorderattachment_order_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="purchaseorder",
|
||||||
|
name="status_custom_key",
|
||||||
|
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Additional status information for this item",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Custom status key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="returnorder",
|
||||||
|
name="status_custom_key",
|
||||||
|
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Additional status information for this item",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Custom status key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="returnorderlineitem",
|
||||||
|
name="outcome_custom_key",
|
||||||
|
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Additional status information for this item",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Custom status key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="salesorder",
|
||||||
|
name="status_custom_key",
|
||||||
|
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Additional status information for this item",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Custom status key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="purchaseorder",
|
||||||
|
name="status",
|
||||||
|
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||||
|
choices=InvenTree.status_codes.PurchaseOrderStatus.items(),
|
||||||
|
default=10,
|
||||||
|
help_text="Purchase order status",
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="returnorder",
|
||||||
|
name="status",
|
||||||
|
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||||
|
choices=InvenTree.status_codes.ReturnOrderStatus.items(),
|
||||||
|
default=10,
|
||||||
|
help_text="Return order status",
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="returnorderlineitem",
|
||||||
|
name="outcome",
|
||||||
|
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||||
|
choices=InvenTree.status_codes.ReturnOrderLineStatus.items(),
|
||||||
|
default=10,
|
||||||
|
help_text="Outcome for this line item",
|
||||||
|
verbose_name="Outcome",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="salesorder",
|
||||||
|
name="status",
|
||||||
|
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||||
|
choices=InvenTree.status_codes.SalesOrderStatus.items(),
|
||||||
|
default=10,
|
||||||
|
help_text="Sales order status",
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -37,6 +37,7 @@ from common.notifications import InvenTreeNotificationBodies
|
|||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
from company.models import Address, Company, Contact, SupplierPart
|
from company.models import Address, Company, Contact, SupplierPart
|
||||||
from generic.states import StateTransitionMixin
|
from generic.states import StateTransitionMixin
|
||||||
|
from generic.states.fields import InvenTreeCustomStatusModelField
|
||||||
from InvenTree.exceptions import log_error
|
from InvenTree.exceptions import log_error
|
||||||
from InvenTree.fields import (
|
from InvenTree.fields import (
|
||||||
InvenTreeModelMoneyField,
|
InvenTreeModelMoneyField,
|
||||||
@ -470,7 +471,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
|||||||
validators=[order.validators.validate_purchase_order_reference],
|
validators=[order.validators.validate_purchase_order_reference],
|
||||||
)
|
)
|
||||||
|
|
||||||
status = models.PositiveIntegerField(
|
status = InvenTreeCustomStatusModelField(
|
||||||
default=PurchaseOrderStatus.PENDING.value,
|
default=PurchaseOrderStatus.PENDING.value,
|
||||||
choices=PurchaseOrderStatus.items(),
|
choices=PurchaseOrderStatus.items(),
|
||||||
verbose_name=_('Status'),
|
verbose_name=_('Status'),
|
||||||
@ -996,7 +997,7 @@ class SalesOrder(TotalPriceMixin, Order):
|
|||||||
"""Accessor helper for Order base."""
|
"""Accessor helper for Order base."""
|
||||||
return self.customer
|
return self.customer
|
||||||
|
|
||||||
status = models.PositiveIntegerField(
|
status = InvenTreeCustomStatusModelField(
|
||||||
default=SalesOrderStatus.PENDING.value,
|
default=SalesOrderStatus.PENDING.value,
|
||||||
choices=SalesOrderStatus.items(),
|
choices=SalesOrderStatus.items(),
|
||||||
verbose_name=_('Status'),
|
verbose_name=_('Status'),
|
||||||
@ -2153,7 +2154,7 @@ class ReturnOrder(TotalPriceMixin, Order):
|
|||||||
"""Accessor helper for Order base class."""
|
"""Accessor helper for Order base class."""
|
||||||
return self.customer
|
return self.customer
|
||||||
|
|
||||||
status = models.PositiveIntegerField(
|
status = InvenTreeCustomStatusModelField(
|
||||||
default=ReturnOrderStatus.PENDING.value,
|
default=ReturnOrderStatus.PENDING.value,
|
||||||
choices=ReturnOrderStatus.items(),
|
choices=ReturnOrderStatus.items(),
|
||||||
verbose_name=_('Status'),
|
verbose_name=_('Status'),
|
||||||
@ -2404,7 +2405,7 @@ class ReturnOrderLineItem(OrderLineItem):
|
|||||||
"""Return True if this item has been received."""
|
"""Return True if this item has been received."""
|
||||||
return self.received_date is not None
|
return self.received_date is not None
|
||||||
|
|
||||||
outcome = models.PositiveIntegerField(
|
outcome = InvenTreeCustomStatusModelField(
|
||||||
default=ReturnOrderLineStatus.PENDING.value,
|
default=ReturnOrderLineStatus.PENDING.value,
|
||||||
choices=ReturnOrderLineStatus.items(),
|
choices=ReturnOrderLineStatus.items(),
|
||||||
verbose_name=_('Outcome'),
|
verbose_name=_('Outcome'),
|
||||||
|
@ -32,6 +32,7 @@ from company.serializers import (
|
|||||||
ContactSerializer,
|
ContactSerializer,
|
||||||
SupplierPartSerializer,
|
SupplierPartSerializer,
|
||||||
)
|
)
|
||||||
|
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
from importer.registry import register_importer
|
from importer.registry import register_importer
|
||||||
from InvenTree.helpers import (
|
from InvenTree.helpers import (
|
||||||
@ -161,6 +162,7 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria
|
|||||||
'address_detail',
|
'address_detail',
|
||||||
'status',
|
'status',
|
||||||
'status_text',
|
'status_text',
|
||||||
|
'status_custom_key',
|
||||||
'notes',
|
'notes',
|
||||||
'barcode_hash',
|
'barcode_hash',
|
||||||
'overdue',
|
'overdue',
|
||||||
@ -216,7 +218,11 @@ class AbstractExtraLineMeta:
|
|||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
class PurchaseOrderSerializer(
|
class PurchaseOrderSerializer(
|
||||||
NotesFieldMixin, TotalPriceMixin, AbstractOrderSerializer, InvenTreeModelSerializer
|
NotesFieldMixin,
|
||||||
|
TotalPriceMixin,
|
||||||
|
InvenTreeCustomStatusSerializerMixin,
|
||||||
|
AbstractOrderSerializer,
|
||||||
|
InvenTreeModelSerializer,
|
||||||
):
|
):
|
||||||
"""Serializer for a PurchaseOrder object."""
|
"""Serializer for a PurchaseOrder object."""
|
||||||
|
|
||||||
@ -859,7 +865,11 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
class SalesOrderSerializer(
|
class SalesOrderSerializer(
|
||||||
NotesFieldMixin, TotalPriceMixin, AbstractOrderSerializer, InvenTreeModelSerializer
|
NotesFieldMixin,
|
||||||
|
TotalPriceMixin,
|
||||||
|
InvenTreeCustomStatusSerializerMixin,
|
||||||
|
AbstractOrderSerializer,
|
||||||
|
InvenTreeModelSerializer,
|
||||||
):
|
):
|
||||||
"""Serializer for the SalesOrder model class."""
|
"""Serializer for the SalesOrder model class."""
|
||||||
|
|
||||||
@ -1642,7 +1652,11 @@ class SalesOrderExtraLineSerializer(
|
|||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
class ReturnOrderSerializer(
|
class ReturnOrderSerializer(
|
||||||
NotesFieldMixin, AbstractOrderSerializer, TotalPriceMixin, InvenTreeModelSerializer
|
NotesFieldMixin,
|
||||||
|
InvenTreeCustomStatusSerializerMixin,
|
||||||
|
AbstractOrderSerializer,
|
||||||
|
TotalPriceMixin,
|
||||||
|
InvenTreeModelSerializer,
|
||||||
):
|
):
|
||||||
"""Serializer for the ReturnOrder model class."""
|
"""Serializer for the ReturnOrder model class."""
|
||||||
|
|
||||||
|
@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from generic.states import StatusCode
|
from generic.states import ColorEnum, StatusCode
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderStatus(StatusCode):
|
class PurchaseOrderStatus(StatusCode):
|
||||||
"""Defines a set of status codes for a PurchaseOrder."""
|
"""Defines a set of status codes for a PurchaseOrder."""
|
||||||
|
|
||||||
# Order status codes
|
# Order status codes
|
||||||
PENDING = 10, _('Pending'), 'secondary' # Order is pending (not yet placed)
|
PENDING = 10, _('Pending'), ColorEnum.secondary # Order is pending (not yet placed)
|
||||||
PLACED = 20, _('Placed'), 'primary' # Order has been placed with supplier
|
PLACED = 20, _('Placed'), ColorEnum.primary # Order has been placed with supplier
|
||||||
ON_HOLD = 25, _('On Hold'), 'warning' # Order is on hold
|
ON_HOLD = 25, _('On Hold'), ColorEnum.warning # Order is on hold
|
||||||
COMPLETE = 30, _('Complete'), 'success' # Order has been completed
|
COMPLETE = 30, _('Complete'), ColorEnum.success # Order has been completed
|
||||||
CANCELLED = 40, _('Cancelled'), 'danger' # Order was cancelled
|
CANCELLED = 40, _('Cancelled'), ColorEnum.danger # Order was cancelled
|
||||||
LOST = 50, _('Lost'), 'warning' # Order was lost
|
LOST = 50, _('Lost'), ColorEnum.warning # Order was lost
|
||||||
RETURNED = 60, _('Returned'), 'warning' # Order was returned
|
RETURNED = 60, _('Returned'), ColorEnum.warning # Order was returned
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderStatusGroups:
|
class PurchaseOrderStatusGroups:
|
||||||
@ -39,18 +39,18 @@ class PurchaseOrderStatusGroups:
|
|||||||
class SalesOrderStatus(StatusCode):
|
class SalesOrderStatus(StatusCode):
|
||||||
"""Defines a set of status codes for a SalesOrder."""
|
"""Defines a set of status codes for a SalesOrder."""
|
||||||
|
|
||||||
PENDING = 10, _('Pending'), 'secondary' # Order is pending
|
PENDING = 10, _('Pending'), ColorEnum.secondary # Order is pending
|
||||||
IN_PROGRESS = (
|
IN_PROGRESS = (
|
||||||
15,
|
15,
|
||||||
_('In Progress'),
|
_('In Progress'),
|
||||||
'primary',
|
ColorEnum.primary,
|
||||||
) # Order has been issued, and is in progress
|
) # Order has been issued, and is in progress
|
||||||
SHIPPED = 20, _('Shipped'), 'success' # Order has been shipped to customer
|
SHIPPED = 20, _('Shipped'), ColorEnum.success # Order has been shipped to customer
|
||||||
ON_HOLD = 25, _('On Hold'), 'warning' # Order is on hold
|
ON_HOLD = 25, _('On Hold'), ColorEnum.warning # Order is on hold
|
||||||
COMPLETE = 30, _('Complete'), 'success' # Order is complete
|
COMPLETE = 30, _('Complete'), ColorEnum.success # Order is complete
|
||||||
CANCELLED = 40, _('Cancelled'), 'danger' # Order has been cancelled
|
CANCELLED = 40, _('Cancelled'), ColorEnum.danger # Order has been cancelled
|
||||||
LOST = 50, _('Lost'), 'warning' # Order was lost
|
LOST = 50, _('Lost'), ColorEnum.warning # Order was lost
|
||||||
RETURNED = 60, _('Returned'), 'warning' # Order was returned
|
RETURNED = 60, _('Returned'), ColorEnum.warning # Order was returned
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderStatusGroups:
|
class SalesOrderStatusGroups:
|
||||||
@ -71,15 +71,15 @@ class ReturnOrderStatus(StatusCode):
|
|||||||
"""Defines a set of status codes for a ReturnOrder."""
|
"""Defines a set of status codes for a ReturnOrder."""
|
||||||
|
|
||||||
# Order is pending, waiting for receipt of items
|
# Order is pending, waiting for receipt of items
|
||||||
PENDING = 10, _('Pending'), 'secondary'
|
PENDING = 10, _('Pending'), ColorEnum.secondary
|
||||||
|
|
||||||
# Items have been received, and are being inspected
|
# Items have been received, and are being inspected
|
||||||
IN_PROGRESS = 20, _('In Progress'), 'primary'
|
IN_PROGRESS = 20, _('In Progress'), ColorEnum.primary
|
||||||
|
|
||||||
ON_HOLD = 25, _('On Hold'), 'warning'
|
ON_HOLD = 25, _('On Hold'), ColorEnum.warning
|
||||||
|
|
||||||
COMPLETE = 30, _('Complete'), 'success'
|
COMPLETE = 30, _('Complete'), ColorEnum.success
|
||||||
CANCELLED = 40, _('Cancelled'), 'danger'
|
CANCELLED = 40, _('Cancelled'), ColorEnum.danger
|
||||||
|
|
||||||
|
|
||||||
class ReturnOrderStatusGroups:
|
class ReturnOrderStatusGroups:
|
||||||
@ -95,19 +95,19 @@ class ReturnOrderStatusGroups:
|
|||||||
class ReturnOrderLineStatus(StatusCode):
|
class ReturnOrderLineStatus(StatusCode):
|
||||||
"""Defines a set of status codes for a ReturnOrderLineItem."""
|
"""Defines a set of status codes for a ReturnOrderLineItem."""
|
||||||
|
|
||||||
PENDING = 10, _('Pending'), 'secondary'
|
PENDING = 10, _('Pending'), ColorEnum.secondary
|
||||||
|
|
||||||
# Item is to be returned to customer, no other action
|
# Item is to be returned to customer, no other action
|
||||||
RETURN = 20, _('Return'), 'success'
|
RETURN = 20, _('Return'), ColorEnum.success
|
||||||
|
|
||||||
# Item is to be repaired, and returned to customer
|
# Item is to be repaired, and returned to customer
|
||||||
REPAIR = 30, _('Repair'), 'primary'
|
REPAIR = 30, _('Repair'), ColorEnum.primary
|
||||||
|
|
||||||
# Item is to be replaced (new item shipped)
|
# Item is to be replaced (new item shipped)
|
||||||
REPLACE = 40, _('Replace'), 'warning'
|
REPLACE = 40, _('Replace'), ColorEnum.warning
|
||||||
|
|
||||||
# Item is to be refunded (cannot be repaired)
|
# Item is to be refunded (cannot be repaired)
|
||||||
REFUND = 50, _('Refund'), 'info'
|
REFUND = 50, _('Refund'), ColorEnum.info
|
||||||
|
|
||||||
# Item is rejected
|
# Item is rejected
|
||||||
REJECT = 60, _('Reject'), 'danger'
|
REJECT = 60, _('Reject'), ColorEnum.danger
|
||||||
|
@ -122,7 +122,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
<td>{% trans "Order Status" %}</td>
|
<td>{% trans "Order Status" %}</td>
|
||||||
<td>
|
<td>
|
||||||
{% status_label 'purchase_order' order.status %}
|
{% display_status_label 'purchase_order' order.status_custom_key order.status %}
|
||||||
{% if order.is_overdue %}
|
{% if order.is_overdue %}
|
||||||
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -115,7 +115,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
<td>{% trans "Order Status" %}</td>
|
<td>{% trans "Order Status" %}</td>
|
||||||
<td>
|
<td>
|
||||||
{% status_label 'return_order' order.status %}
|
{% display_status_label 'return_order' order.status_custom_key order.status %}
|
||||||
{% if order.is_overdue %}
|
{% if order.is_overdue %}
|
||||||
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -124,7 +124,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
<td>{% trans "Order Status" %}</td>
|
<td>{% trans "Order Status" %}</td>
|
||||||
<td>
|
<td>
|
||||||
{% status_label 'sales_order' order.status %}
|
{% display_status_label 'sales_order' order.status_custom_key order.status %}
|
||||||
{% if order.is_overdue %}
|
{% if order.is_overdue %}
|
||||||
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -125,6 +125,7 @@ class LabelMixinTests(PrintTestMixins, InvenTreeAPITestCase):
|
|||||||
self.assertGreater(len(plugins), 0)
|
self.assertGreater(len(plugins), 0)
|
||||||
|
|
||||||
plugin = registry.get_plugin('samplelabelprinter')
|
plugin = registry.get_plugin('samplelabelprinter')
|
||||||
|
self.assertIsNotNone(plugin)
|
||||||
config = plugin.plugin_config()
|
config = plugin.plugin_config()
|
||||||
|
|
||||||
# Ensure that the plugin is not active
|
# Ensure that the plugin is not active
|
||||||
|
@ -491,7 +491,10 @@ class PrintTestMixins:
|
|||||||
|
|
||||||
def do_activate_plugin(self):
|
def do_activate_plugin(self):
|
||||||
"""Activate the 'samplelabel' plugin."""
|
"""Activate the 'samplelabel' plugin."""
|
||||||
config = registry.get_plugin(self.plugin_ref).plugin_config()
|
plugin = registry.get_plugin(self.plugin_ref)
|
||||||
|
self.assertIsNotNone(plugin)
|
||||||
|
config = plugin.plugin_config()
|
||||||
|
self.assertIsNotNone(config)
|
||||||
config.active = True
|
config.active = True
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
|
@ -142,6 +142,7 @@ class StockItemResource(InvenTreeResource):
|
|||||||
'barcode_hash',
|
'barcode_hash',
|
||||||
'barcode_data',
|
'barcode_data',
|
||||||
'owner',
|
'owner',
|
||||||
|
'status_custom_key',
|
||||||
]
|
]
|
||||||
|
|
||||||
id = Field(
|
id = Field(
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 4.2.14 on 2024-08-07 22:40
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import generic.states
|
||||||
|
import generic.states.fields
|
||||||
|
import InvenTree.status_codes
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("stock", "0112_alter_stocklocation_custom_icon_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="stockitem",
|
||||||
|
name="status_custom_key",
|
||||||
|
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Additional status information for this item",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Custom status key",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="stockitem",
|
||||||
|
name="status",
|
||||||
|
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||||
|
choices=InvenTree.status_codes.StockStatus.items(),
|
||||||
|
default=10,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -37,13 +37,18 @@ from build import models as BuildModels
|
|||||||
from common.icons import validate_icon
|
from common.icons import validate_icon
|
||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
from company import models as CompanyModels
|
from company import models as CompanyModels
|
||||||
|
from generic.states.fields import InvenTreeCustomStatusModelField
|
||||||
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
|
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
|
||||||
from order.status_codes import SalesOrderStatusGroups
|
from InvenTree.status_codes import (
|
||||||
|
SalesOrderStatusGroups,
|
||||||
|
StockHistoryCode,
|
||||||
|
StockStatus,
|
||||||
|
StockStatusGroups,
|
||||||
|
)
|
||||||
from part import models as PartModels
|
from part import models as PartModels
|
||||||
from plugin.events import trigger_event
|
from plugin.events import trigger_event
|
||||||
from stock import models as StockModels
|
from stock import models as StockModels
|
||||||
from stock.generators import generate_batch_code
|
from stock.generators import generate_batch_code
|
||||||
from stock.status_codes import StockHistoryCode, StockStatus, StockStatusGroups
|
|
||||||
from users.models import Owner
|
from users.models import Owner
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
@ -940,7 +945,7 @@ class StockItem(
|
|||||||
help_text=_('Delete this Stock Item when stock is depleted'),
|
help_text=_('Delete this Stock Item when stock is depleted'),
|
||||||
)
|
)
|
||||||
|
|
||||||
status = models.PositiveIntegerField(
|
status = InvenTreeCustomStatusModelField(
|
||||||
default=StockStatus.OK.value,
|
default=StockStatus.OK.value,
|
||||||
choices=StockStatus.items(),
|
choices=StockStatus.items(),
|
||||||
validators=[MinValueValidator(0)],
|
validators=[MinValueValidator(0)],
|
||||||
|
@ -27,6 +27,7 @@ import part.serializers as part_serializers
|
|||||||
import stock.filters
|
import stock.filters
|
||||||
import stock.status_codes
|
import stock.status_codes
|
||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
|
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
from importer.registry import register_importer
|
from importer.registry import register_importer
|
||||||
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
|
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
|
||||||
@ -238,7 +239,10 @@ class StockItemTestResultSerializer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(
|
attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(
|
||||||
required=False
|
required=False,
|
||||||
|
allow_null=True,
|
||||||
|
label=_('Attachment'),
|
||||||
|
help_text=_('Test result attachment'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
@ -326,7 +330,9 @@ class StockItemSerializerBrief(
|
|||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
class StockItemSerializer(
|
class StockItemSerializer(
|
||||||
DataImportExportSerializerMixin, InvenTree.serializers.InvenTreeTagModelSerializer
|
DataImportExportSerializerMixin,
|
||||||
|
InvenTreeCustomStatusSerializerMixin,
|
||||||
|
InvenTree.serializers.InvenTreeTagModelSerializer,
|
||||||
):
|
):
|
||||||
"""Serializer for a StockItem.
|
"""Serializer for a StockItem.
|
||||||
|
|
||||||
@ -373,6 +379,7 @@ class StockItemSerializer(
|
|||||||
'serial',
|
'serial',
|
||||||
'status',
|
'status',
|
||||||
'status_text',
|
'status_text',
|
||||||
|
'status_custom_key',
|
||||||
'stocktake_date',
|
'stocktake_date',
|
||||||
'supplier_part',
|
'supplier_part',
|
||||||
'sku',
|
'sku',
|
||||||
|
@ -2,24 +2,28 @@
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from generic.states import StatusCode
|
from generic.states import ColorEnum, StatusCode
|
||||||
|
|
||||||
|
|
||||||
class StockStatus(StatusCode):
|
class StockStatus(StatusCode):
|
||||||
"""Status codes for Stock."""
|
"""Status codes for Stock."""
|
||||||
|
|
||||||
OK = 10, _('OK'), 'success' # Item is OK
|
OK = 10, _('OK'), ColorEnum.success # Item is OK
|
||||||
ATTENTION = 50, _('Attention needed'), 'warning' # Item requires attention
|
ATTENTION = 50, _('Attention needed'), ColorEnum.warning # Item requires attention
|
||||||
DAMAGED = 55, _('Damaged'), 'warning' # Item is damaged
|
DAMAGED = 55, _('Damaged'), ColorEnum.warning # Item is damaged
|
||||||
DESTROYED = 60, _('Destroyed'), 'danger' # Item is destroyed
|
DESTROYED = 60, _('Destroyed'), ColorEnum.danger # Item is destroyed
|
||||||
REJECTED = 65, _('Rejected'), 'danger' # Item is rejected
|
REJECTED = 65, _('Rejected'), ColorEnum.danger # Item is rejected
|
||||||
LOST = 70, _('Lost'), 'dark' # Item has been lost
|
LOST = 70, _('Lost'), ColorEnum.dark # Item has been lost
|
||||||
QUARANTINED = (
|
QUARANTINED = (
|
||||||
75,
|
75,
|
||||||
_('Quarantined'),
|
_('Quarantined'),
|
||||||
'info',
|
ColorEnum.info,
|
||||||
) # Item has been quarantined and is unavailable
|
) # Item has been quarantined and is unavailable
|
||||||
RETURNED = 85, _('Returned'), 'warning' # Item has been returned from a customer
|
RETURNED = (
|
||||||
|
85,
|
||||||
|
_('Returned'),
|
||||||
|
ColorEnum.warning,
|
||||||
|
) # Item has been returned from a customer
|
||||||
|
|
||||||
|
|
||||||
class StockStatusGroups:
|
class StockStatusGroups:
|
||||||
|
@ -425,7 +425,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
<td>{% trans "Status" %}</td>
|
<td>{% trans "Status" %}</td>
|
||||||
<td>{% status_label 'stock' item.status %}</td>
|
<td>{% display_status_label 'stock' item.status_custom_key item.status %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if item.expiry_date %}
|
{% if item.expiry_date %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -7,6 +7,7 @@ from datetime import datetime, timedelta
|
|||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
import django.http
|
import django.http
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ from rest_framework import status
|
|||||||
import build.models
|
import build.models
|
||||||
import company.models
|
import company.models
|
||||||
import part.models
|
import part.models
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeCustomUserStateModel, InvenTreeSetting
|
||||||
from InvenTree.unit_test import InvenTreeAPITestCase
|
from InvenTree.unit_test import InvenTreeAPITestCase
|
||||||
from part.models import Part, PartTestTemplate
|
from part.models import Part, PartTestTemplate
|
||||||
from stock.models import (
|
from stock.models import (
|
||||||
@ -925,6 +926,108 @@ class StockItemListTest(StockAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomStockItemStatusTest(StockAPITestCase):
|
||||||
|
"""Tests for custom stock item statuses."""
|
||||||
|
|
||||||
|
list_url = reverse('api-stock-list')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Setup for all tests."""
|
||||||
|
super().setUp()
|
||||||
|
self.status = InvenTreeCustomUserStateModel.objects.create(
|
||||||
|
key=11,
|
||||||
|
name='OK - advanced',
|
||||||
|
label='OK - adv.',
|
||||||
|
color='secondary',
|
||||||
|
logical_key=10,
|
||||||
|
model=ContentType.objects.get(model='stockitem'),
|
||||||
|
reference_status='StockStatus',
|
||||||
|
)
|
||||||
|
self.status2 = InvenTreeCustomUserStateModel.objects.create(
|
||||||
|
key=51,
|
||||||
|
name='attention 2',
|
||||||
|
label='attention 2',
|
||||||
|
color='secondary',
|
||||||
|
logical_key=50,
|
||||||
|
model=ContentType.objects.get(model='stockitem'),
|
||||||
|
reference_status='StockStatus',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_custom_status(self):
|
||||||
|
"""Tests interaction with states."""
|
||||||
|
# Create a stock item with the custom status code via the API
|
||||||
|
response = self.post(
|
||||||
|
self.list_url,
|
||||||
|
{
|
||||||
|
'name': 'Test Type 1',
|
||||||
|
'description': 'Test desc 1',
|
||||||
|
'quantity': 1,
|
||||||
|
'part': 1,
|
||||||
|
'status_custom_key': self.status.key,
|
||||||
|
},
|
||||||
|
expected_code=201,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.data['status'], self.status.logical_key)
|
||||||
|
self.assertEqual(response.data['status_custom_key'], self.status.key)
|
||||||
|
pk = response.data['pk']
|
||||||
|
|
||||||
|
# Update the stock item with another custom status code via the API
|
||||||
|
response = self.patch(
|
||||||
|
reverse('api-stock-detail', kwargs={'pk': pk}),
|
||||||
|
{'status_custom_key': self.status2.key},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.data['status'], self.status2.logical_key)
|
||||||
|
self.assertEqual(response.data['status_custom_key'], self.status2.key)
|
||||||
|
|
||||||
|
# Try if status_custom_key is rewrite with status bying set
|
||||||
|
response = self.patch(
|
||||||
|
reverse('api-stock-detail', kwargs={'pk': pk}),
|
||||||
|
{'status': self.status.logical_key},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.data['status'], self.status.logical_key)
|
||||||
|
self.assertEqual(response.data['status_custom_key'], self.status.logical_key)
|
||||||
|
|
||||||
|
# Create a stock item with a normal status code via the API
|
||||||
|
response = self.post(
|
||||||
|
self.list_url,
|
||||||
|
{
|
||||||
|
'name': 'Test Type 1',
|
||||||
|
'description': 'Test desc 1',
|
||||||
|
'quantity': 1,
|
||||||
|
'part': 1,
|
||||||
|
'status_key': self.status.key,
|
||||||
|
},
|
||||||
|
expected_code=201,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.data['status'], self.status.logical_key)
|
||||||
|
self.assertEqual(response.data['status_custom_key'], self.status.logical_key)
|
||||||
|
|
||||||
|
# Test case with wrong key
|
||||||
|
response = self.patch(
|
||||||
|
reverse('api-stock-detail', kwargs={'pk': pk}),
|
||||||
|
{'status_custom_key': 23456789},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
self.assertIn('Invalid choice', str(response.data))
|
||||||
|
|
||||||
|
def test_options(self):
|
||||||
|
"""Test the StockItem OPTIONS endpoint to contain custom StockStatuses."""
|
||||||
|
response = self.options(self.list_url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Check that the response contains the custom StockStatuses
|
||||||
|
actions = response.data['actions']['POST']
|
||||||
|
self.assertIn('status_custom_key', actions)
|
||||||
|
status_custom_key = actions['status_custom_key']
|
||||||
|
self.assertEqual(len(status_custom_key['choices']), 10)
|
||||||
|
status = status_custom_key['choices'][1]
|
||||||
|
self.assertEqual(status['value'], self.status.key)
|
||||||
|
self.assertEqual(status['display_name'], self.status.label)
|
||||||
|
|
||||||
|
|
||||||
class StockItemTest(StockAPITestCase):
|
class StockItemTest(StockAPITestCase):
|
||||||
"""Series of API tests for the StockItem API."""
|
"""Series of API tests for the StockItem API."""
|
||||||
|
|
||||||
|
@ -615,7 +615,7 @@ function completeBuildOutputs(build_id, outputs, options={}) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
preFormContent: html,
|
preFormContent: html,
|
||||||
fields: {
|
fields: {
|
||||||
status: {},
|
status_custom_key: {},
|
||||||
location: {
|
location: {
|
||||||
filters: {
|
filters: {
|
||||||
structural: false,
|
structural: false,
|
||||||
@ -644,7 +644,7 @@ function completeBuildOutputs(build_id, outputs, options={}) {
|
|||||||
// Extract data elements from the form
|
// Extract data elements from the form
|
||||||
var data = {
|
var data = {
|
||||||
outputs: [],
|
outputs: [],
|
||||||
status: getFormFieldValue('status', {}, opts),
|
status_custom_key: getFormFieldValue('status_custom_key', {}, opts),
|
||||||
location: getFormFieldValue('location', {}, opts),
|
location: getFormFieldValue('location', {}, opts),
|
||||||
notes: getFormFieldValue('notes', {}, opts),
|
notes: getFormFieldValue('notes', {}, opts),
|
||||||
accept_incomplete_allocation: getFormFieldValue('accept_incomplete_allocation', {type: 'boolean'}, opts),
|
accept_incomplete_allocation: getFormFieldValue('accept_incomplete_allocation', {type: 'boolean'}, opts),
|
||||||
@ -1153,7 +1153,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
|
|||||||
if (row.build_detail) {
|
if (row.build_detail) {
|
||||||
html += `- <small>${row.build_detail.title}</small>`;
|
html += `- <small>${row.build_detail.title}</small>`;
|
||||||
|
|
||||||
html += buildStatusDisplay(row.build_detail.status, {
|
html += buildStatusDisplay(row.build_detail.status_custom_key, {
|
||||||
classes: 'float-right',
|
classes: 'float-right',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1556,7 +1556,7 @@ function loadBuildOutputTable(build_info, options={}) {
|
|||||||
text += ` <small>({% trans "Batch" %}: ${row.batch})</small>`;
|
text += ` <small>({% trans "Batch" %}: ${row.batch})</small>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
text += stockStatusDisplay(row.status, {classes: 'float-right'});
|
text += stockStatusDisplay(row.status_custom_key, {classes: 'float-right'});
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
@ -2362,7 +2362,7 @@ function loadBuildTable(table, options) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status_custom_key',
|
||||||
title: '{% trans "Status" %}',
|
title: '{% trans "Status" %}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value) {
|
formatter: function(value) {
|
||||||
|
@ -1761,7 +1761,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
|
|||||||
var html = renderLink(order.reference, `/order/purchase-order/${order.pk}/`);
|
var html = renderLink(order.reference, `/order/purchase-order/${order.pk}/`);
|
||||||
|
|
||||||
html += purchaseOrderStatusDisplay(
|
html += purchaseOrderStatusDisplay(
|
||||||
order.status,
|
order.status_custom_key,
|
||||||
{
|
{
|
||||||
classes: 'float-right',
|
classes: 'float-right',
|
||||||
}
|
}
|
||||||
|
@ -1788,12 +1788,12 @@ function loadPurchaseOrderTable(table, options) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status_custom_key',
|
||||||
title: '{% trans "Status" %}',
|
title: '{% trans "Status" %}',
|
||||||
switchable: true,
|
switchable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row) {
|
formatter: function(value, row) {
|
||||||
return purchaseOrderStatusDisplay(row.status);
|
return purchaseOrderStatusDisplay(row.status_custom_key);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -326,10 +326,10 @@ function loadReturnOrderTable(table, options={}) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: 'status',
|
field: 'status_custom_key',
|
||||||
title: '{% trans "Status" %}',
|
title: '{% trans "Status" %}',
|
||||||
formatter: function(value, row) {
|
formatter: function(value, row) {
|
||||||
return returnOrderStatusDisplay(row.status);
|
return returnOrderStatusDisplay(row.status_custom_key);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -851,10 +851,10 @@ function loadSalesOrderTable(table, options) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: 'status',
|
field: 'status_custom_key',
|
||||||
title: '{% trans "Status" %}',
|
title: '{% trans "Status" %}',
|
||||||
formatter: function(value, row) {
|
formatter: function(value, row) {
|
||||||
return salesOrderStatusDisplay(row.status);
|
return salesOrderStatusDisplay(row.status_custom_key);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -380,7 +380,7 @@ function stockItemFields(options={}) {
|
|||||||
batch: {
|
batch: {
|
||||||
icon: 'fa-layer-group',
|
icon: 'fa-layer-group',
|
||||||
},
|
},
|
||||||
status: {},
|
status_custom_key: {},
|
||||||
expiry_date: {
|
expiry_date: {
|
||||||
icon: 'fa-calendar-alt',
|
icon: 'fa-calendar-alt',
|
||||||
},
|
},
|
||||||
@ -698,7 +698,7 @@ function assignStockToCustomer(items, options={}) {
|
|||||||
|
|
||||||
var thumbnail = thumbnailImage(part.thumbnail || part.image);
|
var thumbnail = thumbnailImage(part.thumbnail || part.image);
|
||||||
|
|
||||||
var status = stockStatusDisplay(item.status, {classes: 'float-right'});
|
var status = stockStatusDisplay(item.status_custom_key, {classes: 'float-right'});
|
||||||
|
|
||||||
var quantity = '';
|
var quantity = '';
|
||||||
|
|
||||||
@ -879,7 +879,7 @@ function mergeStockItems(items, options={}) {
|
|||||||
quantity = `{% trans "Quantity" %}: ${item.quantity}`;
|
quantity = `{% trans "Quantity" %}: ${item.quantity}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
quantity += stockStatusDisplay(item.status, {classes: 'float-right'});
|
quantity += stockStatusDisplay(item.status_custom_key, {classes: 'float-right'});
|
||||||
|
|
||||||
let buttons = wrapButtons(
|
let buttons = wrapButtons(
|
||||||
makeIconButton(
|
makeIconButton(
|
||||||
@ -1113,7 +1113,7 @@ function adjustStock(action, items, options={}) {
|
|||||||
|
|
||||||
var thumb = thumbnailImage(item.part_detail.thumbnail || item.part_detail.image);
|
var thumb = thumbnailImage(item.part_detail.thumbnail || item.part_detail.image);
|
||||||
|
|
||||||
var status = stockStatusDisplay(item.status, {
|
var status = stockStatusDisplay(item.status_custom_key, {
|
||||||
classes: 'float-right'
|
classes: 'float-right'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1922,7 +1922,8 @@ function makeStockActions(table) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'status',
|
|
||||||
|
label: 'status_custom_key',
|
||||||
icon: 'fa-info-circle icon-blue',
|
icon: 'fa-info-circle icon-blue',
|
||||||
title: '{% trans "Change stock status" %}',
|
title: '{% trans "Change stock status" %}',
|
||||||
permission: 'stock.change',
|
permission: 'stock.change',
|
||||||
@ -2257,7 +2258,7 @@ function loadStockTable(table, options) {
|
|||||||
columns.push(col);
|
columns.push(col);
|
||||||
|
|
||||||
col = {
|
col = {
|
||||||
field: 'status',
|
field: 'status_custom_key',
|
||||||
title: '{% trans "Status" %}',
|
title: '{% trans "Status" %}',
|
||||||
formatter: function(value) {
|
formatter: function(value) {
|
||||||
return stockStatusDisplay(value);
|
return stockStatusDisplay(value);
|
||||||
@ -3075,11 +3076,11 @@ function loadStockTrackingTable(table, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status information
|
// Status information
|
||||||
if (details.status) {
|
if (details.status_custom_key) {
|
||||||
html += `<tr><th>{% trans "Status" %}</td>`;
|
html += `<tr><th>{% trans "Status" %}</td>`;
|
||||||
|
|
||||||
html += '<td>';
|
html += '<td>';
|
||||||
html += stockStatusDisplay(details.status);
|
html += stockStatusDisplay(details.status_custom_key);
|
||||||
html += '</td></tr>';
|
html += '</td></tr>';
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -3200,7 +3201,7 @@ function loadInstalledInTable(table, options) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status_custom_key',
|
||||||
title: '{% trans "Status" %}',
|
title: '{% trans "Status" %}',
|
||||||
formatter: function(value) {
|
formatter: function(value) {
|
||||||
return stockStatusDisplay(value);
|
return stockStatusDisplay(value);
|
||||||
@ -3401,7 +3402,7 @@ function setStockStatus(items, options={}) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
preFormContent: html,
|
preFormContent: html,
|
||||||
fields: {
|
fields: {
|
||||||
status: {},
|
status_custom_key: {},
|
||||||
note: {},
|
note: {},
|
||||||
},
|
},
|
||||||
processBeforeUpload: function(data) {
|
processBeforeUpload: function(data) {
|
||||||
|
@ -345,6 +345,7 @@ class RuleSet(models.Model):
|
|||||||
'common_projectcode',
|
'common_projectcode',
|
||||||
'common_webhookendpoint',
|
'common_webhookendpoint',
|
||||||
'common_webhookmessage',
|
'common_webhookmessage',
|
||||||
|
'common_inventreecustomuserstatemodel',
|
||||||
'users_owner',
|
'users_owner',
|
||||||
# Third-party tables
|
# Third-party tables
|
||||||
'error_report_error',
|
'error_report_error',
|
||||||
|
@ -10,59 +10,74 @@ build==1.2.1 \
|
|||||||
--hash=sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d \
|
--hash=sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d \
|
||||||
--hash=sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4
|
--hash=sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
cffi==1.16.0 \
|
cffi==1.17.0 \
|
||||||
--hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \
|
--hash=sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f \
|
||||||
--hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \
|
--hash=sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab \
|
||||||
--hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \
|
--hash=sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499 \
|
||||||
--hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \
|
--hash=sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058 \
|
||||||
--hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \
|
--hash=sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693 \
|
||||||
--hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \
|
--hash=sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb \
|
||||||
--hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \
|
--hash=sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377 \
|
||||||
--hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \
|
--hash=sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885 \
|
||||||
--hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \
|
--hash=sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2 \
|
||||||
--hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \
|
--hash=sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401 \
|
||||||
--hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \
|
--hash=sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4 \
|
||||||
--hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \
|
--hash=sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b \
|
||||||
--hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \
|
--hash=sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59 \
|
||||||
--hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \
|
--hash=sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f \
|
||||||
--hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \
|
--hash=sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c \
|
||||||
--hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \
|
--hash=sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555 \
|
||||||
--hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \
|
--hash=sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa \
|
||||||
--hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \
|
--hash=sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424 \
|
||||||
--hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \
|
--hash=sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb \
|
||||||
--hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \
|
--hash=sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2 \
|
||||||
--hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \
|
--hash=sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8 \
|
||||||
--hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \
|
--hash=sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e \
|
||||||
--hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \
|
--hash=sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9 \
|
||||||
--hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \
|
--hash=sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82 \
|
||||||
--hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \
|
--hash=sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828 \
|
||||||
--hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \
|
--hash=sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759 \
|
||||||
--hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \
|
--hash=sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc \
|
||||||
--hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \
|
--hash=sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118 \
|
||||||
--hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \
|
--hash=sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf \
|
||||||
--hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \
|
--hash=sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932 \
|
||||||
--hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \
|
--hash=sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a \
|
||||||
--hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \
|
--hash=sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29 \
|
||||||
--hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \
|
--hash=sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206 \
|
||||||
--hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \
|
--hash=sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2 \
|
||||||
--hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \
|
--hash=sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c \
|
||||||
--hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \
|
--hash=sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c \
|
||||||
--hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \
|
--hash=sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0 \
|
||||||
--hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \
|
--hash=sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a \
|
||||||
--hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \
|
--hash=sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195 \
|
||||||
--hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \
|
--hash=sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6 \
|
||||||
--hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \
|
--hash=sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9 \
|
||||||
--hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \
|
--hash=sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc \
|
||||||
--hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \
|
--hash=sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb \
|
||||||
--hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \
|
--hash=sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0 \
|
||||||
--hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \
|
--hash=sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7 \
|
||||||
--hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \
|
--hash=sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb \
|
||||||
--hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \
|
--hash=sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a \
|
||||||
--hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \
|
--hash=sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492 \
|
||||||
--hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \
|
--hash=sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720 \
|
||||||
--hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \
|
--hash=sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42 \
|
||||||
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
|
--hash=sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7 \
|
||||||
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
|
--hash=sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d \
|
||||||
|
--hash=sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d \
|
||||||
|
--hash=sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb \
|
||||||
|
--hash=sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4 \
|
||||||
|
--hash=sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2 \
|
||||||
|
--hash=sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b \
|
||||||
|
--hash=sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8 \
|
||||||
|
--hash=sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e \
|
||||||
|
--hash=sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204 \
|
||||||
|
--hash=sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3 \
|
||||||
|
--hash=sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150 \
|
||||||
|
--hash=sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4 \
|
||||||
|
--hash=sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76 \
|
||||||
|
--hash=sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e \
|
||||||
|
--hash=sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb \
|
||||||
|
--hash=sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# cryptography
|
# cryptography
|
||||||
@ -168,93 +183,108 @@ click==8.1.7 \
|
|||||||
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
|
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
|
||||||
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
|
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
coverage[toml]==7.5.4 \
|
coverage[toml]==7.6.1 \
|
||||||
--hash=sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f \
|
--hash=sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca \
|
||||||
--hash=sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d \
|
--hash=sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d \
|
||||||
--hash=sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747 \
|
--hash=sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6 \
|
||||||
--hash=sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f \
|
--hash=sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989 \
|
||||||
--hash=sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d \
|
--hash=sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c \
|
||||||
--hash=sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f \
|
--hash=sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b \
|
||||||
--hash=sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47 \
|
--hash=sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223 \
|
||||||
--hash=sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e \
|
--hash=sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f \
|
||||||
--hash=sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba \
|
--hash=sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56 \
|
||||||
--hash=sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c \
|
--hash=sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3 \
|
||||||
--hash=sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b \
|
--hash=sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8 \
|
||||||
--hash=sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4 \
|
--hash=sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb \
|
||||||
--hash=sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7 \
|
--hash=sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388 \
|
||||||
--hash=sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555 \
|
--hash=sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0 \
|
||||||
--hash=sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233 \
|
--hash=sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a \
|
||||||
--hash=sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace \
|
--hash=sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8 \
|
||||||
--hash=sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805 \
|
--hash=sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f \
|
||||||
--hash=sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136 \
|
--hash=sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a \
|
||||||
--hash=sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4 \
|
--hash=sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962 \
|
||||||
--hash=sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d \
|
--hash=sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8 \
|
||||||
--hash=sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806 \
|
--hash=sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391 \
|
||||||
--hash=sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99 \
|
--hash=sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc \
|
||||||
--hash=sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8 \
|
--hash=sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2 \
|
||||||
--hash=sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b \
|
--hash=sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155 \
|
||||||
--hash=sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5 \
|
--hash=sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb \
|
||||||
--hash=sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da \
|
--hash=sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0 \
|
||||||
--hash=sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0 \
|
--hash=sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c \
|
||||||
--hash=sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078 \
|
--hash=sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a \
|
||||||
--hash=sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f \
|
--hash=sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004 \
|
||||||
--hash=sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029 \
|
--hash=sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060 \
|
||||||
--hash=sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353 \
|
--hash=sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232 \
|
||||||
--hash=sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638 \
|
--hash=sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93 \
|
||||||
--hash=sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9 \
|
--hash=sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129 \
|
||||||
--hash=sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f \
|
--hash=sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163 \
|
||||||
--hash=sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7 \
|
--hash=sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de \
|
||||||
--hash=sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3 \
|
--hash=sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6 \
|
||||||
--hash=sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e \
|
--hash=sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23 \
|
||||||
--hash=sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016 \
|
--hash=sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569 \
|
||||||
--hash=sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088 \
|
--hash=sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d \
|
||||||
--hash=sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4 \
|
--hash=sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778 \
|
||||||
--hash=sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882 \
|
--hash=sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d \
|
||||||
--hash=sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7 \
|
--hash=sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36 \
|
||||||
--hash=sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53 \
|
--hash=sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a \
|
||||||
--hash=sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d \
|
--hash=sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6 \
|
||||||
--hash=sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080 \
|
--hash=sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34 \
|
||||||
--hash=sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5 \
|
--hash=sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704 \
|
||||||
--hash=sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d \
|
--hash=sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106 \
|
||||||
--hash=sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c \
|
--hash=sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9 \
|
||||||
--hash=sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8 \
|
--hash=sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862 \
|
||||||
--hash=sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633 \
|
--hash=sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b \
|
||||||
--hash=sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9 \
|
--hash=sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255 \
|
||||||
--hash=sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c
|
--hash=sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16 \
|
||||||
|
--hash=sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3 \
|
||||||
|
--hash=sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133 \
|
||||||
|
--hash=sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb \
|
||||||
|
--hash=sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657 \
|
||||||
|
--hash=sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d \
|
||||||
|
--hash=sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca \
|
||||||
|
--hash=sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36 \
|
||||||
|
--hash=sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c \
|
||||||
|
--hash=sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e \
|
||||||
|
--hash=sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff \
|
||||||
|
--hash=sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7 \
|
||||||
|
--hash=sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5 \
|
||||||
|
--hash=sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02 \
|
||||||
|
--hash=sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c \
|
||||||
|
--hash=sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df \
|
||||||
|
--hash=sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3 \
|
||||||
|
--hash=sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a \
|
||||||
|
--hash=sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959 \
|
||||||
|
--hash=sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234 \
|
||||||
|
--hash=sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
cryptography==42.0.8 \
|
cryptography==43.0.0 \
|
||||||
--hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \
|
--hash=sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709 \
|
||||||
--hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \
|
--hash=sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069 \
|
||||||
--hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \
|
--hash=sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2 \
|
||||||
--hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \
|
--hash=sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b \
|
||||||
--hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \
|
--hash=sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e \
|
||||||
--hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \
|
--hash=sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70 \
|
||||||
--hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \
|
--hash=sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778 \
|
||||||
--hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \
|
--hash=sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22 \
|
||||||
--hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \
|
--hash=sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895 \
|
||||||
--hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \
|
--hash=sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf \
|
||||||
--hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \
|
--hash=sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431 \
|
||||||
--hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \
|
--hash=sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f \
|
||||||
--hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \
|
--hash=sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947 \
|
||||||
--hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \
|
--hash=sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74 \
|
||||||
--hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \
|
--hash=sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc \
|
||||||
--hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \
|
--hash=sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66 \
|
||||||
--hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \
|
--hash=sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66 \
|
||||||
--hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \
|
--hash=sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf \
|
||||||
--hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \
|
--hash=sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f \
|
||||||
--hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \
|
--hash=sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5 \
|
||||||
--hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \
|
--hash=sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e \
|
||||||
--hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \
|
--hash=sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f \
|
||||||
--hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \
|
--hash=sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55 \
|
||||||
--hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \
|
--hash=sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1 \
|
||||||
--hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \
|
--hash=sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47 \
|
||||||
--hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \
|
--hash=sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5 \
|
||||||
--hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \
|
--hash=sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0
|
||||||
--hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \
|
|
||||||
--hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \
|
|
||||||
--hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \
|
|
||||||
--hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \
|
|
||||||
--hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e
|
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# pdfminer-six
|
# pdfminer-six
|
||||||
@ -287,13 +317,13 @@ filelock==3.15.4 \
|
|||||||
--hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \
|
--hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \
|
||||||
--hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7
|
--hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
identify==2.5.36 \
|
identify==2.6.0 \
|
||||||
--hash=sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa \
|
--hash=sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf \
|
||||||
--hash=sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d
|
--hash=sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
importlib-metadata==7.1.0 \
|
importlib-metadata==8.0.0 \
|
||||||
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \
|
--hash=sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f \
|
||||||
--hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2
|
--hash=sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# build
|
# build
|
||||||
@ -311,9 +341,9 @@ packaging==24.1 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# build
|
# build
|
||||||
pdfminer-six==20231228 \
|
pdfminer-six==20240706 \
|
||||||
--hash=sha256:6004da3ad1a7a4d45930cb950393df89b068e73be365a6ff64a838d37bcb08c4 \
|
--hash=sha256:c631a46d5da957a9ffe4460c5dce21e8431dabb615fee5f9f4400603a58d95a6 \
|
||||||
--hash=sha256:e8d3c3310e6fbc1fe414090123ab01351634b4ecb021232206c4c9a8ca3e3b8f
|
--hash=sha256:f4f70e74174b4b3542fcb8406a210b6e2e27cd0f0b5fd04534a8cc0d8951e38c
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
pip==24.2 \
|
pip==24.2 \
|
||||||
--hash=sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2 \
|
--hash=sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2 \
|
||||||
@ -327,9 +357,9 @@ platformdirs==4.2.2 \
|
|||||||
--hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \
|
--hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \
|
||||||
--hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3
|
--hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
pre-commit==3.7.1 \
|
pre-commit==3.8.0 \
|
||||||
--hash=sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a \
|
--hash=sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af \
|
||||||
--hash=sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5
|
--hash=sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
pycparser==2.22 \
|
pycparser==2.22 \
|
||||||
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
|
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
|
||||||
@ -343,71 +373,73 @@ pyproject-hooks==1.1.0 \
|
|||||||
# via
|
# via
|
||||||
# build
|
# build
|
||||||
# pip-tools
|
# pip-tools
|
||||||
pyyaml==6.0.1 \
|
pyyaml==6.0.2 \
|
||||||
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
|
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||||
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
|
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
|
||||||
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
|
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
|
||||||
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
|
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
|
||||||
--hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
|
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
|
||||||
--hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
|
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
|
||||||
--hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
|
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
|
||||||
--hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
|
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
|
||||||
--hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
|
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
|
||||||
--hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
|
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
|
||||||
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
|
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
|
||||||
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
|
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
|
||||||
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
|
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
|
||||||
--hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
|
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
|
||||||
--hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
|
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
|
||||||
--hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
|
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
|
||||||
--hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
|
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
|
||||||
--hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
|
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
|
||||||
--hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
|
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
|
||||||
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
|
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
|
||||||
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
|
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
|
||||||
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
|
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
|
||||||
--hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
|
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
|
||||||
--hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
|
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
|
||||||
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
|
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
|
||||||
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
|
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
|
||||||
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
|
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
|
||||||
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
|
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
|
||||||
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
|
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
|
||||||
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
|
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
|
||||||
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
|
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
|
||||||
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
|
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
|
||||||
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
|
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
|
||||||
--hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
|
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
|
||||||
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
|
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
|
||||||
--hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
|
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
|
||||||
--hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
|
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
|
||||||
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
|
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
|
||||||
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
|
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
|
||||||
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
|
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
|
||||||
--hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
|
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
|
||||||
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
|
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
|
||||||
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
|
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
|
||||||
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
|
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
|
||||||
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
|
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
|
||||||
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
|
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
|
||||||
--hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
|
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
|
||||||
--hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
|
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
|
||||||
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
|
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
|
||||||
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
|
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
|
||||||
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
|
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
|
||||||
|
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
|
||||||
|
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# pre-commit
|
# pre-commit
|
||||||
setuptools==72.1.0 \
|
setuptools==73.0.1 \
|
||||||
--hash=sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1 \
|
--hash=sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e \
|
||||||
--hash=sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec
|
--hash=sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# -r src/backend/requirements-dev.in
|
# -r src/backend/requirements-dev.in
|
||||||
# pip-tools
|
# pip-tools
|
||||||
sqlparse==0.5.0 \
|
sqlparse==0.5.1 \
|
||||||
--hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \
|
--hash=sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4 \
|
||||||
--hash=sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663
|
--hash=sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# django
|
# django
|
||||||
@ -415,6 +447,7 @@ tomli==2.0.1 \
|
|||||||
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
|
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
|
||||||
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
|
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
|
||||||
# via
|
# via
|
||||||
|
# -c src/backend/requirements.txt
|
||||||
# build
|
# build
|
||||||
# coverage
|
# coverage
|
||||||
# pip-tools
|
# pip-tools
|
||||||
@ -429,13 +462,13 @@ virtualenv==20.26.3 \
|
|||||||
--hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \
|
--hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \
|
||||||
--hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589
|
--hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
wheel==0.43.0 \
|
wheel==0.44.0 \
|
||||||
--hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \
|
--hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \
|
||||||
--hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81
|
--hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
zipp==3.19.2 \
|
zipp==3.20.0 \
|
||||||
--hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \
|
--hash=sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31 \
|
||||||
--hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c
|
--hash=sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# importlib-metadata
|
# importlib-metadata
|
||||||
|
@ -62,3 +62,6 @@ opentelemetry-exporter-otlp
|
|||||||
opentelemetry-instrumentation-django
|
opentelemetry-instrumentation-django
|
||||||
opentelemetry-instrumentation-requests
|
opentelemetry-instrumentation-requests
|
||||||
opentelemetry-instrumentation-redis
|
opentelemetry-instrumentation-redis
|
||||||
|
|
||||||
|
# pinned sub-deps
|
||||||
|
pydyf==0.10.0 # Fixed 2024-08-22 see https://github.com/inventree/InvenTree/pull/7961/files
|
||||||
|
File diff suppressed because it is too large
Load Diff
22
src/frontend/src/components/buttons/RemoveRowButton.tsx
Normal file
22
src/frontend/src/components/buttons/RemoveRowButton.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
|
import { ActionButton } from './ActionButton';
|
||||||
|
|
||||||
|
export default function RemoveRowButton({
|
||||||
|
onClick,
|
||||||
|
tooltip = t`Remove this row`
|
||||||
|
}: {
|
||||||
|
onClick: () => void;
|
||||||
|
tooltip?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ActionButton
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<InvenTreeIcon icon="square_x" />}
|
||||||
|
tooltip={tooltip}
|
||||||
|
tooltipAlignment="top"
|
||||||
|
color="red"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -502,7 +502,20 @@ export function ApiForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof v === 'object' && Array.isArray(v)) {
|
if (typeof v === 'object' && Array.isArray(v)) {
|
||||||
form.setError(path, { message: v.join(', ') });
|
if (field?.field_type == 'table') {
|
||||||
|
// Special handling for "table" fields - they have nested errors
|
||||||
|
v.forEach((item: any, idx: number) => {
|
||||||
|
for (const [key, value] of Object.entries(item)) {
|
||||||
|
const path: string = `${k}.${idx}.${key}`;
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
form.setError(path, { message: value.join(', ') });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Standard error handling for other fields
|
||||||
|
form.setError(path, { message: v.join(', ') });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
processErrors(v, path);
|
processErrors(v, path);
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,53 @@
|
|||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { ApiFormField, ApiFormFieldType } from './fields/ApiFormField';
|
import { ApiFormField, ApiFormFieldType } from './fields/ApiFormField';
|
||||||
|
|
||||||
export function StandaloneField({
|
export function StandaloneField({
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
|
fieldName = 'field',
|
||||||
defaultValue,
|
defaultValue,
|
||||||
hideLabels
|
hideLabels,
|
||||||
|
error
|
||||||
}: {
|
}: {
|
||||||
fieldDefinition: ApiFormFieldType;
|
fieldDefinition: ApiFormFieldType;
|
||||||
|
fieldName?: string;
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
hideLabels?: boolean;
|
hideLabels?: boolean;
|
||||||
|
error?: string;
|
||||||
}) {
|
}) {
|
||||||
|
// Field must have a defined name
|
||||||
|
const name = useMemo(() => fieldName ?? 'field', [fieldName]);
|
||||||
|
|
||||||
const defaultValues = useMemo(() => {
|
const defaultValues = useMemo(() => {
|
||||||
if (defaultValue)
|
if (defaultValue)
|
||||||
return {
|
return {
|
||||||
field: defaultValue
|
[name]: defaultValue
|
||||||
};
|
};
|
||||||
return {};
|
return {};
|
||||||
}, [defaultValue]);
|
}, [defaultValue]);
|
||||||
|
|
||||||
const form = useForm<{}>({
|
const form = useForm({
|
||||||
criteriaMode: 'all',
|
criteriaMode: 'all',
|
||||||
defaultValues
|
defaultValues
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.clearErrors();
|
||||||
|
|
||||||
|
if (!!error) {
|
||||||
|
form.setError(name, { message: error });
|
||||||
|
}
|
||||||
|
}, [form, error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<ApiFormField
|
<ApiFormField
|
||||||
fieldName="field"
|
fieldName={name}
|
||||||
definition={fieldDefinition}
|
definition={fieldDefinition}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
hideLabels={hideLabels}
|
hideLabels={hideLabels}
|
||||||
|
setFields={undefined}
|
||||||
/>
|
/>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
);
|
);
|
||||||
|
@ -204,8 +204,8 @@ export function ApiFormField({
|
|||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
// Construct the individual field
|
// Construct the individual field
|
||||||
function buildField() {
|
const fieldInstance = useMemo(() => {
|
||||||
switch (definition.field_type) {
|
switch (fieldDefinition.field_type) {
|
||||||
case 'related field':
|
case 'related field':
|
||||||
return (
|
return (
|
||||||
<RelatedModelField
|
<RelatedModelField
|
||||||
@ -236,7 +236,7 @@ export function ApiFormField({
|
|||||||
checked={booleanValue}
|
checked={booleanValue}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={fieldId}
|
id={fieldId}
|
||||||
aria-label={`boolean-field-${field.name}`}
|
aria-label={`boolean-field-${fieldName}`}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
size="sm"
|
size="sm"
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
@ -322,16 +322,30 @@ export function ApiFormField({
|
|||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}, [
|
||||||
|
booleanValue,
|
||||||
|
control,
|
||||||
|
controller,
|
||||||
|
field,
|
||||||
|
fieldId,
|
||||||
|
fieldName,
|
||||||
|
fieldDefinition,
|
||||||
|
numericalValue,
|
||||||
|
onChange,
|
||||||
|
reducedDefinition,
|
||||||
|
ref,
|
||||||
|
setFields,
|
||||||
|
value
|
||||||
|
]);
|
||||||
|
|
||||||
if (definition.hidden) {
|
if (fieldDefinition.hidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
{definition.preFieldContent}
|
{definition.preFieldContent}
|
||||||
{buildField()}
|
{fieldInstance}
|
||||||
{definition.postFieldContent}
|
{definition.postFieldContent}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@ -207,7 +207,7 @@ export function RelatedModelField({
|
|||||||
setPk(_pk);
|
setPk(_pk);
|
||||||
|
|
||||||
// Run custom callback for this field (if provided)
|
// Run custom callback for this field (if provided)
|
||||||
definition.onValueChange?.(_pk, value.data ?? {});
|
definition.onValueChange?.(_pk, value?.data ?? {});
|
||||||
},
|
},
|
||||||
[field.onChange, definition]
|
[field.onChange, definition]
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
import { Container, Group, Table } from '@mantine/core';
|
import { Container, Group, Table } from '@mantine/core';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||||
|
|
||||||
import { InvenTreeIcon } from '../../../functions/icons';
|
import { InvenTreeIcon } from '../../../functions/icons';
|
||||||
import { StandaloneField } from '../StandaloneField';
|
import { StandaloneField } from '../StandaloneField';
|
||||||
import { ApiFormFieldType } from './ApiFormField';
|
import { ApiFormFieldType } from './ApiFormField';
|
||||||
|
|
||||||
|
export interface TableFieldRowProps {
|
||||||
|
item: any;
|
||||||
|
idx: number;
|
||||||
|
rowErrors: any;
|
||||||
|
control: UseControllerReturn<FieldValues, any>;
|
||||||
|
changeFn: (idx: number, key: string, value: any) => void;
|
||||||
|
removeFn: (idx: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export function TableField({
|
export function TableField({
|
||||||
definition,
|
definition,
|
||||||
fieldName,
|
fieldName,
|
||||||
@ -25,6 +34,7 @@ export function TableField({
|
|||||||
const onRowFieldChange = (idx: number, key: string, value: any) => {
|
const onRowFieldChange = (idx: number, key: string, value: any) => {
|
||||||
const val = field.value;
|
const val = field.value;
|
||||||
val[idx][key] = value;
|
val[idx][key] = value;
|
||||||
|
|
||||||
field.onChange(val);
|
field.onChange(val);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -34,6 +44,16 @@ export function TableField({
|
|||||||
field.onChange(val);
|
field.onChange(val);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extract errors associated with the current row
|
||||||
|
const rowErrors = useCallback(
|
||||||
|
(idx: number) => {
|
||||||
|
if (Array.isArray(error)) {
|
||||||
|
return error[idx];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[error]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table highlightOnHover striped aria-label={`table-field-${field.name}`}>
|
<Table highlightOnHover striped aria-label={`table-field-${field.name}`}>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
@ -49,18 +69,21 @@ export function TableField({
|
|||||||
// Table fields require render function
|
// Table fields require render function
|
||||||
if (!definition.modelRenderer) {
|
if (!definition.modelRenderer) {
|
||||||
return (
|
return (
|
||||||
<Table.Tr>{t`modelRenderer entry required for tables`}</Table.Tr>
|
<Table.Tr key="table-row-no-renderer">{t`modelRenderer entry required for tables`}</Table.Tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return definition.modelRenderer({
|
return definition.modelRenderer({
|
||||||
item: item,
|
item: item,
|
||||||
idx: idx,
|
idx: idx,
|
||||||
|
rowErrors: rowErrors(idx),
|
||||||
|
control: control,
|
||||||
changeFn: onRowFieldChange,
|
changeFn: onRowFieldChange,
|
||||||
removeFn: removeRow
|
removeFn: removeRow
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<Table.Tr>
|
<Table.Tr key="table-row-no-entries">
|
||||||
<Table.Td
|
<Table.Td
|
||||||
style={{ textAlign: 'center' }}
|
style={{ textAlign: 'center' }}
|
||||||
colSpan={definition.headers?.length}
|
colSpan={definition.headers?.length}
|
||||||
@ -92,11 +115,13 @@ export function TableFieldExtraRow({
|
|||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
emptyValue,
|
emptyValue,
|
||||||
|
error,
|
||||||
onValueChange
|
onValueChange
|
||||||
}: {
|
}: {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
fieldDefinition: ApiFormFieldType;
|
fieldDefinition: ApiFormFieldType;
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
|
error?: string;
|
||||||
emptyValue?: any;
|
emptyValue?: any;
|
||||||
onValueChange: (value: any) => void;
|
onValueChange: (value: any) => void;
|
||||||
}) {
|
}) {
|
||||||
@ -129,6 +154,7 @@ export function TableFieldExtraRow({
|
|||||||
<StandaloneField
|
<StandaloneField
|
||||||
fieldDefinition={field}
|
fieldDefinition={field}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
|
error={error}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
|
@ -20,7 +20,7 @@ import { ReactNode, useMemo } from 'react';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { identifierString } from '../../functions/conversion';
|
import { identifierString } from '../../functions/conversion';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { InvenTreeQRCode } from './QRCode';
|
import { InvenTreeQRCode, QRCodeLink, QRCodeUnlink } from './QRCode';
|
||||||
|
|
||||||
export type ActionDropdownItem = {
|
export type ActionDropdownItem = {
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
@ -112,69 +112,91 @@ export function ActionDropdown({
|
|||||||
|
|
||||||
// Dropdown menu for barcode actions
|
// Dropdown menu for barcode actions
|
||||||
export function BarcodeActionDropdown({
|
export function BarcodeActionDropdown({
|
||||||
actions
|
model,
|
||||||
}: {
|
pk,
|
||||||
actions: ActionDropdownItem[];
|
hash = null,
|
||||||
}) {
|
actions = [],
|
||||||
|
perm: permission = true
|
||||||
|
}: Readonly<{
|
||||||
|
model: ModelType;
|
||||||
|
pk: number;
|
||||||
|
hash?: boolean | null;
|
||||||
|
actions?: ActionDropdownItem[];
|
||||||
|
perm?: boolean;
|
||||||
|
}>) {
|
||||||
|
const hidden = hash === null;
|
||||||
|
const prop = { model, pk, hash };
|
||||||
return (
|
return (
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
tooltip={t`Barcode Actions`}
|
tooltip={t`Barcode Actions`}
|
||||||
icon={<IconQrcode />}
|
icon={<IconQrcode />}
|
||||||
actions={actions}
|
actions={[
|
||||||
|
GeneralBarcodeAction({
|
||||||
|
mdl_prop: prop,
|
||||||
|
title: t`View`,
|
||||||
|
icon: <IconQrcode />,
|
||||||
|
tooltip: t`View barcode`,
|
||||||
|
ChildItem: InvenTreeQRCode
|
||||||
|
}),
|
||||||
|
GeneralBarcodeAction({
|
||||||
|
hidden: hidden || hash || !permission,
|
||||||
|
mdl_prop: prop,
|
||||||
|
title: t`Link Barcode`,
|
||||||
|
icon: <IconLink />,
|
||||||
|
tooltip: t`Link a custom barcode to this item`,
|
||||||
|
ChildItem: QRCodeLink
|
||||||
|
}),
|
||||||
|
GeneralBarcodeAction({
|
||||||
|
hidden: hidden || !hash || !permission,
|
||||||
|
mdl_prop: prop,
|
||||||
|
title: t`Unlink Barcode`,
|
||||||
|
icon: <IconUnlink />,
|
||||||
|
tooltip: t`Unlink custom barcode`,
|
||||||
|
ChildItem: QRCodeUnlink
|
||||||
|
}),
|
||||||
|
...actions
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common action button for viewing a barcode
|
export type QrCodeType = {
|
||||||
export function ViewBarcodeAction({
|
|
||||||
hidden = false,
|
|
||||||
model,
|
|
||||||
pk
|
|
||||||
}: {
|
|
||||||
hidden?: boolean;
|
|
||||||
model: ModelType;
|
model: ModelType;
|
||||||
pk: number;
|
pk: number;
|
||||||
|
hash?: boolean | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function GeneralBarcodeAction({
|
||||||
|
hidden = false,
|
||||||
|
mdl_prop,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
tooltip,
|
||||||
|
ChildItem
|
||||||
|
}: {
|
||||||
|
hidden?: boolean;
|
||||||
|
mdl_prop: QrCodeType;
|
||||||
|
title: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
tooltip: string;
|
||||||
|
ChildItem: any;
|
||||||
}): ActionDropdownItem {
|
}): ActionDropdownItem {
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
modals.open({
|
modals.open({
|
||||||
title: t`View Barcode`,
|
title: title,
|
||||||
children: <InvenTreeQRCode model={model} pk={pk} />
|
children: <ChildItem mdl_prop={mdl_prop} />
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
icon: <IconQrcode />,
|
icon: icon,
|
||||||
name: t`View`,
|
name: title,
|
||||||
tooltip: t`View barcode`,
|
tooltip: tooltip,
|
||||||
onClick: onClick,
|
onClick: onClick,
|
||||||
hidden: hidden
|
hidden: hidden
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common action button for linking a custom barcode
|
|
||||||
export function LinkBarcodeAction(
|
|
||||||
props: ActionDropdownItem
|
|
||||||
): ActionDropdownItem {
|
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
icon: <IconLink />,
|
|
||||||
name: t`Link Barcode`,
|
|
||||||
tooltip: t`Link custom barcode`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common action button for un-linking a custom barcode
|
|
||||||
export function UnlinkBarcodeAction(
|
|
||||||
props: ActionDropdownItem
|
|
||||||
): ActionDropdownItem {
|
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
icon: <IconUnlink />,
|
|
||||||
name: t`Unlink Barcode`,
|
|
||||||
tooltip: t`Unlink custom barcode`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common action button for editing an item
|
// Common action button for editing an item
|
||||||
export function EditItemAction(props: ActionDropdownItem): ActionDropdownItem {
|
export function EditItemAction(props: ActionDropdownItem): ActionDropdownItem {
|
||||||
return {
|
return {
|
||||||
|
@ -18,8 +18,11 @@ export function ProgressBar(props: Readonly<ProgressBarProps>) {
|
|||||||
let maximum = props.maximum ?? 100;
|
let maximum = props.maximum ?? 100;
|
||||||
let value = Math.max(props.value, 0);
|
let value = Math.max(props.value, 0);
|
||||||
|
|
||||||
// Calculate progress as a percentage of the maximum value
|
if (maximum == 0) {
|
||||||
return Math.min(100, (value / maximum) * 100);
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (value / maximum) * 100;
|
||||||
}, [props]);
|
}, [props]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
Code,
|
Code,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
Image,
|
||||||
Select,
|
Select,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Stack,
|
Stack,
|
||||||
Text
|
Text,
|
||||||
|
TextInput
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { modals } from '@mantine/modals';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import QR from 'qrcode';
|
import QR from 'qrcode';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { ModelType } from '../../enums/ModelType';
|
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||||
import { CopyButton } from '../buttons/CopyButton';
|
import { CopyButton } from '../buttons/CopyButton';
|
||||||
|
import { QrCodeType } from './ActionDropdown';
|
||||||
|
|
||||||
type QRCodeProps = {
|
type QRCodeProps = {
|
||||||
ecl?: 'L' | 'M' | 'Q' | 'H';
|
ecl?: 'L' | 'M' | 'Q' | 'H';
|
||||||
@ -51,15 +55,13 @@ export const QRCode = ({ data, ecl = 'Q', margin = 1 }: QRCodeProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type InvenTreeQRCodeProps = {
|
type InvenTreeQRCodeProps = {
|
||||||
model: ModelType;
|
mdl_prop: QrCodeType;
|
||||||
pk: number;
|
|
||||||
showEclSelector?: boolean;
|
showEclSelector?: boolean;
|
||||||
} & Omit<QRCodeProps, 'data'>;
|
} & Omit<QRCodeProps, 'data'>;
|
||||||
|
|
||||||
export const InvenTreeQRCode = ({
|
export const InvenTreeQRCode = ({
|
||||||
|
mdl_prop,
|
||||||
showEclSelector = true,
|
showEclSelector = true,
|
||||||
model,
|
|
||||||
pk,
|
|
||||||
ecl: eclProp = 'Q',
|
ecl: eclProp = 'Q',
|
||||||
...props
|
...props
|
||||||
}: InvenTreeQRCodeProps) => {
|
}: InvenTreeQRCodeProps) => {
|
||||||
@ -71,11 +73,11 @@ export const InvenTreeQRCode = ({
|
|||||||
}, [eclProp]);
|
}, [eclProp]);
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
queryKey: ['qr-code', model, pk],
|
queryKey: ['qr-code', mdl_prop.model, mdl_prop.pk],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await api.post(apiUrl(ApiEndpoints.generate_barcode), {
|
const res = await api.post(apiUrl(ApiEndpoints.generate_barcode), {
|
||||||
model,
|
model: mdl_prop.model,
|
||||||
pk
|
pk: mdl_prop.pk
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.data?.barcode as string;
|
return res.data?.barcode as string;
|
||||||
@ -94,6 +96,15 @@ export const InvenTreeQRCode = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
|
{mdl_prop.hash ? (
|
||||||
|
<Alert variant="outline" color="red" title={t`Custom bascode`}>
|
||||||
|
<Trans>
|
||||||
|
A custom barcode is registered for this item. The shown code is not
|
||||||
|
that custom barcode.
|
||||||
|
</Trans>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<QRCode data={data} ecl={ecl} {...props} />
|
<QRCode data={data} ecl={ecl} {...props} />
|
||||||
|
|
||||||
{data && settings.getSetting('BARCODE_SHOW_TEXT', 'false') && (
|
{data && settings.getSetting('BARCODE_SHOW_TEXT', 'false') && (
|
||||||
@ -128,3 +139,55 @@ export const InvenTreeQRCode = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const QRCodeLink = ({ mdl_prop }: { mdl_prop: QrCodeType }) => {
|
||||||
|
const [barcode, setBarcode] = useState('');
|
||||||
|
|
||||||
|
function linkBarcode() {
|
||||||
|
api
|
||||||
|
.post(apiUrl(ApiEndpoints.barcode_link), {
|
||||||
|
[mdl_prop.model]: mdl_prop.pk,
|
||||||
|
barcode: barcode
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
modals.closeAll();
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<TextInput
|
||||||
|
label={t`Barcode`}
|
||||||
|
value={barcode}
|
||||||
|
onChange={(event) => setBarcode(event.currentTarget.value)}
|
||||||
|
placeholder={t`Scan barcode data here using barcode scanner`}
|
||||||
|
/>
|
||||||
|
<Button color="green" onClick={linkBarcode} mt="lg" fullWidth>
|
||||||
|
<Trans>Link</Trans>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QRCodeUnlink = ({ mdl_prop }: { mdl_prop: QrCodeType }) => {
|
||||||
|
function unlinkBarcode() {
|
||||||
|
api
|
||||||
|
.post(apiUrl(ApiEndpoints.barcode_unlink), {
|
||||||
|
[mdl_prop.model]: mdl_prop.pk
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
modals.closeAll();
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Text>
|
||||||
|
<Trans>This will remove the link to the associated barcode</Trans>
|
||||||
|
</Text>
|
||||||
|
<Button color="red" onClick={unlinkBarcode}>
|
||||||
|
<Trans>Unlink Barcode</Trans>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -19,7 +19,7 @@ export function RenderBuildOrder(
|
|||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.title}
|
secondary={instance.title}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
status: instance.status,
|
status: instance.status_custom_key,
|
||||||
type: ModelType.build
|
type: ModelType.build
|
||||||
})}
|
})}
|
||||||
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
||||||
@ -39,7 +39,7 @@ export function RenderBuildLine({
|
|||||||
primary={instance.part_detail.full_name}
|
primary={instance.part_detail.full_name}
|
||||||
secondary={instance.quantity}
|
secondary={instance.quantity}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
status: instance.status,
|
status: instance.status_custom_key,
|
||||||
type: ModelType.build
|
type: ModelType.build
|
||||||
})}
|
})}
|
||||||
image={instance.part_detail.thumbnail || instance.part_detail.image}
|
image={instance.part_detail.thumbnail || instance.part_detail.image}
|
||||||
|
@ -15,6 +15,12 @@ export function RenderProjectCode({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RenderContentType({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
|
return instance && <RenderInlineModel primary={instance.app_labeled_name} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function RenderImportSession({
|
export function RenderImportSession({
|
||||||
instance
|
instance
|
||||||
}: {
|
}: {
|
||||||
|
@ -16,7 +16,11 @@ import {
|
|||||||
RenderManufacturerPart,
|
RenderManufacturerPart,
|
||||||
RenderSupplierPart
|
RenderSupplierPart
|
||||||
} from './Company';
|
} from './Company';
|
||||||
import { RenderImportSession, RenderProjectCode } from './Generic';
|
import {
|
||||||
|
RenderContentType,
|
||||||
|
RenderImportSession,
|
||||||
|
RenderProjectCode
|
||||||
|
} from './Generic';
|
||||||
import { ModelInformationDict } from './ModelType';
|
import { ModelInformationDict } from './ModelType';
|
||||||
import {
|
import {
|
||||||
RenderPurchaseOrder,
|
RenderPurchaseOrder,
|
||||||
@ -87,7 +91,8 @@ const RendererLookup: EnumDictionary<
|
|||||||
[ModelType.importsession]: RenderImportSession,
|
[ModelType.importsession]: RenderImportSession,
|
||||||
[ModelType.reporttemplate]: RenderReportTemplate,
|
[ModelType.reporttemplate]: RenderReportTemplate,
|
||||||
[ModelType.labeltemplate]: RenderLabelTemplate,
|
[ModelType.labeltemplate]: RenderLabelTemplate,
|
||||||
[ModelType.pluginconfig]: RenderPlugin
|
[ModelType.pluginconfig]: RenderPlugin,
|
||||||
|
[ModelType.contenttype]: RenderContentType
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RenderInstanceProps = {
|
export type RenderInstanceProps = {
|
||||||
|
@ -241,6 +241,11 @@ export const ModelInformationDict: ModelDict = {
|
|||||||
url_overview: '/pluginconfig',
|
url_overview: '/pluginconfig',
|
||||||
url_detail: '/pluginconfig/:pk/',
|
url_detail: '/pluginconfig/:pk/',
|
||||||
api_endpoint: ApiEndpoints.plugin_list
|
api_endpoint: ApiEndpoints.plugin_list
|
||||||
|
},
|
||||||
|
contenttype: {
|
||||||
|
label: t`Content Type`,
|
||||||
|
label_multiple: t`Content Types`,
|
||||||
|
api_endpoint: ApiEndpoints.content_type_list
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ export function RenderPurchaseOrder(
|
|||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
status: instance.status,
|
status: instance.status_custom_key,
|
||||||
type: ModelType.purchaseorder
|
type: ModelType.purchaseorder
|
||||||
})}
|
})}
|
||||||
image={supplier.thumnbnail || supplier.image}
|
image={supplier.thumnbnail || supplier.image}
|
||||||
@ -49,7 +49,7 @@ export function RenderReturnOrder(
|
|||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
status: instance.status,
|
status: instance.status_custom_key,
|
||||||
type: ModelType.returnorder
|
type: ModelType.returnorder
|
||||||
})}
|
})}
|
||||||
image={customer.thumnbnail || customer.image}
|
image={customer.thumnbnail || customer.image}
|
||||||
@ -94,7 +94,7 @@ export function RenderSalesOrder(
|
|||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
status: instance.status,
|
status: instance.status_custom_key,
|
||||||
type: ModelType.salesorder
|
type: ModelType.salesorder
|
||||||
})}
|
})}
|
||||||
image={customer.thumnbnail || customer.image}
|
image={customer.thumnbnail || customer.image}
|
||||||
|
19
src/frontend/src/components/settings/FactCollection.tsx
Normal file
19
src/frontend/src/components/settings/FactCollection.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { SimpleGrid } from '@mantine/core';
|
||||||
|
|
||||||
|
import { FactItem } from './FactItem';
|
||||||
|
|
||||||
|
export function FactCollection({
|
||||||
|
items,
|
||||||
|
minItems = 3
|
||||||
|
}: {
|
||||||
|
items: { title: string; value: any }[];
|
||||||
|
minItems?: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SimpleGrid cols={minItems} spacing="xs">
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<FactItem key={index} title={item.title} value={item.value} />
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
);
|
||||||
|
}
|
14
src/frontend/src/components/settings/FactItem.tsx
Normal file
14
src/frontend/src/components/settings/FactItem.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Paper, Stack, Text } from '@mantine/core';
|
||||||
|
|
||||||
|
import { StylishText } from '../items/StylishText';
|
||||||
|
|
||||||
|
export function FactItem({ title, value }: { title: string; value: number }) {
|
||||||
|
return (
|
||||||
|
<Paper p="md" shadow="xs">
|
||||||
|
<Stack gap="xs">
|
||||||
|
<StylishText size="md">{title}</StylishText>
|
||||||
|
<Text>{value}</Text>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
@ -39,14 +39,18 @@ export enum ApiEndpoints {
|
|||||||
settings_global_list = 'settings/global/',
|
settings_global_list = 'settings/global/',
|
||||||
settings_user_list = 'settings/user/',
|
settings_user_list = 'settings/user/',
|
||||||
barcode = 'barcode/',
|
barcode = 'barcode/',
|
||||||
|
barcode_link = 'barcode/link/',
|
||||||
|
barcode_unlink = 'barcode/unlink/',
|
||||||
generate_barcode = 'barcode/generate/',
|
generate_barcode = 'barcode/generate/',
|
||||||
news = 'news/',
|
news = 'news/',
|
||||||
global_status = 'generic/status/',
|
global_status = 'generic/status/',
|
||||||
|
custom_state_list = 'generic/status/custom/',
|
||||||
version = 'version/',
|
version = 'version/',
|
||||||
license = 'license/',
|
license = 'license/',
|
||||||
sso_providers = 'auth/providers/',
|
sso_providers = 'auth/providers/',
|
||||||
group_list = 'user/group/',
|
group_list = 'user/group/',
|
||||||
owner_list = 'user/owner/',
|
owner_list = 'user/owner/',
|
||||||
|
content_type_list = 'contenttype/',
|
||||||
icons = 'icons/',
|
icons = 'icons/',
|
||||||
|
|
||||||
// Data import endpoints
|
// Data import endpoints
|
||||||
@ -70,6 +74,9 @@ export enum ApiEndpoints {
|
|||||||
build_output_create = 'build/:id/create-output/',
|
build_output_create = 'build/:id/create-output/',
|
||||||
build_output_scrap = 'build/:id/scrap-outputs/',
|
build_output_scrap = 'build/:id/scrap-outputs/',
|
||||||
build_output_delete = 'build/:id/delete-outputs/',
|
build_output_delete = 'build/:id/delete-outputs/',
|
||||||
|
build_order_auto_allocate = 'build/:id/auto-allocate/',
|
||||||
|
build_order_allocate = 'build/:id/allocate/',
|
||||||
|
build_order_deallocate = 'build/:id/unallocate/',
|
||||||
build_line_list = 'build/line/',
|
build_line_list = 'build/line/',
|
||||||
build_item_list = 'build/item/',
|
build_item_list = 'build/item/',
|
||||||
|
|
||||||
|
@ -31,5 +31,6 @@ export enum ModelType {
|
|||||||
group = 'group',
|
group = 'group',
|
||||||
reporttemplate = 'reporttemplate',
|
reporttemplate = 'reporttemplate',
|
||||||
labeltemplate = 'labeltemplate',
|
labeltemplate = 'labeltemplate',
|
||||||
pluginconfig = 'pluginconfig'
|
pluginconfig = 'pluginconfig',
|
||||||
|
contenttype = 'contenttype'
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Alert, Stack, Text } from '@mantine/core';
|
import { Alert, Stack, Table, Text } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconCalendar,
|
IconCalendar,
|
||||||
IconLink,
|
IconLink,
|
||||||
@ -10,16 +10,26 @@ import {
|
|||||||
IconUsersGroup
|
IconUsersGroup
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { DataTable } from 'mantine-datatable';
|
import { DataTable } from 'mantine-datatable';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { api } from '../App';
|
import { api } from '../App';
|
||||||
import { ActionButton } from '../components/buttons/ActionButton';
|
import { ActionButton } from '../components/buttons/ActionButton';
|
||||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
import RemoveRowButton from '../components/buttons/RemoveRowButton';
|
||||||
|
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||||
|
import {
|
||||||
|
ApiFormFieldSet,
|
||||||
|
ApiFormFieldType
|
||||||
|
} from '../components/forms/fields/ApiFormField';
|
||||||
|
import { TableFieldRowProps } from '../components/forms/fields/TableField';
|
||||||
|
import { ProgressBar } from '../components/items/ProgressBar';
|
||||||
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||||
import { ModelType } from '../enums/ModelType';
|
import { ModelType } from '../enums/ModelType';
|
||||||
|
import { resolveItem } from '../functions/conversion';
|
||||||
import { InvenTreeIcon } from '../functions/icons';
|
import { InvenTreeIcon } from '../functions/icons';
|
||||||
import { useCreateApiFormModal } from '../hooks/UseForm';
|
import { useCreateApiFormModal } from '../hooks/UseForm';
|
||||||
import { useBatchCodeGenerator } from '../hooks/UseGenerator';
|
import { useBatchCodeGenerator } from '../hooks/UseGenerator';
|
||||||
|
import { useSelectedRows } from '../hooks/UseSelectedRows';
|
||||||
import { apiUrl } from '../states/ApiState';
|
import { apiUrl } from '../states/ApiState';
|
||||||
import { useGlobalSettingsState } from '../states/SettingsState';
|
import { useGlobalSettingsState } from '../states/SettingsState';
|
||||||
import { PartColumn, StatusColumn } from '../tables/ColumnRenderers';
|
import { PartColumn, StatusColumn } from '../tables/ColumnRenderers';
|
||||||
@ -240,7 +250,7 @@ function buildOutputFormTable(outputs: any[], onRemove: (output: any) => void) {
|
|||||||
tooltip={t`Remove output`}
|
tooltip={t`Remove output`}
|
||||||
icon={<InvenTreeIcon icon="cancel" />}
|
icon={<InvenTreeIcon icon="cancel" />}
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => onRemove(record)}
|
onClick={() => onRemove(record.pk)}
|
||||||
disabled={outputs.length <= 1}
|
disabled={outputs.length <= 1}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -259,13 +269,11 @@ export function useCompleteBuildOutputsForm({
|
|||||||
outputs: any[];
|
outputs: any[];
|
||||||
onFormSuccess: (response: any) => void;
|
onFormSuccess: (response: any) => void;
|
||||||
}) {
|
}) {
|
||||||
const [selectedOutputs, setSelectedOutputs] = useState<any[]>([]);
|
|
||||||
|
|
||||||
const [location, setLocation] = useState<number | null>(null);
|
const [location, setLocation] = useState<number | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const { selectedRows, removeRow } = useSelectedRows({
|
||||||
setSelectedOutputs(outputs);
|
rows: outputs
|
||||||
}, [outputs]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location) {
|
if (location) {
|
||||||
@ -277,31 +285,21 @@ export function useCompleteBuildOutputsForm({
|
|||||||
);
|
);
|
||||||
}, [location, build.destination, build.part_detail]);
|
}, [location, build.destination, build.part_detail]);
|
||||||
|
|
||||||
// Remove a selected output from the list
|
|
||||||
const removeOutput = useCallback(
|
|
||||||
(output: any) => {
|
|
||||||
setSelectedOutputs(
|
|
||||||
selectedOutputs.filter((item) => item.pk != output.pk)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[selectedOutputs]
|
|
||||||
);
|
|
||||||
|
|
||||||
const preFormContent = useMemo(() => {
|
const preFormContent = useMemo(() => {
|
||||||
return buildOutputFormTable(selectedOutputs, removeOutput);
|
return buildOutputFormTable(selectedRows, removeRow);
|
||||||
}, [selectedOutputs, removeOutput]);
|
}, [selectedRows, removeRow]);
|
||||||
|
|
||||||
const buildOutputCompleteFields: ApiFormFieldSet = useMemo(() => {
|
const buildOutputCompleteFields: ApiFormFieldSet = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
outputs: {
|
outputs: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
value: selectedOutputs.map((output) => {
|
value: selectedRows.map((output: any) => {
|
||||||
return {
|
return {
|
||||||
output: output.pk
|
output: output.pk
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
status: {},
|
status_custom_key: {},
|
||||||
location: {
|
location: {
|
||||||
filters: {
|
filters: {
|
||||||
structural: false
|
structural: false
|
||||||
@ -314,7 +312,7 @@ export function useCompleteBuildOutputsForm({
|
|||||||
notes: {},
|
notes: {},
|
||||||
accept_incomplete_allocation: {}
|
accept_incomplete_allocation: {}
|
||||||
};
|
};
|
||||||
}, [selectedOutputs, location]);
|
}, [selectedRows, location]);
|
||||||
|
|
||||||
return useCreateApiFormModal({
|
return useCreateApiFormModal({
|
||||||
url: apiUrl(ApiEndpoints.build_output_complete, build.pk),
|
url: apiUrl(ApiEndpoints.build_output_complete, build.pk),
|
||||||
@ -327,6 +325,9 @@ export function useCompleteBuildOutputsForm({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dynamic form for scraping multiple build outputs
|
||||||
|
*/
|
||||||
export function useScrapBuildOutputsForm({
|
export function useScrapBuildOutputsForm({
|
||||||
build,
|
build,
|
||||||
outputs,
|
outputs,
|
||||||
@ -337,21 +338,10 @@ export function useScrapBuildOutputsForm({
|
|||||||
onFormSuccess: (response: any) => void;
|
onFormSuccess: (response: any) => void;
|
||||||
}) {
|
}) {
|
||||||
const [location, setLocation] = useState<number | null>(null);
|
const [location, setLocation] = useState<number | null>(null);
|
||||||
const [selectedOutputs, setSelectedOutputs] = useState<any[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const { selectedRows, removeRow } = useSelectedRows({
|
||||||
setSelectedOutputs(outputs);
|
rows: outputs
|
||||||
}, [outputs]);
|
});
|
||||||
|
|
||||||
// Remove a selected output from the list
|
|
||||||
const removeOutput = useCallback(
|
|
||||||
(output: any) => {
|
|
||||||
setSelectedOutputs(
|
|
||||||
selectedOutputs.filter((item) => item.pk != output.pk)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[selectedOutputs]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location) {
|
if (location) {
|
||||||
@ -364,14 +354,14 @@ export function useScrapBuildOutputsForm({
|
|||||||
}, [location, build.destination, build.part_detail]);
|
}, [location, build.destination, build.part_detail]);
|
||||||
|
|
||||||
const preFormContent = useMemo(() => {
|
const preFormContent = useMemo(() => {
|
||||||
return buildOutputFormTable(selectedOutputs, removeOutput);
|
return buildOutputFormTable(selectedRows, removeRow);
|
||||||
}, [selectedOutputs, removeOutput]);
|
}, [selectedRows, removeRow]);
|
||||||
|
|
||||||
const buildOutputScrapFields: ApiFormFieldSet = useMemo(() => {
|
const buildOutputScrapFields: ApiFormFieldSet = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
outputs: {
|
outputs: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
value: selectedOutputs.map((output) => {
|
value: selectedRows.map((output: any) => {
|
||||||
return {
|
return {
|
||||||
output: output.pk,
|
output: output.pk,
|
||||||
quantity: output.quantity
|
quantity: output.quantity
|
||||||
@ -387,7 +377,7 @@ export function useScrapBuildOutputsForm({
|
|||||||
notes: {},
|
notes: {},
|
||||||
discard_allocations: {}
|
discard_allocations: {}
|
||||||
};
|
};
|
||||||
}, [location, selectedOutputs]);
|
}, [location, selectedRows]);
|
||||||
|
|
||||||
return useCreateApiFormModal({
|
return useCreateApiFormModal({
|
||||||
url: apiUrl(ApiEndpoints.build_output_scrap, build.pk),
|
url: apiUrl(ApiEndpoints.build_output_scrap, build.pk),
|
||||||
@ -409,21 +399,9 @@ export function useCancelBuildOutputsForm({
|
|||||||
outputs: any[];
|
outputs: any[];
|
||||||
onFormSuccess: (response: any) => void;
|
onFormSuccess: (response: any) => void;
|
||||||
}) {
|
}) {
|
||||||
const [selectedOutputs, setSelectedOutputs] = useState<any[]>([]);
|
const { selectedRows, removeRow } = useSelectedRows({
|
||||||
|
rows: outputs
|
||||||
useEffect(() => {
|
});
|
||||||
setSelectedOutputs(outputs);
|
|
||||||
}, [outputs]);
|
|
||||||
|
|
||||||
// Remove a selected output from the list
|
|
||||||
const removeOutput = useCallback(
|
|
||||||
(output: any) => {
|
|
||||||
setSelectedOutputs(
|
|
||||||
selectedOutputs.filter((item) => item.pk != output.pk)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[selectedOutputs]
|
|
||||||
);
|
|
||||||
|
|
||||||
const preFormContent = useMemo(() => {
|
const preFormContent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@ -431,23 +409,23 @@ export function useCancelBuildOutputsForm({
|
|||||||
<Alert color="red" title={t`Cancel Build Outputs`}>
|
<Alert color="red" title={t`Cancel Build Outputs`}>
|
||||||
<Text>{t`Selected build outputs will be deleted`}</Text>
|
<Text>{t`Selected build outputs will be deleted`}</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
{buildOutputFormTable(selectedOutputs, removeOutput)}
|
{buildOutputFormTable(selectedRows, removeRow)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}, [selectedOutputs, removeOutput]);
|
}, [selectedRows, removeRow]);
|
||||||
|
|
||||||
const buildOutputCancelFields: ApiFormFieldSet = useMemo(() => {
|
const buildOutputCancelFields: ApiFormFieldSet = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
outputs: {
|
outputs: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
value: selectedOutputs.map((output) => {
|
value: selectedRows.map((output: any) => {
|
||||||
return {
|
return {
|
||||||
output: output.pk
|
output: output.pk
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [selectedOutputs]);
|
}, [selectedRows]);
|
||||||
|
|
||||||
return useCreateApiFormModal({
|
return useCreateApiFormModal({
|
||||||
url: apiUrl(ApiEndpoints.build_output_delete, build.pk),
|
url: apiUrl(ApiEndpoints.build_output_delete, build.pk),
|
||||||
@ -459,3 +437,233 @@ export function useCancelBuildOutputsForm({
|
|||||||
successMessage: t`Build outputs have been cancelled`
|
successMessage: t`Build outputs have been cancelled`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildAllocationFormTable(
|
||||||
|
outputs: any[],
|
||||||
|
onRemove: (output: any) => void
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
idAccessor="pk"
|
||||||
|
records={outputs}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessor: 'part',
|
||||||
|
title: t`Part`,
|
||||||
|
render: (record: any) => PartColumn(record.part_detail)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'allocated',
|
||||||
|
title: t`Allocated`,
|
||||||
|
render: (record: any) => (
|
||||||
|
<ProgressBar
|
||||||
|
value={record.allocated}
|
||||||
|
maximum={record.quantity}
|
||||||
|
progressLabel
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'actions',
|
||||||
|
title: '',
|
||||||
|
render: (record: any) => (
|
||||||
|
<ActionButton
|
||||||
|
key={`remove-line-${record.pk}`}
|
||||||
|
tooltip={t`Remove line`}
|
||||||
|
icon={<InvenTreeIcon icon="cancel" />}
|
||||||
|
color="red"
|
||||||
|
onClick={() => onRemove(record.pk)}
|
||||||
|
disabled={outputs.length <= 1}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a single row in the 'allocate stock to build' table
|
||||||
|
function BuildAllocateLineRow({
|
||||||
|
props,
|
||||||
|
record,
|
||||||
|
sourceLocation
|
||||||
|
}: {
|
||||||
|
props: TableFieldRowProps;
|
||||||
|
record: any;
|
||||||
|
sourceLocation: number | undefined;
|
||||||
|
}) {
|
||||||
|
const stockField: ApiFormFieldType = useMemo(() => {
|
||||||
|
return {
|
||||||
|
field_type: 'related field',
|
||||||
|
api_url: apiUrl(ApiEndpoints.stock_item_list),
|
||||||
|
model: ModelType.stockitem,
|
||||||
|
filters: {
|
||||||
|
available: true,
|
||||||
|
part_detail: true,
|
||||||
|
location_detail: true,
|
||||||
|
bom_item: record.bom_item,
|
||||||
|
location: sourceLocation,
|
||||||
|
cascade: sourceLocation ? true : undefined
|
||||||
|
},
|
||||||
|
value: props.item.stock_item,
|
||||||
|
name: 'stock_item',
|
||||||
|
onValueChange: (value: any, instance: any) => {
|
||||||
|
props.changeFn(props.idx, 'stock_item', value);
|
||||||
|
|
||||||
|
// Update the allocated quantity based on the selected stock item
|
||||||
|
if (instance) {
|
||||||
|
let available = instance.quantity - instance.allocated;
|
||||||
|
|
||||||
|
props.changeFn(
|
||||||
|
props.idx,
|
||||||
|
'quantity',
|
||||||
|
Math.min(props.item.quantity, available)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
const quantityField: ApiFormFieldType = useMemo(() => {
|
||||||
|
return {
|
||||||
|
field_type: 'number',
|
||||||
|
name: 'quantity',
|
||||||
|
required: true,
|
||||||
|
value: props.item.quantity,
|
||||||
|
onValueChange: (value: any) => {
|
||||||
|
props.changeFn(props.idx, 'quantity', value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
const partDetail = useMemo(
|
||||||
|
() => PartColumn(record.part_detail),
|
||||||
|
[record.part_detail]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table.Tr key={`table-row-${record.pk}`}>
|
||||||
|
<Table.Td>{partDetail}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<ProgressBar
|
||||||
|
value={record.allocated}
|
||||||
|
maximum={record.quantity}
|
||||||
|
progressLabel
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<StandaloneField
|
||||||
|
fieldName="stock_item"
|
||||||
|
fieldDefinition={stockField}
|
||||||
|
error={props.rowErrors?.stock_item?.message}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<StandaloneField
|
||||||
|
fieldName="quantity"
|
||||||
|
fieldDefinition={quantityField}
|
||||||
|
error={props.rowErrors?.quantity?.message}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dynamic form for allocating stock against multiple build order line items
|
||||||
|
*/
|
||||||
|
export function useAllocateStockToBuildForm({
|
||||||
|
buildId,
|
||||||
|
outputId,
|
||||||
|
build,
|
||||||
|
lineItems,
|
||||||
|
onFormSuccess
|
||||||
|
}: {
|
||||||
|
buildId: number;
|
||||||
|
outputId?: number | null;
|
||||||
|
build: any;
|
||||||
|
lineItems: any[];
|
||||||
|
onFormSuccess: (response: any) => void;
|
||||||
|
}) {
|
||||||
|
const [sourceLocation, setSourceLocation] = useState<number | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildAllocateFields: ApiFormFieldSet = useMemo(() => {
|
||||||
|
const fields: ApiFormFieldSet = {
|
||||||
|
items: {
|
||||||
|
field_type: 'table',
|
||||||
|
value: [],
|
||||||
|
headers: [t`Part`, t`Allocated`, t`Stock Item`, t`Quantity`],
|
||||||
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
|
// Find the matching record from the passed 'lineItems'
|
||||||
|
const record =
|
||||||
|
lineItems.find((item) => item.pk == row.item.build_line) ?? {};
|
||||||
|
return (
|
||||||
|
<BuildAllocateLineRow
|
||||||
|
props={row}
|
||||||
|
record={record}
|
||||||
|
sourceLocation={sourceLocation}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}, [lineItems, sourceLocation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSourceLocation(build.take_from);
|
||||||
|
}, [build.take_from]);
|
||||||
|
|
||||||
|
const sourceLocationField: ApiFormFieldType = useMemo(() => {
|
||||||
|
return {
|
||||||
|
field_type: 'related field',
|
||||||
|
api_url: apiUrl(ApiEndpoints.stock_location_list),
|
||||||
|
model: ModelType.stocklocation,
|
||||||
|
required: false,
|
||||||
|
label: t`Source Location`,
|
||||||
|
description: t`Select the source location for the stock allocation`,
|
||||||
|
name: 'source_location',
|
||||||
|
value: build.take_from,
|
||||||
|
onValueChange: (value: any) => {
|
||||||
|
setSourceLocation(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [build?.take_from]);
|
||||||
|
|
||||||
|
const preFormContent = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<Stack gap="xs">
|
||||||
|
<StandaloneField fieldDefinition={sourceLocationField} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}, [sourceLocationField]);
|
||||||
|
|
||||||
|
return useCreateApiFormModal({
|
||||||
|
url: ApiEndpoints.build_order_allocate,
|
||||||
|
pk: buildId,
|
||||||
|
title: t`Allocate Stock`,
|
||||||
|
fields: buildAllocateFields,
|
||||||
|
preFormContent: preFormContent,
|
||||||
|
successMessage: t`Stock items allocated`,
|
||||||
|
onFormSuccess: onFormSuccess,
|
||||||
|
initialData: {
|
||||||
|
items: lineItems.map((item) => {
|
||||||
|
return {
|
||||||
|
build_line: item.pk,
|
||||||
|
stock_item: undefined,
|
||||||
|
quantity: Math.max(0, item.quantity - item.allocated),
|
||||||
|
output: null
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
size: '80%'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -12,6 +12,18 @@ export function projectCodeFields(): ApiFormFieldSet {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function customStateFields(): ApiFormFieldSet {
|
||||||
|
return {
|
||||||
|
key: {},
|
||||||
|
name: {},
|
||||||
|
label: {},
|
||||||
|
color: {},
|
||||||
|
logical_key: {},
|
||||||
|
model: {},
|
||||||
|
reference_status: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function customUnitsFields(): ApiFormFieldSet {
|
export function customUnitsFields(): ApiFormFieldSet {
|
||||||
return {
|
return {
|
||||||
name: {},
|
name: {},
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
FocusTrap,
|
FocusTrap,
|
||||||
Group,
|
Group,
|
||||||
Modal,
|
Modal,
|
||||||
NumberInput,
|
|
||||||
Table,
|
Table,
|
||||||
TextInput
|
TextInput
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
@ -28,12 +27,16 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
|
|
||||||
import { api } from '../App';
|
import { api } from '../App';
|
||||||
import { ActionButton } from '../components/buttons/ActionButton';
|
import { ActionButton } from '../components/buttons/ActionButton';
|
||||||
|
import RemoveRowButton from '../components/buttons/RemoveRowButton';
|
||||||
import { StandaloneField } from '../components/forms/StandaloneField';
|
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||||
import {
|
import {
|
||||||
ApiFormAdjustFilterType,
|
ApiFormAdjustFilterType,
|
||||||
ApiFormFieldSet
|
ApiFormFieldSet
|
||||||
} from '../components/forms/fields/ApiFormField';
|
} from '../components/forms/fields/ApiFormField';
|
||||||
import { TableFieldExtraRow } from '../components/forms/fields/TableField';
|
import {
|
||||||
|
TableFieldExtraRow,
|
||||||
|
TableFieldRowProps
|
||||||
|
} from '../components/forms/fields/TableField';
|
||||||
import { Thumbnail } from '../components/images/Thumbnail';
|
import { Thumbnail } from '../components/images/Thumbnail';
|
||||||
import { ProgressBar } from '../components/items/ProgressBar';
|
import { ProgressBar } from '../components/items/ProgressBar';
|
||||||
import { StylishText } from '../components/items/StylishText';
|
import { StylishText } from '../components/items/StylishText';
|
||||||
@ -191,67 +194,53 @@ export function usePurchaseOrderFields(): ApiFormFieldSet {
|
|||||||
* Render a table row for a single TableField entry
|
* Render a table row for a single TableField entry
|
||||||
*/
|
*/
|
||||||
function LineItemFormRow({
|
function LineItemFormRow({
|
||||||
input,
|
props,
|
||||||
record,
|
record,
|
||||||
statuses
|
statuses
|
||||||
}: {
|
}: {
|
||||||
input: any;
|
props: TableFieldRowProps;
|
||||||
record: any;
|
record: any;
|
||||||
statuses: any;
|
statuses: any;
|
||||||
}) {
|
}) {
|
||||||
// Barcode Modal state
|
// Barcode Modal state
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false, {
|
||||||
|
onClose: () => props.changeFn(props.idx, 'barcode', undefined)
|
||||||
|
});
|
||||||
|
|
||||||
// Location value
|
const [locationOpen, locationHandlers] = useDisclosure(false, {
|
||||||
const [location, setLocation] = useState(
|
onClose: () => props.changeFn(props.idx, 'location', undefined)
|
||||||
input.item.location ??
|
});
|
||||||
record.part_detail.default_location ??
|
|
||||||
record.part_detail.category_default_location
|
|
||||||
);
|
|
||||||
const [locationOpen, locationHandlers] = useDisclosure(
|
|
||||||
location ? true : false,
|
|
||||||
{
|
|
||||||
onClose: () => input.changeFn(input.idx, 'location', null),
|
|
||||||
onOpen: () => input.changeFn(input.idx, 'location', location)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Change form value when state is altered
|
|
||||||
useEffect(() => {
|
|
||||||
input.changeFn(input.idx, 'location', location);
|
|
||||||
}, [location]);
|
|
||||||
|
|
||||||
|
// Batch code generator
|
||||||
const batchCodeGenerator = useBatchCodeGenerator((value: any) => {
|
const batchCodeGenerator = useBatchCodeGenerator((value: any) => {
|
||||||
if (!batchCode) {
|
if (value) {
|
||||||
setBatchCode(value);
|
props.changeFn(props.idx, 'batch_code', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Serial numbebr generator
|
||||||
const serialNumberGenerator = useSerialNumberGenerator((value: any) => {
|
const serialNumberGenerator = useSerialNumberGenerator((value: any) => {
|
||||||
if (!serials) {
|
if (value) {
|
||||||
setSerials(value);
|
props.changeFn(props.idx, 'serial_numbers', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [packagingOpen, packagingHandlers] = useDisclosure(false, {
|
const [packagingOpen, packagingHandlers] = useDisclosure(false, {
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
input.changeFn(input.idx, 'packaging', undefined);
|
props.changeFn(props.idx, 'packaging', undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [noteOpen, noteHandlers] = useDisclosure(false, {
|
const [noteOpen, noteHandlers] = useDisclosure(false, {
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
input.changeFn(input.idx, 'note', undefined);
|
props.changeFn(props.idx, 'note', undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// State for serializing
|
|
||||||
const [batchCode, setBatchCode] = useState<string>('');
|
|
||||||
const [serials, setSerials] = useState<string>('');
|
|
||||||
const [batchOpen, batchHandlers] = useDisclosure(false, {
|
const [batchOpen, batchHandlers] = useDisclosure(false, {
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
input.changeFn(input.idx, 'batch_code', undefined);
|
props.changeFn(props.idx, 'batch_code', undefined);
|
||||||
input.changeFn(input.idx, 'serial_numbers', '');
|
props.changeFn(props.idx, 'serial_numbers', undefined);
|
||||||
},
|
},
|
||||||
onOpen: () => {
|
onOpen: () => {
|
||||||
// Generate a new batch code
|
// Generate a new batch code
|
||||||
@ -262,23 +251,23 @@ function LineItemFormRow({
|
|||||||
// Generate new serial numbers
|
// Generate new serial numbers
|
||||||
serialNumberGenerator.update({
|
serialNumberGenerator.update({
|
||||||
part: record?.supplier_part_detail?.part,
|
part: record?.supplier_part_detail?.part,
|
||||||
quantity: input.item.quantity
|
quantity: props.item.quantity
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Status value
|
// Status value
|
||||||
const [statusOpen, statusHandlers] = useDisclosure(false, {
|
const [statusOpen, statusHandlers] = useDisclosure(false, {
|
||||||
onClose: () => input.changeFn(input.idx, 'status', 10)
|
onClose: () => props.changeFn(props.idx, 'status', undefined)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Barcode value
|
// Barcode value
|
||||||
const [barcodeInput, setBarcodeInput] = useState<any>('');
|
const [barcodeInput, setBarcodeInput] = useState<any>('');
|
||||||
const [barcode, setBarcode] = useState(null);
|
const [barcode, setBarcode] = useState<String | undefined>(undefined);
|
||||||
|
|
||||||
// Change form value when state is altered
|
// Change form value when state is altered
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
input.changeFn(input.idx, 'barcode', barcode);
|
props.changeFn(props.idx, 'barcode', barcode);
|
||||||
}, [barcode]);
|
}, [barcode]);
|
||||||
|
|
||||||
// Update location field description on state change
|
// Update location field description on state change
|
||||||
@ -370,13 +359,16 @@ function LineItemFormRow({
|
|||||||
progressLabel
|
progressLabel
|
||||||
/>
|
/>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
<Table.Td style={{ whiteSpace: 'nowrap' }}>
|
||||||
<NumberInput
|
<StandaloneField
|
||||||
value={input.item.quantity}
|
fieldName="quantity"
|
||||||
style={{ width: '100px' }}
|
fieldDefinition={{
|
||||||
max={input.item.quantity}
|
field_type: 'number',
|
||||||
min={0}
|
value: props.item.quantity,
|
||||||
onChange={(value) => input.changeFn(input.idx, 'quantity', value)}
|
onValueChange: (value) =>
|
||||||
|
props.changeFn(props.idx, 'quantity', value)
|
||||||
|
}}
|
||||||
|
error={props.rowErrors?.quantity?.message}
|
||||||
/>
|
/>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
||||||
@ -403,6 +395,7 @@ function LineItemFormRow({
|
|||||||
size="sm"
|
size="sm"
|
||||||
icon={<InvenTreeIcon icon="packaging" />}
|
icon={<InvenTreeIcon icon="packaging" />}
|
||||||
tooltip={t`Adjust Packaging`}
|
tooltip={t`Adjust Packaging`}
|
||||||
|
tooltipAlignment="top"
|
||||||
onClick={() => packagingHandlers.toggle()}
|
onClick={() => packagingHandlers.toggle()}
|
||||||
variant={packagingOpen ? 'filled' : 'transparent'}
|
variant={packagingOpen ? 'filled' : 'transparent'}
|
||||||
/>
|
/>
|
||||||
@ -427,7 +420,7 @@ function LineItemFormRow({
|
|||||||
tooltipAlignment="top"
|
tooltipAlignment="top"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => setBarcode(null)}
|
onClick={() => setBarcode(undefined)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -438,13 +431,7 @@ function LineItemFormRow({
|
|||||||
onClick={() => open()}
|
onClick={() => open()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ActionButton
|
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||||
onClick={() => input.removeFn(input.idx)}
|
|
||||||
icon={<InvenTreeIcon icon="square_x" />}
|
|
||||||
tooltip={t`Remove item from list`}
|
|
||||||
tooltipAlignment="top"
|
|
||||||
color="red"
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
@ -464,7 +451,7 @@ function LineItemFormRow({
|
|||||||
structural: false
|
structural: false
|
||||||
},
|
},
|
||||||
onValueChange: (value) => {
|
onValueChange: (value) => {
|
||||||
setLocation(value);
|
props.changeFn(props.idx, 'location', value);
|
||||||
},
|
},
|
||||||
description: locationDescription,
|
description: locationDescription,
|
||||||
value: location,
|
value: location,
|
||||||
@ -485,7 +472,9 @@ function LineItemFormRow({
|
|||||||
icon={<InvenTreeIcon icon="default_location" />}
|
icon={<InvenTreeIcon icon="default_location" />}
|
||||||
tooltip={t`Store at default location`}
|
tooltip={t`Store at default location`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setLocation(
|
props.changeFn(
|
||||||
|
props.idx,
|
||||||
|
'location',
|
||||||
record.part_detail.default_location ??
|
record.part_detail.default_location ??
|
||||||
record.part_detail.category_default_location
|
record.part_detail.category_default_location
|
||||||
)
|
)
|
||||||
@ -497,7 +486,9 @@ function LineItemFormRow({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<InvenTreeIcon icon="destination" />}
|
icon={<InvenTreeIcon icon="destination" />}
|
||||||
tooltip={t`Store at line item destination `}
|
tooltip={t`Store at line item destination `}
|
||||||
onClick={() => setLocation(record.destination)}
|
onClick={() =>
|
||||||
|
props.changeFn(props.idx, 'location', record.destination)
|
||||||
|
}
|
||||||
tooltipAlignment="top"
|
tooltipAlignment="top"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -507,7 +498,13 @@ function LineItemFormRow({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<InvenTreeIcon icon="repeat_destination" />}
|
icon={<InvenTreeIcon icon="repeat_destination" />}
|
||||||
tooltip={t`Store with already received stock`}
|
tooltip={t`Store with already received stock`}
|
||||||
onClick={() => setLocation(record.destination_detail.pk)}
|
onClick={() =>
|
||||||
|
props.changeFn(
|
||||||
|
props.idx,
|
||||||
|
'location',
|
||||||
|
record.destination_detail.pk
|
||||||
|
)
|
||||||
|
}
|
||||||
tooltipAlignment="top"
|
tooltipAlignment="top"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -518,51 +515,56 @@ function LineItemFormRow({
|
|||||||
)}
|
)}
|
||||||
<TableFieldExtraRow
|
<TableFieldExtraRow
|
||||||
visible={batchOpen}
|
visible={batchOpen}
|
||||||
onValueChange={(value) => input.changeFn(input.idx, 'batch', value)}
|
onValueChange={(value) => props.changeFn(props.idx, 'batch', value)}
|
||||||
fieldDefinition={{
|
fieldDefinition={{
|
||||||
field_type: 'string',
|
field_type: 'string',
|
||||||
label: t`Batch Code`,
|
label: t`Batch Code`,
|
||||||
value: batchCode
|
value: props.item.batch_code
|
||||||
}}
|
}}
|
||||||
|
error={props.rowErrors?.batch_code?.message}
|
||||||
/>
|
/>
|
||||||
<TableFieldExtraRow
|
<TableFieldExtraRow
|
||||||
visible={batchOpen && record.trackable}
|
visible={batchOpen && record.trackable}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
input.changeFn(input.idx, 'serial_numbers', value)
|
props.changeFn(props.idx, 'serial_numbers', value)
|
||||||
}
|
}
|
||||||
fieldDefinition={{
|
fieldDefinition={{
|
||||||
field_type: 'string',
|
field_type: 'string',
|
||||||
label: t`Serial numbers`,
|
label: t`Serial numbers`,
|
||||||
value: serials
|
value: props.item.serial_numbers
|
||||||
}}
|
}}
|
||||||
|
error={props.rowErrors?.serial_numbers?.message}
|
||||||
/>
|
/>
|
||||||
<TableFieldExtraRow
|
<TableFieldExtraRow
|
||||||
visible={packagingOpen}
|
visible={packagingOpen}
|
||||||
onValueChange={(value) => input.changeFn(input.idx, 'packaging', value)}
|
onValueChange={(value) => props.changeFn(props.idx, 'packaging', value)}
|
||||||
fieldDefinition={{
|
fieldDefinition={{
|
||||||
field_type: 'string',
|
field_type: 'string',
|
||||||
label: t`Packaging`
|
label: t`Packaging`
|
||||||
}}
|
}}
|
||||||
defaultValue={record?.supplier_part_detail?.packaging}
|
defaultValue={record?.supplier_part_detail?.packaging}
|
||||||
|
error={props.rowErrors?.packaging?.message}
|
||||||
/>
|
/>
|
||||||
<TableFieldExtraRow
|
<TableFieldExtraRow
|
||||||
visible={statusOpen}
|
visible={statusOpen}
|
||||||
defaultValue={10}
|
defaultValue={10}
|
||||||
onValueChange={(value) => input.changeFn(input.idx, 'status', value)}
|
onValueChange={(value) => props.changeFn(props.idx, 'status', value)}
|
||||||
fieldDefinition={{
|
fieldDefinition={{
|
||||||
field_type: 'choice',
|
field_type: 'choice',
|
||||||
api_url: apiUrl(ApiEndpoints.stock_status),
|
api_url: apiUrl(ApiEndpoints.stock_status),
|
||||||
choices: statuses,
|
choices: statuses,
|
||||||
label: t`Status`
|
label: t`Status`
|
||||||
}}
|
}}
|
||||||
|
error={props.rowErrors?.status?.message}
|
||||||
/>
|
/>
|
||||||
<TableFieldExtraRow
|
<TableFieldExtraRow
|
||||||
visible={noteOpen}
|
visible={noteOpen}
|
||||||
onValueChange={(value) => input.changeFn(input.idx, 'note', value)}
|
onValueChange={(value) => props.changeFn(props.idx, 'note', value)}
|
||||||
fieldDefinition={{
|
fieldDefinition={{
|
||||||
field_type: 'string',
|
field_type: 'string',
|
||||||
label: t`Note`
|
label: t`Note`
|
||||||
}}
|
}}
|
||||||
|
error={props.rowErrors?.note?.message}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -624,12 +626,12 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
|||||||
barcode: null
|
barcode: null
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
modelRenderer: (instance) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
const record = records[instance.item.line_item];
|
const record = records[row.item.line_item];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LineItemFormRow
|
<LineItemFormRow
|
||||||
input={instance}
|
props={row}
|
||||||
record={record}
|
record={record}
|
||||||
statuses={data}
|
statuses={data}
|
||||||
key={record.pk}
|
key={record.pk}
|
||||||
@ -645,18 +647,14 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = apiUrl(ApiEndpoints.purchase_order_receive, null, {
|
|
||||||
id: props.orderPk
|
|
||||||
});
|
|
||||||
|
|
||||||
return useCreateApiFormModal({
|
return useCreateApiFormModal({
|
||||||
...props.formProps,
|
...props.formProps,
|
||||||
url: url,
|
url: apiUrl(ApiEndpoints.purchase_order_receive, props.orderPk),
|
||||||
title: t`Receive Line Items`,
|
title: t`Receive Line Items`,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
initialData: {
|
initialData: {
|
||||||
location: null
|
location: null
|
||||||
},
|
},
|
||||||
size: 'xl'
|
size: '80%'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Flex, Group, NumberInput, Skeleton, Table, Text } from '@mantine/core';
|
import { Flex, Group, Skeleton, Table, Text } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { modals } from '@mantine/modals';
|
import { modals } from '@mantine/modals';
|
||||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { Suspense, useCallback, useMemo, useState } from 'react';
|
import { Suspense, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { api } from '../App';
|
import { api } from '../App';
|
||||||
import { ActionButton } from '../components/buttons/ActionButton';
|
import { ActionButton } from '../components/buttons/ActionButton';
|
||||||
|
import RemoveRowButton from '../components/buttons/RemoveRowButton';
|
||||||
|
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||||
import {
|
import {
|
||||||
ApiFormAdjustFilterType,
|
ApiFormAdjustFilterType,
|
||||||
ApiFormFieldSet
|
ApiFormFieldSet
|
||||||
} from '../components/forms/fields/ApiFormField';
|
} from '../components/forms/fields/ApiFormField';
|
||||||
import { TableFieldExtraRow } from '../components/forms/fields/TableField';
|
import {
|
||||||
|
TableFieldExtraRow,
|
||||||
|
TableFieldRowProps
|
||||||
|
} from '../components/forms/fields/TableField';
|
||||||
import { Thumbnail } from '../components/images/Thumbnail';
|
import { Thumbnail } from '../components/images/Thumbnail';
|
||||||
import { StylishText } from '../components/items/StylishText';
|
import { StylishText } from '../components/items/StylishText';
|
||||||
import { StatusRenderer } from '../components/render/StatusRenderer';
|
import { StatusRenderer } from '../components/render/StatusRenderer';
|
||||||
@ -138,7 +143,9 @@ export function useStockFields({
|
|||||||
value: batchCode,
|
value: batchCode,
|
||||||
onValueChange: (value) => setBatchCode(value)
|
onValueChange: (value) => setBatchCode(value)
|
||||||
},
|
},
|
||||||
status: {},
|
status_custom_key: {
|
||||||
|
label: t`Stock Status`
|
||||||
|
},
|
||||||
expiry_date: {
|
expiry_date: {
|
||||||
// TODO: icon
|
// TODO: icon
|
||||||
},
|
},
|
||||||
@ -294,54 +301,37 @@ type StockRow = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function StockOperationsRow({
|
function StockOperationsRow({
|
||||||
input,
|
props,
|
||||||
transfer = false,
|
transfer = false,
|
||||||
add = false,
|
add = false,
|
||||||
setMax = false,
|
setMax = false,
|
||||||
merge = false,
|
merge = false,
|
||||||
record
|
record
|
||||||
}: {
|
}: {
|
||||||
input: StockRow;
|
props: TableFieldRowProps;
|
||||||
transfer?: boolean;
|
transfer?: boolean;
|
||||||
add?: boolean;
|
add?: boolean;
|
||||||
setMax?: boolean;
|
setMax?: boolean;
|
||||||
merge?: boolean;
|
merge?: boolean;
|
||||||
record?: any;
|
record?: any;
|
||||||
}) {
|
}) {
|
||||||
const item = input.item;
|
const [quantity, setQuantity] = useState<StockItemQuantity>(
|
||||||
|
add ? 0 : props.item?.quantity ?? 0
|
||||||
const [value, setValue] = useState<StockItemQuantity>(
|
|
||||||
add ? 0 : item.quantity ?? 0
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChange = useCallback(
|
|
||||||
(value: any) => {
|
|
||||||
setValue(value);
|
|
||||||
input.changeFn(input.idx, 'quantity', value);
|
|
||||||
},
|
|
||||||
[item]
|
|
||||||
);
|
|
||||||
|
|
||||||
const changeSubItem = useCallback(
|
|
||||||
(key: string, value: any) => {
|
|
||||||
input.changeFn(input.idx, key, value);
|
|
||||||
},
|
|
||||||
[input]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeAndRefresh = () => {
|
const removeAndRefresh = () => {
|
||||||
input.removeFn(input.idx);
|
props.removeFn(props.idx);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [packagingOpen, packagingHandlers] = useDisclosure(false, {
|
const [packagingOpen, packagingHandlers] = useDisclosure(false, {
|
||||||
onOpen: () => {
|
onOpen: () => {
|
||||||
if (transfer) {
|
if (transfer) {
|
||||||
input.changeFn(input.idx, 'packaging', record?.packaging || undefined);
|
props.changeFn(props.idx, 'packaging', record?.packaging || undefined);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
if (transfer) {
|
if (transfer) {
|
||||||
input.changeFn(input.idx, 'packaging', undefined);
|
props.changeFn(props.idx, 'packaging', undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -377,25 +367,24 @@ function StockOperationsRow({
|
|||||||
{record.location ? record.location_detail?.pathstring : '-'}
|
{record.location ? record.location_detail?.pathstring : '-'}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Flex align="center" gap="xs">
|
<Group grow justify="space-between" wrap="nowrap">
|
||||||
<Group justify="space-between">
|
<Text>{stockString}</Text>
|
||||||
<Text>{stockString}</Text>
|
<StatusRenderer status={record.status} type={ModelType.stockitem} />
|
||||||
<StatusRenderer
|
</Group>
|
||||||
status={record.status}
|
|
||||||
type={ModelType.stockitem}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
</Flex>
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
{!merge && (
|
{!merge && (
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<NumberInput
|
<StandaloneField
|
||||||
value={value}
|
fieldName="quantity"
|
||||||
onChange={onChange}
|
fieldDefinition={{
|
||||||
disabled={!!record.serial && record.quantity == 1}
|
field_type: 'number',
|
||||||
max={setMax ? record.quantity : undefined}
|
value: quantity,
|
||||||
min={0}
|
onValueChange: (value: any) => {
|
||||||
style={{ maxWidth: '100px' }}
|
setQuantity(value);
|
||||||
|
props.changeFn(props.idx, 'quantity', value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
error={props.rowErrors?.quantity?.message}
|
||||||
/>
|
/>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
)}
|
)}
|
||||||
@ -403,7 +392,9 @@ function StockOperationsRow({
|
|||||||
<Flex gap="3px">
|
<Flex gap="3px">
|
||||||
{transfer && (
|
{transfer && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={() => moveToDefault(record, value, removeAndRefresh)}
|
onClick={() =>
|
||||||
|
moveToDefault(record, props.item.quantity, removeAndRefresh)
|
||||||
|
}
|
||||||
icon={<InvenTreeIcon icon="default_location" />}
|
icon={<InvenTreeIcon icon="default_location" />}
|
||||||
tooltip={t`Move to default location`}
|
tooltip={t`Move to default location`}
|
||||||
tooltipAlignment="top"
|
tooltipAlignment="top"
|
||||||
@ -422,13 +413,7 @@ function StockOperationsRow({
|
|||||||
variant={packagingOpen ? 'filled' : 'transparent'}
|
variant={packagingOpen ? 'filled' : 'transparent'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ActionButton
|
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||||
onClick={() => input.removeFn(input.idx)}
|
|
||||||
icon={<InvenTreeIcon icon="square_x" />}
|
|
||||||
tooltip={t`Remove item from list`}
|
|
||||||
tooltipAlignment="top"
|
|
||||||
color="red"
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
@ -436,7 +421,7 @@ function StockOperationsRow({
|
|||||||
<TableFieldExtraRow
|
<TableFieldExtraRow
|
||||||
visible={transfer && packagingOpen}
|
visible={transfer && packagingOpen}
|
||||||
onValueChange={(value: any) => {
|
onValueChange={(value: any) => {
|
||||||
input.changeFn(input.idx, 'packaging', value || undefined);
|
props.changeFn(props.idx, 'packaging', value || undefined);
|
||||||
}}
|
}}
|
||||||
fieldDefinition={{
|
fieldDefinition={{
|
||||||
field_type: 'string',
|
field_type: 'string',
|
||||||
@ -464,9 +449,9 @@ function mapAdjustmentItems(items: any[]) {
|
|||||||
return {
|
return {
|
||||||
pk: elem.pk,
|
pk: elem.pk,
|
||||||
quantity: elem.quantity,
|
quantity: elem.quantity,
|
||||||
batch: elem.batch,
|
batch: elem.batch || undefined,
|
||||||
status: elem.status,
|
status: elem.status || undefined,
|
||||||
packaging: elem.packaging,
|
packaging: elem.packaging || undefined,
|
||||||
obj: elem
|
obj: elem
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -485,14 +470,16 @@ function stockTransferFields(items: any[]): ApiFormFieldSet {
|
|||||||
items: {
|
items: {
|
||||||
field_type: 'table',
|
field_type: 'table',
|
||||||
value: mapAdjustmentItems(items),
|
value: mapAdjustmentItems(items),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
|
const record = records[row.item.pk];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow
|
||||||
input={val}
|
props={row}
|
||||||
transfer
|
transfer
|
||||||
setMax
|
setMax
|
||||||
key={val.item.pk}
|
key={record.pk}
|
||||||
record={records[val.item.pk]}
|
record={record}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -520,13 +507,16 @@ function stockRemoveFields(items: any[]): ApiFormFieldSet {
|
|||||||
items: {
|
items: {
|
||||||
field_type: 'table',
|
field_type: 'table',
|
||||||
value: mapAdjustmentItems(items),
|
value: mapAdjustmentItems(items),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
|
const record = records[row.item.pk];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow
|
||||||
input={val}
|
props={row}
|
||||||
setMax
|
setMax
|
||||||
key={val.item.pk}
|
add
|
||||||
record={records[val.item.pk]}
|
key={record.pk}
|
||||||
|
record={record}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -549,14 +539,11 @@ function stockAddFields(items: any[]): ApiFormFieldSet {
|
|||||||
items: {
|
items: {
|
||||||
field_type: 'table',
|
field_type: 'table',
|
||||||
value: mapAdjustmentItems(items),
|
value: mapAdjustmentItems(items),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
|
const record = records[row.item.pk];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow props={row} add key={record.pk} record={record} />
|
||||||
input={val}
|
|
||||||
add
|
|
||||||
key={val.item.pk}
|
|
||||||
record={records[val.item.pk]}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
headers: [t`Part`, t`Location`, t`In Stock`, t`Add`, t`Actions`]
|
headers: [t`Part`, t`Location`, t`In Stock`, t`Add`, t`Actions`]
|
||||||
@ -578,12 +565,12 @@ function stockCountFields(items: any[]): ApiFormFieldSet {
|
|||||||
items: {
|
items: {
|
||||||
field_type: 'table',
|
field_type: 'table',
|
||||||
value: mapAdjustmentItems(items),
|
value: mapAdjustmentItems(items),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow
|
||||||
input={val}
|
props={row}
|
||||||
key={val.item.pk}
|
key={row.item.pk}
|
||||||
record={records[val.item.pk]}
|
record={records[row.item.pk]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -608,13 +595,13 @@ function stockChangeStatusFields(items: any[]): ApiFormFieldSet {
|
|||||||
value: items.map((elem) => {
|
value: items.map((elem) => {
|
||||||
return elem.pk;
|
return elem.pk;
|
||||||
}),
|
}),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow
|
||||||
input={val}
|
props={row}
|
||||||
key={val.item}
|
key={row.item}
|
||||||
merge
|
merge
|
||||||
record={records[val.item]}
|
record={records[row.item]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -643,13 +630,13 @@ function stockMergeFields(items: any[]): ApiFormFieldSet {
|
|||||||
obj: elem
|
obj: elem
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow
|
||||||
input={val}
|
props={row}
|
||||||
key={val.item.item}
|
key={row.item.item}
|
||||||
merge
|
merge
|
||||||
record={records[val.item.item]}
|
record={records[row.item.item]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -685,13 +672,13 @@ function stockAssignFields(items: any[]): ApiFormFieldSet {
|
|||||||
obj: elem
|
obj: elem
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow
|
||||||
input={val}
|
props={row}
|
||||||
key={val.item.item}
|
key={row.item.item}
|
||||||
merge
|
merge
|
||||||
record={records[val.item.item]}
|
record={records[row.item.item]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -721,13 +708,15 @@ function stockDeleteFields(items: any[]): ApiFormFieldSet {
|
|||||||
value: items.map((elem) => {
|
value: items.map((elem) => {
|
||||||
return elem.pk;
|
return elem.pk;
|
||||||
}),
|
}),
|
||||||
modelRenderer: (val) => {
|
modelRenderer: (row: TableFieldRowProps) => {
|
||||||
|
const record = records[row.item];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StockOperationsRow
|
<StockOperationsRow
|
||||||
input={val}
|
props={row}
|
||||||
key={val.item}
|
key={record.pk}
|
||||||
merge
|
merge
|
||||||
record={records[val.item]}
|
record={record}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -815,6 +804,7 @@ function stockOperationModal({
|
|||||||
url: endpoint,
|
url: endpoint,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
title: title,
|
title: title,
|
||||||
|
size: '80%',
|
||||||
onFormSuccess: () => refresh()
|
onFormSuccess: () => refresh()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -922,10 +912,14 @@ export function stockLocationFields(): ApiFormFieldSet {
|
|||||||
// Construct a set of fields for
|
// Construct a set of fields for
|
||||||
export function useTestResultFields({
|
export function useTestResultFields({
|
||||||
partId,
|
partId,
|
||||||
itemId
|
itemId,
|
||||||
|
templateId,
|
||||||
|
editTemplate = false
|
||||||
}: {
|
}: {
|
||||||
partId: number;
|
partId: number;
|
||||||
itemId: number;
|
itemId: number;
|
||||||
|
templateId: number | undefined;
|
||||||
|
editTemplate?: boolean;
|
||||||
}): ApiFormFieldSet {
|
}): ApiFormFieldSet {
|
||||||
// Valid field choices
|
// Valid field choices
|
||||||
const [choices, setChoices] = useState<any[]>([]);
|
const [choices, setChoices] = useState<any[]>([]);
|
||||||
@ -947,6 +941,7 @@ export function useTestResultFields({
|
|||||||
hidden: true
|
hidden: true
|
||||||
},
|
},
|
||||||
template: {
|
template: {
|
||||||
|
disabled: !editTemplate && !!templateId,
|
||||||
filters: {
|
filters: {
|
||||||
include_inherited: true,
|
include_inherited: true,
|
||||||
part: partId
|
part: partId
|
||||||
@ -990,5 +985,13 @@ export function useTestResultFields({
|
|||||||
hidden: !includeTestStation
|
hidden: !includeTestStation
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [choices, fieldType, partId, itemId, includeTestStation]);
|
}, [
|
||||||
|
choices,
|
||||||
|
editTemplate,
|
||||||
|
fieldType,
|
||||||
|
partId,
|
||||||
|
itemId,
|
||||||
|
templateId,
|
||||||
|
includeTestStation
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
37
src/frontend/src/hooks/UseSelectedRows.tsx
Normal file
37
src/frontend/src/hooks/UseSelectedRows.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to manage multiple selected rows in a multi-action modal.
|
||||||
|
*
|
||||||
|
* - The hook is initially provided with a list of rows
|
||||||
|
* - A callback is provided to remove a row, based on the provided ID value
|
||||||
|
*/
|
||||||
|
export function useSelectedRows<T>({
|
||||||
|
rows,
|
||||||
|
pkField = 'pk'
|
||||||
|
}: {
|
||||||
|
rows: T[];
|
||||||
|
pkField?: string;
|
||||||
|
}) {
|
||||||
|
const [selectedRows, setSelectedRows] = useState<T[]>(rows);
|
||||||
|
|
||||||
|
// Update selection whenever input rows are updated
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedRows(rows);
|
||||||
|
}, [rows]);
|
||||||
|
|
||||||
|
// Callback to remove the selected row
|
||||||
|
const removeRow = useCallback(
|
||||||
|
(pk: any) => {
|
||||||
|
setSelectedRows((rows) =>
|
||||||
|
rows.filter((row: any) => row[pkField ?? 'pk'] !== pk)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[pkField]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedRows,
|
||||||
|
removeRow
|
||||||
|
};
|
||||||
|
}
|
@ -36,9 +36,13 @@ export type TableState = {
|
|||||||
setRecordCount: (count: number) => void;
|
setRecordCount: (count: number) => void;
|
||||||
page: number;
|
page: number;
|
||||||
setPage: (page: number) => void;
|
setPage: (page: number) => void;
|
||||||
|
pageSize: number;
|
||||||
|
setPageSize: (pageSize: number) => void;
|
||||||
records: any[];
|
records: any[];
|
||||||
setRecords: (records: any[]) => void;
|
setRecords: (records: any[]) => void;
|
||||||
updateRecord: (record: any) => void;
|
updateRecord: (record: any) => void;
|
||||||
|
editable: boolean;
|
||||||
|
setEditable: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,6 +101,7 @@ export function useTable(tableName: string): TableState {
|
|||||||
|
|
||||||
// Pagination data
|
// Pagination data
|
||||||
const [page, setPage] = useState<number>(1);
|
const [page, setPage] = useState<number>(1);
|
||||||
|
const [pageSize, setPageSize] = useState<number>(25);
|
||||||
|
|
||||||
// A list of hidden columns, saved to local storage
|
// A list of hidden columns, saved to local storage
|
||||||
const [hiddenColumns, setHiddenColumns] = useLocalStorage<string[]>({
|
const [hiddenColumns, setHiddenColumns] = useLocalStorage<string[]>({
|
||||||
@ -131,6 +136,8 @@ export function useTable(tableName: string): TableState {
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [editable, setEditable] = useState<boolean>(false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tableKey,
|
tableKey,
|
||||||
refreshTable,
|
refreshTable,
|
||||||
@ -154,8 +161,12 @@ export function useTable(tableName: string): TableState {
|
|||||||
setRecordCount,
|
setRecordCount,
|
||||||
page,
|
page,
|
||||||
setPage,
|
setPage,
|
||||||
|
pageSize,
|
||||||
|
setPageSize,
|
||||||
records,
|
records,
|
||||||
setRecords,
|
setRecords,
|
||||||
updateRecord
|
updateRecord,
|
||||||
|
editable,
|
||||||
|
setEditable
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,24 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
import { Divider, Stack } from '@mantine/core';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { IconReload } from '@tabler/icons-react';
|
import { IconReload } from '@tabler/icons-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../../../App';
|
||||||
import { ActionButton } from '../../components/buttons/ActionButton';
|
import { ActionButton } from '../../../../components/buttons/ActionButton';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { FactCollection } from '../../../../components/settings/FactCollection';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { useTable } from '../../../../hooks/UseTable';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { apiUrl } from '../../../../states/ApiState';
|
||||||
|
import { InvenTreeTable } from '../../../../tables/InvenTreeTable';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Table for displaying available currencies
|
* Table for displaying available currencies
|
||||||
*/
|
*/
|
||||||
export default function CurrencyTable() {
|
export function CurrencyTable({
|
||||||
|
setInfo
|
||||||
|
}: Readonly<{ setInfo: (info: any) => void }>) {
|
||||||
const table = useTable('currency');
|
const table = useTable('currency');
|
||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -53,6 +56,7 @@ export default function CurrencyTable() {
|
|||||||
const tableActions = useMemo(() => {
|
const tableActions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
key="refresh"
|
||||||
onClick={refreshCurrencies}
|
onClick={refreshCurrencies}
|
||||||
tooltip={t`Refresh currency exchange rates`}
|
tooltip={t`Refresh currency exchange rates`}
|
||||||
icon={<IconReload />}
|
icon={<IconReload />}
|
||||||
@ -66,8 +70,10 @@ export default function CurrencyTable() {
|
|||||||
tableState={table}
|
tableState={table}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
props={{
|
props={{
|
||||||
|
idAccessor: 'currency',
|
||||||
tableActions: tableActions,
|
tableActions: tableActions,
|
||||||
dataFormatter: (data: any) => {
|
dataFormatter: (data: any) => {
|
||||||
|
setInfo(data);
|
||||||
let rates = data.exchange_rates ?? {};
|
let rates = data.exchange_rates ?? {};
|
||||||
|
|
||||||
return Object.entries(rates).map(([currency, rate]) => {
|
return Object.entries(rates).map(([currency, rate]) => {
|
||||||
@ -81,3 +87,20 @@ export default function CurrencyTable() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function CurrencyManagmentPanel() {
|
||||||
|
const [info, setInfo] = useState<any>({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="xs">
|
||||||
|
<FactCollection
|
||||||
|
items={[
|
||||||
|
{ title: t`Last fetched`, value: info?.updated },
|
||||||
|
{ title: t`Base currency`, value: info?.base_currency }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<CurrencyTable setInfo={setInfo} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
@ -44,6 +44,10 @@ const TaskManagementPanel = Loadable(
|
|||||||
lazy(() => import('./TaskManagementPanel'))
|
lazy(() => import('./TaskManagementPanel'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const CurrencyManagmentPanel = Loadable(
|
||||||
|
lazy(() => import('./CurrencyManagmentPanel'))
|
||||||
|
);
|
||||||
|
|
||||||
const PluginManagementPanel = Loadable(
|
const PluginManagementPanel = Loadable(
|
||||||
lazy(() => import('./PluginManagementPanel'))
|
lazy(() => import('./PluginManagementPanel'))
|
||||||
);
|
);
|
||||||
@ -64,6 +68,10 @@ const ProjectCodeTable = Loadable(
|
|||||||
lazy(() => import('../../../../tables/settings/ProjectCodeTable'))
|
lazy(() => import('../../../../tables/settings/ProjectCodeTable'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const CustomStateTable = Loadable(
|
||||||
|
lazy(() => import('../../../../tables/settings/CustomStateTable'))
|
||||||
|
);
|
||||||
|
|
||||||
const CustomUnitsTable = Loadable(
|
const CustomUnitsTable = Loadable(
|
||||||
lazy(() => import('../../../../tables/settings/CustomUnitsTable'))
|
lazy(() => import('../../../../tables/settings/CustomUnitsTable'))
|
||||||
);
|
);
|
||||||
@ -80,10 +88,6 @@ const LocationTypesTable = Loadable(
|
|||||||
lazy(() => import('../../../../tables/stock/LocationTypesTable'))
|
lazy(() => import('../../../../tables/stock/LocationTypesTable'))
|
||||||
);
|
);
|
||||||
|
|
||||||
const CurrencyTable = Loadable(
|
|
||||||
lazy(() => import('../../../../tables/settings/CurrencyTable'))
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function AdminCenter() {
|
export default function AdminCenter() {
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -133,7 +137,7 @@ export default function AdminCenter() {
|
|||||||
name: 'currencies',
|
name: 'currencies',
|
||||||
label: t`Currencies`,
|
label: t`Currencies`,
|
||||||
icon: <IconCoins />,
|
icon: <IconCoins />,
|
||||||
content: <CurrencyTable />
|
content: <CurrencyManagmentPanel />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'projectcodes',
|
name: 'projectcodes',
|
||||||
@ -147,6 +151,12 @@ export default function AdminCenter() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'customstates',
|
||||||
|
label: t`Custom States`,
|
||||||
|
icon: <IconListDetails />,
|
||||||
|
content: <CustomStateTable />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'customunits',
|
name: 'customunits',
|
||||||
label: t`Custom Units`,
|
label: t`Custom Units`,
|
||||||
|
@ -1,16 +1,9 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import { Accordion, Alert, Divider, Stack, Text } from '@mantine/core';
|
||||||
Accordion,
|
|
||||||
Alert,
|
|
||||||
Divider,
|
|
||||||
Paper,
|
|
||||||
SimpleGrid,
|
|
||||||
Stack,
|
|
||||||
Text
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
|
|
||||||
import { StylishText } from '../../../../components/items/StylishText';
|
import { StylishText } from '../../../../components/items/StylishText';
|
||||||
|
import { FactCollection } from '../../../../components/settings/FactCollection';
|
||||||
import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
|
||||||
import { Loadable } from '../../../../functions/loading';
|
import { Loadable } from '../../../../functions/loading';
|
||||||
import { useInstance } from '../../../../hooks/UseInstance';
|
import { useInstance } from '../../../../hooks/UseInstance';
|
||||||
@ -27,17 +20,6 @@ const FailedTasksTable = Loadable(
|
|||||||
lazy(() => import('../../../../tables/settings/FailedTasksTable'))
|
lazy(() => import('../../../../tables/settings/FailedTasksTable'))
|
||||||
);
|
);
|
||||||
|
|
||||||
function TaskCountOverview({ title, value }: { title: string; value: number }) {
|
|
||||||
return (
|
|
||||||
<Paper p="md" shadow="xs">
|
|
||||||
<Stack gap="xs">
|
|
||||||
<StylishText size="md">{title}</StylishText>
|
|
||||||
<Text>{value}</Text>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TaskManagementPanel() {
|
export default function TaskManagementPanel() {
|
||||||
const { instance: taskInfo } = useInstance({
|
const { instance: taskInfo } = useInstance({
|
||||||
endpoint: ApiEndpoints.task_overview,
|
endpoint: ApiEndpoints.task_overview,
|
||||||
@ -55,20 +37,13 @@ export default function TaskManagementPanel() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<SimpleGrid cols={3} spacing="xs">
|
<FactCollection
|
||||||
<TaskCountOverview
|
items={[
|
||||||
title={t`Pending Tasks`}
|
{ title: t`Pending Tasks`, value: taskInfo?.pending_tasks },
|
||||||
value={taskInfo?.pending_tasks}
|
{ title: t`Scheduled Tasks`, value: taskInfo?.scheduled_tasks },
|
||||||
/>
|
{ title: t`Failed Tasks`, value: taskInfo?.failed_tasks }
|
||||||
<TaskCountOverview
|
]}
|
||||||
title={t`Scheduled Tasks`}
|
/>
|
||||||
value={taskInfo?.scheduled_tasks}
|
|
||||||
/>
|
|
||||||
<TaskCountOverview
|
|
||||||
title={t`Failed Tasks`}
|
|
||||||
value={taskInfo?.failed_tasks}
|
|
||||||
/>
|
|
||||||
</SimpleGrid>
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Accordion defaultValue="pending">
|
<Accordion defaultValue="pending">
|
||||||
<Accordion.Item value="pending" key="pending-tasks">
|
<Accordion.Item value="pending" key="pending-tasks">
|
||||||
|
@ -30,10 +30,7 @@ import {
|
|||||||
CancelItemAction,
|
CancelItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction,
|
||||||
HoldItemAction,
|
HoldItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
@ -43,7 +40,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
@ -257,7 +253,7 @@ export default function BuildDetail() {
|
|||||||
label: t`Line Items`,
|
label: t`Line Items`,
|
||||||
icon: <IconListNumbers />,
|
icon: <IconListNumbers />,
|
||||||
content: build?.pk ? (
|
content: build?.pk ? (
|
||||||
<BuildLineTable buildId={build.pk} />
|
<BuildLineTable build={build} buildId={build.pk} />
|
||||||
) : (
|
) : (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)
|
)
|
||||||
@ -472,20 +468,9 @@ export default function BuildDetail() {
|
|||||||
/>,
|
/>,
|
||||||
<AdminButton model={ModelType.build} pk={build.pk} />,
|
<AdminButton model={ModelType.build} pk={build.pk} />,
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
model={ModelType.build}
|
||||||
ViewBarcodeAction({
|
pk={build.pk}
|
||||||
model: ModelType.build,
|
hash={build?.barcode_hash}
|
||||||
pk: build.pk
|
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
hidden: build?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
hidden: !build?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
/>,
|
/>,
|
||||||
<PrintingActions
|
<PrintingActions
|
||||||
modelType={ModelType.build}
|
modelType={ModelType.build}
|
||||||
@ -526,7 +511,7 @@ export default function BuildDetail() {
|
|||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
<StatusRenderer
|
<StatusRenderer
|
||||||
status={build.status}
|
status={build.status_custom_key}
|
||||||
type={ModelType.build}
|
type={ModelType.build}
|
||||||
options={{ size: 'lg' }}
|
options={{ size: 'lg' }}
|
||||||
/>
|
/>
|
||||||
|
@ -22,10 +22,7 @@ import {
|
|||||||
BarcodeActionDropdown,
|
BarcodeActionDropdown,
|
||||||
DeleteItemAction,
|
DeleteItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
@ -34,7 +31,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@ -271,24 +267,10 @@ export default function SupplierPartDetail() {
|
|||||||
return [
|
return [
|
||||||
<AdminButton model={ModelType.supplierpart} pk={supplierPart.pk} />,
|
<AdminButton model={ModelType.supplierpart} pk={supplierPart.pk} />,
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
model={ModelType.supplierpart}
|
||||||
ViewBarcodeAction({
|
pk={supplierPart.pk}
|
||||||
model: ModelType.supplierpart,
|
hash={supplierPart.barcode_hash}
|
||||||
pk: supplierPart.pk
|
perm={user.hasChangeRole(UserRoles.purchase_order)}
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
hidden:
|
|
||||||
supplierPart.barcode_hash ||
|
|
||||||
!user.hasChangeRole(UserRoles.purchase_order),
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
hidden:
|
|
||||||
!supplierPart.barcode_hash ||
|
|
||||||
!user.hasChangeRole(UserRoles.purchase_order),
|
|
||||||
onClick: notYetImplemented
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
tooltip={t`Supplier Part Actions`}
|
tooltip={t`Supplier Part Actions`}
|
||||||
|
@ -51,10 +51,7 @@ import {
|
|||||||
BarcodeActionDropdown,
|
BarcodeActionDropdown,
|
||||||
DeleteItemAction,
|
DeleteItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { StylishText } from '../../components/items/StylishText';
|
||||||
@ -74,7 +71,6 @@ import {
|
|||||||
useTransferStockItem
|
useTransferStockItem
|
||||||
} from '../../forms/StockForms';
|
} from '../../forms/StockForms';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@ -626,7 +622,7 @@ export default function PartDetail() {
|
|||||||
name: 'builds',
|
name: 'builds',
|
||||||
label: t`Build Orders`,
|
label: t`Build Orders`,
|
||||||
icon: <IconTools />,
|
icon: <IconTools />,
|
||||||
hidden: !part.assembly,
|
hidden: !part.assembly || !part.active,
|
||||||
content: part?.pk ? <BuildOrderTable partId={part.pk} /> : <Skeleton />
|
content: part?.pk ? <BuildOrderTable partId={part.pk} /> : <Skeleton />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -993,20 +989,10 @@ export default function PartDetail() {
|
|||||||
return [
|
return [
|
||||||
<AdminButton model={ModelType.part} pk={part.pk} />,
|
<AdminButton model={ModelType.part} pk={part.pk} />,
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
model={ModelType.part}
|
||||||
ViewBarcodeAction({
|
pk={part.pk}
|
||||||
model: ModelType.part,
|
hash={part?.barcode_hash}
|
||||||
pk: part.pk
|
perm={user.hasChangeRole(UserRoles.part)}
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
hidden: part?.barcode_hash || !user.hasChangeRole(UserRoles.part),
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
hidden: !part?.barcode_hash || !user.hasChangeRole(UserRoles.part),
|
|
||||||
onClick: notYetImplemented
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
key="action_dropdown"
|
key="action_dropdown"
|
||||||
/>,
|
/>,
|
||||||
<PrintingActions
|
<PrintingActions
|
||||||
|
@ -24,10 +24,7 @@ import {
|
|||||||
CancelItemAction,
|
CancelItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction,
|
||||||
HoldItemAction,
|
HoldItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { StylishText } from '../../components/items/StylishText';
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
@ -39,7 +36,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
@ -403,20 +399,9 @@ export default function PurchaseOrderDetail() {
|
|||||||
/>,
|
/>,
|
||||||
<AdminButton model={ModelType.purchaseorder} pk={order.pk} />,
|
<AdminButton model={ModelType.purchaseorder} pk={order.pk} />,
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
model={ModelType.purchaseorder}
|
||||||
ViewBarcodeAction({
|
pk={order.pk}
|
||||||
model: ModelType.purchaseorder,
|
hash={order?.barcode_hash}
|
||||||
pk: order.pk
|
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
hidden: order?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
hidden: !order?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
/>,
|
/>,
|
||||||
<PrintingActions
|
<PrintingActions
|
||||||
modelType={ModelType.purchaseorder}
|
modelType={ModelType.purchaseorder}
|
||||||
@ -459,7 +444,7 @@ export default function PurchaseOrderDetail() {
|
|||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
<StatusRenderer
|
<StatusRenderer
|
||||||
status={order.status}
|
status={order.status_custom_key}
|
||||||
type={ModelType.purchaseorder}
|
type={ModelType.purchaseorder}
|
||||||
options={{ size: 'lg' }}
|
options={{ size: 'lg' }}
|
||||||
/>
|
/>
|
||||||
|
@ -23,10 +23,7 @@ import {
|
|||||||
CancelItemAction,
|
CancelItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction,
|
||||||
HoldItemAction,
|
HoldItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { StylishText } from '../../components/items/StylishText';
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
@ -38,7 +35,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
@ -300,7 +296,7 @@ export default function ReturnOrderDetail() {
|
|||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
<StatusRenderer
|
<StatusRenderer
|
||||||
status={order.status}
|
status={order.status_custom_key}
|
||||||
type={ModelType.returnorder}
|
type={ModelType.returnorder}
|
||||||
options={{ size: 'lg' }}
|
options={{ size: 'lg' }}
|
||||||
/>
|
/>
|
||||||
@ -404,20 +400,9 @@ export default function ReturnOrderDetail() {
|
|||||||
/>,
|
/>,
|
||||||
<AdminButton model={ModelType.returnorder} pk={order.pk} />,
|
<AdminButton model={ModelType.returnorder} pk={order.pk} />,
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
model={ModelType.returnorder}
|
||||||
ViewBarcodeAction({
|
pk={order.pk}
|
||||||
model: ModelType.returnorder,
|
hash={order?.barcode_hash}
|
||||||
pk: order.pk
|
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
hidden: order?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
hidden: !order?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
/>,
|
/>,
|
||||||
<PrintingActions
|
<PrintingActions
|
||||||
modelType={ModelType.returnorder}
|
modelType={ModelType.returnorder}
|
||||||
|
@ -26,10 +26,7 @@ import {
|
|||||||
CancelItemAction,
|
CancelItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction,
|
||||||
HoldItemAction,
|
HoldItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { StylishText } from '../../components/items/StylishText';
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
@ -41,7 +38,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
@ -444,20 +440,9 @@ export default function SalesOrderDetail() {
|
|||||||
/>,
|
/>,
|
||||||
<AdminButton model={ModelType.salesorder} pk={order.pk} />,
|
<AdminButton model={ModelType.salesorder} pk={order.pk} />,
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
model={ModelType.salesorder}
|
||||||
ViewBarcodeAction({
|
pk={order.pk}
|
||||||
model: ModelType.salesorder,
|
hash={order?.barcode_hash}
|
||||||
pk: order.pk
|
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
hidden: order?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
hidden: !order?.barcode_hash,
|
|
||||||
onClick: notYetImplemented
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
/>,
|
/>,
|
||||||
<PrintingActions
|
<PrintingActions
|
||||||
modelType={ModelType.salesorder}
|
modelType={ModelType.salesorder}
|
||||||
@ -498,7 +483,7 @@ export default function SalesOrderDetail() {
|
|||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
<StatusRenderer
|
<StatusRenderer
|
||||||
status={order.status}
|
status={order.status_custom_key}
|
||||||
type={ModelType.salesorder}
|
type={ModelType.salesorder}
|
||||||
options={{ size: 'lg' }}
|
options={{ size: 'lg' }}
|
||||||
key={order.pk}
|
key={order.pk}
|
||||||
|
@ -18,10 +18,7 @@ import {
|
|||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
BarcodeActionDropdown,
|
BarcodeActionDropdown,
|
||||||
DeleteItemAction,
|
DeleteItemAction,
|
||||||
EditItemAction,
|
EditItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { ApiIcon } from '../../components/items/ApiIcon';
|
import { ApiIcon } from '../../components/items/ApiIcon';
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
@ -287,17 +284,9 @@ export default function Stock() {
|
|||||||
/>,
|
/>,
|
||||||
location.pk ? (
|
location.pk ? (
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
|
model={ModelType.stocklocation}
|
||||||
|
pk={location.pk}
|
||||||
actions={[
|
actions={[
|
||||||
ViewBarcodeAction({
|
|
||||||
model: ModelType.stocklocation,
|
|
||||||
pk: location.pk
|
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
{
|
{
|
||||||
name: 'Scan in stock items',
|
name: 'Scan in stock items',
|
||||||
icon: <InvenTreeIcon icon="stock" />,
|
icon: <InvenTreeIcon icon="stock" />,
|
||||||
|
@ -27,10 +27,7 @@ import {
|
|||||||
BarcodeActionDropdown,
|
BarcodeActionDropdown,
|
||||||
DeleteItemAction,
|
DeleteItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction
|
||||||
LinkBarcodeAction,
|
|
||||||
UnlinkBarcodeAction,
|
|
||||||
ViewBarcodeAction
|
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { StylishText } from '../../components/items/StylishText';
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
@ -50,7 +47,6 @@ import {
|
|||||||
useTransferStockItem
|
useTransferStockItem
|
||||||
} from '../../forms/StockForms';
|
} from '../../forms/StockForms';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@ -477,22 +473,10 @@ export default function StockDetail() {
|
|||||||
() => [
|
() => [
|
||||||
<AdminButton model={ModelType.stockitem} pk={stockitem.pk} />,
|
<AdminButton model={ModelType.stockitem} pk={stockitem.pk} />,
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
model={ModelType.stockitem}
|
||||||
ViewBarcodeAction({
|
pk={stockitem.pk}
|
||||||
model: ModelType.stockitem,
|
hash={stockitem?.barcode_hash}
|
||||||
pk: stockitem.pk
|
perm={user.hasChangeRole(UserRoles.stock)}
|
||||||
}),
|
|
||||||
LinkBarcodeAction({
|
|
||||||
hidden:
|
|
||||||
stockitem?.barcode_hash || !user.hasChangeRole(UserRoles.stock),
|
|
||||||
onClick: notYetImplemented
|
|
||||||
}),
|
|
||||||
UnlinkBarcodeAction({
|
|
||||||
hidden:
|
|
||||||
!stockitem?.barcode_hash || !user.hasChangeRole(UserRoles.stock),
|
|
||||||
onClick: notYetImplemented
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
/>,
|
/>,
|
||||||
<PrintingActions
|
<PrintingActions
|
||||||
modelType={ModelType.stockitem}
|
modelType={ModelType.stockitem}
|
||||||
@ -601,7 +585,7 @@ export default function StockDetail() {
|
|||||||
key="batch"
|
key="batch"
|
||||||
/>,
|
/>,
|
||||||
<StatusRenderer
|
<StatusRenderer
|
||||||
status={stockitem.status}
|
status={stockitem.status_custom_key}
|
||||||
type={ModelType.stockitem}
|
type={ModelType.stockitem}
|
||||||
options={{ size: 'lg' }}
|
options={{ size: 'lg' }}
|
||||||
key="status"
|
key="status"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ApiFormFieldType } from '../components/forms/fields/ApiFormField';
|
||||||
|
|
||||||
export type TableColumnProps<T = any> = {
|
export type TableColumnProps<T = any> = {
|
||||||
accessor?: string; // The key in the record to access
|
accessor?: string; // The key in the record to access
|
||||||
title?: string; // The title of the column - Note: this may be supplied by the API, and is not required, but it can be overridden if required
|
title?: string; // The title of the column - Note: this may be supplied by the API, and is not required, but it can be overridden if required
|
||||||
@ -5,6 +7,8 @@ export type TableColumnProps<T = any> = {
|
|||||||
sortable?: boolean; // Whether the column is sortable
|
sortable?: boolean; // Whether the column is sortable
|
||||||
switchable?: boolean; // Whether the column is switchable
|
switchable?: boolean; // Whether the column is switchable
|
||||||
hidden?: boolean; // Whether the column is hidden
|
hidden?: boolean; // Whether the column is hidden
|
||||||
|
editable?: boolean; // Whether the value of this column can be edited
|
||||||
|
definition?: ApiFormFieldType; // Optional field definition for the column
|
||||||
render?: (record: T, index?: number) => any; // A custom render function
|
render?: (record: T, index?: number) => any; // A custom render function
|
||||||
filter?: any; // A custom filter function
|
filter?: any; // A custom filter function
|
||||||
filtering?: boolean; // Whether the column is filterable
|
filtering?: boolean; // Whether the column is filterable
|
||||||
|
@ -178,7 +178,7 @@ export function StatusColumn({
|
|||||||
sortable: sortable ?? true,
|
sortable: sortable ?? true,
|
||||||
title: title,
|
title: title,
|
||||||
hidden: hidden,
|
hidden: hidden,
|
||||||
render: TableStatusRenderer(model, accessor ?? 'status')
|
render: TableStatusRenderer(model, accessor ?? 'status_custom_key')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ import { RowAction, RowActions } from './RowActions';
|
|||||||
import { TableSearchInput } from './Search';
|
import { TableSearchInput } from './Search';
|
||||||
|
|
||||||
const defaultPageSize: number = 25;
|
const defaultPageSize: number = 25;
|
||||||
|
const PAGE_SIZES = [10, 15, 20, 25, 50, 100, 500];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of optional properties which can be passed to an InvenTreeTable component
|
* Set of optional properties which can be passed to an InvenTreeTable component
|
||||||
@ -74,7 +75,6 @@ const defaultPageSize: number = 25;
|
|||||||
* @param enableRefresh : boolean - Enable refresh actions
|
* @param enableRefresh : boolean - Enable refresh actions
|
||||||
* @param enableColumnSwitching : boolean - Enable column switching
|
* @param enableColumnSwitching : boolean - Enable column switching
|
||||||
* @param enableColumnCaching : boolean - Enable caching of column names via API
|
* @param enableColumnCaching : boolean - Enable caching of column names via API
|
||||||
* @param pageSize : number - Number of records per page
|
|
||||||
* @param barcodeActions : any[] - List of barcode actions
|
* @param barcodeActions : any[] - List of barcode actions
|
||||||
* @param tableFilters : TableFilter[] - List of custom filters
|
* @param tableFilters : TableFilter[] - List of custom filters
|
||||||
* @param tableActions : any[] - List of custom action groups
|
* @param tableActions : any[] - List of custom action groups
|
||||||
@ -100,7 +100,6 @@ export type InvenTreeTableProps<T = any> = {
|
|||||||
enableLabels?: boolean;
|
enableLabels?: boolean;
|
||||||
enableReports?: boolean;
|
enableReports?: boolean;
|
||||||
afterBulkDelete?: () => void;
|
afterBulkDelete?: () => void;
|
||||||
pageSize?: number;
|
|
||||||
barcodeActions?: React.ReactNode[];
|
barcodeActions?: React.ReactNode[];
|
||||||
tableFilters?: TableFilter[];
|
tableFilters?: TableFilter[];
|
||||||
tableActions?: React.ReactNode[];
|
tableActions?: React.ReactNode[];
|
||||||
@ -129,7 +128,6 @@ const defaultInvenTreeTableProps: InvenTreeTableProps = {
|
|||||||
enableRefresh: true,
|
enableRefresh: true,
|
||||||
enableSearch: true,
|
enableSearch: true,
|
||||||
enableSelection: false,
|
enableSelection: false,
|
||||||
pageSize: defaultPageSize,
|
|
||||||
defaultSortColumn: '',
|
defaultSortColumn: '',
|
||||||
barcodeActions: [],
|
barcodeActions: [],
|
||||||
tableFilters: [],
|
tableFilters: [],
|
||||||
@ -360,7 +358,8 @@ export function InvenTreeTable<T = any>({
|
|||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
if (tableProps.enablePagination && paginate) {
|
if (tableProps.enablePagination && paginate) {
|
||||||
let pageSize = tableProps.pageSize ?? defaultPageSize;
|
let pageSize = tableState.pageSize ?? defaultPageSize;
|
||||||
|
if (pageSize != tableState.pageSize) tableState.setPageSize(pageSize);
|
||||||
queryParams.limit = pageSize;
|
queryParams.limit = pageSize;
|
||||||
queryParams.offset = (tableState.page - 1) * pageSize;
|
queryParams.offset = (tableState.page - 1) * pageSize;
|
||||||
}
|
}
|
||||||
@ -588,6 +587,13 @@ export function InvenTreeTable<T = any>({
|
|||||||
[props.onRowClick, props.onCellClick]
|
[props.onRowClick, props.onCellClick]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// pagination refresth table if pageSize changes
|
||||||
|
function updatePageSize(newData: number) {
|
||||||
|
tableState.setPageSize(newData);
|
||||||
|
tableState.setPage(1);
|
||||||
|
tableState.refreshTable();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{deleteRecords.modal}
|
{deleteRecords.modal}
|
||||||
@ -697,6 +703,7 @@ export function InvenTreeTable<T = any>({
|
|||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
withTableBorder
|
withTableBorder
|
||||||
|
withColumnBorders
|
||||||
striped
|
striped
|
||||||
highlightOnHover
|
highlightOnHover
|
||||||
loaderType="dots"
|
loaderType="dots"
|
||||||
@ -704,7 +711,7 @@ export function InvenTreeTable<T = any>({
|
|||||||
idAccessor={tableProps.idAccessor}
|
idAccessor={tableProps.idAccessor}
|
||||||
minHeight={300}
|
minHeight={300}
|
||||||
totalRecords={tableState.recordCount}
|
totalRecords={tableState.recordCount}
|
||||||
recordsPerPage={tableProps.pageSize ?? defaultPageSize}
|
recordsPerPage={tableState.pageSize}
|
||||||
page={tableState.page}
|
page={tableState.page}
|
||||||
onPageChange={tableState.setPage}
|
onPageChange={tableState.setPage}
|
||||||
sortStatus={sortStatus}
|
sortStatus={sortStatus}
|
||||||
@ -732,6 +739,8 @@ export function InvenTreeTable<T = any>({
|
|||||||
overflow: 'hidden'
|
overflow: 'hidden'
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
recordsPerPageOptions={PAGE_SIZES}
|
||||||
|
onRecordsPerPageChange={updatePageSize}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user