From 4b5fec190360adaca707dde724b54e29a4a07fe2 Mon Sep 17 00:00:00 2001 From: Claude Project Manager Date: Thu, 16 Oct 2025 09:05:11 +0200 Subject: [PATCH] Update changes --- backend/src/routes/employeesSecure.ts | 43 ++++++++++++++++++++++++--- frontend/src/utils/text.ts | 19 +++++++----- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/backend/src/routes/employeesSecure.ts b/backend/src/routes/employeesSecure.ts index b8e6989..3b46324 100644 --- a/backend/src/routes/employeesSecure.ts +++ b/backend/src/routes/employeesSecure.ts @@ -11,6 +11,7 @@ import { FieldEncryption } from '../services/encryption' import { emailService } from '../services/emailService' import { logger } from '../utils/logger' import { createDepartmentResolver } from '../utils/department' +import { decodeHtmlEntities } from '../utils/html' const router = Router() @@ -205,6 +206,23 @@ router.get('/', authenticate, requirePermission('employees:read'), async (req: A primaryUnitId: emp.primary_unit_id, }) + // Prefer freshly stored department path if it differs from resolver (e.g., after profile update but before assignment sync) + const storedDeptRaw = (decodeHtmlEntities(emp.department) ?? emp.department ?? '').trim() + const splitStored = (() => { + const sep = ' -> ' + const idx = storedDeptRaw.lastIndexOf(sep) + if (idx === -1) return { path: storedDeptRaw, task: undefined as string | undefined } + return { path: storedDeptRaw.slice(0, idx) || storedDeptRaw, task: storedDeptRaw.slice(idx + sep.length) || undefined } + })() + const resolvedLabel = (departmentInfo.label || '').trim() + const last = (s: string) => s.split(' -> ').map(p => p.trim()).filter(Boolean).pop() || '' + const storedLast = last(splitStored.path) + const resolvedLast = last(resolvedLabel) + const finalLabel = (storedLast && resolvedLast && storedLast !== resolvedLast) + ? splitStored.path + : (resolvedLabel || splitStored.path) + const finalTasks = splitStored.task || departmentInfo.tasks + const employee: Employee = { id: emp.id, firstName: emp.first_name, @@ -213,9 +231,9 @@ router.get('/', authenticate, requirePermission('employees:read'), async (req: A photo: emp.photo, position: emp.position, officialTitle: emp.official_title || undefined, - department: departmentInfo.label || emp.department, + department: finalLabel, departmentDescription: departmentInfo.description, - departmentTasks: departmentInfo.tasks, + departmentTasks: finalTasks, email: emp.email, phone: emp.phone, mobile: emp.mobile, @@ -298,6 +316,23 @@ router.get('/public', authenticate, async (req: AuthRequest, res, next) => { primaryUnitId: emp.primary_unit_id, }) + // Prefer freshly stored department path if it differs from resolver (e.g., after profile update but before assignment sync) + const storedDeptRaw = (decodeHtmlEntities(emp.department) ?? emp.department ?? '').trim() + const splitStored = (() => { + const sep = ' -> ' + const idx = storedDeptRaw.lastIndexOf(sep) + if (idx === -1) return { path: storedDeptRaw, task: undefined as string | undefined } + return { path: storedDeptRaw.slice(0, idx) || storedDeptRaw, task: storedDeptRaw.slice(idx + sep.length) || undefined } + })() + const resolvedLabel = (departmentInfo.label || '').trim() + const last = (s: string) => s.split(' -> ').map(p => p.trim()).filter(Boolean).pop() || '' + const storedLast = last(splitStored.path) + const resolvedLast = last(resolvedLabel) + const finalLabel = (storedLast && resolvedLast && storedLast !== resolvedLast) + ? splitStored.path + : (resolvedLabel || splitStored.path) + const finalTasks = splitStored.task || departmentInfo.tasks + const employee: Employee = { id: emp.id, firstName: emp.first_name, @@ -306,9 +341,9 @@ router.get('/public', authenticate, async (req: AuthRequest, res, next) => { photo: emp.photo, position: emp.position, officialTitle: emp.official_title || undefined, - department: departmentInfo.label || emp.department, + department: finalLabel, departmentDescription: departmentInfo.description, - departmentTasks: departmentInfo.tasks, + departmentTasks: finalTasks, email: emp.email, phone: emp.phone, mobile: emp.mobile, diff --git a/frontend/src/utils/text.ts b/frontend/src/utils/text.ts index 119eee6..3b82c24 100644 --- a/frontend/src/utils/text.ts +++ b/frontend/src/utils/text.ts @@ -53,16 +53,21 @@ const splitPathAndTask = (value?: string | null): { path: string; task?: string const normalized = normalizeDepartment(value) if (!normalized) return { path: '' } const separator = ' -> ' - const lastIndex = normalized.lastIndexOf(separator) - if (lastIndex === -1) { + const segments = normalized.split(separator).map(s => s.trim()).filter(Boolean) + if (segments.length <= 1) { return { path: normalized } } - const path = normalized.slice(0, lastIndex) - const task = normalized.slice(lastIndex + separator.length) - return { - path: path || normalized, - task: task || undefined + const lastSeg = segments[segments.length - 1] + // Heuristik: Wenn das letzte Segment wie ein Organisationscode aussieht (z. B. "Abt 4", "Dez 41", "SG 41.1"), + // ist es Teil der Hierarchie und KEIN Aufgaben-Text. In dem Fall nichts abtrennen. + const CODE_LIKE_REGEX = /^(dir|lka\s*nrw|lstab|za(\s*\d+)?|abt\.?\s*\d+|abteilung\s*\d+|dez\.?\s*\d+[a-z]?|dezernat\s*\d+[a-z]?|sg\s*\d+(?:\.\d+)?|td\s*\d+(?:\.\d+)?)$/i + if (CODE_LIKE_REGEX.test(lastSeg)) { + return { path: normalized } } + // Andernfalls letzte Komponente als Aufgaben-Text behandeln + const path = segments.slice(0, -1).join(separator) + const task = lastSeg + return { path: path || normalized, task: task || undefined } } export const formatDepartmentWithDescription = (