Compare commits

...
Author SHA1 Message Date
shamoon 7d74177c55 Enhancement: option to stop processing further mail rules 2025-02-24 09:00:47 -08:00
shamoonandGitHub a548c32c1f Enhancement: allow disabling the filesystem consumer (#9199) 2025-02-23 13:52:41 -08:00
shamoonandGitHub ea911e73c6 Fix: correct split confirm removal (#9195) 2025-02-22 07:27:44 -08:00
Max MehlandGitHub 6b7fb286f7 Chore: bump gotenberg docker images (#9189)
* Chore: update gotenberg Docker images to latest minor version

* Chore: update gotenberg Docker images to latest minor version for devcontainer
2025-02-21 13:29:21 -08:00
Andy GrunwaldandGitHub b40479632b Development: Fix ImageMagick policy.xml path in devcontainer setup (#9188) 2025-02-21 18:20:40 +00:00
shamoonandGitHub c122c60d3f Feature: email document button (#8950) 2025-02-21 16:44:03 +00:00
40 changed files with 1031 additions and 370 deletions
+2 -2
View File
@@ -123,13 +123,13 @@ RUN set -eux \
WORKDIR /usr/src/paperless/src/docker/
COPY [ \
"docker/imagemagick-policy.xml", \
"docker/rootfs/etc/ImageMagick-6/paperless-policy.xml", \
"./" \
]
RUN set -eux \
&& echo "Configuring ImageMagick" \
&& mv imagemagick-policy.xml /etc/ImageMagick-6/policy.xml
&& mv paperless-policy.xml /etc/ImageMagick-6/policy.xml
# Packages needed only for building a few quick Python
# dependencies
@@ -65,7 +65,7 @@ services:
command: /bin/sh -c "chown -R paperless:paperless /usr/src/paperless/paperless-ngx/src/documents/static/frontend && chown -R paperless:paperless /usr/src/paperless/paperless-ngx/.ruff_cache && while sleep 1000; do :; done"
gotenberg:
image: docker.io/gotenberg/gotenberg:7.10
image: docker.io/gotenberg/gotenberg:8.17
restart: unless-stopped
# The Gotenberg Chromium route is used to convert .eml files. We do not
+1 -1
View File
@@ -5,7 +5,7 @@
services:
gotenberg:
image: docker.io/gotenberg/gotenberg:8.7
image: docker.io/gotenberg/gotenberg:8.17
hostname: gotenberg
container_name: gotenberg
network_mode: host
@@ -77,7 +77,7 @@ services:
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.7
image: docker.io/gotenberg/gotenberg:8.17
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
@@ -71,7 +71,7 @@ services:
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.7
image: docker.io/gotenberg/gotenberg:8.17
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
@@ -59,7 +59,7 @@ services:
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.7
image: docker.io/gotenberg/gotenberg:8.17
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
@@ -1,10 +1,18 @@
#!/command/with-contenv /usr/bin/bash
# shellcheck shell=bash
cd ${PAPERLESS_SRC_DIR}
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
exec python3 manage.py document_consumer
if [[ -n "${PAPERLESS_CONSUMER_DISABLE}" ]]; then
echo "[svc-consumer] Consumer is disabled, exiting"
# https://skarnet.org/software/s6/s6-svc.html
s6-svc -Od .
else
exec s6-setuidgid paperless python3 manage.py document_consumer
cd ${PAPERLESS_SRC_DIR}
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
exec python3 manage.py document_consumer
else
exec s6-setuidgid paperless python3 manage.py document_consumer
fi
fi
+5
View File
@@ -1030,6 +1030,11 @@ be used with caution!
## Document Consumption {#consume_config}
#### [`PAPERLESS_CONSUMER_DISABLE=<bool>`](#PAPERLESS_CONSUMER_DISABLE) {#PAPERLESS_CONSUMER_DISABLE}
: Completely disable the directory-based consumer in docker. If you don't plan to consume documents
via the consumption directory, you can disable the consumer to save resources.
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
: When the consumer detects a duplicate document, it will not touch
+2
View File
@@ -714,6 +714,8 @@ the Pi and configuring some options in paperless can help improve
performance immensely:
- Stick with SQLite to save some resources.
- If you do not need the filesystem-based consumer, consider disabling it
entirely by setting [`PAPERLESS_CONSUMER_DISABLE`](configuration.md#PAPERLESS_CONSUMER_DISABLE) to `true`.
- Consider setting [`PAPERLESS_OCR_PAGES`](configuration.md#PAPERLESS_OCR_PAGES) to 1, so that paperless will
only OCR the first page of your documents. In most cases, this page
contains enough information to be able to find it.
+223 -135
View File
@@ -385,7 +385,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">100</context>
<context context-type="linenumber">117</context>
</context-group>
</trans-unit>
<trans-unit id="1241348629231510663" datatype="html">
@@ -534,7 +534,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">353</context>
<context context-type="linenumber">370</context>
</context-group>
</trans-unit>
<trans-unit id="3768927257183755959" datatype="html">
@@ -569,7 +569,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">76</context>
<context context-type="linenumber">81</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
@@ -593,7 +593,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">346</context>
<context context-type="linenumber">363</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/custom-fields-bulk-edit-dialog/custom-fields-bulk-edit-dialog.component.html</context>
@@ -739,7 +739,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">366</context>
<context context-type="linenumber">383</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@@ -1190,7 +1190,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">322</context>
<context context-type="linenumber">339</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
@@ -1453,7 +1453,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">75</context>
<context context-type="linenumber">80</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
@@ -1736,7 +1736,7 @@
</context-group>
<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="linenumber">83</context>
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@@ -2077,8 +2077,8 @@
<context context-type="linenumber">19</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">37</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">36</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
@@ -3391,7 +3391,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">94</context>
<context context-type="linenumber">111</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
@@ -3543,7 +3543,7 @@
</context-group>
<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="linenumber">79</context>
<context context-type="linenumber">83</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@@ -3890,18 +3890,11 @@
<context context-type="linenumber">113</context>
</context-group>
</trans-unit>
<trans-unit id="220550782947016929" datatype="html">
<source>Order</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="4816216590591222133" datatype="html">
<source>Enabled</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">22</context>
<context context-type="linenumber">19</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
@@ -3916,155 +3909,176 @@
<context context-type="linenumber">41</context>
</context-group>
</trans-unit>
<trans-unit id="220550782947016929" datatype="html">
<source>Order</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit id="1504364192557145528" datatype="html">
<source>Stop further processing</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="6164556674897067551" datatype="html">
<source>Stop processing further rules if this rule queues any document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="4348351765075925931" datatype="html">
<source>Paperless will only process mails that match <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;em&gt;"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;/em&gt;"/> of the criteria specified below.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="7046259383943324039" datatype="html">
<source>Folder</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">29</context>
<context context-type="linenumber">34</context>
</context-group>
</trans-unit>
<trans-unit id="1391527525114848695" datatype="html">
<source>Subfolders must be separated by a delimiter, often a dot (&apos;.&apos;) or slash (&apos;/&apos;), but it varies by mail server.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">29</context>
<context context-type="linenumber">34</context>
</context-group>
</trans-unit>
<trans-unit id="101686279614365671" datatype="html">
<source>Maximum age (days)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">30</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="6925928412364847639" datatype="html">
<source>Filter from</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">33</context>
<context context-type="linenumber">38</context>
</context-group>
</trans-unit>
<trans-unit id="8977094263269822022" datatype="html">
<source>Filter to</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">34</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="8497813481090627874" datatype="html">
<source>Filter subject</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">40</context>
</context-group>
</trans-unit>
<trans-unit id="7314357616097563149" datatype="html">
<source>Filter body</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">36</context>
<context context-type="linenumber">41</context>
</context-group>
</trans-unit>
<trans-unit id="559099472394646919" datatype="html">
<source>Consumption scope</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">47</context>
</context-group>
</trans-unit>
<trans-unit id="56643687972548912" datatype="html">
<source>See docs for .eml processing requirements</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">47</context>
</context-group>
</trans-unit>
<trans-unit id="7093509971705471817" datatype="html">
<source>Attachment type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">43</context>
<context context-type="linenumber">48</context>
</context-group>
</trans-unit>
<trans-unit id="3842519365862452117" datatype="html">
<source>PDF layout</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">44</context>
<context context-type="linenumber">49</context>
</context-group>
</trans-unit>
<trans-unit id="2873939123535615966" datatype="html">
<source>Include only files matching</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">47</context>
<context context-type="linenumber">52</context>
</context-group>
</trans-unit>
<trans-unit id="7233407036155150477" datatype="html">
<source>Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">47</context>
<context context-type="linenumber">52</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">48</context>
<context context-type="linenumber">53</context>
</context-group>
</trans-unit>
<trans-unit id="1546332577833742677" datatype="html">
<source>Exclude files matching</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">48</context>
<context context-type="linenumber">53</context>
</context-group>
</trans-unit>
<trans-unit id="9216117865911519658" datatype="html">
<source>Action</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">54</context>
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7841986067387421166" datatype="html">
<source>Only performed if the mail is processed.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">54</context>
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="1261794314435932203" datatype="html">
<source>Action parameter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">61</context>
</context-group>
</trans-unit>
<trans-unit id="6093797930511670257" datatype="html">
<source>Assign title from</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">58</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit id="5232720756589450549" datatype="html">
<source>Assign owner from rule</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">59</context>
<context context-type="linenumber">64</context>
</context-group>
</trans-unit>
<trans-unit id="6695990587380209737" datatype="html">
<source>Assign document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">63</context>
<context context-type="linenumber">68</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
@@ -4075,14 +4089,14 @@
<source>Assign correspondent from</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">64</context>
<context context-type="linenumber">69</context>
</context-group>
</trans-unit>
<trans-unit id="4875491778188965469" datatype="html">
<source>Assign correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">66</context>
<context context-type="linenumber">71</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
@@ -4093,7 +4107,7 @@
<source>Error</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">73</context>
<context context-type="linenumber">78</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
@@ -4288,7 +4302,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">288</context>
<context context-type="linenumber">305</context>
</context-group>
</trans-unit>
<trans-unit id="8057014866157903311" datatype="html">
@@ -4384,12 +4398,16 @@
</context-group>
<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="linenumber">125</context>
<context context-type="linenumber">129</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
<context context-type="linenumber">10</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="5342432350421167093" datatype="html">
<source>First name</source>
@@ -4980,11 +4998,18 @@
<context context-type="linenumber">72</context>
</context-group>
</trans-unit>
<trans-unit id="235571817610183244" datatype="html">
<source>Web UI</source>
<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="linenumber">76</context>
</context-group>
</trans-unit>
<trans-unit id="3553216189604488439" datatype="html">
<source>Modified</source>
<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="linenumber">87</context>
<context context-type="linenumber">91</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
@@ -4995,70 +5020,126 @@
<source>Custom Field</source>
<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="linenumber">91</context>
<context context-type="linenumber">95</context>
</context-group>
</trans-unit>
<trans-unit id="8696908693776094667" datatype="html">
<source>Consumption Started</source>
<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="linenumber">98</context>
<context context-type="linenumber">102</context>
</context-group>
</trans-unit>
<trans-unit id="7858311467093621703" datatype="html">
<source>Document Added</source>
<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="linenumber">102</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="7955486237346046731" datatype="html">
<source>Document Updated</source>
<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="linenumber">106</context>
<context context-type="linenumber">110</context>
</context-group>
</trans-unit>
<trans-unit id="9172233176401579786" datatype="html">
<source>Scheduled</source>
<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="linenumber">110</context>
<context context-type="linenumber">114</context>
</context-group>
</trans-unit>
<trans-unit id="5502398334173581061" datatype="html">
<source>Assignment</source>
<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="linenumber">117</context>
<context context-type="linenumber">121</context>
</context-group>
</trans-unit>
<trans-unit id="6234812824772766804" datatype="html">
<source>Removal</source>
<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="linenumber">121</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit id="4206419737792796794" datatype="html">
<source>Webhook</source>
<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="linenumber">129</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit id="3138206142174978019" datatype="html">
<source>Create new workflow</source>
<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="linenumber">225</context>
<context context-type="linenumber">229</context>
</context-group>
</trans-unit>
<trans-unit id="5996779210524133604" datatype="html">
<source>Edit workflow</source>
<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="linenumber">229</context>
<context context-type="linenumber">233</context>
</context-group>
</trans-unit>
<trans-unit id="7376342558017986274" datatype="html">
<source>Email address(es)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
<context context-type="linenumber">7</context>
</context-group>
</trans-unit>
<trans-unit id="9127604588498960753" datatype="html">
<source>Subject</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="8066608938393600549" datatype="html">
<source>Message</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
<context context-type="linenumber">15</context>
</context-group>
</trans-unit>
<trans-unit id="5867799091834207531" datatype="html">
<source>Use archive version</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="4312183290449350804" datatype="html">
<source>Send email</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="1342170399958833675" datatype="html">
<source>Email Document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="9049148856403142491" datatype="html">
<source>Email sent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit id="3742745894977668908" datatype="html">
<source>Error emailing document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.ts</context>
<context context-type="linenumber">69</context>
</context-group>
</trans-unit>
<trans-unit id="6381578200008167206" datatype="html">
@@ -5082,8 +5163,8 @@
<context context-type="linenumber">58</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">64</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">65</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
@@ -5543,8 +5624,8 @@
<context context-type="linenumber">155</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">29</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">28</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
@@ -5585,8 +5666,8 @@
<context context-type="linenumber">162</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">40</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="4369881772624105142" datatype="html">
@@ -5765,103 +5846,103 @@
<context context-type="linenumber">320</context>
</context-group>
</trans-unit>
<trans-unit id="686374493515618129" datatype="html">
<source>Share Links</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="6617773613987957957" datatype="html">
<source> No existing links </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">9,11</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">8,10</context>
</context-group>
</trans-unit>
<trans-unit id="7419704019640008953" datatype="html">
<source>Share</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">33</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="6811921365829755679" datatype="html">
<source>Share archive version</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">47</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">48</context>
</context-group>
</trans-unit>
<trans-unit id="8037476586059399916" datatype="html">
<source>Expires</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context>
<context context-type="linenumber">51</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
<context context-type="linenumber">52</context>
</context-group>
</trans-unit>
<trans-unit id="4776429682428363094" datatype="html">
<source>1 day</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">25</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">20</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">111</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">104</context>
</context-group>
</trans-unit>
<trans-unit id="8542568275115626925" datatype="html">
<source>7 days</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">26</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="7152095234138763013" datatype="html">
<source>30 days</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">27</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit id="8372007266188249803" datatype="html">
<source>Never</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">28</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="686374493515618129" datatype="html">
<source>Share Links</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">27</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit id="3429210839568770054" datatype="html">
<source>Error retrieving links</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">92</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">85</context>
</context-group>
</trans-unit>
<trans-unit id="3242255798983858463" datatype="html">
<source><x id="PH" equiv-text="days"/> days</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">111</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">104</context>
</context-group>
</trans-unit>
<trans-unit id="2897042887615940599" datatype="html">
<source>Error deleting link</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">140</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit id="8400747326190565173" datatype="html">
<source>Error creating link</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context>
<context context-type="linenumber">168</context>
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.ts</context>
<context context-type="linenumber">161</context>
</context-group>
</trans-unit>
<trans-unit id="9180110319941008393" datatype="html">
@@ -6379,25 +6460,32 @@
<context context-type="linenumber">70</context>
</context-group>
</trans-unit>
<trans-unit id="6490688569532630280" datatype="html">
<source>Send</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">88</context>
</context-group>
</trans-unit>
<trans-unit id="4452427314943113135" datatype="html">
<source>Previous</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">97</context>
<context context-type="linenumber">114</context>
</context-group>
</trans-unit>
<trans-unit id="5028777105388019087" datatype="html">
<source>Details</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">110</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5701618810648052610" datatype="html">
<source>Title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">113</context>
<context context-type="linenumber">130</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@@ -6420,21 +6508,21 @@
<source>Archive serial number</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">114</context>
<context context-type="linenumber">131</context>
</context-group>
</trans-unit>
<trans-unit id="5114742157723900905" datatype="html">
<source>Date created</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">115</context>
<context context-type="linenumber">132</context>
</context-group>
</trans-unit>
<trans-unit id="2691296884221415710" datatype="html">
<source>Correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">117</context>
<context context-type="linenumber">134</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
@@ -6461,7 +6549,7 @@
<source>Document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">119</context>
<context context-type="linenumber">136</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
@@ -6488,7 +6576,7 @@
<source>Storage path</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">121</context>
<context context-type="linenumber">138</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
@@ -6511,7 +6599,7 @@
<source>Default</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">122</context>
<context context-type="linenumber">139</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
@@ -6522,14 +6610,14 @@
<source>Content</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">218</context>
<context context-type="linenumber">235</context>
</context-group>
</trans-unit>
<trans-unit id="218403386307979629" datatype="html">
<source>Metadata</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">227</context>
<context context-type="linenumber">244</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts</context>
@@ -6540,119 +6628,119 @@
<source>Date modified</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">234</context>
<context context-type="linenumber">251</context>
</context-group>
</trans-unit>
<trans-unit id="6392918669949841614" datatype="html">
<source>Date added</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">238</context>
<context context-type="linenumber">255</context>
</context-group>
</trans-unit>
<trans-unit id="146828917013192897" datatype="html">
<source>Media filename</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">242</context>
<context context-type="linenumber">259</context>
</context-group>
</trans-unit>
<trans-unit id="4500855521601039868" datatype="html">
<source>Original filename</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">246</context>
<context context-type="linenumber">263</context>
</context-group>
</trans-unit>
<trans-unit id="7985558498848210210" datatype="html">
<source>Original MD5 checksum</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">250</context>
<context context-type="linenumber">267</context>
</context-group>
</trans-unit>
<trans-unit id="5888243105821763422" datatype="html">
<source>Original file size</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">254</context>
<context context-type="linenumber">271</context>
</context-group>
</trans-unit>
<trans-unit id="2696647325713149563" datatype="html">
<source>Original mime type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">258</context>
<context context-type="linenumber">275</context>
</context-group>
</trans-unit>
<trans-unit id="342875990758166588" datatype="html">
<source>Archive MD5 checksum</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">263</context>
<context context-type="linenumber">280</context>
</context-group>
</trans-unit>
<trans-unit id="6033581412811562084" datatype="html">
<source>Archive file size</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">269</context>
<context context-type="linenumber">286</context>
</context-group>
</trans-unit>
<trans-unit id="6992781481378431874" datatype="html">
<source>Original document metadata</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">278</context>
<context context-type="linenumber">295</context>
</context-group>
</trans-unit>
<trans-unit id="2846565152091361585" datatype="html">
<source>Archived document metadata</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">281</context>
<context context-type="linenumber">298</context>
</context-group>
</trans-unit>
<trans-unit id="7206723502037428235" datatype="html">
<source>Notes <x id="START_BLOCK_IF" equiv-text="@if (document?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">300,303</context>
<context context-type="linenumber">317,320</context>
</context-group>
</trans-unit>
<trans-unit id="186236568870281953" datatype="html">
<source>History</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">311</context>
<context context-type="linenumber">328</context>
</context-group>
</trans-unit>
<trans-unit id="5129524307369213584" datatype="html">
<source>Save &amp; next</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">348</context>
<context context-type="linenumber">365</context>
</context-group>
</trans-unit>
<trans-unit id="4910102545766233758" datatype="html">
<source>Save &amp; close</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">368</context>
</context-group>
</trans-unit>
<trans-unit id="1309556917227148591" datatype="html">
<source>Document loading...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">361</context>
<context context-type="linenumber">378</context>
</context-group>
</trans-unit>
<trans-unit id="8191371354890763172" datatype="html">
<source>Enter Password</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">415</context>
<context context-type="linenumber">432</context>
</context-group>
</trans-unit>
<trans-unit id="2218903673684131427" datatype="html">
@@ -6949,11 +7037,11 @@
<source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1461</context>
<context context-type="linenumber">1481</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">1465</context>
<context context-type="linenumber">1485</context>
</context-group>
</trans-unit>
<trans-unit id="4958946940233632319" datatype="html">
@@ -9659,28 +9747,28 @@
<source>Connecting...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit id="1245343823699368872" datatype="html">
<source>Uploading...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
<context context-type="linenumber">54</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="7446520539098045935" datatype="html">
<source>Upload complete, waiting...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
<context context-type="linenumber">57</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="1405142710727603568" datatype="html">
<source>HTTP error: <x id="PH" equiv-text="error.status"/> <x id="PH_1" equiv-text="error.statusText"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
<context context-type="linenumber">70</context>
<context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit id="2119857572761283468" datatype="html">
@@ -84,7 +84,7 @@ export class SplitConfirmDialogComponent
addSplit() {
if (this.page === this.totalPages) return
this.pages.add(this.page)
this.pages = new Set(Array.from(this.pages).sort())
this.pages = new Set(Array.from(this.pages).sort((a, b) => a - b))
this.confirmButtonEnabled = this.pages.size > 0
}
@@ -9,19 +9,24 @@
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-4">
<div class="col-md-6">
<pngx-input-text [horizontal]="true" i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
</div>
<div class="col-md-3">
<div class="col-md-4">
<pngx-input-select [horizontal]="true" i18n-title title="Account" [items]="accounts" formControlName="account"></pngx-input-select>
</div>
<div class="col-md-3">
<pngx-input-number [horizontal]="true" i18n-title title="Order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
</div>
<div class="col-md-2 pt-2">
<pngx-input-switch [horizontal]="true" i18n-title title="Enabled" formControlName="enabled"></pngx-input-switch>
</div>
</div>
<div class="row">
<div class="col-md-6">
<pngx-input-number [horizontal]="true" i18n-title title="Order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
</div>
<div class="col-md-6">
<pngx-input-switch [horizontal]="true" i18n-title title="Stop further processing" formControlName="stop_processing" i18n-hint hint="Stop processing further rules if this rule queues any document(s)."></pngx-input-switch>
</div>
</div>
<hr class="mt-0"/>
<div class="row">
<p class="small" i18n>Paperless will only process mails that match <em>all</em> of the criteria specified below.</p>
@@ -221,6 +221,7 @@ export class MailRuleEditDialogComponent extends EditDialogComponent<MailRule> {
),
assign_correspondent: new FormControl(null),
assign_owner_from_rule: new FormControl(true),
stop_processing: new FormControl(false),
})
}
@@ -0,0 +1,32 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
</div>
<div class="modal-body">
<div class="mb-1">
<label for="email" class="form-label" i18n>Email address(es)</label>
<input type="email" class="form-control" id="email" [(ngModel)]="emailAddress">
</div>
<div class="mb-1">
<label for="email" class="form-label" i18n>Subject</label>
<input type="email" class="form-control" id="subject" [(ngModel)]="emailSubject">
</div>
<div>
<label for="message" class="form-label" i18n>Message</label>
<textarea class="form-control" id="message" rows="3" [(ngModel)]="emailMessage"></textarea>
</div>
</div>
<div class="modal-footer">
<div class="input-group">
<div class="input-group-text flex-grow-1">
<input class="form-check-input mt-0 me-2" type="checkbox" role="switch" id="useArchiveVersion" [disabled]="!hasArchiveVersion" [(ngModel)]="useArchiveVersion">
<label class="form-check-label w-100 text-start" for="useArchiveVersion" i18n>Use archive version</label>
</div>
<button type="submit" class="btn btn-outline-primary" (click)="emailDocument()" [disabled]="loading || emailAddress.length === 0 || emailMessage.length === 0 || emailSubject.length === 0">
@if (loading) {
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
}
<ng-container i18n>Send email</ng-container>
</button>
</div>
</div>
@@ -0,0 +1,72 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ToastService } from 'src/app/services/toast.service'
import { EmailDocumentDialogComponent } from './email-document-dialog.component'
describe('EmailDocumentDialogComponent', () => {
let component: EmailDocumentDialogComponent
let fixture: ComponentFixture<EmailDocumentDialogComponent>
let documentService: DocumentService
let permissionsService: PermissionsService
let toastService: ToastService
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
EmailDocumentDialogComponent,
IfPermissionsDirective,
NgxBootstrapIconsModule.pick(allIcons),
],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
NgbActiveModal,
],
}).compileComponents()
fixture = TestBed.createComponent(EmailDocumentDialogComponent)
documentService = TestBed.inject(DocumentService)
toastService = TestBed.inject(ToastService)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should set hasArchiveVersion and useArchiveVersion', () => {
expect(component.hasArchiveVersion).toBeTruthy()
component.hasArchiveVersion = false
expect(component.hasArchiveVersion).toBeFalsy()
expect(component.useArchiveVersion).toBeFalsy()
})
it('should support sending document via email, showing error if needed', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastSuccessSpy = jest.spyOn(toastService, 'showInfo')
component.emailAddress = 'hello@paperless-ngx.com'
component.emailSubject = 'Hello'
component.emailMessage = 'World'
jest
.spyOn(documentService, 'emailDocument')
.mockReturnValue(throwError(() => new Error('Unable to email document')))
component.emailDocument()
expect(toastErrorSpy).toHaveBeenCalled()
jest.spyOn(documentService, 'emailDocument').mockReturnValue(of(true))
component.emailDocument()
expect(toastSuccessSpy).toHaveBeenCalled()
})
it('should close the dialog', () => {
const activeModal = TestBed.inject(NgbActiveModal)
const closeSpy = jest.spyOn(activeModal, 'close')
component.close()
expect(closeSpy).toHaveBeenCalled()
})
})
@@ -0,0 +1,77 @@
import { Component, Input } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ToastService } from 'src/app/services/toast.service'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
@Component({
selector: 'pngx-email-document-dialog',
templateUrl: './email-document-dialog.component.html',
styleUrl: './email-document-dialog.component.scss',
imports: [FormsModule, NgxBootstrapIconsModule],
})
export class EmailDocumentDialogComponent extends LoadingComponentWithPermissions {
@Input()
title = $localize`Email Document`
@Input()
documentId: number
private _hasArchiveVersion: boolean = true
@Input()
set hasArchiveVersion(value: boolean) {
this._hasArchiveVersion = value
this.useArchiveVersion = value
}
get hasArchiveVersion(): boolean {
return this._hasArchiveVersion
}
public useArchiveVersion: boolean = true
public emailAddress: string = ''
public emailSubject: string = ''
public emailMessage: string = ''
constructor(
private activeModal: NgbActiveModal,
private documentService: DocumentService,
private toastService: ToastService
) {
super()
this.loading = false
}
public emailDocument() {
this.loading = true
this.documentService
.emailDocument(
this.documentId,
this.emailAddress,
this.emailSubject,
this.emailMessage,
this.useArchiveVersion
)
.subscribe({
next: () => {
this.loading = false
this.emailAddress = ''
this.emailSubject = ''
this.emailMessage = ''
this.toastService.showInfo($localize`Email sent`)
},
error: (e) => {
this.loading = false
this.toastService.showError($localize`Error emailing document`, e)
},
})
}
public close() {
this.activeModal.close()
}
}
@@ -0,0 +1,68 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
</div>
<div class="modal-body p-0">
<ul class="list-group list-group-flush">
@if (!shareLinks || shareLinks.length === 0) {
<li class="list-group-item fst-italic small text-center text-secondary" i18n>
No existing links
</li>
}
@for (link of shareLinks; track link) {
<li class="list-group-item">
<div class="input-group w-100">
<input type="text" class="form-control" aria-label="Share link" [value]="getShareUrl(link)" readonly>
@if (link.expiration) {
<span class="input-group-text">
{{ getDaysRemaining(link) }}
</span>
}
<button type="button" class="btn btn-outline-primary" (click)="copy(link)">
@if (copied !== link.id) {
<i-bs width="1.2em" height="1.2em" name="clipboard-fill"></i-bs>
}
@if (copied === link.id) {
<i-bs width="1.2em" height="1.2em" name="clipboard-check-fill"></i-bs>
}
<span class="visually-hidden" i18n>Copy</span>
</button>
@if (canShare(link)) {
<button type="button" class="btn btn-outline-primary" (click)="share(link)">
<i-bs width="1.2em" height="1.2em" name="box-arrow-up"></i-bs><span class="visually-hidden" i18n>Share</span>
</button>
}
<button type="button" class="btn btn-outline-danger" (click)="delete(link)">
<i-bs width="1.2em" height="1.2em" name="trash"></i-bs><span class="visually-hidden" i18n>Delete</span>
</button>
</div>
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied === link.id" i18n>Copied!</span>
</li>
}
</ul>
</div>
<div class="modal-footer">
<div class="input-group w-100">
<div class="form-check form-switch ms-auto">
<input class="form-check-input" type="checkbox" role="switch" id="versionSwitch" [disabled]="!hasArchiveVersion" [(ngModel)]="useArchiveVersion">
<label class="form-check-label" for="versionSwitch" i18n>Share archive version</label>
</div>
</div>
<div class="input-group w-100 mt-2">
<label class="input-group-text" for="addLink"><ng-container i18n>Expires</ng-container>:</label>
<select class="form-select fs-6" [(ngModel)]="expirationDays">
@for (option of EXPIRATION_OPTIONS; track option) {
<option [ngValue]="option.value">{{ option.label }}</option>
}
</select>
<button class="btn btn-outline-primary ms-auto" type="button" (click)="createLink()" [disabled]="loading">
@if (loading) {
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
}
@if (!loading) {
<i-bs name="plus"></i-bs>
}
<ng-container i18n>Create</ng-container>
</button>
</div>
</div>
@@ -0,0 +1,3 @@
.copied-badge {
right: 15em;
}
@@ -11,17 +11,18 @@ import {
tick,
} from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { FileVersion, ShareLink } from 'src/app/data/share-link'
import { ShareLinkService } from 'src/app/services/rest/share-link.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
import { ShareLinksDropdownComponent } from './share-links-dropdown.component'
import { ShareLinksDialogComponent } from './share-links-dialog.component'
describe('ShareLinksDropdownComponent', () => {
let component: ShareLinksDropdownComponent
let fixture: ComponentFixture<ShareLinksDropdownComponent>
describe('ShareLinksDialogComponent', () => {
let component: ShareLinksDialogComponent
let fixture: ComponentFixture<ShareLinksDialogComponent>
let shareLinkService: ShareLinkService
let toastService: ToastService
let httpController: HttpTestingController
@@ -30,16 +31,17 @@ describe('ShareLinksDropdownComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
ShareLinksDropdownComponent,
ShareLinksDialogComponent,
NgxBootstrapIconsModule.pick(allIcons),
],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
NgbActiveModal,
],
})
fixture = TestBed.createComponent(ShareLinksDropdownComponent)
fixture = TestBed.createComponent(ShareLinksDialogComponent)
shareLinkService = TestBed.inject(ShareLinkService)
toastService = TestBed.inject(ToastService)
httpController = TestBed.inject(HttpTestingController)
@@ -232,4 +234,11 @@ describe('ShareLinksDropdownComponent', () => {
]
).toBeTruthy()
})
it('should support close', () => {
const activeModal = TestBed.inject(NgbActiveModal)
const closeSpy = jest.spyOn(activeModal, 'close')
component.close()
expect(closeSpy).toHaveBeenCalled()
})
})
@@ -1,7 +1,7 @@
import { Clipboard } from '@angular/cdk/clipboard'
import { Component, Input, OnInit } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first } from 'rxjs'
import { FileVersion, ShareLink } from 'src/app/data/share-link'
@@ -10,17 +10,12 @@ import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
@Component({
selector: 'pngx-share-links-dropdown',
templateUrl: './share-links-dropdown.component.html',
styleUrls: ['./share-links-dropdown.component.scss'],
imports: [
FormsModule,
ReactiveFormsModule,
NgbDropdownModule,
NgxBootstrapIconsModule,
],
selector: 'pngx-share-links-dialog',
templateUrl: './share-links-dialog.component.html',
styleUrls: ['./share-links-dialog.component.scss'],
imports: [FormsModule, ReactiveFormsModule, NgxBootstrapIconsModule],
})
export class ShareLinksDropdownComponent implements OnInit {
export class ShareLinksDialogComponent implements OnInit {
EXPIRATION_OPTIONS = [
{ label: $localize`1 day`, value: 1 },
{ label: $localize`7 days`, value: 7 },
@@ -41,9 +36,6 @@ export class ShareLinksDropdownComponent implements OnInit {
}
}
@Input()
disabled: boolean = false
private _hasArchiveVersion: boolean = true
@Input()
@@ -67,6 +59,7 @@ export class ShareLinksDropdownComponent implements OnInit {
useArchiveVersion: boolean = true
constructor(
private activeModal: NgbActiveModal,
private shareLinkService: ShareLinkService,
private toastService: ToastService,
private clipboard: Clipboard
@@ -169,4 +162,8 @@ export class ShareLinksDropdownComponent implements OnInit {
},
})
}
close() {
this.activeModal.close()
}
}
@@ -1,70 +0,0 @@
<div ngbDropdown>
<button class="btn btn-sm btn-outline-primary" id="shareLinksDropdown" [disabled]="disabled" ngbDropdownToggle>
<i-bs name="link"></i-bs>
<div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Share Links</ng-container></div>
</button>
<div ngbDropdownMenu aria-labelledby="shareLinksDropdown" class="shadow share-links-dropdown">
<ul class="list-group list-group-flush">
@if (!shareLinks || shareLinks.length === 0) {
<li class="list-group-item fst-italic small text-center text-secondary" i18n>
No existing links
</li>
}
@for (link of shareLinks; track link) {
<li class="list-group-item">
<div class="input-group input-group-sm w-100">
<input type="text" class="form-control" aria-label="Share link" [value]="getShareUrl(link)" readonly>
@if (link.expiration) {
<span class="input-group-text">
{{ getDaysRemaining(link) }}
</span>
}
<button type="button" class="btn btn-sm btn-outline-primary" (click)="copy(link)">
@if (copied !== link.id) {
<i-bs width="1.2em" height="1.2em" name="clipboard-fill"></i-bs>
}
@if (copied === link.id) {
<i-bs width="1.2em" height="1.2em" name="clipboard-check-fill"></i-bs>
}
<span class="visually-hidden" i18n>Copy</span>
</button>
@if (canShare(link)) {
<button type="button" class="btn btn-sm btn-outline-primary" (click)="share(link)">
<i-bs width="1.2em" height="1.2em" name="box-arrow-up"></i-bs><span class="visually-hidden" i18n>Share</span>
</button>
}
<button type="button" class="btn btn-sm btn-outline-danger" (click)="delete(link)">
<i-bs width="1.2em" height="1.2em" name="trash"></i-bs><span class="visually-hidden" i18n>Delete</span>
</button>
</div>
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied === link.id" i18n>Copied!</span>
</li>
}
<li class="list-group-item pt-3 pb-2">
<div class="input-group input-group-sm w-100">
<div class="form-check form-switch ms-auto small">
<input class="form-check-input" type="checkbox" role="switch" id="versionSwitch" [disabled]="!hasArchiveVersion" [(ngModel)]="useArchiveVersion">
<label class="form-check-label" for="versionSwitch" i18n>Share archive version</label>
</div>
</div>
<div class="input-group input-group-sm w-100 mt-2">
<label class="input-group-text" for="addLink"><ng-container i18n>Expires</ng-container>:</label>
<select class="form-select form-select-sm" [(ngModel)]="expirationDays">
@for (option of EXPIRATION_OPTIONS; track option) {
<option [ngValue]="option.value">{{ option.label }}</option>
}
</select>
<button class="btn btn-sm btn-outline-primary ms-auto" type="button" (click)="createLink()" [disabled]="loading">
@if (loading) {
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
}
@if (!loading) {
<i-bs name="plus"></i-bs>
}
<ng-container i18n>Create</ng-container>
</button>
</div>
</li>
</ul>
</div>
</div>
@@ -1,14 +0,0 @@
.share-links-dropdown {
min-width: 350px;
// correct position on mobile
@media (max-width: 575.98px) {
&.show {
margin-left: -175px !important;
}
}
}
.copied-badge {
right: 7.5em;
}
@@ -81,7 +81,24 @@
(added)="addField($event)">
</pngx-custom-fields-dropdown>
<pngx-share-links-dropdown [documentId]="documentId" [hasArchiveVersion]="!!document?.archived_file_name" [disabled]="!userCanEdit && !userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
<div class="ms-auto" ngbDropdown>
<button class="btn btn-sm btn-outline-primary" id="sendDropdown" ngbDropdownToggle>
<i-bs name="send"></i-bs>
<div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Send</ng-container></div>
</button>
<div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow">
<button ngbDropdownItem (click)="openShareLinks()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }">
<i-bs name="link"></i-bs>&nbsp;<span i18n>Share Links</span>
</button>
@if (emailEnabled) {
<button ngbDropdownItem (click)="openEmailDocument()">
<i-bs name="envelope"></i-bs>&nbsp;<span i18n>Email</span>
</button>
}
</div>
</div>
</pngx-page-header>
<div class="row">
@@ -1330,4 +1330,18 @@ describe('DocumentDetailComponent', () => {
expect(createSpy).toHaveBeenCalledWith('a')
expect(urlRevokeSpy).toHaveBeenCalled()
})
it('should get email enabled status from settings', () => {
jest.spyOn(settingsService, 'get').mockReturnValue(true)
expect(component.emailEnabled).toBeTruthy()
})
it('should support open share links and email modals', () => {
const modalSpy = jest.spyOn(modalService, 'open')
initNormally()
component.openShareLinks()
expect(modalSpy).toHaveBeenCalled()
component.openEmailDocument()
expect(modalSpy).toHaveBeenCalled()
})
})
@@ -88,6 +88,7 @@ import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspo
import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { EditDialogMode } from '../common/edit-dialog/edit-dialog.component'
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { EmailDocumentDialogComponent } from '../common/email-document-dialog/email-document-dialog.component'
import { CheckComponent } from '../common/input/check/check.component'
import { DateComponent } from '../common/input/date/date.component'
import { DocumentLinkComponent } from '../common/input/document-link/document-link.component'
@@ -99,7 +100,7 @@ import { TagsComponent } from '../common/input/tags/tags.component'
import { TextComponent } from '../common/input/text/text.component'
import { UrlComponent } from '../common/input/url/url.component'
import { PageHeaderComponent } from '../common/page-header/page-header.component'
import { ShareLinksDropdownComponent } from '../common/share-links-dropdown/share-links-dropdown.component'
import { ShareLinksDialogComponent } from '../common/share-links-dialog/share-links-dialog.component'
import { DocumentHistoryComponent } from '../document-history/document-history.component'
import { DocumentNotesComponent } from '../document-notes/document-notes.component'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
@@ -145,7 +146,6 @@ export enum ZoomSetting {
CustomFieldsDropdownComponent,
DocumentNotesComponent,
DocumentHistoryComponent,
ShareLinksDropdownComponent,
CheckComponent,
DateComponent,
DocumentLinkComponent,
@@ -1426,6 +1426,26 @@ export class DocumentDetailComponent
})
}
public openShareLinks() {
const modal = this.modalService.open(ShareLinksDialogComponent)
modal.componentInstance.documentId = this.document.id
modal.componentInstance.hasArchiveVersion =
!!this.document?.archived_file_name
}
get emailEnabled(): boolean {
return this.settings.get(SETTINGS_KEYS.EMAIL_ENABLED)
}
public openEmailDocument() {
const modal = this.modalService.open(EmailDocumentDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.documentId = this.document.id
modal.componentInstance.hasArchiveVersion =
!!this.document?.archived_file_name
}
private tryRenderTiff() {
this.http.get(this.previewUrl, { responseType: 'arraybuffer' }).subscribe({
next: (res) => {
+2
View File
@@ -84,4 +84,6 @@ export interface MailRule extends ObjectWithPermissions {
assign_correspondent?: number // PaperlessCorrespondent.id
assign_owner_from_rule: boolean
stop_processing: boolean
}
@@ -355,6 +355,21 @@ it('should include custom fields in sort fields if user has permission', () => {
])
})
it('should call appropriate api endpoint for email document', () => {
subscription = service
.emailDocument(
documents[0].id,
'hello@paperless-ngx.com',
'hello',
'world',
true
)
.subscribe()
httpTestingController.expectOne(
`${environment.apiBaseUrl}${endpoint}/${documents[0].id}/email/`
)
})
afterEach(() => {
subscription?.unsubscribe()
httpTestingController.verify()
@@ -258,4 +258,19 @@ export class DocumentService extends AbstractPaperlessService<Document> {
public get searchQuery(): string {
return this._searchQuery
}
emailDocument(
documentId: number,
addresses: string,
subject: string,
message: string,
useArchiveVersion: boolean
): Observable<any> {
return this.http.post(this.getResourceUrl(documentId, 'email'), {
addresses: addresses,
subject: subject,
message: message,
use_archive_version: useArchiveVersion,
})
}
}
@@ -33,6 +33,7 @@ const mail_rules = [
action: MailAction.MarkRead,
assign_title_from: MailMetadataTitleOption.FromSubject,
assign_owner_from_rule: true,
stop_processing: false,
},
{
name: 'Mail Rule 2',
@@ -52,6 +53,7 @@ const mail_rules = [
action: MailAction.Delete,
assign_title_from: MailMetadataTitleOption.FromSubject,
assign_owner_from_rule: true,
stop_processing: false,
},
{
name: 'Mail Rule 3',
@@ -71,6 +73,7 @@ const mail_rules = [
action: MailAction.Flag,
assign_title_from: MailMetadataTitleOption.FromSubject,
assign_owner_from_rule: false,
stop_processing: false,
},
]
+2
View File
@@ -112,6 +112,7 @@ import {
questionCircle,
scissors,
search,
send,
slashCircle,
sliders2Vertical,
sortAlphaDown,
@@ -316,6 +317,7 @@ const icons = {
questionCircle,
scissors,
search,
send,
slashCircle,
sliders2Vertical,
sortAlphaDown,
+148
View File
@@ -15,6 +15,7 @@ from dateutil import parser
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.core import mail
from django.core.cache import cache
from django.db import DataError
from django.test import override_settings
@@ -2651,6 +2652,153 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(doc1.tags.count(), 2)
@override_settings(
EMAIL_ENABLED=True,
EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
)
def test_email_document(self):
"""
GIVEN:
- Existing document
WHEN:
- API request is made to email document action
THEN:
- Email is sent, with document (original or archive) attached
"""
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document 1",
checksum="1",
filename="test.pdf",
archive_checksum="A",
archive_filename="archive.pdf",
)
doc2 = Document.objects.create(
title="test2",
mime_type="application/pdf",
content="this is a document 2",
checksum="2",
filename="test2.pdf",
)
archive_file = Path(__file__).parent / "samples" / "simple.pdf"
source_file = Path(__file__).parent / "samples" / "simple.pdf"
shutil.copy(archive_file, doc.archive_path)
shutil.copy(source_file, doc2.source_path)
self.client.post(
f"/api/documents/{doc.pk}/email/",
{
"addresses": "hello@paperless-ngx.com",
"subject": "test",
"message": "hello",
},
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].attachments[0][0], "archive.pdf")
self.client.post(
f"/api/documents/{doc2.pk}/email/",
{
"addresses": "hello@paperless-ngx.com",
"subject": "test",
"message": "hello",
"use_archive_version": False,
},
)
self.assertEqual(len(mail.outbox), 2)
self.assertEqual(mail.outbox[1].attachments[0][0], "test2.pdf")
@mock.patch("django.core.mail.message.EmailMessage.send", side_effect=Exception)
def test_email_document_errors(self, mocked_send):
"""
GIVEN:
- Existing document
WHEN:
- API request is made to email document action with insufficient permissions
- API request is made to email document action with invalid document id
- API request is made to email document action with missing data
- API request is made to email document action with invalid email address
- API request is made to email document action and error occurs during email send
THEN:
- Error response is returned
"""
user1 = User.objects.create_user(username="test1")
user1.user_permissions.add(*Permission.objects.all())
user1.save()
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document 1",
checksum="1",
filename="test.pdf",
archive_checksum="A",
archive_filename="archive.pdf",
)
doc2 = Document.objects.create(
title="test2",
mime_type="application/pdf",
content="this is a document 2",
checksum="2",
owner=self.user,
)
self.client.force_authenticate(user1)
resp = self.client.post(
f"/api/documents/{doc2.pk}/email/",
{
"addresses": "hello@paperless-ngx.com",
"subject": "test",
"message": "hello",
},
)
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
resp = self.client.post(
"/api/documents/999/email/",
{
"addresses": "hello@paperless-ngx.com",
"subject": "test",
"message": "hello",
},
)
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
resp = self.client.post(
f"/api/documents/{doc.pk}/email/",
{
"addresses": "hello@paperless-ngx.com",
},
)
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
resp = self.client.post(
f"/api/documents/{doc.pk}/email/",
{
"addresses": "hello@paperless-ngx.com,hello",
"subject": "test",
"message": "hello",
},
)
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
resp = self.client.post(
f"/api/documents/{doc.pk}/email/",
{
"addresses": "hello@paperless-ngx.com",
"subject": "test",
"message": "hello",
},
)
self.assertEqual(resp.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
@mock.patch("django_softdelete.models.SoftDeleteModel.delete")
def test_warn_on_delete_with_old_uuid_field(self, mocked_delete):
"""
@@ -8,7 +8,7 @@ class TestMigrateWorkflow(TestMigrations):
dependencies = (
(
"paperless_mail",
"0029_mailrule_pdf_layout",
"0030_mailrule_stop_processing",
),
)
+53
View File
@@ -37,6 +37,7 @@ from django.http import HttpResponse
from django.http import HttpResponseBadRequest
from django.http import HttpResponseForbidden
from django.http import HttpResponseRedirect
from django.http import HttpResponseServerError
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.decorators import method_decorator
@@ -106,6 +107,7 @@ from documents.filters import ObjectOwnedPermissionsFilter
from documents.filters import ShareLinkFilterSet
from documents.filters import StoragePathFilterSet
from documents.filters import TagFilterSet
from documents.mail import send_email
from documents.matching import match_correspondents
from documents.matching import match_document_types
from documents.matching import match_storage_paths
@@ -1023,6 +1025,57 @@ class DocumentViewSet(
return Response(sorted(entries, key=lambda x: x["timestamp"], reverse=True))
@action(methods=["post"], detail=True)
def email(self, request, pk=None):
try:
doc = Document.objects.select_related("owner").get(pk=pk)
if request.user is not None and not has_perms_owner_aware(
request.user,
"view_document",
doc,
):
return HttpResponseForbidden("Insufficient permissions")
except Document.DoesNotExist:
raise Http404
try:
if (
"addresses" not in request.data
or "subject" not in request.data
or "message" not in request.data
):
return HttpResponseBadRequest("Missing required fields")
use_archive_version = request.data.get("use_archive_version", True)
addresses = request.data.get("addresses").split(",")
if not all(
re.match(r"[^@]+@[^@]+\.[^@]+", address.strip())
for address in addresses
):
return HttpResponseBadRequest("Invalid email address found")
send_email(
subject=request.data.get("subject"),
body=request.data.get("message"),
to=addresses,
attachment=(
doc.archive_path
if use_archive_version and doc.has_archive_version
else doc.source_path
),
attachment_mime_type=doc.mime_type,
)
logger.debug(
f"Sent document {doc.id} via email to {addresses}",
)
return Response({"message": "Email sent"})
except Exception as e:
logger.warning(f"An error occurred emailing document: {e!s}")
return HttpResponseServerError(
"Error emailing document, check logs for more detail.",
)
@extend_schema_view(
list=extend_schema(
+130 -110
View File
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-11 18:43-0800\n"
"POT-Creation-Date: 2025-02-24 09:00-0800\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -89,20 +89,20 @@ msgstr ""
msgid "Automatic"
msgstr ""
#: documents/models.py:67 documents/models.py:433 documents/models.py:1498
#: documents/models.py:67 documents/models.py:433 documents/models.py:1502
#: paperless_mail/models.py:23 paperless_mail/models.py:143
msgid "name"
msgstr ""
#: documents/models.py:69 documents/models.py:1085
#: documents/models.py:69 documents/models.py:1086
msgid "match"
msgstr ""
#: documents/models.py:72 documents/models.py:1088
#: documents/models.py:72 documents/models.py:1089
msgid "matching algorithm"
msgstr ""
#: documents/models.py:77 documents/models.py:1093
#: documents/models.py:77 documents/models.py:1094
msgid "is insensitive"
msgstr ""
@@ -256,7 +256,7 @@ msgid "The position of this document in your physical document archive."
msgstr ""
#: documents/models.py:295 documents/models.py:737 documents/models.py:791
#: documents/models.py:1541
#: documents/models.py:1545
msgid "document"
msgstr ""
@@ -276,7 +276,7 @@ msgstr ""
msgid "warning"
msgstr ""
#: documents/models.py:387 paperless_mail/models.py:363
#: documents/models.py:387 paperless_mail/models.py:371
msgid "error"
msgstr ""
@@ -320,11 +320,11 @@ msgstr ""
msgid "Title"
msgstr ""
#: documents/models.py:420 documents/models.py:1037
#: documents/models.py:420 documents/models.py:1038
msgid "Created"
msgstr ""
#: documents/models.py:421 documents/models.py:1036
#: documents/models.py:421 documents/models.py:1037
msgid "Added"
msgstr ""
@@ -812,377 +812,381 @@ msgstr ""
msgid "Mail Fetch"
msgstr ""
#: documents/models.py:1038
msgid "Modified"
#: documents/models.py:1034
msgid "Web UI"
msgstr ""
#: documents/models.py:1039
msgid "Modified"
msgstr ""
#: documents/models.py:1040
msgid "Custom Field"
msgstr ""
#: documents/models.py:1042
#: documents/models.py:1043
msgid "Workflow Trigger Type"
msgstr ""
#: documents/models.py:1054
#: documents/models.py:1055
msgid "filter path"
msgstr ""
#: documents/models.py:1059
#: documents/models.py:1060
msgid ""
"Only consume documents with a path that matches this if specified. Wildcards "
"specified as * are allowed. Case insensitive."
msgstr ""
#: documents/models.py:1066
#: documents/models.py:1067
msgid "filter filename"
msgstr ""
#: documents/models.py:1071 paperless_mail/models.py:200
#: documents/models.py:1072 paperless_mail/models.py:200
msgid ""
"Only consume documents which entirely match this filename if specified. "
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
msgstr ""
#: documents/models.py:1082
#: documents/models.py:1083
msgid "filter documents from this mail rule"
msgstr ""
#: documents/models.py:1098
#: documents/models.py:1099
msgid "has these tag(s)"
msgstr ""
#: documents/models.py:1106
#: documents/models.py:1107
msgid "has this document type"
msgstr ""
#: documents/models.py:1114
#: documents/models.py:1115
msgid "has this correspondent"
msgstr ""
#: documents/models.py:1118
#: documents/models.py:1119
msgid "schedule offset days"
msgstr ""
#: documents/models.py:1121
#: documents/models.py:1122
msgid "The number of days to offset the schedule trigger by."
msgstr ""
#: documents/models.py:1126
#: documents/models.py:1127
msgid "schedule is recurring"
msgstr ""
#: documents/models.py:1129
#: documents/models.py:1130
msgid "If the schedule should be recurring."
msgstr ""
#: documents/models.py:1134
#: documents/models.py:1135
msgid "schedule recurring delay in days"
msgstr ""
#: documents/models.py:1138
#: documents/models.py:1139
msgid "The number of days between recurring schedule triggers."
msgstr ""
#: documents/models.py:1143
#: documents/models.py:1144
msgid "schedule date field"
msgstr ""
#: documents/models.py:1148
#: documents/models.py:1149
msgid "The field to check for a schedule trigger."
msgstr ""
#: documents/models.py:1157
#: documents/models.py:1158
msgid "schedule date custom field"
msgstr ""
#: documents/models.py:1161
#: documents/models.py:1162
msgid "workflow trigger"
msgstr ""
#: documents/models.py:1162
#: documents/models.py:1163
msgid "workflow triggers"
msgstr ""
#: documents/models.py:1170
#: documents/models.py:1171
msgid "email subject"
msgstr ""
#: documents/models.py:1174
#: documents/models.py:1175
msgid ""
"The subject of the email, can include some placeholders, see documentation."
msgstr ""
#: documents/models.py:1180
#: documents/models.py:1181
msgid "email body"
msgstr ""
#: documents/models.py:1183
#: documents/models.py:1184
msgid ""
"The body (message) of the email, can include some placeholders, see "
"documentation."
msgstr ""
#: documents/models.py:1189
#: documents/models.py:1190
msgid "emails to"
msgstr ""
#: documents/models.py:1192
#: documents/models.py:1193
msgid "The destination email addresses, comma separated."
msgstr ""
#: documents/models.py:1198
#: documents/models.py:1199
msgid "include document in email"
msgstr ""
#: documents/models.py:1207
#: documents/models.py:1210
msgid "webhook url"
msgstr ""
#: documents/models.py:1209
#: documents/models.py:1213
msgid "The destination URL for the notification."
msgstr ""
#: documents/models.py:1214
#: documents/models.py:1218
msgid "use parameters"
msgstr ""
#: documents/models.py:1219
#: documents/models.py:1223
msgid "send as JSON"
msgstr ""
#: documents/models.py:1223
#: documents/models.py:1227
msgid "webhook parameters"
msgstr ""
#: documents/models.py:1226
#: documents/models.py:1230
msgid "The parameters to send with the webhook URL if body not used."
msgstr ""
#: documents/models.py:1230
#: documents/models.py:1234
msgid "webhook body"
msgstr ""
#: documents/models.py:1233
#: documents/models.py:1237
msgid "The body to send with the webhook URL if parameters not used."
msgstr ""
#: documents/models.py:1237
#: documents/models.py:1241
msgid "webhook headers"
msgstr ""
#: documents/models.py:1240
#: documents/models.py:1244
msgid "The headers to send with the webhook URL."
msgstr ""
#: documents/models.py:1245
#: documents/models.py:1249
msgid "include document in webhook"
msgstr ""
#: documents/models.py:1256
#: documents/models.py:1260
msgid "Assignment"
msgstr ""
#: documents/models.py:1260
#: documents/models.py:1264
msgid "Removal"
msgstr ""
#: documents/models.py:1264 documents/templates/account/password_reset.html:15
#: documents/models.py:1268 documents/templates/account/password_reset.html:15
msgid "Email"
msgstr ""
#: documents/models.py:1268
#: documents/models.py:1272
msgid "Webhook"
msgstr ""
#: documents/models.py:1272
#: documents/models.py:1276
msgid "Workflow Action Type"
msgstr ""
#: documents/models.py:1278
#: documents/models.py:1282
msgid "assign title"
msgstr ""
#: documents/models.py:1283
#: documents/models.py:1287
msgid ""
"Assign a document title, can include some placeholders, see documentation."
msgstr ""
#: documents/models.py:1292 paperless_mail/models.py:274
#: documents/models.py:1296 paperless_mail/models.py:274
msgid "assign this tag"
msgstr ""
#: documents/models.py:1301 paperless_mail/models.py:282
#: documents/models.py:1305 paperless_mail/models.py:282
msgid "assign this document type"
msgstr ""
#: documents/models.py:1310 paperless_mail/models.py:296
#: documents/models.py:1314 paperless_mail/models.py:296
msgid "assign this correspondent"
msgstr ""
#: documents/models.py:1319
#: documents/models.py:1323
msgid "assign this storage path"
msgstr ""
#: documents/models.py:1328
#: documents/models.py:1332
msgid "assign this owner"
msgstr ""
#: documents/models.py:1335
#: documents/models.py:1339
msgid "grant view permissions to these users"
msgstr ""
#: documents/models.py:1342
#: documents/models.py:1346
msgid "grant view permissions to these groups"
msgstr ""
#: documents/models.py:1349
#: documents/models.py:1353
msgid "grant change permissions to these users"
msgstr ""
#: documents/models.py:1356
#: documents/models.py:1360
msgid "grant change permissions to these groups"
msgstr ""
#: documents/models.py:1363
#: documents/models.py:1367
msgid "assign these custom fields"
msgstr ""
#: documents/models.py:1370
#: documents/models.py:1374
msgid "remove these tag(s)"
msgstr ""
#: documents/models.py:1375
#: documents/models.py:1379
msgid "remove all tags"
msgstr ""
#: documents/models.py:1382
#: documents/models.py:1386
msgid "remove these document type(s)"
msgstr ""
#: documents/models.py:1387
#: documents/models.py:1391
msgid "remove all document types"
msgstr ""
#: documents/models.py:1394
#: documents/models.py:1398
msgid "remove these correspondent(s)"
msgstr ""
#: documents/models.py:1399
#: documents/models.py:1403
msgid "remove all correspondents"
msgstr ""
#: documents/models.py:1406
#: documents/models.py:1410
msgid "remove these storage path(s)"
msgstr ""
#: documents/models.py:1411
#: documents/models.py:1415
msgid "remove all storage paths"
msgstr ""
#: documents/models.py:1418
#: documents/models.py:1422
msgid "remove these owner(s)"
msgstr ""
#: documents/models.py:1423
#: documents/models.py:1427
msgid "remove all owners"
msgstr ""
#: documents/models.py:1430
#: documents/models.py:1434
msgid "remove view permissions for these users"
msgstr ""
#: documents/models.py:1437
#: documents/models.py:1441
msgid "remove view permissions for these groups"
msgstr ""
#: documents/models.py:1444
#: documents/models.py:1448
msgid "remove change permissions for these users"
msgstr ""
#: documents/models.py:1451
#: documents/models.py:1455
msgid "remove change permissions for these groups"
msgstr ""
#: documents/models.py:1456
#: documents/models.py:1460
msgid "remove all permissions"
msgstr ""
#: documents/models.py:1463
#: documents/models.py:1467
msgid "remove these custom fields"
msgstr ""
#: documents/models.py:1468
#: documents/models.py:1472
msgid "remove all custom fields"
msgstr ""
#: documents/models.py:1477
#: documents/models.py:1481
msgid "email"
msgstr ""
#: documents/models.py:1486
#: documents/models.py:1490
msgid "webhook"
msgstr ""
#: documents/models.py:1490
#: documents/models.py:1494
msgid "workflow action"
msgstr ""
#: documents/models.py:1491
#: documents/models.py:1495
msgid "workflow actions"
msgstr ""
#: documents/models.py:1500 paperless_mail/models.py:145
#: documents/models.py:1504 paperless_mail/models.py:145
msgid "order"
msgstr ""
#: documents/models.py:1506
#: documents/models.py:1510
msgid "triggers"
msgstr ""
#: documents/models.py:1513
#: documents/models.py:1517
msgid "actions"
msgstr ""
#: documents/models.py:1516 paperless_mail/models.py:154
#: documents/models.py:1520 paperless_mail/models.py:154
msgid "enabled"
msgstr ""
#: documents/models.py:1527
#: documents/models.py:1531
msgid "workflow"
msgstr ""
#: documents/models.py:1531
#: documents/models.py:1535
msgid "workflow trigger type"
msgstr ""
#: documents/models.py:1545
#: documents/models.py:1549
msgid "date run"
msgstr ""
#: documents/models.py:1551
#: documents/models.py:1555
msgid "workflow run"
msgstr ""
#: documents/models.py:1552
#: documents/models.py:1556
msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:127
#: documents/serialisers.py:128
#, python-format
msgid "Invalid regular expression: %(error)s"
msgstr ""
#: documents/serialisers.py:553
#: documents/serialisers.py:554
msgid "Invalid color."
msgstr ""
#: documents/serialisers.py:1554
#: documents/serialisers.py:1570
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
#: documents/serialisers.py:1643
#: documents/serialisers.py:1659
msgid "Invalid variable detected."
msgstr ""
@@ -1402,17 +1406,23 @@ msgstr ""
msgid "As a final step, please complete the following form:"
msgstr ""
#: documents/validators.py:17
#: documents/validators.py:24
#, python-brace-format
msgid "Unable to parse URI {value}, missing scheme"
msgstr ""
#: documents/validators.py:22
#: documents/validators.py:29
#, python-brace-format
msgid "Unable to parse URI {value}, missing net location or path"
msgstr ""
#: documents/validators.py:27
#: documents/validators.py:36
msgid ""
"URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '."
"join(allowed_schemes)}"
msgstr ""
#: documents/validators.py:45
#, python-brace-format
msgid "Unable to parse URI {value}"
msgstr ""
@@ -1701,7 +1711,7 @@ msgstr ""
msgid "Chinese Traditional"
msgstr ""
#: paperless/urls.py:364
#: paperless/urls.py:369
msgid "Paperless-ngx administration"
msgstr ""
@@ -1933,7 +1943,7 @@ msgstr ""
msgid "account"
msgstr ""
#: paperless_mail/models.py:157 paperless_mail/models.py:318
#: paperless_mail/models.py:157 paperless_mail/models.py:326
msgid "folder"
msgstr ""
@@ -2025,22 +2035,32 @@ msgstr ""
msgid "Assign the rule owner to documents"
msgstr ""
#: paperless_mail/models.py:326
msgid "uid"
#: paperless_mail/models.py:305
msgid "Stop processing further rules"
msgstr ""
#: paperless_mail/models.py:308
msgid ""
"If True, no further rules will be processed after this one if any document "
"is queued."
msgstr ""
#: paperless_mail/models.py:334
msgid "subject"
msgid "uid"
msgstr ""
#: paperless_mail/models.py:342
msgid "subject"
msgstr ""
#: paperless_mail/models.py:350
msgid "received"
msgstr ""
#: paperless_mail/models.py:349
#: paperless_mail/models.py:357
msgid "processed"
msgstr ""
#: paperless_mail/models.py:355
#: paperless_mail/models.py:363
msgid "status"
msgstr ""
+5
View File
@@ -571,6 +571,11 @@ class MailAccountHandler(LoggingMixin):
rule,
supports_gmail_labels=supports_gmail_labels,
)
if total_processed_files > 0 and rule.stop_processing:
self.log.debug(
f"Rule {rule}: Stopping processing rules due to stop_processing flag",
)
break
except Exception as e:
self.log.exception(
f"Rule {rule}: Error while processing rule: {e}",
@@ -0,0 +1,22 @@
# Generated by Django 5.1.6 on 2025-02-24 16:07
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("paperless_mail", "0029_mailrule_pdf_layout"),
]
operations = [
migrations.AddField(
model_name="mailrule",
name="stop_processing",
field=models.BooleanField(
default=False,
help_text="If True, no further rules will be processed after this one if any document is consumed.",
verbose_name="Stop processing further rules",
),
),
]
+8
View File
@@ -301,6 +301,14 @@ class MailRule(document_models.ModelWithOwner):
default=True,
)
stop_processing = models.BooleanField(
_("Stop processing further rules"),
default=False,
help_text=_(
"If True, no further rules will be processed after this one if any document is queued.",
),
)
def __str__(self):
return f"{self.account.name}.{self.name}"
+1
View File
@@ -101,6 +101,7 @@ class MailRuleSerializer(OwnedObjectSerializer):
"user_can_change",
"permissions",
"set_permissions",
"stop_processing",
]
def update(self, instance, validated_data):
+33
View File
@@ -1671,6 +1671,39 @@ class TestTasks(TestCase):
result = tasks.process_mail_accounts(account_ids=[account_b.id])
self.assertIn("No new", result)
@mock.patch("paperless_mail.tasks.MailAccountHandler.handle_mail_account")
def test_rule_with_stop_processing(self, m):
"""
GIVEN:
- Mail account with a rule with stop_processing=True
WHEN:
- Mail account is processed
THEN:
- Should only process the first rule
"""
m.side_effect = lambda account: 6
account = MailAccount.objects.create(
name="A",
imap_server="A",
username="A",
password="A",
)
MailRule.objects.create(
name="A",
account=account,
stop_processing=True,
)
MailRule.objects.create(
name="B",
account=account,
)
result = tasks.process_mail_accounts()
self.assertEqual(m.call_count, 1)
self.assertIn("Added 6", result)
class TestMailAccountTestView(APITestCase):
def setUp(self):