fix: Favicon 502, QR Feld cr/mdcDeviceId Support und 401 Session-Handling
All checks were successful
Build and Deploy / build (push) Successful in 55s
All checks were successful
Build and Deploy / build (push) Successful in 55s
This commit is contained in:
13
posClient.js
13
posClient.js
@@ -54,7 +54,9 @@ async function processQrSetup(qrPayload) {
|
||||
throw new Error('Ungültiger QR-Code-Typ: $type muss "ww-setup" sein');
|
||||
}
|
||||
|
||||
const { url, servicepasslink, mdcDeviceId } = data;
|
||||
const { url, servicepasslink } = data;
|
||||
const mdcDeviceId = data.mdcDeviceId || data.cr || data.cashRegisterNumber || '1';
|
||||
|
||||
if (!url || !servicepasslink) {
|
||||
throw new Error('Fehlende Pflichtfelder (url oder servicepasslink) im QR-Code');
|
||||
}
|
||||
@@ -86,10 +88,10 @@ async function processQrSetup(qrPayload) {
|
||||
upsert.run('pos_url', cleanUrl);
|
||||
upsert.run('pos_pass_id', passId);
|
||||
upsert.run('pos_app_id', appId);
|
||||
if (mdcDeviceId) upsert.run('pos_cash_register', String(mdcDeviceId));
|
||||
upsert.run('pos_cash_register', String(mdcDeviceId));
|
||||
|
||||
db.prepare('INSERT INTO sync_logs (status, message) VALUES (?, ?)')
|
||||
.run('SUCCESS', `QR-Code Handshake erfolgreich. Pass-ID: ${passId}, Kasse: ${mdcDeviceId || '1'}`);
|
||||
.run('SUCCESS', `QR-Code Handshake erfolgreich. Pass-ID: ${passId}, Kasse: ${mdcDeviceId}`);
|
||||
|
||||
// 3. Direkt automatischen Initial-Sync ausführen
|
||||
let syncResult = null;
|
||||
@@ -104,7 +106,7 @@ async function processQrSetup(qrPayload) {
|
||||
url: cleanUrl,
|
||||
passId,
|
||||
appId,
|
||||
mdcDeviceId: mdcDeviceId || '1',
|
||||
mdcDeviceId: String(mdcDeviceId),
|
||||
syncResult
|
||||
};
|
||||
}
|
||||
@@ -231,7 +233,6 @@ async function parseAndStoreZip(buffer) {
|
||||
if (Array.isArray(item.prices) && item.prices.length > 0) {
|
||||
const vk1Obj = item.prices.find(p => String(p.level) === '1') || item.prices[0];
|
||||
if (vk1Obj && vk1Obj.value !== undefined) {
|
||||
// Deutsches Zahlenformat "2,86" -> 2.86
|
||||
preis1 = parseFloat(String(vk1Obj.value).replace(',', '.'));
|
||||
}
|
||||
} else if (typeof item.prices === 'object' && item.prices !== null) {
|
||||
@@ -267,7 +268,6 @@ async function parseAndStoreZip(buffer) {
|
||||
try { parsed = await parser.parseStringPromise(rawContent); } catch(e) {}
|
||||
const root = parsed.root || parsed;
|
||||
|
||||
// Warengruppen
|
||||
let groups = root.ProductGroups?.ProductGroup || root.productGroups?.productGroup || [];
|
||||
if (!Array.isArray(groups)) groups = [groups];
|
||||
for (const g of groups) {
|
||||
@@ -276,7 +276,6 @@ async function parseAndStoreZip(buffer) {
|
||||
if (id) { insertGroup.run(id, name, 0); groupsCount++; }
|
||||
}
|
||||
|
||||
// Artikel
|
||||
let items = root.Products?.Product || root.products?.product || [];
|
||||
if (!Array.isArray(items)) items = [items];
|
||||
for (const item of items) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
|
||||
<!-- jsQR & Html5Qrcode für maximale Kompatibilität -->
|
||||
<!-- jsQR für maximale Kompatibilität -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
@@ -301,7 +301,7 @@
|
||||
<input type="password" id="cfg-pass-id" placeholder="Passwort aus ServicePass" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Kassennummer (cashRegisterNumber / MDCDEVICEID)</label>
|
||||
<label>Kassennummer (cashRegisterNumber / MDCDEVICEID / cr)</label>
|
||||
<input type="text" id="cfg-register" placeholder="z.B. 1" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -436,6 +436,7 @@
|
||||
// Config laden & speichern
|
||||
async function loadConfig() {
|
||||
const res = await fetch('/api/admin/config');
|
||||
if (res.status === 401) return showLogin();
|
||||
if (res.ok) {
|
||||
const cfg = await res.json();
|
||||
document.getElementById('cfg-url').value = cfg.pos_url || '';
|
||||
@@ -445,8 +446,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Robuster jsQR Scanner ====================
|
||||
|
||||
// Kamera Scanner
|
||||
document.getElementById('btn-toggle-camera').addEventListener('click', () => {
|
||||
if (videoStream) {
|
||||
stopCamera();
|
||||
@@ -557,6 +557,10 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ qrContent })
|
||||
});
|
||||
if (res.status === 401) {
|
||||
showAlert('login-alert', 'Sitzung abgelaufen. Bitte neu anmelden.', true);
|
||||
return showLogin();
|
||||
}
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
showAlert('dash-alert', 'QR-Code Handshake erfolgreich! Parameter übernommen & aktiviert.');
|
||||
@@ -591,6 +595,7 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (res.status === 401) return showLogin();
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
showAlert('dash-alert', 'Einstellungen erfolgreich gespeichert!');
|
||||
@@ -609,6 +614,7 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type })
|
||||
});
|
||||
if (res.status === 401) return showLogin();
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
showAlert('dash-alert', `Sync erfolgreich: ${data.result.productsCount} Artikel importiert.`);
|
||||
@@ -654,6 +660,7 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(mockData)
|
||||
});
|
||||
if (res.status === 401) return showLogin();
|
||||
if (res.ok) {
|
||||
showAlert('dash-alert', 'Musterartikel erfolgreich eingespielt!');
|
||||
loadData();
|
||||
@@ -663,6 +670,7 @@
|
||||
// Daten & Logs laden
|
||||
async function loadData() {
|
||||
const res = await fetch('/api/admin/data');
|
||||
if (res.status === 401) return showLogin();
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use('/Getraenkekarte.pdf', express.static(path.join(__dirname, 'Getraenkekarte.pdf')));
|
||||
|
||||
// Favicon dummy damit kein 502/404 geloggt wird
|
||||
app.get('/favicon.ico', (req, res) => res.status(204).end());
|
||||
|
||||
// JWT Auth Middleware
|
||||
function getSecret() {
|
||||
const row = db.prepare('SELECT value FROM config WHERE key = ?').get('jwt_secret');
|
||||
@@ -86,8 +89,8 @@ app.post('/api/admin/login', (req, res) => {
|
||||
const hash = hashRow ? hashRow.value : '';
|
||||
|
||||
if (username === adminUser && bcrypt.compareSync(password, hash)) {
|
||||
const token = jwt.sign({ user: username }, getSecret(), { expiresIn: '7d' });
|
||||
res.cookie('admin_token', token, { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000, sameSite: 'lax' });
|
||||
const token = jwt.sign({ user: username }, getSecret(), { expiresIn: '30d' });
|
||||
res.cookie('admin_token', token, { httpOnly: true, maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'lax' });
|
||||
return res.json({ success: true, message: 'Erfolgreich eingeloggt' });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user