183 lines
7.4 KiB
JavaScript
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);
|
|
};
|