Files
dashboard/backend/src/services/personalEquipment.service.ts

283 lines
9.0 KiB
TypeScript

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<string, { id: number; persoenlich_id: string; eigenschaft_id: number | null; name: string; wert: string }[]>();
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<string, typeof result.rows>();
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,
};