Feature: scheduled workflow trigger (#8036)

This commit is contained in:
shamoon 2024-11-24 10:22:31 -08:00 committed by GitHub
parent d5572137de
commit 2b29233a1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 907 additions and 78 deletions

2
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e4cb2328c49829f56793ef25780dcc73ea8e4838e6e9bc25d1b6feb74eb3befe" "sha256": "584249cbeaf29659c975000b5e02b12e45d768d795e4a8ac36118e73bd7c0b8a"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},

View File

@ -331,8 +331,10 @@ Currently, there are three events that correspond to workflow trigger 'types':
be used for filtering. be used for filtering.
3. **Document Updated**: when a document is updated. Similar to 'added' events, triggers can include filtering by content matching, 3. **Document Updated**: when a document is updated. Similar to 'added' events, triggers can include filtering by content matching,
tags, doc type, or correspondent. tags, doc type, or correspondent.
4. **Scheduled**: a scheduled trigger that can be used to run workflows at a specific time. The date used can be either the document
added, created, updated date or you can specify a (date) custom field. You can also specify a day offset from the date.
The following flow diagram illustrates the three trigger types: The following flow diagram illustrates the three document trigger types:
```mermaid ```mermaid
flowchart TD flowchart TD

View File

@ -1213,19 +1213,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">174</context> <context context-type="linenumber">200</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">193</context> <context context-type="linenumber">219</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">260</context> <context context-type="linenumber">286</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">279</context> <context context-type="linenumber">305</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context> <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
@ -1248,19 +1248,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">182</context> <context context-type="linenumber">208</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">201</context> <context context-type="linenumber">227</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">268</context> <context context-type="linenumber">294</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">287</context> <context context-type="linenumber">313</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context> <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
@ -1286,11 +1286,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">207</context> <context context-type="linenumber">233</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">293</context> <context context-type="linenumber">319</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context> <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
@ -1991,6 +1991,10 @@
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">11</context> <context context-type="linenumber">11</context>
</context-group> </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">59</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">239</context> <context context-type="linenumber">239</context>
@ -3482,6 +3486,10 @@
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
<context context-type="linenumber">74</context> <context context-type="linenumber">74</context>
</context-group> </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">55</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">248</context> <context context-type="linenumber">248</context>
@ -3581,7 +3589,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">137</context> <context context-type="linenumber">163</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6457471243969293847" datatype="html"> <trans-unit id="6457471243969293847" datatype="html">
@ -3998,7 +4006,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">162</context> <context context-type="linenumber">188</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4754802869258527587" datatype="html"> <trans-unit id="4754802869258527587" datatype="html">
@ -4016,7 +4024,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">163</context> <context context-type="linenumber">189</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1519954996184640001" datatype="html"> <trans-unit id="1519954996184640001" datatype="html">
@ -4462,322 +4470,417 @@
<context context-type="linenumber">121</context> <context context-type="linenumber">121</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5337452276818111131" datatype="html">
<source>Set scheduled trigger offset and which field to use.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit id="4779176004576564638" datatype="html">
<source>Offset days</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit id="8816141193078203810" datatype="html">
<source>Use 0 for immediate.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit id="3726450101884717309" datatype="html">
<source>Relative to</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit id="1500318445250299453" datatype="html">
<source>Delay custom field</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit id="1088170562604583291" datatype="html">
<source>Custom field to use for date.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit id="1011433830042635014" datatype="html">
<source>Recurring</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">139</context>
</context-group>
</trans-unit>
<trans-unit id="1421663004162437543" datatype="html">
<source>Trigger is recurring.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">139</context>
</context-group>
</trans-unit>
<trans-unit id="5937989815294159481" datatype="html">
<source>Recurring interval days</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">143</context>
</context-group>
</trans-unit>
<trans-unit id="722765958672682251" datatype="html">
<source>Repeat the trigger every n days.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">143</context>
</context-group>
</trans-unit>
<trans-unit id="8727727835543352574" datatype="html"> <trans-unit id="8727727835543352574" datatype="html">
<source>Trigger for documents that match <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;em&gt;"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="&lt;/em&gt;"/> filters specified below.</source> <source>Trigger for documents 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;"/> filters specified below.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">122</context> <context context-type="linenumber">148</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7467799586957602479" datatype="html"> <trans-unit id="7467799586957602479" datatype="html">
<source>Filter filename</source> <source>Filter filename</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">125</context> <context context-type="linenumber">151</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3694878959415278689" datatype="html"> <trans-unit id="3694878959415278689" datatype="html">
<source>Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.</source> <source>Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">125</context> <context context-type="linenumber">151</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1473412958770421458" datatype="html"> <trans-unit id="1473412958770421458" datatype="html">
<source>Filter sources</source> <source>Filter sources</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">127</context> <context context-type="linenumber">153</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6540860478788535250" datatype="html"> <trans-unit id="6540860478788535250" datatype="html">
<source>Filter path</source> <source>Filter path</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">128</context> <context context-type="linenumber">154</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5491897741674893121" datatype="html"> <trans-unit id="5491897741674893121" datatype="html">
<source>Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.&lt;/a&gt;</source> <source>Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.&lt;/a&gt;</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">128</context> <context context-type="linenumber">154</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7468453896129193641" datatype="html"> <trans-unit id="7468453896129193641" datatype="html">
<source>Filter mail rule</source> <source>Filter mail rule</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">129</context> <context context-type="linenumber">155</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8663702115863339485" datatype="html"> <trans-unit id="8663702115863339485" datatype="html">
<source>Apply to documents consumed via this mail rule.</source> <source>Apply to documents consumed via this mail rule.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">129</context> <context context-type="linenumber">155</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6840369584127435743" datatype="html"> <trans-unit id="6840369584127435743" datatype="html">
<source>Content matching algorithm</source> <source>Content matching algorithm</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">132</context> <context context-type="linenumber">158</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="510635115034690805" datatype="html"> <trans-unit id="510635115034690805" datatype="html">
<source>Content matching pattern</source> <source>Content matching pattern</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">134</context> <context context-type="linenumber">160</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3484236514968690689" datatype="html"> <trans-unit id="3484236514968690689" datatype="html">
<source>Has any of tags</source> <source>Has any of tags</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">143</context> <context context-type="linenumber">169</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5281365940563983618" datatype="html"> <trans-unit id="5281365940563983618" datatype="html">
<source>Has correspondent</source> <source>Has correspondent</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">144</context> <context context-type="linenumber">170</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4806713133917046341" datatype="html"> <trans-unit id="4806713133917046341" datatype="html">
<source>Has document type</source> <source>Has document type</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">145</context> <context context-type="linenumber">171</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6417103744331194518" datatype="html"> <trans-unit id="6417103744331194518" datatype="html">
<source>Action type</source> <source>Action type</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">155</context> <context context-type="linenumber">181</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6019822389883736115" datatype="html"> <trans-unit id="6019822389883736115" datatype="html">
<source>Assign title</source> <source>Assign title</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">160</context> <context context-type="linenumber">186</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1098196422099517191" datatype="html"> <trans-unit id="1098196422099517191" datatype="html">
<source>Can include some placeholders, see &lt;a target=&apos;_blank&apos; href=&apos;https://docs.paperless-ngx.com/usage/#workflows&apos;&gt;documentation&lt;/a&gt;.</source> <source>Can include some placeholders, see &lt;a target=&apos;_blank&apos; href=&apos;https://docs.paperless-ngx.com/usage/#workflows&apos;&gt;documentation&lt;/a&gt;.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">160</context> <context context-type="linenumber">186</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6528897010417701530" datatype="html"> <trans-unit id="6528897010417701530" datatype="html">
<source>Assign tags</source> <source>Assign tags</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">161</context> <context context-type="linenumber">187</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7198346314713788799" datatype="html"> <trans-unit id="7198346314713788799" datatype="html">
<source>Assign storage path</source> <source>Assign storage path</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">164</context> <context context-type="linenumber">190</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="475685412372379925" datatype="html"> <trans-unit id="475685412372379925" datatype="html">
<source>Assign custom fields</source> <source>Assign custom fields</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">165</context> <context context-type="linenumber">191</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5057200219587080996" datatype="html"> <trans-unit id="5057200219587080996" datatype="html">
<source>Assign owner</source> <source>Assign owner</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">168</context> <context context-type="linenumber">194</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1749184201773078639" datatype="html"> <trans-unit id="1749184201773078639" datatype="html">
<source>Assign view permissions</source> <source>Assign view permissions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">170</context> <context context-type="linenumber">196</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1744964187586405039" datatype="html"> <trans-unit id="1744964187586405039" datatype="html">
<source>Assign edit permissions</source> <source>Assign edit permissions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">189</context> <context context-type="linenumber">215</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6236311670364192011" datatype="html"> <trans-unit id="6236311670364192011" datatype="html">
<source>Remove tags</source> <source>Remove tags</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">216</context> <context context-type="linenumber">242</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7890599006071681081" datatype="html"> <trans-unit id="7890599006071681081" datatype="html">
<source>Remove all</source> <source>Remove all</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">217</context> <context context-type="linenumber">243</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">223</context> <context context-type="linenumber">249</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">229</context> <context context-type="linenumber">255</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">235</context> <context context-type="linenumber">261</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">241</context> <context context-type="linenumber">267</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">248</context> <context context-type="linenumber">274</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">254</context> <context context-type="linenumber">280</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8636414563726517994" datatype="html"> <trans-unit id="8636414563726517994" datatype="html">
<source>Remove correspondents</source> <source>Remove correspondents</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">222</context> <context context-type="linenumber">248</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5305293055593064952" datatype="html"> <trans-unit id="5305293055593064952" datatype="html">
<source>Remove document types</source> <source>Remove document types</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">228</context> <context context-type="linenumber">254</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2400388879708187" datatype="html"> <trans-unit id="2400388879708187" datatype="html">
<source>Remove storage paths</source> <source>Remove storage paths</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">234</context> <context context-type="linenumber">260</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4324304327041955720" datatype="html"> <trans-unit id="4324304327041955720" datatype="html">
<source>Remove custom fields</source> <source>Remove custom fields</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">240</context> <context context-type="linenumber">266</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8367536502602515064" datatype="html"> <trans-unit id="8367536502602515064" datatype="html">
<source>Remove owners</source> <source>Remove owners</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">247</context> <context context-type="linenumber">273</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3393772184866313281" datatype="html"> <trans-unit id="3393772184866313281" datatype="html">
<source>Remove permissions</source> <source>Remove permissions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">253</context> <context context-type="linenumber">279</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3145629643370481114" datatype="html"> <trans-unit id="3145629643370481114" datatype="html">
<source>View permissions</source> <source>View permissions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">256</context> <context context-type="linenumber">282</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1946660694635960249" datatype="html"> <trans-unit id="1946660694635960249" datatype="html">
<source>Edit permissions</source> <source>Edit permissions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">275</context> <context context-type="linenumber">301</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4626030417479279989" datatype="html"> <trans-unit id="4626030417479279989" datatype="html">
<source>Consume Folder</source> <source>Consume Folder</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="526966086395145275" datatype="html"> <trans-unit id="526966086395145275" datatype="html">
<source>API Upload</source> <source>API Upload</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">43</context> <context context-type="linenumber">44</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7502272564743467653" datatype="html"> <trans-unit id="7502272564743467653" datatype="html">
<source>Mail Fetch</source> <source>Mail Fetch</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">47</context> <context context-type="linenumber">48</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">63</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="8686921715946540725" datatype="html">
<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">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8696908693776094667" datatype="html"> <trans-unit id="8696908693776094667" datatype="html">
<source>Consumption Started</source> <source>Consumption Started</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">54</context> <context context-type="linenumber">74</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7858311467093621703" datatype="html"> <trans-unit id="7858311467093621703" datatype="html">
<source>Document Added</source> <source>Document Added</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">58</context> <context context-type="linenumber">78</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7955486237346046731" datatype="html"> <trans-unit id="7955486237346046731" datatype="html">
<source>Document Updated</source> <source>Document Updated</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">62</context> <context context-type="linenumber">82</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">86</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5502398334173581061" datatype="html"> <trans-unit id="5502398334173581061" datatype="html">
<source>Assignment</source> <source>Assignment</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">69</context> <context context-type="linenumber">93</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6234812824772766804" datatype="html"> <trans-unit id="6234812824772766804" datatype="html">
<source>Removal</source> <source>Removal</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">97</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3138206142174978019" datatype="html"> <trans-unit id="3138206142174978019" datatype="html">
<source>Create new workflow</source> <source>Create new workflow</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">142</context> <context context-type="linenumber">172</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5996779210524133604" datatype="html"> <trans-unit id="5996779210524133604" datatype="html">
<source>Edit workflow</source> <source>Edit workflow</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
<context context-type="linenumber">146</context> <context context-type="linenumber">176</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6381578200008167206" datatype="html"> <trans-unit id="6381578200008167206" datatype="html">
@ -8480,13 +8583,6 @@
<context context-type="linenumber">46</context> <context context-type="linenumber">46</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3553216189604488439" datatype="html">
<source>Modified</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="4460262093225954455" datatype="html"> <trans-unit id="4460262093225954455" datatype="html">
<source>Search score</source> <source>Search score</source>
<context-group purpose="location"> <context-group purpose="location">

View File

@ -119,6 +119,32 @@
<div [formGroup]="formGroup"> <div [formGroup]="formGroup">
<input type="hidden" formControlName="id" /> <input type="hidden" formControlName="id" />
<pngx-input-select i18n-title title="Trigger type" [horizontal]="true" [items]="triggerTypeOptions" formControlName="type"></pngx-input-select> <pngx-input-select i18n-title title="Trigger type" [horizontal]="true" [items]="triggerTypeOptions" formControlName="type"></pngx-input-select>
@if (formGroup.get('type').value === WorkflowTriggerType.Scheduled) {
<p class="small" i18n>Set scheduled trigger offset and which field to use.</p>
<div class="row">
<div class="col-4">
<pngx-input-number i18n-title title="Offset days" formControlName="schedule_offset_days" i18n-hint hint="Use 0 for immediate." [showAdd]="false" [error]="error?.schedule_offset_days"></pngx-input-number>
</div>
<div class="col-4">
<pngx-input-select i18n-title title="Relative to" formControlName="schedule_date_field" [items]="scheduleDateFieldOptions" [error]="error?.schedule_date_field"></pngx-input-select>
</div>
@if (formGroup.get('schedule_date_field').value === 'custom_field') {
<div class="col-4">
<pngx-input-select i18n-title title="Delay custom field" formControlName="schedule_date_custom_field" [items]="dateCustomFields" i18n-hint hint="Custom field to use for date." [error]="error?.schedule_date_custom_field"></pngx-input-select>
</div>
}
</div>
<div class="row">
<div class="col-4">
<pngx-input-check i18n-title title="Recurring" formControlName="schedule_is_recurring" i18n-hint hint="Trigger is recurring." [error]="error?.schedule_is_recurring"></pngx-input-check>
</div>
<div class="col-4">
@if (formGroup.get('schedule_is_recurring').value === true) {
<pngx-input-number i18n-title title="Recurring interval days" formControlName="schedule_recurring_interval_days" i18n-hint hint="Repeat the trigger every n days." [showAdd]="false" [error]="error?.schedule_recurring_interval_days"></pngx-input-number>
}
</div>
</div>
}
<p class="small" i18n>Trigger for documents that match <em>all</em> filters specified below.</p> <p class="small" i18n>Trigger for documents that match <em>all</em> filters specified below.</p>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -128,7 +154,7 @@
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</a>" [error]="error?.filter_path"></pngx-input-text> <pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</a>" [error]="error?.filter_path"></pngx-input-text>
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select> <pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
} }
@if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated) { @if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated || formGroup.get('type').value === WorkflowTriggerType.Scheduled) {
<pngx-input-select i18n-title title="Content matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select> <pngx-input-select i18n-title title="Content matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
@if (patternRequired) { @if (patternRequired) {
<pngx-input-text i18n-title title="Content matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text> <pngx-input-text i18n-title title="Content matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
@ -138,7 +164,7 @@
} }
} }
</div> </div>
@if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated) { @if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated || formGroup.get('type').value === WorkflowTriggerType.Scheduled) {
<div class="col-md-6"> <div class="col-md-6">
<pngx-input-tags [allowCreate]="false" i18n-title title="Has any of tags" formControlName="filter_has_tags"></pngx-input-tags> <pngx-input-tags [allowCreate]="false" i18n-title title="Has any of tags" formControlName="filter_has_tags"></pngx-input-tags>
<pngx-input-select i18n-title title="Has correspondent" [items]="correspondents" [allowNull]="true" formControlName="filter_has_correspondent"></pngx-input-select> <pngx-input-select i18n-title title="Has correspondent" [items]="correspondents" [allowNull]="true" formControlName="filter_has_correspondent"></pngx-input-select>

View File

@ -22,6 +22,7 @@ import { SwitchComponent } from '../../input/switch/switch.component'
import { EditDialogMode } from '../edit-dialog.component' import { EditDialogMode } from '../edit-dialog.component'
import { import {
DOCUMENT_SOURCE_OPTIONS, DOCUMENT_SOURCE_OPTIONS,
SCHEDULE_DATE_FIELD_OPTIONS,
WORKFLOW_ACTION_OPTIONS, WORKFLOW_ACTION_OPTIONS,
WORKFLOW_TYPE_OPTIONS, WORKFLOW_TYPE_OPTIONS,
WorkflowEditDialogComponent, WorkflowEditDialogComponent,
@ -40,6 +41,7 @@ import {
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model' import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component' import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { CustomFieldDataType } from 'src/app/data/custom-field'
const workflow: Workflow = { const workflow: Workflow = {
name: 'Workflow 1', name: 'Workflow 1',
@ -148,7 +150,18 @@ describe('WorkflowEditDialogComponent', () => {
useValue: { useValue: {
listAll: () => listAll: () =>
of({ of({
results: [], results: [
{
id: 1,
name: 'cf1',
data_type: CustomFieldDataType.String,
},
{
id: 2,
name: 'cf2',
data_type: CustomFieldDataType.Date,
},
],
}), }),
}, },
}, },
@ -186,7 +199,7 @@ describe('WorkflowEditDialogComponent', () => {
expect(editTitleSpy).toHaveBeenCalled() expect(editTitleSpy).toHaveBeenCalled()
}) })
it('should return source options, type options, type name', () => { it('should return source options, type options, type name, schedule date field options', () => {
// coverage // coverage
expect(component.sourceOptions).toEqual(DOCUMENT_SOURCE_OPTIONS) expect(component.sourceOptions).toEqual(DOCUMENT_SOURCE_OPTIONS)
expect(component.triggerTypeOptions).toEqual(WORKFLOW_TYPE_OPTIONS) expect(component.triggerTypeOptions).toEqual(WORKFLOW_TYPE_OPTIONS)
@ -200,6 +213,9 @@ describe('WorkflowEditDialogComponent', () => {
component.getActionTypeOptionName(WorkflowActionType.Assignment) component.getActionTypeOptionName(WorkflowActionType.Assignment)
).toEqual('Assignment') ).toEqual('Assignment')
expect(component.getActionTypeOptionName(null)).toEqual('') expect(component.getActionTypeOptionName(null)).toEqual('')
expect(component.scheduleDateFieldOptions).toEqual(
SCHEDULE_DATE_FIELD_OPTIONS
)
}) })
it('should support add and remove triggers and actions', () => { it('should support add and remove triggers and actions', () => {

View File

@ -16,9 +16,10 @@ import { EditDialogComponent } from '../edit-dialog.component'
import { MailRuleService } from 'src/app/services/rest/mail-rule.service' import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
import { MailRule } from 'src/app/data/mail-rule' import { MailRule } from 'src/app/data/mail-rule'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { CustomField } from 'src/app/data/custom-field' import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { import {
DocumentSource, DocumentSource,
ScheduleDateField,
WorkflowTrigger, WorkflowTrigger,
WorkflowTriggerType, WorkflowTriggerType,
} from 'src/app/data/workflow-trigger' } from 'src/app/data/workflow-trigger'
@ -48,6 +49,25 @@ export const DOCUMENT_SOURCE_OPTIONS = [
}, },
] ]
export const SCHEDULE_DATE_FIELD_OPTIONS = [
{
id: ScheduleDateField.Added,
name: $localize`Added`,
},
{
id: ScheduleDateField.Created,
name: $localize`Created`,
},
{
id: ScheduleDateField.Modified,
name: $localize`Modified`,
},
{
id: ScheduleDateField.CustomField,
name: $localize`Custom Field`,
},
]
export const WORKFLOW_TYPE_OPTIONS = [ export const WORKFLOW_TYPE_OPTIONS = [
{ {
id: WorkflowTriggerType.Consumption, id: WorkflowTriggerType.Consumption,
@ -61,6 +81,10 @@ export const WORKFLOW_TYPE_OPTIONS = [
id: WorkflowTriggerType.DocumentUpdated, id: WorkflowTriggerType.DocumentUpdated,
name: $localize`Document Updated`, name: $localize`Document Updated`,
}, },
{
id: WorkflowTriggerType.Scheduled,
name: $localize`Scheduled`,
},
] ]
export const WORKFLOW_ACTION_OPTIONS = [ export const WORKFLOW_ACTION_OPTIONS = [
@ -96,6 +120,7 @@ export class WorkflowEditDialogComponent
storagePaths: StoragePath[] storagePaths: StoragePath[]
mailRules: MailRule[] mailRules: MailRule[]
customFields: CustomField[] customFields: CustomField[]
dateCustomFields: CustomField[]
expandedItem: number = null expandedItem: number = null
@ -135,7 +160,12 @@ export class WorkflowEditDialogComponent
customFieldsService customFieldsService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.customFields = result.results)) .subscribe((result) => {
this.customFields = result.results
this.dateCustomFields = this.customFields?.filter(
(f) => f.data_type === CustomFieldDataType.Date
)
})
} }
getCreateTitle() { getCreateTitle() {
@ -314,6 +344,15 @@ export class WorkflowEditDialogComponent
filter_has_document_type: new FormControl( filter_has_document_type: new FormControl(
trigger.filter_has_document_type trigger.filter_has_document_type
), ),
schedule_offset_days: new FormControl(trigger.schedule_offset_days),
schedule_is_recurring: new FormControl(trigger.schedule_is_recurring),
schedule_recurring_interval_days: new FormControl(
trigger.schedule_recurring_interval_days
),
schedule_date_field: new FormControl(trigger.schedule_date_field),
schedule_date_custom_field: new FormControl(
trigger.schedule_date_custom_field
),
}), }),
{ emitEvent } { emitEvent }
) )
@ -388,6 +427,10 @@ export class WorkflowEditDialogComponent
return WORKFLOW_TYPE_OPTIONS return WORKFLOW_TYPE_OPTIONS
} }
get scheduleDateFieldOptions() {
return SCHEDULE_DATE_FIELD_OPTIONS
}
getTriggerTypeOptionName(type: WorkflowTriggerType): string { getTriggerTypeOptionName(type: WorkflowTriggerType): string {
return this.triggerTypeOptions.find((t) => t.id === type)?.name ?? '' return this.triggerTypeOptions.find((t) => t.id === type)?.name ?? ''
} }
@ -408,6 +451,11 @@ export class WorkflowEditDialogComponent
matching_algorithm: MATCH_NONE, matching_algorithm: MATCH_NONE,
match: '', match: '',
is_insensitive: true, is_insensitive: true,
schedule_offset_days: 0,
schedule_is_recurring: false,
schedule_recurring_interval_days: 1,
schedule_date_field: ScheduleDateField.Added,
schedule_date_custom_field: null,
} }
this.object.triggers.push(trigger) this.object.triggers.push(trigger)
this.createTriggerField(trigger) this.createTriggerField(trigger)

View File

@ -10,6 +10,14 @@ export enum WorkflowTriggerType {
Consumption = 1, Consumption = 1,
DocumentAdded = 2, DocumentAdded = 2,
DocumentUpdated = 3, DocumentUpdated = 3,
Scheduled = 4,
}
export enum ScheduleDateField {
Added = 'added',
Created = 'created',
Modified = 'modified',
CustomField = 'custom_field',
} }
export interface WorkflowTrigger extends ObjectWithId { export interface WorkflowTrigger extends ObjectWithId {
@ -34,4 +42,14 @@ export interface WorkflowTrigger extends ObjectWithId {
filter_has_correspondent?: number // Correspondent.id filter_has_correspondent?: number // Correspondent.id
filter_has_document_type?: number // DocumentType.id filter_has_document_type?: number // DocumentType.id
schedule_offset_days?: number
schedule_is_recurring?: boolean
schedule_recurring_interval_days?: number
schedule_date_field?: ScheduleDateField
schedule_date_custom_field?: number // CustomField.id
} }

View File

@ -409,6 +409,7 @@ def document_matches_workflow(
elif ( elif (
trigger_type == WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED trigger_type == WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED
or trigger_type == WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED or trigger_type == WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED
or trigger_type == WorkflowTrigger.WorkflowTriggerType.SCHEDULED
): ):
trigger_matched, reason = existing_document_matches_workflow( trigger_matched, reason = existing_document_matches_workflow(
document, document,

View File

@ -0,0 +1,143 @@
# Generated by Django 5.1.1 on 2024-11-05 05:19
import django.core.validators
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1057_paperlesstask_owner"),
]
operations = [
migrations.AddField(
model_name="workflowtrigger",
name="schedule_date_custom_field",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="documents.customfield",
verbose_name="schedule date custom field",
),
),
migrations.AddField(
model_name="workflowtrigger",
name="schedule_date_field",
field=models.CharField(
choices=[
("added", "Added"),
("created", "Created"),
("modified", "Modified"),
("custom_field", "Custom Field"),
],
default="added",
help_text="The field to check for a schedule trigger.",
max_length=20,
verbose_name="schedule date field",
),
),
migrations.AddField(
model_name="workflowtrigger",
name="schedule_is_recurring",
field=models.BooleanField(
default=False,
help_text="If the schedule should be recurring.",
verbose_name="schedule is recurring",
),
),
migrations.AddField(
model_name="workflowtrigger",
name="schedule_offset_days",
field=models.PositiveIntegerField(
default=0,
help_text="The number of days to offset the schedule trigger by.",
verbose_name="schedule offset days",
),
),
migrations.AddField(
model_name="workflowtrigger",
name="schedule_recurring_interval_days",
field=models.PositiveIntegerField(
default=1,
help_text="The number of days between recurring schedule triggers.",
validators=[django.core.validators.MinValueValidator(1)],
verbose_name="schedule recurring delay in days",
),
),
migrations.AlterField(
model_name="workflowtrigger",
name="type",
field=models.PositiveIntegerField(
choices=[
(1, "Consumption Started"),
(2, "Document Added"),
(3, "Document Updated"),
(4, "Scheduled"),
],
default=1,
verbose_name="Workflow Trigger Type",
),
),
migrations.CreateModel(
name="WorkflowRun",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"type",
models.PositiveIntegerField(
choices=[
(1, "Consumption Started"),
(2, "Document Added"),
(3, "Document Updated"),
(4, "Scheduled"),
],
null=True,
verbose_name="workflow trigger type",
),
),
(
"run_at",
models.DateTimeField(
db_index=True,
default=django.utils.timezone.now,
verbose_name="date run",
),
),
(
"document",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="workflow_runs",
to="documents.document",
verbose_name="document",
),
),
(
"workflow",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="runs",
to="documents.workflow",
verbose_name="workflow",
),
),
],
options={
"verbose_name": "workflow run",
"verbose_name_plural": "workflow runs",
},
),
]

View File

@ -1016,12 +1016,19 @@ class WorkflowTrigger(models.Model):
CONSUMPTION = 1, _("Consumption Started") CONSUMPTION = 1, _("Consumption Started")
DOCUMENT_ADDED = 2, _("Document Added") DOCUMENT_ADDED = 2, _("Document Added")
DOCUMENT_UPDATED = 3, _("Document Updated") DOCUMENT_UPDATED = 3, _("Document Updated")
SCHEDULED = 4, _("Scheduled")
class DocumentSourceChoices(models.IntegerChoices): class DocumentSourceChoices(models.IntegerChoices):
CONSUME_FOLDER = DocumentSource.ConsumeFolder.value, _("Consume Folder") CONSUME_FOLDER = DocumentSource.ConsumeFolder.value, _("Consume Folder")
API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload") API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload")
MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch") MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch")
class ScheduleDateField(models.TextChoices):
ADDED = "added", _("Added")
CREATED = "created", _("Created")
MODIFIED = "modified", _("Modified")
CUSTOM_FIELD = "custom_field", _("Custom Field")
type = models.PositiveIntegerField( type = models.PositiveIntegerField(
_("Workflow Trigger Type"), _("Workflow Trigger Type"),
choices=WorkflowTriggerType.choices, choices=WorkflowTriggerType.choices,
@ -1098,6 +1105,49 @@ class WorkflowTrigger(models.Model):
verbose_name=_("has this correspondent"), verbose_name=_("has this correspondent"),
) )
schedule_offset_days = models.PositiveIntegerField(
_("schedule offset days"),
default=0,
help_text=_(
"The number of days to offset the schedule trigger by.",
),
)
schedule_is_recurring = models.BooleanField(
_("schedule is recurring"),
default=False,
help_text=_(
"If the schedule should be recurring.",
),
)
schedule_recurring_interval_days = models.PositiveIntegerField(
_("schedule recurring delay in days"),
default=1,
validators=[MinValueValidator(1)],
help_text=_(
"The number of days between recurring schedule triggers.",
),
)
schedule_date_field = models.CharField(
_("schedule date field"),
max_length=20,
choices=ScheduleDateField.choices,
default=ScheduleDateField.ADDED,
help_text=_(
"The field to check for a schedule trigger.",
),
)
schedule_date_custom_field = models.ForeignKey(
CustomField,
null=True,
blank=True,
on_delete=models.SET_NULL,
verbose_name=_("schedule date custom field"),
)
class Meta: class Meta:
verbose_name = _("workflow trigger") verbose_name = _("workflow trigger")
verbose_name_plural = _("workflow triggers") verbose_name_plural = _("workflow triggers")
@ -1348,3 +1398,39 @@ class Workflow(models.Model):
def __str__(self): def __str__(self):
return f"Workflow: {self.name}" return f"Workflow: {self.name}"
class WorkflowRun(models.Model):
workflow = models.ForeignKey(
Workflow,
on_delete=models.CASCADE,
related_name="runs",
verbose_name=_("workflow"),
)
type = models.PositiveIntegerField(
_("workflow trigger type"),
choices=WorkflowTrigger.WorkflowTriggerType.choices,
null=True,
)
document = models.ForeignKey(
Document,
null=True,
on_delete=models.CASCADE,
related_name="workflow_runs",
verbose_name=_("document"),
)
run_at = models.DateTimeField(
_("date run"),
default=timezone.now,
db_index=True,
)
class Meta:
verbose_name = _("workflow run")
verbose_name_plural = _("workflow runs")
def __str__(self):
return f"WorkflowRun of {self.workflow} at {self.run_at} on {self.document}"

View File

@ -1772,6 +1772,11 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer):
"filter_has_tags", "filter_has_tags",
"filter_has_correspondent", "filter_has_correspondent",
"filter_has_document_type", "filter_has_document_type",
"schedule_offset_days",
"schedule_is_recurring",
"schedule_recurring_interval_days",
"schedule_date_field",
"schedule_date_custom_field",
] ]
def validate(self, attrs): def validate(self, attrs):

View File

@ -37,6 +37,7 @@ from documents.models import PaperlessTask
from documents.models import Tag from documents.models import Tag
from documents.models import Workflow from documents.models import Workflow
from documents.models import WorkflowAction from documents.models import WorkflowAction
from documents.models import WorkflowRun
from documents.models import WorkflowTrigger from documents.models import WorkflowTrigger
from documents.permissions import get_objects_for_user_owner_aware from documents.permissions import get_objects_for_user_owner_aware
from documents.permissions import set_permissions_for_object from documents.permissions import set_permissions_for_object
@ -916,6 +917,12 @@ def run_workflows(
document.save() document.save()
document.tags.set(doc_tag_ids) document.tags.set(doc_tag_ids)
WorkflowRun.objects.create(
workflow=workflow,
type=trigger_type,
document=document if not use_overrides else None,
)
if use_overrides: if use_overrides:
return overrides, "\n".join(messages) return overrides, "\n".join(messages)

View File

@ -31,10 +31,14 @@ from documents.double_sided import CollatePlugin
from documents.file_handling import create_source_path_directory from documents.file_handling import create_source_path_directory
from documents.file_handling import generate_unique_filename from documents.file_handling import generate_unique_filename
from documents.models import Correspondent from documents.models import Correspondent
from documents.models import CustomFieldInstance
from documents.models import Document from documents.models import Document
from documents.models import DocumentType from documents.models import DocumentType
from documents.models import StoragePath from documents.models import StoragePath
from documents.models import Tag from documents.models import Tag
from documents.models import Workflow
from documents.models import WorkflowRun
from documents.models import WorkflowTrigger
from documents.parsers import DocumentParser from documents.parsers import DocumentParser
from documents.parsers import get_parser_class_for_mime_type from documents.parsers import get_parser_class_for_mime_type
from documents.plugins.base import ConsumeTaskPlugin from documents.plugins.base import ConsumeTaskPlugin
@ -44,6 +48,7 @@ from documents.plugins.helpers import ProgressStatusOptions
from documents.sanity_checker import SanityCheckFailedException from documents.sanity_checker import SanityCheckFailedException
from documents.signals import document_updated from documents.signals import document_updated
from documents.signals.handlers import cleanup_document_deletion from documents.signals.handlers import cleanup_document_deletion
from documents.signals.handlers import run_workflows
if settings.AUDIT_LOG_ENABLED: if settings.AUDIT_LOG_ENABLED:
from auditlog.models import LogEntry from auditlog.models import LogEntry
@ -337,3 +342,81 @@ def empty_trash(doc_ids=None):
cleanup_document_deletion, cleanup_document_deletion,
sender=Document, sender=Document,
) )
@shared_task
def check_scheduled_workflows():
scheduled_workflows: list[Workflow] = Workflow.objects.filter(
triggers__type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
enabled=True,
).prefetch_related("triggers")
if scheduled_workflows.count() > 0:
logger.debug(f"Checking {len(scheduled_workflows)} scheduled workflows")
for workflow in scheduled_workflows:
schedule_triggers = workflow.triggers.filter(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
)
trigger: WorkflowTrigger
for trigger in schedule_triggers:
documents = Document.objects.none()
offset_td = timedelta(days=trigger.schedule_offset_days)
logger.debug(
f"Checking trigger {trigger} with offset {offset_td} against field: {trigger.schedule_date_field}",
)
match trigger.schedule_date_field:
case WorkflowTrigger.ScheduleDateField.ADDED:
documents = Document.objects.filter(
added__lt=timezone.now() - offset_td,
)
case WorkflowTrigger.ScheduleDateField.CREATED:
documents = Document.objects.filter(
created__lt=timezone.now() - offset_td,
)
case WorkflowTrigger.ScheduleDateField.MODIFIED:
documents = Document.objects.filter(
modified__lt=timezone.now() - offset_td,
)
case WorkflowTrigger.ScheduleDateField.CUSTOM_FIELD:
cf_instances = CustomFieldInstance.objects.filter(
field=trigger.schedule_date_custom_field,
value_date__lt=timezone.now() - offset_td,
)
documents = Document.objects.filter(
id__in=cf_instances.values_list("document", flat=True),
)
if documents.count() > 0:
logger.debug(
f"Found {documents.count()} documents for trigger {trigger}",
)
for document in documents:
workflow_runs = WorkflowRun.objects.filter(
document=document,
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
workflow=workflow,
).order_by("-run_at")
if not trigger.schedule_is_recurring and workflow_runs.exists():
# schedule is non-recurring and the workflow has already been run
logger.debug(
f"Skipping document {document} for non-recurring workflow {workflow} as it has already been run",
)
continue
elif (
trigger.schedule_is_recurring
and workflow_runs.exists()
and (
workflow_runs.last().run_at
> timezone.now()
- timedelta(
days=trigger.schedule_recurring_interval_days,
)
)
):
# schedule is recurring but the last run was within the number of recurring interval days
logger.debug(
f"Skipping document {document} for recurring workflow {workflow} as the last run was within the recurring interval",
)
continue
run_workflows(
WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
document,
)

View File

@ -29,6 +29,7 @@ from documents.models import StoragePath
from documents.models import Tag from documents.models import Tag
from documents.models import Workflow from documents.models import Workflow
from documents.models import WorkflowAction from documents.models import WorkflowAction
from documents.models import WorkflowRun
from documents.models import WorkflowTrigger from documents.models import WorkflowTrigger
from documents.signals import document_consumption_finished from documents.signals import document_consumption_finished
from documents.tests.utils import DirectoriesMixin from documents.tests.utils import DirectoriesMixin
@ -1306,6 +1307,275 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
# group2 should have been added # group2 should have been added
self.assertIn(self.group2, group_perms) self.assertIn(self.group2, group_perms)
def test_workflow_scheduled_trigger_created(self):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger against the created field and action that assigns owner
- Existing doc that matches the trigger
WHEN:
- Scheduled workflows are checked
THEN:
- Workflow runs, document owner is updated
"""
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_offset_days=1,
schedule_date_field="created",
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
now = timezone.localtime(timezone.now())
created = now - timedelta(weeks=520)
doc = Document.objects.create(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
created=created,
)
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertEqual(doc.owner, self.user2)
def test_workflow_scheduled_trigger_added(self):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger against the added field and action that assigns owner
- Existing doc that matches the trigger
WHEN:
- Scheduled workflows are checked
THEN:
- Workflow runs, document owner is updated
"""
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_offset_days=1,
schedule_date_field=WorkflowTrigger.ScheduleDateField.ADDED,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
added = timezone.now() - timedelta(days=365)
doc = Document.objects.create(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
added=added,
)
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertEqual(doc.owner, self.user2)
@mock.patch("documents.models.Document.objects.filter", autospec=True)
def test_workflow_scheduled_trigger_modified(self, mock_filter):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger against the modified field and action that assigns owner
- Existing doc that matches the trigger
WHEN:
- Scheduled workflows are checked
THEN:
- Workflow runs, document owner is updated
"""
# we have to mock because modified field is auto_now
mock_filter.return_value = Document.objects.all()
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_offset_days=1,
schedule_date_field=WorkflowTrigger.ScheduleDateField.MODIFIED,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
doc = Document.objects.create(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
)
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertEqual(doc.owner, self.user2)
def test_workflow_scheduled_trigger_custom_field(self):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger against a custom field and action that assigns owner
- Existing doc that matches the trigger
WHEN:
- Scheduled workflows are checked
THEN:
- Workflow runs, document owner is updated
"""
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_offset_days=1,
schedule_date_field=WorkflowTrigger.ScheduleDateField.CUSTOM_FIELD,
schedule_date_custom_field=self.cf1,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
doc = Document.objects.create(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
)
CustomFieldInstance.objects.create(
document=doc,
field=self.cf1,
value_date=timezone.now() - timedelta(days=2),
)
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertEqual(doc.owner, self.user2)
def test_workflow_scheduled_already_run(self):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger
- Existing doc that has already had the workflow run
WHEN:
- Scheduled workflows are checked
THEN:
- Workflow does not run again
"""
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_offset_days=1,
schedule_date_field=WorkflowTrigger.ScheduleDateField.CREATED,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
doc = Document.objects.create(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
created=timezone.now() - timedelta(days=2),
)
wr = WorkflowRun.objects.create(
workflow=w,
document=doc,
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
run_at=timezone.now(),
)
self.assertEqual(
str(wr),
f"WorkflowRun of {w} at {wr.run_at} on {doc}",
) # coverage
tasks.check_scheduled_workflows()
doc.refresh_from_db()
self.assertIsNone(doc.owner)
def test_workflow_scheduled_trigger_too_early(self):
"""
GIVEN:
- Existing workflow with SCHEDULED trigger and recurring interval of 7 days
- Workflow run date is 6 days ago
WHEN:
- Scheduled workflows are checked
THEN:
- Workflow does not run as the offset is not met
"""
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_offset_days=30,
schedule_date_field=WorkflowTrigger.ScheduleDateField.CREATED,
schedule_is_recurring=True,
schedule_recurring_interval_days=7,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
doc = Document.objects.create(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
created=timezone.now() - timedelta(days=40),
)
WorkflowRun.objects.create(
workflow=w,
document=doc,
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
run_at=timezone.now() - timedelta(days=6),
)
with self.assertLogs(level="DEBUG") as cm:
tasks.check_scheduled_workflows()
self.assertIn(
"last run was within the recurring interval",
" ".join(cm.output),
)
doc.refresh_from_db()
self.assertIsNone(doc.owner)
def test_workflow_enabled_disabled(self): def test_workflow_enabled_disabled(self):
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED, type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
@ -1354,7 +1624,7 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
def test_new_trigger_type_raises_exception(self): def test_new_trigger_type_raises_exception(self):
trigger = WorkflowTrigger.objects.create( trigger = WorkflowTrigger.objects.create(
type=4, type=99,
) )
action = WorkflowAction.objects.create( action = WorkflowAction.objects.create(
assign_title="Doc assign owner", assign_title="Doc assign owner",
@ -1370,7 +1640,7 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
doc = Document.objects.create( doc = Document.objects.create(
title="test", title="test",
) )
self.assertRaises(Exception, document_matches_workflow, doc, w, 4) self.assertRaises(Exception, document_matches_workflow, doc, w, 99)
def test_removal_action_document_updated_workflow(self): def test_removal_action_document_updated_workflow(self):
""" """

View File

@ -216,6 +216,17 @@ def _parse_beat_schedule() -> dict:
"expires": 23.0 * 60.0 * 60.0, "expires": 23.0 * 60.0 * 60.0,
}, },
}, },
{
"name": "Check and run scheduled workflows",
"env_key": "PAPERLESS_WORKFLOW_SCHEDULED_TASK_CRON",
# Default hourly at 5 minutes past the hour
"env_default": "5 */1 * * *",
"task": "documents.tasks.check_scheduled_workflows",
"options": {
# 1 minute before default schedule sends again
"expires": 59.0 * 60.0,
},
},
] ]
for task in tasks: for task in tasks:
# Either get the environment setting or use the default # Either get the environment setting or use the default

View File

@ -157,6 +157,7 @@ class TestCeleryScheduleParsing(TestCase):
INDEX_EXPIRE_TIME = 23.0 * 60.0 * 60.0 INDEX_EXPIRE_TIME = 23.0 * 60.0 * 60.0
SANITY_EXPIRE_TIME = ((7.0 * 24.0) - 1.0) * 60.0 * 60.0 SANITY_EXPIRE_TIME = ((7.0 * 24.0) - 1.0) * 60.0 * 60.0
EMPTY_TRASH_EXPIRE_TIME = 23.0 * 60.0 * 60.0 EMPTY_TRASH_EXPIRE_TIME = 23.0 * 60.0 * 60.0
RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME = 59.0 * 60.0
def test_schedule_configuration_default(self): def test_schedule_configuration_default(self):
""" """
@ -196,6 +197,11 @@ class TestCeleryScheduleParsing(TestCase):
"schedule": crontab(minute=0, hour="1"), "schedule": crontab(minute=0, hour="1"),
"options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME}, "options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME},
}, },
"Check and run scheduled workflows": {
"task": "documents.tasks.check_scheduled_workflows",
"schedule": crontab(minute="5", hour="*/1"),
"options": {"expires": self.RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME},
},
}, },
schedule, schedule,
) )
@ -243,6 +249,11 @@ class TestCeleryScheduleParsing(TestCase):
"schedule": crontab(minute=0, hour="1"), "schedule": crontab(minute=0, hour="1"),
"options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME}, "options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME},
}, },
"Check and run scheduled workflows": {
"task": "documents.tasks.check_scheduled_workflows",
"schedule": crontab(minute="5", hour="*/1"),
"options": {"expires": self.RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME},
},
}, },
schedule, schedule,
) )
@ -282,6 +293,11 @@ class TestCeleryScheduleParsing(TestCase):
"schedule": crontab(minute=0, hour="1"), "schedule": crontab(minute=0, hour="1"),
"options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME}, "options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME},
}, },
"Check and run scheduled workflows": {
"task": "documents.tasks.check_scheduled_workflows",
"schedule": crontab(minute="5", hour="*/1"),
"options": {"expires": self.RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME},
},
}, },
schedule, schedule,
) )
@ -303,6 +319,7 @@ class TestCeleryScheduleParsing(TestCase):
"PAPERLESS_SANITY_TASK_CRON": "disable", "PAPERLESS_SANITY_TASK_CRON": "disable",
"PAPERLESS_INDEX_TASK_CRON": "disable", "PAPERLESS_INDEX_TASK_CRON": "disable",
"PAPERLESS_EMPTY_TRASH_TASK_CRON": "disable", "PAPERLESS_EMPTY_TRASH_TASK_CRON": "disable",
"PAPERLESS_WORKFLOW_SCHEDULED_TASK_CRON": "disable",
}, },
): ):
schedule = _parse_beat_schedule() schedule = _parse_beat_schedule()