</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>
</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>
</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>
<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>
<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">
<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">
<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">
</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>
<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>
<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>
<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>
<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">
)"/></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">
</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>
<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">
<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">
)"/></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">
<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">
<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>
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[] = []
(state != ToggleableItemState.Selected &&
state != ToggleableItemState.Excluded)
) {
- this.temporarySelectionStates.set(id, ToggleableItemState.Selected)
+ if (this.manyToOne || this.singleSelect) {
+ this.temporarySelectionStates.set(id, ToggleableItemState.Selected)
+
+ 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 (!this.multiple) {
- for (let key of this.temporarySelectionStates.keys()) {
- if (key != id) {
- this.temporarySelectionStates.delete(key)
- }
- }
- }
-
if (!id) {
for (let key of this.temporarySelectionStates.keys()) {
if (key) {
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 (!this.multiple) {
- for (let key of this.temporarySelectionStates.keys()) {
- if (key != id) {
- this.temporarySelectionStates.delete(key)
+ if (id && (state == null || state != ToggleableItemState.Excluded)) {
+ this.temporaryLogicalOperator = this._logicalOperator = this.manyToOne
+ ? LogicalOperator.And
+ : LogicalOperator.Or
+
+ 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) {
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
}
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
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)
}
return true
} else if (this.temporaryLogicalOperator !== this._logicalOperator) {
return true
+ } else if (this.temporaryIntersection !== this._intersection) {
+ return true
} else {
return false
}
this.selectionStates.set(key, value)
})
this._logicalOperator = this.temporaryLogicalOperator
+ this._intersection = this.temporaryIntersection
}
- reset() {
+ reset(complete: boolean = false) {
this.temporarySelectionStates.clear()
- this.selectionStates.forEach((value, key) => {
- this.temporarySelectionStates.set(key, value)
- })
+ if (complete) {
+ this.selectionStates.clear()
+ } else {
+ this.selectionStates.forEach((value, key) => {
+ this.temporarySelectionStates.set(key, value)
+ })
+ }
}
diff(): ChangedItems {
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)
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()
@Output()
opened = new EventEmitter()
- get operatorToggleEnabled(): boolean {
- return (
- this.selectionModel.selectionSize() > 1 &&
- this.selectionModel.getExcludedItems().length == 0
- )
+ 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
modelIsDirty: boolean = false
constructor(private filterPipe: FilterPipe) {
- this.selectionModel = new FilterableDropdownSelectionModel()
this.selectionModelChange.subscribe((updatedModel) => {
this.modelIsDirty = updatedModel.isDirty()
})
}
reset() {
- this.selectionModel.reset()
+ this.selectionModel.reset(true)
this.selectionModelChange.emit(this.selectionModel)
}
}
[items]="tags"
[disabled]="!userCanEditAll"
[editing]="true"
- [multiple]="true"
+ [manyToOne]="true"
[applyOnClose]="applyOnClose"
(opened)="openTagsDropdown()"
[(selectionModel)]="tagSelectionModel"
</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" />
<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()"
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,
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,
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
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
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,
)
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,
)
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 =
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
})
})
}
- this.correspondentSelectionModel
- .getSelectedItems()
- .forEach((correspondent) => {
- filterRules.push({
- rule_type: FILTER_CORRESPONDENT,
- value: correspondent.id?.toString(),
+ if (this.correspondentSelectionModel.isNoneSelected()) {
+ filterRules.push({ rule_type: FILTER_CORRESPONDENT, value: null })
+ } else {
+ this.correspondentSelectionModel
+ .getSelectedItems()
+ .forEach((correspondent) => {
+ filterRules.push({
+ rule_type: FILTER_HAS_CORRESPONDENT_ANY,
+ value: correspondent.id?.toString(),
+ })
})
- })
- this.documentTypeSelectionModel
- .getSelectedItems()
- .forEach((documentType) => {
- filterRules.push({
- rule_type: FILTER_DOCUMENT_TYPE,
- value: documentType.id?.toString(),
+ this.correspondentSelectionModel
+ .getExcludedItems()
+ .forEach((correspondent) => {
+ filterRules.push({
+ rule_type: FILTER_DOES_NOT_HAVE_CORRESPONDENT,
+ value: correspondent.id?.toString(),
+ })
})
- })
- this.storagePathSelectionModel.getSelectedItems().forEach((storagePath) => {
- filterRules.push({
- rule_type: FILTER_STORAGE_PATH,
- value: storagePath.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_HAS_DOCUMENT_TYPE_ANY,
+ value: documentType.id?.toString(),
+ })
+ })
+ this.documentTypeSelectionModel
+ .getExcludedItems()
+ .forEach((documentType) => {
+ filterRules.push({
+ 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,
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'
toastService,
documentListViewService,
permissionsService,
- FILTER_CORRESPONDENT,
+ FILTER_HAS_CORRESPONDENT_ANY,
$localize`correspondent`,
$localize`correspondents`,
PermissionType.Correspondent,
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 {
toastService,
documentListViewService,
permissionsService,
- FILTER_DOCUMENT_TYPE,
+ FILTER_HAS_DOCUMENT_TYPE_ANY,
$localize`document type`,
$localize`document types`,
PermissionType.DocumentType,
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 {
toastService,
documentListViewService,
permissionsService,
- FILTER_STORAGE_PATH,
+ FILTER_HAS_STORAGE_PATH_ANY,
$localize`storage path`,
$localize`storage paths`,
PermissionType.StoragePath,
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
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
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',
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',
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',
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')
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
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()
--- /dev/null
+# 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",
+ ),
+ ),
+ ]
(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(