new features

This commit is contained in:
Matthias Hochmeister
2026-03-23 14:01:39 +01:00
parent d2dc64d54a
commit 3326156b15
35 changed files with 1341 additions and 257 deletions

View File

@@ -35,13 +35,13 @@ async function getVendorById(id: number) {
}
}
async function createVendor(data: { name: string; kontakt_person?: string; email?: string; telefon?: string; adresse?: string; notizen?: string }, userId: string) {
async function createVendor(data: { name: string; kontakt_name?: string; email?: string; telefon?: string; adresse?: string; website?: string; notizen?: string }, userId: string) {
try {
const result = await pool.query(
`INSERT INTO lieferanten (name, kontakt_person, email, telefon, adresse, notizen, erstellt_von)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`INSERT INTO lieferanten (name, kontakt_name, email, telefon, adresse, website, notizen, erstellt_von)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *`,
[data.name, data.kontakt_person || null, data.email || null, data.telefon || null, data.adresse || null, data.notizen || null, userId]
[data.name, data.kontakt_name || null, data.email || null, data.telefon || null, data.adresse || null, data.website || null, data.notizen || null, userId]
);
return result.rows[0];
} catch (error) {
@@ -50,20 +50,21 @@ async function createVendor(data: { name: string; kontakt_person?: string; email
}
}
async function updateVendor(id: number, data: { name?: string; kontakt_person?: string; email?: string; telefon?: string; adresse?: string; notizen?: string }, userId: string) {
async function updateVendor(id: number, data: { name?: string; kontakt_name?: string; email?: string; telefon?: string; adresse?: string; website?: string; notizen?: string }, userId: string) {
try {
const result = await pool.query(
`UPDATE lieferanten
SET name = COALESCE($1, name),
kontakt_person = COALESCE($2, kontakt_person),
kontakt_name = COALESCE($2, kontakt_name),
email = COALESCE($3, email),
telefon = COALESCE($4, telefon),
adresse = COALESCE($5, adresse),
notizen = COALESCE($6, notizen),
website = COALESCE($6, website),
notizen = COALESCE($7, notizen),
aktualisiert_am = NOW()
WHERE id = $7
WHERE id = $8
RETURNING *`,
[data.name, data.kontakt_person, data.email, data.telefon, data.adresse, data.notizen, id]
[data.name, data.kontakt_name, data.email, data.telefon, data.adresse, data.website, data.notizen, id]
);
if (result.rows.length === 0) return null;
@@ -157,7 +158,7 @@ async function getOrderById(id: number) {
pool.query(`SELECT * FROM bestellpositionen WHERE bestellung_id = $1 ORDER BY id`, [id]),
pool.query(`SELECT * FROM bestellung_dateien WHERE bestellung_id = $1 ORDER BY hochgeladen_am DESC`, [id]),
pool.query(`SELECT * FROM bestellung_erinnerungen WHERE bestellung_id = $1 ORDER BY faellig_am`, [id]),
pool.query(`SELECT h.*, u.display_name AS benutzer_name FROM bestellung_historie h LEFT JOIN users u ON u.id = h.benutzer_id WHERE h.bestellung_id = $1 ORDER BY h.erstellt_am DESC`, [id]),
pool.query(`SELECT h.*, u.display_name AS benutzer_name FROM bestellung_historie h LEFT JOIN users u ON u.id = h.erstellt_von WHERE h.bestellung_id = $1 ORDER BY h.erstellt_am DESC`, [id]),
]);
return {
@@ -173,16 +174,16 @@ async function getOrderById(id: number) {
}
}
async function createOrder(data: { titel: string; lieferant_id?: number; beschreibung?: string; prioritaet?: string }, userId: string) {
async function createOrder(data: { bezeichnung: string; lieferant_id?: number; notizen?: string; budget?: number }, userId: string) {
try {
const result = await pool.query(
`INSERT INTO bestellungen (titel, lieferant_id, beschreibung, prioritaet, erstellt_von)
`INSERT INTO bestellungen (bezeichnung, lieferant_id, notizen, budget, erstellt_von)
VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[data.titel, data.lieferant_id || null, data.beschreibung || null, data.prioritaet || 'normal', userId]
[data.bezeichnung, data.lieferant_id || null, data.notizen || null, data.budget || null, userId]
);
const order = result.rows[0];
await logAction(order.id, 'Bestellung erstellt', `Bestellung "${data.titel}" erstellt`, userId);
await logAction(order.id, 'Bestellung erstellt', `Bestellung "${data.bezeichnung}" erstellt`, userId);
return order;
} catch (error) {
logger.error('BestellungService.createOrder failed', { error });
@@ -190,7 +191,7 @@ async function createOrder(data: { titel: string; lieferant_id?: number; beschre
}
}
async function updateOrder(id: number, data: { titel?: string; lieferant_id?: number; beschreibung?: string; prioritaet?: string; status?: string }, userId: string) {
async function updateOrder(id: number, data: { bezeichnung?: string; lieferant_id?: number; notizen?: string; budget?: number; status?: string }, userId: string) {
try {
// Check current order for status change detection
const current = await pool.query(`SELECT * FROM bestellungen WHERE id = $1`, [id]);
@@ -213,25 +214,25 @@ async function updateOrder(id: number, data: { titel?: string; lieferant_id?: nu
const result = await pool.query(
`UPDATE bestellungen
SET titel = COALESCE($1, titel),
SET bezeichnung = COALESCE($1, bezeichnung),
lieferant_id = COALESCE($2, lieferant_id),
beschreibung = COALESCE($3, beschreibung),
prioritaet = COALESCE($4, prioritaet),
notizen = COALESCE($3, notizen),
budget = COALESCE($4, budget),
status = COALESCE($5, status),
bestellt_am = $6,
abgeschlossen_am = $7,
aktualisiert_am = NOW()
WHERE id = $8
RETURNING *`,
[data.titel, data.lieferant_id, data.beschreibung, data.prioritaet, data.status, bestellt_am, abgeschlossen_am, id]
[data.bezeichnung, data.lieferant_id, data.notizen, data.budget, data.status, bestellt_am, abgeschlossen_am, id]
);
if (result.rows.length === 0) return null;
const changes: string[] = [];
if (data.titel) changes.push(`Titel geändert`);
if (data.bezeichnung) changes.push(`Bezeichnung geändert`);
if (data.lieferant_id) changes.push(`Lieferant geändert`);
if (data.status && data.status !== oldStatus) changes.push(`Status: ${oldStatus}${data.status}`);
if (data.prioritaet) changes.push(`Priorität geändert`);
if (data.budget) changes.push(`Budget geändert`);
await logAction(id, 'Bestellung aktualisiert', changes.join(', ') || 'Bestellung bearbeitet', userId);
return result.rows[0];
@@ -275,12 +276,12 @@ async function deleteOrder(id: number, _userId: string) {
}
const VALID_STATUS_TRANSITIONS: Record<string, string[]> = {
entwurf: ['bestellt', 'storniert'],
bestellt: ['teillieferung', 'vollstaendig', 'storniert'],
teillieferung: ['vollstaendig', 'storniert'],
entwurf: ['erstellt', 'bestellt'],
erstellt: ['bestellt'],
bestellt: ['teillieferung', 'vollstaendig'],
teillieferung: ['vollstaendig'],
vollstaendig: ['abgeschlossen'],
abgeschlossen: [],
storniert: ['entwurf'],
};
async function updateOrderStatus(id: number, status: string, userId: string) {
@@ -323,15 +324,15 @@ async function updateOrderStatus(id: number, status: string, userId: string) {
// Line Items (Bestellpositionen)
// ---------------------------------------------------------------------------
async function addLineItem(bestellungId: number, data: { artikel: string; menge: number; einheit?: string; einzelpreis?: number; notizen?: string }, userId: string) {
async function addLineItem(bestellungId: number, data: { bezeichnung: string; artikelnummer?: string; menge: number; einheit?: string; einzelpreis?: number; notizen?: string }, userId: string) {
try {
const result = await pool.query(
`INSERT INTO bestellpositionen (bestellung_id, artikel, menge, einheit, einzelpreis, notizen)
VALUES ($1, $2, $3, $4, $5, $6)
`INSERT INTO bestellpositionen (bestellung_id, bezeichnung, artikelnummer, menge, einheit, einzelpreis, notizen)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[bestellungId, data.artikel, data.menge, data.einheit || 'Stück', data.einzelpreis || 0, data.notizen || null]
[bestellungId, data.bezeichnung, data.artikelnummer || null, data.menge, data.einheit || 'Stk', data.einzelpreis || 0, data.notizen || null]
);
await logAction(bestellungId, 'Position hinzugefügt', `"${data.artikel}" x${data.menge}`, userId);
await logAction(bestellungId, 'Position hinzugefügt', `"${data.bezeichnung}" x${data.menge}`, userId);
return result.rows[0];
} catch (error) {
logger.error('BestellungService.addLineItem failed', { error, bestellungId });
@@ -339,23 +340,24 @@ async function addLineItem(bestellungId: number, data: { artikel: string; menge:
}
}
async function updateLineItem(id: number, data: { artikel?: string; menge?: number; einheit?: string; einzelpreis?: number; notizen?: string }, userId: string) {
async function updateLineItem(id: number, data: { bezeichnung?: string; artikelnummer?: string; menge?: number; einheit?: string; einzelpreis?: number; notizen?: string }, userId: string) {
try {
const result = await pool.query(
`UPDATE bestellpositionen
SET artikel = COALESCE($1, artikel),
menge = COALESCE($2, menge),
einheit = COALESCE($3, einheit),
einzelpreis = COALESCE($4, einzelpreis),
notizen = COALESCE($5, notizen)
WHERE id = $6
SET bezeichnung = COALESCE($1, bezeichnung),
artikelnummer = COALESCE($2, artikelnummer),
menge = COALESCE($3, menge),
einheit = COALESCE($4, einheit),
einzelpreis = COALESCE($5, einzelpreis),
notizen = COALESCE($6, notizen)
WHERE id = $7
RETURNING *`,
[data.artikel, data.menge, data.einheit, data.einzelpreis, data.notizen, id]
[data.bezeichnung, data.artikelnummer, data.menge, data.einheit, data.einzelpreis, data.notizen, id]
);
if (result.rows.length === 0) return null;
const item = result.rows[0];
await logAction(item.bestellung_id, 'Position aktualisiert', `"${item.artikel}" bearbeitet`, userId);
await logAction(item.bestellung_id, 'Position aktualisiert', `"${item.bezeichnung}" bearbeitet`, userId);
return item;
} catch (error) {
logger.error('BestellungService.updateLineItem failed', { error, id });
@@ -369,7 +371,7 @@ async function deleteLineItem(id: number, userId: string) {
if (item.rows.length === 0) return false;
await pool.query(`DELETE FROM bestellpositionen WHERE id = $1`, [id]);
await logAction(item.rows[0].bestellung_id, 'Position entfernt', `"${item.rows[0].artikel}" entfernt`, userId);
await logAction(item.rows[0].bestellung_id, 'Position entfernt', `"${item.rows[0].bezeichnung}" entfernt`, userId);
return true;
} catch (error) {
logger.error('BestellungService.deleteLineItem failed', { error, id });
@@ -386,7 +388,7 @@ async function updateReceivedQuantity(id: number, menge: number, userId: string)
if (result.rows.length === 0) return null;
const item = result.rows[0];
await logAction(item.bestellung_id, 'Liefermenge aktualisiert', `"${item.artikel}": ${menge} von ${item.menge} erhalten`, userId);
await logAction(item.bestellung_id, 'Liefermenge aktualisiert', `"${item.bezeichnung}": ${menge} von ${item.menge} erhalten`, userId);
// Check if all items for this order are fully received
const allItems = await pool.query(
@@ -477,15 +479,15 @@ async function getFilesByOrder(bestellungId: number) {
// Reminders (Bestellung Erinnerungen)
// ---------------------------------------------------------------------------
async function addReminder(bestellungId: number, data: { titel: string; faellig_am: string; notizen?: string }, userId: string) {
async function addReminder(bestellungId: number, data: { nachricht: string; faellig_am: string }, userId: string) {
try {
const result = await pool.query(
`INSERT INTO bestellung_erinnerungen (bestellung_id, titel, faellig_am, notizen, erstellt_von)
VALUES ($1, $2, $3, $4, $5)
`INSERT INTO bestellung_erinnerungen (bestellung_id, faellig_am, nachricht, erstellt_von)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[bestellungId, data.titel, data.faellig_am, data.notizen || null, userId]
[bestellungId, data.faellig_am, data.nachricht || null, userId]
);
await logAction(bestellungId, 'Erinnerung erstellt', `"${data.titel}" fällig am ${data.faellig_am}`, userId);
await logAction(bestellungId, 'Erinnerung erstellt', `Erinnerung fällig am ${data.faellig_am}`, userId);
return result.rows[0];
} catch (error) {
logger.error('BestellungService.addReminder failed', { error, bestellungId });
@@ -502,7 +504,7 @@ async function markReminderDone(id: number, userId: string) {
if (result.rows.length === 0) return null;
const reminder = result.rows[0];
await logAction(reminder.bestellung_id, 'Erinnerung erledigt', `"${reminder.titel}"`, userId);
await logAction(reminder.bestellung_id, 'Erinnerung erledigt', `Erinnerung #${reminder.id}`, userId);
return reminder;
} catch (error) {
logger.error('BestellungService.markReminderDone failed', { error, id });
@@ -526,7 +528,7 @@ async function deleteReminder(id: number) {
async function getDueReminders() {
try {
const result = await pool.query(
`SELECT e.*, b.titel AS bestellung_titel, b.erstellt_von AS besteller_id
`SELECT e.*, b.bezeichnung AS bestellung_bezeichnung, b.erstellt_von AS besteller_id
FROM bestellung_erinnerungen e
JOIN bestellungen b ON b.id = e.bestellung_id
WHERE e.faellig_am <= NOW() AND e.erledigt = FALSE
@@ -546,9 +548,9 @@ async function getDueReminders() {
async function logAction(bestellungId: number, aktion: string, details: string, userId: string) {
try {
await pool.query(
`INSERT INTO bestellung_historie (bestellung_id, benutzer_id, aktion, details)
VALUES ($1, $2, $3, $4)`,
[bestellungId, userId, aktion, details]
`INSERT INTO bestellung_historie (bestellung_id, erstellt_von, aktion, details)
VALUES ($1, $2, $3, $4::jsonb)`,
[bestellungId, userId, aktion, JSON.stringify({ text: details })]
);
} catch (error) {
logger.error('BestellungService.logAction failed', { error, bestellungId, aktion });
@@ -561,7 +563,7 @@ async function getHistory(bestellungId: number) {
const result = await pool.query(
`SELECT h.*, u.display_name AS benutzer_name
FROM bestellung_historie h
LEFT JOIN users u ON u.id = h.benutzer_id
LEFT JOIN users u ON u.id = h.erstellt_von
WHERE h.bestellung_id = $1
ORDER BY h.erstellt_am DESC`,
[bestellungId]