const WarehouseLocation = require('../models/WarehouseLocation'); const AssetMovement = require('../models/AssetMovement'); const StockThreshold = require('../models/StockThreshold'); const PurchaseOrder = require('../models/PurchaseOrder'); const Asset = require('../models/Asset'); const { getDatabase } = require('../config/database'); // ─── Locations ──────────────────────────────────────────────────────────────── exports.getLocations = (req, res) => { res.json(WarehouseLocation.getAll()); }; exports.createLocation = (req, res) => { const { name, description } = req.body; if (!name) return res.status(400).json({ error: 'Name ist erforderlich' }); try { const loc = WarehouseLocation.create({ name, description }); res.status(201).json(loc); } catch (e) { if (e.message?.includes('UNIQUE')) return res.status(409).json({ error: 'Lagerort existiert bereits' }); res.status(500).json({ error: e.message }); } }; exports.updateLocation = (req, res) => { const loc = WarehouseLocation.update(parseInt(req.params.id), req.body); if (!loc) return res.status(404).json({ error: 'Nicht gefunden' }); res.json(loc); }; exports.deleteLocation = (req, res) => { const ok = WarehouseLocation.delete(parseInt(req.params.id)); if (!ok) return res.status(404).json({ error: 'Nicht gefunden' }); res.json({ success: true }); }; // ─── Movements ──────────────────────────────────────────────────────────────── exports.getMovements = (req, res) => { const asset_id = req.query.asset_id ? parseInt(req.query.asset_id) : undefined; res.json(AssetMovement.getAll({ asset_id, limit: 500 })); }; exports.createMovement = (req, res) => { const { asset_id, type, to_location_id, assigned_user_id, ticket_id, reason, notes, new_status } = req.body; if (!asset_id || !type) return res.status(400).json({ error: 'asset_id und type sind erforderlich' }); const asset = Asset.getById(parseInt(asset_id)); if (!asset) return res.status(404).json({ error: 'Asset nicht gefunden' }); const db = getDatabase(); const updateAsset = db.transaction(() => { // Create movement record const movement = AssetMovement.create({ asset_id, type, from_location_id: asset.location_id, to_location_id: to_location_id || null, assigned_user_id: assigned_user_id || null, ticket_id: ticket_id || null, reason: reason || '', notes: notes || '', performed_by: req.user.id, }); // Update asset location + status if needed const updates = {}; if (to_location_id) updates.location_id = to_location_id; if (new_status) updates.status = new_status; if (assigned_user_id) { updates.assigned_to_user_id = assigned_user_id; updates.status = 'zugewiesen'; } if (type === 'in') { updates.status = 'verfuegbar'; updates.assigned_to_user_id = null; } if (Object.keys(updates).length > 0) { Asset.update(asset_id, updates, req.user.id); } return movement; }); try { const movement = updateAsset(); res.status(201).json(movement); } catch (e) { res.status(500).json({ error: e.message }); } }; // ─── Stock Thresholds ───────────────────────────────────────────────────────── exports.getThresholds = (req, res) => { res.json(StockThreshold.getAll()); }; exports.upsertThreshold = (req, res) => { const { category, min_stock, notify_email } = req.body; if (!category) return res.status(400).json({ error: 'Kategorie ist erforderlich' }); const threshold = StockThreshold.upsert({ category, min_stock, notify_email }); res.json(threshold); }; exports.deleteThreshold = (req, res) => { const ok = StockThreshold.delete(req.params.category); if (!ok) return res.status(404).json({ error: 'Nicht gefunden' }); res.json({ success: true }); }; exports.getStockViolations = (req, res) => { res.json(StockThreshold.getViolations()); }; // ─── Purchase Orders ────────────────────────────────────────────────────────── exports.getPurchaseOrders = (req, res) => { const { status } = req.query; res.json(PurchaseOrder.getAll({ status })); }; exports.createPurchaseOrder = (req, res) => { const { category, item_name, quantity, notes } = req.body; if (!category || !item_name) return res.status(400).json({ error: 'Kategorie und Artikelname sind erforderlich' }); const order = PurchaseOrder.create({ category, item_name, quantity, notes, created_by: req.user.id }); res.status(201).json(order); }; exports.updatePurchaseOrder = (req, res) => { const order = PurchaseOrder.update(parseInt(req.params.id), req.body); if (!order) return res.status(404).json({ error: 'Nicht gefunden' }); res.json(order); }; exports.deletePurchaseOrder = (req, res) => { const ok = PurchaseOrder.delete(parseInt(req.params.id)); if (!ok) return res.status(404).json({ error: 'Nicht gefunden' }); res.json({ success: true }); }; // ─── Dashboard Summary ──────────────────────────────────────────────────────── exports.getSummary = (req, res) => { const db = getDatabase(); const stockByLocation = db.prepare(` SELECT wl.name AS location, COUNT(a.id) AS count FROM warehouse_locations wl LEFT JOIN assets a ON a.location_id = wl.id AND a.status = 'verfuegbar' GROUP BY wl.id ORDER BY wl.name `).all(); const stockByCategory = db.prepare(` SELECT a.type AS category, a.status, COUNT(*) AS count FROM assets a GROUP BY a.type, a.status ORDER BY a.type, a.status `).all(); const violations = StockThreshold.getViolations(); const openOrders = PurchaseOrder.getOpenCount(); const recentMovements = AssetMovement.getAll({ limit: 10 }); res.json({ stockByLocation, stockByCategory, violations, openOrders, recentMovements }); }; // ─── QR Code ────────────────────────────────────────────────────────────────── exports.getQrCode = async (req, res) => { const asset = Asset.getById(parseInt(req.params.id)); if (!asset) return res.status(404).json({ error: 'Asset nicht gefunden' }); const QRCode = require('qrcode'); const url = `${process.env.FRONTEND_URL || 'https://it-nexus.cereda-systems.de'}/assets?scan=${asset.id}`; const svg = await QRCode.toString(url, { type: 'svg', width: 200, margin: 1 }); res.setHeader('Content-Type', 'image/svg+xml'); res.send(svg); };