feat: vehicle/equipment type system, equipment checklist support, and checklist overview redesign

This commit is contained in:
Matthias Hochmeister
2026-03-28 17:27:01 +01:00
parent 692093cc85
commit 6b46e97eb6
25 changed files with 2230 additions and 494 deletions

View File

@@ -0,0 +1,151 @@
import pool from '../config/database';
import logger from '../utils/logger';
class AusruestungTypService {
async getAll() {
try {
const result = await pool.query(
`SELECT * FROM ausruestung_typen ORDER BY name ASC`
);
return result.rows;
} catch (error) {
logger.error('AusruestungTypService.getAll failed', { error });
throw new Error('Ausrüstungs-Typen konnten nicht geladen werden');
}
}
async getById(id: number) {
try {
const result = await pool.query(
`SELECT * FROM ausruestung_typen WHERE id = $1`,
[id]
);
return result.rows[0] || null;
} catch (error) {
logger.error('AusruestungTypService.getById failed', { error, id });
throw new Error('Ausrüstungs-Typ konnte nicht geladen werden');
}
}
async create(data: { name: string; beschreibung?: string; icon?: string }) {
try {
const result = await pool.query(
`INSERT INTO ausruestung_typen (name, beschreibung, icon)
VALUES ($1, $2, $3)
RETURNING *`,
[data.name, data.beschreibung ?? null, data.icon ?? null]
);
return result.rows[0];
} catch (error) {
logger.error('AusruestungTypService.create failed', { error });
throw new Error('Ausrüstungs-Typ konnte nicht erstellt werden');
}
}
async update(id: number, data: { name?: string; beschreibung?: string; icon?: string }) {
try {
const setClauses: string[] = [];
const values: any[] = [];
let idx = 1;
if (data.name !== undefined) {
setClauses.push(`name = $${idx}`);
values.push(data.name);
idx++;
}
if (data.beschreibung !== undefined) {
setClauses.push(`beschreibung = $${idx}`);
values.push(data.beschreibung);
idx++;
}
if (data.icon !== undefined) {
setClauses.push(`icon = $${idx}`);
values.push(data.icon);
idx++;
}
if (setClauses.length === 0) {
return this.getById(id);
}
values.push(id);
const result = await pool.query(
`UPDATE ausruestung_typen SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`,
values
);
return result.rows[0] || null;
} catch (error) {
logger.error('AusruestungTypService.update failed', { error, id });
throw new Error('Ausrüstungs-Typ konnte nicht aktualisiert werden');
}
}
async delete(id: number) {
try {
// Check if any equipment uses this type
const usage = await pool.query(
`SELECT COUNT(*) FROM ausruestung_ausruestung_typen WHERE ausruestung_typ_id = $1`,
[id]
);
if (parseInt(usage.rows[0].count, 10) > 0) {
throw new Error('Typ wird noch von Ausrüstung verwendet');
}
const result = await pool.query(
`DELETE FROM ausruestung_typen WHERE id = $1 RETURNING *`,
[id]
);
return result.rows[0] || null;
} catch (error) {
logger.error('AusruestungTypService.delete failed', { error, id });
throw error;
}
}
async getTypesForEquipment(ausruestungId: string) {
try {
const result = await pool.query(
`SELECT at.* FROM ausruestung_typen at
JOIN ausruestung_ausruestung_typen aat ON aat.ausruestung_typ_id = at.id
WHERE aat.ausruestung_id = $1
ORDER BY at.name ASC`,
[ausruestungId]
);
return result.rows;
} catch (error) {
logger.error('AusruestungTypService.getTypesForEquipment failed', { error, ausruestungId });
throw new Error('Ausrüstungs-Typen konnten nicht geladen werden');
}
}
async setTypesForEquipment(ausruestungId: string, typIds: number[]) {
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(
`DELETE FROM ausruestung_ausruestung_typen WHERE ausruestung_id = $1`,
[ausruestungId]
);
for (const typId of typIds) {
await client.query(
`INSERT INTO ausruestung_ausruestung_typen (ausruestung_id, ausruestung_typ_id) VALUES ($1, $2)`,
[ausruestungId, typId]
);
}
await client.query('COMMIT');
return this.getTypesForEquipment(ausruestungId);
} catch (error) {
await client.query('ROLLBACK').catch(() => {});
logger.error('AusruestungTypService.setTypesForEquipment failed', { error, ausruestungId });
throw new Error('Ausrüstungs-Typen konnten nicht gesetzt werden');
} finally {
client.release();
}
}
}
export default new AusruestungTypService();

View File

@@ -30,7 +30,7 @@ function calculateNextDueDate(intervall: string | null, intervall_tage: number |
// Vorlagen (Templates)
// ---------------------------------------------------------------------------
async function getVorlagen(filter?: { fahrzeug_typ_id?: number; aktiv?: boolean }) {
async function getVorlagen(filter?: { fahrzeug_typ_id?: number; ausruestung_typ_id?: number; aktiv?: boolean }) {
try {
const conditions: string[] = [];
const values: any[] = [];
@@ -41,6 +41,11 @@ async function getVorlagen(filter?: { fahrzeug_typ_id?: number; aktiv?: boolean
values.push(filter.fahrzeug_typ_id);
idx++;
}
if (filter?.ausruestung_typ_id !== undefined) {
conditions.push(`v.ausruestung_typ_id = $${idx}`);
values.push(filter.ausruestung_typ_id);
idx++;
}
if (filter?.aktiv !== undefined) {
conditions.push(`v.aktiv = $${idx}`);
values.push(filter.aktiv);
@@ -49,9 +54,12 @@ async function getVorlagen(filter?: { fahrzeug_typ_id?: number; aktiv?: boolean
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
const result = await pool.query(
`SELECT v.*, ft.name AS fahrzeug_typ_name
`SELECT v.*,
ft.name AS fahrzeug_typ_name,
at.name AS ausruestung_typ_name
FROM checklist_vorlagen v
LEFT JOIN fahrzeug_typen ft ON ft.id = v.fahrzeug_typ_id
LEFT JOIN ausruestung_typen at ON at.id = v.ausruestung_typ_id
${where}
ORDER BY v.name ASC`,
values
@@ -66,9 +74,12 @@ async function getVorlagen(filter?: { fahrzeug_typ_id?: number; aktiv?: boolean
async function getVorlageById(id: number) {
try {
const vorlageResult = await pool.query(
`SELECT v.*, ft.name AS fahrzeug_typ_name
`SELECT v.*,
ft.name AS fahrzeug_typ_name,
at.name AS ausruestung_typ_name
FROM checklist_vorlagen v
LEFT JOIN fahrzeug_typen ft ON ft.id = v.fahrzeug_typ_id
LEFT JOIN ausruestung_typen at ON at.id = v.ausruestung_typ_id
WHERE v.id = $1`,
[id]
);
@@ -90,18 +101,24 @@ async function getVorlageById(id: number) {
async function createVorlage(data: {
name: string;
fahrzeug_typ_id?: number | null;
fahrzeug_id?: string | null;
ausruestung_typ_id?: number | null;
ausruestung_id?: string | 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)
`INSERT INTO checklist_vorlagen (name, fahrzeug_typ_id, fahrzeug_id, ausruestung_typ_id, ausruestung_id, intervall, intervall_tage, beschreibung)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *`,
[
data.name,
data.fahrzeug_typ_id ?? null,
data.fahrzeug_id ?? null,
data.ausruestung_typ_id ?? null,
data.ausruestung_id ?? null,
data.intervall ?? null,
data.intervall_tage ?? null,
data.beschreibung ?? null,
@@ -117,6 +134,9 @@ async function createVorlage(data: {
async function updateVorlage(id: number, data: {
name?: string;
fahrzeug_typ_id?: number | null;
fahrzeug_id?: string | null;
ausruestung_typ_id?: number | null;
ausruestung_id?: string | null;
intervall?: string | null;
intervall_tage?: number | null;
beschreibung?: string | null;
@@ -129,6 +149,9 @@ async function updateVorlage(id: number, data: {
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 ('fahrzeug_id' in data) { setClauses.push(`fahrzeug_id = $${idx}`); values.push(data.fahrzeug_id); idx++; }
if ('ausruestung_typ_id' in data) { setClauses.push(`ausruestung_typ_id = $${idx}`); values.push(data.ausruestung_typ_id); idx++; }
if ('ausruestung_id' in data) { setClauses.push(`ausruestung_id = $${idx}`); values.push(data.ausruestung_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++; }
@@ -329,23 +352,111 @@ async function deleteVehicleItem(id: number) {
}
}
// ---------------------------------------------------------------------------
// Ausrüstung-spezifische Items
// ---------------------------------------------------------------------------
async function getEquipmentItems(ausruestungId: string) {
try {
const result = await pool.query(
`SELECT * FROM ausruestung_checklist_items WHERE ausruestung_id = $1 AND aktiv = true ORDER BY sort_order ASC, id ASC`,
[ausruestungId]
);
return result.rows;
} catch (error) {
logger.error('ChecklistService.getEquipmentItems failed', { error, ausruestungId });
throw new Error('Ausrüstungs-Items konnten nicht geladen werden');
}
}
async function addEquipmentItem(ausruestungId: string, data: {
bezeichnung: string;
beschreibung?: string | null;
pflicht?: boolean;
sort_order?: number;
}) {
try {
const result = await pool.query(
`INSERT INTO ausruestung_checklist_items (ausruestung_id, bezeichnung, beschreibung, pflicht, sort_order)
VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[ausruestungId, data.bezeichnung, data.beschreibung ?? null, data.pflicht ?? true, data.sort_order ?? 0]
);
return result.rows[0];
} catch (error) {
logger.error('ChecklistService.addEquipmentItem failed', { error, ausruestungId });
throw new Error('Ausrüstungs-Item konnte nicht erstellt werden');
}
}
async function updateEquipmentItem(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 ausruestung_checklist_items WHERE id = $1`, [id]);
return r.rows[0] || null;
}
values.push(id);
const result = await pool.query(
`UPDATE ausruestung_checklist_items SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`,
values
);
return result.rows[0] || null;
} catch (error) {
logger.error('ChecklistService.updateEquipmentItem failed', { error, id });
throw new Error('Ausrüstungs-Item konnte nicht aktualisiert werden');
}
}
async function deleteEquipmentItem(id: number) {
try {
const result = await pool.query(
`UPDATE ausruestung_checklist_items SET aktiv = false WHERE id = $1 RETURNING *`,
[id]
);
return result.rows[0] || null;
} catch (error) {
logger.error('ChecklistService.deleteEquipmentItem failed', { error, id });
throw new Error('Ausrüstungs-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)
// Templates that match the vehicle directly, by type, or global (no assignment)
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.ausruestung_id IS NULL
AND v.ausruestung_typ_id IS NULL
AND (
v.fahrzeug_typ_id IS NULL
v.fahrzeug_id = $1
OR v.fahrzeug_typ_id IN (
SELECT fahrzeug_typ_id FROM fahrzeug_fahrzeug_typen WHERE fahrzeug_id = $1
)
OR (v.fahrzeug_typ_id IS NULL AND v.fahrzeug_id IS NULL)
)
ORDER BY v.name ASC`,
[fahrzeugId]
@@ -367,21 +478,109 @@ async function getTemplatesForVehicle(fahrzeugId: string) {
}
}
// ---------------------------------------------------------------------------
// Templates for a specific equipment item (via type junction)
// ---------------------------------------------------------------------------
async function getTemplatesForEquipment(ausruestungId: string) {
try {
const result = await pool.query(
`SELECT DISTINCT v.*, at.name AS ausruestung_typ_name
FROM checklist_vorlagen v
LEFT JOIN ausruestung_typen at ON at.id = v.ausruestung_typ_id
WHERE v.aktiv = true
AND (
v.ausruestung_id = $1
OR v.ausruestung_typ_id IN (
SELECT ausruestung_typ_id FROM ausruestung_ausruestung_typen WHERE ausruestung_id = $1
)
OR (v.fahrzeug_typ_id IS NULL AND v.fahrzeug_id IS NULL AND v.ausruestung_typ_id IS NULL AND v.ausruestung_id IS NULL)
)
ORDER BY v.name ASC`,
[ausruestungId]
);
// 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.getTemplatesForEquipment failed', { error, ausruestungId });
throw new Error('Checklisten für Ausrüstung konnten nicht geladen werden');
}
}
// ---------------------------------------------------------------------------
// Overview Items (vehicles + equipment with open checklists)
// ---------------------------------------------------------------------------
async function getOverviewItems() {
try {
// Vehicles with overdue or upcoming checklists (within 7 days)
const vehiclesResult = await pool.query(`
SELECT f.id, f.bezeichnung AS name, f.kurzname,
json_agg(json_build_object(
'vorlage_id', cf.vorlage_id,
'vorlage_name', v.name,
'next_due', cf.naechste_faellig_am
) ORDER BY cf.naechste_faellig_am ASC) AS checklists
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.fahrzeug_id IS NOT NULL
AND cf.naechste_faellig_am <= CURRENT_DATE + INTERVAL '7 days'
GROUP BY f.id, f.bezeichnung, f.kurzname
ORDER BY MIN(cf.naechste_faellig_am) ASC
`);
// Equipment with overdue or upcoming checklists (within 7 days)
const equipmentResult = await pool.query(`
SELECT a.id, a.name,
json_agg(json_build_object(
'vorlage_id', cf.vorlage_id,
'vorlage_name', v.name,
'next_due', cf.naechste_faellig_am
) ORDER BY cf.naechste_faellig_am ASC) AS checklists
FROM checklist_faelligkeit cf
JOIN ausruestung a ON a.id = cf.ausruestung_id
JOIN checklist_vorlagen v ON v.id = cf.vorlage_id AND v.aktiv = true
WHERE cf.ausruestung_id IS NOT NULL
AND cf.naechste_faellig_am <= CURRENT_DATE + INTERVAL '7 days'
GROUP BY a.id, a.name
ORDER BY MIN(cf.naechste_faellig_am) ASC
`);
return {
vehicles: vehiclesResult.rows,
equipment: equipmentResult.rows,
};
} catch (error) {
logger.error('ChecklistService.getOverviewItems failed', { error });
throw new Error('Übersichtsdaten konnten nicht geladen werden');
}
}
// ---------------------------------------------------------------------------
// Ausführungen (Executions)
// ---------------------------------------------------------------------------
async function startExecution(fahrzeugId: string, vorlageId: number, userId: string) {
async function startExecution(fahrzeugId: string | null, vorlageId: number, userId: string, ausruestungId?: string | null) {
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)
`INSERT INTO checklist_ausfuehrungen (fahrzeug_id, ausruestung_id, vorlage_id, ausgefuehrt_von)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[fahrzeugId, vorlageId, userId]
[fahrzeugId || null, ausruestungId || null, vorlageId, userId]
);
const execution = execResult.rows[0];
@@ -398,17 +597,31 @@ async function startExecution(fahrzeugId: string, vorlageId: number, userId: str
);
}
// 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]
// Copy entity-specific items (vehicle or equipment)
if (ausruestungId) {
const equipmentItems = await client.query(
`SELECT * FROM ausruestung_checklist_items WHERE ausruestung_id = $1 AND aktiv = true ORDER BY sort_order ASC, id ASC`,
[ausruestungId]
);
for (const item of equipmentItems.rows) {
await client.query(
`INSERT INTO checklist_ausfuehrung_items (ausfuehrung_id, ausruestung_item_id, bezeichnung)
VALUES ($1, $2, $3)`,
[execution.id, item.id, item.bezeichnung]
);
}
} else if (fahrzeugId) {
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');
@@ -429,11 +642,13 @@ async function getExecutionById(id: string) {
const execResult = await pool.query(
`SELECT a.*,
f.bezeichnung AS fahrzeug_name, f.kurzname AS fahrzeug_kurzname,
ar.name AS ausruestung_name,
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 ausruestung ar ON ar.id = a.ausruestung_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
@@ -475,12 +690,13 @@ async function submitExecution(
// 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
`SELECT ai.id, ai.ergebnis, ai.vorlage_item_id, ai.fahrzeug_item_id, ai.ausruestung_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
LEFT JOIN ausruestung_checklist_items aci ON aci.id = ai.ausruestung_item_id
WHERE ai.ausfuehrung_id = $1
AND (COALESCE(vi.pflicht, fi.pflicht, true) = true)`,
AND (COALESCE(vi.pflicht, fi.pflicht, aci.pflicht, true) = true)`,
[id]
);
@@ -494,20 +710,30 @@ async function submitExecution(
// Update checklist_faelligkeit if completed
if (allPflichtOk) {
const exec = await client.query(`SELECT vorlage_id, fahrzeug_id FROM checklist_ausfuehrungen WHERE id = $1`, [id]);
const exec = await client.query(`SELECT vorlage_id, fahrzeug_id, ausruestung_id FROM checklist_ausfuehrungen WHERE id = $1`, [id]);
if (exec.rows.length > 0) {
const { vorlage_id, fahrzeug_id } = exec.rows[0];
const { vorlage_id, fahrzeug_id, ausruestung_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]
);
if (ausruestung_id) {
await client.query(
`INSERT INTO checklist_faelligkeit (ausruestung_id, vorlage_id, naechste_faellig_am, letzte_ausfuehrung_id)
VALUES ($1, $2, $3, $4)
ON CONFLICT (vorlage_id, ausruestung_id) WHERE ausruestung_id IS NOT NULL AND fahrzeug_id IS NULL
DO UPDATE SET naechste_faellig_am = $3, letzte_ausfuehrung_id = $4`,
[ausruestung_id, vorlage_id, nextDue, id]
);
} else if (fahrzeug_id) {
await client.query(
`INSERT INTO checklist_faelligkeit (fahrzeug_id, vorlage_id, naechste_faellig_am, letzte_ausfuehrung_id)
VALUES ($1, $2, $3, $4)
ON CONFLICT (vorlage_id, fahrzeug_id) WHERE fahrzeug_id IS NOT NULL AND ausruestung_id IS NULL
DO UPDATE SET naechste_faellig_am = $3, letzte_ausfuehrung_id = $4`,
[fahrzeug_id, vorlage_id, nextDue, id]
);
}
}
}
}
@@ -541,7 +767,7 @@ async function approveExecution(id: string, userId: string) {
}
}
async function getExecutions(filter?: { fahrzeugId?: string; vorlageId?: number; status?: string }) {
async function getExecutions(filter?: { fahrzeugId?: string; ausruestungId?: string; vorlageId?: number; status?: string }) {
try {
const conditions: string[] = [];
const values: any[] = [];
@@ -552,6 +778,11 @@ async function getExecutions(filter?: { fahrzeugId?: string; vorlageId?: number;
values.push(filter.fahrzeugId);
idx++;
}
if (filter?.ausruestungId) {
conditions.push(`a.ausruestung_id = $${idx}`);
values.push(filter.ausruestungId);
idx++;
}
if (filter?.vorlageId) {
conditions.push(`a.vorlage_id = $${idx}`);
values.push(filter.vorlageId);
@@ -567,11 +798,13 @@ async function getExecutions(filter?: { fahrzeugId?: string; vorlageId?: number;
const result = await pool.query(
`SELECT a.*,
f.bezeichnung AS fahrzeug_name, f.kurzname AS fahrzeug_kurzname,
ar.name AS ausruestung_name,
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 ausruestung ar ON ar.id = a.ausruestung_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
@@ -593,12 +826,17 @@ async function getExecutions(filter?: { fahrzeugId?: string; vorlageId?: number;
async function getOverdueChecklists() {
try {
const result = await pool.query(`
SELECT cf.*, f.bezeichnung AS fahrzeug_name, f.kurzname AS fahrzeug_kurzname,
SELECT cf.*,
f.bezeichnung AS fahrzeug_name, f.kurzname AS fahrzeug_kurzname,
ar.name AS ausruestung_name,
v.name AS vorlage_name
FROM checklist_faelligkeit cf
JOIN fahrzeuge f ON f.id = cf.fahrzeug_id AND f.deleted_at IS NULL
LEFT JOIN fahrzeuge f ON f.id = cf.fahrzeug_id AND f.deleted_at IS NULL
LEFT JOIN ausruestung ar ON ar.id = cf.ausruestung_id
JOIN checklist_vorlagen v ON v.id = cf.vorlage_id AND v.aktiv = true
WHERE cf.naechste_faellig_am <= CURRENT_DATE
AND (cf.fahrzeug_id IS NOT NULL OR cf.ausruestung_id IS NOT NULL)
AND (cf.fahrzeug_id IS NULL OR f.id IS NOT NULL)
ORDER BY cf.naechste_faellig_am ASC
`);
return result.rows;
@@ -639,7 +877,13 @@ export default {
addVehicleItem,
updateVehicleItem,
deleteVehicleItem,
getEquipmentItems,
addEquipmentItem,
updateEquipmentItem,
deleteEquipmentItem,
getTemplatesForVehicle,
getTemplatesForEquipment,
getOverviewItems,
startExecution,
getExecutionById,
submitExecution,