So mit neuen UI Ideen und so
Dieser Commit ist enthalten in:
317
backend/src/repositories/employeeRepository.ts
Normale Datei
317
backend/src/repositories/employeeRepository.ts
Normale Datei
@ -0,0 +1,317 @@
|
||||
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 || 'Mitarbeiter'
|
||||
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 || 'Mitarbeiter', 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()
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren