Compare commits

..

No commits in common. "485dad01b70823f2e562ee0da14274785c53d2ea" and "89e5c08a1fe4ca0b7641ae8fbd5554502199ae40" have entirely different histories.

59 changed files with 1118 additions and 1666 deletions

View File

@ -3,7 +3,7 @@
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml", "dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
"service": "paperless-development", "service": "paperless-development",
"workspaceFolder": "/usr/src/paperless/paperless-ngx", "workspaceFolder": "/usr/src/paperless/paperless-ngx",
"postCreateCommand": "/bin/bash -c 'uv sync --group dev && uv run pre-commit install'", "postCreateCommand": "/bin/bash -c uv sync --dev && uv run pre-commit install",
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [

View File

@ -271,6 +271,7 @@ jobs:
name: playwright-report-${{ matrix.shard-index }} name: playwright-report-${{ matrix.shard-index }}
path: src-ui/playwright-report path: src-ui/playwright-report
retention-days: 7 retention-days: 7
if-no-files-found: error
- -
name: Upload frontend test results name: Upload frontend test results
if: always() if: always()
@ -279,6 +280,7 @@ jobs:
name: junit-report-${{ matrix.shard-index }} name: junit-report-${{ matrix.shard-index }}
path: src-ui/junit-report-${{ matrix.shard-index }}.xml path: src-ui/junit-report-${{ matrix.shard-index }}.xml
retention-days: 7 retention-days: 7
if-no-files-found: error
tests-coverage-upload: tests-coverage-upload:
name: "Upload to Codecov" name: "Upload to Codecov"

View File

@ -75,7 +75,7 @@ first-time setup.
4. Install the Python dependencies: 4. Install the Python dependencies:
```bash ```bash
$ uv sync --group dev $ uv sync --dev
``` ```
5. Install pre-commit hooks: 5. Install pre-commit hooks:

View File

@ -209,18 +209,37 @@ lint.per-file-ignores."src/documents/management/commands/document_consumer.py" =
lint.per-file-ignores."src/documents/management/commands/document_exporter.py" = [ lint.per-file-ignores."src/documents/management/commands/document_exporter.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/migrations/0012_auto_20160305_0040.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/migrations/0014_document_checksum.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/migrations/1003_mime_types.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/migrations/1012_fix_archive_files.py" = [ lint.per-file-ignores."src/documents/migrations/1012_fix_archive_files.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/models.py" = [ lint.per-file-ignores."src/documents/models.py" = [
"PTH",
"SIM115", "SIM115",
] ] # TODO PTH Enable & remove
lint.per-file-ignores."src/documents/parsers.py" = [ lint.per-file-ignores."src/documents/parsers.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/signals/handlers.py" = [ lint.per-file-ignores."src/documents/signals/handlers.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/tasks.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_api_app_config.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_classifier.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_consumer.py" = [ lint.per-file-ignores."src/documents/tests/test_consumer.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
@ -236,6 +255,9 @@ lint.per-file-ignores."src/documents/tests/test_management_consumer.py" = [
lint.per-file-ignores."src/documents/tests/test_management_exporter.py" = [ lint.per-file-ignores."src/documents/tests/test_management_exporter.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_management_thumbnails.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_migration_archive_files.py" = [ lint.per-file-ignores."src/documents/tests/test_migration_archive_files.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
@ -248,6 +270,12 @@ lint.per-file-ignores."src/documents/tests/test_migration_mime_type.py" = [
lint.per-file-ignores."src/documents/tests/test_sanity_check.py" = [ lint.per-file-ignores."src/documents/tests/test_sanity_check.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_tasks.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_views.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/views.py" = [ lint.per-file-ignores."src/documents/views.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
@ -257,16 +285,34 @@ lint.per-file-ignores."src/paperless/checks.py" = [
lint.per-file-ignores."src/paperless/settings.py" = [ lint.per-file-ignores."src/paperless/settings.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/paperless/tests/test_checks.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless/urls.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless/views.py" = [ lint.per-file-ignores."src/paperless/views.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/paperless_mail/mail.py" = [ lint.per-file-ignores."src/paperless_mail/mail.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/paperless_mail/preprocessor.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_tesseract/parsers.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [ lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [
"PTH", "PTH",
"RUF001", "RUF001",
] # TODO PTH Enable & remove ] # TODO PTH Enable & remove
lint.per-file-ignores."src/paperless_tika/tests/test_live_tika.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_tika/tests/test_tika_parser.py" = [
"PTH",
] # TODO Enable & remove
lint.isort.force-single-line = true lint.isort.force-single-line = true
[tool.pytest.ini_options] [tool.pytest.ini_options]

View File

@ -83,17 +83,10 @@ test('date filtering', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR3, { notFound: 'fallback' }) await page.routeFromHAR(REQUESTS_HAR3, { notFound: 'fallback' })
await page.goto('/documents') await page.goto('/documents')
await page.getByRole('button', { name: 'Dates' }).click() await page.getByRole('button', { name: 'Dates' }).click()
await page.locator('.ng-arrow-wrapper').first().click() await page.getByRole('menuitem', { name: 'Within 3 months' }).first().click()
await page.getByRole('option', { name: 'Within 3 months' }).click()
await expect(page.locator('pngx-document-list')).toHaveText(/one document/i) await expect(page.locator('pngx-document-list')).toHaveText(/one document/i)
await page await page.getByRole('menuitem', { name: 'Within 3 months' }).first().click()
.getByRole('menuitem', { name: 'Relative dates' }) await page.getByLabel('Datesselected').getByRole('button').first().click()
.locator('span')
.first()
.click()
await page.getByRole('option', { name: 'Within 3 months' }).click()
await page.getByLabel('Dates selected').locator('button').first().click()
await page.getByLabel('Dates selected').locator('button').first().click()
await page.getByRole('combobox', { name: 'Select month' }).selectOption('12') await page.getByRole('combobox', { name: 'Select month' }).selectOption('12')
await page.getByRole('combobox', { name: 'Select year' }).selectOption('2022') await page.getByRole('combobox', { name: 'Select year' }).selectOption('2022')
await page.getByText('11', { exact: true }).click() await page.getByText('11', { exact: true }).click()

View File

@ -1120,7 +1120,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">193</context> <context context-type="linenumber">173</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8508424367627989968" datatype="html"> <trans-unit id="8508424367627989968" datatype="html">
@ -1198,7 +1198,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">106</context> <context context-type="linenumber">105</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context> <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
@ -1284,19 +1284,19 @@
</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">201</context> <context context-type="linenumber">200</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">220</context> <context context-type="linenumber">219</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">287</context> <context context-type="linenumber">286</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">306</context> <context context-type="linenumber">305</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>
@ -1319,19 +1319,19 @@
</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">209</context> <context context-type="linenumber">208</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">228</context> <context context-type="linenumber">227</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">295</context> <context context-type="linenumber">294</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">314</context> <context context-type="linenumber">313</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>
@ -1357,11 +1357,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">234</context> <context context-type="linenumber">233</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">320</context> <context context-type="linenumber">319</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>
@ -1732,11 +1732,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">8</context> <context context-type="linenumber">11</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">88</context> <context context-type="linenumber">87</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@ -3246,25 +3246,18 @@
<context context-type="linenumber">24</context> <context context-type="linenumber">24</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2710430925353472741" datatype="html">
<source>Try to include archive version in merge for non-PDF files</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="5612366187076076264" datatype="html"> <trans-unit id="5612366187076076264" datatype="html">
<source>Delete original documents after successful merge</source> <source>Delete original documents after successful merge</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
<context context-type="linenumber">36</context> <context context-type="linenumber">32</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5138283234724909648" datatype="html"> <trans-unit id="5138283234724909648" datatype="html">
<source>Note that only PDFs will be included.</source> <source>Note that only PDFs will be included.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">34</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8157388568390631653" datatype="html"> <trans-unit id="8157388568390631653" datatype="html">
@ -3351,7 +3344,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">52</context> <context context-type="linenumber">50</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
@ -3359,16 +3352,12 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">128</context> <context context-type="linenumber">126</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">152</context> <context context-type="linenumber">152</context>
</context-group> </context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">103</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context> <context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">21</context>
@ -3382,7 +3371,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">53</context> <context context-type="linenumber">51</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
@ -3390,7 +3379,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">129</context> <context context-type="linenumber">127</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
@ -3513,8 +3502,8 @@
<context context-type="linenumber">168</context> <context context-type="linenumber">168</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6312759212949884929" datatype="html"> <trans-unit id="6052766076365105714" datatype="html">
<source>Relative dates</source> <source>now</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">25</context> <context context-type="linenumber">25</context>
@ -3524,26 +3513,15 @@
<context context-type="linenumber">101</context> <context context-type="linenumber">101</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6052766076365105714" datatype="html">
<source>now</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">29</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">105</context>
</context-group>
</trans-unit>
<trans-unit id="5203279511751768967" datatype="html"> <trans-unit id="5203279511751768967" datatype="html">
<source>From</source> <source>From</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">42</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">120</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1640609344969975994" datatype="html"> <trans-unit id="1640609344969975994" datatype="html">
@ -3561,11 +3539,11 @@
<source>Added</source> <source>Added</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">84</context> <context context-type="linenumber">86</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">84</context> <context context-type="linenumber">83</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@ -3584,53 +3562,28 @@
<source>Within 1 week</source> <source>Within 1 week</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="123064370501514576" datatype="html"> <trans-unit id="123064370501514576" datatype="html">
<source>Within 1 month</source> <source>Within 1 month</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">78</context> <context context-type="linenumber">72</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1027161426440526546" datatype="html"> <trans-unit id="1027161426440526546" datatype="html">
<source>Within 3 months</source> <source>Within 3 months</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">83</context> <context context-type="linenumber">77</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="226779700214642230" datatype="html"> <trans-unit id="226779700214642230" datatype="html">
<source>Within 1 year</source> <source>Within 1 year</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">88</context> <context context-type="linenumber">82</context>
</context-group>
</trans-unit>
<trans-unit id="8462417627724236320" datatype="html">
<source>This year</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">93</context>
</context-group>
</trans-unit>
<trans-unit id="842657237693374355" datatype="html">
<source>This month</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">98</context>
</context-group>
</trans-unit>
<trans-unit id="4498682414491138092" datatype="html">
<source>Yesterday</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
<context context-type="linenumber">108</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">29</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8743659855412792665" datatype="html"> <trans-unit id="8743659855412792665" datatype="html">
@ -4443,7 +4396,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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">130</context> <context context-type="linenumber">129</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>
@ -4834,227 +4787,227 @@
<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">195</context> <context context-type="linenumber">194</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">197</context> <context context-type="linenumber">196</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">216</context> <context context-type="linenumber">215</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6236311670364192011" datatype="html"> <trans-unit id="6236311670364192011" datatype="html">
<source>Remove tags</source> <source>Remove 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">243</context> <context context-type="linenumber">242</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7890599006071681081" datatype="html"> <trans-unit id="7890599006071681081" datatype="html">
<source>Remove all</source> <source>Remove all</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">244</context> <context context-type="linenumber">243</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">250</context> <context context-type="linenumber">249</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">256</context> <context context-type="linenumber">255</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">262</context> <context context-type="linenumber">261</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">268</context> <context context-type="linenumber">267</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">275</context> <context context-type="linenumber">274</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">281</context> <context context-type="linenumber">280</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8636414563726517994" datatype="html"> <trans-unit id="8636414563726517994" datatype="html">
<source>Remove correspondents</source> <source>Remove correspondents</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">249</context> <context context-type="linenumber">248</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5305293055593064952" datatype="html"> <trans-unit id="5305293055593064952" datatype="html">
<source>Remove document types</source> <source>Remove document types</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">255</context> <context context-type="linenumber">254</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2400388879708187" datatype="html"> <trans-unit id="2400388879708187" datatype="html">
<source>Remove storage paths</source> <source>Remove storage paths</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">261</context> <context context-type="linenumber">260</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4324304327041955720" datatype="html"> <trans-unit id="4324304327041955720" datatype="html">
<source>Remove custom fields</source> <source>Remove 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">267</context> <context context-type="linenumber">266</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8367536502602515064" datatype="html"> <trans-unit id="8367536502602515064" datatype="html">
<source>Remove owners</source> <source>Remove owners</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">274</context> <context context-type="linenumber">273</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3393772184866313281" datatype="html"> <trans-unit id="3393772184866313281" datatype="html">
<source>Remove permissions</source> <source>Remove 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">280</context> <context context-type="linenumber">279</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3145629643370481114" datatype="html"> <trans-unit id="3145629643370481114" datatype="html">
<source>View permissions</source> <source>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">283</context> <context context-type="linenumber">282</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1946660694635960249" datatype="html"> <trans-unit id="1946660694635960249" datatype="html">
<source>Edit permissions</source> <source>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">302</context> <context context-type="linenumber">301</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8987736563240025468" datatype="html"> <trans-unit id="8987736563240025468" datatype="html">
<source>Email subject</source> <source>Email subject</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">330</context> <context context-type="linenumber">329</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8239445959209739142" datatype="html"> <trans-unit id="8239445959209739142" datatype="html">
<source>Email body</source> <source>Email body</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">331</context> <context context-type="linenumber">330</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1222152280703048012" datatype="html"> <trans-unit id="1222152280703048012" datatype="html">
<source>Email recipients</source> <source>Email recipients</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">332</context> <context context-type="linenumber">331</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7916910101279824329" datatype="html"> <trans-unit id="7916910101279824329" datatype="html">
<source>Attach document</source> <source>Attach document</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">333</context> <context context-type="linenumber">332</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5028001922785731600" datatype="html"> <trans-unit id="5028001922785731600" datatype="html">
<source>Webhook url</source> <source>Webhook url</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">341</context> <context context-type="linenumber">340</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7491983459027245019" datatype="html"> <trans-unit id="7491983459027245019" datatype="html">
<source>Use parameters for webhook body</source> <source>Use parameters for webhook body</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">343</context> <context context-type="linenumber">342</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4078214298308732810" datatype="html"> <trans-unit id="4078214298308732810" datatype="html">
<source>Send webhook payload as JSON</source> <source>Send webhook payload as JSON</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">344</context> <context context-type="linenumber">343</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6806149889743731985" datatype="html"> <trans-unit id="6806149889743731985" datatype="html">
<source>Webhook params</source> <source>Webhook params</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">347</context> <context context-type="linenumber">346</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7089924379374330" datatype="html"> <trans-unit id="7089924379374330" datatype="html">
<source>Webhook body</source> <source>Webhook body</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">349</context> <context context-type="linenumber">348</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3829826512656746316" datatype="html"> <trans-unit id="3829826512656746316" datatype="html">
<source>Webhook headers</source> <source>Webhook headers</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">351</context> <context context-type="linenumber">350</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2114525789021600887" datatype="html"> <trans-unit id="2114525789021600887" datatype="html">
<source>Include document</source> <source>Include document</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">352</context> <context context-type="linenumber">351</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4626030417479279989" datatype="html"> <trans-unit id="4626030417479279989" datatype="html">
<source>Consume Folder</source> <source>Consume Folder</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">65</context> <context context-type="linenumber">64</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="526966086395145275" datatype="html"> <trans-unit id="526966086395145275" datatype="html">
<source>API Upload</source> <source>API Upload</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">69</context> <context context-type="linenumber">68</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7502272564743467653" datatype="html"> <trans-unit id="7502272564743467653" datatype="html">
<source>Mail Fetch</source> <source>Mail Fetch</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">72</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="235571817610183244" datatype="html"> <trans-unit id="235571817610183244" datatype="html">
<source>Web UI</source> <source>Web UI</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">77</context> <context context-type="linenumber">76</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3553216189604488439" datatype="html"> <trans-unit id="3553216189604488439" datatype="html">
<source>Modified</source> <source>Modified</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">92</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/data/document.ts</context> <context context-type="sourcefile">src/app/data/document.ts</context>
@ -5065,70 +5018,70 @@
<source>Custom Field</source> <source>Custom Field</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">96</context> <context context-type="linenumber">95</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8696908693776094667" datatype="html"> <trans-unit id="8696908693776094667" datatype="html">
<source>Consumption Started</source> <source>Consumption Started</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">102</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7858311467093621703" datatype="html"> <trans-unit id="7858311467093621703" datatype="html">
<source>Document Added</source> <source>Document Added</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">107</context> <context context-type="linenumber">106</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7955486237346046731" datatype="html"> <trans-unit id="7955486237346046731" datatype="html">
<source>Document Updated</source> <source>Document Updated</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">111</context> <context context-type="linenumber">110</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9172233176401579786" datatype="html"> <trans-unit id="9172233176401579786" datatype="html">
<source>Scheduled</source> <source>Scheduled</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">115</context> <context context-type="linenumber">114</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5502398334173581061" datatype="html"> <trans-unit id="5502398334173581061" datatype="html">
<source>Assignment</source> <source>Assignment</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">122</context> <context context-type="linenumber">121</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6234812824772766804" datatype="html"> <trans-unit id="6234812824772766804" datatype="html">
<source>Removal</source> <source>Removal</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">126</context> <context context-type="linenumber">125</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4206419737792796794" datatype="html"> <trans-unit id="4206419737792796794" datatype="html">
<source>Webhook</source> <source>Webhook</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">134</context> <context context-type="linenumber">133</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3138206142174978019" datatype="html"> <trans-unit id="3138206142174978019" datatype="html">
<source>Create new workflow</source> <source>Create new workflow</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">231</context> <context context-type="linenumber">229</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5996779210524133604" datatype="html"> <trans-unit id="5996779210524133604" datatype="html">
<source>Edit workflow</source> <source>Edit workflow</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.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">235</context> <context context-type="linenumber">233</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7376342558017986274" datatype="html"> <trans-unit id="7376342558017986274" datatype="html">
@ -6555,7 +6508,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">180</context> <context context-type="linenumber">160</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context> <context context-type="sourcefile">src/app/data/document.ts</context>
@ -7183,7 +7136,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">188</context> <context context-type="linenumber">168</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6475890479659129881" datatype="html"> <trans-unit id="6475890479659129881" datatype="html">
@ -7478,21 +7431,21 @@
<source>Merged document will be queued for consumption.</source> <source>Merged document will be queued for consumption.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">866</context> <context context-type="linenumber">863</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="476913782630693351" datatype="html"> <trans-unit id="476913782630693351" datatype="html">
<source>Custom fields updated.</source> <source>Custom fields updated.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">888</context> <context context-type="linenumber">885</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3873496751167944011" datatype="html"> <trans-unit id="3873496751167944011" datatype="html">
<source>Error updating custom fields.</source> <source>Error updating custom fields.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">897</context> <context context-type="linenumber">894</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6307402210351946694" datatype="html"> <trans-unit id="6307402210351946694" datatype="html">
@ -7760,7 +7713,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">112</context> <context context-type="linenumber">111</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1559883523769732271" datatype="html"> <trans-unit id="1559883523769732271" datatype="html">
@ -7785,7 +7738,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">185</context> <context context-type="linenumber">165</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context> <context context-type="sourcefile">src/app/data/document.ts</context>
@ -7981,154 +7934,154 @@
<source>Title &amp; content</source> <source>Title &amp; content</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">183</context> <context context-type="linenumber">163</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7408932238599462499" datatype="html"> <trans-unit id="7408932238599462499" datatype="html">
<source>File type</source> <source>File type</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">190</context> <context context-type="linenumber">170</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2649431021108393503" datatype="html"> <trans-unit id="2649431021108393503" datatype="html">
<source>More like</source> <source>More like</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">199</context> <context context-type="linenumber">179</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3697582909018473071" datatype="html"> <trans-unit id="3697582909018473071" datatype="html">
<source>equals</source> <source>equals</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">205</context> <context context-type="linenumber">185</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5325481293405718739" datatype="html"> <trans-unit id="5325481293405718739" datatype="html">
<source>is empty</source> <source>is empty</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">209</context> <context context-type="linenumber">189</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6166785695326182482" datatype="html"> <trans-unit id="6166785695326182482" datatype="html">
<source>is not empty</source> <source>is not empty</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">213</context> <context context-type="linenumber">193</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4686622206659266699" datatype="html"> <trans-unit id="4686622206659266699" datatype="html">
<source>greater than</source> <source>greater than</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">217</context> <context context-type="linenumber">197</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8014012170270529279" datatype="html"> <trans-unit id="8014012170270529279" datatype="html">
<source>less than</source> <source>less than</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">221</context> <context context-type="linenumber">201</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5195932016807797291" datatype="html"> <trans-unit id="5195932016807797291" datatype="html">
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) =&gt; c.id == +rule.value)?.name"/></source> <source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) =&gt; c.id == +rule.value)?.name"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">253,255</context> <context context-type="linenumber">233,235</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8170755470576301659" datatype="html"> <trans-unit id="8170755470576301659" datatype="html">
<source>Without correspondent</source> <source>Without correspondent</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">257</context> <context context-type="linenumber">237</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="317796810569008208" datatype="html"> <trans-unit id="317796810569008208" datatype="html">
<source>Document type: <x id="PH" equiv-text="this.documentTypes.find((dt) =&gt; dt.id == +rule.value)?.name"/></source> <source>Document type: <x id="PH" equiv-text="this.documentTypes.find((dt) =&gt; dt.id == +rule.value)?.name"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">263,265</context> <context context-type="linenumber">243,245</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4362173610367509215" datatype="html"> <trans-unit id="4362173610367509215" datatype="html">
<source>Without document type</source> <source>Without document type</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">267</context> <context context-type="linenumber">247</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="232202047340644471" datatype="html"> <trans-unit id="232202047340644471" datatype="html">
<source>Storage path: <x id="PH" equiv-text="this.storagePaths.find((sp) =&gt; sp.id == +rule.value)?.name"/></source> <source>Storage path: <x id="PH" equiv-text="this.storagePaths.find((sp) =&gt; sp.id == +rule.value)?.name"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">273,275</context> <context context-type="linenumber">253,255</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1562820715074533164" datatype="html"> <trans-unit id="1562820715074533164" datatype="html">
<source>Without storage path</source> <source>Without storage path</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">277</context> <context context-type="linenumber">257</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8180755793012580465" datatype="html"> <trans-unit id="8180755793012580465" datatype="html">
<source>Tag: <x id="PH" equiv-text="this.tags.find((t) =&gt; t.id == +rule.value)?.name"/></source> <source>Tag: <x id="PH" equiv-text="this.tags.find((t) =&gt; t.id == +rule.value)?.name"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">281,283</context> <context context-type="linenumber">261,263</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6494566478302448576" datatype="html"> <trans-unit id="6494566478302448576" datatype="html">
<source>Without any tag</source> <source>Without any tag</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">287</context> <context context-type="linenumber">267</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8644099678903817943" datatype="html"> <trans-unit id="8644099678903817943" datatype="html">
<source>Custom fields query</source> <source>Custom fields query</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">291</context> <context context-type="linenumber">271</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6523384805359286307" datatype="html"> <trans-unit id="6523384805359286307" datatype="html">
<source>Title: <x id="PH" equiv-text="rule.value"/></source> <source>Title: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">294</context> <context context-type="linenumber">274</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1872523635812236432" datatype="html"> <trans-unit id="1872523635812236432" datatype="html">
<source>ASN: <x id="PH" equiv-text="rule.value"/></source> <source>ASN: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">297</context> <context context-type="linenumber">277</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="102674688969746976" datatype="html"> <trans-unit id="102674688969746976" datatype="html">
<source>Owner: <x id="PH" equiv-text="rule.value"/></source> <source>Owner: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">300</context> <context context-type="linenumber">280</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3550877650686009106" datatype="html"> <trans-unit id="3550877650686009106" datatype="html">
<source>Owner not in: <x id="PH" equiv-text="rule.value"/></source> <source>Owner not in: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">303</context> <context context-type="linenumber">283</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1082034558646673343" datatype="html"> <trans-unit id="1082034558646673343" datatype="html">
<source>Without an owner</source> <source>Without an owner</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">306</context> <context context-type="linenumber">286</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7210076240260527720" datatype="html"> <trans-unit id="7210076240260527720" datatype="html">
@ -9462,6 +9415,13 @@
<context context-type="linenumber">25</context> <context context-type="linenumber">25</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4498682414491138092" datatype="html">
<source>Yesterday</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="5601594741748068208" datatype="html"> <trans-unit id="5601594741748068208" datatype="html">
<source>%s days ago</source> <source>%s days ago</source>
<context-group purpose="location"> <context-group purpose="location">

View File

@ -28,16 +28,10 @@
</select> </select>
</div> </div>
<div class="form-check form-switch mt-4"> <div class="form-check form-switch mt-4">
<input class="form-check-input" type="checkbox" role="switch" id="archiveFallbackSwitch" [(ngModel)]="archiveFallback">
<label class="form-check-label" for="archiveFallbackSwitch" i18n>Try to include archive version in merge for non-PDF files</label>
</div>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalsSwitch" [(ngModel)]="deleteOriginals" [disabled]="!userOwnsAllDocuments"> <input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalsSwitch" [(ngModel)]="deleteOriginals" [disabled]="!userOwnsAllDocuments">
<label class="form-check-label" for="deleteOriginalsSwitch" i18n>Delete original documents after successful merge</label> <label class="form-check-label" for="deleteOriginalsSwitch" i18n>Delete original documents after successful merge</label>
</div> </div>
@if (!archiveFallback) { <p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be included.</p>
<p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be included.</p>
}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled"> <button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">

View File

@ -29,7 +29,6 @@ export class MergeConfirmDialogComponent
implements OnInit implements OnInit
{ {
public documentIDs: number[] = [] public documentIDs: number[] = []
public archiveFallback: boolean = false
public deleteOriginals: boolean = false public deleteOriginals: boolean = false
private _documents: Document[] = [] private _documents: Document[] = []
get documents(): Document[] { get documents(): Document[] {

View File

@ -34,7 +34,7 @@ import {
CustomFieldQueryElement, CustomFieldQueryElement,
CustomFieldQueryExpression, CustomFieldQueryExpression,
} from 'src/app/utils/custom-field-query-element' } from 'src/app/utils/custom-field-query-element'
import { pngxPopperOptions } from 'src/app/utils/popper-options' import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component' import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
import { DocumentLinkComponent } from '../input/document-link/document-link.component' import { DocumentLinkComponent } from '../input/document-link/document-link.component'
@ -183,7 +183,7 @@ export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPerm
public CustomFieldDataType = CustomFieldDataType public CustomFieldDataType = CustomFieldDataType
public CUSTOM_FIELD_QUERY_MAX_DEPTH = CUSTOM_FIELD_QUERY_MAX_DEPTH public CUSTOM_FIELD_QUERY_MAX_DEPTH = CUSTOM_FIELD_QUERY_MAX_DEPTH
public CUSTOM_FIELD_QUERY_MAX_ATOMS = CUSTOM_FIELD_QUERY_MAX_ATOMS public CUSTOM_FIELD_QUERY_MAX_ATOMS = CUSTOM_FIELD_QUERY_MAX_ATOMS
public popperOptions = pngxPopperOptions public popperOptions = popperOptionsReenablePreventOverflow
@Input() @Input()
title: string title: string

View File

@ -1,158 +1,161 @@
<div class="btn-group w-100" ngbDropdown role="group" [popperOptions]="popperOptions" [placement]="placement"> <div class="btn-group w-100" ngbDropdown role="group" [popperOptions]="popperOptions">
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="createdDateTo || createdDateFrom ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled"> <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="createdDateTo || createdDateFrom ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
<i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs> <i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs>
<div class="d-none d-sm-inline">&nbsp;{{title}}</div> <div class="d-none d-sm-inline">&nbsp;{{title}}</div>
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span> <pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
</button> </button>
<div class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}"> <div class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
<h6 class="dropdown-header border-bottom" i18n>Created</h6> <div class="row d-flex">
<div class="list-group list-group-flush"> <div class="col border-end">
<div class="list-group-item d-flex p-2 select-item" role="menuitem"> <div class="list-group list-group-flush">
<div class="selected-icon"> <h6 class="dropdown-header border-bottom" i18n>Created</h6>
@if (createdRelativeDate) { @for (rd of relativeDates; track rd) {
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedRelativeDate()"> <button class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setCreatedRelativeDate(rd.id)">
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs> <div class="selected-icon">
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs> @if (createdRelativeDate === rd.id) {
</a> <i-bs width="1em" height="1em" name="check"></i-bs>
}
</div>
<div class="d-flex justify-content-between w-100 align-items-center ps-2">
<div class="pe-4">
{{rd.name}}
</div>
<div class="text-muted small pe-2">
<span class="small">
{{ rd.date | customDate:'mediumDate' }} &ndash; <ng-container i18n>now</ng-container>
</span>
</div>
</div>
</button>
} }
</div> <div class="list-group-item d-flex p-2" role="menuitem">
<div class="input-group input-group-sm small ps-1 pe-2">
<ng-select class="w-100" name="createdRelativeDate"
[items]="relativeDates" [(ngModel)]="createdRelativeDate"
bindValue="id"
bindLabel="name"
clearable="false"
placeholder="Relative dates"
i18n-placeholder
(change)="onSetCreatedRelativeDate($event)">
<ng-template ng-option-tmp let-item="item">
<div class="d-flex">{{ item.name }}<span class="ms-auto text-muted small">{{ item.date | customDate:'mediumDate' }} &ndash; <ng-container i18n>now</ng-container></span></div>
</ng-template>
</ng-select>
</div>
</div>
<div class="list-group-item d-flex p-2" role="menuitem">
<div class="selected-icon">
@if (createdDateFrom) {
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedFrom()">
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</a>
}
</div>
<div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>From</span>
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
maxlength="10" [(ngModel)]="createdDateFrom" ngbDatepicker #createdDateFromPicker="ngbDatepicker" [footerTemplate]="createdFromFooterTemplate">
<button class="btn btn-outline-secondary" (click)="createdDateFromPicker.toggle()" type="button">
<i-bs width="1em" height="1em" name="calendar"></i-bs>
</button>
<ng-template #createdFromFooterTemplate>
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="createdDateFrom = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="createdDateFromPicker.close()" i18n>Close</button>
</div>
</ng-template>
</div>
</div>
<div class="list-group-item d-flex p-2" role="menuitem">
<div class="selected-icon">
@if (createdDateTo) {
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedTo()">
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</a>
}
</div>
<div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>To</span>
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
maxlength="10" [(ngModel)]="createdDateTo" ngbDatepicker #createdDateToPicker="ngbDatepicker" [footerTemplate]="createdToFooterTemplate">
<button class="btn btn-outline-secondary" (click)="createdDateToPicker.toggle()" type="button">
<i-bs width="1em" height="1em" name="calendar"></i-bs>
</button>
<ng-template #createdToFooterTemplate>
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="createdDateTo = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="createdDateToPicker.close()" i18n>Close</button>
</div>
</ng-template>
</div>
</div> <div class="selected-icon">
</div> @if (createdDateFrom) {
<h6 class="dropdown-header border-bottom" i18n>Added</h6> <a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedFrom()">
<div class="list-group list-group-flush"> <i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<div class="list-group-item d-flex p-2 select-item" role="menuitem"> <i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
<div class="selected-icon"> </a>
@if (addedRelativeDate) { }
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedRelativeDate()">
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</a>
}
</div>
<div class="input-group input-group-sm small ps-1 pe-2">
<ng-select class="w-100" name="addedRelativeDate"
[items]="relativeDates" [(ngModel)]="addedRelativeDate"
bindValue="id"
bindLabel="name"
clearable="false"
placeholder="Relative dates"
i18n-placeholder
(change)="onSetAddedRelativeDate($event)">
<ng-template ng-option-tmp let-item="item">
<div class="d-flex">{{ item.name }}<span class="ms-auto text-muted small">{{ item.date | customDate:'mediumDate' }} &ndash; <ng-container i18n>now</ng-container></span></div>
</ng-template>
</ng-select>
</div>
</div>
<div class="list-group-item d-flex p-2" role="menuitem">
<div class="selected-icon">
@if (addedDateFrom) {
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedFrom()">
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</a>
}
</div>
<div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>From</span>
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
maxlength="10" [(ngModel)]="addedDateFrom" ngbDatepicker #addedDateFromPicker="ngbDatepicker" [footerTemplate]="addedFromFooterTemplate">
<button class="btn btn-outline-secondary" (click)="addedDateFromPicker.toggle()" type="button">
<i-bs width="1em" height="1em" name="calendar"></i-bs>
</button>
<ng-template #addedFromFooterTemplate>
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="addedDateFrom = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="addedDateFromPicker.close()" i18n>Close</button>
</div> </div>
</ng-template> <div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>From</span>
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
maxlength="10" [(ngModel)]="createdDateFrom" ngbDatepicker #createdDateFromPicker="ngbDatepicker" [footerTemplate]="createdFromFooterTemplate">
<button class="btn btn-outline-secondary" (click)="createdDateFromPicker.toggle()" type="button">
<i-bs width="1em" height="1em" name="calendar"></i-bs>
</button>
<ng-template #createdFromFooterTemplate>
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="createdDateFrom = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="createdDateFromPicker.close()" i18n>Close</button>
</div>
</ng-template>
</div>
</div>
<div class="list-group-item d-flex p-2" role="menuitem">
<div class="selected-icon">
@if (createdDateTo) {
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedTo()">
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</a>
}
</div>
<div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>To</span>
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
maxlength="10" [(ngModel)]="createdDateTo" ngbDatepicker #createdDateToPicker="ngbDatepicker" [footerTemplate]="createdToFooterTemplate">
<button class="btn btn-outline-secondary" (click)="createdDateToPicker.toggle()" type="button">
<i-bs width="1em" height="1em" name="calendar"></i-bs>
</button>
<ng-template #createdToFooterTemplate>
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="createdDateTo = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="createdDateToPicker.close()" i18n>Close</button>
</div>
</ng-template>
</div>
</div>
</div> </div>
</div> </div>
<div class="list-group-item d-flex p-2" role="menuitem"> <div class="col">
<div class="selected-icon"> <h6 class="dropdown-header border-bottom" i18n>Added</h6>
@if (addedDateTo) { <div class="list-group list-group-flush">
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedTo()"> @for (rd of relativeDates; track rd) {
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs> <button class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setAddedRelativeDate(rd.id)">
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs> <div class="selected-icon">
</a> @if (addedRelativeDate === rd.id) {
<i-bs width="1em" height="1em" name="check"></i-bs>
}
</div>
<div class="d-flex justify-content-between w-100 align-items-center ps-2">
<div class="pe-4">
{{rd.name}}
</div>
<div class="text-muted small pe-2">
<span class="small">
{{ rd.date | customDate:'mediumDate' }} &ndash; <ng-container i18n>now</ng-container>
</span>
</div>
</div>
</button>
} }
</div> <div class="list-group-item d-flex p-2" role="menuitem">
<div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>To</span> <div class="selected-icon">
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)" @if (addedDateFrom) {
maxlength="10" [(ngModel)]="addedDateTo" ngbDatepicker #addedDateToPicker="ngbDatepicker" [footerTemplate]="addedToFooterTemplate"> <a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedFrom()">
<button class="btn btn-outline-secondary" (click)="addedDateToPicker.toggle()" type="button"> <i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="calendar"></i-bs> <i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</button> </a>
<ng-template #addedToFooterTemplate> }
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="addedDateTo = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="addedDateToPicker.close()" i18n>Close</button>
</div> </div>
</ng-template> <div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>From</span>
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
maxlength="10" [(ngModel)]="addedDateFrom" ngbDatepicker #addedDateFromPicker="ngbDatepicker" [footerTemplate]="addedFromFooterTemplate">
<button class="btn btn-outline-secondary" (click)="addedDateFromPicker.toggle()" type="button">
<i-bs width="1em" height="1em" name="calendar"></i-bs>
</button>
<ng-template #addedFromFooterTemplate>
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="addedDateFrom = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="addedDateFromPicker.close()" i18n>Close</button>
</div>
</ng-template>
</div>
</div>
<div class="list-group-item d-flex p-2" role="menuitem">
<div class="selected-icon">
@if (addedDateTo) {
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedTo()">
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</a>
}
</div>
<div class="input-group input-group-sm small ps-1 pe-2">
<span class="input-group-text w-25 small text-muted" i18n>To</span>
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
maxlength="10" [(ngModel)]="addedDateTo" ngbDatepicker #addedDateToPicker="ngbDatepicker" [footerTemplate]="addedToFooterTemplate">
<button class="btn btn-outline-secondary" (click)="addedDateToPicker.toggle()" type="button">
<i-bs width="1em" height="1em" name="calendar"></i-bs>
</button>
<ng-template #addedToFooterTemplate>
<div class="btn-group-xs border-top p-2 d-flex">
<button class="btn btn-primary" (click)="addedDateTo = today; onChangeDebounce()" i18n>Today</button>
<button class="btn btn-secondary ms-auto" (click)="addedDateToPicker.close()" i18n>Close</button>
</div>
</ng-template>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,16 @@
.date-dropdown { .date-dropdown {
--bs-dropdown-min-width: 22rem;
white-space: nowrap; white-space: nowrap;
@media(min-width: 768px) {
--bs-dropdown-min-width: 40rem;
}
@media screen and (max-width: 767px) {
.border-end {
border: none !important;
}
}
.btn-link { .btn-link {
line-height: 1; line-height: 1;
} }
@ -12,10 +21,6 @@
min-height: 1em; min-height: 1em;
} }
.select-item .selected-icon {
line-height: 2em;
}
.input-group-sm { .input-group-sm {
.form-control { .form-control {
font-size: 0.875rem; font-size: 0.875rem;

View File

@ -82,12 +82,10 @@ describe('DatesDropdownComponent', () => {
it('should support relative dates', fakeAsync(() => { it('should support relative dates', fakeAsync(() => {
let result: DateSelection let result: DateSelection
component.datesSet.subscribe((date) => (result = date)) component.datesSet.subscribe((date) => (result = date))
component.createdRelativeDate = RelativeDate.WITHIN_1_WEEK // normally set by ngModel binding in dropdown component.setCreatedRelativeDate(null)
component.onSetCreatedRelativeDate({ component.setCreatedRelativeDate(RelativeDate.WITHIN_1_WEEK)
id: RelativeDate.WITHIN_1_WEEK, component.setAddedRelativeDate(null)
} as any) component.setAddedRelativeDate(RelativeDate.WITHIN_1_WEEK)
component.addedRelativeDate = RelativeDate.WITHIN_1_WEEK // normally set by ngModel binding in dropdown
component.onSetAddedRelativeDate({ id: RelativeDate.WITHIN_1_WEEK } as any)
tick(500) tick(500)
expect(result).toEqual({ expect(result).toEqual({
createdFrom: null, createdFrom: null,
@ -149,19 +147,8 @@ describe('DatesDropdownComponent', () => {
expect(component.addedDateTo).toBeNull() expect(component.addedDateTo).toBeNull()
}) })
it('should support clearRelativeDate', () => {
component.createdRelativeDate = RelativeDate.WITHIN_1_WEEK
component.clearCreatedRelativeDate()
expect(component.createdRelativeDate).toBeNull()
component.addedRelativeDate = RelativeDate.WITHIN_1_WEEK
component.clearAddedRelativeDate()
expect(component.addedRelativeDate).toBeNull()
})
it('should limit keyboard events', () => { it('should limit keyboard events', () => {
const input: HTMLInputElement = const input: HTMLInputElement = fixture.nativeElement.querySelector('input')
fixture.nativeElement.querySelector('input.form-control')
let event: KeyboardEvent = new KeyboardEvent('keypress', { let event: KeyboardEvent = new KeyboardEvent('keypress', {
key: '9', key: '9',
}) })
@ -176,19 +163,4 @@ describe('DatesDropdownComponent', () => {
input.dispatchEvent(event) input.dispatchEvent(event)
expect(eventSpy).toHaveBeenCalled() expect(eventSpy).toHaveBeenCalled()
}) })
it('should support debounce', fakeAsync(() => {
let result: DateSelection
component.datesSet.subscribe((date) => (result = date))
component.onChangeDebounce()
tick(500)
expect(result).toEqual({
createdFrom: null,
createdTo: null,
createdRelativeDateID: null,
addedFrom: null,
addedTo: null,
addedRelativeDateID: null,
})
}))
}) })

View File

@ -13,14 +13,13 @@ import {
NgbDatepickerModule, NgbDatepickerModule,
NgbDropdownModule, NgbDropdownModule,
} from '@ng-bootstrap/ng-bootstrap' } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subject, Subscription } from 'rxjs' import { Subject, Subscription } from 'rxjs'
import { debounceTime } from 'rxjs/operators' import { debounceTime } from 'rxjs/operators'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter' import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
import { pngxPopperOptions } from 'src/app/utils/popper-options' import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component' import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
export interface DateSelection { export interface DateSelection {
@ -33,14 +32,10 @@ export interface DateSelection {
} }
export enum RelativeDate { export enum RelativeDate {
WITHIN_1_WEEK = 1, WITHIN_1_WEEK = 0,
WITHIN_1_MONTH = 2, WITHIN_1_MONTH = 1,
WITHIN_3_MONTHS = 3, WITHIN_3_MONTHS = 2,
WITHIN_1_YEAR = 4, WITHIN_1_YEAR = 3,
THIS_YEAR = 5,
THIS_MONTH = 6,
TODAY = 7,
YESTERDAY = 8,
} }
@Component({ @Component({
@ -54,14 +49,13 @@ export enum RelativeDate {
NgxBootstrapIconsModule, NgxBootstrapIconsModule,
NgbDatepickerModule, NgbDatepickerModule,
NgbDropdownModule, NgbDropdownModule,
NgSelectModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
NgClass, NgClass,
], ],
}) })
export class DatesDropdownComponent implements OnInit, OnDestroy { export class DatesDropdownComponent implements OnInit, OnDestroy {
public popperOptions = pngxPopperOptions public popperOptions = popperOptionsReenablePreventOverflow
constructor(settings: SettingsService) { constructor(settings: SettingsService) {
this.datePlaceHolder = settings.getLocalizedDateInputFormat() this.datePlaceHolder = settings.getLocalizedDateInputFormat()
@ -88,64 +82,44 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
name: $localize`Within 1 year`, name: $localize`Within 1 year`,
date: new Date().setFullYear(new Date().getFullYear() - 1), date: new Date().setFullYear(new Date().getFullYear() - 1),
}, },
{
id: RelativeDate.THIS_YEAR,
name: $localize`This year`,
date: new Date('1/1/' + new Date().getFullYear()),
},
{
id: RelativeDate.THIS_MONTH,
name: $localize`This month`,
date: new Date().setDate(1),
},
{
id: RelativeDate.TODAY,
name: $localize`Today`,
date: new Date().setHours(0, 0, 0, 0),
},
{
id: RelativeDate.YESTERDAY,
name: $localize`Yesterday`,
date: new Date().setDate(new Date().getDate() - 1),
},
] ]
datePlaceHolder: string datePlaceHolder: string
// created // created
@Input() @Input()
createdDateTo: string = null createdDateTo: string
@Output() @Output()
createdDateToChange = new EventEmitter<string>() createdDateToChange = new EventEmitter<string>()
@Input() @Input()
createdDateFrom: string = null createdDateFrom: string
@Output() @Output()
createdDateFromChange = new EventEmitter<string>() createdDateFromChange = new EventEmitter<string>()
@Input() @Input()
createdRelativeDate: RelativeDate = null createdRelativeDate: RelativeDate
@Output() @Output()
createdRelativeDateChange = new EventEmitter<number>() createdRelativeDateChange = new EventEmitter<number>()
// added // added
@Input() @Input()
addedDateTo: string = null addedDateTo: string
@Output() @Output()
addedDateToChange = new EventEmitter<string>() addedDateToChange = new EventEmitter<string>()
@Input() @Input()
addedDateFrom: string = null addedDateFrom: string
@Output() @Output()
addedDateFromChange = new EventEmitter<string>() addedDateFromChange = new EventEmitter<string>()
@Input() @Input()
addedRelativeDate: RelativeDate = null addedRelativeDate: RelativeDate
@Output() @Output()
addedRelativeDateChange = new EventEmitter<number>() addedRelativeDateChange = new EventEmitter<number>()
@ -159,9 +133,6 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
@Input() @Input()
disabled: boolean = false disabled: boolean = false
@Input()
placement: string = 'bottom-start'
public readonly today: string = new Date().toISOString().split('T')[0] public readonly today: string = new Date().toISOString().split('T')[0]
get isActive(): boolean { get isActive(): boolean {
@ -201,17 +172,17 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
this.onChange() this.onChange()
} }
onSetCreatedRelativeDate(rd: { id: number; name: string; date: number }) { setCreatedRelativeDate(rd: RelativeDate) {
// createdRelativeDate is set by ngModel
this.createdDateTo = null this.createdDateTo = null
this.createdDateFrom = null this.createdDateFrom = null
this.createdRelativeDate = this.createdRelativeDate == rd ? null : rd
this.onChange() this.onChange()
} }
onSetAddedRelativeDate(rd: { id: number; name: string; date: number }) { setAddedRelativeDate(rd: RelativeDate) {
// addedRelativeDate is set by ngModel
this.addedDateTo = null this.addedDateTo = null
this.addedDateFrom = null this.addedDateFrom = null
this.addedRelativeDate = this.addedRelativeDate == rd ? null : rd
this.onChange() this.onChange()
} }
@ -253,11 +224,6 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
this.onChange() this.onChange()
} }
clearCreatedRelativeDate() {
this.createdRelativeDate = null
this.onChange()
}
clearAddedTo() { clearAddedTo() {
this.addedDateTo = null this.addedDateTo = null
this.onChange() this.onChange()
@ -268,11 +234,6 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
this.onChange() this.onChange()
} }
clearAddedRelativeDate() {
this.addedRelativeDate = null
this.onChange()
}
// prevent chars other than numbers and separators // prevent chars other than numbers and separators
onKeyPress(event: KeyboardEvent) { onKeyPress(event: KeyboardEvent) {
if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) { if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) {

View File

@ -189,7 +189,6 @@
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select> <pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select> <pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select> <pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
<pngx-input-custom-fields-values formControlName="assign_custom_fields_values" [selectedFields]="formGroup.get('assign_custom_fields').value" (removeSelectedField)="removeSelectedCustomField($event, formGroup)"></pngx-input-custom-fields-values>
</div> </div>
<div class="col"> <div class="col">
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select> <pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>

View File

@ -2,12 +2,7 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing' import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing' import { ComponentFixture, TestBed } from '@angular/core/testing'
import { import { FormsModule, ReactiveFormsModule } from '@angular/forms'
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
} from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select' import { NgSelectModule } from '@ng-select/ng-select'
import { of } from 'rxjs' import { of } from 'rxjs'
@ -374,19 +369,4 @@ describe('WorkflowEditDialogComponent', () => {
expect(component.objectForm.get('actions').value[0].email).toBeNull() expect(component.objectForm.get('actions').value[0].email).toBeNull()
expect(component.objectForm.get('actions').value[0].webhook).toBeNull() expect(component.objectForm.get('actions').value[0].webhook).toBeNull()
}) })
it('should remove selected custom field from the form group', () => {
const formGroup = new FormGroup({
assign_custom_fields: new FormControl([1, 2, 3]),
})
component.removeSelectedCustomField(2, formGroup)
expect(formGroup.get('assign_custom_fields').value).toEqual([1, 3])
component.removeSelectedCustomField(1, formGroup)
expect(formGroup.get('assign_custom_fields').value).toEqual([3])
component.removeSelectedCustomField(3, formGroup)
expect(formGroup.get('assign_custom_fields').value).toEqual([])
})
}) })

View File

@ -47,7 +47,6 @@ import { WorkflowService } from 'src/app/services/rest/workflow.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component' import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
import { CheckComponent } from '../../input/check/check.component' import { CheckComponent } from '../../input/check/check.component'
import { CustomFieldsValuesComponent } from '../../input/custom-fields-values/custom-fields-values.component'
import { EntriesComponent } from '../../input/entries/entries.component' import { EntriesComponent } from '../../input/entries/entries.component'
import { NumberComponent } from '../../input/number/number.component' import { NumberComponent } from '../../input/number/number.component'
import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component' import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component'
@ -152,7 +151,6 @@ const TRIGGER_MATCHING_ALGORITHMS = MATCHING_ALGORITHMS.filter(
SelectComponent, SelectComponent,
TextAreaComponent, TextAreaComponent,
TagsComponent, TagsComponent,
CustomFieldsValuesComponent,
PermissionsGroupComponent, PermissionsGroupComponent,
PermissionsUserComponent, PermissionsUserComponent,
ConfirmButtonComponent, ConfirmButtonComponent,
@ -441,9 +439,6 @@ export class WorkflowEditDialogComponent
assign_change_users: new FormControl(action.assign_change_users), assign_change_users: new FormControl(action.assign_change_users),
assign_change_groups: new FormControl(action.assign_change_groups), assign_change_groups: new FormControl(action.assign_change_groups),
assign_custom_fields: new FormControl(action.assign_custom_fields), assign_custom_fields: new FormControl(action.assign_custom_fields),
assign_custom_fields_values: new FormControl(
action.assign_custom_fields_values
),
remove_tags: new FormControl(action.remove_tags), remove_tags: new FormControl(action.remove_tags),
remove_all_tags: new FormControl(action.remove_all_tags), remove_all_tags: new FormControl(action.remove_all_tags),
remove_document_types: new FormControl(action.remove_document_types), remove_document_types: new FormControl(action.remove_document_types),
@ -570,7 +565,6 @@ export class WorkflowEditDialogComponent
assign_change_users: [], assign_change_users: [],
assign_change_groups: [], assign_change_groups: [],
assign_custom_fields: [], assign_custom_fields: [],
assign_custom_fields_values: {},
remove_tags: [], remove_tags: [],
remove_all_tags: false, remove_all_tags: false,
remove_document_types: [], remove_document_types: [],
@ -649,12 +643,4 @@ export class WorkflowEditDialogComponent
}) })
super.save() super.save()
} }
public removeSelectedCustomField(fieldId: number, group: FormGroup) {
group
.get('assign_custom_fields')
.setValue(
group.get('assign_custom_fields').value.filter((id) => id !== fieldId)
)
}
} }

View File

@ -17,7 +17,7 @@ import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
import { FilterPipe } from 'src/app/pipes/filter.pipe' import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { HotKeyService } from 'src/app/services/hot-key.service' import { HotKeyService } from 'src/app/services/hot-key.service'
import { SelectionDataItem } from 'src/app/services/rest/document.service' import { SelectionDataItem } from 'src/app/services/rest/document.service'
import { pngxPopperOptions } from 'src/app/utils/popper-options' import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component' import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
import { import {
@ -380,7 +380,7 @@ export class FilterableDropdownComponent
@ViewChild('dropdown') dropdown: NgbDropdown @ViewChild('dropdown') dropdown: NgbDropdown
@ViewChild('buttonItems') buttonItems: ElementRef @ViewChild('buttonItems') buttonItems: ElementRef
public popperOptions = pngxPopperOptions public popperOptions = popperOptionsReenablePreventOverflow
filterText: string filterText: string

View File

@ -1,77 +0,0 @@
<div class="list-group mt-3 selected-fields">
@for (fieldId of selectedFields; track fieldId) {
<div class="list-group-item
d-flex
justify-content-between
align-items-center">
@switch (getCustomField(fieldId)?.data_type) {
@case (CustomFieldDataType.String) {
<pngx-input-text [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[horizontal]="true"></pngx-input-text>
}
@case (CustomFieldDataType.Date) {
<pngx-input-date [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[horizontal]="true"></pngx-input-date>
}
@case (CustomFieldDataType.Integer) {
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[horizontal]="true"
[showAdd]="false"></pngx-input-number>
}
@case (CustomFieldDataType.Float) {
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[horizontal]="true"
[showAdd]="false"
[step]=".1"></pngx-input-number>
}
@case (CustomFieldDataType.Monetary) {
<pngx-input-monetary [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[defaultCurrency]="getCustomField(fieldId)?.extra_data?.default_currency"
class="flex-grow-1"
[horizontal]="true"></pngx-input-monetary>
}
@case (CustomFieldDataType.Boolean) {
<pngx-input-check [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[horizontal]="true"></pngx-input-check>
}
@case (CustomFieldDataType.Url) {
<pngx-input-url [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[horizontal]="true"></pngx-input-url>
}
@case (CustomFieldDataType.DocumentLink) {
<pngx-input-document-link [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[horizontal]="true"></pngx-input-document-link>
}
@case (CustomFieldDataType.Select) {
<pngx-input-select [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
[title]="getCustomField(fieldId)?.name"
class="flex-grow-1"
[items]="getCustomField(fieldId)?.extra_data.select_options"
class="flex-grow-1"
bindLabel="label"
[allowNull]="true"
[horizontal]="true"></pngx-input-select>
}
}
<button type="button" class="btn btn-link text-danger" (click)="removeSelectedField.next(fieldId)">
<i-bs name="trash"></i-bs>
</button>
</div>
}
</div>

View File

@ -1,3 +0,0 @@
:host ::ng-deep .list-group-item .mb-3 {
margin-bottom: 0 !important;
}

View File

@ -1,69 +0,0 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import {
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
} from '@angular/forms'
import { of } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { CustomFieldsValuesComponent } from './custom-fields-values.component'
describe('CustomFieldsValuesComponent', () => {
let component: CustomFieldsValuesComponent
let fixture: ComponentFixture<CustomFieldsValuesComponent>
let customFieldsService: CustomFieldsService
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, CustomFieldsValuesComponent],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
}).compileComponents()
fixture = TestBed.createComponent(CustomFieldsValuesComponent)
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
component = fixture.componentInstance
customFieldsService = TestBed.inject(CustomFieldsService)
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
of({
all: [1],
count: 1,
results: [
{
id: 1,
name: 'Field 1',
data_type: CustomFieldDataType.String,
} as CustomField,
],
})
)
fixture.detectChanges()
})
beforeEach(() => {
fixture = TestBed.createComponent(CustomFieldsValuesComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should set selectedFields and map values correctly', () => {
component.value = { 1: 'value1' }
component.selectedFields = [1, 2]
expect(component.selectedFields).toEqual([1, 2])
expect(component.value).toEqual({ 1: 'value1', 2: null })
})
it('should return the correct custom field by id', () => {
const field = component.getCustomField(1)
expect(field).toEqual({
id: 1,
name: 'Field 1',
data_type: CustomFieldDataType.String,
} as CustomField)
})
})

View File

@ -1,90 +0,0 @@
import {
Component,
EventEmitter,
forwardRef,
Input,
Output,
} from '@angular/core'
import {
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
} from '@angular/forms'
import { RouterModule } from '@angular/router'
import { NgSelectModule } from '@ng-select/ng-select'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { AbstractInputComponent } from '../abstract-input'
import { CheckComponent } from '../check/check.component'
import { DateComponent } from '../date/date.component'
import { DocumentLinkComponent } from '../document-link/document-link.component'
import { MonetaryComponent } from '../monetary/monetary.component'
import { NumberComponent } from '../number/number.component'
import { SelectComponent } from '../select/select.component'
import { TextComponent } from '../text/text.component'
import { UrlComponent } from '../url/url.component'
@Component({
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomFieldsValuesComponent),
multi: true,
},
],
selector: 'pngx-input-custom-fields-values',
templateUrl: './custom-fields-values.component.html',
styleUrl: './custom-fields-values.component.scss',
imports: [
TextComponent,
DateComponent,
NumberComponent,
DocumentLinkComponent,
UrlComponent,
SelectComponent,
MonetaryComponent,
CheckComponent,
NgSelectModule,
FormsModule,
ReactiveFormsModule,
RouterModule,
NgxBootstrapIconsModule,
],
})
export class CustomFieldsValuesComponent extends AbstractInputComponent<Object> {
public CustomFieldDataType = CustomFieldDataType
constructor(customFieldsService: CustomFieldsService) {
super()
customFieldsService.listAll().subscribe((items) => {
this.fields = items.results
})
}
private fields: CustomField[]
private _selectedFields: number[]
@Input()
set selectedFields(newFields: number[]) {
this._selectedFields = newFields
// map the selected fields to an object with field_id as key and value as value
this.value = newFields.reduce((acc, fieldId) => {
acc[fieldId] = this.value?.[fieldId] || null
return acc
}, {})
this.onChange(this.value)
}
get selectedFields(): number[] {
return this._selectedFields
}
@Output()
public removeSelectedField: EventEmitter<number> = new EventEmitter<number>()
public getCustomField(id: number): CustomField {
return this.fields.find((field) => field.id === id)
}
}

View File

@ -48,7 +48,7 @@
</details> </details>
} }
@if (toast.action) { @if (toast.action) {
<p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="closed.emit(toast); toast.action()">{{toast.actionName}}</button></p> <p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(toast); toast.action()">{{toast.actionName}}</button></p>
} }
</div> </div>
<button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="closed.emit(toast);"></button> <button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="closed.emit(toast);"></button>

View File

@ -1040,27 +1040,6 @@ describe('BulkEditorComponent', () => {
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id` `${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds ) // listAllFilteredIds
expect(documentListViewService.selected.size).toEqual(0) expect(documentListViewService.selected.size).toEqual(0)
// Test with archiveFallback enabled
modal.componentInstance.deleteOriginals = false
modal.componentInstance.archiveFallback = true
modal.componentInstance.confirm()
req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'merge',
parameters: { metadata_document_id: 3, archive_fallback: true },
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
expect(documentListViewService.selected.size).toEqual(0)
}) })
it('should support bulk download with archive, originals or both and file formatting', () => { it('should support bulk download with archive, originals or both and file formatting', () => {

View File

@ -857,9 +857,6 @@ export class BulkEditorComponent
if (mergeDialog.deleteOriginals) { if (mergeDialog.deleteOriginals) {
args['delete_originals'] = true args['delete_originals'] = true
} }
if (mergeDialog.archiveFallback) {
args['archive_fallback'] = true
}
mergeDialog.buttonsEnabled = false mergeDialog.buttonsEnabled = false
this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs) this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs)
this.toastService.showInfo( this.toastService.showInfo(

View File

@ -93,7 +93,6 @@
} }
<pngx-dates-dropdown class="flex-fill fade" [class.show]="show" <pngx-dates-dropdown class="flex-fill fade" [class.show]="show"
title="Dates" i18n-title title="Dates" i18n-title
placement="bottom-end"
(datesSet)="updateRules()" (datesSet)="updateRules()"
[(createdDateTo)]="dateCreatedTo" [(createdDateTo)]="dateCreatedTo"
[(createdDateFrom)]="dateCreatedFrom" [(createdDateFrom)]="dateCreatedFrom"

View File

@ -96,10 +96,7 @@ import {
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ClearableBadgeComponent } from '../../common/clearable-badge/clearable-badge.component' import { ClearableBadgeComponent } from '../../common/clearable-badge/clearable-badge.component'
import { CustomFieldsQueryDropdownComponent } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component' import { CustomFieldsQueryDropdownComponent } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component'
import { import { DatesDropdownComponent } from '../../common/dates-dropdown/dates-dropdown.component'
DatesDropdownComponent,
RelativeDate,
} from '../../common/dates-dropdown/dates-dropdown.component'
import { import {
FilterableDropdownComponent, FilterableDropdownComponent,
Intersection, Intersection,
@ -425,7 +422,7 @@ describe('FilterEditorComponent', () => {
value: 'created:[-1 week to now]', value: 'created:[-1 week to now]',
}, },
] ]
expect(component.dateCreatedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now'] expect(component.dateCreatedRelativeDate).toEqual(0) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
expect(component.textFilter).toBeNull() expect(component.textFilter).toBeNull()
})) }))
@ -437,7 +434,7 @@ describe('FilterEditorComponent', () => {
value: 'added:[-1 week to now]', value: 'added:[-1 week to now]',
}, },
] ]
expect(component.dateAddedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now'] expect(component.dateAddedRelativeDate).toEqual(0) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
expect(component.textFilter).toBeNull() expect(component.textFilter).toBeNull()
})) }))
@ -1590,8 +1587,10 @@ describe('FilterEditorComponent', () => {
const dateCreatedDropdown = fixture.debugElement.queryAll( const dateCreatedDropdown = fixture.debugElement.queryAll(
By.directive(DatesDropdownComponent) By.directive(DatesDropdownComponent)
)[0] )[0]
component.dateCreatedRelativeDate = RelativeDate.WITHIN_1_WEEK const dateCreatedBeforeRelativeButton = dateCreatedDropdown.queryAll(
dateCreatedDropdown.triggerEventHandler('datesSet') By.css('button')
)[1]
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
@ -1607,8 +1606,10 @@ describe('FilterEditorComponent', () => {
const dateCreatedDropdown = fixture.debugElement.queryAll( const dateCreatedDropdown = fixture.debugElement.queryAll(
By.directive(DatesDropdownComponent) By.directive(DatesDropdownComponent)
)[0] )[0]
component.dateCreatedRelativeDate = RelativeDate.WITHIN_1_WEEK const dateCreatedBeforeRelativeButton = dateCreatedDropdown.queryAll(
dateCreatedDropdown.triggerEventHandler('datesSet') By.css('button')
)[1]
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
@ -1691,14 +1692,16 @@ describe('FilterEditorComponent', () => {
const datesDropdown = fixture.debugElement.query( const datesDropdown = fixture.debugElement.query(
By.directive(DatesDropdownComponent) By.directive(DatesDropdownComponent)
) )
component.dateAddedRelativeDate = RelativeDate.WITHIN_1_WEEK const dateCreatedBeforeRelativeButton = datesDropdown.queryAll(
datesDropdown.triggerEventHandler('datesSet') By.css('button')
)[1]
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: 'added:[-1 week to now]', value: 'created:[-1 week to now]',
}, },
]) ])
})) }))
@ -1708,14 +1711,16 @@ describe('FilterEditorComponent', () => {
const datesDropdown = fixture.debugElement.query( const datesDropdown = fixture.debugElement.query(
By.directive(DatesDropdownComponent) By.directive(DatesDropdownComponent)
) )
component.dateAddedRelativeDate = RelativeDate.WITHIN_1_WEEK const dateCreatedBeforeRelativeButton = datesDropdown.queryAll(
datesDropdown.triggerEventHandler('datesSet') By.css('button')
)[1]
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: 'foo,added:[-1 week to now]', value: 'foo,created:[-1 week to now]',
}, },
]) ])
})) }))

View File

@ -135,44 +135,24 @@ const TEXT_FILTER_MODIFIER_NOTNULL = 'not null'
const TEXT_FILTER_MODIFIER_GT = 'greater' const TEXT_FILTER_MODIFIER_GT = 'greater'
const TEXT_FILTER_MODIFIER_LT = 'less' const TEXT_FILTER_MODIFIER_LT = 'less'
const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:[\["]([^\]]+)[\]"]/g const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:\[([^\]]+)\]/g
const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:[\["]([^\]]+)[\]"]/g const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:\[([^\]]+)\]/g
const RELATIVE_DATE_QUERYSTRINGS = [ const RELATIVE_DATE_QUERYSTRINGS = [
{ {
relativeDate: RelativeDate.WITHIN_1_WEEK, relativeDate: RelativeDate.WITHIN_1_WEEK,
dateQuery: '-1 week to now', dateQuery: '-1 week to now',
isRange: true,
}, },
{ {
relativeDate: RelativeDate.WITHIN_1_MONTH, relativeDate: RelativeDate.WITHIN_1_MONTH,
dateQuery: '-1 month to now', dateQuery: '-1 month to now',
isRange: true,
}, },
{ {
relativeDate: RelativeDate.WITHIN_3_MONTHS, relativeDate: RelativeDate.WITHIN_3_MONTHS,
dateQuery: '-3 month to now', dateQuery: '-3 month to now',
isRange: true,
}, },
{ {
relativeDate: RelativeDate.WITHIN_1_YEAR, relativeDate: RelativeDate.WITHIN_1_YEAR,
dateQuery: '-1 year to now', dateQuery: '-1 year to now',
isRange: true,
},
{
relativeDate: RelativeDate.THIS_YEAR,
dateQuery: 'this year',
},
{
relativeDate: RelativeDate.THIS_MONTH,
dateQuery: 'this month',
},
{
relativeDate: RelativeDate.TODAY,
dateQuery: 'today',
},
{
relativeDate: RelativeDate.YESTERDAY,
dateQuery: 'yesterday',
}, },
] ]
@ -927,11 +907,12 @@ export class FilterEditorComponent
let existingRuleArgs = existingRule?.value.split(',') let existingRuleArgs = existingRule?.value.split(',')
if (this.dateCreatedRelativeDate !== null) { if (this.dateCreatedRelativeDate !== null) {
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
(qS) => qS.relativeDate == this.dateCreatedRelativeDate
)
queryArgs.push( queryArgs.push(
`created:${rd.isRange ? `[${rd.dateQuery}]` : `"${rd.dateQuery}"`}` `created:[${
RELATIVE_DATE_QUERYSTRINGS.find(
(qS) => qS.relativeDate == this.dateCreatedRelativeDate
).dateQuery
}]`
) )
if (existingRule) { if (existingRule) {
queryArgs = existingRuleArgs queryArgs = existingRuleArgs
@ -940,11 +921,12 @@ export class FilterEditorComponent
} }
} }
if (this.dateAddedRelativeDate !== null) { if (this.dateAddedRelativeDate !== null) {
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
(qS) => qS.relativeDate == this.dateAddedRelativeDate
)
queryArgs.push( queryArgs.push(
`added:${rd.isRange ? `[${rd.dateQuery}]` : `"${rd.dateQuery}"`}` `added:[${
RELATIVE_DATE_QUERYSTRINGS.find(
(qS) => qS.relativeDate == this.dateAddedRelativeDate
).dateQuery
}]`
) )
if (existingRule) { if (existingRule) {
queryArgs = existingRuleArgs queryArgs = existingRuleArgs

View File

@ -58,8 +58,6 @@ export interface WorkflowAction extends ObjectWithId {
assign_custom_fields?: number[] // [CustomField.id] assign_custom_fields?: number[] // [CustomField.id]
assign_custom_fields_values?: object
remove_tags?: number[] // Tag.id remove_tags?: number[] // Tag.id
remove_all_tags?: boolean remove_all_tags?: boolean

View File

@ -1,10 +1,11 @@
import { Options } from '@popperjs/core' import { Options } from '@popperjs/core'
import { pngxPopperOptions } from './popper-options' import { popperOptionsReenablePreventOverflow } from './popper-options'
describe('popperOptionsReenablePreventOverflow', () => { describe('popperOptionsReenablePreventOverflow', () => {
it('should return the config with add padding', () => { it('should return the config without the empty fun preventOverflow, add padding to other', () => {
const config: Partial<Options> = { const config: Partial<Options> = {
modifiers: [ modifiers: [
{ name: 'preventOverflow', fn: function () {} },
{ {
name: 'preventOverflow', name: 'preventOverflow',
fn: function (arg0) { fn: function (arg0) {
@ -14,7 +15,7 @@ describe('popperOptionsReenablePreventOverflow', () => {
], ],
} }
const result = pngxPopperOptions(config) const result = popperOptionsReenablePreventOverflow(config)
expect(result.modifiers.length).toBe(1) expect(result.modifiers.length).toBe(1)
expect(result.modifiers[0].name).toBe('preventOverflow') expect(result.modifiers[0].name).toBe('preventOverflow')

View File

@ -1,11 +1,16 @@
import { Options } from '@popperjs/core' import { Options } from '@popperjs/core'
export function pngxPopperOptions(config: Partial<Options>): Partial<Options> { export function popperOptionsReenablePreventOverflow(
const preventOverflowModifier = config.modifiers.find( config: Partial<Options>
): Partial<Options> {
config.modifiers = config.modifiers?.filter(
(m) => !(m.name === 'preventOverflow' && m.fn?.length === 0)
)
const ogPreventOverflowModifier = config.modifiers.find(
(m) => m.name === 'preventOverflow' (m) => m.name === 'preventOverflow'
) )
if (preventOverflowModifier) { if (ogPreventOverflowModifier) {
preventOverflowModifier.options = { ogPreventOverflowModifier.options = {
padding: 10, padding: 10,
} }
} }

View File

@ -318,7 +318,6 @@ def merge(
*, *,
metadata_document_id: int | None = None, metadata_document_id: int | None = None,
delete_originals: bool = False, delete_originals: bool = False,
archive_fallback: bool = False,
user: User | None = None, user: User | None = None,
) -> Literal["OK"]: ) -> Literal["OK"]:
logger.info( logger.info(
@ -334,14 +333,7 @@ def merge(
for doc_id in doc_ids: for doc_id in doc_ids:
doc = qs.get(id=doc_id) doc = qs.get(id=doc_id)
try: try:
doc_path = ( with pikepdf.open(str(doc.source_path)) as pdf:
doc.archive_path
if archive_fallback
and doc.mime_type != "application/pdf"
and doc.has_archive_version
else doc.source_path
)
with pikepdf.open(str(doc_path)) as pdf:
version = max(version, pdf.pdf_version) version = max(version, pdf.pdf_version)
merged_pdf.pages.extend(pdf.pages) merged_pdf.pages.extend(pdf.pages)
affected_docs.append(doc.id) affected_docs.append(doc.id)
@ -357,7 +349,7 @@ def merge(
Path( Path(
tempfile.mkdtemp(dir=settings.SCRATCH_DIR), tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
) )
/ f"{'_'.join([str(doc_id) for doc_id in affected_docs])[:100]}_merged.pdf" / f"{'_'.join([str(doc_id) for doc_id in doc_ids])[:100]}_merged.pdf"
) )
merged_pdf.remove_unreferenced_resources() merged_pdf.remove_unreferenced_resources()
merged_pdf.save(filepath, min_version=version) merged_pdf.save(filepath, min_version=version)

View File

@ -806,19 +806,13 @@ class ConsumerPlugin(
} }
set_permissions_for_object(permissions=permissions, object=document) set_permissions_for_object(permissions=permissions, object=document)
if self.metadata.custom_fields: if self.metadata.custom_field_ids:
for field in CustomField.objects.filter( for field_id in self.metadata.custom_field_ids:
id__in=self.metadata.custom_fields.keys(), field = CustomField.objects.get(pk=field_id)
).distinct(): CustomFieldInstance.objects.create(
value_field_name = CustomFieldInstance.get_value_field_name( field=field,
data_type=field.data_type, document=document,
) ) # adds to document
args = {
"field": field,
"document": document,
value_field_name: self.metadata.custom_fields.get(field.id, None),
}
CustomFieldInstance.objects.create(**args) # adds to document
def _write(self, storage_type, source, target): def _write(self, storage_type, source, target):
with ( with (

View File

@ -29,7 +29,7 @@ class DocumentMetadataOverrides:
view_groups: list[int] | None = None view_groups: list[int] | None = None
change_users: list[int] | None = None change_users: list[int] | None = None
change_groups: list[int] | None = None change_groups: list[int] | None = None
custom_fields: dict | None = None custom_field_ids: list[int] | None = None
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides": def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
""" """
@ -81,10 +81,11 @@ class DocumentMetadataOverrides:
self.change_groups.extend(other.change_groups) self.change_groups.extend(other.change_groups)
self.change_groups = list(set(self.change_groups)) self.change_groups = list(set(self.change_groups))
if self.custom_fields is None: if self.custom_field_ids is None:
self.custom_fields = other.custom_fields self.custom_field_ids = other.custom_field_ids
elif other.custom_fields is not None: elif other.custom_field_ids is not None:
self.custom_fields.update(other.custom_fields) self.custom_field_ids.extend(other.custom_field_ids)
self.custom_field_ids = list(set(self.custom_field_ids))
return self return self
@ -113,10 +114,9 @@ class DocumentMetadataOverrides:
only_with_perms_in=["change_document"], only_with_perms_in=["change_document"],
).values_list("id", flat=True), ).values_list("id", flat=True),
) )
overrides.custom_fields = { overrides.custom_field_ids = list(
custom_field.id: custom_field.value doc.custom_fields.values_list("field", flat=True),
for custom_field in doc.custom_fields.all() )
}
groups_with_perms = get_groups_with_perms( groups_with_perms = get_groups_with_perms(
doc, doc,

View File

@ -5,7 +5,6 @@ import re
import shutil import shutil
import subprocess import subprocess
import tempfile import tempfile
from pathlib import Path
import gnupg import gnupg
from django.conf import settings from django.conf import settings
@ -35,16 +34,16 @@ class GnuPG:
def move_documents_and_create_thumbnails(apps, schema_editor): def move_documents_and_create_thumbnails(apps, schema_editor):
(Path(settings.MEDIA_ROOT) / "documents" / "originals").mkdir( os.makedirs(
parents=True, os.path.join(settings.MEDIA_ROOT, "documents", "originals"),
exist_ok=True, exist_ok=True,
) )
(Path(settings.MEDIA_ROOT) / "documents" / "thumbnails").mkdir( os.makedirs(
parents=True, os.path.join(settings.MEDIA_ROOT, "documents", "thumbnails"),
exist_ok=True, exist_ok=True,
) )
documents: list[str] = os.listdir(Path(settings.MEDIA_ROOT) / "documents") documents = os.listdir(os.path.join(settings.MEDIA_ROOT, "documents"))
if set(documents) == {"originals", "thumbnails"}: if set(documents) == {"originals", "thumbnails"}:
return return
@ -61,7 +60,10 @@ def move_documents_and_create_thumbnails(apps, schema_editor):
), ),
) )
Path(settings.SCRATCH_DIR).mkdir(parents=True, exist_ok=True) try:
os.makedirs(settings.SCRATCH_DIR)
except FileExistsError:
pass
for f in sorted(documents): for f in sorted(documents):
if not f.endswith("gpg"): if not f.endswith("gpg"):
@ -75,14 +77,15 @@ def move_documents_and_create_thumbnails(apps, schema_editor):
), ),
) )
thumb_temp: str = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR) thumb_temp = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR)
orig_temp: str = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR) orig_temp = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR)
orig_source: Path = Path(settings.MEDIA_ROOT) / "documents" / f orig_source = os.path.join(settings.MEDIA_ROOT, "documents", f)
orig_target: Path = Path(orig_temp) / f.replace(".gpg", "") orig_target = os.path.join(orig_temp, f.replace(".gpg", ""))
with orig_source.open("rb") as encrypted, orig_target.open("wb") as unencrypted: with open(orig_source, "rb") as encrypted:
unencrypted.write(GnuPG.decrypted(encrypted)) with open(orig_target, "wb") as unencrypted:
unencrypted.write(GnuPG.decrypted(encrypted))
subprocess.Popen( subprocess.Popen(
( (
@ -92,29 +95,27 @@ def move_documents_and_create_thumbnails(apps, schema_editor):
"-alpha", "-alpha",
"remove", "remove",
orig_target, orig_target,
Path(thumb_temp) / "convert-%04d.png", os.path.join(thumb_temp, "convert-%04d.png"),
), ),
).wait() ).wait()
thumb_source: Path = Path(thumb_temp) / "convert-0000.png" thumb_source = os.path.join(thumb_temp, "convert-0000.png")
thumb_target: Path = ( thumb_target = os.path.join(
Path(settings.MEDIA_ROOT) settings.MEDIA_ROOT,
/ "documents" "documents",
/ "thumbnails" "thumbnails",
/ re.sub(r"(\d+)\.\w+(\.gpg)", "\\1.png\\2", f) re.sub(r"(\d+)\.\w+(\.gpg)", "\\1.png\\2", f),
) )
with ( with open(thumb_source, "rb") as unencrypted:
thumb_source.open("rb") as unencrypted, with open(thumb_target, "wb") as encrypted:
thumb_target.open("wb") as encrypted, encrypted.write(GnuPG.encrypted(unencrypted))
):
encrypted.write(GnuPG.encrypted(unencrypted))
shutil.rmtree(thumb_temp) shutil.rmtree(thumb_temp)
shutil.rmtree(orig_temp) shutil.rmtree(orig_temp)
shutil.move( shutil.move(
Path(settings.MEDIA_ROOT) / "documents" / f, os.path.join(settings.MEDIA_ROOT, "documents", f),
Path(settings.MEDIA_ROOT) / "documents" / "originals" / f, os.path.join(settings.MEDIA_ROOT, "documents", "originals", f),
) )

View File

@ -1,7 +1,7 @@
# Generated by Django 1.9.4 on 2016-03-28 19:09 # Generated by Django 1.9.4 on 2016-03-28 19:09
import hashlib import hashlib
from pathlib import Path import os
import django.utils.timezone import django.utils.timezone
import gnupg import gnupg
@ -58,16 +58,16 @@ class Document:
@property @property
def source_path(self): def source_path(self):
return ( return os.path.join(
Path(settings.MEDIA_ROOT) settings.MEDIA_ROOT,
/ "documents" "documents",
/ "originals" "originals",
/ f"{self.pk:07}.{self.file_type}.gpg" f"{self.pk:07}.{self.file_type}.gpg",
).as_posix() )
@property @property
def source_file(self): def source_file(self):
return Path(self.source_path).open("rb") return open(self.source_path, "rb")
@property @property
def file_name(self): def file_name(self):

View File

@ -1,5 +1,5 @@
# Generated by Django 3.1.3 on 2020-11-20 11:21 # Generated by Django 3.1.3 on 2020-11-20 11:21
from pathlib import Path import os
import magic import magic
from django.conf import settings from django.conf import settings
@ -12,15 +12,15 @@ STORAGE_TYPE_UNENCRYPTED = "unencrypted"
STORAGE_TYPE_GPG = "gpg" STORAGE_TYPE_GPG = "gpg"
def source_path(self) -> Path: def source_path(self):
if self.filename: if self.filename:
fname: str = str(self.filename) fname = str(self.filename)
else: else:
fname = f"{self.pk:07}.{self.file_type}" fname = f"{self.pk:07}.{self.file_type}"
if self.storage_type == STORAGE_TYPE_GPG: if self.storage_type == STORAGE_TYPE_GPG:
fname += ".gpg" fname += ".gpg"
return Path(settings.ORIGINALS_DIR) / fname return os.path.join(settings.ORIGINALS_DIR, fname)
def add_mime_types(apps, schema_editor): def add_mime_types(apps, schema_editor):
@ -28,22 +28,24 @@ def add_mime_types(apps, schema_editor):
documents = Document.objects.all() documents = Document.objects.all()
for d in documents: for d in documents:
with Path(source_path(d)).open("rb") as f: f = open(source_path(d), "rb")
if d.storage_type == STORAGE_TYPE_GPG: if d.storage_type == STORAGE_TYPE_GPG:
data = GnuPG.decrypted(f) data = GnuPG.decrypted(f)
else: else:
data = f.read(1024) data = f.read(1024)
d.mime_type = magic.from_buffer(data, mime=True) d.mime_type = magic.from_buffer(data, mime=True)
d.save() d.save()
f.close()
def add_file_extensions(apps, schema_editor): def add_file_extensions(apps, schema_editor):
Document = apps.get_model("documents", "Document") Document = apps.get_model("documents", "Document")
documents = Document.objects.all() documents = Document.objects.all()
for d in documents: for d in documents:
d.file_type = Path(d.filename).suffix.lstrip(".") d.file_type = os.path.splitext(d.filename)[1].strip(".")
d.save() d.save()

View File

@ -1,24 +0,0 @@
# Generated by Django 5.1.6 on 2025-03-01 18:10
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1064_delete_log"),
]
operations = [
migrations.AddField(
model_name="workflowaction",
name="assign_custom_fields_values",
field=models.JSONField(
blank=True,
help_text="Optional values to assign to the custom fields.",
null=True,
verbose_name="custom field values",
default=dict,
),
),
]

View File

@ -315,7 +315,7 @@ class Document(SoftDeleteModel, ModelWithOwner):
@property @property
def source_file(self): def source_file(self):
return Path(self.source_path).open("rb") return open(self.source_path, "rb")
@property @property
def has_archive_version(self) -> bool: def has_archive_version(self) -> bool:
@ -330,7 +330,7 @@ class Document(SoftDeleteModel, ModelWithOwner):
@property @property
def archive_file(self): def archive_file(self):
return Path(self.archive_path).open("rb") return open(self.archive_path, "rb")
def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str: def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str:
""" """
@ -367,7 +367,7 @@ class Document(SoftDeleteModel, ModelWithOwner):
@property @property
def thumbnail_file(self): def thumbnail_file(self):
return Path(self.thumbnail_path).open("rb") return open(self.thumbnail_path, "rb")
@property @property
def created_date(self): def created_date(self):
@ -1271,16 +1271,6 @@ class WorkflowAction(models.Model):
verbose_name=_("assign these custom fields"), verbose_name=_("assign these custom fields"),
) )
assign_custom_fields_values = models.JSONField(
_("custom field values"),
null=True,
blank=True,
help_text=_(
"Optional values to assign to the custom fields.",
),
default=dict,
)
remove_tags = models.ManyToManyField( remove_tags = models.ManyToManyField(
Tag, Tag,
blank=True, blank=True,

View File

@ -1446,11 +1446,6 @@ class BulkEditSerializer(
raise serializers.ValidationError("delete_originals must be a boolean") raise serializers.ValidationError("delete_originals must be a boolean")
else: else:
parameters["delete_originals"] = False parameters["delete_originals"] = False
if "archive_fallback" in parameters:
if not isinstance(parameters["archive_fallback"], bool):
raise serializers.ValidationError("archive_fallback must be a boolean")
else:
parameters["archive_fallback"] = False
def validate(self, attrs): def validate(self, attrs):
method = attrs["method"] method = attrs["method"]
@ -2023,7 +2018,6 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
"assign_change_users", "assign_change_users",
"assign_change_groups", "assign_change_groups",
"assign_custom_fields", "assign_custom_fields",
"assign_custom_fields_values",
"remove_all_tags", "remove_all_tags",
"remove_tags", "remove_tags",
"remove_all_correspondents", "remove_all_correspondents",

View File

@ -770,40 +770,23 @@ def run_workflows(
if action.assign_custom_fields.exists(): if action.assign_custom_fields.exists():
if not use_overrides: if not use_overrides:
for field in action.assign_custom_fields.all(): for field in action.assign_custom_fields.all():
value_field_name = CustomFieldInstance.get_value_field_name( if not CustomFieldInstance.objects.filter(
data_type=field.data_type,
)
args = {
value_field_name: action.assign_custom_fields_values.get(
str(field.pk),
None,
),
}
# for some reason update_or_create doesn't work here
instance = CustomFieldInstance.objects.filter(
field=field, field=field,
document=document, document=document,
).first() ).exists():
if instance: # can be triggered on existing docs, so only add the field if it doesn't already exist
setattr(instance, value_field_name, args[value_field_name])
instance.save()
else:
CustomFieldInstance.objects.create( CustomFieldInstance.objects.create(
**args,
field=field, field=field,
document=document, document=document,
) )
else: else:
if overrides.custom_fields is None: overrides.custom_field_ids = list(
overrides.custom_fields = {} set(
overrides.custom_fields.update( (overrides.custom_field_ids or [])
{ + list(
field.pk: action.assign_custom_fields_values.get( action.assign_custom_fields.values_list("pk", flat=True),
str(field.pk), ),
None, ),
)
for field in action.assign_custom_fields.all()
},
) )
def removal_action(): def removal_action():
@ -961,18 +944,18 @@ def run_workflows(
if not use_overrides: if not use_overrides:
CustomFieldInstance.objects.filter(document=document).delete() CustomFieldInstance.objects.filter(document=document).delete()
else: else:
overrides.custom_fields = None overrides.custom_field_ids = None
elif action.remove_custom_fields.exists(): elif action.remove_custom_fields.exists():
if not use_overrides: if not use_overrides:
CustomFieldInstance.objects.filter( CustomFieldInstance.objects.filter(
field__in=action.remove_custom_fields.all(), field__in=action.remove_custom_fields.all(),
document=document, document=document,
).delete() ).delete()
elif overrides.custom_fields: elif overrides.custom_field_ids:
for field in action.remove_custom_fields.filter( for field in action.remove_custom_fields.filter(
pk__in=overrides.custom_fields.keys(), pk__in=overrides.custom_field_ids,
): ):
overrides.custom_fields.pop(field.pk, None) overrides.custom_field_ids.remove(field.pk)
def email_action(): def email_action():
if not settings.EMAIL_ENABLED: if not settings.EMAIL_ENABLED:

View File

@ -272,7 +272,7 @@ def update_document_content_maybe_archive_file(document_id):
with transaction.atomic(): with transaction.atomic():
oldDocument = Document.objects.get(pk=document.pk) oldDocument = Document.objects.get(pk=document.pk)
if parser.get_archive_path(): if parser.get_archive_path():
with Path(parser.get_archive_path()).open("rb") as f: with open(parser.get_archive_path(), "rb") as f:
checksum = hashlib.md5(f.read()).hexdigest() checksum = hashlib.md5(f.read()).hexdigest()
# I'm going to save first so that in case the file move # I'm going to save first so that in case the file move
# fails, the database is rolled back. # fails, the database is rolled back.

View File

@ -1,18 +0,0 @@
{% extends "paperless-ngx/base.html" %}
{% load i18n %}
{% block head_title %}
{% trans "Paperless-ngx account inactive" %}
{% endblock head_title %}
{% block form_top_content %}
<h4>{% translate "Account inactve." %}</h4>
{% endblock form_top_content %}
{% block form_content %}
{% url 'account_login' as login_url %}
<p>{% translate "This account is inactive." %}</p>
<div class="d-grid mt-3">
<a class="btn btn-lg btn-primary" href="{{ login_url }}">{% translate "Return to login" %}</a>
</div>
{% endblock form_content %}

View File

@ -1,5 +1,5 @@
import json import json
from pathlib import Path import os
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework import status from rest_framework import status
@ -136,7 +136,10 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
THEN: THEN:
- old app_logo file is deleted - old app_logo file is deleted
""" """
with (Path(__file__).parent / "samples" / "simple.jpg").open("rb") as f: with open(
os.path.join(os.path.dirname(__file__), "samples", "simple.jpg"),
"rb",
) as f:
self.client.patch( self.client.patch(
f"{self.ENDPOINT}1/", f"{self.ENDPOINT}1/",
{ {
@ -145,12 +148,15 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
) )
config = ApplicationConfiguration.objects.first() config = ApplicationConfiguration.objects.first()
old_logo = config.app_logo old_logo = config.app_logo
self.assertTrue(Path(old_logo.path).exists()) self.assertTrue(os.path.exists(old_logo.path))
with (Path(__file__).parent / "samples" / "simple.png").open("rb") as f: with open(
os.path.join(os.path.dirname(__file__), "samples", "simple.png"),
"rb",
) as f:
self.client.patch( self.client.patch(
f"{self.ENDPOINT}1/", f"{self.ENDPOINT}1/",
{ {
"app_logo": f, "app_logo": f,
}, },
) )
self.assertFalse(Path(old_logo.path).exists()) self.assertFalse(os.path.exists(old_logo.path))

View File

@ -28,7 +28,6 @@ from documents.caching import CACHE_50_MINUTES
from documents.caching import CLASSIFIER_HASH_KEY from documents.caching import CLASSIFIER_HASH_KEY
from documents.caching import CLASSIFIER_MODIFIED_KEY from documents.caching import CLASSIFIER_MODIFIED_KEY
from documents.caching import CLASSIFIER_VERSION_KEY from documents.caching import CLASSIFIER_VERSION_KEY
from documents.data_models import DocumentSource
from documents.models import Correspondent from documents.models import Correspondent
from documents.models import CustomField from documents.models import CustomField
from documents.models import CustomFieldInstance from documents.models import CustomFieldInstance
@ -40,10 +39,7 @@ from documents.models import SavedView
from documents.models import ShareLink from documents.models import ShareLink
from documents.models import StoragePath from documents.models import StoragePath
from documents.models import Tag from documents.models import Tag
from documents.models import Workflow
from documents.models import WorkflowAction
from documents.models import WorkflowTrigger from documents.models import WorkflowTrigger
from documents.signals.handlers import run_workflows
from documents.tests.utils import DirectoriesMixin from documents.tests.utils import DirectoriesMixin
from documents.tests.utils import DocumentConsumeDelayMixin from documents.tests.utils import DocumentConsumeDelayMixin
@ -1366,69 +1362,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
self.assertEqual(input_doc.original_file.name, "simple.pdf") self.assertEqual(input_doc.original_file.name, "simple.pdf")
self.assertEqual(overrides.filename, "simple.pdf") self.assertEqual(overrides.filename, "simple.pdf")
self.assertEqual(overrides.custom_fields, {custom_field.id: None}) self.assertEqual(overrides.custom_field_ids, [custom_field.id])
def test_upload_with_custom_fields_and_workflow(self):
"""
GIVEN: A document with a source file
WHEN: Upload the document with custom fields and a workflow
THEN: Metadata is set correctly, mimicking what happens in the real consumer plugin
"""
self.consume_file_mock.return_value = celery.result.AsyncResult(
id=str(uuid.uuid4()),
)
cf = CustomField.objects.create(
name="stringfield",
data_type=CustomField.FieldDataType.STRING,
)
cf2 = CustomField.objects.create(
name="intfield",
data_type=CustomField.FieldDataType.INT,
)
trigger1 = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
)
action1 = WorkflowAction.objects.create(
assign_title="Doc title",
)
action1.assign_custom_fields.add(cf2)
action1.assign_custom_fields_values = {cf2.id: 123}
action1.save()
w1 = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w1.triggers.add(trigger1)
w1.actions.add(action1)
w1.save()
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{
"document": f,
"custom_fields": [cf.id],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.consume_file_mock.assert_called_once()
input_doc, overrides = self.get_last_consume_delay_call_args()
new_overrides, msg = run_workflows(
trigger_type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
document=input_doc,
logging_group=None,
overrides=overrides,
)
overrides.update(new_overrides)
self.assertEqual(overrides.custom_fields, {cf.id: None, cf2.id: 123})
def test_upload_with_webui_source(self): def test_upload_with_webui_source(self):
""" """

View File

@ -514,23 +514,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
Path(__file__).parent / "samples" / "simple.jpg", Path(__file__).parent / "samples" / "simple.jpg",
img_doc, img_doc,
) )
img_doc_archive = self.dirs.archive_dir / "sample_image.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
img_doc_archive,
)
self.img_doc = Document.objects.create( self.img_doc = Document.objects.create(
checksum="D", checksum="D",
title="D", title="D",
filename=img_doc, filename=img_doc,
mime_type="image/jpeg", mime_type="image/jpeg",
) )
self.img_doc.archive_filename = img_doc_archive
self.img_doc.save()
@mock.patch("documents.tasks.consume_file.s") @mock.patch("documents.tasks.consume_file.s")
def test_merge(self, mock_consume_file): def test_merge(self, mock_consume_file):
@ -616,32 +605,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
doc_ids, doc_ids,
) )
@mock.patch("documents.tasks.consume_file.s")
def test_merge_with_archive_fallback(self, mock_consume_file):
"""
GIVEN:
- Existing documents
WHEN:
- Merge action is called with 2 documents, one of which is an image and archive_fallback is set to True
THEN:
- Image document should be included
"""
doc_ids = [self.doc2.id, self.img_doc.id]
result = bulk_edit.merge(doc_ids, archive_fallback=True)
self.assertEqual(result, "OK")
expected_filename = (
f"{'_'.join([str(doc_id) for doc_id in doc_ids])[:100]}_merged.pdf"
)
mock_consume_file.assert_called()
consume_file_args, _ = mock_consume_file.call_args
self.assertEqual(
Path(consume_file_args[0].original_file).name,
expected_filename,
)
@mock.patch("documents.tasks.consume_file.delay") @mock.patch("documents.tasks.consume_file.delay")
@mock.patch("pikepdf.open") @mock.patch("pikepdf.open")
def test_merge_with_errors(self, mock_open_pdf, mock_consume_file): def test_merge_with_errors(self, mock_open_pdf, mock_consume_file):

View File

@ -1,3 +1,4 @@
import os
import re import re
import shutil import shutil
from pathlib import Path from pathlib import Path
@ -616,7 +617,7 @@ class TestClassifier(DirectoriesMixin, TestCase):
self.assertListEqual(self.classifier.predict_tags(doc2.content), []) self.assertListEqual(self.classifier.predict_tags(doc2.content), [])
def test_load_classifier_not_exists(self): def test_load_classifier_not_exists(self):
self.assertFalse(Path(settings.MODEL_FILE).exists()) self.assertFalse(os.path.exists(settings.MODEL_FILE))
self.assertIsNone(load_classifier()) self.assertIsNone(load_classifier())
@mock.patch("documents.classifier.DocumentClassifier.load") @mock.patch("documents.classifier.DocumentClassifier.load")
@ -631,7 +632,7 @@ class TestClassifier(DirectoriesMixin, TestCase):
}, },
) )
@override_settings( @override_settings(
MODEL_FILE=(Path(__file__).parent / "data" / "model.pickle").as_posix(), MODEL_FILE=os.path.join(os.path.dirname(__file__), "data", "model.pickle"),
) )
@pytest.mark.skip( @pytest.mark.skip(
reason="Disabled caching due to high memory usage - need to investigate.", reason="Disabled caching due to high memory usage - need to investigate.",
@ -647,24 +648,24 @@ class TestClassifier(DirectoriesMixin, TestCase):
@mock.patch("documents.classifier.DocumentClassifier.load") @mock.patch("documents.classifier.DocumentClassifier.load")
def test_load_classifier_incompatible_version(self, load): def test_load_classifier_incompatible_version(self, load):
Path(settings.MODEL_FILE).touch() Path(settings.MODEL_FILE).touch()
self.assertTrue(Path(settings.MODEL_FILE).exists()) self.assertTrue(os.path.exists(settings.MODEL_FILE))
load.side_effect = IncompatibleClassifierVersionError("Dummy Error") load.side_effect = IncompatibleClassifierVersionError("Dummy Error")
self.assertIsNone(load_classifier()) self.assertIsNone(load_classifier())
self.assertFalse(Path(settings.MODEL_FILE).exists()) self.assertFalse(os.path.exists(settings.MODEL_FILE))
@mock.patch("documents.classifier.DocumentClassifier.load") @mock.patch("documents.classifier.DocumentClassifier.load")
def test_load_classifier_os_error(self, load): def test_load_classifier_os_error(self, load):
Path(settings.MODEL_FILE).touch() Path(settings.MODEL_FILE).touch()
self.assertTrue(Path(settings.MODEL_FILE).exists()) self.assertTrue(os.path.exists(settings.MODEL_FILE))
load.side_effect = OSError() load.side_effect = OSError()
self.assertIsNone(load_classifier()) self.assertIsNone(load_classifier())
self.assertTrue(Path(settings.MODEL_FILE).exists()) self.assertTrue(os.path.exists(settings.MODEL_FILE))
def test_load_old_classifier_version(self): def test_load_old_classifier_version(self):
shutil.copy( shutil.copy(
Path(__file__).parent / "data" / "v1.17.4.model.pickle", os.path.join(os.path.dirname(__file__), "data", "v1.17.4.model.pickle"),
self.dirs.scratch_dir, self.dirs.scratch_dir,
) )
with override_settings( with override_settings(

View File

@ -408,9 +408,7 @@ class TestConsumer(
with self.get_consumer( with self.get_consumer(
self.get_test_file(), self.get_test_file(),
DocumentMetadataOverrides( DocumentMetadataOverrides(custom_field_ids=[cf1.id, cf3.id]),
custom_fields={cf1.id: "value1", cf3.id: "http://example.com"},
),
) as consumer: ) as consumer:
consumer.run() consumer.run()
@ -422,11 +420,6 @@ class TestConsumer(
self.assertIn(cf1, fields_used) self.assertIn(cf1, fields_used)
self.assertNotIn(cf2, fields_used) self.assertNotIn(cf2, fields_used)
self.assertIn(cf3, fields_used) self.assertIn(cf3, fields_used)
self.assertEqual(document.custom_fields.get(field=cf1).value, "value1")
self.assertEqual(
document.custom_fields.get(field=cf3).value,
"http://example.com",
)
self._assert_first_last_send_progress() self._assert_first_last_send_progress()
def testOverrideAsn(self): def testOverrideAsn(self):

View File

@ -1,5 +1,5 @@
import os
import shutil import shutil
from pathlib import Path
from unittest import mock from unittest import mock
from django.core.management import call_command from django.core.management import call_command
@ -22,7 +22,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
filename="test.pdf", filename="test.pdf",
) )
shutil.copy( shutil.copy(
Path(__file__).parent / "samples" / "simple.pdf", os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
self.d1.source_path, self.d1.source_path,
) )
@ -34,7 +34,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
filename="test2.pdf", filename="test2.pdf",
) )
shutil.copy( shutil.copy(
Path(__file__).parent / "samples" / "simple.pdf", os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
self.d2.source_path, self.d2.source_path,
) )
@ -46,7 +46,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
filename="test3.pdf", filename="test3.pdf",
) )
shutil.copy( shutil.copy(
Path(__file__).parent / "samples" / "password-is-test.pdf", os.path.join(os.path.dirname(__file__), "samples", "password-is-test.pdf"),
self.d3.source_path, self.d3.source_path,
) )

View File

@ -1,3 +1,4 @@
import os
import shutil import shutil
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
@ -87,18 +88,18 @@ class TestClassifier(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
tasks.train_classifier() tasks.train_classifier()
self.assertIsFile(settings.MODEL_FILE) self.assertIsFile(settings.MODEL_FILE)
mtime = Path(settings.MODEL_FILE).stat().st_mtime mtime = os.stat(settings.MODEL_FILE).st_mtime
tasks.train_classifier() tasks.train_classifier()
self.assertIsFile(settings.MODEL_FILE) self.assertIsFile(settings.MODEL_FILE)
mtime2 = Path(settings.MODEL_FILE).stat().st_mtime mtime2 = os.stat(settings.MODEL_FILE).st_mtime
self.assertEqual(mtime, mtime2) self.assertEqual(mtime, mtime2)
doc.content = "test2" doc.content = "test2"
doc.save() doc.save()
tasks.train_classifier() tasks.train_classifier()
self.assertIsFile(settings.MODEL_FILE) self.assertIsFile(settings.MODEL_FILE)
mtime3 = Path(settings.MODEL_FILE).stat().st_mtime mtime3 = os.stat(settings.MODEL_FILE).st_mtime
self.assertNotEqual(mtime2, mtime3) self.assertNotEqual(mtime2, mtime3)

View File

@ -1,6 +1,6 @@
import os
import tempfile import tempfile
from datetime import timedelta from datetime import timedelta
from pathlib import Path
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
@ -107,12 +107,12 @@ class TestViews(DirectoriesMixin, TestCase):
content = b"This is a test" content = b"This is a test"
with Path(filename).open("wb") as f: with open(filename, "wb") as f:
f.write(content) f.write(content)
doc = Document.objects.create( doc = Document.objects.create(
title="none", title="none",
filename=Path(filename).name, filename=os.path.basename(filename),
mime_type="application/pdf", mime_type="application/pdf",
) )

View File

@ -133,9 +133,6 @@ class TestWorkflows(
action.assign_change_groups.add(self.group1.pk) action.assign_change_groups.add(self.group1.pk)
action.assign_custom_fields.add(self.cf1.pk) action.assign_custom_fields.add(self.cf1.pk)
action.assign_custom_fields.add(self.cf2.pk) action.assign_custom_fields.add(self.cf2.pk)
action.assign_custom_fields_values = {
self.cf2.pk: 42,
}
action.save() action.save()
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
@ -212,10 +209,6 @@ class TestWorkflows(
list(document.custom_fields.all().values_list("field", flat=True)), list(document.custom_fields.all().values_list("field", flat=True)),
[self.cf1.pk, self.cf2.pk], [self.cf1.pk, self.cf2.pk],
) )
self.assertEqual(
document.custom_fields.get(field=self.cf2.pk).value,
42,
)
info = cm.output[0] info = cm.output[0]
expected_str = f"Document matched {trigger} from {w}" expected_str = f"Document matched {trigger} from {w}"
@ -1222,11 +1215,11 @@ class TestWorkflows(
def test_document_updated_workflow_existing_custom_field(self): def test_document_updated_workflow_existing_custom_field(self):
""" """
GIVEN: GIVEN:
- Existing workflow with UPDATED trigger and action that assigns a custom field with a value - Existing workflow with UPDATED trigger and action that adds a custom field
WHEN: WHEN:
- Document is updated that already contains the field - Document is updated that already contains the field
THEN: THEN:
- Document update succeeds and updates the field - Document update succeeds without trying to re-create the field
""" """
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
@ -1234,8 +1227,6 @@ class TestWorkflows(
) )
action = WorkflowAction.objects.create() action = WorkflowAction.objects.create()
action.assign_custom_fields.add(self.cf1) action.assign_custom_fields.add(self.cf1)
action.assign_custom_fields_values = {self.cf1.pk: "new value"}
action.save()
w = Workflow.objects.create( w = Workflow.objects.create(
name="Workflow 1", name="Workflow 1",
order=0, order=0,
@ -1260,9 +1251,6 @@ class TestWorkflows(
format="json", format="json",
) )
doc.refresh_from_db()
self.assertEqual(doc.custom_fields.get(field=self.cf1).value, "new value")
def test_document_updated_workflow_merge_permissions(self): def test_document_updated_workflow_merge_permissions(self):
""" """
GIVEN: GIVEN:

View File

@ -1471,10 +1471,7 @@ class PostDocumentView(GenericAPIView):
created=created, created=created,
asn=archive_serial_number, asn=archive_serial_number,
owner_id=request.user.id, owner_id=request.user.id,
# TODO: set values custom_field_ids=custom_field_ids,
custom_fields={cf_id: None for cf_id in custom_field_ids}
if custom_field_ids
else None,
) )
async_task = consume_file.delay( async_task = consume_file.delay(

File diff suppressed because it is too large Load Diff

View File

@ -38,9 +38,9 @@ class TestChecks(DirectoriesMixin, TestCase):
self.assertTrue(msg.msg.endswith("is set but doesn't exist.")) self.assertTrue(msg.msg.endswith("is set but doesn't exist."))
def test_paths_check_no_access(self): def test_paths_check_no_access(self):
Path(self.dirs.data_dir).chmod(0o000) os.chmod(self.dirs.data_dir, 0o000)
Path(self.dirs.media_dir).chmod(0o000) os.chmod(self.dirs.media_dir, 0o000)
Path(self.dirs.consumption_dir).chmod(0o000) os.chmod(self.dirs.consumption_dir, 0o000)
self.addCleanup(os.chmod, self.dirs.data_dir, 0o777) self.addCleanup(os.chmod, self.dirs.data_dir, 0o777)
self.addCleanup(os.chmod, self.dirs.media_dir, 0o777) self.addCleanup(os.chmod, self.dirs.media_dir, 0o777)

View File

@ -1,4 +1,4 @@
from pathlib import Path import os
from allauth.account import views as allauth_account_views from allauth.account import views as allauth_account_views
from allauth.mfa.base import views as allauth_mfa_views from allauth.mfa.base import views as allauth_mfa_views
@ -270,7 +270,7 @@ urlpatterns = [
re_path( re_path(
r"^logo(?P<path>.*)$", r"^logo(?P<path>.*)$",
serve, serve,
kwargs={"document_root": Path(settings.MEDIA_ROOT) / "logo"}, kwargs={"document_root": os.path.join(settings.MEDIA_ROOT, "logo")},
), ),
# allauth # allauth
path( path(
@ -278,15 +278,10 @@ urlpatterns = [
include( include(
[ [
# see allauth/account/urls.py # see allauth/account/urls.py
# login, logout, signup, account_inactive # login, logout, signup
path("login/", allauth_account_views.login, name="account_login"), path("login/", allauth_account_views.login, name="account_login"),
path("logout/", allauth_account_views.logout, name="account_logout"), path("logout/", allauth_account_views.logout, name="account_logout"),
path("signup/", allauth_account_views.signup, name="account_signup"), path("signup/", allauth_account_views.signup, name="account_signup"),
path(
"account_inactive/",
allauth_account_views.account_inactive,
name="account_inactive",
),
# password reset # password reset
path( path(
"password/", "password/",

View File

@ -1,8 +1,8 @@
import abc import abc
import os
from email import message_from_bytes from email import message_from_bytes
from email import policy from email import policy
from email.message import Message from email.message import Message
from pathlib import Path
from django.conf import settings from django.conf import settings
from gnupg import GPG from gnupg import GPG
@ -50,7 +50,7 @@ class MailMessageDecryptor(MailMessagePreprocessor, LoggingMixin):
return False return False
if settings.EMAIL_GNUPG_HOME is None: if settings.EMAIL_GNUPG_HOME is None:
return True return True
return Path(settings.EMAIL_GNUPG_HOME).is_dir() return os.path.isdir(settings.EMAIL_GNUPG_HOME)
def run(self, message: MailMessage) -> MailMessage: def run(self, message: MailMessage) -> MailMessage:
if not hasattr(message, "obj"): if not hasattr(message, "obj"):

View File

@ -159,7 +159,7 @@ class RasterisedDocumentParser(DocumentParser):
# the whole text, so do not utilize it in that case # the whole text, so do not utilize it in that case
if ( if (
sidecar_file is not None sidecar_file is not None
and sidecar_file.is_file() and os.path.isfile(sidecar_file)
and self.settings.mode != "redo" and self.settings.mode != "redo"
): ):
text = self.read_file_handle_unicode_errors(sidecar_file) text = self.read_file_handle_unicode_errors(sidecar_file)
@ -174,7 +174,7 @@ class RasterisedDocumentParser(DocumentParser):
# no success with the sidecar file, try PDF # no success with the sidecar file, try PDF
if not Path(pdf_file).is_file(): if not os.path.isfile(pdf_file):
return None return None
try: try:
@ -368,8 +368,8 @@ class RasterisedDocumentParser(DocumentParser):
from ocrmypdf import SubprocessOutputError from ocrmypdf import SubprocessOutputError
from ocrmypdf.exceptions import DigitalSignatureError from ocrmypdf.exceptions import DigitalSignatureError
archive_path = Path(self.tempdir) / "archive.pdf" archive_path = Path(os.path.join(self.tempdir, "archive.pdf"))
sidecar_file = Path(self.tempdir) / "sidecar.txt" sidecar_file = Path(os.path.join(self.tempdir, "sidecar.txt"))
args = self.construct_ocrmypdf_parameters( args = self.construct_ocrmypdf_parameters(
document_path, document_path,
@ -412,8 +412,12 @@ class RasterisedDocumentParser(DocumentParser):
f"Attempting force OCR to get the text.", f"Attempting force OCR to get the text.",
) )
archive_path_fallback = Path(self.tempdir) / "archive-fallback.pdf" archive_path_fallback = Path(
sidecar_file_fallback = Path(self.tempdir) / "sidecar-fallback.txt" os.path.join(self.tempdir, "archive-fallback.pdf"),
)
sidecar_file_fallback = Path(
os.path.join(self.tempdir, "sidecar-fallback.txt"),
)
# Attempt to run OCR with safe settings. # Attempt to run OCR with safe settings.

View File

@ -75,7 +75,7 @@ class TestTikaParserAgainstServer:
== "This is an DOCX test document, also made September 14, 2022" == "This is an DOCX test document, also made September 14, 2022"
) )
assert tika_parser.archive_path is not None assert tika_parser.archive_path is not None
with Path(tika_parser.archive_path).open("rb") as f: with open(tika_parser.archive_path, "rb") as f:
assert b"PDF-" in f.read()[:10] assert b"PDF-" in f.read()[:10]
# self.assertEqual(tika_parser.date, datetime.datetime(2022, 9, 14)) # self.assertEqual(tika_parser.date, datetime.datetime(2022, 9, 14))
@ -104,7 +104,7 @@ class TestTikaParserAgainstServer:
in tika_parser.text in tika_parser.text
) )
assert tika_parser.archive_path is not None assert tika_parser.archive_path is not None
with Path(tika_parser.archive_path).open("rb") as f: with open(tika_parser.archive_path, "rb") as f:
assert b"PDF-" in f.read()[:10] assert b"PDF-" in f.read()[:10]
def test_tika_fails_multi_part( def test_tika_fails_multi_part(
@ -130,5 +130,5 @@ class TestTikaParserAgainstServer:
) )
assert tika_parser.archive_path is not None assert tika_parser.archive_path is not None
with Path(tika_parser.archive_path).open("rb") as f: with open(tika_parser.archive_path, "rb") as f:
assert b"PDF-" in f.read()[:10] assert b"PDF-" in f.read()[:10]

View File

@ -38,7 +38,7 @@ class TestTikaParser:
assert tika_parser.text == "the content" assert tika_parser.text == "the content"
assert tika_parser.archive_path is not None assert tika_parser.archive_path is not None
with Path(tika_parser.archive_path).open("rb") as f: with open(tika_parser.archive_path, "rb") as f:
assert f.read() == b"PDF document" assert f.read() == b"PDF document"
assert tika_parser.date == datetime.datetime( assert tika_parser.date == datetime.datetime(