10)); if (is_wp_error($res)) { if ($fehler === null) $fehler = 'Backend nicht erreichbar (' . $res->get_error_message() . ')'; return null; } $code = wp_remote_retrieve_response_code($res); if ($code !== 200) { if ($fehler === null) $fehler = 'Backend antwortete mit HTTP ' . $code . ' auf ' . $pfad; return null; } $json = json_decode(wp_remote_retrieve_body($res), true); if (!is_array($json) || !array_key_exists('data', $json)) { if ($fehler === null) $fehler = 'Unerwartete Antwort auf ' . $pfad; return null; } return $json['data']; }; $produkte = $hole('/items/products?fields=key,name,base_price,norm_qty,pricing_mode,type&filter[active][_eq]=true&limit=-1'); $settings = $hole('/items/pricing_settings?fields=*'); $items = $hole('/items/price_items?fields=key,price,unit,category&filter[active][_eq]=true&limit=-1'); // Nur bei Erfolg überschreiben – sonst bleiben die alten Werte stehen. if ($fehler === null && is_array($produkte) && !empty($produkte)) { update_option(SKRIFT_PREIS_OPTION, array( 'produkte' => $produkte, 'settings' => is_array($settings) ? $settings : array(), 'items' => is_array($items) ? $items : array(), 'zeit' => time(), 'fehler' => null, ), false); // kein autoload – wird nur bei Bedarf geladen return true; } // Fehler am vorhandenen Datensatz vermerken (für die Diagnose in der Ausgabe). $alt = get_option(SKRIFT_PREIS_OPTION); if (is_array($alt)) { $alt['fehler'] = $fehler; update_option(SKRIFT_PREIS_OPTION, $alt, false); } else { update_option(SKRIFT_PREIS_OPTION, array( 'produkte' => array(), 'settings' => array(), 'items' => array(), 'zeit' => 0, 'fehler' => $fehler, ), false); } return false; } add_action(SKRIFT_PREIS_CRON, 'skrift_preis_sync'); /** Stündlichen Cron beim Aktivieren einplanen und sofort einmal synchronisieren. */ register_activation_hook(__FILE__, function () { if (!wp_next_scheduled(SKRIFT_PREIS_CRON)) { wp_schedule_event(time() + 60, 'hourly', SKRIFT_PREIS_CRON); } skrift_preis_sync(); }); register_deactivation_hook(__FILE__, function () { $ts = wp_next_scheduled(SKRIFT_PREIS_CRON); if ($ts) wp_unschedule_event($ts, SKRIFT_PREIS_CRON); }); /** Sicherheitsnetz: fehlt der Cron-Eintrag (z. B. nach Migration), neu planen. */ add_action('init', function () { if (!wp_next_scheduled(SKRIFT_PREIS_CRON)) { wp_schedule_event(time() + 60, 'hourly', SKRIFT_PREIS_CRON); } // Manuelles Aktualisieren als Admin: ?skrift_preis_refresh=1 if (isset($_GET['skrift_preis_refresh']) && current_user_can('manage_options')) { skrift_preis_sync(); } }); /** * Liefert die gespeicherten Preisdaten. Kein HTTP im Normalfall. * Nur wenn noch nie synchronisiert wurde (oder die Daten deutlich veraltet * sind), wird einmalig nachgeholt. */ function skrift_preis_daten() { $d = get_option(SKRIFT_PREIS_OPTION); $leer = !is_array($d) || empty($d['produkte']); $alt = is_array($d) && isset($d['zeit']) && (time() - (int) $d['zeit']) > SKRIFT_PREIS_MAX_ALTER; if ($leer || $alt) { skrift_preis_sync(); $d = get_option(SKRIFT_PREIS_OPTION); } if (!is_array($d)) { $d = array('produkte' => array(), 'settings' => array(), 'items' => array(), 'zeit' => 0, 'fehler' => 'Keine Daten.'); } return $d; } /** Einen Preis-Positionswert (z. B. porto_inland) aus dem Katalog lesen. */ function skrift_preis_item($items, $key) { foreach ($items as $it) { if (isset($it['key']) && $it['key'] === $key) return (float) $it['price']; } return 0.0; } /** Ein Produkt per Schlüssel finden. */ function skrift_preis_produkt($produkte, $key) { foreach ($produkte as $p) { if (isset($p['key']) && $p['key'] === $key) return $p; } return null; } /** * Die für die Rechnung nötigen Parameter eines Produkts – Grundlage sowohl für * die PHP-Rechnung als auch für die identische Rechnung im Browser. */ function skrift_preis_parameter($daten, $produktKey, $typ = 'privat') { $s = $daten['settings']; $p = skrift_preis_produkt($daten['produkte'], $produktKey); if (!$p) return null; // Normalpreis-Menge kommt vom Produkt; ohne Wert kein Kleinmengen-Aufschlag. $norm = isset($p['norm_qty']) ? (float) $p['norm_qty'] : 0.0; // Eine gemeinsame Formel. $formel = !empty($s['multiplier_formula']) ? $s['multiplier_formula'] : ''; $tiers = array(); if (isset($s['quantity_discounts']) && is_array($s['quantity_discounts'])) { foreach ($s['quantity_discounts'] as $t) { $tiers[] = array( 'min_qty' => isset($t['min_qty']) ? (int) $t['min_qty'] : 0, 'percent' => isset($t['percent']) ? (float) $t['percent'] : 0.0, ); } } return array( 'base' => isset($p['base_price']) ? (float) $p['base_price'] : 0.0, 'norm' => $norm, 'formel' => $formel, 'mwst' => isset($s['mwst_percent']) ? (float) $s['mwst_percent'] : 19.0, 'tiers' => $tiers, 'porto' => skrift_preis_item($daten['items'], 'porto_inland'), 'name' => isset($p['name']) ? $p['name'] : $produktKey, ); } /** * Kernrechnung in PHP (spiegelt die Standard-Formel des Backends): * grund = base * menge * multiplikator * multiplik. = menge >= norm ? 1 : 2 - sqrt(menge / norm) * danach Mengenrabatt-Staffel, dann / menge = Preis pro Stück (netto). */ function skrift_preis_rechne_param($par, $menge, $mitKuvert) { if (!$par) return null; $menge = max(1, (int) $menge); // Multiplikator exakt mit der in Directus hinterlegten Formel berechnen. // Ohne Normalpreis-Menge am Produkt greift kein Kleinmengen-Aufschlag. $mult = 1.0; if ($par['norm'] > 0) { $mult = skrift_formel_eval($par['formel'], array( 'qty' => $menge, 'menge' => $menge, 'norm' => $par['norm'], 'base_price' => $par['base'], ), 1.0); } if (!is_finite($mult) || $mult <= 0) $mult = 1.0; $grund = $par['base'] * $menge * $mult; $besteMin = -1; $bestePercent = 0.0; foreach ($par['tiers'] as $t) { if ($menge >= $t['min_qty'] && $t['percent'] > 0 && $t['min_qty'] > $besteMin) { $besteMin = $t['min_qty']; $bestePercent = $t['percent']; } } if ($bestePercent > 0) $grund *= (1 - $bestePercent / 100); $netto = $grund / $menge; if ($mitKuvert) $netto += $par['porto']; return array( 'netto' => round($netto, 2), 'brutto' => round($netto * (1 + $par['mwst'] / 100), 2), 'mwst' => $par['mwst'], ); } /** REST-Endpunkt bleibt bestehen (PHP-Rechnung), z. B. für eigene Integrationen. */ add_action('rest_api_init', function () { register_rest_route('skrift/v1', '/preis', array( 'methods' => 'GET', 'permission_callback' => '__return_true', 'callback' => function (WP_REST_Request $req) { $daten = skrift_preis_daten(); $par = skrift_preis_parameter( $daten, sanitize_text_field($req->get_param('produkt')), $req->get_param('typ') === 'unternehmen' ? 'unternehmen' : 'privat' ); $r = skrift_preis_rechne_param( $par, (int) $req->get_param('menge'), $req->get_param('kuvert') === '1' || $req->get_param('kuvert') === 'true' ); if (!$r) { $grund = !empty($daten['fehler']) ? $daten['fehler'] : 'Produkt nicht gefunden.'; return new WP_Error('skrift_preis', $grund, array('status' => 404)); } return rest_ensure_response($r); }, )); }); /** * Darstellung. $mitMwst bestimmt, WELCHER Preis gezeigt wird – der Besucher * wählt das bewusst nicht selbst. * * Die Parameter stehen im Seitenquelltext; die Neuberechnung passiert dadurch * sofort im Browser (kein Netzwerkzugriff, keine Latenz) mit exakt derselben * Formel wie in PHP. */ function skrift_preis_render($atts, $mitMwst) { $a = shortcode_atts(array('produkt' => 'briefe', 'typ' => 'privat', 'menge' => 100), $atts); $daten = skrift_preis_daten(); $par = skrift_preis_parameter($daten, $a['produkt'], $a['typ']); if (!$par) { if (!empty($daten['fehler'])) { return '

Preisrechner nicht verfügbar.
' . esc_html($daten['fehler']) . '

'; } $bekannt = array(); foreach ($daten['produkte'] as $pp) { if (isset($pp['key'])) $bekannt[] = $pp['key']; } return '

Preisrechner: Produkt „' . esc_html($a['produkt']) . '" nicht gefunden.' . (count($bekannt) ? '
Verfügbar: ' . esc_html(implode(', ', $bekannt)) . '' : '') . '

'; } $startMenge = max(1, (int) $a['menge']); $start = skrift_preis_rechne_param($par, $startMenge, false); $startWert = $mitMwst ? $start['brutto'] : $start['netto']; $hinweis = $mitMwst ? 'inkl. ' . rtrim(rtrim(number_format($par['mwst'], 1, ',', '.'), '0'), ',') . ' % MwSt.' : 'zzgl. MwSt.'; $id = 'skpreis_' . wp_rand(1000, 9999); ob_start(); ?>
Preis pro Stück €