feat: add issue kanban/attachments/deadlines, dashboard widget DnD, and checklisten system
This commit is contained in:
650
backend/src/services/checklist.service.ts
Normal file
650
backend/src/services/checklist.service.ts
Normal file
@@ -0,0 +1,650 @@
|
||||
import pool from '../config/database';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function calculateNextDueDate(intervall: string | null, intervall_tage: number | null): Date | null {
|
||||
const now = new Date();
|
||||
if (intervall_tage && intervall_tage > 0) {
|
||||
now.setDate(now.getDate() + intervall_tage);
|
||||
return now;
|
||||
}
|
||||
switch (intervall) {
|
||||
case 'weekly':
|
||||
now.setDate(now.getDate() + 7);
|
||||
return now;
|
||||
case 'monthly':
|
||||
now.setMonth(now.getMonth() + 1);
|
||||
return now;
|
||||
case 'yearly':
|
||||
now.setFullYear(now.getFullYear() + 1);
|
||||
return now;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vorlagen (Templates)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getVorlagen(filter?: { fahrzeug_typ_id?: number; aktiv?: boolean }) {
|
||||
try {
|
||||
const conditions: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (filter?.fahrzeug_typ_id !== undefined) {
|
||||
conditions.push(`v.fahrzeug_typ_id = $${idx}`);
|
||||
values.push(filter.fahrzeug_typ_id);
|
||||
idx++;
|
||||
}
|
||||
if (filter?.aktiv !== undefined) {
|
||||
conditions.push(`v.aktiv = $${idx}`);
|
||||
values.push(filter.aktiv);
|
||||
idx++;
|
||||
}
|
||||
|
||||
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
const result = await pool.query(
|
||||
`SELECT v.*, ft.name AS fahrzeug_typ_name
|
||||
FROM checklist_vorlagen v
|
||||
LEFT JOIN fahrzeug_typen ft ON ft.id = v.fahrzeug_typ_id
|
||||
${where}
|
||||
ORDER BY v.name ASC`,
|
||||
values
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getVorlagen failed', { error });
|
||||
throw new Error('Vorlagen konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getVorlageById(id: number) {
|
||||
try {
|
||||
const vorlageResult = await pool.query(
|
||||
`SELECT v.*, ft.name AS fahrzeug_typ_name
|
||||
FROM checklist_vorlagen v
|
||||
LEFT JOIN fahrzeug_typen ft ON ft.id = v.fahrzeug_typ_id
|
||||
WHERE v.id = $1`,
|
||||
[id]
|
||||
);
|
||||
if (vorlageResult.rows.length === 0) return null;
|
||||
|
||||
const vorlage = vorlageResult.rows[0];
|
||||
const itemsResult = await pool.query(
|
||||
`SELECT * FROM checklist_vorlage_items WHERE vorlage_id = $1 ORDER BY sort_order ASC, id ASC`,
|
||||
[id]
|
||||
);
|
||||
vorlage.items = itemsResult.rows;
|
||||
return vorlage;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getVorlageById failed', { error, id });
|
||||
throw new Error('Vorlage konnte nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function createVorlage(data: {
|
||||
name: string;
|
||||
fahrzeug_typ_id?: number | null;
|
||||
intervall?: string | null;
|
||||
intervall_tage?: number | null;
|
||||
beschreibung?: string | null;
|
||||
}) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO checklist_vorlagen (name, fahrzeug_typ_id, intervall, intervall_tage, beschreibung)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[
|
||||
data.name,
|
||||
data.fahrzeug_typ_id ?? null,
|
||||
data.intervall ?? null,
|
||||
data.intervall_tage ?? null,
|
||||
data.beschreibung ?? null,
|
||||
]
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.createVorlage failed', { error });
|
||||
throw new Error('Vorlage konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateVorlage(id: number, data: {
|
||||
name?: string;
|
||||
fahrzeug_typ_id?: number | null;
|
||||
intervall?: string | null;
|
||||
intervall_tage?: number | null;
|
||||
beschreibung?: string | null;
|
||||
aktiv?: boolean;
|
||||
}) {
|
||||
try {
|
||||
const setClauses: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (data.name !== undefined) { setClauses.push(`name = $${idx}`); values.push(data.name); idx++; }
|
||||
if ('fahrzeug_typ_id' in data) { setClauses.push(`fahrzeug_typ_id = $${idx}`); values.push(data.fahrzeug_typ_id); idx++; }
|
||||
if ('intervall' in data) { setClauses.push(`intervall = $${idx}`); values.push(data.intervall); idx++; }
|
||||
if ('intervall_tage' in data) { setClauses.push(`intervall_tage = $${idx}`); values.push(data.intervall_tage); idx++; }
|
||||
if ('beschreibung' in data) { setClauses.push(`beschreibung = $${idx}`); values.push(data.beschreibung); idx++; }
|
||||
if (data.aktiv !== undefined) { setClauses.push(`aktiv = $${idx}`); values.push(data.aktiv); idx++; }
|
||||
|
||||
if (setClauses.length === 0) return getVorlageById(id);
|
||||
|
||||
values.push(id);
|
||||
const result = await pool.query(
|
||||
`UPDATE checklist_vorlagen SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.updateVorlage failed', { error, id });
|
||||
throw new Error('Vorlage konnte nicht aktualisiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteVorlage(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`DELETE FROM checklist_vorlagen WHERE id = $1 RETURNING *`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.deleteVorlage failed', { error, id });
|
||||
throw new Error('Vorlage konnte nicht gelöscht werden');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vorlage Items (Template line items)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getVorlageItems(vorlageId: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM checklist_vorlage_items WHERE vorlage_id = $1 ORDER BY sort_order ASC, id ASC`,
|
||||
[vorlageId]
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getVorlageItems failed', { error, vorlageId });
|
||||
throw new Error('Vorlage-Items konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function addVorlageItem(vorlageId: number, data: {
|
||||
bezeichnung: string;
|
||||
beschreibung?: string | null;
|
||||
pflicht?: boolean;
|
||||
sort_order?: number;
|
||||
}) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO checklist_vorlage_items (vorlage_id, bezeichnung, beschreibung, pflicht, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[vorlageId, data.bezeichnung, data.beschreibung ?? null, data.pflicht ?? true, data.sort_order ?? 0]
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.addVorlageItem failed', { error, vorlageId });
|
||||
throw new Error('Vorlage-Item konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateVorlageItem(id: number, data: {
|
||||
bezeichnung?: string;
|
||||
beschreibung?: string | null;
|
||||
pflicht?: boolean;
|
||||
sort_order?: number;
|
||||
}) {
|
||||
try {
|
||||
const setClauses: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (data.bezeichnung !== undefined) { setClauses.push(`bezeichnung = $${idx}`); values.push(data.bezeichnung); idx++; }
|
||||
if ('beschreibung' in data) { setClauses.push(`beschreibung = $${idx}`); values.push(data.beschreibung); idx++; }
|
||||
if (data.pflicht !== undefined) { setClauses.push(`pflicht = $${idx}`); values.push(data.pflicht); idx++; }
|
||||
if (data.sort_order !== undefined) { setClauses.push(`sort_order = $${idx}`); values.push(data.sort_order); idx++; }
|
||||
|
||||
if (setClauses.length === 0) {
|
||||
const r = await pool.query(`SELECT * FROM checklist_vorlage_items WHERE id = $1`, [id]);
|
||||
return r.rows[0] || null;
|
||||
}
|
||||
|
||||
values.push(id);
|
||||
const result = await pool.query(
|
||||
`UPDATE checklist_vorlage_items SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.updateVorlageItem failed', { error, id });
|
||||
throw new Error('Vorlage-Item konnte nicht aktualisiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteVorlageItem(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`DELETE FROM checklist_vorlage_items WHERE id = $1 RETURNING *`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.deleteVorlageItem failed', { error, id });
|
||||
throw new Error('Vorlage-Item konnte nicht gelöscht werden');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fahrzeug-spezifische Items
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getVehicleItems(fahrzeugId: string) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM fahrzeug_checklist_items WHERE fahrzeug_id = $1 AND aktiv = true ORDER BY sort_order ASC, id ASC`,
|
||||
[fahrzeugId]
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getVehicleItems failed', { error, fahrzeugId });
|
||||
throw new Error('Fahrzeug-Items konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function addVehicleItem(fahrzeugId: string, data: {
|
||||
bezeichnung: string;
|
||||
beschreibung?: string | null;
|
||||
pflicht?: boolean;
|
||||
sort_order?: number;
|
||||
}) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO fahrzeug_checklist_items (fahrzeug_id, bezeichnung, beschreibung, pflicht, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[fahrzeugId, data.bezeichnung, data.beschreibung ?? null, data.pflicht ?? true, data.sort_order ?? 0]
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.addVehicleItem failed', { error, fahrzeugId });
|
||||
throw new Error('Fahrzeug-Item konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateVehicleItem(id: number, data: {
|
||||
bezeichnung?: string;
|
||||
beschreibung?: string | null;
|
||||
pflicht?: boolean;
|
||||
sort_order?: number;
|
||||
aktiv?: boolean;
|
||||
}) {
|
||||
try {
|
||||
const setClauses: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (data.bezeichnung !== undefined) { setClauses.push(`bezeichnung = $${idx}`); values.push(data.bezeichnung); idx++; }
|
||||
if ('beschreibung' in data) { setClauses.push(`beschreibung = $${idx}`); values.push(data.beschreibung); idx++; }
|
||||
if (data.pflicht !== undefined) { setClauses.push(`pflicht = $${idx}`); values.push(data.pflicht); idx++; }
|
||||
if (data.sort_order !== undefined) { setClauses.push(`sort_order = $${idx}`); values.push(data.sort_order); idx++; }
|
||||
if (data.aktiv !== undefined) { setClauses.push(`aktiv = $${idx}`); values.push(data.aktiv); idx++; }
|
||||
|
||||
if (setClauses.length === 0) {
|
||||
const r = await pool.query(`SELECT * FROM fahrzeug_checklist_items WHERE id = $1`, [id]);
|
||||
return r.rows[0] || null;
|
||||
}
|
||||
|
||||
values.push(id);
|
||||
const result = await pool.query(
|
||||
`UPDATE fahrzeug_checklist_items SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.updateVehicleItem failed', { error, id });
|
||||
throw new Error('Fahrzeug-Item konnte nicht aktualisiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteVehicleItem(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE fahrzeug_checklist_items SET aktiv = false WHERE id = $1 RETURNING *`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.deleteVehicleItem failed', { error, id });
|
||||
throw new Error('Fahrzeug-Item konnte nicht deaktiviert werden');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Templates for a specific vehicle (via type junction)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getTemplatesForVehicle(fahrzeugId: string) {
|
||||
try {
|
||||
// Templates that match the vehicle's types, or global templates (no type)
|
||||
const result = await pool.query(
|
||||
`SELECT DISTINCT v.*, ft.name AS fahrzeug_typ_name
|
||||
FROM checklist_vorlagen v
|
||||
LEFT JOIN fahrzeug_typen ft ON ft.id = v.fahrzeug_typ_id
|
||||
WHERE v.aktiv = true
|
||||
AND (
|
||||
v.fahrzeug_typ_id IS NULL
|
||||
OR v.fahrzeug_typ_id IN (
|
||||
SELECT fahrzeug_typ_id FROM fahrzeug_fahrzeug_typen WHERE fahrzeug_id = $1
|
||||
)
|
||||
)
|
||||
ORDER BY v.name ASC`,
|
||||
[fahrzeugId]
|
||||
);
|
||||
|
||||
// Attach items to each template
|
||||
for (const vorlage of result.rows) {
|
||||
const items = await pool.query(
|
||||
`SELECT * FROM checklist_vorlage_items WHERE vorlage_id = $1 ORDER BY sort_order ASC, id ASC`,
|
||||
[vorlage.id]
|
||||
);
|
||||
vorlage.items = items.rows;
|
||||
}
|
||||
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getTemplatesForVehicle failed', { error, fahrzeugId });
|
||||
throw new Error('Checklisten für Fahrzeug konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ausführungen (Executions)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function startExecution(fahrzeugId: string, vorlageId: number, userId: string) {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Create the execution record
|
||||
const execResult = await client.query(
|
||||
`INSERT INTO checklist_ausfuehrungen (fahrzeug_id, vorlage_id, ausgefuehrt_von)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING *`,
|
||||
[fahrzeugId, vorlageId, userId]
|
||||
);
|
||||
const execution = execResult.rows[0];
|
||||
|
||||
// Copy template items into execution items
|
||||
const vorlageItems = await client.query(
|
||||
`SELECT * FROM checklist_vorlage_items WHERE vorlage_id = $1 ORDER BY sort_order ASC, id ASC`,
|
||||
[vorlageId]
|
||||
);
|
||||
for (const item of vorlageItems.rows) {
|
||||
await client.query(
|
||||
`INSERT INTO checklist_ausfuehrung_items (ausfuehrung_id, vorlage_item_id, bezeichnung)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[execution.id, item.id, item.bezeichnung]
|
||||
);
|
||||
}
|
||||
|
||||
// Copy vehicle-specific items
|
||||
const vehicleItems = await client.query(
|
||||
`SELECT * FROM fahrzeug_checklist_items WHERE fahrzeug_id = $1 AND aktiv = true ORDER BY sort_order ASC, id ASC`,
|
||||
[fahrzeugId]
|
||||
);
|
||||
for (const item of vehicleItems.rows) {
|
||||
await client.query(
|
||||
`INSERT INTO checklist_ausfuehrung_items (ausfuehrung_id, fahrzeug_item_id, bezeichnung)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[execution.id, item.id, item.bezeichnung]
|
||||
);
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
// Fetch the complete execution with items
|
||||
return getExecutionById(execution.id);
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK').catch(() => {});
|
||||
logger.error('ChecklistService.startExecution failed', { error, fahrzeugId, vorlageId });
|
||||
throw new Error('Checklist-Ausführung konnte nicht gestartet werden');
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
async function getExecutionById(id: string) {
|
||||
try {
|
||||
const execResult = await pool.query(
|
||||
`SELECT a.*,
|
||||
f.bezeichnung AS fahrzeug_name, f.kurzname AS fahrzeug_kurzname,
|
||||
v.name AS vorlage_name,
|
||||
u1.name AS ausgefuehrt_von_name,
|
||||
u2.name AS freigegeben_von_name
|
||||
FROM checklist_ausfuehrungen a
|
||||
LEFT JOIN fahrzeuge f ON f.id = a.fahrzeug_id
|
||||
LEFT JOIN checklist_vorlagen v ON v.id = a.vorlage_id
|
||||
LEFT JOIN users u1 ON u1.id = a.ausgefuehrt_von
|
||||
LEFT JOIN users u2 ON u2.id = a.freigegeben_von
|
||||
WHERE a.id = $1`,
|
||||
[id]
|
||||
);
|
||||
if (execResult.rows.length === 0) return null;
|
||||
|
||||
const execution = execResult.rows[0];
|
||||
const itemsResult = await pool.query(
|
||||
`SELECT * FROM checklist_ausfuehrung_items WHERE ausfuehrung_id = $1 ORDER BY id ASC`,
|
||||
[id]
|
||||
);
|
||||
execution.items = itemsResult.rows;
|
||||
return execution;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getExecutionById failed', { error, id });
|
||||
throw new Error('Ausführung konnte nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function submitExecution(
|
||||
id: string,
|
||||
items: Array<{ itemId: number; ergebnis: string; kommentar?: string }>,
|
||||
notizen: string | null,
|
||||
userId: string,
|
||||
) {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Update each item's result
|
||||
for (const item of items) {
|
||||
await client.query(
|
||||
`UPDATE checklist_ausfuehrung_items SET ergebnis = $1, kommentar = $2 WHERE id = $3 AND ausfuehrung_id = $4`,
|
||||
[item.ergebnis, item.kommentar ?? null, item.itemId, id]
|
||||
);
|
||||
}
|
||||
|
||||
// Check if all pflicht items have ergebnis = 'ok'
|
||||
const pflichtCheck = await client.query(
|
||||
`SELECT ai.id, ai.ergebnis, ai.vorlage_item_id, ai.fahrzeug_item_id
|
||||
FROM checklist_ausfuehrung_items ai
|
||||
LEFT JOIN checklist_vorlage_items vi ON vi.id = ai.vorlage_item_id
|
||||
LEFT JOIN fahrzeug_checklist_items fi ON fi.id = ai.fahrzeug_item_id
|
||||
WHERE ai.ausfuehrung_id = $1
|
||||
AND (COALESCE(vi.pflicht, fi.pflicht, true) = true)`,
|
||||
[id]
|
||||
);
|
||||
|
||||
const allPflichtOk = pflichtCheck.rows.every((r: any) => r.ergebnis === 'ok');
|
||||
const newStatus = allPflichtOk ? 'abgeschlossen' : 'unvollstaendig';
|
||||
|
||||
await client.query(
|
||||
`UPDATE checklist_ausfuehrungen SET status = $1, ausgefuehrt_am = NOW(), notizen = $2 WHERE id = $3`,
|
||||
[newStatus, notizen, id]
|
||||
);
|
||||
|
||||
// Update checklist_faelligkeit if completed
|
||||
if (allPflichtOk) {
|
||||
const exec = await client.query(`SELECT vorlage_id, fahrzeug_id FROM checklist_ausfuehrungen WHERE id = $1`, [id]);
|
||||
if (exec.rows.length > 0) {
|
||||
const { vorlage_id, fahrzeug_id } = exec.rows[0];
|
||||
const vorlage = await client.query(`SELECT intervall, intervall_tage FROM checklist_vorlagen WHERE id = $1`, [vorlage_id]);
|
||||
if (vorlage.rows.length > 0) {
|
||||
const nextDue = calculateNextDueDate(vorlage.rows[0].intervall, vorlage.rows[0].intervall_tage);
|
||||
if (nextDue) {
|
||||
await client.query(
|
||||
`INSERT INTO checklist_faelligkeit (fahrzeug_id, vorlage_id, naechste_faellig_am, letzte_ausfuehrung_id)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (fahrzeug_id, vorlage_id) DO UPDATE
|
||||
SET naechste_faellig_am = $3, letzte_ausfuehrung_id = $4`,
|
||||
[fahrzeug_id, vorlage_id, nextDue, id]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
return getExecutionById(id);
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK').catch(() => {});
|
||||
logger.error('ChecklistService.submitExecution failed', { error, id });
|
||||
throw new Error('Ausführung konnte nicht abgeschlossen werden');
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
async function approveExecution(id: string, userId: string) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE checklist_ausfuehrungen
|
||||
SET status = 'freigegeben', freigegeben_von = $1, freigegeben_am = NOW()
|
||||
WHERE id = $2 AND status IN ('abgeschlossen', 'unvollstaendig')
|
||||
RETURNING *`,
|
||||
[userId, id]
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
return getExecutionById(id);
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.approveExecution failed', { error, id });
|
||||
throw new Error('Freigabe konnte nicht erteilt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getExecutions(filter?: { fahrzeugId?: string; vorlageId?: number; status?: string }) {
|
||||
try {
|
||||
const conditions: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (filter?.fahrzeugId) {
|
||||
conditions.push(`a.fahrzeug_id = $${idx}`);
|
||||
values.push(filter.fahrzeugId);
|
||||
idx++;
|
||||
}
|
||||
if (filter?.vorlageId) {
|
||||
conditions.push(`a.vorlage_id = $${idx}`);
|
||||
values.push(filter.vorlageId);
|
||||
idx++;
|
||||
}
|
||||
if (filter?.status) {
|
||||
conditions.push(`a.status = $${idx}`);
|
||||
values.push(filter.status);
|
||||
idx++;
|
||||
}
|
||||
|
||||
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
const result = await pool.query(
|
||||
`SELECT a.*,
|
||||
f.bezeichnung AS fahrzeug_name, f.kurzname AS fahrzeug_kurzname,
|
||||
v.name AS vorlage_name,
|
||||
u1.name AS ausgefuehrt_von_name,
|
||||
u2.name AS freigegeben_von_name
|
||||
FROM checklist_ausfuehrungen a
|
||||
LEFT JOIN fahrzeuge f ON f.id = a.fahrzeug_id
|
||||
LEFT JOIN checklist_vorlagen v ON v.id = a.vorlage_id
|
||||
LEFT JOIN users u1 ON u1.id = a.ausgefuehrt_von
|
||||
LEFT JOIN users u2 ON u2.id = a.freigegeben_von
|
||||
${where}
|
||||
ORDER BY a.created_at DESC`,
|
||||
values
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getExecutions failed', { error });
|
||||
throw new Error('Ausführungen konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fälligkeiten (Due dates)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getOverdueChecklists() {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT cf.*, f.bezeichnung AS fahrzeug_name, f.kurzname AS fahrzeug_kurzname,
|
||||
v.name AS vorlage_name
|
||||
FROM checklist_faelligkeit cf
|
||||
JOIN fahrzeuge f ON f.id = cf.fahrzeug_id AND f.deleted_at IS NULL
|
||||
JOIN checklist_vorlagen v ON v.id = cf.vorlage_id AND v.aktiv = true
|
||||
WHERE cf.naechste_faellig_am <= CURRENT_DATE
|
||||
ORDER BY cf.naechste_faellig_am ASC
|
||||
`);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getOverdueChecklists failed', { error });
|
||||
throw new Error('Überfällige Checklisten konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getDueChecklists(fahrzeugId: string) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT cf.*, v.name AS vorlage_name, v.intervall, v.intervall_tage
|
||||
FROM checklist_faelligkeit cf
|
||||
JOIN checklist_vorlagen v ON v.id = cf.vorlage_id AND v.aktiv = true
|
||||
WHERE cf.fahrzeug_id = $1
|
||||
ORDER BY cf.naechste_faellig_am ASC`,
|
||||
[fahrzeugId]
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('ChecklistService.getDueChecklists failed', { error, fahrzeugId });
|
||||
throw new Error('Fälligkeiten konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getVorlagen,
|
||||
getVorlageById,
|
||||
createVorlage,
|
||||
updateVorlage,
|
||||
deleteVorlage,
|
||||
getVorlageItems,
|
||||
addVorlageItem,
|
||||
updateVorlageItem,
|
||||
deleteVorlageItem,
|
||||
getVehicleItems,
|
||||
addVehicleItem,
|
||||
updateVehicleItem,
|
||||
deleteVehicleItem,
|
||||
getTemplatesForVehicle,
|
||||
startExecution,
|
||||
getExecutionById,
|
||||
submitExecution,
|
||||
approveExecution,
|
||||
getExecutions,
|
||||
getOverdueChecklists,
|
||||
getDueChecklists,
|
||||
};
|
||||
Reference in New Issue
Block a user