mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Allow filtering on multiple correspondents, doctypes, storage paths
Preserve 'Not assigned' option Fix default logical operator Update frontend strings Fix radio button name overlaps Use include / exclude with multi-select for OneToOne objects
This commit is contained in:
		| @@ -770,7 +770,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context> | ||||
|           <context context-type="linenumber">17</context> | ||||
|           <context context-type="linenumber">21</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -1015,7 +1015,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context> | ||||
|           <context context-type="linenumber">16</context> | ||||
|           <context context-type="linenumber">20</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/select-dialog/select-dialog.component.html</context> | ||||
| @@ -1498,7 +1498,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">213</context> | ||||
|           <context context-type="linenumber">208</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
| @@ -1787,25 +1787,39 @@ | ||||
|           <context context-type="linenumber">18</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6381578200008167206" datatype="html"> | ||||
|         <source>Include</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">24</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5668077948386857930" datatype="html"> | ||||
|         <source>Exclude</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">26</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4391289919356861627" datatype="html"> | ||||
|         <source>Apply</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">32</context> | ||||
|           <context context-type="linenumber">40</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7780041345210191160" datatype="html"> | ||||
|         <source>Click again to exclude items.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">38</context> | ||||
|           <context context-type="linenumber">46</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7593728289020204896" datatype="html"> | ||||
|         <source>Not assigned</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">262</context> | ||||
|           <context context-type="linenumber">321</context> | ||||
|         </context-group> | ||||
|         <note priority="1" from="description">Filter drop down element to filter for documents with no correspondent/type/tag assigned</note> | ||||
|       </trans-unit> | ||||
| @@ -1974,6 +1988,45 @@ | ||||
|           <context context-type="linenumber">12</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3894950702316166331" datatype="html"> | ||||
|         <source>Loading...</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context> | ||||
|           <context context-type="linenumber">18</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context> | ||||
|           <context context-type="linenumber">26</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html</context> | ||||
|           <context context-type="linenumber">7</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> | ||||
|           <context context-type="linenumber">95</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">230</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">320</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">406</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context> | ||||
|           <context context-type="linenumber">19</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context> | ||||
|           <context context-type="linenumber">27</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8105421668262723483" datatype="html"> | ||||
|         <source>Set Permissions</source> | ||||
|         <context-group purpose="location"> | ||||
| @@ -1985,7 +2038,7 @@ | ||||
|         <source>Note that permissions set here will override any existing permissions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.ts</context> | ||||
|           <context context-type="linenumber">41</context> | ||||
|           <context context-type="linenumber">43</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8650499415827640724" datatype="html"> | ||||
| @@ -2049,41 +2102,6 @@ | ||||
|           <context context-type="linenumber">20</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3894950702316166331" datatype="html"> | ||||
|         <source>Loading...</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context> | ||||
|           <context context-type="linenumber">26</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html</context> | ||||
|           <context context-type="linenumber">7</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> | ||||
|           <context context-type="linenumber">95</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">230</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">320</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">406</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context> | ||||
|           <context context-type="linenumber">19</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context> | ||||
|           <context context-type="linenumber">27</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1865646076514070962" datatype="html"> | ||||
|         <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx</source> | ||||
|         <context-group purpose="location"> | ||||
| @@ -2148,7 +2166,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">162</context> | ||||
|           <context context-type="linenumber">172</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/rest/document.service.ts</context> | ||||
| @@ -2649,64 +2667,64 @@ | ||||
|         <source>Error retrieving suggestions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">319</context> | ||||
|           <context context-type="linenumber">325</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="448882439049417053" datatype="html"> | ||||
|         <source>Error saving document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">432</context> | ||||
|           <context context-type="linenumber">439</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">476</context> | ||||
|           <context context-type="linenumber">483</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="9021887951960049161" datatype="html"> | ||||
|         <source>Confirm delete</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">505</context> | ||||
|           <context context-type="linenumber">512</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">209</context> | ||||
|           <context context-type="linenumber">204</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5382975254277698192" datatype="html"> | ||||
|         <source>Do you really want to delete document "<x id="PH" equiv-text="this.document.title"/>"?</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">506</context> | ||||
|           <context context-type="linenumber">513</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6691075929777935948" datatype="html"> | ||||
|         <source>The files for this document will be deleted permanently. This operation cannot be undone.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">507</context> | ||||
|           <context context-type="linenumber">514</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="719892092227206532" datatype="html"> | ||||
|         <source>Delete document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">509</context> | ||||
|           <context context-type="linenumber">516</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1844801255494293730" datatype="html"> | ||||
|         <source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">525</context> | ||||
|           <context context-type="linenumber">532</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7362691899087997122" datatype="html"> | ||||
|         <source>Redo OCR confirm</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">545</context> | ||||
|           <context context-type="linenumber">552</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -2717,14 +2735,14 @@ | ||||
|         <source>This operation will permanently redo OCR for this document.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">546</context> | ||||
|           <context context-type="linenumber">553</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5641451190833696892" datatype="html"> | ||||
|         <source>This operation cannot be undone.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">547</context> | ||||
|           <context context-type="linenumber">554</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -2755,7 +2773,7 @@ | ||||
|         <source>Proceed</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">549</context> | ||||
|           <context context-type="linenumber">556</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -2782,7 +2800,7 @@ | ||||
|         <source>Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">557</context> | ||||
|           <context context-type="linenumber">564</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8008978164775353960" datatype="html"> | ||||
| @@ -2791,7 +2809,7 @@ | ||||
|               )"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">568,570</context> | ||||
|           <context context-type="linenumber">575,577</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6857598786757174736" datatype="html"> | ||||
| @@ -3305,7 +3323,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">167</context> | ||||
|           <context context-type="linenumber">177</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/rest/document.service.ts</context> | ||||
| @@ -3359,112 +3377,112 @@ | ||||
|         <source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) => c.id == +rule.value)?.name"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">98,100</context> | ||||
|           <context context-type="linenumber">108,110</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8170755470576301659" datatype="html"> | ||||
|         <source>Without correspondent</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">102</context> | ||||
|           <context context-type="linenumber">112</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8705701325879965907" datatype="html"> | ||||
|         <source>Type: <x id="PH" equiv-text="this.documentTypes.find((dt) => dt.id == +rule.value)?.name"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">107,109</context> | ||||
|           <context context-type="linenumber">117,119</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4362173610367509215" datatype="html"> | ||||
|         <source>Without document type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">111</context> | ||||
|           <context context-type="linenumber">121</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8180755793012580465" datatype="html"> | ||||
|         <source>Tag: <x id="PH" equiv-text="this.tags.find((t) => t.id == +rule.value)?.name"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">115,117</context> | ||||
|           <context context-type="linenumber">125,127</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6494566478302448576" datatype="html"> | ||||
|         <source>Without any tag</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">121</context> | ||||
|           <context context-type="linenumber">131</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6523384805359286307" datatype="html"> | ||||
|         <source>Title: <x id="PH" equiv-text="rule.value"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">125</context> | ||||
|           <context context-type="linenumber">135</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1872523635812236432" datatype="html"> | ||||
|         <source>ASN: <x id="PH" equiv-text="rule.value"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">128</context> | ||||
|           <context context-type="linenumber">138</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3100631071441658964" datatype="html"> | ||||
|         <source>Title & content</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">165</context> | ||||
|           <context context-type="linenumber">175</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1010505078885609376" datatype="html"> | ||||
|         <source>Advanced search</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">170</context> | ||||
|           <context context-type="linenumber">180</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2649431021108393503" datatype="html"> | ||||
|         <source>More like</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">176</context> | ||||
|           <context context-type="linenumber">186</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3697582909018473071" datatype="html"> | ||||
|         <source>equals</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">195</context> | ||||
|           <context context-type="linenumber">205</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5325481293405718739" datatype="html"> | ||||
|         <source>is empty</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">199</context> | ||||
|           <context context-type="linenumber">209</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6166785695326182482" datatype="html"> | ||||
|         <source>is not empty</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">203</context> | ||||
|           <context context-type="linenumber">213</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4686622206659266699" datatype="html"> | ||||
|         <source>greater than</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">207</context> | ||||
|           <context context-type="linenumber">217</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8014012170270529279" datatype="html"> | ||||
|         <source>less than</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">211</context> | ||||
|           <context context-type="linenumber">221</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7210076240260527720" datatype="html"> | ||||
| @@ -3699,50 +3717,50 @@ | ||||
|         <source>Error occurred while creating <x id="PH" equiv-text="this.typeName"/> : <x id="PH_1" equiv-text="activeModal.componentInstance.error"/>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">142,144</context> | ||||
|           <context context-type="linenumber">142</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">155,157</context> | ||||
|           <context context-type="linenumber">153,155</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="211408744872436427" datatype="html"> | ||||
|         <source>Successfully created <x id="PH" equiv-text="this.typeName"/>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">149</context> | ||||
|           <context context-type="linenumber">147</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6151710751857751783" datatype="html"> | ||||
|         <source>Error occurred while saving <x id="PH" equiv-text="this.typeName"/> : <x id="PH_1" equiv-text="activeModal.componentInstance.error"/>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">173,175</context> | ||||
|           <context context-type="linenumber">171</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">187,189</context> | ||||
|           <context context-type="linenumber">182,184</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2541368547549828690" datatype="html"> | ||||
|         <source>Successfully updated <x id="PH" equiv-text="this.typeName"/>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">181</context> | ||||
|           <context context-type="linenumber">176</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4012132330507560812" datatype="html"> | ||||
|         <source>Do you really want to delete the <x id="PH" equiv-text="this.typeName"/>?</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">196</context> | ||||
|           <context context-type="linenumber">191</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8371896857609524947" datatype="html"> | ||||
|         <source>Associated documents will not be deleted.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">211</context> | ||||
|           <context context-type="linenumber">206</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5467489005440577210" datatype="html"> | ||||
| @@ -3751,7 +3769,7 @@ | ||||
|             )"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">224,226</context> | ||||
|           <context context-type="linenumber">219,221</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1685061484835793745" datatype="html"> | ||||
|   | ||||
| @@ -1,21 +1,29 @@ | ||||
| <div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown"> | ||||
|   <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled"> | ||||
|   <button class="btn btn-sm" id="dropdown_{{name}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled"> | ||||
|     <svg class="toolbaricon" fill="currentColor"> | ||||
|       <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" /> | ||||
|     </svg> | ||||
|     <div class="d-none d-sm-inline"> {{title}}</div> | ||||
|     <ng-container *ngIf="!editing && selectionModel.totalCount > 0"> | ||||
|       <app-clearable-badge [number]="multiple ? selectionModel.totalCount : undefined" [selected]="!multiple && selectionModel.selectionSize() > 0" (cleared)="reset()"></app-clearable-badge> | ||||
|       <app-clearable-badge [number]="selectionModel.totalCount" [selected]="selectionModel.selectionSize() > 0" (cleared)="reset()"></app-clearable-badge> | ||||
|     </ng-container> | ||||
|   </button> | ||||
|   <div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}"> | ||||
|   <div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}"> | ||||
|     <div class="list-group list-group-flush"> | ||||
|       <div *ngIf="!editing && multiple" class="list-group-item d-flex"> | ||||
|         <div class="btn-group btn-group-xs flex-fill"> | ||||
|           <input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!operatorToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorAnd" value="and"> | ||||
|           <label class="btn btn-outline-primary" for="logicalOperatorAnd" i18n>All</label> | ||||
|           <input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!operatorToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorOr" value="or"> | ||||
|           <label class="btn btn-outline-primary" for="logicalOperatorOr" i18n>Any</label> | ||||
|       <div *ngIf="!editing && manyToOne" class="list-group-item d-flex"> | ||||
|         <div class="btn-group btn-group-xs flex-fill" role="group"> | ||||
|           <input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorAnd_{{name}}" name="logicalOperatorAnd_{{name}}" value="and"> | ||||
|           <label class="btn btn-outline-primary" for="logicalOperatorAnd_{{name}}" i18n>All</label> | ||||
|           <input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorOr_{{name}}" name="logicalOperatorOr_{{name}}" value="or"> | ||||
|           <label class="btn btn-outline-primary" for="logicalOperatorOr_{{name}}" i18n>Any</label> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div *ngIf="!editing && !manyToOne" class="list-group-item d-flex"> | ||||
|         <div class="btn-group btn-group-xs flex-fill" role="group"> | ||||
|           <input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionInclude_{{name}}" name="intersectionInclude_{{name}}" value="include"> | ||||
|           <label class="btn btn-outline-primary" for="intersectionInclude_{{name}}" i18n>Include</label> | ||||
|           <input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionExclude_{{name}}" name="intersectionExclude_{{name}}" value="exclude"> | ||||
|           <label class="btn btn-outline-primary" for="intersectionExclude_{{name}}" i18n>Exclude</label> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="list-group-item"> | ||||
| @@ -34,7 +42,7 @@ | ||||
|           <use xlink:href="assets/bootstrap-icons.svg#arrow-right" /> | ||||
|         </svg> | ||||
|       </button> | ||||
|       <div *ngIf="!editing && multiple" class="list-group-item list-group-item-note pt-1 pb-2"> | ||||
|       <div *ngIf="!editing && manyToOne" class="list-group-item list-group-item-note pt-1 pb-2"> | ||||
|         <small i18n>Click again to exclude items.</small> | ||||
|       </div> | ||||
|     </div> | ||||
|   | ||||
| @@ -18,12 +18,25 @@ export interface ChangedItems { | ||||
|   itemsToRemove: MatchingModel[] | ||||
| } | ||||
|  | ||||
| export enum LogicalOperator { | ||||
|   And = 'and', | ||||
|   Or = 'or', | ||||
| } | ||||
|  | ||||
| export enum Intersection { | ||||
|   Include = 'include', | ||||
|   Exclude = 'exclude', | ||||
| } | ||||
|  | ||||
| export class FilterableDropdownSelectionModel { | ||||
|   changed = new Subject<FilterableDropdownSelectionModel>() | ||||
|  | ||||
|   multiple = false | ||||
|   private _logicalOperator = 'and' | ||||
|   temporaryLogicalOperator = this._logicalOperator | ||||
|   manyToOne = false | ||||
|   singleSelect = false | ||||
|   private _logicalOperator: LogicalOperator = LogicalOperator.And | ||||
|   temporaryLogicalOperator: LogicalOperator = this._logicalOperator | ||||
|   private _intersection: Intersection = Intersection.Include | ||||
|   temporaryIntersection: Intersection = this._intersection | ||||
|  | ||||
|   items: MatchingModel[] = [] | ||||
|  | ||||
| @@ -86,21 +99,36 @@ export class FilterableDropdownSelectionModel { | ||||
|       (state != ToggleableItemState.Selected && | ||||
|         state != ToggleableItemState.Excluded) | ||||
|     ) { | ||||
|       if (this.manyToOne || this.singleSelect) { | ||||
|         this.temporarySelectionStates.set(id, ToggleableItemState.Selected) | ||||
|     } else if ( | ||||
|       state == ToggleableItemState.Selected || | ||||
|       state == ToggleableItemState.Excluded | ||||
|     ) { | ||||
|       this.temporarySelectionStates.delete(id) | ||||
|     } | ||||
|  | ||||
|     if (!this.multiple) { | ||||
|         if (this.singleSelect) { | ||||
|           for (let key of this.temporarySelectionStates.keys()) { | ||||
|             if (key != id) { | ||||
|               this.temporarySelectionStates.delete(key) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         let newState = | ||||
|           this.intersection == Intersection.Include | ||||
|             ? ToggleableItemState.Selected | ||||
|             : ToggleableItemState.Excluded | ||||
|         if (!id) newState = ToggleableItemState.Selected | ||||
|         if ( | ||||
|           state == ToggleableItemState.Excluded && | ||||
|           this.intersection == Intersection.Exclude | ||||
|         ) { | ||||
|           newState = ToggleableItemState.NotSelected | ||||
|         } | ||||
|         this.temporarySelectionStates.set(id, newState) | ||||
|       } | ||||
|     } else if ( | ||||
|       state == ToggleableItemState.Selected || | ||||
|       state == ToggleableItemState.Excluded | ||||
|     ) { | ||||
|       this.temporarySelectionStates.delete(id) | ||||
|     } | ||||
|  | ||||
|     if (!id) { | ||||
|       for (let key of this.temporarySelectionStates.keys()) { | ||||
| @@ -119,20 +147,37 @@ export class FilterableDropdownSelectionModel { | ||||
|  | ||||
|   exclude(id: number, fireEvent: boolean = true) { | ||||
|     let state = this.temporarySelectionStates.get(id) | ||||
|     if (state == null || state != ToggleableItemState.Excluded) { | ||||
|       this.temporarySelectionStates.set(id, ToggleableItemState.Excluded) | ||||
|       this.temporaryLogicalOperator = this._logicalOperator = 'and' | ||||
|     } else if (state == ToggleableItemState.Excluded) { | ||||
|       this.temporarySelectionStates.delete(id) | ||||
|     } | ||||
|     if (id && (state == null || state != ToggleableItemState.Excluded)) { | ||||
|       this.temporaryLogicalOperator = this._logicalOperator = this.manyToOne | ||||
|         ? LogicalOperator.And | ||||
|         : LogicalOperator.Or | ||||
|  | ||||
|     if (!this.multiple) { | ||||
|       if (this.manyToOne || this.singleSelect) { | ||||
|         this.temporarySelectionStates.set(id, ToggleableItemState.Excluded) | ||||
|  | ||||
|         if (this.singleSelect) { | ||||
|           for (let key of this.temporarySelectionStates.keys()) { | ||||
|             if (key != id) { | ||||
|               this.temporarySelectionStates.delete(key) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         let newState = | ||||
|           this.intersection == Intersection.Include | ||||
|             ? ToggleableItemState.Selected | ||||
|             : ToggleableItemState.Excluded | ||||
|         if ( | ||||
|           state == ToggleableItemState.Selected && | ||||
|           this.intersection == Intersection.Include | ||||
|         ) { | ||||
|           newState = ToggleableItemState.NotSelected | ||||
|         } | ||||
|         this.temporarySelectionStates.set(id, newState) | ||||
|       } | ||||
|     } else if (!id || state == ToggleableItemState.Excluded) { | ||||
|       this.temporarySelectionStates.delete(id) | ||||
|     } | ||||
|  | ||||
|     if (fireEvent) { | ||||
|       this.changed.next(this) | ||||
| @@ -143,11 +188,11 @@ export class FilterableDropdownSelectionModel { | ||||
|     return this.selectionStates.get(id) || ToggleableItemState.NotSelected | ||||
|   } | ||||
|  | ||||
|   get logicalOperator(): string { | ||||
|   get logicalOperator(): LogicalOperator { | ||||
|     return this.temporaryLogicalOperator | ||||
|   } | ||||
|  | ||||
|   set logicalOperator(operator: string) { | ||||
|   set logicalOperator(operator: LogicalOperator) { | ||||
|     this.temporaryLogicalOperator = operator | ||||
|   } | ||||
|  | ||||
| @@ -155,6 +200,26 @@ export class FilterableDropdownSelectionModel { | ||||
|     this.changed.next(this) | ||||
|   } | ||||
|  | ||||
|   get intersection(): Intersection { | ||||
|     return this.temporaryIntersection | ||||
|   } | ||||
|  | ||||
|   set intersection(intersection: Intersection) { | ||||
|     this.temporaryIntersection = intersection | ||||
|   } | ||||
|  | ||||
|   toggleIntersection() { | ||||
|     if (this.temporarySelectionStates.size === 0) return | ||||
|     let newState = | ||||
|       this.intersection == Intersection.Include | ||||
|         ? ToggleableItemState.Selected | ||||
|         : ToggleableItemState.Excluded | ||||
|     this.temporarySelectionStates.forEach((state, key) => { | ||||
|       this.temporarySelectionStates.set(key, newState) | ||||
|     }) | ||||
|     this.changed.next(this) | ||||
|   } | ||||
|  | ||||
|   get(id: number) { | ||||
|     return ( | ||||
|       this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected | ||||
| @@ -171,7 +236,8 @@ export class FilterableDropdownSelectionModel { | ||||
|  | ||||
|   clear(fireEvent = true) { | ||||
|     this.temporarySelectionStates.clear() | ||||
|     this.temporaryLogicalOperator = this._logicalOperator = 'and' | ||||
|     this.temporaryLogicalOperator = this._logicalOperator = LogicalOperator.And | ||||
|     this.temporaryIntersection = this._intersection = Intersection.Include | ||||
|     if (fireEvent) { | ||||
|       this.changed.next(this) | ||||
|     } | ||||
| @@ -194,6 +260,8 @@ export class FilterableDropdownSelectionModel { | ||||
|       return true | ||||
|     } else if (this.temporaryLogicalOperator !== this._logicalOperator) { | ||||
|       return true | ||||
|     } else if (this.temporaryIntersection !== this._intersection) { | ||||
|       return true | ||||
|     } else { | ||||
|       return false | ||||
|     } | ||||
| @@ -217,14 +285,19 @@ export class FilterableDropdownSelectionModel { | ||||
|       this.selectionStates.set(key, value) | ||||
|     }) | ||||
|     this._logicalOperator = this.temporaryLogicalOperator | ||||
|     this._intersection = this.temporaryIntersection | ||||
|   } | ||||
|  | ||||
|   reset() { | ||||
|   reset(complete: boolean = false) { | ||||
|     this.temporarySelectionStates.clear() | ||||
|     if (complete) { | ||||
|       this.selectionStates.clear() | ||||
|     } else { | ||||
|       this.selectionStates.forEach((value, key) => { | ||||
|         this.temporarySelectionStates.set(key, value) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   diff(): ChangedItems { | ||||
|     return { | ||||
| @@ -269,14 +342,16 @@ export class FilterableDropdownComponent { | ||||
|     return this._selectionModel.items | ||||
|   } | ||||
|  | ||||
|   _selectionModel = new FilterableDropdownSelectionModel() | ||||
|   _selectionModel: FilterableDropdownSelectionModel = | ||||
|     new FilterableDropdownSelectionModel() | ||||
|  | ||||
|   @Input() | ||||
|   set selectionModel(model: FilterableDropdownSelectionModel) { | ||||
|     if (this.selectionModel) { | ||||
|       this.selectionModel.changed.complete() | ||||
|       model.items = this.selectionModel.items | ||||
|       model.multiple = this.selectionModel.multiple | ||||
|       model.manyToOne = this.selectionModel.manyToOne | ||||
|       model.singleSelect = this.editing && !this.selectionModel.manyToOne | ||||
|     } | ||||
|     model.changed.subscribe((updatedModel) => { | ||||
|       this.selectionModelChange.next(updatedModel) | ||||
| @@ -292,12 +367,12 @@ export class FilterableDropdownComponent { | ||||
|   selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>() | ||||
|  | ||||
|   @Input() | ||||
|   set multiple(value: boolean) { | ||||
|     this.selectionModel.multiple = value | ||||
|   set manyToOne(manyToOne: boolean) { | ||||
|     this.selectionModel.manyToOne = manyToOne | ||||
|   } | ||||
|  | ||||
|   get multiple() { | ||||
|     return this.selectionModel.multiple | ||||
|   get manyToOne() { | ||||
|     return this.selectionModel.manyToOne | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
| @@ -327,16 +402,20 @@ export class FilterableDropdownComponent { | ||||
|   @Output() | ||||
|   opened = new EventEmitter() | ||||
|  | ||||
|   get operatorToggleEnabled(): boolean { | ||||
|     return ( | ||||
|       this.selectionModel.selectionSize() > 1 && | ||||
|   get modifierToggleEnabled(): boolean { | ||||
|     return this.manyToOne | ||||
|       ? this.selectionModel.selectionSize() > 1 && | ||||
|           this.selectionModel.getExcludedItems().length == 0 | ||||
|     ) | ||||
|       : !this.selectionModel.isNoneSelected() | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   documentCounts: SelectionDataItem[] | ||||
|  | ||||
|   get name(): string { | ||||
|     return this.title ? this.title.replace(/\s/g, '_').toLowerCase() : null | ||||
|   } | ||||
|  | ||||
|   getUpdatedDocumentCount(id: number) { | ||||
|     if (this.documentCounts) { | ||||
|       return this.documentCounts.find((c) => c.id === id)?.document_count | ||||
| @@ -346,7 +425,6 @@ export class FilterableDropdownComponent { | ||||
|   modelIsDirty: boolean = false | ||||
|  | ||||
|   constructor(private filterPipe: FilterPipe) { | ||||
|     this.selectionModel = new FilterableDropdownSelectionModel() | ||||
|     this.selectionModelChange.subscribe((updatedModel) => { | ||||
|       this.modelIsDirty = updatedModel.isDirty() | ||||
|     }) | ||||
| @@ -400,7 +478,7 @@ export class FilterableDropdownComponent { | ||||
|   } | ||||
|  | ||||
|   reset() { | ||||
|     this.selectionModel.reset() | ||||
|     this.selectionModel.reset(true) | ||||
|     this.selectionModelChange.emit(this.selectionModel) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -30,7 +30,7 @@ | ||||
|         [items]="tags" | ||||
|         [disabled]="!userCanEditAll" | ||||
|         [editing]="true" | ||||
|         [multiple]="true" | ||||
|         [manyToOne]="true" | ||||
|         [applyOnClose]="applyOnClose" | ||||
|         (opened)="openTagsDropdown()" | ||||
|         [(selectionModel)]="tagSelectionModel" | ||||
|   | ||||
| @@ -14,19 +14,19 @@ | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="btn-group flex-fill" role="group"> | ||||
|     <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="details" (ngModelChange)="saveDisplayMode()" id="displayModeDetails"> | ||||
|     <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="details" (ngModelChange)="saveDisplayMode()" id="displayModeDetails" name="displayModeDetails"> | ||||
|     <label for="displayModeDetails" class="btn btn-outline-primary btn-sm"> | ||||
|       <svg class="toolbaricon" fill="currentColor"> | ||||
|         <use xlink:href="assets/bootstrap-icons.svg#list-ul" /> | ||||
|       </svg> | ||||
|     </label> | ||||
|     <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall"> | ||||
|     <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall" name="displayModeSmall"> | ||||
|     <label for="displayModeSmall" class="btn btn-outline-primary btn-sm"> | ||||
|       <svg class="toolbaricon" fill="currentColor"> | ||||
|         <use xlink:href="assets/bootstrap-icons.svg#grid" /> | ||||
|       </svg> | ||||
|     </label> | ||||
|     <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge"> | ||||
|     <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge" name="displayModeLarge"> | ||||
|     <label for="displayModeLarge" class="btn btn-outline-primary btn-sm"> | ||||
|       <svg class="toolbaricon" fill="currentColor"> | ||||
|         <use xlink:href="assets/bootstrap-icons.svg#hdd-stack" /> | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|           <app-filterable-dropdown class="flex-fill" title="Tags" icon="tag-fill" i18n-title | ||||
|             filterPlaceholder="Filter tags" i18n-filterPlaceholder | ||||
|             [items]="tags" | ||||
|             [multiple]="true" | ||||
|             [manyToOne]="true" | ||||
|             [(selectionModel)]="tagSelectionModel" | ||||
|             (selectionModelChange)="updateRules()" | ||||
|             (opened)="onTagsDropdownOpen()" | ||||
|   | ||||
| @@ -21,10 +21,10 @@ import { | ||||
|   FILTER_ADDED_AFTER, | ||||
|   FILTER_ADDED_BEFORE, | ||||
|   FILTER_ASN, | ||||
|   FILTER_CORRESPONDENT, | ||||
|   FILTER_HAS_CORRESPONDENT_ANY, | ||||
|   FILTER_CREATED_AFTER, | ||||
|   FILTER_CREATED_BEFORE, | ||||
|   FILTER_DOCUMENT_TYPE, | ||||
|   FILTER_HAS_DOCUMENT_TYPE_ANY, | ||||
|   FILTER_FULLTEXT_MORELIKE, | ||||
|   FILTER_FULLTEXT_QUERY, | ||||
|   FILTER_HAS_ANY_TAG, | ||||
| @@ -33,12 +33,22 @@ import { | ||||
|   FILTER_DOES_NOT_HAVE_TAG, | ||||
|   FILTER_TITLE, | ||||
|   FILTER_TITLE_CONTENT, | ||||
|   FILTER_STORAGE_PATH, | ||||
|   FILTER_HAS_STORAGE_PATH_ANY, | ||||
|   FILTER_ASN_ISNULL, | ||||
|   FILTER_ASN_GT, | ||||
|   FILTER_ASN_LT, | ||||
|   FILTER_DOES_NOT_HAVE_CORRESPONDENT, | ||||
|   FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE, | ||||
|   FILTER_DOES_NOT_HAVE_STORAGE_PATH, | ||||
|   FILTER_DOCUMENT_TYPE, | ||||
|   FILTER_CORRESPONDENT, | ||||
|   FILTER_STORAGE_PATH, | ||||
| } from 'src/app/data/filter-rule-type' | ||||
| import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component' | ||||
| import { | ||||
|   FilterableDropdownSelectionModel, | ||||
|   Intersection, | ||||
|   LogicalOperator, | ||||
| } from '../../common/filterable-dropdown/filterable-dropdown.component' | ||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { | ||||
|   DocumentService, | ||||
| @@ -93,7 +103,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|     if (this.filterRules.length == 1) { | ||||
|       let rule = this.filterRules[0] | ||||
|       switch (this.filterRules[0].rule_type) { | ||||
|         case FILTER_CORRESPONDENT: | ||||
|         case FILTER_HAS_CORRESPONDENT_ANY: | ||||
|           if (rule.value) { | ||||
|             return $localize`Correspondent: ${ | ||||
|               this.correspondents.find((c) => c.id == +rule.value)?.name | ||||
| @@ -102,7 +112,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|             return $localize`Without correspondent` | ||||
|           } | ||||
|  | ||||
|         case FILTER_DOCUMENT_TYPE: | ||||
|         case FILTER_HAS_DOCUMENT_TYPE_ANY: | ||||
|           if (rule.value) { | ||||
|             return $localize`Type: ${ | ||||
|               this.documentTypes.find((dt) => dt.id == +rule.value)?.name | ||||
| @@ -335,6 +345,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|           this.dateAddedBefore = rule.value | ||||
|           break | ||||
|         case FILTER_HAS_TAGS_ALL: | ||||
|           this.tagSelectionModel.logicalOperator = LogicalOperator.And | ||||
|           this.tagSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
| @@ -342,7 +353,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_HAS_TAGS_ANY: | ||||
|           this.tagSelectionModel.logicalOperator = 'or' | ||||
|           this.tagSelectionModel.logicalOperator = LogicalOperator.Or | ||||
|           this.tagSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
| @@ -360,26 +371,59 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_CORRESPONDENT: | ||||
|         case FILTER_HAS_CORRESPONDENT_ANY: | ||||
|           this.correspondentSelectionModel.logicalOperator = LogicalOperator.Or | ||||
|           this.correspondentSelectionModel.intersection = Intersection.Include | ||||
|           this.correspondentSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_DOES_NOT_HAVE_CORRESPONDENT: | ||||
|           this.correspondentSelectionModel.intersection = Intersection.Exclude | ||||
|           this.correspondentSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Excluded, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_DOCUMENT_TYPE: | ||||
|         case FILTER_HAS_DOCUMENT_TYPE_ANY: | ||||
|           this.documentTypeSelectionModel.logicalOperator = LogicalOperator.Or | ||||
|           this.documentTypeSelectionModel.intersection = Intersection.Include | ||||
|           this.documentTypeSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE: | ||||
|           this.documentTypeSelectionModel.intersection = Intersection.Exclude | ||||
|           this.documentTypeSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Excluded, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_STORAGE_PATH: | ||||
|         case FILTER_HAS_STORAGE_PATH_ANY: | ||||
|           this.storagePathSelectionModel.logicalOperator = LogicalOperator.Or | ||||
|           this.storagePathSelectionModel.intersection = Intersection.Include | ||||
|           this.storagePathSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_DOES_NOT_HAVE_STORAGE_PATH: | ||||
|           this.storagePathSelectionModel.intersection = Intersection.Exclude | ||||
|           this.storagePathSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Excluded, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_ASN_ISNULL: | ||||
|           this.textFilterTarget = TEXT_FILTER_TARGET_ASN | ||||
|           this.textFilterModifier = | ||||
| @@ -469,7 +513,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|       filterRules.push({ rule_type: FILTER_HAS_ANY_TAG, value: 'false' }) | ||||
|     } else { | ||||
|       const tagFilterType = | ||||
|         this.tagSelectionModel.logicalOperator == 'and' | ||||
|         this.tagSelectionModel.logicalOperator == LogicalOperator.And | ||||
|           ? FILTER_HAS_TAGS_ALL | ||||
|           : FILTER_HAS_TAGS_ANY | ||||
|       this.tagSelectionModel | ||||
| @@ -491,28 +535,66 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|           }) | ||||
|         }) | ||||
|     } | ||||
|     if (this.correspondentSelectionModel.isNoneSelected()) { | ||||
|       filterRules.push({ rule_type: FILTER_CORRESPONDENT, value: null }) | ||||
|     } else { | ||||
|       this.correspondentSelectionModel | ||||
|         .getSelectedItems() | ||||
|         .forEach((correspondent) => { | ||||
|           filterRules.push({ | ||||
|           rule_type: FILTER_CORRESPONDENT, | ||||
|             rule_type: FILTER_HAS_CORRESPONDENT_ANY, | ||||
|             value: correspondent.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|       this.correspondentSelectionModel | ||||
|         .getExcludedItems() | ||||
|         .forEach((correspondent) => { | ||||
|           filterRules.push({ | ||||
|             rule_type: FILTER_DOES_NOT_HAVE_CORRESPONDENT, | ||||
|             value: correspondent.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|     } | ||||
|     if (this.documentTypeSelectionModel.isNoneSelected()) { | ||||
|       filterRules.push({ rule_type: FILTER_DOCUMENT_TYPE, value: null }) | ||||
|     } else { | ||||
|       this.documentTypeSelectionModel | ||||
|         .getSelectedItems() | ||||
|         .forEach((documentType) => { | ||||
|           filterRules.push({ | ||||
|           rule_type: FILTER_DOCUMENT_TYPE, | ||||
|             rule_type: FILTER_HAS_DOCUMENT_TYPE_ANY, | ||||
|             value: documentType.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|     this.storagePathSelectionModel.getSelectedItems().forEach((storagePath) => { | ||||
|       this.documentTypeSelectionModel | ||||
|         .getExcludedItems() | ||||
|         .forEach((documentType) => { | ||||
|           filterRules.push({ | ||||
|         rule_type: FILTER_STORAGE_PATH, | ||||
|             rule_type: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE, | ||||
|             value: documentType.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|     } | ||||
|     if (this.storagePathSelectionModel.isNoneSelected()) { | ||||
|       filterRules.push({ rule_type: FILTER_STORAGE_PATH, value: null }) | ||||
|     } else { | ||||
|       this.storagePathSelectionModel | ||||
|         .getSelectedItems() | ||||
|         .forEach((storagePath) => { | ||||
|           filterRules.push({ | ||||
|             rule_type: FILTER_HAS_STORAGE_PATH_ANY, | ||||
|             value: storagePath.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|       this.storagePathSelectionModel | ||||
|         .getExcludedItems() | ||||
|         .forEach((storagePath) => { | ||||
|           filterRules.push({ | ||||
|             rule_type: FILTER_DOES_NOT_HAVE_STORAGE_PATH, | ||||
|             value: storagePath.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|     } | ||||
|     if (this.dateCreatedBefore) { | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_CREATED_BEFORE, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Component } from '@angular/core' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type' | ||||
| import { FILTER_HAS_CORRESPONDENT_ANY } from 'src/app/data/filter-rule-type' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| @@ -35,7 +35,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles | ||||
|       toastService, | ||||
|       documentListViewService, | ||||
|       permissionsService, | ||||
|       FILTER_CORRESPONDENT, | ||||
|       FILTER_HAS_CORRESPONDENT_ANY, | ||||
|       $localize`correspondent`, | ||||
|       $localize`correspondents`, | ||||
|       PermissionType.Correspondent, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Component } from '@angular/core' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type' | ||||
| import { FILTER_HAS_DOCUMENT_TYPE_ANY } from 'src/app/data/filter-rule-type' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { | ||||
| @@ -32,7 +32,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<Paperless | ||||
|       toastService, | ||||
|       documentListViewService, | ||||
|       permissionsService, | ||||
|       FILTER_DOCUMENT_TYPE, | ||||
|       FILTER_HAS_DOCUMENT_TYPE_ANY, | ||||
|       $localize`document type`, | ||||
|       $localize`document types`, | ||||
|       PermissionType.DocumentType, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Component } from '@angular/core' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { FILTER_STORAGE_PATH } from 'src/app/data/filter-rule-type' | ||||
| import { FILTER_HAS_STORAGE_PATH_ANY } from 'src/app/data/filter-rule-type' | ||||
| import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { | ||||
| @@ -32,7 +32,7 @@ export class StoragePathListComponent extends ManagementListComponent<PaperlessS | ||||
|       toastService, | ||||
|       documentListViewService, | ||||
|       permissionsService, | ||||
|       FILTER_STORAGE_PATH, | ||||
|       FILTER_HAS_STORAGE_PATH_ANY, | ||||
|       $localize`storage path`, | ||||
|       $localize`storage paths`, | ||||
|       PermissionType.StoragePath, | ||||
|   | ||||
| @@ -8,8 +8,12 @@ export const FILTER_ASN_GT = 23 | ||||
| export const FILTER_ASN_LT = 24 | ||||
|  | ||||
| export const FILTER_CORRESPONDENT = 3 | ||||
| export const FILTER_HAS_CORRESPONDENT_ANY = 26 | ||||
| export const FILTER_DOES_NOT_HAVE_CORRESPONDENT = 27 | ||||
|  | ||||
| export const FILTER_DOCUMENT_TYPE = 4 | ||||
| export const FILTER_HAS_DOCUMENT_TYPE_ANY = 28 | ||||
| export const FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE = 29 | ||||
|  | ||||
| export const FILTER_IS_IN_INBOX = 5 | ||||
| export const FILTER_HAS_TAGS_ALL = 6 | ||||
| @@ -18,6 +22,8 @@ export const FILTER_DOES_NOT_HAVE_TAG = 17 | ||||
| export const FILTER_HAS_TAGS_ANY = 22 | ||||
|  | ||||
| export const FILTER_STORAGE_PATH = 25 | ||||
| export const FILTER_HAS_STORAGE_PATH_ANY = 30 | ||||
| export const FILTER_DOES_NOT_HAVE_STORAGE_PATH = 31 | ||||
|  | ||||
| export const FILTER_CREATED_BEFORE = 8 | ||||
| export const FILTER_CREATED_AFTER = 9 | ||||
| @@ -63,6 +69,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ | ||||
|     datatype: 'correspondent', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_HAS_CORRESPONDENT_ANY, | ||||
|     filtervar: 'correspondent__id__in', | ||||
|     datatype: 'correspondent', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_DOES_NOT_HAVE_CORRESPONDENT, | ||||
|     filtervar: 'correspondent__id__none', | ||||
|     datatype: 'correspondent', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_STORAGE_PATH, | ||||
|     filtervar: 'storage_path__id', | ||||
| @@ -70,6 +88,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ | ||||
|     datatype: 'storage_path', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_HAS_STORAGE_PATH_ANY, | ||||
|     filtervar: 'storage_path__id__in', | ||||
|     datatype: 'storage_path', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_DOES_NOT_HAVE_STORAGE_PATH, | ||||
|     filtervar: 'storage_path__id__none', | ||||
|     datatype: 'storage_path', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_DOCUMENT_TYPE, | ||||
|     filtervar: 'document_type__id', | ||||
| @@ -77,6 +107,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ | ||||
|     datatype: 'document_type', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_HAS_DOCUMENT_TYPE_ANY, | ||||
|     filtervar: 'document_type__id__in', | ||||
|     datatype: 'document_type', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE, | ||||
|     filtervar: 'document_type__id__none', | ||||
|     datatype: 'document_type', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_IS_IN_INBOX, | ||||
|     filtervar: 'is_in_inbox', | ||||
|   | ||||
| @@ -86,12 +86,12 @@ export function queryParamsFromFilterRules(filterRules: FilterRule[]): Params { | ||||
|     let params = {} | ||||
|     for (let rule of filterRules) { | ||||
|       let ruleType = FILTER_RULE_TYPES.find((t) => t.id == rule.rule_type) | ||||
|       if (ruleType.multi) { | ||||
|       if (ruleType.isnull_filtervar && rule.value == null) { | ||||
|         params[ruleType.isnull_filtervar] = 1 | ||||
|       } else if (ruleType.multi) { | ||||
|         params[ruleType.filtervar] = params[ruleType.filtervar] | ||||
|           ? params[ruleType.filtervar] + ',' + rule.value | ||||
|           : rule.value | ||||
|       } else if (ruleType.isnull_filtervar && rule.value == null) { | ||||
|         params[ruleType.isnull_filtervar] = 1 | ||||
|       } else { | ||||
|         params[ruleType.filtervar] = rule.value | ||||
|         if (ruleType.datatype == 'boolean') | ||||
|   | ||||
| @@ -36,29 +36,30 @@ class DocumentTypeFilterSet(FilterSet): | ||||
|         fields = {"name": CHAR_KWARGS} | ||||
|  | ||||
|  | ||||
| class TagsFilter(Filter): | ||||
|     def __init__(self, exclude=False, in_list=False): | ||||
| class ObjectFilter(Filter): | ||||
|     def __init__(self, exclude=False, in_list=False, field_name=""): | ||||
|         super().__init__() | ||||
|         self.exclude = exclude | ||||
|         self.in_list = in_list | ||||
|         self.field_name = field_name | ||||
|  | ||||
|     def filter(self, qs, value): | ||||
|         if not value: | ||||
|             return qs | ||||
|  | ||||
|         try: | ||||
|             tag_ids = [int(x) for x in value.split(",")] | ||||
|             object_ids = [int(x) for x in value.split(",")] | ||||
|         except ValueError: | ||||
|             return qs | ||||
|  | ||||
|         if self.in_list: | ||||
|             qs = qs.filter(tags__id__in=tag_ids).distinct() | ||||
|             qs = qs.filter(**{f"{self.field_name}__id__in": object_ids}).distinct() | ||||
|         else: | ||||
|             for tag_id in tag_ids: | ||||
|             for obj_id in object_ids: | ||||
|                 if self.exclude: | ||||
|                     qs = qs.exclude(tags__id=tag_id) | ||||
|                     qs = qs.exclude(**{f"{self.field_name}__id": obj_id}) | ||||
|                 else: | ||||
|                     qs = qs.filter(tags__id=tag_id) | ||||
|                     qs = qs.filter(**{f"{self.field_name}__id": obj_id}) | ||||
|  | ||||
|         return qs | ||||
|  | ||||
| @@ -90,11 +91,17 @@ class DocumentFilterSet(FilterSet): | ||||
|         exclude=True, | ||||
|     ) | ||||
|  | ||||
|     tags__id__all = TagsFilter() | ||||
|     tags__id__all = ObjectFilter(field_name="tags") | ||||
|  | ||||
|     tags__id__none = TagsFilter(exclude=True) | ||||
|     tags__id__none = ObjectFilter(field_name="tags", exclude=True) | ||||
|  | ||||
|     tags__id__in = TagsFilter(in_list=True) | ||||
|     tags__id__in = ObjectFilter(field_name="tags", in_list=True) | ||||
|  | ||||
|     correspondent__id__none = ObjectFilter(field_name="correspondent", exclude=True) | ||||
|  | ||||
|     document_type__id__none = ObjectFilter(field_name="document_type", exclude=True) | ||||
|  | ||||
|     storage_path__id__none = ObjectFilter(field_name="storage_path", exclude=True) | ||||
|  | ||||
|     is_in_inbox = InboxFilter() | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,54 @@ | ||||
| # Generated by Django 4.1.5 on 2023-03-15 07:10 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("documents", "1033_alter_documenttype_options_alter_tag_options_and_more"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="savedviewfilterrule", | ||||
|             name="rule_type", | ||||
|             field=models.PositiveIntegerField( | ||||
|                 choices=[ | ||||
|                     (0, "title contains"), | ||||
|                     (1, "content contains"), | ||||
|                     (2, "ASN is"), | ||||
|                     (3, "correspondent is"), | ||||
|                     (4, "document type is"), | ||||
|                     (5, "is in inbox"), | ||||
|                     (6, "has tag"), | ||||
|                     (7, "has any tag"), | ||||
|                     (8, "created before"), | ||||
|                     (9, "created after"), | ||||
|                     (10, "created year is"), | ||||
|                     (11, "created month is"), | ||||
|                     (12, "created day is"), | ||||
|                     (13, "added before"), | ||||
|                     (14, "added after"), | ||||
|                     (15, "modified before"), | ||||
|                     (16, "modified after"), | ||||
|                     (17, "does not have tag"), | ||||
|                     (18, "does not have ASN"), | ||||
|                     (19, "title or content contains"), | ||||
|                     (20, "fulltext query"), | ||||
|                     (21, "more like this"), | ||||
|                     (22, "has tags in"), | ||||
|                     (23, "ASN greater than"), | ||||
|                     (24, "ASN less than"), | ||||
|                     (25, "storage path is"), | ||||
|                     (26, "has correspondent in"), | ||||
|                     (27, "does not have correspondent in"), | ||||
|                     (28, "has document type in"), | ||||
|                     (29, "does not have document type in"), | ||||
|                     (30, "has storage path in"), | ||||
|                     (31, "does not have storage path in"), | ||||
|                 ], | ||||
|                 verbose_name="rule type", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @@ -447,6 +447,12 @@ class SavedViewFilterRule(models.Model): | ||||
|         (23, _("ASN greater than")), | ||||
|         (24, _("ASN less than")), | ||||
|         (25, _("storage path is")), | ||||
|         (26, _("has correspondent in")), | ||||
|         (27, _("does not have correspondent in")), | ||||
|         (28, _("has document type in")), | ||||
|         (29, _("does not have document type in")), | ||||
|         (30, _("has storage path in")), | ||||
|         (31, _("does not have storage path in")), | ||||
|     ] | ||||
|  | ||||
|     saved_view = models.ForeignKey( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon