rework issue system
This commit is contained in:
@@ -1,21 +1,76 @@
|
||||
import pool from '../config/database';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
async function getIssues(userId: string, canViewAll: boolean) {
|
||||
interface IssueFilters {
|
||||
typ_id?: number[];
|
||||
prioritaet?: string[];
|
||||
status?: string[];
|
||||
erstellt_von?: string;
|
||||
zugewiesen_an?: string;
|
||||
}
|
||||
|
||||
interface GetIssuesParams {
|
||||
userId: string;
|
||||
canViewAll: boolean;
|
||||
filters?: IssueFilters;
|
||||
}
|
||||
|
||||
const BASE_SELECT = `
|
||||
SELECT i.*,
|
||||
it.name AS typ_name, it.icon AS typ_icon, it.farbe AS typ_farbe, it.erlaubt_abgelehnt AS typ_erlaubt_abgelehnt,
|
||||
u1.name AS erstellt_von_name,
|
||||
u2.name AS zugewiesen_an_name
|
||||
FROM issues i
|
||||
LEFT JOIN issue_typen it ON it.id = i.typ_id
|
||||
LEFT JOIN users u1 ON u1.id = i.erstellt_von
|
||||
LEFT JOIN users u2 ON u2.id = i.zugewiesen_an
|
||||
`;
|
||||
|
||||
async function getIssues({ userId, canViewAll, filters }: GetIssuesParams) {
|
||||
try {
|
||||
const query = `
|
||||
SELECT i.*,
|
||||
u1.name AS erstellt_von_name,
|
||||
u2.name AS zugewiesen_an_name
|
||||
FROM issues i
|
||||
LEFT JOIN users u1 ON u1.id = i.erstellt_von
|
||||
LEFT JOIN users u2 ON u2.id = i.zugewiesen_an
|
||||
${canViewAll ? '' : 'WHERE i.erstellt_von = $1'}
|
||||
ORDER BY i.created_at DESC
|
||||
`;
|
||||
const result = canViewAll
|
||||
? await pool.query(query)
|
||||
: await pool.query(query, [userId]);
|
||||
const conditions: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (!canViewAll) {
|
||||
conditions.push(`(i.erstellt_von = $${idx} OR i.zugewiesen_an = $${idx})`);
|
||||
values.push(userId);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (filters?.typ_id && filters.typ_id.length > 0) {
|
||||
conditions.push(`i.typ_id = ANY($${idx}::int[])`);
|
||||
values.push(filters.typ_id);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (filters?.prioritaet && filters.prioritaet.length > 0) {
|
||||
conditions.push(`i.prioritaet = ANY($${idx}::text[])`);
|
||||
values.push(filters.prioritaet);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (filters?.status && filters.status.length > 0) {
|
||||
conditions.push(`i.status = ANY($${idx}::text[])`);
|
||||
values.push(filters.status);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (filters?.erstellt_von) {
|
||||
conditions.push(`i.erstellt_von = $${idx}`);
|
||||
values.push(filters.erstellt_von);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (filters?.zugewiesen_an) {
|
||||
conditions.push(`i.zugewiesen_an = $${idx}`);
|
||||
values.push(filters.zugewiesen_an);
|
||||
idx++;
|
||||
}
|
||||
|
||||
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
const query = `${BASE_SELECT} ${where} ORDER BY i.created_at DESC`;
|
||||
const result = await pool.query(query, values);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.getIssues failed', { error });
|
||||
@@ -26,13 +81,7 @@ async function getIssues(userId: string, canViewAll: boolean) {
|
||||
async function getIssueById(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT i.*,
|
||||
u1.name AS erstellt_von_name,
|
||||
u2.name AS zugewiesen_an_name
|
||||
FROM issues i
|
||||
LEFT JOIN users u1 ON u1.id = i.erstellt_von
|
||||
LEFT JOIN users u2 ON u2.id = i.zugewiesen_an
|
||||
WHERE i.id = $1`,
|
||||
`${BASE_SELECT} WHERE i.id = $1`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
@@ -43,63 +92,96 @@ async function getIssueById(id: number) {
|
||||
}
|
||||
|
||||
async function createIssue(
|
||||
data: { titel: string; beschreibung?: string; typ?: string; prioritaet?: string },
|
||||
data: { titel: string; beschreibung?: string; typ_id?: number; prioritaet?: string },
|
||||
userId: string
|
||||
) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO issues (titel, beschreibung, typ, prioritaet, erstellt_von)
|
||||
`INSERT INTO issues (titel, beschreibung, typ_id, prioritaet, erstellt_von)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[
|
||||
data.titel,
|
||||
data.beschreibung || null,
|
||||
data.typ || 'sonstiges',
|
||||
data.typ_id || 3,
|
||||
data.prioritaet || 'mittel',
|
||||
userId,
|
||||
]
|
||||
);
|
||||
return result.rows[0];
|
||||
return getIssueById(result.rows[0].id);
|
||||
} catch (error) {
|
||||
logger.error('IssueService.createIssue failed', { error });
|
||||
throw new Error('Issue konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
// Sentinel value to explicitly set zugewiesen_an to NULL (not used currently but kept for reference)
|
||||
const UNASSIGN = '__UNASSIGN__';
|
||||
|
||||
async function updateIssue(
|
||||
id: number,
|
||||
data: {
|
||||
titel?: string;
|
||||
beschreibung?: string;
|
||||
typ?: string;
|
||||
typ_id?: number;
|
||||
prioritaet?: string;
|
||||
status?: string;
|
||||
zugewiesen_an?: string | null;
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE issues
|
||||
SET titel = COALESCE($1, titel),
|
||||
beschreibung = COALESCE($2, beschreibung),
|
||||
typ = COALESCE($3, typ),
|
||||
prioritaet = COALESCE($4, prioritaet),
|
||||
status = COALESCE($5, status),
|
||||
zugewiesen_an = COALESCE($6, zugewiesen_an),
|
||||
updated_at = NOW()
|
||||
WHERE id = $7
|
||||
RETURNING *`,
|
||||
[
|
||||
data.titel,
|
||||
data.beschreibung,
|
||||
data.typ,
|
||||
data.prioritaet,
|
||||
data.status,
|
||||
data.zugewiesen_an,
|
||||
id,
|
||||
]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
const setClauses: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (data.titel !== undefined) {
|
||||
setClauses.push(`titel = $${idx}`);
|
||||
values.push(data.titel);
|
||||
idx++;
|
||||
}
|
||||
if (data.beschreibung !== undefined) {
|
||||
setClauses.push(`beschreibung = $${idx}`);
|
||||
values.push(data.beschreibung);
|
||||
idx++;
|
||||
}
|
||||
if (data.typ_id !== undefined) {
|
||||
setClauses.push(`typ_id = $${idx}`);
|
||||
values.push(data.typ_id);
|
||||
idx++;
|
||||
}
|
||||
if (data.prioritaet !== undefined) {
|
||||
setClauses.push(`prioritaet = $${idx}`);
|
||||
values.push(data.prioritaet);
|
||||
idx++;
|
||||
}
|
||||
if (data.status !== undefined) {
|
||||
setClauses.push(`status = $${idx}`);
|
||||
values.push(data.status);
|
||||
idx++;
|
||||
}
|
||||
if ('zugewiesen_an' in data) {
|
||||
setClauses.push(`zugewiesen_an = $${idx}`);
|
||||
values.push(data.zugewiesen_an ?? null);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (setClauses.length === 0) {
|
||||
return getIssueById(id);
|
||||
}
|
||||
|
||||
setClauses.push(`updated_at = NOW()`);
|
||||
values.push(id);
|
||||
|
||||
const query = `
|
||||
UPDATE issues
|
||||
SET ${setClauses.join(', ')}
|
||||
WHERE id = $${idx}
|
||||
RETURNING id
|
||||
`;
|
||||
const result = await pool.query(query, values);
|
||||
if (result.rows.length === 0) return null;
|
||||
|
||||
return getIssueById(id);
|
||||
} catch (error) {
|
||||
logger.error('IssueService.updateIssue failed', { error, id });
|
||||
throw new Error('Issue konnte nicht aktualisiert werden');
|
||||
@@ -151,6 +233,134 @@ async function addComment(issueId: number, autorId: string, inhalt: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getTypes() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM issue_typen WHERE aktiv = true ORDER BY sort_order ASC, id ASC`
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.getTypes failed', { error });
|
||||
throw new Error('Issue-Typen konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function createType(data: {
|
||||
name: string;
|
||||
parent_id?: number | null;
|
||||
icon?: string | null;
|
||||
farbe?: string | null;
|
||||
erlaubt_abgelehnt?: boolean;
|
||||
sort_order?: number;
|
||||
}) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO issue_typen (name, parent_id, icon, farbe, erlaubt_abgelehnt, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *`,
|
||||
[
|
||||
data.name,
|
||||
data.parent_id ?? null,
|
||||
data.icon ?? null,
|
||||
data.farbe ?? null,
|
||||
data.erlaubt_abgelehnt ?? true,
|
||||
data.sort_order ?? 0,
|
||||
]
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('IssueService.createType failed', { error });
|
||||
throw new Error('Issue-Typ konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateType(
|
||||
id: number,
|
||||
data: {
|
||||
name?: string;
|
||||
parent_id?: number | null;
|
||||
icon?: string | null;
|
||||
farbe?: string | null;
|
||||
erlaubt_abgelehnt?: boolean;
|
||||
sort_order?: number;
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const setClauses: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (data.name !== undefined) {
|
||||
setClauses.push(`name = $${idx}`);
|
||||
values.push(data.name);
|
||||
idx++;
|
||||
}
|
||||
if ('parent_id' in data) {
|
||||
setClauses.push(`parent_id = $${idx}`);
|
||||
values.push(data.parent_id);
|
||||
idx++;
|
||||
}
|
||||
if ('icon' in data) {
|
||||
setClauses.push(`icon = $${idx}`);
|
||||
values.push(data.icon);
|
||||
idx++;
|
||||
}
|
||||
if ('farbe' in data) {
|
||||
setClauses.push(`farbe = $${idx}`);
|
||||
values.push(data.farbe);
|
||||
idx++;
|
||||
}
|
||||
if (data.erlaubt_abgelehnt !== undefined) {
|
||||
setClauses.push(`erlaubt_abgelehnt = $${idx}`);
|
||||
values.push(data.erlaubt_abgelehnt);
|
||||
idx++;
|
||||
}
|
||||
if (data.sort_order !== undefined) {
|
||||
setClauses.push(`sort_order = $${idx}`);
|
||||
values.push(data.sort_order);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (setClauses.length === 0) {
|
||||
const result = await pool.query(`SELECT * FROM issue_typen WHERE id = $1`, [id]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
values.push(id);
|
||||
const query = `UPDATE issue_typen SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`;
|
||||
const result = await pool.query(query, values);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.updateType failed', { error, id });
|
||||
throw new Error('Issue-Typ konnte nicht aktualisiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deactivateType(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE issue_typen SET aktiv = false WHERE id = $1 RETURNING *`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.deactivateType failed', { error, id });
|
||||
throw new Error('Issue-Typ konnte nicht deaktiviert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getAssignableMembers() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT id, name FROM users WHERE id IS NOT NULL ORDER BY name`
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.getAssignableMembers failed', { error });
|
||||
throw new Error('Mitglieder konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getIssues,
|
||||
getIssueById,
|
||||
@@ -159,4 +369,10 @@ export default {
|
||||
deleteIssue,
|
||||
getComments,
|
||||
addComment,
|
||||
getTypes,
|
||||
createType,
|
||||
updateType,
|
||||
deactivateType,
|
||||
getAssignableMembers,
|
||||
UNASSIGN,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user