Merge branch 'dev' into feature-confirm-buttons
This commit is contained in:
commit
728e16199c
34
.ruff.toml
34
.ruff.toml
@ -1,7 +1,29 @@
|
|||||||
# https://beta.ruff.rs/docs/settings/
|
# https://docs.astral.sh/ruff/settings/
|
||||||
# https://beta.ruff.rs/docs/rules/
|
# https://docs.astral.sh/ruff/rules/
|
||||||
extend-select = ["I", "W", "UP", "COM", "DJ", "EXE", "ISC", "ICN", "G201", "INP", "PIE", "RSE", "SIM", "TID", "PLC", "PLE", "RUF"]
|
extend-select = [
|
||||||
# TODO PTH
|
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
|
||||||
|
"I", # https://docs.astral.sh/ruff/rules/#isort-i
|
||||||
|
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
|
||||||
|
"COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com
|
||||||
|
"DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj
|
||||||
|
"EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe
|
||||||
|
"ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc
|
||||||
|
"ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn
|
||||||
|
"G201", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
|
||||||
|
"INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp
|
||||||
|
"PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie
|
||||||
|
"Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q
|
||||||
|
"RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse
|
||||||
|
"T20", # https://docs.astral.sh/ruff/rules/#flake8-print-t20
|
||||||
|
"SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
|
||||||
|
"TID", # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid
|
||||||
|
"TCH", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch
|
||||||
|
"PLC", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||||
|
"PLE", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||||
|
"RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
|
||||||
|
"FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly
|
||||||
|
]
|
||||||
|
# TODO PTH https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
|
||||||
ignore = ["DJ001", "SIM105", "RUF012"]
|
ignore = ["DJ001", "SIM105", "RUF012"]
|
||||||
fix = true
|
fix = true
|
||||||
line-length = 88
|
line-length = 88
|
||||||
@ -13,9 +35,9 @@ show-fixes = true
|
|||||||
|
|
||||||
[per-file-ignores]
|
[per-file-ignores]
|
||||||
".github/scripts/*.py" = ["E501", "INP001", "SIM117"]
|
".github/scripts/*.py" = ["E501", "INP001", "SIM117"]
|
||||||
"docker/wait-for-redis.py" = ["INP001"]
|
"docker/wait-for-redis.py" = ["INP001", "T201"]
|
||||||
"*/tests/*.py" = ["E501", "SIM117"]
|
"*/tests/*.py" = ["E501", "SIM117"]
|
||||||
"*/migrations/*.py" = ["E501", "SIM"]
|
"*/migrations/*.py" = ["E501", "SIM", "T201"]
|
||||||
"src/paperless_tesseract/tests/test_parser.py" = ["RUF001"]
|
"src/paperless_tesseract/tests/test_parser.py" = ["RUF001"]
|
||||||
"src/documents/models.py" = ["SIM115"]
|
"src/documents/models.py" = ["SIM115"]
|
||||||
|
|
||||||
|
@ -39,8 +39,6 @@ RUN set -eux \
|
|||||||
# - Don't leave anything extra in here
|
# - Don't leave anything extra in here
|
||||||
FROM docker.io/python:3.11-slim-bookworm as main-app
|
FROM docker.io/python:3.11-slim-bookworm as main-app
|
||||||
|
|
||||||
ENV PYTHONWARNINGS="ignore:::django.http.response:517"
|
|
||||||
|
|
||||||
LABEL org.opencontainers.image.authors="paperless-ngx team <hello@paperless-ngx.com>"
|
LABEL org.opencontainers.image.authors="paperless-ngx team <hello@paperless-ngx.com>"
|
||||||
LABEL org.opencontainers.image.documentation="https://docs.paperless-ngx.com/"
|
LABEL org.opencontainers.image.documentation="https://docs.paperless-ngx.com/"
|
||||||
LABEL org.opencontainers.image.source="https://github.com/paperless-ngx/paperless-ngx"
|
LABEL org.opencontainers.image.source="https://github.com/paperless-ngx/paperless-ngx"
|
||||||
@ -57,6 +55,12 @@ ARG JBIG2ENC_VERSION=0.29
|
|||||||
ARG QPDF_VERSION=11.6.4
|
ARG QPDF_VERSION=11.6.4
|
||||||
ARG GS_VERSION=10.02.1
|
ARG GS_VERSION=10.02.1
|
||||||
|
|
||||||
|
# Set Python environment variables
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
# Ignore warning from Whitenoise
|
||||||
|
PYTHONWARNINGS="ignore:::django.http.response:517"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Begin installation and configuration
|
# Begin installation and configuration
|
||||||
# Order the steps below from least often changed to most
|
# Order the steps below from least often changed to most
|
||||||
|
@ -640,3 +640,42 @@ single-sided split marker page, the split document(s) will have an empty page at
|
|||||||
whatever else was on the backside of the split marker page.) You can work around that by having
|
whatever else was on the backside of the split marker page.) You can work around that by having
|
||||||
a split marker page that has the split barcode on _both_ sides. This way, the extra page will
|
a split marker page that has the split barcode on _both_ sides. This way, the extra page will
|
||||||
get automatically removed.
|
get automatically removed.
|
||||||
|
|
||||||
|
## SSO and third party authentication with Paperless-ngx
|
||||||
|
|
||||||
|
Paperless-ngx has a built-in authentication system from Django but you can easily integrate an
|
||||||
|
external authentication solution using one of the following methods:
|
||||||
|
|
||||||
|
### Remote User authentication
|
||||||
|
|
||||||
|
This is a simple option that uses remote user authentication made available by certain SSO
|
||||||
|
applications. See the relevant configuration options for more information:
|
||||||
|
[PAPERLESS_ENABLE_HTTP_REMOTE_USER](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER) and
|
||||||
|
[PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME](configuration.md#PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME)
|
||||||
|
|
||||||
|
### OpenID Connect and social authentication
|
||||||
|
|
||||||
|
Version 2.5.0 of Paperless-ngx added support for integrating other authentication systems via
|
||||||
|
the [django-allauth](https://github.com/pennersr/django-allauth) package. Once set up, users
|
||||||
|
can either log in or (optionally) sign up using any third party systems you integrate. See the
|
||||||
|
relevant [configuration settings](configuration.md#PAPERLESS_SOCIALACCOUNT_PROVIDERS) and
|
||||||
|
[django-allauth docs](https://docs.allauth.org/en/latest/socialaccount/configuration.html)
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
As an example, to set up login via Github, the following environment variables would need to be
|
||||||
|
set:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
PAPERLESS_APPS="allauth.socialaccount.providers.github"
|
||||||
|
PAPERLESS_SOCIALACCOUNT_PROVIDERS='{"github": {"APPS": [{"provider_id": "github","name": "Github","client_id": "<CLIENT_ID>","secret": "<CLIENT_SECRET>"}]}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, to use OpenID Connect ("OIDC"), via Keycloak in this example:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
PAPERLESS_APPS="allauth.socialaccount.providers.openid_connect"
|
||||||
|
PAPERLESS_SOCIALACCOUNT_PROVIDERS='
|
||||||
|
{"openid_connect": {"APPS": [{"provider_id": "keycloak","name": "Keycloak","client_id": "paperless","secret": "<CLIENT_SECRET>","settings": { "server_url": "https://<KEYCLOAK_SERVER>/realms/<REALM>/.well-known/openid-configuration"}}]}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
More details about configuration option for various providers can be found in the allauth documentation: https://docs.allauth.org/en/latest/socialaccount/providers/index.html#provider-specifics
|
||||||
|
@ -535,6 +535,42 @@ This is for use with self-signed certificates against local IMAP servers.
|
|||||||
Settings this value has security implications for the security of your email.
|
Settings this value has security implications for the security of your email.
|
||||||
Understand what it does and be sure you need to before setting.
|
Understand what it does and be sure you need to before setting.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_SOCIALACCOUNT_PROVIDERS=<json>`](#PAPERLESS_SOCIALACCOUNT_PROVIDERS) {#PAPERLESS_SOCIALACCOUNT_PROVIDERS}
|
||||||
|
|
||||||
|
: This variable is used to setup login and signup via social account providers which are compatible with django-allauth.
|
||||||
|
See the corresponding [django-allauth documentation](https://docs.allauth.org/en/0.60.0/socialaccount/providers/index.html)
|
||||||
|
for a list of provider configurations. You will also likely need to include the relevant Django 'application' inside the
|
||||||
|
[PAPERLESS_APPS](#PAPERLESS_APPS) setting.
|
||||||
|
|
||||||
|
Defaults to None, which does not enable any third party authentication systems.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_SOCIAL_AUTO_SIGNUP=<bool>`](#PAPERLESS_SOCIAL_AUTO_SIGNUP) {#PAPERLESS_SOCIAL_AUTO_SIGNUP}
|
||||||
|
|
||||||
|
: Attempt to signup the user using retrieved email, username etc from the third party authentication
|
||||||
|
system. See the corresponding
|
||||||
|
[django-allauth documentation](https://docs.allauth.org/en/0.60.0/socialaccount/configuration.html)
|
||||||
|
|
||||||
|
Defaults to False
|
||||||
|
|
||||||
|
#### [`PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS=<bool>`](#PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS) {#PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS}
|
||||||
|
|
||||||
|
: Allow users to signup for a new Paperless-ngx account using any setup third party authentication systems.
|
||||||
|
|
||||||
|
Defaults to True
|
||||||
|
|
||||||
|
#### [`PAPERLESS_ACCOUNT_ALLOW_SIGNUPS=<bool>`](#PAPERLESS_ACCOUNT_ALLOW_SIGNUPS) {#PAPERLESS_ACCOUNT_ALLOW_SIGNUPS}
|
||||||
|
|
||||||
|
: Allow users to signup for a new Paperless-ngx account.
|
||||||
|
|
||||||
|
Defaults to False
|
||||||
|
|
||||||
|
#### [`PAPERLESS_ACCOUNT_DEFAULT_HTTP_PROTOCOL=<string>`](#PAPERLESS_ACCOUNT_DEFAULT_HTTP_PROTOCOL) {#PAPERLESS_ACCOUNT_DEFAULT_HTTP_PROTOCOL}
|
||||||
|
|
||||||
|
: The protocol used when generating URLs, e.g. login callback URLs. See the corresponding
|
||||||
|
[django-allauth documentation](https://docs.allauth.org/en/latest/account/configuration.html)
|
||||||
|
|
||||||
|
Defaults to 'https'
|
||||||
|
|
||||||
## OCR settings {#ocr}
|
## OCR settings {#ocr}
|
||||||
|
|
||||||
Paperless uses [OCRmyPDF](https://ocrmypdf.readthedocs.io/en/latest/)
|
Paperless uses [OCRmyPDF](https://ocrmypdf.readthedocs.io/en/latest/)
|
||||||
@ -905,6 +941,14 @@ documents.
|
|||||||
|
|
||||||
Default is none, which disables the temporary directory.
|
Default is none, which disables the temporary directory.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_APPS=<string>`](#PAPERLESS_APPS) {#PAPERLESS_APPS}
|
||||||
|
|
||||||
|
: A comma-separated list of Django apps to be included in Django's
|
||||||
|
[`INSTALLED_APPS`](https://docs.djangoproject.com/en/5.0/ref/applications/). This setting should
|
||||||
|
be used with caution!
|
||||||
|
|
||||||
|
Defaults to None, which does not add any additional apps.
|
||||||
|
|
||||||
## Document Consumption {#consume_config}
|
## Document Consumption {#consume_config}
|
||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
|
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
|
||||||
@ -1173,6 +1217,55 @@ combination with PAPERLESS_CONSUMER_BARCODE_UPSCALE bigger than 1.0.
|
|||||||
|
|
||||||
Defaults to "300"
|
Defaults to "300"
|
||||||
|
|
||||||
|
#### [`PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=<bool>`](#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE) {#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE}
|
||||||
|
|
||||||
|
: Enables the detection of barcodes in the scanned document and
|
||||||
|
assigns or creates tags if a properly formatted barcode is detected.
|
||||||
|
|
||||||
|
The barcode must match one of the (configurable) regular expressions.
|
||||||
|
If the barcode text contains ',' (comma), it is split into multiple
|
||||||
|
barcodes which are individually processed for tagging.
|
||||||
|
|
||||||
|
Matching is case insensitive.
|
||||||
|
|
||||||
|
Defaults to false.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING=<json dict>`](#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING) {#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING}
|
||||||
|
|
||||||
|
: Defines a dictionary of filter regex and substitute expressions.
|
||||||
|
|
||||||
|
Syntax: {"<regex>": "<substitute>" [,...]]}
|
||||||
|
|
||||||
|
A barcode is considered for tagging if the barcode text matches
|
||||||
|
at least one of the provided <regex> pattern.
|
||||||
|
|
||||||
|
If a match is found, the <substitute> rule is applied. This allows very
|
||||||
|
versatile reformatting and mapping of barcode pattern to tag values.
|
||||||
|
|
||||||
|
If a tag is not found it will be created.
|
||||||
|
|
||||||
|
Defaults to:
|
||||||
|
|
||||||
|
{"TAG:(.*)": "\\g<1>"} which defines
|
||||||
|
- a regex TAG:(.*) which includes barcodes beginning with TAG:
|
||||||
|
followed by any text that gets stored into match group #1 and
|
||||||
|
- a substitute \\g<1> that replaces the original barcode text
|
||||||
|
by the content in match group #1.
|
||||||
|
Consequently, the tag is the barcode text without its TAG: prefix.
|
||||||
|
|
||||||
|
More examples:
|
||||||
|
|
||||||
|
{"ASN12.*": "JOHN", "ASN13.*": "SMITH"} for example maps
|
||||||
|
- ASN12nnnn barcodes to the tag JOHN and
|
||||||
|
- ASN13nnnn barcodes to the tag SMITH.
|
||||||
|
|
||||||
|
{"T-J": "JOHN", "T-S": "SMITH", "T-D": "DOE"} directly maps
|
||||||
|
- T-J barcodes to the tag JOHN,
|
||||||
|
- T-S barcodes to the tag SMITH and
|
||||||
|
- T-D barcodes to the tag DOE.
|
||||||
|
|
||||||
|
Please refer to the Python regex documentation for more information.
|
||||||
|
|
||||||
## Audit Trail
|
## Audit Trail
|
||||||
|
|
||||||
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED}
|
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED}
|
||||||
|
@ -68,6 +68,8 @@
|
|||||||
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
|
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
|
||||||
#PAPERLESS_CONSUMER_BARCODE_UPSCALE=0.0
|
#PAPERLESS_CONSUMER_BARCODE_UPSCALE=0.0
|
||||||
#PAPERLESS_CONSUMER_BARCODE_DPI=300
|
#PAPERLESS_CONSUMER_BARCODE_DPI=300
|
||||||
|
#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=false
|
||||||
|
#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}
|
||||||
#PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED=false
|
#PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED=false
|
||||||
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_SUBDIR_NAME=double-sided
|
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_SUBDIR_NAME=double-sided
|
||||||
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_TIFF_SUPPORT=false
|
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_TIFF_SUPPORT=false
|
||||||
|
@ -458,7 +458,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">354</context>
|
<context context-type="linenumber">346</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
|
||||||
@ -498,11 +498,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">167</context>
|
<context context-type="linenumber">159</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">99</context>
|
<context context-type="linenumber">92</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
@ -600,7 +600,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">342</context>
|
<context context-type="linenumber">334</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
@ -1048,11 +1048,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">112</context>
|
<context context-type="linenumber">104</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">131</context>
|
<context context-type="linenumber">123</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
||||||
@ -1075,11 +1075,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">120</context>
|
<context context-type="linenumber">112</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">139</context>
|
<context context-type="linenumber">131</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
||||||
@ -1105,7 +1105,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">145</context>
|
<context context-type="linenumber">137</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
||||||
@ -1346,7 +1346,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">64</context>
|
<context context-type="linenumber">60</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
@ -1393,7 +1393,7 @@
|
|||||||
<source>Delete</source>
|
<source>Delete</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">324</context>
|
<context context-type="linenumber">322</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
|
||||||
@ -1413,7 +1413,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">84</context>
|
<context context-type="linenumber">80</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.html</context>
|
||||||
@ -1475,6 +1475,10 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||||
<context context-type="linenumber">93</context>
|
<context context-type="linenumber">93</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
|
<context context-type="linenumber">205</context>
|
||||||
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
|
||||||
<context context-type="linenumber">38</context>
|
<context context-type="linenumber">38</context>
|
||||||
@ -1484,7 +1488,7 @@
|
|||||||
<source>No saved views defined.</source>
|
<source>No saved views defined.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">336</context>
|
<context context-type="linenumber">328</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6839066544204061364" datatype="html">
|
<trans-unit id="6839066544204061364" datatype="html">
|
||||||
@ -2433,13 +2437,6 @@
|
|||||||
<context context-type="linenumber">55</context>
|
<context context-type="linenumber">55</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7515883357904500238" datatype="html">
|
|
||||||
<source>Are you sure?</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/common/confirm-button/confirm-button.component.ts</context>
|
|
||||||
<context context-type="linenumber">20</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1234709746630139322" datatype="html">
|
<trans-unit id="1234709746630139322" datatype="html">
|
||||||
<source>Confirmation</source>
|
<source>Confirmation</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
@ -2518,7 +2515,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">166</context>
|
<context context-type="linenumber">158</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
|
||||||
@ -2526,7 +2523,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">98</context>
|
<context context-type="linenumber">91</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/select-dialog/select-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/select-dialog/select-dialog.component.html</context>
|
||||||
@ -2702,7 +2699,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">190</context>
|
<context context-type="linenumber">182</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6457471243969293847" datatype="html">
|
<trans-unit id="6457471243969293847" datatype="html">
|
||||||
@ -3068,7 +3065,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">100</context>
|
<context context-type="linenumber">92</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4754802869258527587" datatype="html">
|
<trans-unit id="4754802869258527587" datatype="html">
|
||||||
@ -3086,7 +3083,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">101</context>
|
<context context-type="linenumber">93</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5232720756589450549" datatype="html">
|
<trans-unit id="5232720756589450549" datatype="html">
|
||||||
@ -3104,7 +3101,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">164</context>
|
<context context-type="linenumber">156</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/toasts/toasts.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/toasts/toasts.component.html</context>
|
||||||
@ -3438,175 +3435,175 @@
|
|||||||
<source>Apply Actions:</source>
|
<source>Apply Actions:</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">66</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="51883444329775670" datatype="html">
|
<trans-unit id="51883444329775670" datatype="html">
|
||||||
<source>Add Action</source>
|
<source>Add Action</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">72</context>
|
<context context-type="linenumber">68</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6417103744331194518" datatype="html">
|
<trans-unit id="6417103744331194518" datatype="html">
|
||||||
<source>Action type</source>
|
<source>Action type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">94</context>
|
<context context-type="linenumber">86</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6019822389883736115" datatype="html">
|
<trans-unit id="6019822389883736115" datatype="html">
|
||||||
<source>Assign title</source>
|
<source>Assign title</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">98</context>
|
<context context-type="linenumber">90</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1098196422099517191" datatype="html">
|
<trans-unit id="1098196422099517191" datatype="html">
|
||||||
<source>Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>.</source>
|
<source>Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">98</context>
|
<context context-type="linenumber">90</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6528897010417701530" datatype="html">
|
<trans-unit id="6528897010417701530" datatype="html">
|
||||||
<source>Assign tags</source>
|
<source>Assign tags</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">99</context>
|
<context context-type="linenumber">91</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7198346314713788799" datatype="html">
|
<trans-unit id="7198346314713788799" datatype="html">
|
||||||
<source>Assign storage path</source>
|
<source>Assign storage path</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">102</context>
|
<context context-type="linenumber">94</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="475685412372379925" datatype="html">
|
<trans-unit id="475685412372379925" datatype="html">
|
||||||
<source>Assign custom fields</source>
|
<source>Assign custom fields</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">103</context>
|
<context context-type="linenumber">95</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5057200219587080996" datatype="html">
|
<trans-unit id="5057200219587080996" datatype="html">
|
||||||
<source>Assign owner</source>
|
<source>Assign owner</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">106</context>
|
<context context-type="linenumber">98</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1749184201773078639" datatype="html">
|
<trans-unit id="1749184201773078639" datatype="html">
|
||||||
<source>Assign view permissions</source>
|
<source>Assign view permissions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">108</context>
|
<context context-type="linenumber">100</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1744964187586405039" datatype="html">
|
<trans-unit id="1744964187586405039" datatype="html">
|
||||||
<source>Assign edit permissions</source>
|
<source>Assign edit permissions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">127</context>
|
<context context-type="linenumber">119</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3288318211116868972" datatype="html">
|
<trans-unit id="3288318211116868972" datatype="html">
|
||||||
<source>Trigger type</source>
|
<source>Trigger type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">174</context>
|
<context context-type="linenumber">166</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8727727835543352574" datatype="html">
|
<trans-unit id="8727727835543352574" datatype="html">
|
||||||
<source>Trigger for documents that match <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> filters specified below.</source>
|
<source>Trigger for documents that match <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> filters specified below.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">175</context>
|
<context context-type="linenumber">167</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7467799586957602479" datatype="html">
|
<trans-unit id="7467799586957602479" datatype="html">
|
||||||
<source>Filter filename</source>
|
<source>Filter filename</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">178</context>
|
<context context-type="linenumber">170</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3694878959415278689" datatype="html">
|
<trans-unit id="3694878959415278689" datatype="html">
|
||||||
<source>Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.</source>
|
<source>Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">178</context>
|
<context context-type="linenumber">170</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1473412958770421458" datatype="html">
|
<trans-unit id="1473412958770421458" datatype="html">
|
||||||
<source>Filter sources</source>
|
<source>Filter sources</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">180</context>
|
<context context-type="linenumber">172</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6540860478788535250" datatype="html">
|
<trans-unit id="6540860478788535250" datatype="html">
|
||||||
<source>Filter path</source>
|
<source>Filter path</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">181</context>
|
<context context-type="linenumber">173</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5491897741674893121" datatype="html">
|
<trans-unit id="5491897741674893121" datatype="html">
|
||||||
<source>Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</a></source>
|
<source>Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</a></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">181</context>
|
<context context-type="linenumber">173</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7468453896129193641" datatype="html">
|
<trans-unit id="7468453896129193641" datatype="html">
|
||||||
<source>Filter mail rule</source>
|
<source>Filter mail rule</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">182</context>
|
<context context-type="linenumber">174</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8663702115863339485" datatype="html">
|
<trans-unit id="8663702115863339485" datatype="html">
|
||||||
<source>Apply to documents consumed via this mail rule.</source>
|
<source>Apply to documents consumed via this mail rule.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">182</context>
|
<context context-type="linenumber">174</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6840369584127435743" datatype="html">
|
<trans-unit id="6840369584127435743" datatype="html">
|
||||||
<source>Content matching algorithm</source>
|
<source>Content matching algorithm</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">185</context>
|
<context context-type="linenumber">177</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="510635115034690805" datatype="html">
|
<trans-unit id="510635115034690805" datatype="html">
|
||||||
<source>Content matching pattern</source>
|
<source>Content matching pattern</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">187</context>
|
<context context-type="linenumber">179</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1333789258712064056" datatype="html">
|
<trans-unit id="1333789258712064056" datatype="html">
|
||||||
<source>Has tags</source>
|
<source>Has tags</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">196</context>
|
<context context-type="linenumber">188</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5281365940563983618" datatype="html">
|
<trans-unit id="5281365940563983618" datatype="html">
|
||||||
<source>Has correspondent</source>
|
<source>Has correspondent</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">197</context>
|
<context context-type="linenumber">189</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4806713133917046341" datatype="html">
|
<trans-unit id="4806713133917046341" datatype="html">
|
||||||
<source>Has document type</source>
|
<source>Has document type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">198</context>
|
<context context-type="linenumber">190</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4626030417479279989" datatype="html">
|
<trans-unit id="4626030417479279989" datatype="html">
|
||||||
@ -4085,14 +4082,14 @@
|
|||||||
<source>Regenerate auth token</source>
|
<source>Regenerate auth token</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">45</context>
|
<context context-type="linenumber">44</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5392341774767336507" datatype="html">
|
<trans-unit id="5392341774767336507" datatype="html">
|
||||||
<source>Copied!</source>
|
<source>Copied!</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">53</context>
|
<context context-type="linenumber">48</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
|
||||||
@ -4103,21 +4100,28 @@
|
|||||||
<source>Warning: changing the token cannot be undone</source>
|
<source>Warning: changing the token cannot be undone</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">55</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8935717557476105185" datatype="html">
|
<trans-unit id="8935717557476105185" datatype="html">
|
||||||
<source>Connected social accounts</source>
|
<source>Connected social accounts</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">59</context>
|
<context context-type="linenumber">54</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8383227756109993898" datatype="html">
|
<trans-unit id="8383227756109993898" datatype="html">
|
||||||
<source>Set a password before disconnecting social account.</source>
|
<source>Set a password before disconnecting social account.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">63</context>
|
<context context-type="linenumber">58</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5322995394400578831" datatype="html">
|
||||||
|
<source>Disconnect <x id="INTERPOLATION" equiv-text="{{ account.name }}"/> social account</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">68</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2907016025519254862" datatype="html">
|
<trans-unit id="2907016025519254862" datatype="html">
|
||||||
@ -4127,25 +4131,18 @@
|
|||||||
<context context-type="linenumber">69</context>
|
<context context-type="linenumber">69</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5322995394400578831" datatype="html">
|
|
||||||
<source>Disconnect <x id="INTERPOLATION" equiv-text="{{ account.name }}"/> social account</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
|
||||||
<context context-type="linenumber">71</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="649824314893051979" datatype="html">
|
<trans-unit id="649824314893051979" datatype="html">
|
||||||
<source>Warning: disconnecting social accounts cannot be undone</source>
|
<source>Warning: disconnecting social accounts cannot be undone</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">81</context>
|
<context context-type="linenumber">74</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1375396510511350122" datatype="html">
|
<trans-unit id="1375396510511350122" datatype="html">
|
||||||
<source>Connect new social account</source>
|
<source>Connect new social account</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">86</context>
|
<context context-type="linenumber">79</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6141884091799403188" datatype="html">
|
<trans-unit id="6141884091799403188" datatype="html">
|
||||||
@ -4995,6 +4992,10 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">717</context>
|
<context context-type="linenumber">717</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
|
<context context-type="linenumber">201</context>
|
||||||
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5382975254277698192" datatype="html">
|
<trans-unit id="5382975254277698192" datatype="html">
|
||||||
<source>Do you really want to delete document "<x id="PH" equiv-text="this.document.title"/>"?</source>
|
<source>Do you really want to delete document "<x id="PH" equiv-text="this.document.title"/>"?</source>
|
||||||
@ -6187,7 +6188,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">285</context>
|
<context context-type="linenumber">301</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4010735610815226758" datatype="html">
|
<trans-unit id="4010735610815226758" datatype="html">
|
||||||
@ -6270,26 +6271,26 @@
|
|||||||
<source>{VAR_PLURAL, plural, =1 {One <x id="INTERPOLATION"/>} other {<x id="INTERPOLATION_1"/> total <x id="INTERPOLATION_2"/>}}</source>
|
<source>{VAR_PLURAL, plural, =1 {One <x id="INTERPOLATION"/>} other {<x id="INTERPOLATION_1"/> total <x id="INTERPOLATION_2"/>}}</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||||
<context context-type="linenumber">113</context>
|
<context context-type="linenumber">107</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||||
<context context-type="linenumber">113</context>
|
<context context-type="linenumber">107</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||||
<context context-type="linenumber">113</context>
|
<context context-type="linenumber">107</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||||
<context context-type="linenumber">113</context>
|
<context context-type="linenumber">107</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="810888510148304696" datatype="html">
|
<trans-unit id="810888510148304696" datatype="html">
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">110</context>
|
<context context-type="linenumber">113</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/matching-model.ts</context>
|
<context context-type="sourcefile">src/app/data/matching-model.ts</context>
|
||||||
@ -6300,7 +6301,7 @@
|
|||||||
<source>None</source>
|
<source>None</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">112</context>
|
<context context-type="linenumber">115</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/matching-model.ts</context>
|
<context context-type="sourcefile">src/app/data/matching-model.ts</context>
|
||||||
@ -6311,42 +6312,49 @@
|
|||||||
<source>Successfully created <x id="PH" equiv-text="this.typeName"/>.</source>
|
<source>Successfully created <x id="PH" equiv-text="this.typeName"/>.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">155</context>
|
<context context-type="linenumber">158</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3928835053823658072" datatype="html">
|
<trans-unit id="3928835053823658072" datatype="html">
|
||||||
<source>Error occurred while creating <x id="PH" equiv-text="this.typeName"/>.</source>
|
<source>Error occurred while creating <x id="PH" equiv-text="this.typeName"/>.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">160</context>
|
<context context-type="linenumber">163</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2541368547549828690" datatype="html">
|
<trans-unit id="2541368547549828690" datatype="html">
|
||||||
<source>Successfully updated <x id="PH" equiv-text="this.typeName"/>.</source>
|
<source>Successfully updated <x id="PH" equiv-text="this.typeName"/>.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">175</context>
|
<context context-type="linenumber">178</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6442673774206210733" datatype="html">
|
<trans-unit id="6442673774206210733" datatype="html">
|
||||||
<source>Error occurred while saving <x id="PH" equiv-text="this.typeName"/>.</source>
|
<source>Error occurred while saving <x id="PH" equiv-text="this.typeName"/>.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">180</context>
|
<context context-type="linenumber">183</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8371896857609524947" datatype="html">
|
||||||
|
<source>Associated documents will not be deleted.</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
|
<context context-type="linenumber">203</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6639207128255974941" datatype="html">
|
<trans-unit id="6639207128255974941" datatype="html">
|
||||||
<source>Error while deleting element</source>
|
<source>Error while deleting element</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">204</context>
|
<context context-type="linenumber">219</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4863024195229581844" datatype="html">
|
<trans-unit id="4863024195229581844" datatype="html">
|
||||||
<source>Permissions updated successfully</source>
|
<source>Permissions updated successfully</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">278</context>
|
<context context-type="linenumber">294</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5101757640976222639" datatype="html">
|
<trans-unit id="5101757640976222639" datatype="html">
|
||||||
|
@ -33,64 +33,64 @@
|
|||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editUser(user)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.User }">
|
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editUser(user)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.User }">
|
||||||
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteUser(user)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.User }">
|
||||||
|
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (groups) {
|
||||||
|
<h4 class="mt-4 d-flex">
|
||||||
|
<ng-container i18n>Groups</ng-container>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
|
||||||
|
<i-bs name="plus-circle"></i-bs> <ng-container i18n>Add Group</ng-container>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
@if (groups.length > 0) {
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" i18n>Name</div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col" i18n>Actions</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@for (group of groups; track group) {
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Group }">
|
||||||
|
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteUser(user)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.User }">
|
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Group }">
|
||||||
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (groups) {
|
|
||||||
<h4 class="mt-4 d-flex">
|
|
||||||
<ng-container i18n>Groups</ng-container>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
|
|
||||||
<i-bs name="plus-circle"></i-bs> <ng-container i18n>Add Group</ng-container>
|
|
||||||
</button>
|
|
||||||
</h4>
|
|
||||||
@if (groups.length > 0) {
|
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col" i18n>Name</div>
|
|
||||||
<div class="col"></div>
|
|
||||||
<div class="col"></div>
|
|
||||||
<div class="col" i18n>Actions</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
@for (group of groups; track group) {
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
|
|
||||||
<div class="col"></div>
|
|
||||||
<div class="col"></div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Group }">
|
|
||||||
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Group }">
|
|
||||||
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
@if (groups.length === 0) {
|
|
||||||
<li class="list-group-item" i18n>No groups defined</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (!users || !groups) {
|
|
||||||
<div>
|
|
||||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
|
||||||
<div class="visually-hidden" i18n>Loading...</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</li>
|
||||||
|
}
|
||||||
|
@if (groups.length === 0) {
|
||||||
|
<li class="list-group-item" i18n>No groups defined</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!users || !groups) {
|
||||||
|
<div>
|
||||||
|
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||||
|
<div class="visually-hidden" i18n>Loading...</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@ -45,10 +45,18 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (editing) {
|
@if (editing) {
|
||||||
<button class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled">
|
@if ((selectionModel.itemsSorted | filter: filterText).length === 0 && createRef !== undefined) {
|
||||||
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
|
<button class="list-group-item list-group-item-action bg-light" (click)="createClicked()" [disabled]="disabled">
|
||||||
<i-bs width="1.5em" height="1em" name="arrow-right"></i-bs>
|
<small class="ms-2"><ng-container i18n>Create</ng-container> "{{filterText}}"</small>
|
||||||
</button>
|
<i-bs width="1.5em" height="1em" name="plus"></i-bs>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if ((selectionModel.itemsSorted | filter: filterText).length > 0) {
|
||||||
|
<button class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled">
|
||||||
|
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
|
||||||
|
<i-bs width="1.5em" height="1em" name="arrow-right"></i-bs>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@if (!editing && manyToOne) {
|
@if (!editing && manyToOne) {
|
||||||
<div class="list-group-item list-group-item-note pt-1 pb-2">
|
<div class="list-group-item list-group-item-note pt-1 pb-2">
|
||||||
|
@ -500,4 +500,46 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
|
|||||||
selectionModel.apply()
|
selectionModel.apply()
|
||||||
expect(selectionModel.itemsSorted).toEqual([nullItem, items[1], items[0]])
|
expect(selectionModel.itemsSorted).toEqual([nullItem, items[1], items[0]])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should set support create, keep open model and call createRef method', fakeAsync(() => {
|
||||||
|
component.items = items
|
||||||
|
component.icon = 'tag-fill'
|
||||||
|
component.selectionModel = selectionModel
|
||||||
|
fixture.nativeElement
|
||||||
|
.querySelector('button')
|
||||||
|
.dispatchEvent(new MouseEvent('click')) // open
|
||||||
|
fixture.detectChanges()
|
||||||
|
tick(100)
|
||||||
|
|
||||||
|
component.filterText = 'Test Filter Text'
|
||||||
|
component.createRef = jest.fn()
|
||||||
|
component.createClicked()
|
||||||
|
expect(component.creating).toBeTruthy()
|
||||||
|
expect(component.createRef).toHaveBeenCalledWith('Test Filter Text')
|
||||||
|
const openSpy = jest.spyOn(component.dropdown, 'open')
|
||||||
|
component.dropdownOpenChange(false)
|
||||||
|
expect(openSpy).toHaveBeenCalled() // should keep open
|
||||||
|
}))
|
||||||
|
|
||||||
|
it('should call create on enter inside filter field if 0 items remain while editing', fakeAsync(() => {
|
||||||
|
component.items = items
|
||||||
|
component.icon = 'tag-fill'
|
||||||
|
component.editing = true
|
||||||
|
component.createRef = jest.fn()
|
||||||
|
const createSpy = jest.spyOn(component, 'createClicked')
|
||||||
|
expect(component.selectionModel.getSelectedItems()).toEqual([])
|
||||||
|
fixture.nativeElement
|
||||||
|
.querySelector('button')
|
||||||
|
.dispatchEvent(new MouseEvent('click')) // open
|
||||||
|
fixture.detectChanges()
|
||||||
|
tick(100)
|
||||||
|
component.filterText = 'FooBar'
|
||||||
|
fixture.detectChanges()
|
||||||
|
component.listFilterTextInput.nativeElement.dispatchEvent(
|
||||||
|
new KeyboardEvent('keyup', { key: 'Enter' })
|
||||||
|
)
|
||||||
|
expect(component.selectionModel.getSelectedItems()).toEqual([])
|
||||||
|
tick(300)
|
||||||
|
expect(createSpy).toHaveBeenCalled()
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
@ -398,6 +398,11 @@ export class FilterableDropdownComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
disabled = false
|
disabled = false
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
createRef: (name) => void
|
||||||
|
|
||||||
|
creating: boolean = false
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
apply = new EventEmitter<ChangedItems>()
|
apply = new EventEmitter<ChangedItems>()
|
||||||
|
|
||||||
@ -437,6 +442,11 @@ export class FilterableDropdownComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createClicked() {
|
||||||
|
this.creating = true
|
||||||
|
this.createRef(this.filterText)
|
||||||
|
}
|
||||||
|
|
||||||
dropdownOpenChange(open: boolean): void {
|
dropdownOpenChange(open: boolean): void {
|
||||||
if (open) {
|
if (open) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -448,9 +458,14 @@ export class FilterableDropdownComponent {
|
|||||||
}
|
}
|
||||||
this.opened.next(this)
|
this.opened.next(this)
|
||||||
} else {
|
} else {
|
||||||
this.filterText = ''
|
if (this.creating) {
|
||||||
if (this.applyOnClose && this.selectionModel.isDirty()) {
|
this.dropdown.open()
|
||||||
this.apply.emit(this.selectionModel.diff())
|
this.creating = false
|
||||||
|
} else {
|
||||||
|
this.filterText = ''
|
||||||
|
if (this.applyOnClose && this.selectionModel.isDirty()) {
|
||||||
|
this.apply.emit(this.selectionModel.diff())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,6 +481,8 @@ export class FilterableDropdownComponent {
|
|||||||
this.dropdown.close()
|
this.dropdown.close()
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
|
} else if (filtered.length == 0 && this.createRef) {
|
||||||
|
this.createClicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
[editing]="true"
|
[editing]="true"
|
||||||
[manyToOne]="true"
|
[manyToOne]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
|
[createRef]="createTag.bind(this)"
|
||||||
(opened)="openTagsDropdown()"
|
(opened)="openTagsDropdown()"
|
||||||
[(selectionModel)]="tagSelectionModel"
|
[(selectionModel)]="tagSelectionModel"
|
||||||
[documentCounts]="tagDocumentCounts"
|
[documentCounts]="tagDocumentCounts"
|
||||||
@ -38,6 +39,7 @@
|
|||||||
[disabled]="!userCanEditAll"
|
[disabled]="!userCanEditAll"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
|
[createRef]="createCorrespondent.bind(this)"
|
||||||
(opened)="openCorrespondentDropdown()"
|
(opened)="openCorrespondentDropdown()"
|
||||||
[(selectionModel)]="correspondentSelectionModel"
|
[(selectionModel)]="correspondentSelectionModel"
|
||||||
[documentCounts]="correspondentDocumentCounts"
|
[documentCounts]="correspondentDocumentCounts"
|
||||||
@ -51,6 +53,7 @@
|
|||||||
[disabled]="!userCanEditAll"
|
[disabled]="!userCanEditAll"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
|
[createRef]="createDocumentType.bind(this)"
|
||||||
(opened)="openDocumentTypeDropdown()"
|
(opened)="openDocumentTypeDropdown()"
|
||||||
[(selectionModel)]="documentTypeSelectionModel"
|
[(selectionModel)]="documentTypeSelectionModel"
|
||||||
[documentCounts]="documentTypeDocumentCounts"
|
[documentCounts]="documentTypeDocumentCounts"
|
||||||
@ -64,6 +67,7 @@
|
|||||||
[disabled]="!userCanEditAll"
|
[disabled]="!userCanEditAll"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
|
[createRef]="createStoragePath.bind(this)"
|
||||||
(opened)="openStoragePathDropdown()"
|
(opened)="openStoragePathDropdown()"
|
||||||
[(selectionModel)]="storagePathsSelectionModel"
|
[(selectionModel)]="storagePathsSelectionModel"
|
||||||
[documentCounts]="storagePathDocumentCounts"
|
[documentCounts]="storagePathDocumentCounts"
|
||||||
|
@ -42,6 +42,16 @@ import { NgSelectModule } from '@ng-select/ng-select'
|
|||||||
import { GroupService } from 'src/app/services/rest/group.service'
|
import { GroupService } from 'src/app/services/rest/group.service'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { SwitchComponent } from '../../common/input/switch/switch.component'
|
import { SwitchComponent } from '../../common/input/switch/switch.component'
|
||||||
|
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||||
|
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||||
|
import { Results } from 'src/app/data/results'
|
||||||
|
import { Tag } from 'src/app/data/tag'
|
||||||
|
import { Correspondent } from 'src/app/data/correspondent'
|
||||||
|
import { DocumentType } from 'src/app/data/document-type'
|
||||||
|
import { StoragePath } from 'src/app/data/storage-path'
|
||||||
|
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||||
|
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||||
|
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||||
|
|
||||||
const selectionData: SelectionData = {
|
const selectionData: SelectionData = {
|
||||||
selected_tags: [
|
selected_tags: [
|
||||||
@ -65,6 +75,10 @@ describe('BulkEditorComponent', () => {
|
|||||||
let documentService: DocumentService
|
let documentService: DocumentService
|
||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
let modalService: NgbModal
|
let modalService: NgbModal
|
||||||
|
let tagService: TagService
|
||||||
|
let correspondentsService: CorrespondentService
|
||||||
|
let documentTypeService: DocumentTypeService
|
||||||
|
let storagePathService: StoragePathService
|
||||||
let httpTestingController: HttpTestingController
|
let httpTestingController: HttpTestingController
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -165,6 +179,10 @@ describe('BulkEditorComponent', () => {
|
|||||||
documentService = TestBed.inject(DocumentService)
|
documentService = TestBed.inject(DocumentService)
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
modalService = TestBed.inject(NgbModal)
|
modalService = TestBed.inject(NgbModal)
|
||||||
|
tagService = TestBed.inject(TagService)
|
||||||
|
correspondentsService = TestBed.inject(CorrespondentService)
|
||||||
|
documentTypeService = TestBed.inject(DocumentTypeService)
|
||||||
|
storagePathService = TestBed.inject(StoragePathService)
|
||||||
httpTestingController = TestBed.inject(HttpTestingController)
|
httpTestingController = TestBed.inject(HttpTestingController)
|
||||||
|
|
||||||
fixture = TestBed.createComponent(BulkEditorComponent)
|
fixture = TestBed.createComponent(BulkEditorComponent)
|
||||||
@ -902,4 +920,180 @@ describe('BulkEditorComponent', () => {
|
|||||||
`${environment.apiBaseUrl}documents/storage_paths/`
|
`${environment.apiBaseUrl}documents/storage_paths/`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support create new tag', () => {
|
||||||
|
const name = 'New Tag'
|
||||||
|
const newTag = { id: 101, name: 'New Tag' }
|
||||||
|
const tags: Results<Tag> = {
|
||||||
|
results: [
|
||||||
|
{ id: 1, name: 'Tag 1' },
|
||||||
|
{ id: 2, name: 'Tag 2' },
|
||||||
|
],
|
||||||
|
count: 2,
|
||||||
|
all: [1, 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalInstance = {
|
||||||
|
componentInstance: {
|
||||||
|
dialogMode: EditDialogMode.CREATE,
|
||||||
|
object: { name },
|
||||||
|
succeeded: of(newTag),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const tagListAllSpy = jest.spyOn(tagService, 'listAll')
|
||||||
|
tagListAllSpy.mockReturnValue(of(tags))
|
||||||
|
|
||||||
|
const tagSelectionModelToggleSpy = jest.spyOn(
|
||||||
|
component.tagSelectionModel,
|
||||||
|
'toggle'
|
||||||
|
)
|
||||||
|
|
||||||
|
const modalServiceOpenSpy = jest.spyOn(modalService, 'open')
|
||||||
|
modalServiceOpenSpy.mockReturnValue(modalInstance as any)
|
||||||
|
|
||||||
|
component.createTag(name)
|
||||||
|
|
||||||
|
expect(modalServiceOpenSpy).toHaveBeenCalledWith(TagEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
expect(tagListAllSpy).toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(tagSelectionModelToggleSpy).toHaveBeenCalledWith(newTag.id)
|
||||||
|
expect(component.tags).toEqual(tags.results)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support create new correspondent', () => {
|
||||||
|
const name = 'New Correspondent'
|
||||||
|
const newCorrespondent = { id: 101, name: 'New Correspondent' }
|
||||||
|
const correspondents: Results<Correspondent> = {
|
||||||
|
results: [
|
||||||
|
{ id: 1, name: 'Correspondent 1' },
|
||||||
|
{ id: 2, name: 'Correspondent 2' },
|
||||||
|
],
|
||||||
|
count: 2,
|
||||||
|
all: [1, 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalInstance = {
|
||||||
|
componentInstance: {
|
||||||
|
dialogMode: EditDialogMode.CREATE,
|
||||||
|
object: { name },
|
||||||
|
succeeded: of(newCorrespondent),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const correspondentsListAllSpy = jest.spyOn(
|
||||||
|
correspondentsService,
|
||||||
|
'listAll'
|
||||||
|
)
|
||||||
|
correspondentsListAllSpy.mockReturnValue(of(correspondents))
|
||||||
|
|
||||||
|
const correspondentSelectionModelToggleSpy = jest.spyOn(
|
||||||
|
component.correspondentSelectionModel,
|
||||||
|
'toggle'
|
||||||
|
)
|
||||||
|
|
||||||
|
const modalServiceOpenSpy = jest.spyOn(modalService, 'open')
|
||||||
|
modalServiceOpenSpy.mockReturnValue(modalInstance as any)
|
||||||
|
|
||||||
|
component.createCorrespondent(name)
|
||||||
|
|
||||||
|
expect(modalServiceOpenSpy).toHaveBeenCalledWith(
|
||||||
|
CorrespondentEditDialogComponent,
|
||||||
|
{ backdrop: 'static' }
|
||||||
|
)
|
||||||
|
expect(correspondentsListAllSpy).toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(correspondentSelectionModelToggleSpy).toHaveBeenCalledWith(
|
||||||
|
newCorrespondent.id
|
||||||
|
)
|
||||||
|
expect(component.correspondents).toEqual(correspondents.results)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support create new document type', () => {
|
||||||
|
const name = 'New Document Type'
|
||||||
|
const newDocumentType = { id: 101, name: 'New Document Type' }
|
||||||
|
const documentTypes: Results<DocumentType> = {
|
||||||
|
results: [
|
||||||
|
{ id: 1, name: 'Document Type 1' },
|
||||||
|
{ id: 2, name: 'Document Type 2' },
|
||||||
|
],
|
||||||
|
count: 2,
|
||||||
|
all: [1, 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalInstance = {
|
||||||
|
componentInstance: {
|
||||||
|
dialogMode: EditDialogMode.CREATE,
|
||||||
|
object: { name },
|
||||||
|
succeeded: of(newDocumentType),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const documentTypesListAllSpy = jest.spyOn(documentTypeService, 'listAll')
|
||||||
|
documentTypesListAllSpy.mockReturnValue(of(documentTypes))
|
||||||
|
|
||||||
|
const documentTypeSelectionModelToggleSpy = jest.spyOn(
|
||||||
|
component.documentTypeSelectionModel,
|
||||||
|
'toggle'
|
||||||
|
)
|
||||||
|
|
||||||
|
const modalServiceOpenSpy = jest.spyOn(modalService, 'open')
|
||||||
|
modalServiceOpenSpy.mockReturnValue(modalInstance as any)
|
||||||
|
|
||||||
|
component.createDocumentType(name)
|
||||||
|
|
||||||
|
expect(modalServiceOpenSpy).toHaveBeenCalledWith(
|
||||||
|
DocumentTypeEditDialogComponent,
|
||||||
|
{ backdrop: 'static' }
|
||||||
|
)
|
||||||
|
expect(documentTypesListAllSpy).toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(documentTypeSelectionModelToggleSpy).toHaveBeenCalledWith(
|
||||||
|
newDocumentType.id
|
||||||
|
)
|
||||||
|
expect(component.documentTypes).toEqual(documentTypes.results)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support create new storage path', () => {
|
||||||
|
const name = 'New Storage Path'
|
||||||
|
const newStoragePath = { id: 101, name: 'New Storage Path' }
|
||||||
|
const storagePaths: Results<StoragePath> = {
|
||||||
|
results: [
|
||||||
|
{ id: 1, name: 'Storage Path 1' },
|
||||||
|
{ id: 2, name: 'Storage Path 2' },
|
||||||
|
],
|
||||||
|
count: 2,
|
||||||
|
all: [1, 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalInstance = {
|
||||||
|
componentInstance: {
|
||||||
|
dialogMode: EditDialogMode.CREATE,
|
||||||
|
object: { name },
|
||||||
|
succeeded: of(newStoragePath),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const storagePathsListAllSpy = jest.spyOn(storagePathService, 'listAll')
|
||||||
|
storagePathsListAllSpy.mockReturnValue(of(storagePaths))
|
||||||
|
|
||||||
|
const storagePathsSelectionModelToggleSpy = jest.spyOn(
|
||||||
|
component.storagePathsSelectionModel,
|
||||||
|
'toggle'
|
||||||
|
)
|
||||||
|
|
||||||
|
const modalServiceOpenSpy = jest.spyOn(modalService, 'open')
|
||||||
|
modalServiceOpenSpy.mockReturnValue(modalInstance as any)
|
||||||
|
|
||||||
|
component.createStoragePath(name)
|
||||||
|
|
||||||
|
expect(modalServiceOpenSpy).toHaveBeenCalledWith(
|
||||||
|
StoragePathEditDialogComponent,
|
||||||
|
{ backdrop: 'static' }
|
||||||
|
)
|
||||||
|
expect(storagePathsListAllSpy).toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(storagePathsSelectionModelToggleSpy).toHaveBeenCalledWith(
|
||||||
|
newStoragePath.id
|
||||||
|
)
|
||||||
|
expect(component.storagePaths).toEqual(storagePaths.results)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -33,7 +33,12 @@ import {
|
|||||||
PermissionType,
|
PermissionType,
|
||||||
} from 'src/app/services/permissions.service'
|
} from 'src/app/services/permissions.service'
|
||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
import { first, Subject, takeUntil } from 'rxjs'
|
import { first, map, Subject, switchMap, takeUntil } from 'rxjs'
|
||||||
|
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||||
|
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||||
|
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||||
|
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||||
|
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-bulk-editor',
|
selector: 'pngx-bulk-editor',
|
||||||
@ -479,6 +484,92 @@ export class BulkEditorComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createTag(name: string) {
|
||||||
|
let modal = this.modalService.open(TagEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||||
|
modal.componentInstance.object = { name }
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(
|
||||||
|
switchMap((newTag) => {
|
||||||
|
return this.tagService
|
||||||
|
.listAll()
|
||||||
|
.pipe(map((tags) => ({ newTag, tags })))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(({ newTag, tags }) => {
|
||||||
|
this.tags = tags.results
|
||||||
|
this.tagSelectionModel.toggle(newTag.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createCorrespondent(name: string) {
|
||||||
|
let modal = this.modalService.open(CorrespondentEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||||
|
modal.componentInstance.object = { name }
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(
|
||||||
|
switchMap((newCorrespondent) => {
|
||||||
|
return this.correspondentService
|
||||||
|
.listAll()
|
||||||
|
.pipe(
|
||||||
|
map((correspondents) => ({ newCorrespondent, correspondents }))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(({ newCorrespondent, correspondents }) => {
|
||||||
|
this.correspondents = correspondents.results
|
||||||
|
this.correspondentSelectionModel.toggle(newCorrespondent.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createDocumentType(name: string) {
|
||||||
|
let modal = this.modalService.open(DocumentTypeEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||||
|
modal.componentInstance.object = { name }
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(
|
||||||
|
switchMap((newDocumentType) => {
|
||||||
|
return this.documentTypeService
|
||||||
|
.listAll()
|
||||||
|
.pipe(map((documentTypes) => ({ newDocumentType, documentTypes })))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(({ newDocumentType, documentTypes }) => {
|
||||||
|
this.documentTypes = documentTypes.results
|
||||||
|
this.documentTypeSelectionModel.toggle(newDocumentType.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createStoragePath(name: string) {
|
||||||
|
let modal = this.modalService.open(StoragePathEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||||
|
modal.componentInstance.object = { name }
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(
|
||||||
|
switchMap((newStoragePath) => {
|
||||||
|
return this.storagePathService
|
||||||
|
.listAll()
|
||||||
|
.pipe(map((storagePaths) => ({ newStoragePath, storagePaths })))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(({ newStoragePath, storagePaths }) => {
|
||||||
|
this.storagePaths = storagePaths.results
|
||||||
|
this.storagePathsSelectionModel.toggle(newStoragePath.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
applyDelete() {
|
applyDelete() {
|
||||||
let modal = this.modalService.open(ConfirmDialogComponent, {
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
@if (notesEnabled && document.notes.length) {
|
@if (notesEnabled && document.notes.length) {
|
||||||
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
|
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
|
||||||
<span class="badge rounded-pill bg-light border text-primary">
|
<span class="badge rounded-pill bg-light border text-primary">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="ms-1 me-1" name="chat-left-text"></i-bs>
|
<i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
|
||||||
{{document.notes.length}}</span>
|
{{document.notes.length}}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
@ -43,14 +43,14 @@
|
|||||||
@if (document.document_type) {
|
@if (document.document_type) {
|
||||||
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
|
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
|
||||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="me-2 text-muted" name="file-earmark"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
|
||||||
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
|
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@if (document.storage_path) {
|
@if (document.storage_path) {
|
||||||
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
|
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
|
||||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="me-2 text-muted" name="folder"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
|
||||||
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
|
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@ -63,25 +63,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
|
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="me-2 text-muted" name="calendar-event"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
|
||||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (document.archive_serial_number | isNumber) {
|
@if (document.archive_serial_number | isNumber) {
|
||||||
<div class="ps-0 p-1">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="me-2 text-muted" name="upc-scan"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="upc-scan"></i-bs>
|
||||||
<small>#{{document.archive_serial_number}}</small>
|
<small>#{{document.archive_serial_number}}</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (document.owner && document.owner !== settingsService.currentUser.id) {
|
@if (document.owner && document.owner !== settingsService.currentUser.id) {
|
||||||
<div class="ps-0 p-1">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
||||||
<small>{{document.owner | username}}</small>
|
<small>{{document.owner | username}}</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (document.is_shared_by_requester) {
|
@if (document.is_shared_by_requester) {
|
||||||
<div class="ps-0 p-1">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="me-2 text-muted" name="people-fill"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="people-fill"></i-bs>
|
||||||
<small i18n>Shared</small>
|
<small i18n>Shared</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@
|
|||||||
@if (d.notes.length) {
|
@if (d.notes.length) {
|
||||||
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
||||||
<span class="badge rounded-pill bg-light border text-primary">
|
<span class="badge rounded-pill bg-light border text-primary">
|
||||||
<i-bs width="0.9rem" height="0.9rem" class="ms-1 me-1" name="chat-left-text"></i-bs>
|
<i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
|
||||||
{{d.notes.length}}</span>
|
{{d.notes.length}}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
@ -29,16 +29,16 @@
|
|||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editField(field)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editField(field)">
|
||||||
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteField(field)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteField(field)">
|
||||||
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
}
|
</div>
|
||||||
@if (fields.length === 0) {
|
</li>
|
||||||
<li class="list-group-item" i18n>No fields defined.</li>
|
}
|
||||||
}
|
@if (fields.length === 0) {
|
||||||
</ul>
|
<li class="list-group-item" i18n>No fields defined.</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
@ -32,72 +32,72 @@
|
|||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userCanEdit(account)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailAccount(account)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userCanEdit(account)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailAccount(account)">
|
||||||
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button *pngxIfOwner="account" class="btn btn-sm btn-outline-secondary" type="button" (click)="editPermissions(account)">
|
<button *pngxIfOwner="account" class="btn btn-sm btn-outline-secondary" type="button" (click)="editPermissions(account)">
|
||||||
<i-bs width="1em" height="1em" name="person-lock"></i-bs> <ng-container i18n>Permissions</ng-container>
|
<i-bs width="1em" height="1em" name="person-lock"></i-bs> <ng-container i18n>Permissions</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)">
|
||||||
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
@if (mailAccounts.length === 0) {
|
|
||||||
<li class="list-group-item" i18n>No mail accounts defined.</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }">
|
|
||||||
<h4 class="mt-4">
|
|
||||||
<ng-container i18n>Mail rules</ng-container>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailRule()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }">
|
|
||||||
<i-bs name="plus-circle"></i-bs> <ng-container i18n>Add Rule</ng-container>
|
|
||||||
</button>
|
|
||||||
</h4>
|
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col" i18n>Name</div>
|
|
||||||
<div class="col" i18n>Account</div>
|
|
||||||
<div class="col" i18n>Actions</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
@if (mailAccounts.length === 0) {
|
||||||
|
<li class="list-group-item" i18n>No mail accounts defined.</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
@for (rule of mailRules; track rule) {
|
</ng-container>
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
|
|
||||||
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailRule(rule)">
|
|
||||||
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
|
||||||
</button>
|
|
||||||
<button *pngxIfOwner="rule" class="btn btn-sm btn-outline-secondary" type="button" (click)="editPermissions(rule)">
|
|
||||||
<i-bs width="1em" height="1em" name="person-lock"></i-bs> <ng-container i18n>Permissions</ng-container>
|
|
||||||
</button>
|
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)">
|
|
||||||
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
@if (mailRules.length === 0) {
|
|
||||||
<li class="list-group-item" i18n>No mail rules defined.</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</ng-container>
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }">
|
||||||
|
<h4 class="mt-4">
|
||||||
|
<ng-container i18n>Mail rules</ng-container>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailRule()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }">
|
||||||
|
<i-bs name="plus-circle"></i-bs> <ng-container i18n>Add Rule</ng-container>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" i18n>Name</div>
|
||||||
|
<div class="col" i18n>Account</div>
|
||||||
|
<div class="col" i18n>Actions</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
@if (!mailAccounts || !mailRules) {
|
@for (rule of mailRules; track rule) {
|
||||||
<div>
|
<li class="list-group-item">
|
||||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
<div class="row">
|
||||||
<div class="visually-hidden" i18n>Loading...</div>
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
|
||||||
</div>
|
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
||||||
}
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailRule(rule)">
|
||||||
|
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
||||||
|
</button>
|
||||||
|
<button *pngxIfOwner="rule" class="btn btn-sm btn-outline-secondary" type="button" (click)="editPermissions(rule)">
|
||||||
|
<i-bs width="1em" height="1em" name="person-lock"></i-bs> <ng-container i18n>Permissions</ng-container>
|
||||||
|
</button>
|
||||||
|
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)">
|
||||||
|
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
@if (mailRules.length === 0) {
|
||||||
|
<li class="list-group-item" i18n>No mail rules defined.</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
@if (!mailAccounts || !mailRules) {
|
||||||
|
<div>
|
||||||
|
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||||
|
<div class="visually-hidden" i18n>Loading...</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@ -33,16 +33,16 @@
|
|||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Workflow }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editWorkflow(workflow)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Workflow }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editWorkflow(workflow)">
|
||||||
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
<i-bs width="1em" height="1em" name="pencil"></i-bs> <ng-container i18n>Edit</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Workflow }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteWorkflow(workflow)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Workflow }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteWorkflow(workflow)">
|
||||||
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
}
|
</div>
|
||||||
@if (workflows.length === 0) {
|
</li>
|
||||||
<li class="list-group-item" i18n>No workflows defined.</li>
|
}
|
||||||
}
|
@if (workflows.length === 0) {
|
||||||
</ul>
|
<li class="list-group-item" i18n>No workflows defined.</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
@ -14,6 +14,7 @@ from PIL import Image
|
|||||||
|
|
||||||
from documents.converters import convert_from_tiff_to_pdf
|
from documents.converters import convert_from_tiff_to_pdf
|
||||||
from documents.data_models import ConsumableDocument
|
from documents.data_models import ConsumableDocument
|
||||||
|
from documents.models import Tag
|
||||||
from documents.plugins.base import ConsumeTaskPlugin
|
from documents.plugins.base import ConsumeTaskPlugin
|
||||||
from documents.plugins.base import StopConsumeTaskError
|
from documents.plugins.base import StopConsumeTaskError
|
||||||
from documents.plugins.helpers import ProgressStatusOptions
|
from documents.plugins.helpers import ProgressStatusOptions
|
||||||
@ -65,7 +66,9 @@ class BarcodePlugin(ConsumeTaskPlugin):
|
|||||||
supported_mimes = {"application/pdf"}
|
supported_mimes = {"application/pdf"}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
settings.CONSUMER_ENABLE_ASN_BARCODE or settings.CONSUMER_ENABLE_BARCODES
|
settings.CONSUMER_ENABLE_ASN_BARCODE
|
||||||
|
or settings.CONSUMER_ENABLE_BARCODES
|
||||||
|
or settings.CONSUMER_ENABLE_TAG_BARCODE
|
||||||
) and self.input_doc.mime_type in supported_mimes
|
) and self.input_doc.mime_type in supported_mimes
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@ -90,6 +93,16 @@ class BarcodePlugin(ConsumeTaskPlugin):
|
|||||||
logger.info(f"Found ASN in barcode: {located_asn}")
|
logger.info(f"Found ASN in barcode: {located_asn}")
|
||||||
self.metadata.asn = located_asn
|
self.metadata.asn = located_asn
|
||||||
|
|
||||||
|
# try reading tags from barcodes
|
||||||
|
if settings.CONSUMER_ENABLE_TAG_BARCODE:
|
||||||
|
tags = self.tags
|
||||||
|
if tags is not None and len(tags) > 0:
|
||||||
|
if self.metadata.tag_ids:
|
||||||
|
self.metadata.tag_ids += tags
|
||||||
|
else:
|
||||||
|
self.metadata.tag_ids = tags
|
||||||
|
logger.info(f"Found tags in barcode: {tags}")
|
||||||
|
|
||||||
separator_pages = self.get_separation_pages()
|
separator_pages = self.get_separation_pages()
|
||||||
if not separator_pages:
|
if not separator_pages:
|
||||||
return "No pages to split on!"
|
return "No pages to split on!"
|
||||||
@ -279,6 +292,53 @@ class BarcodePlugin(ConsumeTaskPlugin):
|
|||||||
|
|
||||||
return asn
|
return asn
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tags(self) -> Optional[list[int]]:
|
||||||
|
"""
|
||||||
|
Search the parsed barcodes for any tags.
|
||||||
|
Returns the detected tag ids (or empty list)
|
||||||
|
"""
|
||||||
|
tags = []
|
||||||
|
|
||||||
|
# Ensure the barcodes have been read
|
||||||
|
self.detect()
|
||||||
|
|
||||||
|
for x in self.barcodes:
|
||||||
|
tag_texts = x.value
|
||||||
|
|
||||||
|
for raw in tag_texts.split(","):
|
||||||
|
try:
|
||||||
|
tag = None
|
||||||
|
for regex in settings.CONSUMER_TAG_BARCODE_MAPPING:
|
||||||
|
if re.match(regex, raw, flags=re.IGNORECASE):
|
||||||
|
sub = settings.CONSUMER_TAG_BARCODE_MAPPING[regex]
|
||||||
|
tag = (
|
||||||
|
re.sub(regex, sub, raw, flags=re.IGNORECASE)
|
||||||
|
if sub
|
||||||
|
else raw
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
if tag:
|
||||||
|
tag = Tag.objects.get_or_create(
|
||||||
|
name__iexact=tag,
|
||||||
|
defaults={"name": tag},
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Found Tag Barcode '{raw}', substituted "
|
||||||
|
f"to '{tag}' and mapped to "
|
||||||
|
f"tag #{tag.pk}.",
|
||||||
|
)
|
||||||
|
tags.append(tag.pk)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Failed to find or create TAG '{raw}' because: {e}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return tags
|
||||||
|
|
||||||
def get_separation_pages(self) -> dict[int, bool]:
|
def get_separation_pages(self) -> dict[int, bool]:
|
||||||
"""
|
"""
|
||||||
Search the parsed barcodes for separators and returns a dict of page
|
Search the parsed barcodes for separators and returns a dict of page
|
||||||
|
@ -90,7 +90,6 @@ def set_suggestions_cache(
|
|||||||
"""
|
"""
|
||||||
if classifier is not None:
|
if classifier is not None:
|
||||||
doc_key = get_suggestion_cache_key(document_id)
|
doc_key = get_suggestion_cache_key(document_id)
|
||||||
print(classifier.last_auto_type_hash)
|
|
||||||
cache.set(
|
cache.set(
|
||||||
doc_key,
|
doc_key,
|
||||||
SuggestionCacheData(
|
SuggestionCacheData(
|
||||||
|
@ -4,11 +4,14 @@ import pickle
|
|||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from datetime import datetime
|
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from pathlib import Path
|
from typing import TYPE_CHECKING
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from sklearn.exceptions import InconsistentVersionWarning
|
from sklearn.exceptions import InconsistentVersionWarning
|
||||||
|
@ -69,8 +69,6 @@ class Command(ProgressBarMixin, BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
self.handle_progress_bar_mixin(**options)
|
self.handle_progress_bar_mixin(**options)
|
||||||
# Detect if we support color
|
|
||||||
color = self.style.ERROR("test") != "test"
|
|
||||||
|
|
||||||
if options["inbox_only"]:
|
if options["inbox_only"]:
|
||||||
queryset = Document.objects.filter(tags__is_inbox_tag=True)
|
queryset = Document.objects.filter(tags__is_inbox_tag=True)
|
||||||
@ -96,7 +94,8 @@ class Command(ProgressBarMixin, BaseCommand):
|
|||||||
use_first=options["use_first"],
|
use_first=options["use_first"],
|
||||||
suggest=options["suggest"],
|
suggest=options["suggest"],
|
||||||
base_url=options["base_url"],
|
base_url=options["base_url"],
|
||||||
color=color,
|
stdout=self.stdout,
|
||||||
|
style_func=self.style,
|
||||||
)
|
)
|
||||||
|
|
||||||
if options["document_type"]:
|
if options["document_type"]:
|
||||||
@ -108,7 +107,8 @@ class Command(ProgressBarMixin, BaseCommand):
|
|||||||
use_first=options["use_first"],
|
use_first=options["use_first"],
|
||||||
suggest=options["suggest"],
|
suggest=options["suggest"],
|
||||||
base_url=options["base_url"],
|
base_url=options["base_url"],
|
||||||
color=color,
|
stdout=self.stdout,
|
||||||
|
style_func=self.style,
|
||||||
)
|
)
|
||||||
|
|
||||||
if options["tags"]:
|
if options["tags"]:
|
||||||
@ -119,7 +119,8 @@ class Command(ProgressBarMixin, BaseCommand):
|
|||||||
replace=options["overwrite"],
|
replace=options["overwrite"],
|
||||||
suggest=options["suggest"],
|
suggest=options["suggest"],
|
||||||
base_url=options["base_url"],
|
base_url=options["base_url"],
|
||||||
color=color,
|
stdout=self.stdout,
|
||||||
|
style_func=self.style,
|
||||||
)
|
)
|
||||||
if options["storage_path"]:
|
if options["storage_path"]:
|
||||||
set_storage_path(
|
set_storage_path(
|
||||||
@ -130,5 +131,6 @@ class Command(ProgressBarMixin, BaseCommand):
|
|||||||
use_first=options["use_first"],
|
use_first=options["use_first"],
|
||||||
suggest=options["suggest"],
|
suggest=options["suggest"],
|
||||||
base_url=options["base_url"],
|
base_url=options["base_url"],
|
||||||
color=color,
|
stdout=self.stdout,
|
||||||
|
style_func=self.style,
|
||||||
)
|
)
|
||||||
|
@ -19,7 +19,7 @@ def _process_document(doc_id):
|
|||||||
if parser_class:
|
if parser_class:
|
||||||
parser = parser_class(logging_group=None)
|
parser = parser_class(logging_group=None)
|
||||||
else:
|
else:
|
||||||
print(f"{document} No parser for mime type {document.mime_type}")
|
print(f"{document} No parser for mime type {document.mime_type}") # noqa: T201
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -5,7 +5,9 @@ from typing import Union
|
|||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
from channels_redis.pubsub import RedisPubSubChannelLayer
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from channels_redis.pubsub import RedisPubSubChannelLayer
|
||||||
|
|
||||||
|
|
||||||
class ProgressStatusOptions(str, enum.Enum):
|
class ProgressStatusOptions(str, enum.Enum):
|
||||||
|
@ -18,7 +18,6 @@ from django.db import close_old_connections
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import termcolors
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
|
|
||||||
@ -54,6 +53,26 @@ def add_inbox_tags(sender, document: Document, logging_group=None, **kwargs):
|
|||||||
document.tags.add(*inbox_tags)
|
document.tags.add(*inbox_tags)
|
||||||
|
|
||||||
|
|
||||||
|
def _suggestion_printer(
|
||||||
|
stdout,
|
||||||
|
style_func,
|
||||||
|
suggestion_type: str,
|
||||||
|
document: Document,
|
||||||
|
selected: MatchingModel,
|
||||||
|
base_url: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Smaller helper to reduce duplication when just outputting suggestions to the console
|
||||||
|
"""
|
||||||
|
doc_str = str(document)
|
||||||
|
if base_url is not None:
|
||||||
|
stdout.write(style_func.SUCCESS(doc_str))
|
||||||
|
stdout.write(style_func.SUCCESS(f"{base_url}/documents/{document.pk}"))
|
||||||
|
else:
|
||||||
|
stdout.write(style_func.SUCCESS(f"{doc_str} [{document.pk}]"))
|
||||||
|
stdout.write(f"Suggest {suggestion_type}: {selected}")
|
||||||
|
|
||||||
|
|
||||||
def set_correspondent(
|
def set_correspondent(
|
||||||
sender,
|
sender,
|
||||||
document: Document,
|
document: Document,
|
||||||
@ -63,7 +82,8 @@ def set_correspondent(
|
|||||||
use_first=True,
|
use_first=True,
|
||||||
suggest=False,
|
suggest=False,
|
||||||
base_url=None,
|
base_url=None,
|
||||||
color=False,
|
stdout=None,
|
||||||
|
style_func=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if document.correspondent and not replace:
|
if document.correspondent and not replace:
|
||||||
@ -90,23 +110,14 @@ def set_correspondent(
|
|||||||
|
|
||||||
if selected or replace:
|
if selected or replace:
|
||||||
if suggest:
|
if suggest:
|
||||||
if base_url:
|
_suggestion_printer(
|
||||||
print(
|
stdout,
|
||||||
termcolors.colorize(str(document), fg="green")
|
style_func,
|
||||||
if color
|
"correspondent",
|
||||||
else str(document),
|
document,
|
||||||
)
|
selected,
|
||||||
print(f"{base_url}/documents/{document.pk}")
|
base_url,
|
||||||
else:
|
)
|
||||||
print(
|
|
||||||
(
|
|
||||||
termcolors.colorize(str(document), fg="green")
|
|
||||||
if color
|
|
||||||
else str(document)
|
|
||||||
)
|
|
||||||
+ f" [{document.pk}]",
|
|
||||||
)
|
|
||||||
print(f"Suggest correspondent {selected}")
|
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Assigning correspondent {selected} to {document}",
|
f"Assigning correspondent {selected} to {document}",
|
||||||
@ -126,7 +137,8 @@ def set_document_type(
|
|||||||
use_first=True,
|
use_first=True,
|
||||||
suggest=False,
|
suggest=False,
|
||||||
base_url=None,
|
base_url=None,
|
||||||
color=False,
|
stdout=None,
|
||||||
|
style_func=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if document.document_type and not replace:
|
if document.document_type and not replace:
|
||||||
@ -154,23 +166,14 @@ def set_document_type(
|
|||||||
|
|
||||||
if selected or replace:
|
if selected or replace:
|
||||||
if suggest:
|
if suggest:
|
||||||
if base_url:
|
_suggestion_printer(
|
||||||
print(
|
stdout,
|
||||||
termcolors.colorize(str(document), fg="green")
|
style_func,
|
||||||
if color
|
"document type",
|
||||||
else str(document),
|
document,
|
||||||
)
|
selected,
|
||||||
print(f"{base_url}/documents/{document.pk}")
|
base_url,
|
||||||
else:
|
)
|
||||||
print(
|
|
||||||
(
|
|
||||||
termcolors.colorize(str(document), fg="green")
|
|
||||||
if color
|
|
||||||
else str(document)
|
|
||||||
)
|
|
||||||
+ f" [{document.pk}]",
|
|
||||||
)
|
|
||||||
print(f"Suggest document type {selected}")
|
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Assigning document type {selected} to {document}",
|
f"Assigning document type {selected} to {document}",
|
||||||
@ -189,7 +192,8 @@ def set_tags(
|
|||||||
replace=False,
|
replace=False,
|
||||||
suggest=False,
|
suggest=False,
|
||||||
base_url=None,
|
base_url=None,
|
||||||
color=False,
|
stdout=None,
|
||||||
|
style_func=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if replace:
|
if replace:
|
||||||
@ -212,26 +216,16 @@ def set_tags(
|
|||||||
]
|
]
|
||||||
if not relevant_tags and not extra_tags:
|
if not relevant_tags and not extra_tags:
|
||||||
return
|
return
|
||||||
|
doc_str = style_func.SUCCESS(str(document))
|
||||||
if base_url:
|
if base_url:
|
||||||
print(
|
stdout.write(doc_str)
|
||||||
termcolors.colorize(str(document), fg="green")
|
stdout.write(f"{base_url}/documents/{document.pk}")
|
||||||
if color
|
|
||||||
else str(document),
|
|
||||||
)
|
|
||||||
print(f"{base_url}/documents/{document.pk}")
|
|
||||||
else:
|
else:
|
||||||
print(
|
stdout.write(doc_str + style_func.SUCCESS(f" [{document.pk}]"))
|
||||||
(
|
|
||||||
termcolors.colorize(str(document), fg="green")
|
|
||||||
if color
|
|
||||||
else str(document)
|
|
||||||
)
|
|
||||||
+ f" [{document.pk}]",
|
|
||||||
)
|
|
||||||
if relevant_tags:
|
if relevant_tags:
|
||||||
print("Suggest tags: " + ", ".join([t.name for t in relevant_tags]))
|
stdout.write("Suggest tags: " + ", ".join([t.name for t in relevant_tags]))
|
||||||
if extra_tags:
|
if extra_tags:
|
||||||
print("Extra tags: " + ", ".join([t.name for t in extra_tags]))
|
stdout.write("Extra tags: " + ", ".join([t.name for t in extra_tags]))
|
||||||
else:
|
else:
|
||||||
if not relevant_tags:
|
if not relevant_tags:
|
||||||
return
|
return
|
||||||
@ -254,7 +248,8 @@ def set_storage_path(
|
|||||||
use_first=True,
|
use_first=True,
|
||||||
suggest=False,
|
suggest=False,
|
||||||
base_url=None,
|
base_url=None,
|
||||||
color=False,
|
stdout=None,
|
||||||
|
style_func=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if document.storage_path and not replace:
|
if document.storage_path and not replace:
|
||||||
@ -285,23 +280,14 @@ def set_storage_path(
|
|||||||
|
|
||||||
if selected or replace:
|
if selected or replace:
|
||||||
if suggest:
|
if suggest:
|
||||||
if base_url:
|
_suggestion_printer(
|
||||||
print(
|
stdout,
|
||||||
termcolors.colorize(str(document), fg="green")
|
style_func,
|
||||||
if color
|
"storage directory",
|
||||||
else str(document),
|
document,
|
||||||
)
|
selected,
|
||||||
print(f"{base_url}/documents/{document.pk}")
|
base_url,
|
||||||
else:
|
)
|
||||||
print(
|
|
||||||
(
|
|
||||||
termcolors.colorize(str(document), fg="green")
|
|
||||||
if color
|
|
||||||
else str(document)
|
|
||||||
)
|
|
||||||
+ f" [{document.pk}]",
|
|
||||||
)
|
|
||||||
print(f"Suggest storage directory {selected}")
|
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Assigning storage path {selected} to {document}",
|
f"Assigning storage path {selected} to {document}",
|
||||||
|
@ -246,8 +246,6 @@ class TestBulkDownload(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
self.doc3.title = "Title 2 - Doc 3"
|
self.doc3.title = "Title 2 - Doc 3"
|
||||||
self.doc3.save()
|
self.doc3.save()
|
||||||
print(self.doc3.archive_path)
|
|
||||||
print(self.doc3.archive_filename)
|
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.ENDPOINT,
|
self.ENDPOINT,
|
||||||
|
@ -14,6 +14,7 @@ from documents.barcodes import BarcodePlugin
|
|||||||
from documents.data_models import ConsumableDocument
|
from documents.data_models import ConsumableDocument
|
||||||
from documents.data_models import DocumentMetadataOverrides
|
from documents.data_models import DocumentMetadataOverrides
|
||||||
from documents.data_models import DocumentSource
|
from documents.data_models import DocumentSource
|
||||||
|
from documents.models import Tag
|
||||||
from documents.tests.utils import DirectoriesMixin
|
from documents.tests.utils import DirectoriesMixin
|
||||||
from documents.tests.utils import DocumentConsumeDelayMixin
|
from documents.tests.utils import DocumentConsumeDelayMixin
|
||||||
from documents.tests.utils import DummyProgressManager
|
from documents.tests.utils import DummyProgressManager
|
||||||
@ -741,3 +742,125 @@ class TestBarcodeZxing(TestBarcode):
|
|||||||
@override_settings(CONSUMER_BARCODE_SCANNER="ZXING")
|
@override_settings(CONSUMER_BARCODE_SCANNER="ZXING")
|
||||||
class TestAsnBarcodesZxing(TestAsnBarcode):
|
class TestAsnBarcodesZxing(TestAsnBarcode):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestTagBarcode(DirectoriesMixin, SampleDirMixin, GetReaderPluginMixin, TestCase):
|
||||||
|
@contextmanager
|
||||||
|
def get_reader(self, filepath: Path) -> BarcodePlugin:
|
||||||
|
reader = BarcodePlugin(
|
||||||
|
ConsumableDocument(DocumentSource.ConsumeFolder, original_file=filepath),
|
||||||
|
DocumentMetadataOverrides(),
|
||||||
|
DummyProgressManager(filepath.name, None),
|
||||||
|
self.dirs.scratch_dir,
|
||||||
|
"task-id",
|
||||||
|
)
|
||||||
|
reader.setup()
|
||||||
|
yield reader
|
||||||
|
reader.cleanup()
|
||||||
|
|
||||||
|
@override_settings(CONSUMER_ENABLE_TAG_BARCODE=True)
|
||||||
|
def test_scan_file_without_matching_barcodes(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- PDF containing tag barcodes but none with matching prefix (default "TAG:")
|
||||||
|
WHEN:
|
||||||
|
- File is scanned for barcodes
|
||||||
|
THEN:
|
||||||
|
- No TAG has been created
|
||||||
|
"""
|
||||||
|
test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-custom-prefix.pdf"
|
||||||
|
with self.get_reader(test_file) as reader:
|
||||||
|
reader.run()
|
||||||
|
tags = reader.metadata.tag_ids
|
||||||
|
self.assertEqual(tags, None)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
CONSUMER_ENABLE_TAG_BARCODE=False,
|
||||||
|
CONSUMER_TAG_BARCODE_MAPPING={"CUSTOM-PREFIX-(.*)": "\\g<1>"},
|
||||||
|
)
|
||||||
|
def test_scan_file_with_matching_barcode_but_function_disabled(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- PDF containing a tag barcode with matching custom prefix
|
||||||
|
- The tag barcode functionality is disabled
|
||||||
|
WHEN:
|
||||||
|
- File is scanned for barcodes
|
||||||
|
THEN:
|
||||||
|
- No TAG has been created
|
||||||
|
"""
|
||||||
|
test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-custom-prefix.pdf"
|
||||||
|
with self.get_reader(test_file) as reader:
|
||||||
|
reader.run()
|
||||||
|
tags = reader.metadata.tag_ids
|
||||||
|
self.assertEqual(tags, None)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
CONSUMER_ENABLE_TAG_BARCODE=True,
|
||||||
|
CONSUMER_TAG_BARCODE_MAPPING={"CUSTOM-PREFIX-(.*)": "\\g<1>"},
|
||||||
|
)
|
||||||
|
def test_scan_file_for_tag_custom_prefix(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- PDF containing a tag barcode with custom prefix
|
||||||
|
- The barcode mapping accepts this prefix and removes it from the mapped tag value
|
||||||
|
- The created tag is the non-prefixed values
|
||||||
|
WHEN:
|
||||||
|
- File is scanned for barcodes
|
||||||
|
THEN:
|
||||||
|
- The TAG is located
|
||||||
|
- One TAG has been created
|
||||||
|
"""
|
||||||
|
test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-custom-prefix.pdf"
|
||||||
|
with self.get_reader(test_file) as reader:
|
||||||
|
reader.metadata.tag_ids = [99]
|
||||||
|
reader.run()
|
||||||
|
self.assertEqual(reader.pdf_file, test_file)
|
||||||
|
tags = reader.metadata.tag_ids
|
||||||
|
self.assertEqual(len(tags), 2)
|
||||||
|
self.assertEqual(tags[0], 99)
|
||||||
|
self.assertEqual(Tag.objects.get(name__iexact="00123").pk, tags[1])
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
CONSUMER_ENABLE_TAG_BARCODE=True,
|
||||||
|
CONSUMER_TAG_BARCODE_MAPPING={"ASN(.*)": "\\g<1>"},
|
||||||
|
)
|
||||||
|
def test_scan_file_for_many_custom_tags(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- PDF containing multiple tag barcode with custom prefix
|
||||||
|
- The barcode mapping accepts this prefix and removes it from the mapped tag value
|
||||||
|
- The created tags are the non-prefixed values
|
||||||
|
WHEN:
|
||||||
|
- File is scanned for barcodes
|
||||||
|
THEN:
|
||||||
|
- The TAG is located
|
||||||
|
- File Tags have been created
|
||||||
|
"""
|
||||||
|
test_file = self.BARCODE_SAMPLE_DIR / "split-by-asn-1.pdf"
|
||||||
|
with self.get_reader(test_file) as reader:
|
||||||
|
reader.run()
|
||||||
|
tags = reader.metadata.tag_ids
|
||||||
|
self.assertEqual(len(tags), 5)
|
||||||
|
self.assertEqual(Tag.objects.get(name__iexact="00123").pk, tags[0])
|
||||||
|
self.assertEqual(Tag.objects.get(name__iexact="00124").pk, tags[1])
|
||||||
|
self.assertEqual(Tag.objects.get(name__iexact="00125").pk, tags[2])
|
||||||
|
self.assertEqual(Tag.objects.get(name__iexact="00126").pk, tags[3])
|
||||||
|
self.assertEqual(Tag.objects.get(name__iexact="00127").pk, tags[4])
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
CONSUMER_ENABLE_TAG_BARCODE=True,
|
||||||
|
CONSUMER_TAG_BARCODE_MAPPING={"CUSTOM-PREFIX-(.*)": "\\g<3>"},
|
||||||
|
)
|
||||||
|
def test_scan_file_for_tag_raises_value_error(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Any error occurs during tag barcode processing
|
||||||
|
THEN:
|
||||||
|
- The processing should be skipped and not break the import
|
||||||
|
"""
|
||||||
|
test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-custom-prefix.pdf"
|
||||||
|
with self.get_reader(test_file) as reader:
|
||||||
|
reader.run()
|
||||||
|
# expect error to be caught and logged only
|
||||||
|
tags = reader.metadata.tag_ids
|
||||||
|
self.assertEqual(tags, None)
|
||||||
|
@ -88,10 +88,10 @@ class ConsumerThreadMixin(DocumentConsumeDelayMixin):
|
|||||||
):
|
):
|
||||||
eq = filecmp.cmp(input_doc.original_file, self.sample_file, shallow=False)
|
eq = filecmp.cmp(input_doc.original_file, self.sample_file, shallow=False)
|
||||||
if not eq:
|
if not eq:
|
||||||
print("Consumed an INVALID file.")
|
print("Consumed an INVALID file.") # noqa: T201
|
||||||
raise ConsumerError("Incomplete File READ FAILED")
|
raise ConsumerError("Incomplete File READ FAILED")
|
||||||
else:
|
else:
|
||||||
print("Consumed a perfectly valid file.")
|
print("Consumed a perfectly valid file.") # noqa: T201
|
||||||
|
|
||||||
def slow_write_file(self, target, incomplete=False):
|
def slow_write_file(self, target, incomplete=False):
|
||||||
with open(self.sample_file, "rb") as f:
|
with open(self.sample_file, "rb") as f:
|
||||||
@ -102,11 +102,11 @@ class ConsumerThreadMixin(DocumentConsumeDelayMixin):
|
|||||||
|
|
||||||
with open(target, "wb") as f:
|
with open(target, "wb") as f:
|
||||||
# this will take 2 seconds, since the file is about 20k.
|
# this will take 2 seconds, since the file is about 20k.
|
||||||
print("Start writing file.")
|
print("Start writing file.") # noqa: T201
|
||||||
for b in chunked(1000, pdf_bytes):
|
for b in chunked(1000, pdf_bytes):
|
||||||
f.write(b)
|
f.write(b)
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
print("file completed.")
|
print("file completed.") # noqa: T201
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
|
@ -196,7 +196,7 @@ class TestFuzzyMatchCommand(TestCase):
|
|||||||
self.assertEqual(Document.objects.count(), 3)
|
self.assertEqual(Document.objects.count(), 3)
|
||||||
|
|
||||||
stdout, _ = self.call_command("--delete")
|
stdout, _ = self.call_command("--delete")
|
||||||
print(stdout)
|
|
||||||
lines = [x.strip() for x in stdout.split("\n") if len(x.strip())]
|
lines = [x.strip() for x in stdout.split("\n") if len(x.strip())]
|
||||||
self.assertEqual(len(lines), 3)
|
self.assertEqual(len(lines), 3)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import QuerySet
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from guardian.shortcuts import assign_perm
|
from guardian.shortcuts import assign_perm
|
||||||
from guardian.shortcuts import get_groups_with_perms
|
from guardian.shortcuts import get_groups_with_perms
|
||||||
from guardian.shortcuts import get_users_with_perms
|
from guardian.shortcuts import get_users_with_perms
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from documents import tasks
|
from documents import tasks
|
||||||
from documents.data_models import ConsumableDocument
|
from documents.data_models import ConsumableDocument
|
||||||
from documents.data_models import DocumentSource
|
from documents.data_models import DocumentSource
|
||||||
|
@ -340,7 +340,6 @@ class DummyProgressManager:
|
|||||||
def __init__(self, filename: str, task_id: Optional[str] = None) -> None:
|
def __init__(self, filename: str, task_id: Optional[str] = None) -> None:
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.task_id = task_id
|
self.task_id = task_id
|
||||||
print("hello world")
|
|
||||||
self.payloads = []
|
self.payloads = []
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
@ -2,7 +2,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-02-02 20:17-0800\n"
|
"POT-Creation-Date: 2024-02-07 06:20+0000\n"
|
||||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: English\n"
|
"Language-Team: English\n"
|
||||||
@ -777,15 +777,136 @@ msgstr ""
|
|||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1049
|
#: documents/serialisers.py:1060
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1152
|
#: documents/serialisers.py:1163
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:14
|
||||||
|
msgid "Paperless-ngx sign in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:47
|
||||||
|
msgid "Please sign in."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:50
|
||||||
|
msgid "Your username and password didn't match. Please try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:54
|
||||||
|
msgid "Share link was not found."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:58
|
||||||
|
msgid "Share link has expired."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:61
|
||||||
|
#: documents/templates/socialaccount/signup.html:56
|
||||||
|
msgid "Username"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:62
|
||||||
|
msgid "Password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:72
|
||||||
|
msgid "Sign in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:76
|
||||||
|
msgid "Forgot your password?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/login.html:83
|
||||||
|
msgid "or sign in via"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset.html:15
|
||||||
|
msgid "Paperless-ngx reset password request"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset.html:43
|
||||||
|
msgid ""
|
||||||
|
"Enter your email address below, and we'll email instructions for setting a "
|
||||||
|
"new one."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset.html:46
|
||||||
|
msgid "An error occurred. Please try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset.html:49
|
||||||
|
#: documents/templates/socialaccount/signup.html:57
|
||||||
|
msgid "Email"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset.html:56
|
||||||
|
msgid "Send me instructions!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_done.html:14
|
||||||
|
msgid "Paperless-ngx reset password sent"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_done.html:40
|
||||||
|
msgid "Check your inbox."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_done.html:41
|
||||||
|
msgid ""
|
||||||
|
"We've emailed you instructions for setting your password. You should receive "
|
||||||
|
"the email shortly!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key.html:15
|
||||||
|
msgid "Paperless-ngx reset password confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key.html:44
|
||||||
|
msgid "request a new password reset"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key.html:46
|
||||||
|
msgid "Set a new password."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key.html:50
|
||||||
|
msgid "Passwords did not match or too weak. Try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key.html:53
|
||||||
|
msgid "New Password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key.html:54
|
||||||
|
msgid "Confirm Password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key.html:65
|
||||||
|
msgid "Change my password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key_done.html:14
|
||||||
|
msgid "Paperless-ngx reset password complete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key_done.html:40
|
||||||
|
msgid "Password reset complete."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/templates/account/password_reset_from_key_done.html:42
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Your new password has been set. You can now <a href=\"%(login_url)s\">log "
|
||||||
|
"in</a>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/index.html:79
|
#: documents/templates/index.html:79
|
||||||
msgid "Paperless-ngx is loading..."
|
msgid "Paperless-ngx is loading..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -798,131 +919,40 @@ msgstr ""
|
|||||||
msgid "Here's a link to the docs."
|
msgid "Here's a link to the docs."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/registration/logged_out.html:14
|
#: documents/templates/socialaccount/authentication_error.html:15
|
||||||
msgid "Paperless-ngx signed out"
|
#: documents/templates/socialaccount/login.html:15
|
||||||
|
msgid "Paperless-ngx social account sign in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/registration/logged_out.html:40
|
#: documents/templates/socialaccount/authentication_error.html:43
|
||||||
msgid "You have been successfully logged out. Bye!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/logged_out.html:41
|
|
||||||
msgid "Sign in again"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:14
|
|
||||||
msgid "Paperless-ngx sign in"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:41
|
|
||||||
msgid "Please sign in."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:44
|
|
||||||
msgid "Your username and password didn't match. Please try again."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:48
|
|
||||||
msgid "Share link was not found."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:52
|
|
||||||
msgid "Share link has expired."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:55
|
|
||||||
msgid "Username"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:56
|
|
||||||
msgid "Password"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:66
|
|
||||||
msgid "Sign in"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/login.html:70
|
|
||||||
msgid "Forgot your password?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_complete.html:14
|
|
||||||
msgid "Paperless-ngx reset password complete"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_complete.html:40
|
|
||||||
msgid "Password reset complete."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_complete.html:42
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Your new password has been set. You can now <a href=\"%(login_url)s\">log "
|
"An error occurred while attempting to login via your social network account. "
|
||||||
"in</a>"
|
"Back to the <a href=\"%(login_url)s\">login page</a>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_confirm.html:14
|
#: documents/templates/socialaccount/login.html:44
|
||||||
msgid "Paperless-ngx reset password confirmation"
|
#, python-format
|
||||||
|
msgid "You are about to connect a new third-party account from %(provider)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_confirm.html:42
|
#: documents/templates/socialaccount/login.html:47
|
||||||
msgid "Set a new password."
|
msgid "Continue"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_confirm.html:46
|
#: documents/templates/socialaccount/signup.html:14
|
||||||
msgid "Passwords did not match or too weak. Try again."
|
msgid "Paperless-ngx social account sign up"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_confirm.html:49
|
#: documents/templates/socialaccount/signup.html:53
|
||||||
msgid "New Password"
|
#, python-format
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_confirm.html:50
|
|
||||||
msgid "Confirm Password"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_confirm.html:61
|
|
||||||
msgid "Change my password"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_confirm.html:65
|
|
||||||
msgid "request a new password reset"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_done.html:14
|
|
||||||
msgid "Paperless-ngx reset password sent"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_done.html:40
|
|
||||||
msgid "Check your inbox."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_done.html:41
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"We've emailed you instructions for setting your password. You should receive "
|
"You are about to use your %(provider_name)s account to login to\n"
|
||||||
"the email shortly!"
|
"%(site_name)s. As a final step, please complete the following form:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_form.html:14
|
#: documents/templates/socialaccount/signup.html:72
|
||||||
msgid "Paperless-ngx reset password request"
|
msgid "Sign up"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_form.html:41
|
|
||||||
msgid ""
|
|
||||||
"Enter your email address below, and we'll email instructions for setting a "
|
|
||||||
"new one."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_form.html:44
|
|
||||||
msgid "An error occurred. Please try again."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_form.html:47
|
|
||||||
msgid "Email"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/templates/registration/password_reset_form.html:54
|
|
||||||
msgid "Send me instructions!"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/validators.py:17
|
#: documents/validators.py:17
|
||||||
@ -1088,135 +1118,135 @@ msgstr ""
|
|||||||
msgid "paperless application settings"
|
msgid "paperless application settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:617
|
#: paperless/settings.py:658
|
||||||
msgid "English (US)"
|
msgid "English (US)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:618
|
#: paperless/settings.py:659
|
||||||
msgid "Arabic"
|
msgid "Arabic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:619
|
#: paperless/settings.py:660
|
||||||
msgid "Afrikaans"
|
msgid "Afrikaans"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:620
|
#: paperless/settings.py:661
|
||||||
msgid "Belarusian"
|
msgid "Belarusian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:621
|
#: paperless/settings.py:662
|
||||||
msgid "Bulgarian"
|
msgid "Bulgarian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:622
|
#: paperless/settings.py:663
|
||||||
msgid "Catalan"
|
msgid "Catalan"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:623
|
#: paperless/settings.py:664
|
||||||
msgid "Czech"
|
msgid "Czech"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:624
|
#: paperless/settings.py:665
|
||||||
msgid "Danish"
|
msgid "Danish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:625
|
#: paperless/settings.py:666
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:626
|
#: paperless/settings.py:667
|
||||||
msgid "Greek"
|
msgid "Greek"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:627
|
#: paperless/settings.py:668
|
||||||
msgid "English (GB)"
|
msgid "English (GB)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:628
|
#: paperless/settings.py:669
|
||||||
msgid "Spanish"
|
msgid "Spanish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:629
|
#: paperless/settings.py:670
|
||||||
msgid "Finnish"
|
msgid "Finnish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:630
|
#: paperless/settings.py:671
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:631
|
#: paperless/settings.py:672
|
||||||
msgid "Hungarian"
|
msgid "Hungarian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:632
|
#: paperless/settings.py:673
|
||||||
msgid "Italian"
|
msgid "Italian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:633
|
#: paperless/settings.py:674
|
||||||
msgid "Japanese"
|
msgid "Japanese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:634
|
#: paperless/settings.py:675
|
||||||
msgid "Luxembourgish"
|
msgid "Luxembourgish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:635
|
#: paperless/settings.py:676
|
||||||
msgid "Norwegian"
|
msgid "Norwegian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:636
|
#: paperless/settings.py:677
|
||||||
msgid "Dutch"
|
msgid "Dutch"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:637
|
#: paperless/settings.py:678
|
||||||
msgid "Polish"
|
msgid "Polish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:638
|
#: paperless/settings.py:679
|
||||||
msgid "Portuguese (Brazil)"
|
msgid "Portuguese (Brazil)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:639
|
#: paperless/settings.py:680
|
||||||
msgid "Portuguese"
|
msgid "Portuguese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:640
|
#: paperless/settings.py:681
|
||||||
msgid "Romanian"
|
msgid "Romanian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:641
|
#: paperless/settings.py:682
|
||||||
msgid "Russian"
|
msgid "Russian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:642
|
#: paperless/settings.py:683
|
||||||
msgid "Slovak"
|
msgid "Slovak"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:643
|
#: paperless/settings.py:684
|
||||||
msgid "Slovenian"
|
msgid "Slovenian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:644
|
#: paperless/settings.py:685
|
||||||
msgid "Serbian"
|
msgid "Serbian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:645
|
#: paperless/settings.py:686
|
||||||
msgid "Swedish"
|
msgid "Swedish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:646
|
#: paperless/settings.py:687
|
||||||
msgid "Turkish"
|
msgid "Turkish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:647
|
#: paperless/settings.py:688
|
||||||
msgid "Ukrainian"
|
msgid "Ukrainian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:648
|
#: paperless/settings.py:689
|
||||||
msgid "Chinese Simplified"
|
msgid "Chinese Simplified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/urls.py:214
|
#: paperless/urls.py:224
|
||||||
msgid "Paperless-ngx administration"
|
msgid "Paperless-ngx administration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -27,4 +27,4 @@ class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
|
|||||||
|
|
||||||
def populate_user(self, request, sociallogin, data):
|
def populate_user(self, request, sociallogin, data):
|
||||||
# TODO: If default global permissions are implemented, should also be here
|
# TODO: If default global permissions are implemented, should also be here
|
||||||
return super().populate_user(request, sociallogin, data)
|
return super().populate_user(request, sociallogin, data) # pragma: no cover
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.contrib.auth.middleware import PersistentRemoteUserMiddleware
|
from django.contrib.auth.middleware import PersistentRemoteUserMiddleware
|
||||||
@ -6,6 +8,8 @@ from django.http import HttpRequest
|
|||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
from rest_framework import authentication
|
from rest_framework import authentication
|
||||||
|
|
||||||
|
logger = logging.getLogger("paperless.auth")
|
||||||
|
|
||||||
|
|
||||||
class AutoLoginMiddleware(MiddlewareMixin):
|
class AutoLoginMiddleware(MiddlewareMixin):
|
||||||
def process_request(self, request: HttpRequest):
|
def process_request(self, request: HttpRequest):
|
||||||
@ -35,7 +39,7 @@ class AngularApiAuthenticationOverride(authentication.BaseAuthentication):
|
|||||||
and request.headers["Referer"].startswith("http://localhost:4200/")
|
and request.headers["Referer"].startswith("http://localhost:4200/")
|
||||||
):
|
):
|
||||||
user = User.objects.filter(is_staff=True).first()
|
user = User.objects.filter(is_staff=True).first()
|
||||||
print(f"Auto-Login with user {user}")
|
logger.debug(f"Auto-Login with user {user}")
|
||||||
return (user, None)
|
return (user, None)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -796,6 +796,11 @@ CACHES = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if DEBUG and os.getenv("PAPERLESS_CACHE_BACKEND") is None:
|
||||||
|
CACHES["default"][
|
||||||
|
"BACKEND"
|
||||||
|
] = "django.core.cache.backends.locmem.LocMemCache" # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
def default_threads_per_worker(task_workers) -> int:
|
def default_threads_per_worker(task_workers) -> int:
|
||||||
# always leave one core open
|
# always leave one core open
|
||||||
@ -878,6 +883,19 @@ CONSUMER_BARCODE_UPSCALE: Final[float] = __get_float(
|
|||||||
|
|
||||||
CONSUMER_BARCODE_DPI: Final[int] = __get_int("PAPERLESS_CONSUMER_BARCODE_DPI", 300)
|
CONSUMER_BARCODE_DPI: Final[int] = __get_int("PAPERLESS_CONSUMER_BARCODE_DPI", 300)
|
||||||
|
|
||||||
|
CONSUMER_ENABLE_TAG_BARCODE: Final[bool] = __get_boolean(
|
||||||
|
"PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE",
|
||||||
|
)
|
||||||
|
|
||||||
|
CONSUMER_TAG_BARCODE_MAPPING = dict(
|
||||||
|
json.loads(
|
||||||
|
os.getenv(
|
||||||
|
"PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING",
|
||||||
|
'{"TAG:(.*)": "\\\\g<1>"}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED: Final[bool] = __get_boolean(
|
CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED: Final[bool] = __get_boolean(
|
||||||
"PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED",
|
"PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED",
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user