Ich habe gestern vergessen zu pushen
Dieser Commit ist enthalten in:
@ -83,13 +83,13 @@ export const encryptedDb = {
|
|||||||
|
|
||||||
return db.prepare(`
|
return db.prepare(`
|
||||||
INSERT INTO employees (
|
INSERT INTO employees (
|
||||||
id, first_name, last_name, employee_number, photo, position,
|
id, first_name, last_name, employee_number, photo, position, official_title,
|
||||||
department, email, email_hash, phone, phone_hash, mobile, office, availability,
|
department, email, email_hash, phone, phone_hash, mobile, office, availability,
|
||||||
clearance_level, clearance_valid_until, clearance_issued_date,
|
clearance_level, clearance_valid_until, clearance_issued_date,
|
||||||
primary_unit_id,
|
primary_unit_id,
|
||||||
created_at, updated_at, created_by
|
created_at, updated_at, created_by
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@id, @first_name, @last_name, @employee_number, @photo, @position,
|
@id, @first_name, @last_name, @employee_number, @photo, @position, @official_title,
|
||||||
@department, @email, @email_hash, @phone, @phone_hash, @mobile, @office, @availability,
|
@department, @email, @email_hash, @phone, @phone_hash, @mobile, @office, @availability,
|
||||||
@clearance_level, @clearance_valid_until, @clearance_issued_date,
|
@clearance_level, @clearance_valid_until, @clearance_issued_date,
|
||||||
@primary_unit_id,
|
@primary_unit_id,
|
||||||
@ -109,6 +109,7 @@ export const encryptedDb = {
|
|||||||
// Decrypt sensitive fields
|
// Decrypt sensitive fields
|
||||||
return {
|
return {
|
||||||
...employee,
|
...employee,
|
||||||
|
official_title: employee.official_title,
|
||||||
email: FieldEncryption.decrypt(employee.email),
|
email: FieldEncryption.decrypt(employee.email),
|
||||||
phone: FieldEncryption.decrypt(employee.phone),
|
phone: FieldEncryption.decrypt(employee.phone),
|
||||||
mobile: FieldEncryption.decrypt(employee.mobile),
|
mobile: FieldEncryption.decrypt(employee.mobile),
|
||||||
@ -136,6 +137,7 @@ export const encryptedDb = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...emp,
|
...emp,
|
||||||
|
official_title: emp.official_title,
|
||||||
email: safeDecrypt(emp.email),
|
email: safeDecrypt(emp.email),
|
||||||
phone: safeDecrypt(emp.phone),
|
phone: safeDecrypt(emp.phone),
|
||||||
mobile: safeDecrypt(emp.mobile),
|
mobile: safeDecrypt(emp.mobile),
|
||||||
@ -164,6 +166,7 @@ export function initializeSecureDatabase() {
|
|||||||
employee_number TEXT UNIQUE NOT NULL,
|
employee_number TEXT UNIQUE NOT NULL,
|
||||||
photo TEXT,
|
photo TEXT,
|
||||||
position TEXT NOT NULL,
|
position TEXT NOT NULL,
|
||||||
|
official_title TEXT,
|
||||||
department TEXT NOT NULL,
|
department TEXT NOT NULL,
|
||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
email_hash TEXT,
|
email_hash TEXT,
|
||||||
@ -190,6 +193,10 @@ export function initializeSecureDatabase() {
|
|||||||
if (!hasPrimaryUnitId) {
|
if (!hasPrimaryUnitId) {
|
||||||
db.exec(`ALTER TABLE employees ADD COLUMN primary_unit_id TEXT`)
|
db.exec(`ALTER TABLE employees ADD COLUMN primary_unit_id TEXT`)
|
||||||
}
|
}
|
||||||
|
const hasOfficialTitle = cols.some(c => c.name === 'official_title')
|
||||||
|
if (!hasOfficialTitle) {
|
||||||
|
db.exec(`ALTER TABLE employees ADD COLUMN official_title TEXT`)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export interface EmployeeInput {
|
|||||||
employeeNumber?: string | null
|
employeeNumber?: string | null
|
||||||
photo?: string | null
|
photo?: string | null
|
||||||
position?: string
|
position?: string
|
||||||
|
officialTitle?: string | null
|
||||||
department: string
|
department: string
|
||||||
email: string
|
email: string
|
||||||
phone?: string | null
|
phone?: string | null
|
||||||
@ -76,6 +77,7 @@ export function getAllWithDetails(): Employee[] {
|
|||||||
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
||||||
photo: emp.photo,
|
photo: emp.photo,
|
||||||
position: emp.position,
|
position: emp.position,
|
||||||
|
officialTitle: emp.official_title || undefined,
|
||||||
department: emp.department,
|
department: emp.department,
|
||||||
email: emp.email,
|
email: emp.email,
|
||||||
phone: emp.phone,
|
phone: emp.phone,
|
||||||
@ -136,6 +138,7 @@ function buildEmployeeWithDetails(emp: any): Employee {
|
|||||||
mobile: emp.mobile,
|
mobile: emp.mobile,
|
||||||
office: emp.office,
|
office: emp.office,
|
||||||
availability: emp.availability,
|
availability: emp.availability,
|
||||||
|
officialTitle: emp.official_title || undefined,
|
||||||
skills: skills.map((s: any) => ({
|
skills: skills.map((s: any) => ({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
name: s.name,
|
name: s.name,
|
||||||
@ -166,6 +169,7 @@ export function createEmployee(input: EmployeeInput, actorUserId: string): { id:
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
const employeeId = uuidv4()
|
const employeeId = uuidv4()
|
||||||
const position = input.position || 'Teammitglied'
|
const position = input.position || 'Teammitglied'
|
||||||
|
const officialTitle = input.officialTitle || null
|
||||||
const phone = input.phone || 'Nicht angegeben'
|
const phone = input.phone || 'Nicht angegeben'
|
||||||
const availability = input.availability || 'available'
|
const availability = input.availability || 'available'
|
||||||
const employeeNumber = input.employeeNumber || `EMP${Date.now()}`
|
const employeeNumber = input.employeeNumber || `EMP${Date.now()}`
|
||||||
@ -186,6 +190,7 @@ export function createEmployee(input: EmployeeInput, actorUserId: string): { id:
|
|||||||
employee_number: employeeNumber,
|
employee_number: employeeNumber,
|
||||||
photo: input.photo || null,
|
photo: input.photo || null,
|
||||||
position,
|
position,
|
||||||
|
official_title: officialTitle,
|
||||||
department: input.department,
|
department: input.department,
|
||||||
email: input.email,
|
email: input.email,
|
||||||
phone,
|
phone,
|
||||||
@ -249,14 +254,14 @@ export function updateEmployee(id: string, input: EmployeeInput, actorUserId: st
|
|||||||
|
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE employees SET
|
UPDATE employees SET
|
||||||
first_name = ?, last_name = ?, position = ?, department = ?,
|
first_name = ?, last_name = ?, position = ?, official_title = ?, department = ?,
|
||||||
email = ?, email_hash = ?, phone = ?, phone_hash = ?,
|
email = ?, email_hash = ?, phone = ?, phone_hash = ?,
|
||||||
mobile = ?, office = ?, availability = ?,
|
mobile = ?, office = ?, availability = ?,
|
||||||
clearance_level = ?, clearance_valid_until = ?, clearance_issued_date = ?,
|
clearance_level = ?, clearance_valid_until = ?, clearance_issued_date = ?,
|
||||||
updated_at = ?, updated_by = ?
|
updated_at = ?, updated_by = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(
|
`).run(
|
||||||
input.firstName, input.lastName, input.position || 'Teammitglied', input.department,
|
input.firstName, input.lastName, input.position || 'Teammitglied', input.officialTitle || null, input.department,
|
||||||
// encryption handled in secure db layer caller; here store encrypted values already in input? Route prepares with FieldEncryption
|
// encryption handled in secure db layer caller; here store encrypted values already in input? Route prepares with FieldEncryption
|
||||||
input.email, // already encrypted by route layer
|
input.email, // already encrypted by route layer
|
||||||
null, // email_hash set by route if needed
|
null, // email_hash set by route if needed
|
||||||
|
|||||||
@ -34,7 +34,7 @@ function mapProficiencyToLevel(proficiency: string): 'basic' | 'fluent' | 'nativ
|
|||||||
router.get('/', authenticate, requirePermission('employees:read'), async (req: AuthRequest, res, next) => {
|
router.get('/', authenticate, requirePermission('employees:read'), async (req: AuthRequest, res, next) => {
|
||||||
try {
|
try {
|
||||||
const employees = db.prepare(`
|
const employees = db.prepare(`
|
||||||
SELECT id, first_name, last_name, employee_number, photo, position,
|
SELECT id, first_name, last_name, employee_number, photo, position, official_title,
|
||||||
department, email, phone, mobile, office, availability,
|
department, email, phone, mobile, office, availability,
|
||||||
clearance_level, clearance_valid_until, clearance_issued_date,
|
clearance_level, clearance_valid_until, clearance_issued_date,
|
||||||
created_at, updated_at, created_by, updated_by
|
created_at, updated_at, created_by, updated_by
|
||||||
@ -70,6 +70,7 @@ router.get('/', authenticate, requirePermission('employees:read'), async (req: A
|
|||||||
employeeNumber: emp.employee_number,
|
employeeNumber: emp.employee_number,
|
||||||
photo: emp.photo,
|
photo: emp.photo,
|
||||||
position: emp.position,
|
position: emp.position,
|
||||||
|
officialTitle: emp.official_title || undefined,
|
||||||
department: emp.department,
|
department: emp.department,
|
||||||
email: emp.email,
|
email: emp.email,
|
||||||
phone: emp.phone,
|
phone: emp.phone,
|
||||||
@ -116,7 +117,7 @@ router.get('/:id', authenticate, requirePermission('employees:read'), async (req
|
|||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
|
||||||
const emp = db.prepare(`
|
const emp = db.prepare(`
|
||||||
SELECT id, first_name, last_name, employee_number, photo, position,
|
SELECT id, first_name, last_name, employee_number, photo, position, official_title,
|
||||||
department, email, phone, mobile, office, availability,
|
department, email, phone, mobile, office, availability,
|
||||||
clearance_level, clearance_valid_until, clearance_issued_date,
|
clearance_level, clearance_valid_until, clearance_issued_date,
|
||||||
created_at, updated_at, created_by, updated_by
|
created_at, updated_at, created_by, updated_by
|
||||||
@ -158,6 +159,7 @@ router.get('/:id', authenticate, requirePermission('employees:read'), async (req
|
|||||||
employeeNumber: emp.employee_number,
|
employeeNumber: emp.employee_number,
|
||||||
photo: emp.photo,
|
photo: emp.photo,
|
||||||
position: emp.position,
|
position: emp.position,
|
||||||
|
officialTitle: emp.official_title || undefined,
|
||||||
department: emp.department,
|
department: emp.department,
|
||||||
email: emp.email,
|
email: emp.email,
|
||||||
phone: emp.phone,
|
phone: emp.phone,
|
||||||
@ -202,6 +204,7 @@ router.post('/',
|
|||||||
[
|
[
|
||||||
body('firstName').notEmpty().trim(),
|
body('firstName').notEmpty().trim(),
|
||||||
body('lastName').notEmpty().trim(),
|
body('lastName').notEmpty().trim(),
|
||||||
|
body('officialTitle').optional().trim(),
|
||||||
body('email').isEmail(),
|
body('email').isEmail(),
|
||||||
body('department').notEmpty().trim(),
|
body('department').notEmpty().trim(),
|
||||||
body('organizationUnitId').optional({ checkFalsy: true }).isUUID(),
|
body('organizationUnitId').optional({ checkFalsy: true }).isUUID(),
|
||||||
@ -221,7 +224,7 @@ router.post('/',
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
firstName, lastName, employeeNumber, photo, position,
|
firstName, lastName, employeeNumber, photo, position, officialTitle,
|
||||||
department, email, phone, mobile, office, availability,
|
department, email, phone, mobile, office, availability,
|
||||||
clearance, skills, languages, specializations,
|
clearance, skills, languages, specializations,
|
||||||
userRole, createUser, organizationUnitId, organizationRole
|
userRole, createUser, organizationUnitId, organizationRole
|
||||||
@ -253,11 +256,11 @@ router.post('/',
|
|||||||
// Insert employee with default values for missing fields
|
// Insert employee with default values for missing fields
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
INSERT INTO employees (
|
INSERT INTO employees (
|
||||||
id, first_name, last_name, employee_number, photo, position,
|
id, first_name, last_name, employee_number, photo, position, official_title,
|
||||||
department, email, phone, mobile, office, availability, primary_unit_id,
|
department, email, phone, mobile, office, availability, primary_unit_id,
|
||||||
clearance_level, clearance_valid_until, clearance_issued_date,
|
clearance_level, clearance_valid_until, clearance_issued_date,
|
||||||
created_at, updated_at, created_by
|
created_at, updated_at, created_by
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`).run(
|
`).run(
|
||||||
employeeId,
|
employeeId,
|
||||||
firstName,
|
firstName,
|
||||||
@ -265,6 +268,7 @@ router.post('/',
|
|||||||
employeeNumber || null,
|
employeeNumber || null,
|
||||||
photo || null,
|
photo || null,
|
||||||
position || 'Teammitglied', // Default position
|
position || 'Teammitglied', // Default position
|
||||||
|
officialTitle || null,
|
||||||
resolvedDepartment,
|
resolvedDepartment,
|
||||||
email,
|
email,
|
||||||
phone || 'Nicht angegeben', // Default phone
|
phone || 'Nicht angegeben', // Default phone
|
||||||
@ -430,6 +434,7 @@ router.put('/:id',
|
|||||||
body('firstName').notEmpty().trim(),
|
body('firstName').notEmpty().trim(),
|
||||||
body('lastName').notEmpty().trim(),
|
body('lastName').notEmpty().trim(),
|
||||||
body('position').notEmpty().trim(),
|
body('position').notEmpty().trim(),
|
||||||
|
body('officialTitle').optional().trim(),
|
||||||
body('department').notEmpty().trim(),
|
body('department').notEmpty().trim(),
|
||||||
body('email').isEmail(),
|
body('email').isEmail(),
|
||||||
body('phone').notEmpty().trim(),
|
body('phone').notEmpty().trim(),
|
||||||
@ -449,7 +454,7 @@ router.put('/:id',
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
firstName, lastName, position, department, email, phone,
|
firstName, lastName, position, officialTitle, department, email, phone,
|
||||||
mobile, office, availability, clearance, skills, languages, specializations
|
mobile, office, availability, clearance, skills, languages, specializations
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|
||||||
@ -465,13 +470,13 @@ router.put('/:id',
|
|||||||
// Update employee
|
// Update employee
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE employees SET
|
UPDATE employees SET
|
||||||
first_name = ?, last_name = ?, position = ?, department = ?,
|
first_name = ?, last_name = ?, position = ?, official_title = ?, department = ?,
|
||||||
email = ?, phone = ?, mobile = ?, office = ?, availability = ?,
|
email = ?, phone = ?, mobile = ?, office = ?, availability = ?,
|
||||||
clearance_level = ?, clearance_valid_until = ?, clearance_issued_date = ?,
|
clearance_level = ?, clearance_valid_until = ?, clearance_issued_date = ?,
|
||||||
updated_at = ?, updated_by = ?
|
updated_at = ?, updated_by = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(
|
`).run(
|
||||||
firstName, lastName, position, department,
|
firstName, lastName, position, officialTitle || null, department,
|
||||||
email, phone, mobile || null, office || null, availability,
|
email, phone, mobile || null, office || null, availability,
|
||||||
clearance?.level || null, clearance?.validUntil || null, clearance?.issuedDate || null,
|
clearance?.level || null, clearance?.validUntil || null, clearance?.issuedDate || null,
|
||||||
now, req.user!.id, id
|
now, req.user!.id, id
|
||||||
@ -508,6 +513,7 @@ router.put('/:id',
|
|||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
position,
|
position,
|
||||||
|
officialTitle: officialTitle || null,
|
||||||
department,
|
department,
|
||||||
email,
|
email,
|
||||||
phone,
|
phone,
|
||||||
|
|||||||
@ -121,6 +121,7 @@ router.get('/', authenticate, requirePermission('employees:read'), async (req: A
|
|||||||
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
||||||
photo: emp.photo,
|
photo: emp.photo,
|
||||||
position: emp.position,
|
position: emp.position,
|
||||||
|
officialTitle: emp.official_title || undefined,
|
||||||
department: emp.department,
|
department: emp.department,
|
||||||
email: emp.email,
|
email: emp.email,
|
||||||
phone: emp.phone,
|
phone: emp.phone,
|
||||||
@ -203,6 +204,7 @@ router.get('/public', authenticate, async (req: AuthRequest, res, next) => {
|
|||||||
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
||||||
photo: emp.photo,
|
photo: emp.photo,
|
||||||
position: emp.position,
|
position: emp.position,
|
||||||
|
officialTitle: emp.official_title || undefined,
|
||||||
department: emp.department,
|
department: emp.department,
|
||||||
email: emp.email,
|
email: emp.email,
|
||||||
phone: emp.phone,
|
phone: emp.phone,
|
||||||
@ -285,6 +287,7 @@ router.get('/:id', authenticate, requirePermission('employees:read'), async (req
|
|||||||
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
|
||||||
photo: emp.photo,
|
photo: emp.photo,
|
||||||
position: emp.position,
|
position: emp.position,
|
||||||
|
officialTitle: emp.official_title || undefined,
|
||||||
department: emp.department,
|
department: emp.department,
|
||||||
email: emp.email,
|
email: emp.email,
|
||||||
phone: emp.phone,
|
phone: emp.phone,
|
||||||
@ -354,7 +357,7 @@ router.post('/',
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
firstName, lastName, employeeNumber, photo, position = 'Teammitglied',
|
firstName, lastName, employeeNumber, photo, position = 'Teammitglied', officialTitle,
|
||||||
department, email, phone = 'Nicht angegeben', mobile, office, availability = 'available',
|
department, email, phone = 'Nicht angegeben', mobile, office, availability = 'available',
|
||||||
clearance, skills = [], languages = [], specializations = [], userRole, createUser,
|
clearance, skills = [], languages = [], specializations = [], userRole, createUser,
|
||||||
primaryUnitId, assignmentRole
|
primaryUnitId, assignmentRole
|
||||||
@ -380,6 +383,7 @@ router.post('/',
|
|||||||
employee_number: finalEmployeeNumber,
|
employee_number: finalEmployeeNumber,
|
||||||
photo: photo || null,
|
photo: photo || null,
|
||||||
position,
|
position,
|
||||||
|
official_title: officialTitle || null,
|
||||||
department,
|
department,
|
||||||
email,
|
email,
|
||||||
phone,
|
phone,
|
||||||
@ -513,6 +517,7 @@ router.post('/',
|
|||||||
employeeNumber: finalEmployeeNumber,
|
employeeNumber: finalEmployeeNumber,
|
||||||
photo: photo || null,
|
photo: photo || null,
|
||||||
position,
|
position,
|
||||||
|
officialTitle: officialTitle || null,
|
||||||
department,
|
department,
|
||||||
email,
|
email,
|
||||||
phone,
|
phone,
|
||||||
@ -591,6 +596,7 @@ router.put('/:id',
|
|||||||
body('firstName').notEmpty().trim().escape(),
|
body('firstName').notEmpty().trim().escape(),
|
||||||
body('lastName').notEmpty().trim().escape(),
|
body('lastName').notEmpty().trim().escape(),
|
||||||
body('position').optional().trim().escape(),
|
body('position').optional().trim().escape(),
|
||||||
|
body('officialTitle').optional().trim().escape(),
|
||||||
body('department').notEmpty().trim().escape(),
|
body('department').notEmpty().trim().escape(),
|
||||||
body('email').isEmail().normalizeEmail(),
|
body('email').isEmail().normalizeEmail(),
|
||||||
body('phone').optional().trim(),
|
body('phone').optional().trim(),
|
||||||
@ -612,7 +618,7 @@ router.put('/:id',
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
firstName, lastName, position = 'Teammitglied', department, email, phone = 'Nicht angegeben',
|
firstName, lastName, position = 'Teammitglied', officialTitle, department, email, phone = 'Nicht angegeben',
|
||||||
mobile, office, availability = 'available', clearance, skills, languages, specializations,
|
mobile, office, availability = 'available', clearance, skills, languages, specializations,
|
||||||
employeeNumber
|
employeeNumber
|
||||||
} = req.body
|
} = req.body
|
||||||
@ -629,14 +635,14 @@ router.put('/:id',
|
|||||||
// Update employee with encrypted fields
|
// Update employee with encrypted fields
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE employees SET
|
UPDATE employees SET
|
||||||
first_name = ?, last_name = ?, position = ?, department = ?,
|
first_name = ?, last_name = ?, position = ?, official_title = ?, department = ?,
|
||||||
email = ?, email_hash = ?, phone = ?, phone_hash = ?,
|
email = ?, email_hash = ?, phone = ?, phone_hash = ?,
|
||||||
mobile = ?, office = ?, availability = ?,
|
mobile = ?, office = ?, availability = ?,
|
||||||
clearance_level = ?, clearance_valid_until = ?, clearance_issued_date = ?,
|
clearance_level = ?, clearance_valid_until = ?, clearance_issued_date = ?,
|
||||||
updated_at = ?, updated_by = ?
|
updated_at = ?, updated_by = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(
|
`).run(
|
||||||
firstName, lastName, position, department,
|
firstName, lastName, position, officialTitle || null, department,
|
||||||
FieldEncryption.encrypt(email),
|
FieldEncryption.encrypt(email),
|
||||||
FieldEncryption.hash(email),
|
FieldEncryption.hash(email),
|
||||||
FieldEncryption.encrypt(phone || ''),
|
FieldEncryption.encrypt(phone || ''),
|
||||||
@ -702,6 +708,7 @@ router.put('/:id',
|
|||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
position,
|
position,
|
||||||
|
officialTitle: officialTitle || null,
|
||||||
department,
|
department,
|
||||||
email,
|
email,
|
||||||
phone,
|
phone,
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export async function createEmployeeUC(req: Request, body: any, actorUserId: str
|
|||||||
employeeNumber: body.employeeNumber,
|
employeeNumber: body.employeeNumber,
|
||||||
photo: body.photo,
|
photo: body.photo,
|
||||||
position: body.position,
|
position: body.position,
|
||||||
|
officialTitle: body.officialTitle,
|
||||||
department: body.department,
|
department: body.department,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
phone: body.phone,
|
phone: body.phone,
|
||||||
@ -40,6 +41,7 @@ export async function createEmployeeUC(req: Request, body: any, actorUserId: str
|
|||||||
employeeNumber: body.employeeNumber || null,
|
employeeNumber: body.employeeNumber || null,
|
||||||
photo: body.photo || null,
|
photo: body.photo || null,
|
||||||
position: body.position || 'Teammitglied',
|
position: body.position || 'Teammitglied',
|
||||||
|
officialTitle: body.officialTitle || null,
|
||||||
department: body.department,
|
department: body.department,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
phone: body.phone || 'Nicht angegeben',
|
phone: body.phone || 'Nicht angegeben',
|
||||||
@ -65,6 +67,7 @@ export async function updateEmployeeUC(req: Request, id: string, body: any, acto
|
|||||||
firstName: body.firstName,
|
firstName: body.firstName,
|
||||||
lastName: body.lastName,
|
lastName: body.lastName,
|
||||||
position: body.position,
|
position: body.position,
|
||||||
|
officialTitle: body.officialTitle,
|
||||||
department: body.department,
|
department: body.department,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
phone: body.phone,
|
phone: body.phone,
|
||||||
@ -84,6 +87,7 @@ export async function updateEmployeeUC(req: Request, id: string, body: any, acto
|
|||||||
firstName: body.firstName,
|
firstName: body.firstName,
|
||||||
lastName: body.lastName,
|
lastName: body.lastName,
|
||||||
position: body.position,
|
position: body.position,
|
||||||
|
officialTitle: body.officialTitle,
|
||||||
department: body.department,
|
department: body.department,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
phone: body.phone,
|
phone: body.phone,
|
||||||
|
|||||||
@ -63,6 +63,11 @@ export default function EmployeeCard({ employee, onClick }: EmployeeCardProps) {
|
|||||||
<p className="text-body text-secondary">
|
<p className="text-body text-secondary">
|
||||||
<span className="font-medium">Position:</span> {employee.position}
|
<span className="font-medium">Position:</span> {employee.position}
|
||||||
</p>
|
</p>
|
||||||
|
{employee.officialTitle && (
|
||||||
|
<p className="text-body text-secondary">
|
||||||
|
<span className="font-medium">Amtsbezeichnung:</span> {employee.officialTitle}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p className="text-body text-secondary">
|
<p className="text-body text-secondary">
|
||||||
<span className="font-medium">Dienststelle:</span> {employee.department}
|
<span className="font-medium">Dienststelle:</span> {employee.department}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -168,6 +168,12 @@ export default function EmployeeDetail() {
|
|||||||
<span className="text-tertiary">Position:</span>
|
<span className="text-tertiary">Position:</span>
|
||||||
<p className="text-secondary font-medium">{employee.position}</p>
|
<p className="text-secondary font-medium">{employee.position}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{employee.officialTitle && (
|
||||||
|
<div>
|
||||||
|
<span className="text-tertiary">Amtsbezeichnung:</span>
|
||||||
|
<p className="text-secondary font-medium">{employee.officialTitle}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<span className="text-tertiary">Dienststelle:</span>
|
<span className="text-tertiary">Dienststelle:</span>
|
||||||
<p className="text-secondary font-medium">{employee.department}</p>
|
<p className="text-secondary font-medium">{employee.department}</p>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export default function EmployeeForm() {
|
|||||||
lastName: '',
|
lastName: '',
|
||||||
employeeNumber: '',
|
employeeNumber: '',
|
||||||
position: '',
|
position: '',
|
||||||
|
officialTitle: '',
|
||||||
department: '',
|
department: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
@ -219,6 +220,7 @@ export default function EmployeeForm() {
|
|||||||
validUntil: new Date(new Date().setFullYear(new Date().getFullYear() + 5)),
|
validUntil: new Date(new Date().setFullYear(new Date().getFullYear() + 5)),
|
||||||
issuedDate: new Date()
|
issuedDate: new Date()
|
||||||
} : undefined,
|
} : undefined,
|
||||||
|
officialTitle: formData.officialTitle || undefined,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
createdBy: 'admin'
|
createdBy: 'admin'
|
||||||
@ -394,6 +396,20 @@ export default function EmployeeForm() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-secondary mb-2">
|
||||||
|
Amtsbezeichnung
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="officialTitle"
|
||||||
|
value={formData.officialTitle}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="input-field"
|
||||||
|
placeholder="z. B. KOK, KHK, EKHK"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-secondary mb-2">
|
<label className="block text-sm font-medium text-secondary mb-2">
|
||||||
Abteilung *
|
Abteilung *
|
||||||
|
|||||||
@ -22,6 +22,17 @@ export default function MyProfile() {
|
|||||||
const [activeTab, setActiveTab] = useState<'profile' | 'deputies'>('profile')
|
const [activeTab, setActiveTab] = useState<'profile' | 'deputies'>('profile')
|
||||||
const [currentUnitId, setCurrentUnitId] = useState<string | null>(null)
|
const [currentUnitId, setCurrentUnitId] = useState<string | null>(null)
|
||||||
const [myUnits, setMyUnits] = useState<any[]>([])
|
const [myUnits, setMyUnits] = useState<any[]>([])
|
||||||
|
const AVAILABILITY_OPTIONS = [
|
||||||
|
{ value: 'available', label: 'Verfügbar' },
|
||||||
|
{ value: 'busy', label: 'Beschäftigt' },
|
||||||
|
{ value: 'away', label: 'Abwesend' },
|
||||||
|
{ value: 'vacation', label: 'Urlaub' },
|
||||||
|
{ value: 'sick', label: 'Erkrankt' },
|
||||||
|
{ value: 'training', label: 'Fortbildung' },
|
||||||
|
{ value: 'operation', label: 'Im Einsatz' },
|
||||||
|
{ value: 'parttime', label: 'Teilzeit' },
|
||||||
|
{ value: 'unavailable', label: 'Nicht verfügbar' }
|
||||||
|
]
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!employeeId) {
|
if (!employeeId) {
|
||||||
@ -143,6 +154,7 @@ export default function MyProfile() {
|
|||||||
firstName: form.firstName,
|
firstName: form.firstName,
|
||||||
lastName: form.lastName,
|
lastName: form.lastName,
|
||||||
position: form.position || 'Teammitglied',
|
position: form.position || 'Teammitglied',
|
||||||
|
officialTitle: form.officialTitle || null,
|
||||||
department: form.department || '',
|
department: form.department || '',
|
||||||
employeeNumber: form.employeeNumber || undefined,
|
employeeNumber: form.employeeNumber || undefined,
|
||||||
email: user?.email || form.email,
|
email: user?.email || form.email,
|
||||||
@ -241,8 +253,23 @@ export default function MyProfile() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-body font-medium text-secondary mb-2">Position</label>
|
<label className="block text-body font-medium text-secondary mb-2">Position</label>
|
||||||
<input className="input-field w-full" value={form.position || ''} onChange={(e) => setForm((p: any) => ({ ...p, position: e.target.value }))} placeholder="z. B. Sachbearbeiter; Führungskraft g. D.; Führungskraft h. D." />
|
<input
|
||||||
<p className="text-small text-tertiary mt-1">Beispiele: Sachbearbeiter, Führungskraft g. D., Führungskraft h. D.</p>
|
className="input-field w-full"
|
||||||
|
value={form.position || ''}
|
||||||
|
onChange={(e) => setForm((p: any) => ({ ...p, position: e.target.value }))}
|
||||||
|
placeholder="z. B. Sachbearbeitung, Teamleitung"
|
||||||
|
/>
|
||||||
|
<p className="text-small text-tertiary mt-1">Beispiele: Sachbearbeitung, Teamleitung, Stabsstelle.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-body font-medium text-secondary mb-2">Amtsbezeichnung</label>
|
||||||
|
<input
|
||||||
|
className="input-field w-full"
|
||||||
|
value={form.officialTitle || ''}
|
||||||
|
onChange={(e) => setForm((p: any) => ({ ...p, officialTitle: e.target.value }))}
|
||||||
|
placeholder="z. B. KOK, KHK, EKHK"
|
||||||
|
/>
|
||||||
|
<p className="text-small text-tertiary mt-1">Freifeld für Amts- bzw. Dienstbezeichnungen (z. B. KOK, RBe, EKHK).</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-body font-medium text-secondary mb-2">NW-Kennung</label>
|
<label className="block text-body font-medium text-secondary mb-2">NW-Kennung</label>
|
||||||
@ -273,6 +300,19 @@ export default function MyProfile() {
|
|||||||
<input className="input-field w-full" value={form.office || ''} onChange={(e) => setForm((p: any) => ({ ...p, office: e.target.value }))} placeholder="z. B. Gebäude A, 3.OG, Raum 3.12" />
|
<input className="input-field w-full" value={form.office || ''} onChange={(e) => setForm((p: any) => ({ ...p, office: e.target.value }))} placeholder="z. B. Gebäude A, 3.OG, Raum 3.12" />
|
||||||
<p className="text-small text-tertiary mt-1">Angabe zum Standort, z. B. Gebäude, Etage und Raum.</p>
|
<p className="text-small text-tertiary mt-1">Angabe zum Standort, z. B. Gebäude, Etage und Raum.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-body font-medium text-secondary mb-2">Verfügbarkeit</label>
|
||||||
|
<select
|
||||||
|
className="input-field w-full"
|
||||||
|
value={form.availability || 'available'}
|
||||||
|
onChange={(e) => setForm((p: any) => ({ ...p, availability: e.target.value }))}
|
||||||
|
>
|
||||||
|
{AVAILABILITY_OPTIONS.map(option => (
|
||||||
|
<option key={option.value} value={option.value}>{option.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<p className="text-small text-tertiary mt-1">Dieser Status wird in der Mitarbeitendenübersicht und Teamplanung angezeigt.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -567,6 +567,7 @@ export default function SkillSearch() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-tertiary">
|
<div className="text-xs text-tertiary">
|
||||||
{employee.position}
|
{employee.position}
|
||||||
|
{employee.officialTitle ? ` • ${employee.officialTitle}` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{selectedSkills.size > 0 && (
|
{selectedSkills.size > 0 && (
|
||||||
|
|||||||
@ -688,7 +688,9 @@ export default function TeamZusammenstellung() {
|
|||||||
{employee.firstName} {employee.lastName}
|
{employee.firstName} {employee.lastName}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-tertiary">
|
<div className="text-sm text-tertiary">
|
||||||
{employee.position} • {employee.department}
|
{employee.position}
|
||||||
|
{employee.officialTitle ? ` • ${employee.officialTitle}` : ''}
|
||||||
|
{` • ${employee.department}`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
1
shared/index.d.ts
vendored
1
shared/index.d.ts
vendored
@ -46,6 +46,7 @@ export interface Employee {
|
|||||||
employeeNumber: string
|
employeeNumber: string
|
||||||
photo?: string | null
|
photo?: string | null
|
||||||
position: string
|
position: string
|
||||||
|
officialTitle?: string | null
|
||||||
department: string
|
department: string
|
||||||
email?: string | null
|
email?: string | null
|
||||||
phone?: string | null
|
phone?: string | null
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren