mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Feature: scheduled workflow trigger (#8036)
This commit is contained in:
		
							
								
								
									
										2
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|     "_meta": { |     "_meta": { | ||||||
|         "hash": { |         "hash": { | ||||||
|             "sha256": "e4cb2328c49829f56793ef25780dcc73ea8e4838e6e9bc25d1b6feb74eb3befe" |             "sha256": "584249cbeaf29659c975000b5e02b12e45d768d795e4a8ac36118e73bd7c0b8a" | ||||||
|         }, |         }, | ||||||
|         "pipfile-spec": 6, |         "pipfile-spec": 6, | ||||||
|         "requires": {}, |         "requires": {}, | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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="<em>"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> filters specified below.</source> |         <source>Trigger for documents that match <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> filters specified below.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> |           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||||
|           <context context-type="linenumber">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.</a></source> |         <source>Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</a></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> |           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||||
|           <context context-type="linenumber">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 <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>.</source> |         <source>Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> |           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||||
|           <context context-type="linenumber">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"> | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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', () => { | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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 | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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", | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -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}" | ||||||
|   | |||||||
| @@ -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): | ||||||
|   | |||||||
| @@ -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) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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, | ||||||
|  |                         ) | ||||||
|   | |||||||
| @@ -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): | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon