mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Make separate properties, assign_custom_fields_values
This commit is contained in:
parent
6964b184d1
commit
9de605d2e6
@ -188,7 +188,8 @@
|
|||||||
<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 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 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>
|
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
|
||||||
<pngx-input-custom-fields-select i18n-title title="Assign custom fields" formControlName="assign_custom_fields_w_values"></pngx-input-custom-fields-select>
|
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
|
||||||
|
<pngx-input-custom-fields-values i18n-title title="Assign custom field values" [selectedFields]="formGroup.get('assign_custom_fields').value" formControlName="assign_custom_fields_values"></pngx-input-custom-fields-values>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
|
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
|
||||||
|
@ -16,7 +16,7 @@ import { NgbAccordionModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { first } from 'rxjs'
|
import { first } from 'rxjs'
|
||||||
import { Correspondent } from 'src/app/data/correspondent'
|
import { Correspondent } from 'src/app/data/correspondent'
|
||||||
import { CustomField } from 'src/app/data/custom-field'
|
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
import { DocumentType } from 'src/app/data/document-type'
|
import { DocumentType } from 'src/app/data/document-type'
|
||||||
import { MailRule } from 'src/app/data/mail-rule'
|
import { MailRule } from 'src/app/data/mail-rule'
|
||||||
import {
|
import {
|
||||||
@ -38,6 +38,7 @@ import {
|
|||||||
WorkflowTriggerType,
|
WorkflowTriggerType,
|
||||||
} from 'src/app/data/workflow-trigger'
|
} from 'src/app/data/workflow-trigger'
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||||
@ -46,7 +47,7 @@ import { WorkflowService } from 'src/app/services/rest/workflow.service'
|
|||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
|
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
|
||||||
import { CheckComponent } from '../../input/check/check.component'
|
import { CheckComponent } from '../../input/check/check.component'
|
||||||
import { CustomFieldsSelectComponent } from '../../input/custom-fields-select/custom-fields-select.component'
|
import { CustomFieldsValuesComponent } from '../../input/custom-fields-values/custom-fields-values.component'
|
||||||
import { EntriesComponent } from '../../input/entries/entries.component'
|
import { EntriesComponent } from '../../input/entries/entries.component'
|
||||||
import { NumberComponent } from '../../input/number/number.component'
|
import { NumberComponent } from '../../input/number/number.component'
|
||||||
import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component'
|
import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component'
|
||||||
@ -148,10 +149,10 @@ const TRIGGER_MATCHING_ALGORITHMS = MATCHING_ALGORITHMS.filter(
|
|||||||
SwitchComponent,
|
SwitchComponent,
|
||||||
NumberComponent,
|
NumberComponent,
|
||||||
TextComponent,
|
TextComponent,
|
||||||
CustomFieldsSelectComponent,
|
|
||||||
SelectComponent,
|
SelectComponent,
|
||||||
TextAreaComponent,
|
TextAreaComponent,
|
||||||
TagsComponent,
|
TagsComponent,
|
||||||
|
CustomFieldsValuesComponent,
|
||||||
PermissionsGroupComponent,
|
PermissionsGroupComponent,
|
||||||
PermissionsUserComponent,
|
PermissionsUserComponent,
|
||||||
ConfirmButtonComponent,
|
ConfirmButtonComponent,
|
||||||
@ -169,12 +170,14 @@ export class WorkflowEditDialogComponent
|
|||||||
{
|
{
|
||||||
public WorkflowTriggerType = WorkflowTriggerType
|
public WorkflowTriggerType = WorkflowTriggerType
|
||||||
public WorkflowActionType = WorkflowActionType
|
public WorkflowActionType = WorkflowActionType
|
||||||
|
public CustomFieldDataType = CustomFieldDataType
|
||||||
|
|
||||||
templates: Workflow[]
|
templates: Workflow[]
|
||||||
correspondents: Correspondent[]
|
correspondents: Correspondent[]
|
||||||
documentTypes: DocumentType[]
|
documentTypes: DocumentType[]
|
||||||
storagePaths: StoragePath[]
|
storagePaths: StoragePath[]
|
||||||
mailRules: MailRule[]
|
mailRules: MailRule[]
|
||||||
|
customFields: CustomField[]
|
||||||
dateCustomFields: CustomField[]
|
dateCustomFields: CustomField[]
|
||||||
|
|
||||||
expandedItem: number = null
|
expandedItem: number = null
|
||||||
@ -189,7 +192,8 @@ export class WorkflowEditDialogComponent
|
|||||||
storagePathService: StoragePathService,
|
storagePathService: StoragePathService,
|
||||||
mailRuleService: MailRuleService,
|
mailRuleService: MailRuleService,
|
||||||
userService: UserService,
|
userService: UserService,
|
||||||
settingsService: SettingsService
|
settingsService: SettingsService,
|
||||||
|
customFieldsService: CustomFieldsService
|
||||||
) {
|
) {
|
||||||
super(service, activeModal, userService, settingsService)
|
super(service, activeModal, userService, settingsService)
|
||||||
|
|
||||||
@ -212,6 +216,16 @@ export class WorkflowEditDialogComponent
|
|||||||
.listAll()
|
.listAll()
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe((result) => (this.mailRules = result.results))
|
.subscribe((result) => (this.mailRules = result.results))
|
||||||
|
|
||||||
|
customFieldsService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => {
|
||||||
|
this.customFields = result.results
|
||||||
|
this.dateCustomFields = this.customFields?.filter(
|
||||||
|
(f) => f.data_type === CustomFieldDataType.Date
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
getCreateTitle() {
|
||||||
@ -252,8 +266,6 @@ export class WorkflowEditDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
private checkRemovalActionFields(formWorkflow: Workflow) {
|
private checkRemovalActionFields(formWorkflow: Workflow) {
|
||||||
console.log('checkRemovalActionFields', formWorkflow)
|
|
||||||
|
|
||||||
formWorkflow.actions
|
formWorkflow.actions
|
||||||
.filter((action) => action.type === WorkflowActionType.Removal)
|
.filter((action) => action.type === WorkflowActionType.Removal)
|
||||||
.forEach((action, i) => {
|
.forEach((action, i) => {
|
||||||
@ -429,8 +441,9 @@ export class WorkflowEditDialogComponent
|
|||||||
assign_view_groups: new FormControl(action.assign_view_groups),
|
assign_view_groups: new FormControl(action.assign_view_groups),
|
||||||
assign_change_users: new FormControl(action.assign_change_users),
|
assign_change_users: new FormControl(action.assign_change_users),
|
||||||
assign_change_groups: new FormControl(action.assign_change_groups),
|
assign_change_groups: new FormControl(action.assign_change_groups),
|
||||||
assign_custom_fields_w_values: new FormControl(
|
assign_custom_fields: new FormControl(action.assign_custom_fields),
|
||||||
action.assign_custom_fields_w_values
|
assign_custom_fields_values: new FormControl(
|
||||||
|
action.assign_custom_fields_values
|
||||||
),
|
),
|
||||||
remove_tags: new FormControl(action.remove_tags),
|
remove_tags: new FormControl(action.remove_tags),
|
||||||
remove_all_tags: new FormControl(action.remove_all_tags),
|
remove_all_tags: new FormControl(action.remove_all_tags),
|
||||||
@ -557,7 +570,8 @@ export class WorkflowEditDialogComponent
|
|||||||
assign_view_groups: [],
|
assign_view_groups: [],
|
||||||
assign_change_users: [],
|
assign_change_users: [],
|
||||||
assign_change_groups: [],
|
assign_change_groups: [],
|
||||||
assign_custom_fields_w_values: [],
|
assign_custom_fields: [],
|
||||||
|
assign_custom_fields_values: {},
|
||||||
remove_tags: [],
|
remove_tags: [],
|
||||||
remove_all_tags: false,
|
remove_all_tags: false,
|
||||||
remove_document_types: [],
|
remove_document_types: [],
|
||||||
@ -636,4 +650,8 @@ export class WorkflowEditDialogComponent
|
|||||||
})
|
})
|
||||||
super.save()
|
super.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCustomField(id: number): CustomField {
|
||||||
|
return this.customFields.find((field) => field.id === id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
<div class="mb-3 paperless-input-select" [class.disabled]="disabled">
|
|
||||||
<div class="row">
|
|
||||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
|
||||||
@if (title) {
|
|
||||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div [class.col-md-9]="horizontal">
|
|
||||||
<div [class.is-invalid]="error">
|
|
||||||
<ng-select name="inputId" [(ngModel)]="selectedFields"
|
|
||||||
[disabled]="disabled"
|
|
||||||
[clearable]="true"
|
|
||||||
[items]="fields"
|
|
||||||
[addTag]="false"
|
|
||||||
notFoundText="No fields found"
|
|
||||||
i18n-notFoundText
|
|
||||||
[multiple]="true"
|
|
||||||
bindLabel="name"
|
|
||||||
bindValue="id"
|
|
||||||
(change)="onChange(value)">
|
|
||||||
<ng-template ng-option-tmp let-item="item">
|
|
||||||
<span [title]="item.name">{{item.name}}</span>
|
|
||||||
</ng-template>
|
|
||||||
</ng-select>
|
|
||||||
@if (selectedFields.length) {
|
|
||||||
<div class="list-group mt-3 selected-fields">
|
|
||||||
@for (fieldId of selectedFields; track fieldId) {
|
|
||||||
<div class="list-group-item
|
|
||||||
d-flex
|
|
||||||
justify-content-between
|
|
||||||
align-items-center">
|
|
||||||
@switch (getCustomField(fieldId)?.data_type) {
|
|
||||||
@case (CustomFieldDataType.String) {
|
|
||||||
<pngx-input-text [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"></pngx-input-text>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Date) {
|
|
||||||
<pngx-input-date [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"></pngx-input-date>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Integer) {
|
|
||||||
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"
|
|
||||||
[showAdd]="false"></pngx-input-number>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Float) {
|
|
||||||
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"
|
|
||||||
[showAdd]="false"
|
|
||||||
[step]=".1"></pngx-input-number>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Monetary) {
|
|
||||||
<pngx-input-monetary [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[defaultCurrency]="getCustomField(fieldId)?.extra_data?.default_currency"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"></pngx-input-monetary>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Boolean) {
|
|
||||||
<pngx-input-check [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"></pngx-input-check>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Url) {
|
|
||||||
<pngx-input-url [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"></pngx-input-url>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.DocumentLink) {
|
|
||||||
<pngx-input-document-link [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[horizontal]="true"></pngx-input-document-link>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Select) {
|
|
||||||
<pngx-input-select [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
|
||||||
[title]="getCustomField(fieldId)?.name"
|
|
||||||
class="flex-grow-1"
|
|
||||||
[items]="getCustomField(fieldId)?.extra_data.select_options"
|
|
||||||
class="flex-grow-1"
|
|
||||||
bindLabel="label"
|
|
||||||
[allowNull]="true"
|
|
||||||
[horizontal]="true"></pngx-input-select>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<button type="button" class="btn btn-link text-danger" (click)="removeField(fieldId)">
|
|
||||||
<i-bs name="trash"></i-bs>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
{{error}}
|
|
||||||
</div>
|
|
||||||
@if (hint) {
|
|
||||||
<small class="form-text text-muted">{{hint}}</small>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,41 +0,0 @@
|
|||||||
// styles for ng-select child are in styles.scss
|
|
||||||
.paperless-input-select.disabled {
|
|
||||||
.input-group,
|
|
||||||
div > div {
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep ng-select {
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
.ng-select-container {
|
|
||||||
background-color: var(--pngx-bg-disabled) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep .private .ng-value-container {
|
|
||||||
font-style: italic;
|
|
||||||
opacity: .75;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep .is-invalid ng-select .ng-select-container input {
|
|
||||||
// replicate bootstrap
|
|
||||||
padding-right: calc(1.5em + 0.75rem) !important;
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") !important;
|
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
background-position: right calc(0.375em + 0.1875rem) center !important;
|
|
||||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group .ng-select-taggable:first-child:nth-last-child(2) {
|
|
||||||
max-width: calc(100% - 45px); // fudge factor for (1x) ng-select button width
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group .ng-select-taggable:first-child:nth-last-child(3) {
|
|
||||||
max-width: calc(100% - 90px); // fudge factor for (2x) ng-select button width
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .list-group-item .mb-3 {
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
import {
|
|
||||||
ComponentFixture,
|
|
||||||
TestBed,
|
|
||||||
fakeAsync,
|
|
||||||
tick,
|
|
||||||
} from '@angular/core/testing'
|
|
||||||
import {
|
|
||||||
FormsModule,
|
|
||||||
NG_VALUE_ACCESSOR,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
} from '@angular/forms'
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing'
|
|
||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
|
||||||
import {
|
|
||||||
DEFAULT_MATCHING_ALGORITHM,
|
|
||||||
MATCH_ALL,
|
|
||||||
} from 'src/app/data/matching-model'
|
|
||||||
import { Tag } from 'src/app/data/tag'
|
|
||||||
import { SelectComponent } from './select.component'
|
|
||||||
|
|
||||||
const items: Tag[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'Tag1',
|
|
||||||
is_inbox_tag: false,
|
|
||||||
matching_algorithm: DEFAULT_MATCHING_ALGORITHM,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Tag2',
|
|
||||||
is_inbox_tag: true,
|
|
||||||
matching_algorithm: MATCH_ALL,
|
|
||||||
match: 'str',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
name: 'Tag10',
|
|
||||||
is_inbox_tag: false,
|
|
||||||
matching_algorithm: DEFAULT_MATCHING_ALGORITHM,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
describe('SelectComponent', () => {
|
|
||||||
let component: SelectComponent
|
|
||||||
let fixture: ComponentFixture<SelectComponent>
|
|
||||||
let input: HTMLInputElement
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [],
|
|
||||||
imports: [
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
NgSelectModule,
|
|
||||||
RouterTestingModule,
|
|
||||||
SelectComponent,
|
|
||||||
],
|
|
||||||
}).compileComponents()
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(SelectComponent)
|
|
||||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
|
||||||
component = fixture.componentInstance
|
|
||||||
fixture.detectChanges()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support private items', () => {
|
|
||||||
component.value = 3
|
|
||||||
component.items = items
|
|
||||||
expect(component.items).toContainEqual({
|
|
||||||
id: 3,
|
|
||||||
name: 'Private',
|
|
||||||
private: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
component.checkForPrivateItems([4, 5])
|
|
||||||
expect(component.items).toContainEqual({
|
|
||||||
id: 4,
|
|
||||||
name: 'Private',
|
|
||||||
private: true,
|
|
||||||
})
|
|
||||||
expect(component.items).toContainEqual({
|
|
||||||
id: 5,
|
|
||||||
name: 'Private',
|
|
||||||
private: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support suggestions', () => {
|
|
||||||
expect(component.value).toBeUndefined()
|
|
||||||
component.items = items
|
|
||||||
component.suggestions = [1, 2]
|
|
||||||
fixture.detectChanges()
|
|
||||||
const suggestionAnchor: HTMLAnchorElement =
|
|
||||||
fixture.nativeElement.querySelector('a')
|
|
||||||
suggestionAnchor.click()
|
|
||||||
expect(component.value).toEqual(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support create new and emit the value', () => {
|
|
||||||
expect(component.allowCreateNew).toBeFalsy()
|
|
||||||
component.items = items
|
|
||||||
let createNewVal
|
|
||||||
component.createNew.subscribe((v) => (createNewVal = v))
|
|
||||||
expect(component.allowCreateNew).toBeTruthy()
|
|
||||||
component.onSearch({ term: 'foo' })
|
|
||||||
component.addItem(undefined)
|
|
||||||
expect(createNewVal).toEqual('foo')
|
|
||||||
component.addItem('bar')
|
|
||||||
expect(createNewVal).toEqual('bar')
|
|
||||||
component.onSearch({ term: 'baz' })
|
|
||||||
component.clickNew()
|
|
||||||
expect(createNewVal).toEqual('baz')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should clear search term on blur after delay', fakeAsync(() => {
|
|
||||||
const clearSpy = jest.spyOn(component, 'clearLastSearchTerm')
|
|
||||||
component.onBlur()
|
|
||||||
tick(3000)
|
|
||||||
expect(clearSpy).toHaveBeenCalled()
|
|
||||||
}))
|
|
||||||
|
|
||||||
it('should emit filtered documents', () => {
|
|
||||||
component.value = 10
|
|
||||||
component.items = items
|
|
||||||
const emitSpy = jest.spyOn(component.filterDocuments, 'emit')
|
|
||||||
component.onFilterDocuments()
|
|
||||||
expect(emitSpy).toHaveBeenCalledWith([items[2]])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return the correct filter button title', () => {
|
|
||||||
component.title = 'Tag'
|
|
||||||
const expectedTitle = `Filter documents with this ${component.title}`
|
|
||||||
expect(component.filterButtonTitle).toEqual(expectedTitle)
|
|
||||||
})
|
|
||||||
})
|
|
@ -0,0 +1,77 @@
|
|||||||
|
<div class="list-group mt-3 selected-fields">
|
||||||
|
@for (fieldId of selectedFields; track fieldId) {
|
||||||
|
<div class="list-group-item
|
||||||
|
d-flex
|
||||||
|
justify-content-between
|
||||||
|
align-items-center">
|
||||||
|
@switch (getCustomField(fieldId)?.data_type) {
|
||||||
|
@case (CustomFieldDataType.String) {
|
||||||
|
<pngx-input-text [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-text>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Date) {
|
||||||
|
<pngx-input-date [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-date>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Integer) {
|
||||||
|
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"
|
||||||
|
[showAdd]="false"></pngx-input-number>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Float) {
|
||||||
|
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"
|
||||||
|
[showAdd]="false"
|
||||||
|
[step]=".1"></pngx-input-number>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Monetary) {
|
||||||
|
<pngx-input-monetary [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[defaultCurrency]="getCustomField(fieldId)?.extra_data?.default_currency"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-monetary>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Boolean) {
|
||||||
|
<pngx-input-check [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-check>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Url) {
|
||||||
|
<pngx-input-url [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-url>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.DocumentLink) {
|
||||||
|
<pngx-input-document-link [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-document-link>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Select) {
|
||||||
|
<pngx-input-select [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[items]="getCustomField(fieldId)?.extra_data.select_options"
|
||||||
|
class="flex-grow-1"
|
||||||
|
bindLabel="label"
|
||||||
|
[allowNull]="true"
|
||||||
|
[horizontal]="true"></pngx-input-select>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-link text-danger" (click)="removeField(fieldId)">
|
||||||
|
<i-bs name="trash"></i-bs>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
@ -0,0 +1,3 @@
|
|||||||
|
:host ::ng-deep .list-group-item .mb-3 {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef } from '@angular/core'
|
import { Component, forwardRef, Input } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NG_VALUE_ACCESSOR,
|
NG_VALUE_ACCESSOR,
|
||||||
@ -23,13 +23,13 @@ import { UrlComponent } from '../url/url.component'
|
|||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
useExisting: forwardRef(() => CustomFieldsSelectComponent),
|
useExisting: forwardRef(() => CustomFieldsValuesComponent),
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selector: 'pngx-input-custom-fields-select',
|
selector: 'pngx-input-custom-fields-values',
|
||||||
templateUrl: './custom-fields-select.component.html',
|
templateUrl: './custom-fields-values.component.html',
|
||||||
styleUrls: ['./custom-fields-select.component.scss'],
|
styleUrl: './custom-fields-values.component.scss',
|
||||||
imports: [
|
imports: [
|
||||||
TextComponent,
|
TextComponent,
|
||||||
DateComponent,
|
DateComponent,
|
||||||
@ -46,7 +46,7 @@ import { UrlComponent } from '../url/url.component'
|
|||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CustomFieldsSelectComponent extends AbstractInputComponent<Object> {
|
export class CustomFieldsValuesComponent extends AbstractInputComponent<Object> {
|
||||||
public CustomFieldDataType = CustomFieldDataType
|
public CustomFieldDataType = CustomFieldDataType
|
||||||
|
|
||||||
constructor(customFieldsService: CustomFieldsService) {
|
constructor(customFieldsService: CustomFieldsService) {
|
||||||
@ -56,9 +56,11 @@ export class CustomFieldsSelectComponent extends AbstractInputComponent<Object>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fields: CustomField[]
|
private fields: CustomField[]
|
||||||
|
|
||||||
_selectedFields: number[]
|
private _selectedFields: number[]
|
||||||
|
|
||||||
|
@Input()
|
||||||
set selectedFields(newFields: number[]) {
|
set selectedFields(newFields: number[]) {
|
||||||
this._selectedFields = newFields
|
this._selectedFields = newFields
|
||||||
// map the selected fields to an object with field_id as key and value as value
|
// map the selected fields to an object with field_id as key and value as value
|
||||||
@ -68,20 +70,11 @@ export class CustomFieldsSelectComponent extends AbstractInputComponent<Object>
|
|||||||
}, {})
|
}, {})
|
||||||
this.onChange(this.value)
|
this.onChange(this.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedFields(): number[] {
|
get selectedFields(): number[] {
|
||||||
return this._selectedFields
|
return this._selectedFields
|
||||||
}
|
}
|
||||||
|
|
||||||
writeValue(newValue: Object): void {
|
|
||||||
// value will be a json object with field_id as key and value as value
|
|
||||||
this._selectedFields = newValue
|
|
||||||
? this.fields
|
|
||||||
.filter((field) => field.id in newValue)
|
|
||||||
.map((field) => field.id)
|
|
||||||
: []
|
|
||||||
super.writeValue(newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCustomField(id: number): CustomField {
|
public getCustomField(id: number): CustomField {
|
||||||
return this.fields.find((field) => field.id === id)
|
return this.fields.find((field) => field.id === id)
|
||||||
}
|
}
|
@ -56,7 +56,9 @@ export interface WorkflowAction extends ObjectWithId {
|
|||||||
|
|
||||||
assign_change_groups?: number[] // [Group.id]
|
assign_change_groups?: number[] // [Group.id]
|
||||||
|
|
||||||
assign_custom_fields_w_values?: number[] // { [CustomField.id]: value }
|
assign_custom_fields?: number[] // [CustomField.id]
|
||||||
|
|
||||||
|
assign_custom_fields_values?: object
|
||||||
|
|
||||||
remove_tags?: number[] // Tag.id
|
remove_tags?: number[] // Tag.id
|
||||||
|
|
||||||
|
@ -114,11 +114,8 @@ class DocumentMetadataOverrides:
|
|||||||
).values_list("id", flat=True),
|
).values_list("id", flat=True),
|
||||||
)
|
)
|
||||||
overrides.custom_fields = {
|
overrides.custom_fields = {
|
||||||
custom_field.id: value
|
custom_field.id: custom_field.value
|
||||||
for custom_field, value in doc.custom_fields.all().values_list(
|
for custom_field in doc.custom_fields.all()
|
||||||
"id",
|
|
||||||
"value",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
groups_with_perms = get_groups_with_perms(
|
groups_with_perms = get_groups_with_perms(
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
# Generated by Django 5.1.6 on 2025-03-01 04:49
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
import documents.models
|
|
||||||
|
|
||||||
|
|
||||||
def convert_assign_custom_fields(apps, schema_editor):
|
|
||||||
# Convert the old assign_custom_fields ManyToManyField to the new assign_custom_fields_w_values JSONField
|
|
||||||
WorkflowAction = apps.get_model("documents", "WorkflowAction")
|
|
||||||
for workflow_action in WorkflowAction.objects.all():
|
|
||||||
if workflow_action.assign_custom_fields.exists():
|
|
||||||
workflow_action.assign_custom_fields_w_values = {
|
|
||||||
custom_field.id: None
|
|
||||||
for custom_field in workflow_action.assign_custom_fields.all()
|
|
||||||
}
|
|
||||||
workflow_action.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("documents", "1063_paperlesstask_type_alter_paperlesstask_task_name_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="workflowaction",
|
|
||||||
name="assign_custom_fields_w_values",
|
|
||||||
field=models.JSONField(
|
|
||||||
blank=True,
|
|
||||||
help_text="assign these custom fields, with optional values",
|
|
||||||
null=True,
|
|
||||||
verbose_name=documents.models.CustomField,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.RunPython(convert_assign_custom_fields, migrations.RunPython.noop),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="workflowaction",
|
|
||||||
name="assign_custom_fields",
|
|
||||||
),
|
|
||||||
]
|
|
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-01 18:10
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("documents", "1063_paperlesstask_type_alter_paperlesstask_task_name_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflowaction",
|
||||||
|
name="assign_custom_fields_values",
|
||||||
|
field=models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional values to assign to the custom fields.",
|
||||||
|
null=True,
|
||||||
|
verbose_name="custom field values",
|
||||||
|
default={},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1264,13 +1264,21 @@ class WorkflowAction(models.Model):
|
|||||||
verbose_name=_("grant change permissions to these groups"),
|
verbose_name=_("grant change permissions to these groups"),
|
||||||
)
|
)
|
||||||
|
|
||||||
assign_custom_fields_w_values = models.JSONField(
|
assign_custom_fields = models.ManyToManyField(
|
||||||
CustomField,
|
CustomField,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
related_name="+",
|
||||||
|
verbose_name=_("assign these custom fields"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assign_custom_fields_values = models.JSONField(
|
||||||
|
_("custom field values"),
|
||||||
null=True,
|
null=True,
|
||||||
|
blank=True,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"assign these custom fields, with optional values",
|
"Optional values to assign to the custom fields.",
|
||||||
),
|
),
|
||||||
|
default={},
|
||||||
)
|
)
|
||||||
|
|
||||||
remove_tags = models.ManyToManyField(
|
remove_tags = models.ManyToManyField(
|
||||||
|
@ -2017,7 +2017,8 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
|
|||||||
"assign_view_groups",
|
"assign_view_groups",
|
||||||
"assign_change_users",
|
"assign_change_users",
|
||||||
"assign_change_groups",
|
"assign_change_groups",
|
||||||
"assign_custom_fields_w_values",
|
"assign_custom_fields",
|
||||||
|
"assign_custom_fields_values",
|
||||||
"remove_all_tags",
|
"remove_all_tags",
|
||||||
"remove_tags",
|
"remove_tags",
|
||||||
"remove_all_correspondents",
|
"remove_all_correspondents",
|
||||||
@ -2135,6 +2136,7 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
assign_view_groups = action.pop("assign_view_groups", None)
|
assign_view_groups = action.pop("assign_view_groups", None)
|
||||||
assign_change_users = action.pop("assign_change_users", None)
|
assign_change_users = action.pop("assign_change_users", None)
|
||||||
assign_change_groups = action.pop("assign_change_groups", None)
|
assign_change_groups = action.pop("assign_change_groups", None)
|
||||||
|
assign_custom_fields = action.pop("assign_custom_fields", None)
|
||||||
remove_tags = action.pop("remove_tags", None)
|
remove_tags = action.pop("remove_tags", None)
|
||||||
remove_correspondents = action.pop("remove_correspondents", None)
|
remove_correspondents = action.pop("remove_correspondents", None)
|
||||||
remove_document_types = action.pop("remove_document_types", None)
|
remove_document_types = action.pop("remove_document_types", None)
|
||||||
@ -2184,6 +2186,8 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
action_instance.assign_change_users.set(assign_change_users)
|
action_instance.assign_change_users.set(assign_change_users)
|
||||||
if assign_change_groups is not None:
|
if assign_change_groups is not None:
|
||||||
action_instance.assign_change_groups.set(assign_change_groups)
|
action_instance.assign_change_groups.set(assign_change_groups)
|
||||||
|
if assign_custom_fields is not None:
|
||||||
|
action_instance.assign_custom_fields.set(assign_custom_fields)
|
||||||
if remove_tags is not None:
|
if remove_tags is not None:
|
||||||
action_instance.remove_tags.set(remove_tags)
|
action_instance.remove_tags.set(remove_tags)
|
||||||
if remove_correspondents is not None:
|
if remove_correspondents is not None:
|
||||||
|
@ -769,29 +769,34 @@ def run_workflows(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if action.assign_custom_fields_w_values:
|
if action.assign_custom_fields.exists():
|
||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
for field_id in action.assign_custom_fields_w_values:
|
for field in action.assign_custom_fields.all():
|
||||||
if not CustomFieldInstance.objects.filter(
|
if not CustomFieldInstance.objects.filter(
|
||||||
field_id=field_id,
|
field=field,
|
||||||
document=document,
|
document=document,
|
||||||
).exists():
|
).exists():
|
||||||
# can be triggered on existing docs, so only add the field if it doesn't already exist
|
# can be triggered on existing docs, so only add the field if it doesn't already exist
|
||||||
field = CustomField.objects.get(pk=field_id)
|
|
||||||
value_field_name = CustomFieldInstance.get_value_field_name(
|
value_field_name = CustomFieldInstance.get_value_field_name(
|
||||||
data_type=field.data_type,
|
data_type=field.data_type,
|
||||||
)
|
)
|
||||||
args = {
|
args = {
|
||||||
"field": field,
|
"field": field,
|
||||||
"document": document,
|
"document": document,
|
||||||
value_field_name: action.assign_custom_fields_w_values[
|
value_field_name: action.assign_custom_fields_values.get(
|
||||||
field_id
|
field.pk,
|
||||||
],
|
None,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
CustomFieldInstance.objects.create(**args)
|
CustomFieldInstance.objects.create(**args)
|
||||||
else:
|
else:
|
||||||
|
if overrides.custom_fields is None:
|
||||||
|
overrides.custom_fields = {}
|
||||||
overrides.custom_fields.update(
|
overrides.custom_fields.update(
|
||||||
action.assign_custom_fields_w_values,
|
{
|
||||||
|
field.pk: action.assign_custom_fields_values.get(field.pk, None)
|
||||||
|
for field in action.assign_custom_fields.all()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def removal_action():
|
def removal_action():
|
||||||
|
@ -1471,7 +1471,10 @@ class PostDocumentView(GenericAPIView):
|
|||||||
created=created,
|
created=created,
|
||||||
asn=archive_serial_number,
|
asn=archive_serial_number,
|
||||||
owner_id=request.user.id,
|
owner_id=request.user.id,
|
||||||
custom_fields={cf_id: None for cf_id in custom_field_ids}, # for now
|
# TODO: set values
|
||||||
|
custom_fields={cf_id: None for cf_id in custom_field_ids}
|
||||||
|
if custom_field_ids
|
||||||
|
else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
async_task = consume_file.delay(
|
async_task = consume_file.delay(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user