Frontend implementation of consumption templates

Testing
This commit is contained in:
shamoon 2023-09-16 01:09:04 -07:00
parent 483fa245b0
commit ed37f82c0f
23 changed files with 674 additions and 3 deletions

View File

@ -21,6 +21,7 @@ import {
PermissionAction,
PermissionType,
} from './services/permissions.service'
import { ConsmptionTemplatesListComponent } from './components/manage/consmption-templates-list/consmption-templates-list.component'
export const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
@ -182,7 +183,17 @@ export const routes: Routes = [
},
},
},
{ path: 'tasks', component: TasksComponent },
{
path: 'templates',
component: ConsmptionTemplatesListComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.ConsumptionTemplate,
},
},
},
],
},

View File

@ -95,6 +95,8 @@ import { UsernamePipe } from './pipes/username.pipe'
import { LogoComponent } from './components/common/logo/logo.component'
import { IsNumberPipe } from './pipes/is-number.pipe'
import { ShareLinksDropdownComponent } from './components/common/share-links-dropdown/share-links-dropdown.component'
import { ConsmptionTemplatesListComponent } from './components/manage/consmption-templates-list/consmption-templates-list.component'
import { ConsumptionTemplateEditDialogComponent } from './components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
import localeAf from '@angular/common/locales/af'
import localeAr from '@angular/common/locales/ar'
@ -233,6 +235,8 @@ function initializeApp(settings: SettingsService) {
LogoComponent,
IsNumberPipe,
ShareLinksDropdownComponent,
ConsmptionTemplatesListComponent,
ConsumptionTemplateEditDialogComponent,
],
imports: [
BrowserModule,

View File

@ -155,6 +155,13 @@
</svg><span>&nbsp;<ng-container i18n>Storage paths</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.ConsumptionTemplate }">
<a class="nav-link" routerLink="templates" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Consumption templates" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-ruled"/>
</svg><span>&nbsp;<ng-container i18n>Templates</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>

View File

@ -0,0 +1,73 @@
<form [formGroup]="objectForm" (ngSubmit)="save()">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col">
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
<pngx-input-number i18n-title title="Rule order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
<p class="small" i18n>Paperless-ngx will process mails that match <em>any</em> of the filters specified below.</p>
<pngx-input-text i18n-title title="Filter filename" formControlName="filter_filename" i18n-hint hint="Apply template to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_filename"></pngx-input-text>
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply template to documents that match this path. Wildcards specified as * are allowed. Case insensitive." [error]="error?.filter_path"></pngx-input-text>
</div>
<div class="col">
<pngx-input-tags [allowCreate]="false" i18n-title title="Assign tags" formControlName="assign_tags"></pngx-input-tags>
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
</div>
<div class="col">
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
<div>
<label class="form-label" i18n>Assign view permissions</label>
<div class="mb-2">
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="view" formControlName="assign_view_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="view" formControlName="assign_view_groups"></pngx-permissions-group>
</div>
</div>
</div>
<label class="form-label" i18n>Assign edit permissions</label>
<div>
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="change" formControlName="assign_change_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="change" formControlName="assign_change_groups"></pngx-permissions-group>
</div>
</div>
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<span class="text-danger" *ngIf="error?.non_field_errors"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
</div>
</form>

View File

@ -0,0 +1,63 @@
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { NumberComponent } from '../../input/number/number.component'
import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component'
import { PermissionsUserComponent } from '../../input/permissions/permissions-user/permissions-user.component'
import { SelectComponent } from '../../input/select/select.component'
import { TagsComponent } from '../../input/tags/tags.component'
import { TextComponent } from '../../input/text/text.component'
import { ConsumptionTemplateEditDialogComponent } from './consumption-template-edit-dialog.component'
import { EditDialogMode } from '../edit-dialog.component'
describe('ConsumptionTemplateEditDialogComponent', () => {
let component: ConsumptionTemplateEditDialogComponent
let fixture: ComponentFixture<ConsumptionTemplateEditDialogComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
ConsumptionTemplateEditDialogComponent,
IfPermissionsDirective,
IfOwnerDirective,
SelectComponent,
TextComponent,
NumberComponent,
TagsComponent,
PermissionsUserComponent,
PermissionsGroupComponent,
SafeHtmlPipe,
],
providers: [NgbActiveModal],
imports: [
HttpClientTestingModule,
FormsModule,
ReactiveFormsModule,
NgSelectModule,
NgbModule,
],
}).compileComponents()
fixture = TestBed.createComponent(ConsumptionTemplateEditDialogComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should support create and edit modes', () => {
component.dialogMode = EditDialogMode.CREATE
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
fixture.detectChanges()
expect(createTitleSpy).toHaveBeenCalled()
expect(editTitleSpy).not.toHaveBeenCalled()
component.dialogMode = EditDialogMode.EDIT
fixture.detectChanges()
expect(editTitleSpy).toHaveBeenCalled()
})
})

View File

@ -0,0 +1,88 @@
import { Component } from '@angular/core'
import { FormGroup, FormControl } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { first } from 'rxjs'
import { PaperlessConsumptionTemplate } from 'src/app/data/paperless-consumption-template'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import {
MailFilterAttachmentType,
MailRuleConsumptionScope,
MailAction,
MailMetadataTitleOption,
MailMetadataCorrespondentOption,
} from 'src/app/data/paperless-mail-rule'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { ConsumptionTemplateService } from 'src/app/services/rest/consumption-template.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
import { EditDialogComponent } from '../edit-dialog.component'
@Component({
selector: 'pngx-consumption-template-edit-dialog',
templateUrl: './consumption-template-edit-dialog.component.html',
styleUrls: ['./consumption-template-edit-dialog.component.scss'],
})
export class ConsumptionTemplateEditDialogComponent extends EditDialogComponent<PaperlessConsumptionTemplate> {
templates: PaperlessConsumptionTemplate[]
correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[]
storagePaths: PaperlessStoragePath[]
constructor(
service: ConsumptionTemplateService,
activeModal: NgbActiveModal,
correspondentService: CorrespondentService,
documentTypeService: DocumentTypeService,
storagePathService: StoragePathService,
userService: UserService,
settingsService: SettingsService
) {
super(service, activeModal, userService, settingsService)
correspondentService
.listAll()
.pipe(first())
.subscribe((result) => (this.correspondents = result.results))
documentTypeService
.listAll()
.pipe(first())
.subscribe((result) => (this.documentTypes = result.results))
storagePathService
.listAll()
.pipe(first())
.subscribe((result) => (this.storagePaths = result.results))
}
getCreateTitle() {
return $localize`Create new consumption template`
}
getEditTitle() {
return $localize`Edit consumption template`
}
getForm(): FormGroup {
return new FormGroup({
name: new FormControl(null),
account: new FormControl(null),
filter_filename: new FormControl(null),
filter_path: new FormControl(null),
order: new FormControl(null),
assign_tags: new FormControl([]),
assign_owner: new FormControl(null),
assign_document_type: new FormControl(null),
assign_correspondent: new FormControl(null),
assign_storage_path: new FormControl(null),
assign_view_users: new FormControl(null),
assign_view_groups: new FormControl(null),
assign_change_users: new FormControl(null),
assign_change_groups: new FormControl(null),
})
}
}

View File

@ -1,5 +1,5 @@
<div class="mb-3 paperless-input-select paperless-input-tags" [class.disabled]="disabled">
<label class="form-label" for="tags" i18n>Tags</label>
<label class="form-label" for="tags" i18n>{{title}}</label>
<div class="input-group flex-nowrap">
<ng-select #tagSelect name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"

View File

@ -59,6 +59,9 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
})
}
@Input()
title = $localize`Tags`
@Input()
disabled = false

View File

@ -0,0 +1,33 @@
<pngx-page-header title="Consumption Templates">
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editTemplate()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ConsumptionTemplate }">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>
<ng-container i18n>Add Template</ng-container>
</button>
</pngx-page-header>
<table class="table table-striped align-middle border shadow-sm">
<thead>
<tr>
<th scope="col" i18n>Name</th>
<th scope="col" i18n>File name filter</th>
<th scope="col" i18n>Path filter</th>
<th scope="col" i18n>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let template of templates">
<td scope="row"><button class="btn btn-link p-0" type="button" (click)="editTemplate(template)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.ConsumptionTemplate)">{{template.name}}</button></td>
<td scope="row"><code>{{template.filter_filename}}</code></td>
<td scope="row"><code>{{template.filter_path}}</code></td>
<td scope="row">
<div class="btn-group">
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.ConsumptionTemplate }" [disabled]="!userCanEdit(template)" class="btn btn-sm btn-primary" type="button" (click)="editTemplate(template)" i18n>Edit</button>
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.ConsumptionTemplate }" [disabled]="!userIsOwner(template)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteTemplate(template)" i18n>Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
<div *ngIf="templates.length === 0" i18n>No templates defined.</div>

View File

@ -0,0 +1,166 @@
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import {
NgbModal,
NgbPaginationModule,
NgbModalRef,
NgbModalModule,
} from '@ng-bootstrap/ng-bootstrap'
import { of, throwError } from 'rxjs'
import { PaperlessConsumptionTemplate } from 'src/app/data/paperless-consumption-template'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { ConsumptionTemplateService } from 'src/app/services/rest/consumption-template.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { ConsmptionTemplatesListComponent } from './consmption-templates-list.component'
import { ConsumptionTemplateEditDialogComponent } from '../../common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
import { PermissionsService } from 'src/app/services/permissions.service'
const templates: PaperlessConsumptionTemplate[] = [
{
id: 0,
name: 'Template 1',
order: 0,
filter_filename: 'foo',
filter_path: 'bar',
assign_tags: [1, 2, 3],
},
{
id: 1,
name: 'Template 2',
order: 1,
filter_filename: null,
filter_path: 'foo/bar',
assign_owner: 1,
},
]
describe('ConsmptionTemplatesComponent', () => {
let component: ConsmptionTemplatesListComponent
let fixture: ComponentFixture<ConsmptionTemplatesListComponent>
let consumptionTemplateService: ConsumptionTemplateService
let modalService: NgbModal
let toastService: ToastService
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
ConsmptionTemplatesListComponent,
IfPermissionsDirective,
PageHeaderComponent,
ConfirmDialogComponent,
],
providers: [
{
provide: PermissionsService,
useValue: {
currentUserCan: () => true,
currentUserHasObjectPermissions: () => true,
currentUserOwnsObject: () => true,
},
},
],
imports: [
HttpClientTestingModule,
NgbPaginationModule,
FormsModule,
ReactiveFormsModule,
NgbModalModule,
],
})
consumptionTemplateService = TestBed.inject(ConsumptionTemplateService)
jest.spyOn(consumptionTemplateService, 'listAll').mockReturnValue(
of({
count: templates.length,
all: templates.map((o) => o.id),
results: templates,
})
)
modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService)
fixture = TestBed.createComponent(ConsmptionTemplatesListComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should support create, show notification on error / success', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reload')
const createButton = fixture.debugElement.queryAll(By.css('button'))[0]
createButton.triggerEventHandler('click')
expect(modal).not.toBeUndefined()
const editDialog =
modal.componentInstance as ConsumptionTemplateEditDialogComponent
// fail first
editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled()
// succeed
editDialog.succeeded.emit(templates[0])
expect(toastInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled()
})
it('should support edit, show notification on error / success', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reload')
const editButton = fixture.debugElement.queryAll(By.css('button'))[1]
editButton.triggerEventHandler('click')
expect(modal).not.toBeUndefined()
const editDialog =
modal.componentInstance as ConsumptionTemplateEditDialogComponent
expect(editDialog.object).toEqual(templates[0])
// fail first
editDialog.failed.emit({ error: 'error editing item' })
expect(toastErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled()
// succeed
editDialog.succeeded.emit(templates[0])
expect(toastInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled()
})
it('should support delete, show notification on error / success', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const deleteSpy = jest.spyOn(consumptionTemplateService, 'delete')
const reloadSpy = jest.spyOn(component, 'reload')
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[3]
deleteButton.triggerEventHandler('click')
expect(modal).not.toBeUndefined()
const editDialog = modal.componentInstance as ConfirmDialogComponent
// fail first
deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
editDialog.confirmClicked.emit()
expect(toastErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled()
// succeed
deleteSpy.mockReturnValueOnce(of(true))
editDialog.confirmClicked.emit()
expect(reloadSpy).toHaveBeenCalled()
})
})

View File

@ -0,0 +1,111 @@
import { Component, OnInit } from '@angular/core'
import { ConsumptionTemplateService } from 'src/app/services/rest/consumption-template.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { Subject, takeUntil } from 'rxjs'
import { PaperlessConsumptionTemplate } from 'src/app/data/paperless-consumption-template'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ToastService } from 'src/app/services/toast.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { ConsumptionTemplateEditDialogComponent } from '../../common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
@Component({
selector: 'pngx-consmption-templates-list',
templateUrl: './consmption-templates-list.component.html',
styleUrls: ['./consmption-templates-list.component.scss'],
})
export class ConsmptionTemplatesListComponent
extends ComponentWithPermissions
implements OnInit
{
public templates: PaperlessConsumptionTemplate[] = []
private unsubscribeNotifier: Subject<any> = new Subject()
constructor(
private consumptionTemplateService: ConsumptionTemplateService,
public permissionsService: PermissionsService,
private modalService: NgbModal,
private toastService: ToastService
) {
super()
}
ngOnInit() {
this.reload()
}
reload() {
this.consumptionTemplateService
.listAll()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((r) => {
this.templates = r.results
})
}
editTemplate(rule: PaperlessConsumptionTemplate) {
const modal = this.modalService.open(
ConsumptionTemplateEditDialogComponent,
{
backdrop: 'static',
size: 'xl',
}
)
modal.componentInstance.dialogMode = rule
? EditDialogMode.EDIT
: EditDialogMode.CREATE
modal.componentInstance.object = rule
modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newTemplate) => {
this.toastService.showInfo(
$localize`Saved template "${newTemplate.name}".`
)
this.consumptionTemplateService.clearCache()
this.reload()
})
modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => {
this.toastService.showError($localize`Error saving template.`, e)
})
}
deleteTemplate(rule: PaperlessConsumptionTemplate) {
const modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.title = $localize`Confirm delete template`
modal.componentInstance.messageBold = $localize`This operation will permanently delete this template.`
modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Proceed`
modal.componentInstance.confirmClicked.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.consumptionTemplateService.delete(rule).subscribe({
next: () => {
modal.close()
this.toastService.showInfo($localize`Deleted template`)
this.consumptionTemplateService.clearCache()
this.reload()
},
error: (e) => {
this.toastService.showError($localize`Error deleting template.`, e)
},
})
})
}
userCanEdit(template: PaperlessConsumptionTemplate): boolean {
return this.permissionsService.currentUserHasObjectPermissions(
this.PermissionAction.Change,
template
)
}
userIsOwner(template: PaperlessConsumptionTemplate): boolean {
return this.permissionsService.currentUserOwnsObject(template)
}
}

View File

@ -0,0 +1 @@
<p>consmption-templates works!</p>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ConsmptionTemplatesComponent } from './consmption-templates.component'
describe('ConsmptionTemplatesComponent', () => {
let component: ConsmptionTemplatesComponent
let fixture: ComponentFixture<ConsmptionTemplatesComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ConsmptionTemplatesComponent],
})
fixture = TestBed.createComponent(ConsmptionTemplatesComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@ -0,0 +1,8 @@
import { Component } from '@angular/core'
@Component({
selector: 'pngx-consmption-templates',
templateUrl: './consmption-templates.component.html',
styleUrls: ['./consmption-templates.component.scss'],
})
export class ConsmptionTemplatesComponent {}

View File

@ -0,0 +1,29 @@
import { ObjectWithPermissions } from './object-with-permissions'
export interface PaperlessConsumptionTemplate extends ObjectWithPermissions {
name: string
order: number
filter_filename: string
filter_path: string
assign_tags?: number[] // PaperlessTag.id
assign_document_type?: number // PaperlessDocumentType.id
assign_correspondent?: number // PaperlessCorrespondent.id
assign_storage_path?: number // PaperlessStoragePath.id
assign_owner?: number // PaperlessUser.id
assign_view_users?: number[] // [PaperlessUser.id]
assign_view_groups?: number[] // [PaperlessGroup.id]
assign_change_users?: number[] // [PaperlessUser.id]
assign_change_groups?: number[] // [PaperlessGroup.id]
}

View File

@ -1,4 +1,3 @@
import { PaperlessGroup } from 'src/app/data/paperless-group'
import { ObjectWithId } from './object-with-id'
export interface PaperlessUser extends ObjectWithId {

View File

@ -252,6 +252,10 @@ describe('PermissionsService', () => {
'view_sharelink',
'change_sharelink',
'delete_sharelink',
'add_consumptiontemplate',
'view_consumptiontemplate',
'change_consumptiontemplate',
'delete_consumptiontemplate',
],
{
username: 'testuser',

View File

@ -25,6 +25,7 @@ export enum PermissionType {
Group = '%s_group',
Admin = '%s_logentry',
ShareLink = '%s_sharelink',
ConsumptionTemplate = '%s_consumptiontemplate',
}
@Injectable({

View File

@ -0,0 +1,7 @@
import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec'
import { ConsumptionTemplateService } from './consumption-template.service'
commonAbstractPaperlessServiceTests(
'consumption_templates',
ConsumptionTemplateService
)

View File

@ -0,0 +1,42 @@
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { tap } from 'rxjs'
import { PaperlessConsumptionTemplate } from 'src/app/data/paperless-consumption-template'
import { AbstractPaperlessService } from './abstract-paperless-service'
@Injectable({
providedIn: 'root',
})
export class ConsumptionTemplateService extends AbstractPaperlessService<PaperlessConsumptionTemplate> {
loading: boolean
constructor(http: HttpClient) {
super(http, 'consumption_templates')
}
private reload() {
this.loading = true
this.listAll().subscribe((r) => {
this.templates = r.results
this.loading = false
})
}
private templates: PaperlessConsumptionTemplate[] = []
public get allTemplates(): PaperlessConsumptionTemplate[] {
return this.templates
}
create(o: PaperlessConsumptionTemplate) {
return super.create(o).pipe(tap(() => this.reload()))
}
update(o: PaperlessConsumptionTemplate) {
return super.update(o).pipe(tap(() => this.reload()))
}
delete(o: PaperlessConsumptionTemplate) {
return super.delete(o).pipe(tap(() => this.reload()))
}
}