Add: Lizenzpreise, Rollen-Picker, Audit-Log Fix, Ticket Benutzerfilter
This commit is contained in:
@@ -15,6 +15,8 @@ const {
|
||||
getConditionalAccessPolicies,
|
||||
invalidateUserSessions,
|
||||
} = require('../services/graph.service');
|
||||
const Database = require('better-sqlite3');
|
||||
const DB_PATH = process.env.DATABASE_PATH || './database.sqlite';
|
||||
|
||||
/**
|
||||
* GET /api/entra/users
|
||||
@@ -154,7 +156,28 @@ exports.getUserLicenses = async (req, res) => {
|
||||
]);
|
||||
const skuMap = {};
|
||||
for (const s of skus) skuMap[s.skuId] = s;
|
||||
const enriched = licenses.map(l => ({ ...l, skuInfo: skuMap[l.skuId] || null }));
|
||||
|
||||
// Preise aus license_prices Tabelle laden
|
||||
let priceRows = [];
|
||||
try {
|
||||
const db = new Database(DB_PATH);
|
||||
priceRows = db.prepare('SELECT sku_part_number, display_name, price_per_month FROM license_prices').all();
|
||||
db.close();
|
||||
} catch (_) {}
|
||||
|
||||
const enriched = licenses.map(l => {
|
||||
const skuPart = l.skuPartNumber || '';
|
||||
const skuPartUpper = skuPart.toUpperCase();
|
||||
// Exact match zuerst, dann partial match
|
||||
const priceRow = priceRows.find(p => p.sku_part_number.toUpperCase() === skuPartUpper)
|
||||
|| priceRows.find(p => skuPartUpper.includes(p.sku_part_number.toUpperCase()) || p.sku_part_number.toUpperCase().includes(skuPartUpper));
|
||||
return {
|
||||
...l,
|
||||
skuInfo: skuMap[l.skuId] || null,
|
||||
price_per_month: priceRow?.price_per_month ?? null,
|
||||
price_display_name: priceRow?.display_name ?? null,
|
||||
};
|
||||
});
|
||||
res.json({ success: true, data: enriched });
|
||||
} catch (e) {
|
||||
res.status(500).json({ success: false, message: e.message });
|
||||
|
||||
@@ -509,6 +509,37 @@ async function initializeDatabase() {
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_cron_logs_job ON cron_logs(job_name, started_at)`,
|
||||
`ALTER TABLE users ADD COLUMN avatar_url TEXT`,
|
||||
// Lizenzpreise
|
||||
`CREATE TABLE IF NOT EXISTS license_prices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sku_part_number TEXT UNIQUE NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
price_per_month REAL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('SPE_E3', 'Microsoft 365 E3', 36.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('SPE_E5', 'Microsoft 365 E5', 57.20)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('O365_BUSINESS_PREMIUM', 'Microsoft 365 Business Premium', 22.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('O365_BUSINESS_ESSENTIALS', 'Microsoft 365 Business Basic', 6.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('O365_BUSINESS', 'Microsoft 365 Apps for Business', 8.80)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('ENTERPRISEPREMIUM', 'Office 365 E3', 23.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('ENTERPRISEPACK', 'Office 365 E3', 23.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('MCOEV', 'Microsoft Teams Phone', 8.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('FLOW_FREE', 'Power Automate Free', 0.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('POWER_BI_PRO', 'Power BI Pro', 10.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('POWER_BI_STANDARD', 'Power BI Free', 0.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('INTUNE_A', 'Microsoft Intune', 8.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('AAD_PREMIUM', 'Azure AD Premium P1', 6.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('AAD_PREMIUM_P2', 'Azure AD Premium P2', 9.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('EMS', 'Enterprise Mobility + Security E3', 8.80)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('EMSPREMIUM', 'Enterprise Mobility + Security E5', 14.80)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('VISIOCLIENT', 'Visio Plan 2', 28.10)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('PROJECTCLIENT', 'Project Plan 3', 30.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('WINDOWS_STORE', 'Windows Store', 0.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('MCOPSTN1', 'Teams Domestic Calling', 8.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('Remote_', 'Remote Desktop Services', 0.00)`,
|
||||
`INSERT OR IGNORE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES ('SPB', 'Microsoft 365 Business Premium', 22.00)`,
|
||||
];
|
||||
for (const migration of migrations) {
|
||||
try {
|
||||
|
||||
49
backend/src/routes/licensePrice.routes.js
Normal file
49
backend/src/routes/licensePrice.routes.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
const { requireAdmin } = require('../middleware/roleCheck');
|
||||
const { asyncHandler } = require('../middleware/errorHandler');
|
||||
const Database = require('better-sqlite3');
|
||||
const DB_PATH = process.env.DATABASE_PATH || './database.sqlite';
|
||||
|
||||
router.use(authenticateToken);
|
||||
|
||||
// GET alle Preise
|
||||
router.get('/', asyncHandler(async (req, res) => {
|
||||
const db = new Database(DB_PATH);
|
||||
const prices = db.prepare('SELECT * FROM license_prices ORDER BY display_name').all();
|
||||
db.close();
|
||||
res.json({ status: 'success', data: prices });
|
||||
}));
|
||||
|
||||
// PUT Preis aktualisieren
|
||||
router.put('/:id', requireAdmin, asyncHandler(async (req, res) => {
|
||||
const { price_per_month, display_name } = req.body;
|
||||
const db = new Database(DB_PATH);
|
||||
db.prepare('UPDATE license_prices SET price_per_month=?, display_name=?, updated_at=CURRENT_TIMESTAMP WHERE id=?')
|
||||
.run(parseFloat(price_per_month) || 0, display_name, parseInt(req.params.id));
|
||||
const updated = db.prepare('SELECT * FROM license_prices WHERE id=?').get(parseInt(req.params.id));
|
||||
db.close();
|
||||
res.json({ status: 'success', data: updated });
|
||||
}));
|
||||
|
||||
// POST neuer Preis
|
||||
router.post('/', requireAdmin, asyncHandler(async (req, res) => {
|
||||
const { sku_part_number, display_name, price_per_month } = req.body;
|
||||
const db = new Database(DB_PATH);
|
||||
const result = db.prepare('INSERT OR REPLACE INTO license_prices (sku_part_number, display_name, price_per_month) VALUES (?,?,?)')
|
||||
.run(sku_part_number, display_name, parseFloat(price_per_month) || 0);
|
||||
const created = db.prepare('SELECT * FROM license_prices WHERE id=?').get(result.lastInsertRowid);
|
||||
db.close();
|
||||
res.json({ status: 'success', data: created });
|
||||
}));
|
||||
|
||||
// DELETE
|
||||
router.delete('/:id', requireAdmin, asyncHandler(async (req, res) => {
|
||||
const db = new Database(DB_PATH);
|
||||
db.prepare('DELETE FROM license_prices WHERE id=?').run(parseInt(req.params.id));
|
||||
db.close();
|
||||
res.json({ status: 'success' });
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
@@ -179,6 +179,8 @@ const feedbackRoutes = require('./routes/feedback.routes');
|
||||
app.use('/api/feedback', feedbackRoutes);
|
||||
const cronRoutes = require('./routes/cron.routes');
|
||||
app.use('/api/cron', cronRoutes);
|
||||
const licensePriceRoutes = require('./routes/licensePrice.routes');
|
||||
app.use('/api/license-prices', licensePriceRoutes);
|
||||
|
||||
// Static file serving for uploads (PDFs)
|
||||
const path = require('path');
|
||||
|
||||
Reference in New Issue
Block a user