Files
IT-Nexus/backend/src/controllers/warehouse.controller.js

183 lines
7.4 KiB
JavaScript

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);
};