Files
SkillMate/backend/src/repositories/employeeRepository.ts
2025-09-29 00:35:31 +02:00

318 Zeilen
12 KiB
TypeScript

import { db, encryptedDb } from '../config/secureDatabase'
import { Employee } from '@skillmate/shared'
import { v4 as uuidv4 } from 'uuid'
export interface EmployeeInput {
firstName: string
lastName: string
employeeNumber?: string | null
photo?: string | null
position?: string
department: string
email: string
phone?: string | null
mobile?: string | null
office?: string | null
availability?: string
clearance?: { level?: string; validUntil?: string | Date | null; issuedDate?: string | Date | null } | null
skills?: Array<{ id: string; level?: any; verified?: boolean; verifiedBy?: string | null; verifiedDate?: string | Date | null }>
languages?: Array<{ code: string; level: string }>
specializations?: string[]
}
export function getAllWithDetails(): Employee[] {
const emps = (encryptedDb.getAllEmployees() as any[]) || []
if (emps.length === 0) return []
const ids = emps.map((e: any) => e.id)
const placeholders = ids.map(() => '?').join(',')
// Batch fetch related data
const skillsRows = db.prepare(`
SELECT es.employee_id, s.id, s.name, s.category, es.level, es.verified, es.verified_by, es.verified_date
FROM employee_skills es
JOIN skills s ON es.skill_id = s.id
WHERE es.employee_id IN (${placeholders})
`).all(...ids) as any[]
const langRows = db.prepare(`
SELECT employee_id, language, proficiency, certified, certificate_type, is_native, can_interpret
FROM language_skills
WHERE employee_id IN (${placeholders})
`).all(...ids) as any[]
const specRows = db.prepare(`
SELECT employee_id, name FROM specializations WHERE employee_id IN (${placeholders})
`).all(...ids) as any[]
const skillsByEmp = new Map<string, any[]>()
for (const r of skillsRows) {
if (!skillsByEmp.has(r.employee_id)) skillsByEmp.set(r.employee_id, [])
skillsByEmp.get(r.employee_id)!.push(r)
}
const langsByEmp = new Map<string, any[]>()
for (const r of langRows) {
if (!langsByEmp.has(r.employee_id)) langsByEmp.set(r.employee_id, [])
langsByEmp.get(r.employee_id)!.push(r)
}
const specsByEmp = new Map<string, string[]>()
for (const r of specRows) {
if (!specsByEmp.has(r.employee_id)) specsByEmp.set(r.employee_id, [])
specsByEmp.get(r.employee_id)!.push(r.name)
}
const mapProf = (p: string): 'basic' | 'fluent' | 'native' | 'business' => {
const m: Record<string, any> = { A1: 'basic', A2: 'basic', B1: 'business', B2: 'business', C1: 'fluent', C2: 'fluent', Muttersprache: 'native', native: 'native', fluent: 'fluent', advanced: 'business', intermediate: 'business', basic: 'basic' }
return m[p] || 'basic'
}
return emps.map((emp: any) => {
const s = skillsByEmp.get(emp.id) || []
const l = langsByEmp.get(emp.id) || []
const sp = specsByEmp.get(emp.id) || []
return {
id: emp.id,
firstName: emp.first_name,
lastName: emp.last_name,
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
photo: emp.photo,
position: emp.position,
department: emp.department,
email: emp.email,
phone: emp.phone,
mobile: emp.mobile,
office: emp.office,
availability: emp.availability,
skills: s.map((r: any) => ({ id: r.id, name: r.name, category: r.category, level: r.level, verified: Boolean(r.verified), verifiedBy: r.verified_by, verifiedDate: r.verified_date ? new Date(r.verified_date) : undefined })),
languages: l.map((r: any) => ({ code: r.language, level: mapProf(r.proficiency) })),
clearance: emp.clearance_level && emp.clearance_valid_until && emp.clearance_issued_date ? { level: emp.clearance_level, validUntil: new Date(emp.clearance_valid_until), issuedDate: new Date(emp.clearance_issued_date) } : undefined,
specializations: sp,
createdAt: new Date(emp.created_at),
updatedAt: new Date(emp.updated_at),
createdBy: emp.created_by,
updatedBy: emp.updated_by
} as Employee
})
}
export function getByIdWithDetails(id: string): Employee | null {
const emp = encryptedDb.getEmployee(id) as any
if (!emp) return null
return buildEmployeeWithDetails(emp)
}
function buildEmployeeWithDetails(emp: any): Employee {
const skills = db.prepare(`
SELECT s.id, s.name, s.category, es.level, es.verified, es.verified_by, es.verified_date
FROM employee_skills es
JOIN skills s ON es.skill_id = s.id
WHERE es.employee_id = ?
`).all(emp.id)
const languages = db.prepare(`
SELECT language, proficiency, certified, certificate_type, is_native, can_interpret
FROM language_skills
WHERE employee_id = ?
`).all(emp.id)
const specializations = db.prepare(`
SELECT name FROM specializations WHERE employee_id = ?
`).all(emp.id).map((s: any) => s.name)
const mapProf = (p: string): 'basic' | 'fluent' | 'native' | 'business' => {
const m: Record<string, any> = { A1: 'basic', A2: 'basic', B1: 'business', B2: 'business', C1: 'fluent', C2: 'fluent', Muttersprache: 'native', native: 'native', fluent: 'fluent', advanced: 'business', intermediate: 'business', basic: 'basic' }
return m[p] || 'basic'
}
return {
id: emp.id,
firstName: emp.first_name,
lastName: emp.last_name,
employeeNumber: (emp.employee_number && !String(emp.employee_number).startsWith('EMP')) ? emp.employee_number : undefined,
photo: emp.photo,
position: emp.position,
department: emp.department,
email: emp.email,
phone: emp.phone,
mobile: emp.mobile,
office: emp.office,
availability: emp.availability,
skills: skills.map((s: any) => ({
id: s.id,
name: s.name,
category: s.category,
level: s.level,
verified: Boolean(s.verified),
verifiedBy: s.verified_by,
verifiedDate: s.verified_date ? new Date(s.verified_date) : undefined
})),
languages: languages.map((l: any) => ({
code: l.language,
level: mapProf(l.proficiency)
})),
clearance: emp.clearance_level && emp.clearance_valid_until && emp.clearance_issued_date ? {
level: emp.clearance_level,
validUntil: new Date(emp.clearance_valid_until),
issuedDate: new Date(emp.clearance_issued_date)
} : undefined,
specializations,
createdAt: new Date(emp.created_at),
updatedAt: new Date(emp.updated_at),
createdBy: emp.created_by,
updatedBy: emp.updated_by
}
}
export function createEmployee(input: EmployeeInput, actorUserId: string): { id: string } {
const now = new Date().toISOString()
const employeeId = uuidv4()
const position = input.position || 'Teammitglied'
const phone = input.phone || 'Nicht angegeben'
const availability = input.availability || 'available'
const employeeNumber = input.employeeNumber || `EMP${Date.now()}`
const tx = db.transaction(() => {
// Uniqueness check for employee number
const existingEmployee = db.prepare('SELECT id FROM employees WHERE employee_number = ?').get(employeeNumber)
if (existingEmployee) {
const err: any = new Error('Employee number already exists')
err.statusCode = 409
throw err
}
encryptedDb.insertEmployee({
id: employeeId,
first_name: input.firstName,
last_name: input.lastName,
employee_number: employeeNumber,
photo: input.photo || null,
position,
department: input.department,
email: input.email,
phone,
mobile: input.mobile || null,
office: input.office || null,
availability,
clearance_level: input.clearance?.level || null,
clearance_valid_until: input.clearance?.validUntil || null,
clearance_issued_date: input.clearance?.issuedDate || null,
created_at: now,
updated_at: now,
created_by: actorUserId,
updated_by: actorUserId,
})
if (input.skills && input.skills.length > 0) {
const stmt = db.prepare(`
INSERT INTO employee_skills (employee_id, skill_id, level, verified, verified_by, verified_date)
VALUES (?, ?, ?, ?, ?, ?)
`)
const checkSkillExists = db.prepare('SELECT id FROM skills WHERE id = ?')
for (const s of input.skills) {
const exists = checkSkillExists.get(s.id)
if (!exists) continue
stmt.run(employeeId, s.id, typeof s.level === 'number' ? s.level : (parseInt(String(s.level)) || null), s.verified ? 1 : 0, s.verifiedBy || null, s.verifiedDate || null)
}
}
if (input.languages && input.languages.length > 0) {
const stmt = db.prepare(`
INSERT INTO language_skills (id, employee_id, language, proficiency, certified, certificate_type, is_native, can_interpret)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`)
for (const l of input.languages) {
stmt.run(uuidv4(), employeeId, l.code, l.level, 0, null, 0, 0)
}
}
if (input.specializations && input.specializations.length > 0) {
const stmt = db.prepare('INSERT INTO specializations (id, employee_id, name) VALUES (?, ?, ?)')
for (const name of input.specializations) {
stmt.run(uuidv4(), employeeId, name)
}
}
})
tx()
return { id: employeeId }
}
export function updateEmployee(id: string, input: EmployeeInput, actorUserId: string) {
const now = new Date().toISOString()
const tx = db.transaction(() => {
const existing = db.prepare('SELECT id FROM employees WHERE id = ?').get(id)
if (!existing) {
const err: any = new Error('Employee not found')
err.statusCode = 404
throw err
}
db.prepare(`
UPDATE employees SET
first_name = ?, last_name = ?, position = ?, department = ?,
email = ?, email_hash = ?, phone = ?, phone_hash = ?,
mobile = ?, office = ?, availability = ?,
clearance_level = ?, clearance_valid_until = ?, clearance_issued_date = ?,
updated_at = ?, updated_by = ?
WHERE id = ?
`).run(
input.firstName, input.lastName, input.position || 'Teammitglied', input.department,
// 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
null, // email_hash set by route if needed
input.phone || '',
null, // phone_hash set by route if needed
input.mobile || null,
input.office || null,
input.availability || 'available',
input.clearance?.level || null,
input.clearance?.validUntil || null,
input.clearance?.issuedDate || null,
now,
actorUserId,
id
)
if (input.skills) {
db.prepare('DELETE FROM employee_skills WHERE employee_id = ?').run(id)
if (input.skills.length > 0) {
const insert = db.prepare(`
INSERT INTO employee_skills (employee_id, skill_id, level, verified, verified_by, verified_date)
VALUES (?, ?, ?, ?, ?, ?)
`)
const checkSkillExists = db.prepare('SELECT id FROM skills WHERE id = ?')
for (const s of input.skills) {
const exists = checkSkillExists.get(s.id)
if (!exists) continue
insert.run(
id,
s.id,
typeof s.level === 'number' ? s.level : (parseInt(String(s.level)) || null),
s.verified ? 1 : 0,
s.verifiedBy || null,
s.verifiedDate || null
)
}
}
}
})
tx()
}
export function deleteEmployee(id: string) {
const tx = db.transaction(() => {
const existing = db.prepare('SELECT id FROM employees WHERE id = ?').get(id)
if (!existing) {
const err: any = new Error('Employee not found')
err.statusCode = 404
throw err
}
db.prepare('DELETE FROM employee_skills WHERE employee_id = ?').run(id)
db.prepare('DELETE FROM language_skills WHERE employee_id = ?').run(id)
db.prepare('DELETE FROM specializations WHERE employee_id = ?').run(id)
db.prepare('DELETE FROM employees WHERE id = ?').run(id)
})
tx()
}