Initial commit with Email Import Wizard and Task Processor updates

This commit is contained in:
2026-05-04 08:02:11 +02:00
commit effdc5d59f
170 changed files with 67739 additions and 0 deletions
@@ -0,0 +1,73 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import {
Client,
DocumentType,
DocumentField,
Task,
Postprocessing,
PostprocessingAction,
PostprocessingLog,
ExportTarget,
Setting,
Kontonummer,
Document,
UserClient,
Email,
Attachment,
Content,
ApiKey,
CorrespondentSetting,
BarcodeTemplate,
InboxDocument,
InboxPostprocessingAction,
CorrespondentEmailMapping,
} from './entities';
const entities = [
Client,
DocumentType,
DocumentField,
Task,
Postprocessing,
PostprocessingAction,
PostprocessingLog,
ExportTarget,
Setting,
Kontonummer,
Document,
UserClient,
Email,
Attachment,
Content,
ApiKey,
CorrespondentSetting,
BarcodeTemplate,
InboxDocument,
InboxPostprocessingAction,
CorrespondentEmailMapping,
];
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'mysql' as const,
host: config.get<string>('DB_HOST', 'localhost'),
port: config.get<number>('DB_PORT', 3306),
username: config.get<string>('DB_USERNAME', 'root'),
password: config.get<string>('DB_PASSWORD', ''),
database: config.get<string>('DB_DATABASE', 'paperlessadd'),
entities,
synchronize: true,
charset: 'utf8mb4',
}),
}),
TypeOrmModule.forFeature(entities),
],
exports: [TypeOrmModule],
})
export class DatabaseModule {}
@@ -0,0 +1,28 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('api_keys')
export class ApiKey {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column({ type: 'varchar', length: 100 })
name!: string;
@Column({ type: 'varchar', length: 10 })
keyPrefix!: string;
@Column({ type: 'varchar', length: 64, unique: true })
keyHash!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@Column({ type: 'datetime', nullable: true })
lastUsedAt!: Date | null;
@Column({ type: 'datetime', nullable: true })
expiresAt!: Date | null;
}
@@ -0,0 +1,54 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToOne, JoinColumn, Index } from 'typeorm';
import { Email } from './email.entity';
import { Content } from './content.entity';
@Entity('Attachments')
export class Attachment {
@PrimaryGeneratedColumn({ type: 'int' })
Id!: number;
@Index('IX_Attachments_EmailMessageId')
@Column({ type: 'int' })
EmailMessageId!: number;
@Column({ type: 'varchar', length: 255 })
FileName!: string;
@Column({ type: 'varchar', length: 100 })
ContentType!: string;
@Column({ type: 'tinyint', width: 1 })
Erechnung!: boolean;
@Column({ type: 'varchar', length: 32, default: '' })
Checksum!: string;
@Column({ type: 'varchar', length: 255, nullable: true })
ContentId!: string | null;
@Column({ type: 'tinyint', width: 1 })
IsEmbedded!: boolean;
@Index('IX_Attachments_ParentId')
@Column({ type: 'int', nullable: true })
ParentId!: number | null;
@Column({ type: 'json', nullable: true })
PaperlessDocumentIds!: any | null;
@Column({ type: 'int', default: 0 })
ImportStatus!: number;
@Column({ type: 'varchar', length: 255, nullable: true })
InterneBelegnummer!: string | null;
@Column({ type: 'int', default: 0 })
PageCount!: number;
@ManyToOne(() => Email, (email) => email.Attachments)
@JoinColumn({ name: 'EmailMessageId' })
EmailMessage!: Email;
@OneToOne(() => Content, (content) => content.AttachmentEntity)
Content!: Content;
}
@@ -0,0 +1,36 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
export type BarcodeActionType = 'SEND_TO_PAPERLESS' | 'SEND_BY_EMAIL';
@Entity('barcode_templates')
export class BarcodeTemplate {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'varchar', length: 100 })
Name!: string;
@Column({ type: 'varchar', length: 500 })
Regex!: string;
@Column({ type: 'boolean', default: false })
SplitBefore!: boolean;
@Column({ type: 'varchar', length: 500, nullable: true })
DateinameTemplate!: string | null;
@Column({ type: 'json' })
Actions!: BarcodeActionType[];
@CreateDateColumn()
CreatedAt!: Date;
@UpdateDateColumn()
UpdatedAt!: Date;
}
@@ -0,0 +1,13 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('clients')
export class Client {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'varchar', length: 45 })
Name!: string;
@Column({ type: 'int' })
PaperlessUserId!: number;
}
@@ -0,0 +1,22 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn, Index } from 'typeorm';
import { Attachment } from './attachment.entity';
@Entity('Contents')
export class Content {
@PrimaryGeneratedColumn({ type: 'int' })
Id!: number;
@Index('IX_Contents_AttachmentEntityId', { unique: true })
@Column({ type: 'int' })
AttachmentEntityId!: number;
@Column({ name: 'Content', type: 'longblob' })
Content1!: Buffer;
@Column({ type: 'bigint' })
ContentLength!: number;
@OneToOne(() => Attachment, (attachment) => attachment.Content)
@JoinColumn({ name: 'AttachmentEntityId' })
AttachmentEntity!: Attachment;
}
@@ -0,0 +1,14 @@
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
@Entity('CorrespondentEmailMappings')
export class CorrespondentEmailMapping {
@PrimaryGeneratedColumn({ type: 'int' })
Id!: number;
@Index('IX_CorrespondentEmailMapping_EmailAddress', { unique: true })
@Column({ type: 'varchar', length: 255 })
EmailAddress!: string;
@Column({ type: 'int' })
PaperlessCorrespondentId!: number;
}
@@ -0,0 +1,10 @@
import { Entity, PrimaryColumn, Column } from 'typeorm';
@Entity('CorrespondentSettings')
export class CorrespondentSetting {
@PrimaryColumn({ type: 'int' })
CorrespondentId!: number;
@Column({ type: 'int', nullable: true })
AgrarmonitorId!: number | null;
}
@@ -0,0 +1,28 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('DocumentFields')
export class DocumentField {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'int' })
DocumentType!: number;
@Column({ type: 'int' })
Type!: number;
@Column({ type: 'int', nullable: true })
TypeIndex!: number | null;
@Column({ type: 'tinyint', default: 0 })
IsRequired!: boolean;
@Column({ type: 'tinyint', default: 0 })
IsRequiredPosteingang!: boolean;
@Column({ type: 'varchar', length: 250, nullable: true })
Hinweis!: string | null;
@Column({ type: 'tinyint', default: 0 })
VisiblePosteingang!: boolean;
}
@@ -0,0 +1,19 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('DocumentTypes')
export class DocumentType {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'int' })
DocumentTypeId!: number;
@Column({ type: 'text' })
TitelTemplate!: string;
@Column({ type: 'int', nullable: true })
TagNotReady!: number | null;
@Column({ type: 'int', nullable: true })
TagReady!: number | null;
}
@@ -0,0 +1,13 @@
import { Entity, PrimaryColumn, Column } from 'typeorm';
@Entity('documents')
export class Document {
@PrimaryColumn({ type: 'int' })
documentId!: number;
@Column({ type: 'varchar', length: 32, nullable: true })
checksum!: string | null;
@Column({ type: 'varchar', length: 1024, nullable: true })
filename!: string | null;
}
@@ -0,0 +1,32 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Attachment } from './attachment.entity';
@Entity('Emails')
export class Email {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'varchar', length: 255 })
MessageId!: string;
@Column({ name: 'From', type: 'varchar', length: 255 })
SenderAddress!: string;
@Column({ name: 'To', type: 'varchar', length: 255 })
RecipientAddress!: string;
@Column({ type: 'varchar', length: 500 })
Subject!: string;
@Column({ type: 'datetime' })
Date!: Date;
@Column({ type: 'longtext' })
Body!: string;
@Column({ type: 'int', default: 0 })
Status!: number;
@OneToMany(() => Attachment, (attachment) => attachment.EmailMessage)
Attachments!: Attachment[];
}
@@ -0,0 +1,31 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('ExportTargets')
export class ExportTarget {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'varchar', length: 100 })
Name!: string;
@Column({ type: 'varchar', length: 20 })
Protocol!: string; // 'ftp' | 'webdav'
@Column({ type: 'varchar', length: 255 })
Host!: string;
@Column({ type: 'int', nullable: true })
Port!: number | null;
@Column({ type: 'varchar', length: 100, nullable: true })
Username!: string | null;
@Column({ type: 'varchar', length: 255, nullable: true })
Password!: string | null;
@Column({ type: 'varchar', length: 500, nullable: true })
RemotePath!: string | null;
@Column({ type: 'tinyint', default: 1 })
IsActive!: boolean;
}
@@ -0,0 +1,64 @@
import {
Entity,
PrimaryColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
} from 'typeorm';
export type InboxSource = 'all' | 'user';
export interface StoredQrCode {
page: number;
value: string;
}
@Entity('inbox_documents')
@Index(['Source', 'OwnerUsername'])
export class InboxDocument {
@PrimaryColumn({ type: 'char', length: 36 })
Id!: string;
@Column({ type: 'varchar', length: 255 })
OriginalName!: string;
@Column({ type: 'varchar', length: 10 })
Source!: InboxSource;
@Column({ type: 'varchar', length: 100, nullable: true })
OwnerUsername!: string | null;
@Column({ type: 'int', default: 0 })
PageCount!: number;
@Column({ type: 'json' })
QrCodes!: StoredQrCode[];
@Column({
type: 'json',
nullable: true,
transformer: {
to: (v: number[] | null | undefined) => (v && v.length ? v : null),
from: (v: number[] | null) => v ?? [],
},
})
DeletedPages!: number[];
@Column({
type: 'json',
nullable: true,
transformer: {
to: (v: Record<string, number> | null | undefined) =>
v && Object.keys(v).length ? v : null,
from: (v: Record<string, number> | null) => v ?? {},
},
})
Rotations!: Record<string, number>;
@CreateDateColumn()
CreatedAt!: Date;
@UpdateDateColumn()
UpdatedAt!: Date;
}
@@ -0,0 +1,36 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
export type InboxActionType = 'MAIL' | 'EXPORT' | 'PAPERLESS';
@Entity('inbox_postprocessing_actions')
export class InboxPostprocessingAction {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'varchar', length: 30 })
ActionType!: InboxActionType;
@Column({ type: 'json' })
Content!: Record<string, any>;
@Column({ type: 'int', nullable: true })
BarcodeTemplateId!: number | null;
@Column({ type: 'int', default: 0 })
Order!: number;
@Column({ type: 'tinyint', default: 1 })
IsActive!: boolean;
@CreateDateColumn()
CreatedAt!: Date;
@UpdateDateColumn()
UpdatedAt!: Date;
}
@@ -0,0 +1,21 @@
export { Client } from './client.entity';
export { DocumentType } from './document-type.entity';
export { DocumentField } from './document-field.entity';
export { Task } from './task.entity';
export { Postprocessing } from './postprocessing.entity';
export { PostprocessingAction } from './postprocessing-action.entity';
export { PostprocessingLog } from './postprocessing-log.entity';
export { ExportTarget } from './export-target.entity';
export { Setting } from './setting.entity';
export { Kontonummer } from './kontonummer.entity';
export { Document } from './document.entity';
export { UserClient } from './user-client.entity';
export { Email } from './email.entity';
export { Attachment } from './attachment.entity';
export { Content } from './content.entity';
export { ApiKey } from './api-key.entity';
export { CorrespondentSetting } from './correspondent-setting.entity';
export { BarcodeTemplate } from './barcode-template.entity';
export { InboxDocument } from './inbox-document.entity';
export { InboxPostprocessingAction } from './inbox-postprocessing-action.entity';
export { CorrespondentEmailMapping } from './correspondent-email-mapping.entity';
@@ -0,0 +1,13 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('Kontonummern')
export class Kontonummer {
@PrimaryGeneratedColumn()
KontonummerId: number;
@Column({ type: 'int' })
CorrespondentId: number;
@Column({ type: 'varchar', length: 100 })
Nummer: string;
}
@@ -0,0 +1,27 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('PostprocessingActions')
export class PostprocessingAction {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'int' })
PostprocessingId!: number;
@Column({ type: 'int' })
ActionType!: number;
// 1 = Export (FTP/WebDAV)
// 2 = Mail
// 3 = Tag setzen/entfernen
// 4 = Custom Field setzen
// 5 = Webhook
@Column({ type: 'json' })
Content!: Record<string, any>;
@Column({ type: 'int' })
Order!: number;
@Column({ type: 'tinyint' })
IsActive!: boolean;
}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('PostprocessingLogs')
export class PostprocessingLog {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'int' })
PostprocessingId!: number;
@Column({ type: 'int', nullable: true })
ActionId!: number | null;
@Column({ type: 'int' })
DocumentId!: number;
@Column({ type: 'varchar', length: 20 })
Status!: string; // 'success' | 'error' | 'skipped'
@Column({ type: 'text', nullable: true })
Message!: string | null;
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
CreatedAt!: Date;
}
@@ -0,0 +1,33 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
export interface FilterCondition {
field: string; // 'document_type' | 'correspondent' | 'owner' | 'tag' | 'custom_field_<id>' | 'title' | 'archive_serial_number'
operator: string; // 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'is_set' | 'is_not_set'
value: any;
}
export interface FilterGroup {
combinator: 'AND' | 'OR';
rules: (FilterCondition | FilterGroup)[];
}
@Entity('Postprocessings')
export class Postprocessing {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'varchar', length: 255 })
Name!: string;
@Column({ type: 'json' })
FilterJson!: FilterGroup;
@Column({ type: 'int', default: 0 })
Order!: number;
@Column({ type: 'tinyint', default: 0 })
NoFurther!: boolean;
@Column({ type: 'tinyint', default: 1 })
IsActive!: boolean;
}
@@ -0,0 +1,16 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('settings')
export class Setting {
@PrimaryGeneratedColumn()
ID!: number;
@Column({ type: 'int' })
Typ!: number;
@Column({ type: 'varchar', length: 255, nullable: true })
Wert!: string | null;
@Column({ type: 'varchar', length: 45, nullable: true })
Tag!: string | null;
}
@@ -0,0 +1,61 @@
import { Entity, PrimaryColumn, Column } from 'typeorm';
@Entity('tasks')
export class Task {
@PrimaryColumn({ type: 'varchar', length: 36 })
TaskId!: string;
@Column({ type: 'varchar', length: 50 })
InterneBelegnummer!: string;
@Column({ type: 'int', nullable: true })
DocumentType!: number | null;
@Column({ type: 'datetime', nullable: true })
Eingangsdatum!: Date | null;
@Column({ type: 'tinyint', nullable: true })
Fertig!: number | null;
@Column({ type: 'varchar', length: 50, nullable: true })
Tags!: string | null;
@Column({ type: 'int', nullable: true })
BetriebID!: number | null;
@Column({ type: 'varchar', length: 100, nullable: true })
Lieferant!: string | null;
@Column({ type: 'varchar', length: 100, nullable: true })
externeBelegnummer!: string | null;
@Column({ type: 'int', nullable: true })
EinkaufID!: number | null;
@Column({ type: 'datetime', nullable: true })
Belegdatum!: Date | null;
@Column({ type: 'int', nullable: true })
PaperlessDocumentID!: number | null;
@Column({ type: 'varchar', length: 36, nullable: true })
TaskReferenceID!: string | null;
@Column({ type: 'longtext', nullable: true })
BarcodeJson!: string | null;
@Column({ type: 'int', nullable: true })
DuplikatZU!: number | null;
@Column({ type: 'longtext', nullable: true })
CustomFieldsJson!: string | null;
@Column({ type: 'varchar', length: 50, nullable: true })
Asn!: string | null;
@Column({ type: 'int', nullable: true })
SourceAttachmentID!: number | null;
@Column({ type: 'varchar', length: 50, nullable: true })
SourceAttachmentRange!: string | null;
}
@@ -0,0 +1,16 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('UserClients')
export class UserClient {
@PrimaryGeneratedColumn()
Id!: number;
@Column({ type: 'varchar', length: 255 })
UserId!: string;
@Column({ type: 'int' })
ClientId!: number;
@Column({ type: 'enum', enum: ['viewer', 'editor', 'admin'], default: 'editor' })
Role!: 'viewer' | 'editor' | 'admin';
}