Zertifizierung erforderlich ist weg
Dieser Commit ist enthalten in:
@ -65,7 +65,7 @@ Shared (Ordner `shared`):
|
|||||||
-----------------------------
|
-----------------------------
|
||||||
- `users`: Benutzer (username, email verschlüsselt + Hash, password (bcrypt), role, employee_id, is_active, …)
|
- `users`: Benutzer (username, email verschlüsselt + Hash, password (bcrypt), role, employee_id, is_active, …)
|
||||||
- `employees`: Mitarbeitende (first_name, last_name, employee_number, photo, position, department, email/phone verschlüsselt+Hash, availability, …)
|
- `employees`: Mitarbeitende (first_name, last_name, employee_number, photo, position, department, email/phone verschlüsselt+Hash, availability, …)
|
||||||
- `skills`: Skills (id, name, category = `catId.subId`, description, requires_certification, expires_after)
|
- `skills`: Skills (id, name, category = `catId.subId`, description, expires_after)
|
||||||
- `employee_skills`: Zuordnung Mitarbeitende↔Skills mit Level/Verifikation
|
- `employee_skills`: Zuordnung Mitarbeitende↔Skills mit Level/Verifikation
|
||||||
- `language_skills`: Sprachkenntnisse (Kompatibilitäts- und Suchzwecke)
|
- `language_skills`: Sprachkenntnisse (Kompatibilitäts- und Suchzwecke)
|
||||||
- `specializations`: Freitext-Spezialisierungen je Mitarbeitendem
|
- `specializations`: Freitext-Spezialisierungen je Mitarbeitendem
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
- **Path**: `A:\GiTea\SkillMate`
|
- **Path**: `A:\GiTea\SkillMate`
|
||||||
- **Files**: 189 files
|
- **Files**: 189 files
|
||||||
- **Size**: 5.3 MB
|
- **Size**: 4.6 MB
|
||||||
- **Last Modified**: 2025-09-28 01:28
|
- **Last Modified**: 2025-09-29 01:39
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
@ -248,3 +248,4 @@ This project is managed with Claude Project Manager. To work with this project:
|
|||||||
- README updated on 2025-09-25 22:01:37
|
- README updated on 2025-09-25 22:01:37
|
||||||
- README updated on 2025-09-27 12:01:06
|
- README updated on 2025-09-27 12:01:06
|
||||||
- README updated on 2025-09-28 18:39:07
|
- README updated on 2025-09-28 18:39:07
|
||||||
|
- README updated on 2025-09-29 19:21:55
|
||||||
|
|||||||
@ -122,7 +122,6 @@ id TEXT PRIMARY KEY
|
|||||||
name TEXT NOT NULL
|
name TEXT NOT NULL
|
||||||
category TEXT NOT NULL
|
category TEXT NOT NULL
|
||||||
description TEXT
|
description TEXT
|
||||||
requires_certification INTEGER DEFAULT 0
|
|
||||||
expires_after INTEGER # Tage bis Ablauf
|
expires_after INTEGER # Tage bis Ablauf
|
||||||
|
|
||||||
TABELLE: employee_skills (Verknüpfungstabelle)
|
TABELLE: employee_skills (Verknüpfungstabelle)
|
||||||
@ -686,4 +685,4 @@ MONITORING
|
|||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
ENDE DER DOKUMENTATION
|
ENDE DER DOKUMENTATION
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|||||||
@ -13,8 +13,6 @@ type Skill = {
|
|||||||
intermediate: number
|
intermediate: number
|
||||||
expert: number
|
expert: number
|
||||||
}
|
}
|
||||||
requires_certification?: boolean
|
|
||||||
certification_months?: number
|
|
||||||
typeKey?: string
|
typeKey?: string
|
||||||
typeName?: string
|
typeName?: string
|
||||||
typeIcon?: string
|
typeIcon?: string
|
||||||
@ -69,9 +67,7 @@ export default function SkillManagement() {
|
|||||||
description: '',
|
description: '',
|
||||||
category: '',
|
category: '',
|
||||||
typeKey: '',
|
typeKey: '',
|
||||||
tags: [] as string[],
|
tags: [] as string[]
|
||||||
requires_certification: false,
|
|
||||||
certification_months: 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Category icon and color mapping
|
// Category icon and color mapping
|
||||||
@ -408,9 +404,7 @@ export default function SkillManagement() {
|
|||||||
description: skill.description || '',
|
description: skill.description || '',
|
||||||
category: categoryId,
|
category: categoryId,
|
||||||
typeKey,
|
typeKey,
|
||||||
tags: skill.tags || [],
|
tags: skill.tags || []
|
||||||
requires_certification: skill.requires_certification || false,
|
|
||||||
certification_months: skill.certification_months || 0
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
setEditingSkill(null)
|
setEditingSkill(null)
|
||||||
@ -419,9 +413,7 @@ export default function SkillManagement() {
|
|||||||
description: '',
|
description: '',
|
||||||
category: '',
|
category: '',
|
||||||
typeKey: '',
|
typeKey: '',
|
||||||
tags: [],
|
tags: []
|
||||||
requires_certification: false,
|
|
||||||
certification_months: 0
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setShowSkillModal(true)
|
setShowSkillModal(true)
|
||||||
@ -970,30 +962,6 @@ export default function SkillManagement() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<label className="flex items-center">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={skillForm.requires_certification}
|
|
||||||
onChange={(e) => setSkillForm(prev => ({ ...prev, requires_certification: e.target.checked }))}
|
|
||||||
className="mr-2"
|
|
||||||
/>
|
|
||||||
Zertifizierung erforderlich
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{skillForm.requires_certification && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<label>Ablauf nach:</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={skillForm.certification_months}
|
|
||||||
onChange={(e) => setSkillForm(prev => ({ ...prev, certification_months: parseInt(e.target.value) || 0 }))}
|
|
||||||
className="w-20 px-2 py-1 border border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
|
|
||||||
/>
|
|
||||||
<span>Monaten</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-3 mt-6">
|
<div className="flex justify-end gap-3 mt-6">
|
||||||
|
|||||||
@ -31,8 +31,8 @@ function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const insert = db.prepare(`
|
const insert = db.prepare(`
|
||||||
INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after)
|
INSERT OR IGNORE INTO skills (id, name, category, description, expires_after)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
let count = 0
|
let count = 0
|
||||||
@ -43,9 +43,8 @@ function main() {
|
|||||||
const id = `${categoryKey}.${sk.id}`
|
const id = `${categoryKey}.${sk.id}`
|
||||||
const name = sk.name
|
const name = sk.name
|
||||||
const description = null
|
const description = null
|
||||||
const requires = cat.id === 'certifications' || sub.id === 'weapons' ? 1 : 0
|
|
||||||
const expires = cat.id === 'certifications' ? 36 : null
|
const expires = cat.id === 'certifications' ? 36 : null
|
||||||
const res = insert.run(id, name, categoryKey, description, requires, expires)
|
const res = insert.run(id, name, categoryKey, description, expires)
|
||||||
if (res.changes > 0) count++
|
if (res.changes > 0) count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,6 @@ function ensureTables(db) {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
category TEXT NOT NULL,
|
category TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
requires_certification INTEGER DEFAULT 0,
|
|
||||||
expires_after INTEGER
|
expires_after INTEGER
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
@ -71,8 +70,8 @@ function seed(db, hierarchy) {
|
|||||||
`)
|
`)
|
||||||
|
|
||||||
const insertSkill = db.prepare(`
|
const insertSkill = db.prepare(`
|
||||||
INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after)
|
INSERT OR IGNORE INTO skills (id, name, category, description, expires_after)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
let catCount = 0
|
let catCount = 0
|
||||||
@ -101,9 +100,8 @@ function seed(db, hierarchy) {
|
|||||||
for (const sk of (sub.skills || [])) {
|
for (const sk of (sub.skills || [])) {
|
||||||
const sId = `${key}.${sk.id}`
|
const sId = `${key}.${sk.id}`
|
||||||
const sName = String(sk.name || sk.id)
|
const sName = String(sk.name || sk.id)
|
||||||
const requires = (catId === 'certifications' || subId === 'weapons') ? 1 : 0
|
|
||||||
const expires = (catId === 'certifications') ? 36 : null
|
const expires = (catId === 'certifications') ? 36 : null
|
||||||
const sRes = insertSkill.run(sId, sName, key, null, requires, expires)
|
const sRes = insertSkill.run(sId, sName, key, null, expires)
|
||||||
if (sRes.changes > 0) skillCount++
|
if (sRes.changes > 0) skillCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,4 +129,3 @@ function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
@ -237,7 +237,6 @@ export function initializeSecureDatabase() {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
category TEXT NOT NULL,
|
category TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
requires_certification INTEGER DEFAULT 0,
|
|
||||||
expires_after INTEGER
|
expires_after INTEGER
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const router = Router()
|
|||||||
router.get('/', authenticate, async (req: AuthRequest, res, next) => {
|
router.get('/', authenticate, async (req: AuthRequest, res, next) => {
|
||||||
try {
|
try {
|
||||||
const skills = db.prepare(`
|
const skills = db.prepare(`
|
||||||
SELECT id, name, category, description, requires_certification, expires_after
|
SELECT id, name, category, description, expires_after
|
||||||
FROM skills
|
FROM skills
|
||||||
ORDER BY category, name
|
ORDER BY category, name
|
||||||
`).all()
|
`).all()
|
||||||
@ -116,8 +116,8 @@ router.post('/initialize',
|
|||||||
async (req: AuthRequest, res, next) => {
|
async (req: AuthRequest, res, next) => {
|
||||||
try {
|
try {
|
||||||
const insertSkill = db.prepare(`
|
const insertSkill = db.prepare(`
|
||||||
INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after)
|
INSERT OR IGNORE INTO skills (id, name, category, description, expires_after)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
let count = 0
|
let count = 0
|
||||||
@ -128,7 +128,6 @@ router.post('/initialize',
|
|||||||
skillName,
|
skillName,
|
||||||
category,
|
category,
|
||||||
null,
|
null,
|
||||||
category === 'certificates' || category === 'weapons' ? 1 : 0,
|
|
||||||
category === 'certificates' ? 36 : null // 3 years for certificates
|
category === 'certificates' ? 36 : null // 3 years for certificates
|
||||||
)
|
)
|
||||||
if (result.changes > 0) count++
|
if (result.changes > 0) count++
|
||||||
@ -151,7 +150,7 @@ router.post('/',
|
|||||||
authorize('admin', 'superuser'),
|
authorize('admin', 'superuser'),
|
||||||
async (req: AuthRequest, res, next) => {
|
async (req: AuthRequest, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id, name, category, description, requiresCertification, expiresAfter } = req.body
|
const { id, name, category, description, expiresAfter } = req.body
|
||||||
|
|
||||||
// Optional custom id to keep stable
|
// Optional custom id to keep stable
|
||||||
let skillId = id && typeof id === 'string' && id.length > 0 ? id : uuidv4()
|
let skillId = id && typeof id === 'string' && id.length > 0 ? id : uuidv4()
|
||||||
@ -171,14 +170,13 @@ router.post('/',
|
|||||||
return res.status(400).json({ success: false, error: { message: 'Unknown subcategory' } })
|
return res.status(400).json({ success: false, error: { message: 'Unknown subcategory' } })
|
||||||
}
|
}
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
INSERT INTO skills (id, name, category, description, requires_certification, expires_after)
|
INSERT INTO skills (id, name, category, description, expires_after)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`).run(
|
`).run(
|
||||||
skillId,
|
skillId,
|
||||||
name,
|
name,
|
||||||
category,
|
category,
|
||||||
description || null,
|
description || null,
|
||||||
requiresCertification ? 1 : 0,
|
|
||||||
expiresAfter || null
|
expiresAfter || null
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -188,7 +186,6 @@ router.post('/',
|
|||||||
name,
|
name,
|
||||||
category,
|
category,
|
||||||
description: description || null,
|
description: description || null,
|
||||||
requiresCertification: requiresCertification || false,
|
|
||||||
expiresAfter: expiresAfter || null
|
expiresAfter: expiresAfter || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,6 @@ export function ensureSkillsSeeded() {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
category TEXT NOT NULL,
|
category TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
requires_certification INTEGER DEFAULT 0,
|
|
||||||
expires_after INTEGER
|
expires_after INTEGER
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
@ -63,8 +62,8 @@ export function ensureSkillsSeeded() {
|
|||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`)
|
`)
|
||||||
const insertSkill = db.prepare(`
|
const insertSkill = db.prepare(`
|
||||||
INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after)
|
INSERT OR IGNORE INTO skills (id, name, category, description, expires_after)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
let cats = 0, subs = 0, skills = 0
|
let cats = 0, subs = 0, skills = 0
|
||||||
@ -87,9 +86,8 @@ export function ensureSkillsSeeded() {
|
|||||||
for (const sk of (sub.skills || [])) {
|
for (const sk of (sub.skills || [])) {
|
||||||
const sId = `${key}.${sk.id}`
|
const sId = `${key}.${sk.id}`
|
||||||
const sName = String(sk.name || sk.id)
|
const sName = String(sk.name || sk.id)
|
||||||
const requires = (catId === 'certifications' || subId === 'weapons') ? 1 : 0
|
|
||||||
const expires = (catId === 'certifications') ? 36 : null
|
const expires = (catId === 'certifications') ? 36 : null
|
||||||
insertSkill.run(sId, sName, key, null, requires, expires)
|
insertSkill.run(sId, sName, key, null, expires)
|
||||||
skills++
|
skills++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,4 +107,3 @@ function cryptoRandomUUID() {
|
|||||||
return v.toString(16)
|
return v.toString(16)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -74,9 +74,9 @@ async function syncSkill(action: string, data: any) {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'create':
|
case 'create':
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
INSERT INTO skills (id, name, category, description, requires_certification, expires_after)
|
INSERT INTO skills (id, name, category, description, expires_after)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`).run(data.id, data.name, data.category, data.description || null, data.requiresCertification ? 1 : 0, data.expiresAfter || null)
|
`).run(data.id, data.name, data.category, data.description || null, data.expiresAfter || null)
|
||||||
break
|
break
|
||||||
case 'update':
|
case 'update':
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
|
|||||||
@ -388,11 +388,11 @@ export class SyncService {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'create':
|
case 'create':
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
INSERT INTO skills (id, name, category, description, requires_certification, expires_after)
|
INSERT INTO skills (id, name, category, description, expires_after)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`).run(
|
`).run(
|
||||||
data.id, data.name, data.category, data.description,
|
data.id, data.name, data.category, data.description,
|
||||||
data.requiresCertification ? 1 : 0, data.expiresAfter
|
data.expiresAfter
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -570,4 +570,4 @@ export class SyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const syncService = SyncService.getInstance()
|
export const syncService = SyncService.getInstance()
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren