diff --git a/backend/src/mqtt.js b/backend/src/mqtt.js index be921f4..3a4646d 100644 --- a/backend/src/mqtt.js +++ b/backend/src/mqtt.js @@ -234,6 +234,7 @@ function publishDiscovery() { name: '3D Bestellungen offen', unique_id: 'dickendock_bestellungen_offen', state_topic: `${BASE_TOPIC}/druck/bestellungen_offen`, + json_attributes_topic: `${BASE_TOPIC}/druck/bestellungen_offen_attributes`, icon: 'mdi:cube-send', unit_of_measurement: 'Bestellungen', state_class: 'measurement', availability_topic: AVAILABILITY_TOPIC, device: DEVICE, @@ -362,14 +363,19 @@ function publishKoepiState({ offers, changed }) { if (!client?.connected) return; const list = [...(offers || [])].sort((a, b) => parsePrice(a.price) - parsePrice(b.price)); - let state = list.length - ? list.map(o => `${o.retailer}: ${o.price}`).join(' | ') + // State: nur die Anzahl (bleibt immer kurz und stabil) — die eigentlichen + // Angebote stehen vollständig in den Attributen + const state = list.length ? `${list.length} Angebote` : 'keine Angebote'; + + // Lesbarer Mehrzeiler fürs Dashboard, zusätzlich zur strukturierten Liste + const listeText = list.length + ? list.map(o => `${o.retailer}: ${o.price}${o.validity || o.dateRange ? ' (' + (o.validity || o.dateRange) + ')' : ''}`).join('\n') : 'keine Angebote'; - if (state.length > 255) state = state.slice(0, 252) + '…'; client.publish(`${BASE_TOPIC}/koepi/state`, state, { retain: true }); client.publish(`${BASE_TOPIC}/koepi/attributes`, JSON.stringify({ angebote: list, + liste_text: listeText, anzahl: list.length, letzter_check: new Date().toISOString(), }), { retain: true }); @@ -383,12 +389,22 @@ function publishKoepiState({ offers, changed }) { // Wird jede Minute vom Scheduler in index.js aufgerufen function publishPresence() { if (!client?.connected) return; - const online = db.prepare(` - SELECT username FROM users WHERE last_active_at > datetime('now','localtime','-5 minutes') + const alleNutzer = db.prepare(` + SELECT username, last_active_at, + CASE WHEN last_active_at > datetime('now','localtime','-5 minutes') THEN 1 ELSE 0 END AS online + FROM users + ORDER BY last_active_at DESC `).all(); + const online = alleNutzer.filter(u => !!u.online); + client.publish(`${BASE_TOPIC}/presence/state`, String(online.length), { retain: true }); client.publish(`${BASE_TOPIC}/presence/attributes`, JSON.stringify({ - nutzer: online.map(u => u.username), + online: online.map(u => ({ benutzer: u.username, zuletzt_aktiv: u.last_active_at })), + alle_nutzer: alleNutzer.map(u => ({ + benutzer: u.username, + zuletzt_aktiv: u.last_active_at, + online: !!u.online, + })), }), { retain: true }); } @@ -495,6 +511,14 @@ function publishDruckStatistik() { } const offen = allOrders.length - byStatus.abgeschlossen; + // Konkrete offene Bestellungen (nicht abgeschlossen) mit Besitzer, für die Attribute + const offeneBestellungen = db.prepare(` + SELECT o.id, o.name, o.status, o.bezahlt, o.abgeholt, o.created_at, u.username + FROM orders o JOIN users u ON u.id = o.user_id + WHERE NOT (o.bezahlt = 1 AND o.abgeholt = 1) + ORDER BY o.created_at DESC + `).all(); + const pub = (topic, val) => client.publish(`${BASE_TOPIC}/${topic}`, String(val), { retain: true }); pub('druck/umsatz_monat', round2(umsatzMonat)); pub('druck/umsatz_gesamt', round2(umsatzGesamt)); @@ -507,6 +531,9 @@ function publishDruckStatistik() { pub('druck/nettogewinn_monat', round2(nettoMonat)); pub('druck/nettogewinn_gesamt', round2(nettoGesamt)); pub('druck/bestellungen_offen', offen); + client.publish(`${BASE_TOPIC}/druck/bestellungen_offen_attributes`, JSON.stringify({ + bestellungen: offeneBestellungen, + }), { retain: true }); pub('druck/bestellungen_warteliste', byStatus.warteliste); pub('druck/bestellungen_in_arbeit', byStatus.in_arbeit); pub('druck/bestellungen_fertig', byStatus.fertig);