update
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
import pool from '../config/database';
|
||||
import logger from '../utils/logger';
|
||||
import fs from 'fs';
|
||||
import notificationService from './notification.service';
|
||||
import { permissionService } from './permission.service';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vendors (Lieferanten)
|
||||
@@ -318,20 +320,22 @@ async function deleteOrder(id: number, _userId: string) {
|
||||
}
|
||||
|
||||
const VALID_STATUS_TRANSITIONS: Record<string, string[]> = {
|
||||
entwurf: ['erstellt', 'bestellt'],
|
||||
erstellt: ['bestellt'],
|
||||
bestellt: ['teillieferung', 'vollstaendig'],
|
||||
teillieferung: ['vollstaendig'],
|
||||
vollstaendig: ['abgeschlossen'],
|
||||
abgeschlossen: [],
|
||||
entwurf: ['wartet_auf_genehmigung'],
|
||||
wartet_auf_genehmigung: ['bereit_zur_bestellung', 'entwurf'],
|
||||
bereit_zur_bestellung: ['bestellt'],
|
||||
bestellt: ['teillieferung', 'lieferung_pruefen'],
|
||||
teillieferung: ['lieferung_pruefen'],
|
||||
lieferung_pruefen: ['abgeschlossen'],
|
||||
abgeschlossen: [],
|
||||
};
|
||||
|
||||
async function updateOrderStatus(id: number, status: string, userId: string, force?: boolean) {
|
||||
async function updateOrderStatus(id: number, status: string, userId: string, force?: boolean, approverUserId?: string) {
|
||||
try {
|
||||
const current = await pool.query(`SELECT status FROM bestellungen WHERE id = $1`, [id]);
|
||||
const current = await pool.query(`SELECT * FROM bestellungen WHERE id = $1`, [id]);
|
||||
if (current.rows.length === 0) return null;
|
||||
|
||||
const oldStatus = current.rows[0].status;
|
||||
const order = current.rows[0];
|
||||
const oldStatus = order.status;
|
||||
if (!force) {
|
||||
const allowed = VALID_STATUS_TRANSITIONS[oldStatus] || [];
|
||||
if (!allowed.includes(status)) {
|
||||
@@ -349,6 +353,12 @@ async function updateOrderStatus(id: number, status: string, userId: string, for
|
||||
if (status === 'abgeschlossen') {
|
||||
updates.push(`abgeschlossen_am = COALESCE(abgeschlossen_am, NOW())`);
|
||||
}
|
||||
if (status === 'bereit_zur_bestellung' && approverUserId) {
|
||||
updates.push(`genehmigt_von = $${paramIndex}`);
|
||||
params.push(approverUserId);
|
||||
paramIndex++;
|
||||
updates.push(`genehmigt_am = NOW()`);
|
||||
}
|
||||
|
||||
params.push(id);
|
||||
const result = await pool.query(
|
||||
@@ -357,6 +367,53 @@ async function updateOrderStatus(id: number, status: string, userId: string, for
|
||||
);
|
||||
|
||||
await logAction(id, 'Status geändert', `${oldStatus} → ${status}${force ? ' (manuell)' : ''}`, userId);
|
||||
|
||||
// Notifications based on transition
|
||||
const orderLabel = order.laufende_nummer ? `#${order.laufende_nummer}` : `#${order.id}`;
|
||||
const bestellerId = order.besteller_id || order.erstellt_von;
|
||||
|
||||
if (status === 'wartet_auf_genehmigung') {
|
||||
try {
|
||||
const approvers = await permissionService.getUsersWithPermission('bestellungen:approve');
|
||||
for (const user of approvers) {
|
||||
await notificationService.createNotification({
|
||||
user_id: user.id,
|
||||
typ: 'bestellung',
|
||||
titel: 'Bestellung wartet auf Genehmigung',
|
||||
nachricht: `Bestellung ${orderLabel} wurde zur Genehmigung eingereicht`,
|
||||
schwere: 'info',
|
||||
quell_typ: 'bestellung_genehmigung',
|
||||
quell_id: order.id.toString(),
|
||||
link: `/bestellungen/${id}`,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to notify approvers', { err, orderId: id });
|
||||
}
|
||||
} else if (status === 'bereit_zur_bestellung') {
|
||||
await notificationService.createNotification({
|
||||
user_id: bestellerId,
|
||||
typ: 'bestellung',
|
||||
titel: 'Bestellung genehmigt',
|
||||
nachricht: `Bestellung ${orderLabel} wurde genehmigt und ist bereit zur Bestellung`,
|
||||
schwere: 'info',
|
||||
quell_typ: 'bestellung_genehmigung',
|
||||
quell_id: order.id.toString(),
|
||||
link: `/bestellungen/${id}`,
|
||||
});
|
||||
} else if (status === 'entwurf' && oldStatus === 'wartet_auf_genehmigung') {
|
||||
await notificationService.createNotification({
|
||||
user_id: bestellerId,
|
||||
typ: 'bestellung',
|
||||
titel: 'Bestellung abgelehnt',
|
||||
nachricht: `Bestellung ${orderLabel} wurde abgelehnt und zurück in den Entwurf gesetzt`,
|
||||
schwere: 'warnung',
|
||||
quell_typ: 'bestellung_genehmigung',
|
||||
quell_id: order.id.toString(),
|
||||
link: `/bestellungen/${id}`,
|
||||
});
|
||||
}
|
||||
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('BestellungService.updateOrderStatus failed', { error, id });
|
||||
@@ -368,13 +425,13 @@ async function updateOrderStatus(id: number, status: string, userId: string, for
|
||||
// Line Items (Bestellpositionen)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function addLineItem(bestellungId: number, data: { bezeichnung: string; artikelnummer?: 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; spezifikationen?: string[] }, userId: string) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO bestellpositionen (bestellung_id, bezeichnung, artikelnummer, menge, einheit, einzelpreis, notizen)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
`INSERT INTO bestellpositionen (bestellung_id, bezeichnung, artikelnummer, menge, einheit, einzelpreis, notizen, spezifikationen)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)
|
||||
RETURNING *`,
|
||||
[bestellungId, data.bezeichnung, data.artikelnummer || null, data.menge, data.einheit || 'Stk', data.einzelpreis || 0, data.notizen || null]
|
||||
[bestellungId, data.bezeichnung, data.artikelnummer || null, data.menge, data.einheit || 'Stk', data.einzelpreis || 0, data.notizen || null, JSON.stringify(data.spezifikationen || [])]
|
||||
);
|
||||
await logAction(bestellungId, 'Position hinzugefügt', `"${data.bezeichnung}" x${data.menge}`, userId);
|
||||
return result.rows[0];
|
||||
@@ -384,7 +441,7 @@ async function addLineItem(bestellungId: number, data: { bezeichnung: string; ar
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLineItem(id: number, data: { bezeichnung?: string; artikelnummer?: 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; spezifikationen?: string[] }, userId: string) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE bestellpositionen
|
||||
@@ -393,10 +450,11 @@ async function updateLineItem(id: number, data: { bezeichnung?: string; artikeln
|
||||
menge = COALESCE($3, menge),
|
||||
einheit = COALESCE($4, einheit),
|
||||
einzelpreis = COALESCE($5, einzelpreis),
|
||||
notizen = COALESCE($6, notizen)
|
||||
WHERE id = $7
|
||||
notizen = COALESCE($6, notizen),
|
||||
spezifikationen = COALESCE($7::jsonb, spezifikationen)
|
||||
WHERE id = $8
|
||||
RETURNING *`,
|
||||
[data.bezeichnung, data.artikelnummer, data.menge, data.einheit, data.einzelpreis, data.notizen, id]
|
||||
[data.bezeichnung, data.artikelnummer, data.menge, data.einheit, data.einzelpreis, data.notizen, data.spezifikationen ? JSON.stringify(data.spezifikationen) : null, id]
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
|
||||
@@ -425,6 +483,21 @@ async function deleteLineItem(id: number, userId: string) {
|
||||
|
||||
async function updateReceivedQuantity(id: number, menge: number, userId: string) {
|
||||
try {
|
||||
// First check if the item's order is in a valid status for receiving
|
||||
const itemCheck = await pool.query(
|
||||
`SELECT bp.bestellung_id, b.status
|
||||
FROM bestellpositionen bp
|
||||
JOIN bestellungen b ON b.id = bp.bestellung_id
|
||||
WHERE bp.id = $1`,
|
||||
[id]
|
||||
);
|
||||
if (itemCheck.rows.length > 0) {
|
||||
const orderStatus = itemCheck.rows[0].status;
|
||||
if (orderStatus !== 'bestellt' && orderStatus !== 'teillieferung') {
|
||||
throw Object.assign(new Error('Mengenaktualisierung nur bei Status bestellt oder Teillieferung möglich'), { statusCode: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const result = await pool.query(
|
||||
`UPDATE bestellpositionen SET erhalten_menge = $1 WHERE id = $2 RETURNING *`,
|
||||
[menge, id]
|
||||
@@ -447,10 +520,10 @@ async function updateReceivedQuantity(id: number, menge: number, userId: string)
|
||||
if (order.rows.length > 0 && (order.rows[0].status === 'bestellt' || order.rows[0].status === 'teillieferung')) {
|
||||
if (allReceived) {
|
||||
await pool.query(
|
||||
`UPDATE bestellungen SET status = 'vollstaendig', aktualisiert_am = NOW() WHERE id = $1`,
|
||||
`UPDATE bestellungen SET status = 'lieferung_pruefen', aktualisiert_am = NOW() WHERE id = $1`,
|
||||
[item.bestellung_id]
|
||||
);
|
||||
await logAction(item.bestellung_id, 'Status geändert', 'Alle Positionen vollständig erhalten → vollstaendig', userId);
|
||||
await logAction(item.bestellung_id, 'Status geändert', 'Alle Positionen vollständig erhalten → lieferung_pruefen', userId);
|
||||
} else if (someReceived && order.rows[0].status === 'bestellt') {
|
||||
await pool.query(
|
||||
`UPDATE bestellungen SET status = 'teillieferung', aktualisiert_am = NOW() WHERE id = $1`,
|
||||
@@ -463,7 +536,7 @@ async function updateReceivedQuantity(id: number, menge: number, userId: string)
|
||||
return item;
|
||||
} catch (error) {
|
||||
logger.error('BestellungService.updateReceivedQuantity failed', { error, id });
|
||||
throw new Error('Liefermenge konnte nicht aktualisiert werden');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user