LexOffice Integration für Gravity Form

Automatische Rechnungserstellung in LexOffice basierend auf Gravity Forms-Bestellungen
Dieses WordPress-Plugin ermöglicht die automatische Erstellung von Rechnungen in LexOffice, sobald eine Bestellung über Gravity Forms eingeht.

📌 Funktionsweise
Nach dem Absenden einer Bestellung über Gravity Forms wird automatisch eine Rechnung in LexOffice erstellt.
Falls der Kunde bereits existiert, wird die Rechnung seinem LexOffice-Konto zugeordnet.
Falls der Kunde neu ist, wird er automatisch als Kontakt in LexOffice angelegt.
Die Rechnungsdetails (Kundenname, Adresse, Preis, Lieferdatum) werden direkt aus dem Formular übernommen.
Die Rechnung wird in LexOffice als Entwurf gespeichert, sodass sie vor dem Versand überprüft werden kann.
🔧 Technische Details
Das Plugin nutzt die LexOffice API für die Kommunikation mit LexOffice.
Es verwendet wp_remote_get und wp_remote_post, um Kunden zu suchen, neue Kontakte anzulegen und Rechnungen zu erstellen.
Die Steuerberechnung erfolgt mit einem Standardwert von 7% MwSt. (kann angepasst werden).
Es gibt Logging via error_log(), um Fehler in der WordPress-Debug-Konsole nachzuvollziehen.
⚙ Installation & Einrichtung
1️⃣ Plugin-Datei in WordPress hochladen
Speichere den unten stehenden Code als lexoffice-integration.php und lade die Datei in den /wp-content/plugins/ Ordner hoch.

2️⃣ API-Schlüssel hinterlegen
Füge folgenden Eintrag in die wp-config.php Datei ein, um den LexOffice API-Key zu speichern:

php
define('LEXOFFICE_API_KEY', 'dein_api_schlüssel');

3️⃣ Plugin in WordPress aktivieren
Gehe in den WordPress-Adminbereich, navigiere zu Plugins und aktiviere das LexOffice Integration Plugin.

🖥 Code: LexOffice Integration für Gravity Forms
php

<?php
/**
 * Plugin Name: LexOffice Integration
 * Plugin URI: https://example.com/
 * Description: Erstellt Rechnungen in LexOffice basierend auf Gravity Forms-Einträgen.
 * Version: 1.0
 * Author: Anonym
 * Author URI: https://example.com/
 * License: GPL2
 */

// Sicherheit: Direkten Zugriff verhindern
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Hook für Gravity Forms
add_action('gform_after_submission', 'gf_to_lexoffice_new_schema', 10, 2);

function gf_to_lexoffice_new_schema($entry, $form) {
    $api_key = defined('LEXOFFICE_API_KEY') ? constant('LEXOFFICE_API_KEY') : '';
    if (!$api_key) {
        error_log("[LexOffice] ❌ Kein API-Key vorhanden - Abbruch");
        return;
    }
    error_log("[LexOffice] ✅ API-Key gefunden");

    // Kundendaten aus Gravity Forms
    $anrede      = rgar($entry, '56');
    $vorname     = rgar($entry, '1');
    $nachname    = rgar($entry, '2');    
    $stadt       = trim(rgar($entry, '35.3'));
    $plz         = trim(rgar($entry, '35.5'));
    $land        = trim(rgar($entry, '35.6')) ?: 'DE';
    $email       = rgar($entry, '21');
    $telefon     = rgar($entry, '34');
    $lieferdatum = rgar($entry, '24');
    $preisklasse_raw = rgar($entry, '18');

    $kundenname = trim($anrede . ' ' . $vorname . ' ' . $nachname);

    // Land auf korrekten Ländercode setzen
    $land = strtoupper($land);
    if ($land === "DEUTSCHLAND") {
        $land = "DE";
    }

    if (empty($strasse) || empty($stadt) || empty($plz)) {
        error_log("[LexOffice] ❌ Adresse unvollständig - Abbruch");
        return;
    }

    preg_match('/\d+/', $preisklasse_raw, $matches);
    $preis_brutto = isset($matches[0]) ? floatval($matches[0]) : 0.0;
    if ($preis_brutto <= 0) {
        error_log("[LexOffice] ❌ Ungültiger Preis: $preis_brutto - Abbruch");
        return;
    }
    error_log("[LexOffice] ✅ Preis gültig: $preis_brutto EUR");

    // Kunde suchen oder anlegen
    $contact_id = lexoffice_find_contact_by_email($api_key, $email);
    if (!$contact_id) {
        $contact_id = lexoffice_create_contact_company_schema($api_key, $kundenname, $strasse, $stadt, $plz, $land, $email, $telefon);
    }
    if (!$contact_id) {
        error_log("[LexOffice] ❌ Kunde konnte nicht angelegt/gefunden werden - Abbruch");
        return;
    }

    // Rechnung erstellen
    lexoffice_create_invoice_new_schema($api_key, $contact_id, $kundenname, $strasse, $stadt, $plz, $land, $lieferdatum, $preis_brutto);
}

// Kontakt per E-Mail suchen
function lexoffice_find_contact_by_email($api_key, $email) {
    if (empty($email)) return null;

    $url = 'https://api.lexoffice.io/v1/contacts?email=' . urlencode($email);
    $response = wp_remote_get($url, array(
        'headers' => array('Authorization' => 'Bearer ' . $api_key),
        'timeout' => 30
    ));

    if (is_wp_error($response)) {
        error_log("[LexOffice] ❌ Fehler bei der Kontakt-Suche: " . $response->get_error_message());
        return null;
    }

    $data = json_decode(wp_remote_retrieve_body($response), true);
    return $data['content'][0]['id'] ?? null;
}

// Rechnung erstellen
function lexoffice_create_invoice_new_schema($api_key, $contact_id, $kundenname, $strasse, $stadt, $plz, $land, $lieferdatum, $preis_brutto) {
    $steuersatz = 7;
    $rechnungsdatum = date('Y-m-d\TH:i:s.vP');
    $shipping_date = date('Y-m-d\TH:i:s.000P', strtotime($lieferdatum . ' 12:00:00'));

    $invoice_data = array(
        "voucherDate" => $rechnungsdatum,
        "type" => "invoice",
        "status" => "draft",
        "language" => "de",
        "customer" => array("id" => $contact_id),
        "address" => array(
            "name" => $kundenname,
            "street" => $strasse,
            "zip" => $plz,
            "city" => $stadt,
            "countryCode" => $land
        ),
        "shippingConditions" => array("shippingDate" => $shipping_date),
        "lineItems" => array(array("type" => "custom", "name" => "Blumenstrauß", "quantity" => 1, "unitPrice" => array("netAmount" => round($preis_brutto / 1.07, 4), "taxRatePercentage" => $steuersatz))),
        "totalPrice" => array("netAmount" => round($preis_brutto / 1.07, 4), "grossAmount" => $preis_brutto)
    );

    wp_remote_post('https://api.lexoffice.io/v1/invoices', array('method'  => 'POST', 'headers' => array('Authorization' => 'Bearer ' . $api_key), 'body' => json_encode($invoice_data), 'timeout' => 30));
}
//Beispiel: $strasse     = trim(rgar($entry, '35.1')); 35.1 ist die Gravity Feld ID usw. **Hinweis:** Dieses Beispiel zeigt lediglich den grundsätzlichen Aufbau der Integration. Neben den Gravity Forms-Feld-IDs müssen also Steuersätze, API-Keys, Adress-Felder und ggf. der Rechnungsstatus angepasst werden, damit das Plugin optimal für Deinen Workflow funktioniert

Hi,

hilfst Du uns kurz auf die Sprünge, was genau Du uns mit dem Beitrag sagen möchtest? :slight_smile:

Ich habe eine Lösung entwickelt, um Rechnungen aus Gravity Forms automatisch in LexOffice zu erstellen. Da es dazu wenig Dokumentation gibt, wollte ich das hier teilen. Vielleicht hilft es jemandem weiter oder es gibt Verbesserungsvorschläge!

Dann wäre es doch nett und hilfreich, dass mit einem kleinen Einführungstext einzuleiten und zu erklären, meinst Du nicht? :slight_smile:

Wenn Du den Beitrag gelesen hättest, wäre Dir klar, dass es sich um eine Lösung für ein spezifisches Problem handelt – automatische Rechnungen mit LexOffice aus Gravity Forms heraus. Ich habe das sauber dokumentiert und den kompletten Code bereitgestellt, damit andere das nutzen oder anpassen können. Wer es braucht, kann es verwenden, wer nicht, ignoriert es. Ich muss hier nicht extra eine Einleitung schreiben, damit jemand versteht, dass eine fertige Lösung ein hilfreicher Beitrag ist.

Ich fände es schön, wenn man einen Erstbeitrag in einem Forum mit Hallo beginnt und dann kurz schreibt was man gemacht hat und dann den Code dazupackt. Das ist ein Minimum, was ich erwarte.

Du kannst dann aber darauf angesprochen auch gern patzig werden und mir unterstellen ich hätte es nicht gelesen und nicht verstanden. Ob das eine gute Idee ist, da darfst Du gern nochmal drüber nachdenken.

Müssen musst Du gar nichts, aber um andere, die vielleicht nicht so tief im Thema sind, abzuholen und auf die „Reise“ mitzunehmen, dafür schadet das nicht. Wie gesagt, dass ist das, was ich als Minimum erwarte.

Mir war es wichtig, eine vollständige Lösung bereitzustellen, inklusive einer ausführlichen Dokumentation. Wer sie nutzen will, kann das tun, wer nicht, lässt es.

Wenn hier der Fokus mehr auf Formalitäten als auf den eigentlichen Inhalt gelegt wird, ist das nicht mein Verständnis eines hilfreichen Forums. Ich bin dann raus – viel Erfolg noch.

Ich finde beides wichtig! Als initialen Inhalt des ersten Beitrags nur einen Code-Block zu posten ohne etwas Input warum etc. finde ich nicht schön und darauf habe ich hingewiesen.

Dann haben wir tatsächlich unterschiedliche Auffassungen von einem gemeinsamen Miteinander in einem, speziell diesem Forum hier. Danke für Deine Teilnahme und alles Gute weiterhin.