import pool from '../config/database'; import logger from '../utils/logger'; interface PersonalEquipmentFilters { userId?: string; kategorie?: string; zustand?: string; } interface CreatePersonalEquipmentData { bezeichnung: string; kategorie?: string; artikel_id?: number; user_id?: string; benutzer_name?: string; groesse?: string; seriennummer?: string; inventarnummer?: string; anschaffung_datum?: string; zustand?: string; notizen?: string; eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[]; } interface UpdatePersonalEquipmentData { bezeichnung?: string; kategorie?: string; artikel_id?: number | null; user_id?: string | null; benutzer_name?: string | null; groesse?: string | null; seriennummer?: string | null; inventarnummer?: string | null; anschaffung_datum?: string | null; zustand?: string; notizen?: string | null; eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[] | null; } const BASE_SELECT = ` SELECT pa.*, COALESCE(u.given_name || ' ' || u.family_name, u.name) AS user_display_name, aa.bezeichnung AS artikel_bezeichnung FROM persoenliche_ausruestung pa LEFT JOIN users u ON u.id = pa.user_id LEFT JOIN ausruestung_artikel aa ON aa.id = pa.artikel_id WHERE pa.geloescht_am IS NULL `; async function loadEigenschaften(ids: string[]) { if (ids.length === 0) return new Map(); const result = await pool.query( `SELECT pae.id, pae.persoenlich_id, pae.eigenschaft_id, pae.name, pae.wert FROM persoenliche_ausruestung_eigenschaften pae WHERE pae.persoenlich_id = ANY($1)`, [ids], ); const map = new Map(); for (const row of result.rows) { const arr = map.get(row.persoenlich_id) || []; arr.push(row); map.set(row.persoenlich_id, arr); } return map; } async function getAll(filters: PersonalEquipmentFilters = {}) { try { const conditions: string[] = []; const params: unknown[] = []; if (filters.userId) { params.push(filters.userId); conditions.push(`pa.user_id = $${params.length}`); } if (filters.kategorie) { params.push(filters.kategorie); conditions.push(`pa.kategorie = $${params.length}`); } if (filters.zustand) { params.push(filters.zustand); conditions.push(`pa.zustand = $${params.length}`); } const where = conditions.length > 0 ? ` AND ${conditions.join(' AND ')}` : ''; const result = await pool.query( `${BASE_SELECT}${where} ORDER BY pa.bezeichnung`, params, ); const ids = result.rows.map((r: { id: string }) => r.id); const eigMap = await loadEigenschaften(ids); for (const row of result.rows) { row.eigenschaften = eigMap.get(row.id) || []; } return result.rows; } catch (error) { logger.error('personalEquipmentService.getAll failed', { error }); throw new Error('Failed to fetch personal equipment'); } } async function getByUserId(userId: string) { try { const result = await pool.query( `${BASE_SELECT} AND pa.user_id = $1 ORDER BY pa.bezeichnung`, [userId], ); const ids = result.rows.map((r: { id: string }) => r.id); const eigMap = await loadEigenschaften(ids); for (const row of result.rows) { row.eigenschaften = eigMap.get(row.id) || []; } return result.rows; } catch (error) { logger.error('personalEquipmentService.getByUserId failed', { error, userId }); throw new Error('Failed to fetch personal equipment for user'); } } async function getById(id: string) { try { const result = await pool.query( `${BASE_SELECT} AND pa.id = $1`, [id], ); if (!result.rows[0]) return null; const eigResult = await pool.query( `SELECT pae.id, pae.persoenlich_id, pae.eigenschaft_id, pae.name, pae.wert FROM persoenliche_ausruestung_eigenschaften pae WHERE pae.persoenlich_id = $1`, [id], ); result.rows[0].eigenschaften = eigResult.rows; return result.rows[0]; } catch (error) { logger.error('personalEquipmentService.getById failed', { error, id }); throw new Error('Failed to fetch personal equipment item'); } } async function create(data: CreatePersonalEquipmentData, requestingUserId: string) { try { const result = await pool.query( `INSERT INTO persoenliche_ausruestung ( bezeichnung, kategorie, artikel_id, user_id, benutzer_name, groesse, seriennummer, inventarnummer, anschaffung_datum, zustand, notizen, erstellt_von ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING *`, [ data.bezeichnung, data.kategorie ?? null, data.artikel_id ?? null, data.user_id ?? null, data.benutzer_name ?? null, data.groesse ?? null, data.seriennummer ?? null, data.inventarnummer ?? null, data.anschaffung_datum ?? null, data.zustand ?? 'gut', data.notizen ?? null, requestingUserId, ], ); const created = result.rows[0]; logger.info('Personal equipment created', { id: created.id, by: requestingUserId }); if (data.eigenschaften && data.eigenschaften.length > 0) { for (const e of data.eigenschaften) { await pool.query( `INSERT INTO persoenliche_ausruestung_eigenschaften (persoenlich_id, eigenschaft_id, name, wert) VALUES ($1, $2, $3, $4)`, [created.id, e.eigenschaft_id ?? null, e.name, e.wert], ); } } return created; } catch (error) { logger.error('personalEquipmentService.create failed', { error }); throw new Error('Failed to create personal equipment'); } } async function update(id: string, data: UpdatePersonalEquipmentData) { try { const fields: string[] = []; const values: unknown[] = []; let p = 1; const addField = (col: string, value: unknown) => { fields.push(`${col} = $${p++}`); values.push(value); }; if (data.bezeichnung !== undefined) addField('bezeichnung', data.bezeichnung); if (data.kategorie !== undefined) addField('kategorie', data.kategorie); if (data.artikel_id !== undefined) addField('artikel_id', data.artikel_id); if (data.user_id !== undefined) addField('user_id', data.user_id); if (data.benutzer_name !== undefined) addField('benutzer_name', data.benutzer_name); if (data.groesse !== undefined) addField('groesse', data.groesse); if (data.seriennummer !== undefined) addField('seriennummer', data.seriennummer); if (data.inventarnummer !== undefined) addField('inventarnummer', data.inventarnummer); if (data.anschaffung_datum !== undefined) addField('anschaffung_datum', data.anschaffung_datum); if (data.zustand !== undefined) addField('zustand', data.zustand); if (data.notizen !== undefined) addField('notizen', data.notizen); if (fields.length === 0 && data.eigenschaften === undefined) { throw new Error('No fields to update'); } let updated = null; if (fields.length > 0) { values.push(id); const result = await pool.query( `UPDATE persoenliche_ausruestung SET ${fields.join(', ')} WHERE id = $${p} AND geloescht_am IS NULL RETURNING *`, values, ); if (result.rows.length === 0) return null; updated = result.rows[0]; } if (data.eigenschaften !== undefined) { await pool.query('DELETE FROM persoenliche_ausruestung_eigenschaften WHERE persoenlich_id = $1', [id]); if (data.eigenschaften && data.eigenschaften.length > 0) { for (const e of data.eigenschaften) { await pool.query( `INSERT INTO persoenliche_ausruestung_eigenschaften (persoenlich_id, eigenschaft_id, name, wert) VALUES ($1, $2, $3, $4)`, [id, e.eigenschaft_id ?? null, e.name, e.wert], ); } } } if (!updated) { // Only eigenschaften were updated, fetch the row const result = await pool.query( `SELECT * FROM persoenliche_ausruestung WHERE id = $1 AND geloescht_am IS NULL`, [id], ); if (result.rows.length === 0) return null; updated = result.rows[0]; } logger.info('Personal equipment updated', { id }); return updated; } catch (error) { logger.error('personalEquipmentService.update failed', { error, id }); throw error; } } async function softDelete(id: string) { try { const result = await pool.query( `UPDATE persoenliche_ausruestung SET geloescht_am = NOW() WHERE id = $1 AND geloescht_am IS NULL RETURNING id`, [id], ); if (result.rows.length === 0) return false; logger.info('Personal equipment soft-deleted', { id }); return true; } catch (error) { logger.error('personalEquipmentService.softDelete failed', { error, id }); throw new Error('Failed to delete personal equipment'); } } export default { getAll, getByUserId, getById, create, update, delete: softDelete, };