feat: add Steuertags concept to separate workflow from content tags
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s

- New steuertag_ids setting to mark tags as workflow-only (not editable)
- DocumentEditModal shows only content tags (non-Steuertags) as editable chips
- Backend preserves Steuertags when saving document tag changes
- ManuellBearbeitenPage renders content tag chips under document title
- New Steuertags settings tab with multi-select and color preview

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 11:46:39 +02:00
parent dad0136365
commit d96e06e86d
8 changed files with 245 additions and 5 deletions
@@ -29,6 +29,10 @@ import { Task } from '../database/entities/task.entity';
import { Document } from '../database/entities/document.entity';
import { DocumentField } from '../database/entities/document-field.entity';
import { DocumentType } from '../database/entities/document-type.entity';
import { Setting } from '../database/entities/setting.entity';
/** Setting.Tag-Schlüssel für die manuell gepflegte Steuertag-Liste */
export const STEUERTAG_SETTING_KEY = 'steuertag_ids';
@Controller('api/paperless')
export class PaperlessController {
@@ -44,8 +48,26 @@ export class PaperlessController {
private readonly documentFieldRepo: Repository<DocumentField>,
@InjectRepository(DocumentType)
private readonly documentTypeRepo: Repository<DocumentType>,
@InjectRepository(Setting)
private readonly settingRepo: Repository<Setting>,
) {}
/** Liest die als Steuertags markierten Tag-IDs aus den Einstellungen. */
private async getSteuertagIds(): Promise<number[]> {
const setting = await this.settingRepo.findOneBy({
Tag: STEUERTAG_SETTING_KEY,
});
return (setting?.Wert ?? '')
.split(',')
.map((s) => parseInt(s.trim(), 10))
.filter((n) => !isNaN(n));
}
@Get('steuertags')
async getSteuertags() {
return { ids: await this.getSteuertagIds() };
}
@Post('checksum')
async checksumExists(@Body('checksum') checksum: string) {
const exists = await this.paperlessService.checksumExists(checksum);
@@ -390,6 +412,19 @@ export class PaperlessController {
oldDocument.tags = oldDocument.tags || [];
// Im Modal bearbeitbare Inhaltstags übernehmen: Steuertags bleiben
// unangetastet, die übrigen (Inhalts-)Tags werden durch die Auswahl ersetzt.
if (Array.isArray(body.tags)) {
const steuertagIds = new Set(await this.getSteuertagIds());
const preserved = oldDocument.tags.filter((t: number) =>
steuertagIds.has(t),
);
const selected = body.tags
.map((t: any) => Number(t))
.filter((t: number) => !isNaN(t) && !steuertagIds.has(t));
oldDocument.tags = Array.from(new Set([...preserved, ...selected]));
}
if (isReady) {
oldDocument.tags = oldDocument.tags.filter((t: number) => t !== 1);
if (docType?.TagNotReady)
@@ -9,6 +9,7 @@ import { DocumentField } from '../database/entities/document-field.entity';
import { Task } from '../database/entities/task.entity';
import { Document } from '../database/entities/document.entity';
import { Attachment } from '../database/entities/attachment.entity';
import { Setting } from '../database/entities/setting.entity';
import { PostprocessingModule } from '../postprocessing/postprocessing.module';
import { AuthModule } from '../auth/auth.module';
@@ -20,6 +21,7 @@ import { AuthModule } from '../auth/auth.module';
Task,
Document,
Attachment,
Setting,
]),
forwardRef(() => PostprocessingModule),
AuthModule,
@@ -374,6 +374,36 @@ export class SettingsController {
return this.settingRepo.findOneByOrFail({ ID: parseInt(id, 10) });
}
// === Steuertags ===
@Get('steuertags')
async getSteuertags() {
const setting = await this.settingRepo.findOneBy({ Tag: 'steuertag_ids' });
const ids = (setting?.Wert ?? '')
.split(',')
.map((s) => parseInt(s.trim(), 10))
.filter((n) => !isNaN(n));
return { ids };
}
@Put('steuertags')
async updateSteuertags(@Body() body: { ids: number[] }) {
const ids = (body.ids ?? [])
.map((id) => Number(id))
.filter((n) => !isNaN(n));
let setting = await this.settingRepo.findOneBy({ Tag: 'steuertag_ids' });
if (!setting) {
setting = this.settingRepo.create({
Typ: 0,
Tag: 'steuertag_ids',
Wert: ids.join(','),
});
} else {
setting.Wert = ids.join(',');
}
await this.settingRepo.save(setting);
return { ids };
}
// === Korrespondenten ===
@Get('correspondents')
async getCorrespondents(