Stabile Version 2.0 QR Code lokal generiert und ins odt eingefügt.

Veränderungen wie Design, Größe, Rahmen usw
This commit is contained in:
Eduard Wisch 2026-01-29 20:37:59 +01:00
parent 97cfcefe22
commit 3078cc6f67
13 changed files with 1828 additions and 958 deletions

711
README.md
View file

@ -1,150 +1,218 @@
# EPCQR - QR-Code Generator für Dolibarr Rechnungen
# EPC QR-Code Modul für Dolibarr
**Version:** 1.5
**Autor:** Eduard Wisch
**Dolibarr:** 13.x - 20.x
**Version:** 2.0
**Autor:** DATA IT-Solutions (Eduard Wisch)
**Kompatibilität:** Dolibarr 19.0+
**Lizenz:** GPL-3.0+
---
## Übersicht
Das **EPCQR Modul** generiert automatisch **EPC-QR-Codes** (European Payments Council) für Dolibarr-Rechnungen und bietet eine **wiederverwendbare Lösung** zum Einfügen von Bildern in ODT-Dokumentvorlagen.
Das **EPC QR-Code Modul** generiert automatisch **EPC/GiroCode QR-Codes** für SEPA-Überweisungen auf Dolibarr-Rechnungen. Kunden können den QR-Code mit ihrer Banking-App scannen und die Zahlung sofort ausführen - ohne manuelle Eingabe von IBAN, Betrag oder Verwendungszweck.
### Neu in Version 1.5
### Neu in Version 2.0
**Lokale QR-Code-Generierung** mit Caching
**Generisches Bildintegration-System** für ODT-Dokumente
**{qrcode} Keyword** in ODT-Templates
**Substitutionssystem** für beliebige Bilder
✅ **Hook-basierte ODT-Verarbeitung**
- **Admin-Center** mit allen Einstellungen an einem Ort
- **4 Modul-Stile:** Quadrat, Abgerundet, Punkte, Diamant
- **5 Rahmen-Stile:** Kein Rahmen, Einfach, Abgerundet, Doppelt, Gestrichelt
- **Transparenter Hintergrund** möglich
- **Logo-Einbettung** mit konfigurierbarer Größe (5-30%)
- **Individuelle Farben** für Vordergrund und Hintergrund
- **Cache-Verwaltung** direkt im Admin-Center
- **Extrafeld-Sichtbarkeit** konfigurierbar
- **Verbesserte ODT-Integration**
### Was sind EPC-QR-Codes?
EPC-QR-Codes enthalten alle relevanten Zahlungsinformationen im GiroCode-Format:
EPC-QR-Codes (auch GiroCode genannt) enthalten alle relevanten Zahlungsinformationen:
- Empfänger (Kontoinhaber)
- IBAN
- BIC
- BIC (optional)
- Betrag
- Verwendungszweck (Rechnungsnummer)
---
## Features
### QR-Code Generierung
**Automatische Generierung** bei Rechnungsfreigabe
**Lokales Caching** (keine externe Service-Abhängigkeit mehr)
**EPC/GiroCode-Standard** konform
**Finale Rechnungsnummer** im QR-Code (keine PROV-Nummern)
### Bildintegration in ODT
**{qrcode} Keyword** für einfache Verwendung in Templates
**Generisches System** für beliebige Bilder
✅ **Automatische Bildgrößen-Anpassung**
**Wiederverwendbar** für andere Module
### Technisch
**Hook-System** für ODT-Verarbeitung
**Substitutionsfunktionen** für Dokumente
**Extra-Felder** mit `_imagepath` Suffix automatisch erkannt
**Vollständige ODT-ZIP-Verarbeitung**
Diese QR-Codes sind mit allen europäischen Banking-Apps kompatibel.
---
## Installation
### 1. Modul aktivieren
### Voraussetzungen
1. Gehe zu: **Home → Setup → Modules/Applications**
2. Suche nach "EPCQR"
3. Klicke auf **Activate**
- Dolibarr 19.0 oder höher
- PHP 7.1 oder höher
- PHP GD-Extension (für Bildgenerierung)
- TCPDF-Bibliothek (in Dolibarr enthalten)
### 2. Extra-Felder werden automatisch erstellt
### Installationsschritte
Das Modul erstellt automatisch 3 Extra-Felder für Rechnungen:
1. **Modul-Ordner kopieren:**
```bash
cp -r epcqr /path/to/dolibarr/htdocs/custom/
```
| Feldname | Typ | Beschreibung |
|----------|-----|--------------|
| `qrcode` | HTML | QR-Code als `<img>` Tag (Kompatibilität) |
| `qrcodepfad` | Varchar(255) | QR-Code URL via viewimage.php |
| `qrcodepath` | Varchar(255) | **NEU**: Lokaler Dateipfad für ODT-Integration |
2. **Modul aktivieren:**
- Einstellungen → Module/Anwendungen
- Kategorie "Finanzen" auswählen
- "EPC QR-Code" aktivieren
3. **Bankdaten konfigurieren:**
- Einstellungen → Module → EPC QR-Code → Einstellungen
- Bankverbindung eingeben oder Systembankkonto auswählen
---
## Konfiguration
### Bankdaten anpassen
### Admin-Center
**WICHTIG:** Du musst deine Bankdaten im Code hinterlegen!
Das Admin-Center erreichen Sie unter:
**Einstellungen → Module → EPC QR-Code → Einstellungen**
**Datei:** `/custom/epcqr/class/actions_epcqr.class.php`
Es gibt drei Tabs:
- **Einstellungen** - Alle Konfigurationsoptionen
- **Debug** - QR-Code Testseite
- **Über** - Modul-Informationen
Suche nach dieser Stelle im Trigger `BILL_VALIDATE`:
### Bankverbindung
```php
// Bankdaten - HIER ANPASSEN!
$accountHolder = 'Peter Casimir';
$iban = 'DE70217625500013438147';
$bic = 'GENODEF1HUM';
| Einstellung | Beschreibung |
|-------------|--------------|
| **Datenquelle** | "Systembankkonto verwenden" oder "Manuelle Eingabe" |
| **Kontoinhaber** | Name des Kontoinhabers (bei manueller Eingabe) |
| **IBAN** | Internationale Bankkontonummer |
| **BIC** | Bank Identifier Code (optional) |
> **Tipp:** Bei "Systembankkonto verwenden" werden die Bankdaten automatisch aus dem in Dolibarr konfigurierten Bankkonto geladen.
### QR-Code Größe
| Einstellung | Beschreibung |
|-------------|--------------|
| **QR-Code Größe (Verhältnis)** | Größe in ODT-Dokumenten (0.01 - 2.0) |
**Empfohlene Werte:**
| Wert | Größe |
|------|-------|
| 0.03 - 0.05 | sehr klein |
| 0.1 | klein |
| 0.3 | mittel (Standard) |
| 0.5 | groß |
### Styling-Optionen
#### Farben
| Einstellung | Beschreibung |
|-------------|--------------|
| **Vordergrundfarbe** | Farbe der QR-Code-Module (Standard: #000000 schwarz) |
| **Hintergrundfarbe** | Hintergrundfarbe oder "Transparent" aktivieren |
> **Hinweis:** Bei transparentem Hintergrund sollte die Vordergrundfarbe ausreichend Kontrast zum Dokumenthintergrund haben.
#### Logo
| Einstellung | Beschreibung |
|-------------|--------------|
| **Logo (optional)** | Absoluter Pfad zum Logo-Bild (PNG, JPG, GIF) |
| **Logo-Größe** | 5% - 30% der QR-Code-Größe (Standard: 20%) |
**Beispiel-Pfade:**
```
/var/www/dolibarr/documents/mycompany/logos/logo.png
/srv/http/dolibarr/htdocs/custom/epcqr/img/mylogo.png
```
**Ändere diese Werte auf deine Daten:**
> **Wichtig:** Bei Verwendung eines Logos wird automatisch die höchste Fehlerkorrektur (H = 30%) verwendet, um die Scanbarkeit zu gewährleisten.
```php
$accountHolder = 'Dein Name / Firmenname';
$iban = 'DE12345678901234567890';
$bic = 'ABCDEFGH123';
```
#### Modul-Stil
**Speichern und fertig!**
| Stil | Beschreibung |
|------|--------------|
| **Quadrat** | Standard QR-Code-Module (klassisch) |
| **Abgerundet** | Module mit abgerundeten Ecken |
| **Punkte** | Kreisförmige Module |
| **Diamant** | Rautenförmige Module |
#### Rahmen-Stil
| Stil | Beschreibung |
|------|--------------|
| **Kein Rahmen** | Standard - ohne Rahmen |
| **Einfach** | Schlichte Linie um den QR-Code |
| **Abgerundet** | Rahmen mit runden Ecken |
| **Doppelt** | Zwei parallele Linien |
| **Gestrichelt** | Unterbrochene Linie |
### Cache-Verwaltung
QR-Codes werden lokal gecached für optimale Performance. Nach Änderung der Styling-Einstellungen:
**Cache leeren** - Klicken Sie auf den Button, um alle gecachten QR-Codes zu löschen. Neue QR-Codes werden mit den aktuellen Einstellungen generiert.
### Extrafeld-Sichtbarkeit
Kontrollieren Sie, welche QR-Code-Felder im Rechnungsformular angezeigt werden:
| Einstellung | Beschreibung |
|-------------|--------------|
| **QR-Code (HTML) ausblenden** | Versteckt das Bild-Feld |
| **QR-Code URL ausblenden** | Versteckt den URL-Link |
| **QR-Code Pfad ausblenden** | Versteckt den lokalen Dateipfad |
---
## Verwendung
### Automatische QR-Code Generierung
### Automatische Generierung
Der QR-Code wird **automatisch** generiert wenn du eine Rechnung freigibst:
Der QR-Code wird **automatisch generiert**, wenn:
1. Eine neue Rechnung erstellt wird
2. Eine bestehende Rechnung validiert wird
1. Rechnung erstellen (Status: Draft)
2. **Freigeben** (Validate)
3. QR-Code wird automatisch generiert
4. Extra-Felder werden befüllt
Der Trigger läuft beim Event `BILL_VALIDATE`.
### QR-Code in ODT-Templates einfügen
### Extrafelder
**NEU in Version 1.5: Verwenden Sie das einfache `{qrcode}` Keyword!**
Das Modul erstellt drei Extrafelder auf Rechnungen:
**Schritt 1:** ODT-Template öffnen (z.B. in LibreOffice)
| Feld | Typ | Inhalt |
|------|-----|--------|
| `qrcode` | HTML | `<img>`-Tag zur Anzeige im Formular |
| `qrcodepfad` | HTML | URL zum QR-Code-Bild (klickbar) |
| `qrcodepath` | varchar | Lokaler Dateipfad (für ODT-Integration) |
**Schritt 2:** An gewünschter Stelle einfügen:
### ODT-Integration
Um den QR-Code in ODT-Dokumentvorlagen einzufügen:
1. **Platzhalter einfügen:**
```
{qrcode}
```
**Schritt 3:** Template speichern und hochladen
2. **Der QR-Code wird automatisch** beim Generieren des Dokuments eingefügt.
**Schritt 4:** Rechnung freigeben → QR-Code wird automatisch generiert
3. **Größe anpassen:** Die Größe wird über die "QR-Code Größe (Verhältnis)" Einstellung im Admin-Center gesteuert.
**Schritt 5:** Dokument als ODT generieren → QR-Code wird eingefügt
**Ergebnis:** Der QR-Code erscheint automatisch als Bild im ODT-Dokument!
**Hinweis:** Das alte Format `{invoice_options_qrcode}` funktioniert weiterhin für Kompatibilität.
> **Hinweis:** Der Platzhalter muss exakt `{qrcode}` lauten.
### Beispiel Template-Position
```
┌─────────────────────────────────┐
│ RECHNUNG IN2601-0001
│ RECHNUNG RE2501-0001
│ │
│ Gesamtbetrag: 1.234,56
│ Gesamtbetrag: 1.234,56 EUR
│ │
│ {invoice_options_qrcode} │ ← QR-Code hier
│ ┌──────────┐ │
│ │ {qrcode} │ │
│ └──────────┘ │
│ │
│ Bitte überweisen Sie... │
│ Bitte überweisen Sie den │
│ Betrag unter Angabe der │
│ Rechnungsnummer. │
└─────────────────────────────────┘
```
@ -152,327 +220,240 @@ Der QR-Code wird **automatisch** generiert wenn du eine Rechnung freigibst:
## Technische Details
### Wie funktioniert das Modul?
### EPC/GiroCode Format
#### 1. Trigger-System
Der generierte QR-Code enthält folgende Daten im EPC-Standard:
Das Modul nutzt den Dolibarr-Trigger **BILL_VALIDATE**, der bei Rechnungsfreigabe ausgeführt wird.
**Problem:** Zu diesem Zeitpunkt hat das `$object` noch die provisorische Nummer (z.B. `PROV23`)!
**Lösung:** Das Modul lädt das Rechnungsobjekt neu aus der Datenbank:
```php
$invoice = new Facture($this-&gt;db);
$invoice-&gt;fetch($object-&gt;id);
// Jetzt hat $invoice-&gt;ref die finale Nummer (z.B. IN26-0001)
```
BCD # Service Tag
002 # Version
1 # Character set (UTF-8)
SCT # SEPA Credit Transfer
[BIC] # Bank Identifier Code
[Kontoinhaber] # Empfänger Name
[IBAN] # Empfänger IBAN
EUR[Betrag] # Währung und Betrag
# Purpose (leer)
[Rechnungsnummer] # Verwendungszweck
# Info (leer)
```
#### 2. QR-Code Service
### Dateispeicherung
Das Modul nutzt einen externen Service:
**URL:** `https://qr.data-it-solution.de/epc`
**Parameter:**
- `name` = Kontoinhaber
- `iban` = IBAN
- `bic` = BIC/SWIFT
- `amount` = Betrag (formatiert mit `price2num()`)
- `remittance` = Verwendungszweck (Rechnungsnummer)
**Beispiel-URL:**
QR-Codes werden gespeichert unter:
```
https://qr.data-it-solution.de/epc?name=Eduard+Wisch&amp;iban=DE70217625500013438147&amp;bic=GENODEF1HUM&amp;amount=1234.56&amp;remittance=IN26-0001
/documents/epcqr/qrcodes/epc_[hash].png
```
#### 3. Speicherung
Der Hash basiert auf:
- Bankdaten (Kontoinhaber, IBAN, BIC)
- Rechnungsdaten (Betrag, Referenz)
- Styling-Einstellungen (Farben, Logo, Modul-Stil, Rahmen-Stil)
Der QR-Code wird in 2 Extra-Feldern gespeichert:
### Hooks
```php
// Als HTML-Tag für ODT
$invoice-&gt;array_options['options_qrcode'] = "<img src="$qrurl" width="100px">";
Das Modul verwendet folgende Dolibarr-Hooks:
// Als URL für Backup/Referenz
$invoice-&gt;array_options['options_qrcodepfad'] = $qrurl;
| Hook | Funktion |
|------|----------|
| `invoicecard` | QR-Code bei Rechnungsvalidierung generieren |
| `odtgeneration` | QR-Code-Bild in ODT einfügen (beforeODTSave) |
| `pdfgeneration` | Substitutionsvariablen für PDF |
### Substitutionsvariablen
Für ODT/PDF-Dokumente stehen folgende Substitutionen zur Verfügung:
| Variable | Inhalt |
|----------|--------|
| `{qrcode}` | QR-Code als Bild |
| `{options_qrcodepath}` | Lokaler Dateipfad |
### Modul-Struktur
```
epcqr/
├── admin/
│ ├── setup.php # Einstellungen
│ └── about.php # Über-Seite
├── class/
│ └── actions_epcqr.class.php # Hook-Aktionen
├── core/
│ ├── modules/
│ │ └── modEpcqr.class.php # Modul-Deskriptor
│ ├── substitutions/
│ │ └── functions_epcqr.lib.php # Substitutionen
│ └── triggers/
│ └── interface_99_modEpcqr_EpcqrTriggers.class.php
├── langs/
│ ├── de_DE/
│ │ └── epcqr.lang # Deutsche Übersetzung
│ └── en_US/
│ └── epcqr.lang # Englische Übersetzung
├── lib/
│ ├── epcqr.lib.php # Bibliotheksfunktionen
│ └── qrcode.class.php # QR-Code Generator
├── sql/
│ ├── llx_epcqr_extrafields.sql # Extrafelder
│ └── dolibarr_allversions.sql # Upgrade-Script
├── test_qrcode.php # Debug-Seite
└── README.md # Diese Dokumentation
```
---
## Workflow-Diagramm
## Fehlerbehebung
```
┌─────────────────────┐
│ Rechnung erstellen │
│ (Status: DRAFT) │
└──────────┬──────────┘
┌─────────────────────┐
│ Freigeben (Validate)│
└──────────┬──────────┘
┌─────────────────────────────┐
│ 1. Datenbank Update │
│ → Finale Nummer vergeben │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ 2. Trigger BILL_VALIDATE │
│ → Modul wird aufgerufen │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ 3. Object neu laden │
│ → Finale Nummer holen │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ 4. QR-URL generieren │
│ → Mit finaler Nummer │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ 5. Extra-Felder speichern │
│ → options_qrcode │
│ → options_qrcodepfad │
└─────────────────────────────┘
```
### QR-Code wird nicht generiert
---
1. **Bankdaten prüfen:** Sind Kontoinhaber und IBAN konfiguriert?
2. **Modul aktiv?** Ist das EPC QR-Code Modul aktiviert?
3. **Rechnung validiert?** QR-Codes werden erst bei Validierung generiert.
4. **Logs prüfen:** Einstellungen → Logs auf Fehlermeldungen prüfen.
- Suche nach "EPCQR" in den Logs
## Troubleshooting
### QR-Code nicht scannbar
### Problem: QR-Code zeigt PROV-Nummer
1. **Größe erhöhen:** Mindestens 2x2 cm für zuverlässiges Scannen.
2. **Kontrast prüfen:** Dunkle Vordergrundfarbe auf hellem Hintergrund.
3. **Logo verkleinern:** Maximale Logo-Größe 30% (besser 20% oder weniger).
4. **Modul-Stil:** Bei Problemen "Quadrat" (Standard) verwenden.
**Symptom:** QR-Code enthält "PROV23" statt "IN26-0001"
### ODT: QR-Code erscheint nicht
**Ursache:** Object wurde nicht neu geladen im Trigger
1. **Platzhalter korrekt?** Muss exakt `{qrcode}` sein.
2. **Pfad existiert?** Prüfen ob die Datei unter `qrcodepath` existiert.
3. **Cache leeren:** Nach Änderungen den Cache leeren.
4. **Extrafeld vorhanden?** `qrcodepath` muss als Extrafeld existieren.
**Lösung:** Prüfe in `actions_epcqr.class.php`:
### Styling-Änderungen haben keine Wirkung
```php
// FALSCH:
$ref = $object-&gt;ref;
Nach Änderung der Styling-Einstellungen:
1. **Cache leeren** im Admin-Center klicken
2. Rechnung erneut generieren (auf Draft setzen → Validieren)
3. Oder neuen QR-Code durch neue Rechnung testen
// RICHTIG:
$invoice = new Facture($this-&gt;db);
$invoice-&gt;fetch($object-&gt;id);
$ref = $invoice-&gt;ref;
```
### Extra-Felder fehlen
### Problem: Extra-Felder sind leer
**Symptom:** Nach Freigabe sind die Felder `qrcode` und `qrcodepfad` leer
**Lösung:**
Falls die Extrafelder nicht automatisch erstellt wurden:
1. Modul **deaktivieren**
2. Modul wieder **aktivieren**
3. Prüfe: **Setup → Dictionaries → Extra Attributes → Invoices**
4. Sollten 2 Felder da sein: `qrcode` und `qrcodepfad`
### Problem: QR-Code wird in ODT nicht angezeigt
**Symptom:** Template zeigt nur `{invoice_options_qrcode}` als Text
**Lösungen:**
1. **Feldname prüfen:** Muss exakt `{invoice_options_qrcode}` heißen
2. **Extra-Feld aktiviert?** Setup → Extra Attributes → "Printable" = Ja
3. **Template neu generieren:** Rechnung → "Generate document"
### Problem: Falscher Betrag im QR-Code
**Symptom:** Banking-App zeigt falschen Betrag
**Ursache:** Formatierung falsch
**Lösung:** Modul nutzt korrekt `price2num($invoice-&gt;total_ttc, 'MT')`
### Problem: QR-Code kann nicht gescannt werden
**Mögliche Ursachen:**
- QR-Code zu klein im PDF → Größe in ODT anpassen
- Externe URL nicht erreichbar → Internetverbindung prüfen
- Falsches Format → Service-URL prüfen
---
## Extra-Felder Details
### Feld: qrcode
- **Typ:** HTML
- **Inhalt:** `<img src="https://..." width="100px">`
- **Verwendung:** Für ODT-Templates
- **Printable:** Ja
- **Position:** 100
### Feld: qrcodepfad
- **Typ:** varchar(255)
- **Inhalt:** Vollständige QR-Code URL
- **Verwendung:** Backup, Debugging, externe Verarbeitung
- **Printable:** Nein (nur intern)
- **Position:** 101
---
## Code-Referenz
### Kompletter Trigger-Code
```php
public function runTrigger($action, $object, User $user, Translate $langs, Conf $conf)
{
if ($action == 'BILL_VALIDATE') {
dol_syslog("EPCQR: Generiere QR-Code für Rechnung ID " . $object-&gt;id);
// ⚠️ WICHTIG: Objekt neu laden für finale Rechnungsnummer!
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
$invoice = new Facture($this-&gt;db);
$result = $invoice-&gt;fetch($object-&gt;id);
if ($result &gt; 0) {
// Bankdaten - HIER ANPASSEN!
$accountHolder = 'Eduard Wisch';
$iban = 'DE70217625500013438147';
$bic = 'GENODEF1HUM';
$amount = price2num($invoice-&gt;total_ttc, 'MT');
$ref = $invoice-&gt;ref; // Finale Nummer
// QR-Code URL generieren
$qrurl = "https://qr.data-it-solution.de/epc?"
. "name=" . urlencode($accountHolder)
. "&amp;iban=" . urlencode($iban)
. "&amp;bic=" . urlencode($bic)
. "&amp;amount=" . urlencode($amount)
. "&amp;remittance=" . urlencode($ref);
// In Extra-Felder speichern
$invoice-&gt;array_options['options_qrcode'] = "<img src="&quot;.$qrurl.&quot;" width="100px">";
$invoice-&gt;insertExtraFields();
$invoice-&gt;array_options['options_qrcodepfad'] = $qrurl;
$invoice-&gt;insertExtraFields();
dol_syslog("EPCQR: QR-Code generiert für " . $invoice-&gt;ref);
} else {
dol_syslog("EPCQR: Fehler beim Laden der Rechnung", LOG_ERR);
return -1;
}
return 1;
}
return 0;
}
```
---
## FAQ
### Kann ich andere QR-Services nutzen?
Ja! Ändere einfach die URL in `actions_epcqr.class.php`:
```php
$qrurl = "https://dein-service.de/qr?"
. "deine_parameter=" . urlencode($value);
```
### Funktioniert das Modul mit allen Rechnungstypen?
Ja, der Trigger `BILL_VALIDATE` wird für alle Rechnungstypen aufgerufen:
- Standard-Rechnungen
- Gutschriften
- Ersatzrechnungen
- Anzahlungsrechnungen
### Kann ich die QR-Code-Größe ändern?
Ja, in 2 Varianten:
**1. Im Code:**
```php
$invoice-&gt;array_options['options_qrcode'] = "<img src="&quot;.$qrurl.&quot;" width="150px">";
```
**2. Im ODT-Template:**
Bild markieren → Rechtsklick → Eigenschaften → Größe ändern
### Werden alte Rechnungen auch mit QR-Codes versehen?
Nein, nur neue Rechnungen die nach Modulaktivierung **freigegeben** werden.
**Für alte Rechnungen:**
1. Rechnung auf "Draft" setzen
2. Erneut freigeben
3. QR-Code wird generiert
---
## Dokumentation
📖 **Ausführliche Dokumentation:** [doc/BILDER_IN_ODT.md](doc/BILDER_IN_ODT.md)
Die erweiterte Dokumentation enthält:
- Technische Details zur Bildverarbeitung
- Anleitung für eigene Bilder
- Fehlerbehebung und Debugging
- Code-Referenz und Architektur
- Erweiterte Verwendungsmöglichkeiten
---
## Support &amp; Kontakt
**Entwickler:** Eduard Wisch
**E-Mail:** data@data-it-solution.de
### Bei Problemen
1. **Log-Dateien prüfen:** `documents/dolibarr.log`
2. **Debug aktivieren:** Setup → Other → Enable debug mode
3. **Dokumentation lesen:** [doc/BILDER_IN_ODT.md](doc/BILDER_IN_ODT.md)
3. Prüfen unter: Einstellungen → Wörterbücher → Zusatzattribute → Rechnungen
---
## Changelog
### Version 1.5 (2026-01-27)
- ✅ **Lokale QR-Code-Generierung** mit Caching-System
- ✅ **{qrcode} Keyword** für ODT-Templates
- ✅ **Generisches Bildintegration-System** für beliebige Bilder
- ✅ **Substitutionssystem** aktiviert und implementiert
- ✅ **Hook-basierte ODT-Verarbeitung** (afterODTCreation)
- ✅ **Automatische Bildgrößen-Anpassung** in ODT
- ✅ **Wiederverwendbare Lösung** für andere Module
- ✅ Neue Klassen: QRCodeGenerator, ActionsEpcqr
- ✅ Neue Funktionen: epcqr_processODTImages, epcqr_insertImagesIntoODT
- ✅ Dokumentation: [doc/BILDER_IN_ODT.md](doc/BILDER_IN_ODT.md)
### Version 2.0 (Januar 2025)
### Version 1.4 (2026-01-11)
- ✅ Erste stabile Version
- ✅ Automatische QR-Code Generierung
- ✅ Extra-Felder für ODT-Integration
- ✅ Externe QR-Service Integration
- ✅ Object-Reload Fix für finale Rechnungsnummer
**Neue Funktionen:**
- Vollständiges Admin-Center mit allen Einstellungen
- Modul-Stile: Quadrat, Abgerundet, Punkte, Diamant
- Rahmen-Stile: Kein Rahmen, Einfach, Abgerundet, Doppelt, Gestrichelt
- Transparenter Hintergrund
- Logo-Einbettung mit konfigurierbarer Größe (5-30%)
- Cache-Verwaltung im Admin-Center
- Extrafeld-Sichtbarkeit konfigurierbar
- Verbesserte ODT-Integration mit beforeODTSave Hook
- Kleinere QR-Code-Größen möglich (ab 0.01)
- Systembankkonto-Unterstützung
**Verbesserungen:**
- Neues Icon (fa-qrcode)
- Deutsche und englische Übersetzungen vollständig
- Bessere Fehlerbehandlung und Logging
- Performance-Optimierungen durch intelligentes Caching
- SQL-Scripts für automatische Extrafeld-Erstellung
**Technische Änderungen:**
- Komplette Überarbeitung der QRCodeGenerator-Klasse
- Neue Methoden für Modul-Stile und Rahmen
- Hash-basiertes Caching mit Styling-Berücksichtigung
- Verbesserte Transparenz-Unterstützung
### Version 1.5 (Januar 2025)
- Lokales QR-Code-Caching
- Extrafeld `qrcodepath` für ODT-Integration
- Grundlegende Farbkonfiguration
- {qrcode} Keyword für ODT-Templates
- Hook-basierte ODT-Verarbeitung
### Version 1.0 (2025)
- Initiale Version
- Automatische EPC QR-Code-Generierung
- Extra-Felder für Rechnungen
- Externe QR-Service Integration
---
## API / Entwickler
### QR-Code manuell generieren
```php
require_once DOL_DOCUMENT_ROOT.'/custom/epcqr/lib/qrcode.class.php';
$qrGen = new QRCodeGenerator($db);
$filepath = $qrGen->generateEPCQRCode(
'Max Mustermann', // Kontoinhaber
'DE89370400440532013000', // IBAN
'COBADEFFXXX', // BIC
1234.56, // Betrag
'RE2501-0001' // Verwendungszweck
);
```
### Cache leeren
```php
require_once DOL_DOCUMENT_ROOT.'/custom/epcqr/lib/qrcode.class.php';
$qrGen = new QRCodeGenerator($db);
$deleted = $qrGen->clearCache();
echo "Gelöscht: $deleted QR-Codes";
```
### Styling programmatisch setzen
Die Styling-Optionen werden aus der Dolibarr-Konfiguration geladen:
```php
// In llx_const Tabelle:
// EPCQR_FG_COLOR = '#000000'
// EPCQR_BG_COLOR = '#FFFFFF' oder 'transparent'
// EPCQR_LOGO_PATH = '/path/to/logo.png'
// EPCQR_LOGO_SIZE = 20
// EPCQR_MODULE_STYLE = 'square' | 'rounded' | 'dots' | 'diamond'
// EPCQR_BORDER_STYLE = 'none' | 'simple' | 'rounded' | 'double' | 'dashed'
```
---
## Support
Bei Fragen oder Problemen:
- **E-Mail:** data@data-it-solution.de
- **Website:** https://data-it-solution.de
### Bei Problemen
1. **Log-Dateien prüfen:** `documents/dolibarr.log`
2. **Debug-Seite nutzen:** Admin-Center → Debug Tab
3. **EPCQR in Logs suchen:** Alle Meldungen beginnen mit "EPCQR:"
---
## Lizenz
GPL-3.0+
**GNU General Public License v3.0 oder höher**
Dieses Modul ist freie Software; Sie können es unter den Bedingungen der GNU General Public License Version 3 oder später weitergeben und/oder modifizieren.
Copyright (C) 2025 Eduard Wisch / DATA IT-Solutions
---
Dieses Programm ist freie Software; Sie können es unter den Bedingungen der GNU General Public License, wie von der Free Software Foundation veröffentlicht, weitergeben und/oder modifizieren; entweder Version 3 der Lizenz oder (nach Ihrer Wahl) jede spätere Version.
**Viel Erfolg mit EPCQR!** 🎉
Dieses Programm wird in der Hoffnung verteilt, dass es nützlich sein wird, aber OHNE JEDE GEWÄHRLEISTUNG; sogar ohne die implizite Gewährleistung der MARKTGÄNGIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
Die vollständige Lizenz finden Sie unter: https://www.gnu.org/licenses/gpl-3.0.html

View file

@ -20,16 +20,14 @@
/**
* \file epcqr/admin/setup.php
* \ingroup epcqr
* \brief Epcqr setup page.
* \brief EPCQR Modul Einstellungen
*/
// Load Dolibarr environment
$res = 0;
// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
}
// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
$tmp2 = realpath(__FILE__);
$i = strlen($tmp) - 1;
@ -44,7 +42,6 @@ if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
}
// Try main.inc.php using relative path
if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php";
}
@ -57,8 +54,8 @@ if (!$res) {
// Libraries
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
require_once DOL_DOCUMENT_ROOT."/core/class/html.form.class.php";
require_once '../lib/epcqr.lib.php';
//require_once "../class/myclass.class.php";
/**
* @var Conf $conf
@ -71,250 +68,135 @@ require_once '../lib/epcqr.lib.php';
// Translations
$langs->loadLangs(array("admin", "epcqr@epcqr"));
// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
/** @var HookManager $hookmanager */
$hookmanager->initHooks(array('epcqrsetup', 'globalsetup'));
// Access control
if (!$user->admin) {
accessforbidden();
}
// Parameters
$action = GETPOST('action', 'aZ09');
$backtopage = GETPOST('backtopage', 'alpha');
$modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php
$value = GETPOST('value', 'alpha');
$label = GETPOST('label', 'alpha');
$scandir = GETPOST('scan_dir', 'alpha');
$type = 'myobject';
$error = 0;
$setupnotempty = 0;
// Access control
if (!$user->admin) {
accessforbidden();
}
// Set this to 1 to use the factory to manage constants. Warning, the generated module will be compatible with version v15+ only
$useFormSetup = 1;
if (!class_exists('FormSetup')) {
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsetup.class.php';
}
$formSetup = new FormSetup($db);
// Access control
if (!$user->admin) {
accessforbidden();
}
// Enter here all parameters in your setup page
// Setup conf for selection of an URL
$item = $formSetup->newItem('EPCQR_MYPARAM1');
$item->fieldParams['isMandatory'] = 1;
$item->fieldAttr['placeholder'] = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'];
$item->cssClass = 'minwidth500';
// Setup conf for selection of a simple string input
$item = $formSetup->newItem('EPCQR_MYPARAM2');
$item->defaultFieldValue = 'default value';
$item->fieldAttr['placeholder'] = 'A placeholder here';
$item->helpText = 'Tooltip text';
// Setup conf for selection of a simple textarea input but we replace the text of field title
$item = $formSetup->newItem('EPCQR_MYPARAM3');
$item->nameText = $item->getNameText().' more html text ';
// Setup conf for a selection of a Thirdparty
$item = $formSetup->newItem('EPCQR_MYPARAM4');
$item->setAsThirdpartyType();
// Setup conf for a selection of a boolean
$formSetup->newItem('EPCQR_MYPARAM5')->setAsYesNo();
// Setup conf for a selection of an Email template of type thirdparty
$formSetup->newItem('EPCQR_MYPARAM6')->setAsEmailTemplate('thirdparty');
// Setup conf for a selection of a secured key
//$formSetup->newItem('EPCQR_MYPARAM7')->setAsSecureKey();
// Setup conf for a selection of a Product
$formSetup->newItem('EPCQR_MYPARAM8')->setAsProduct();
// Add a title for a new section
$formSetup->newItem('NewSection')->setAsTitle();
$TField = array(
'test01' => $langs->trans('test01'),
'test02' => $langs->trans('test02'),
'test03' => $langs->trans('test03'),
'test04' => $langs->trans('test04'),
'test05' => $langs->trans('test05'),
'test06' => $langs->trans('test06'),
);
// Setup conf for a simple combo list
$formSetup->newItem('EPCQR_MYPARAM9')->setAsSelect($TField);
// Setup conf for a multiselect combo list
$item = $formSetup->newItem('EPCQR_MYPARAM10');
$item->setAsMultiSelect($TField);
$item->helpText = $langs->transnoentities('EPCQR_MYPARAM10');
// Setup conf for a category selection
$formSetup->newItem('EPCQR_CATEGORY_ID_XXX')->setAsCategory('product');
// Setup conf EPCQR_MYPARAM10
$item = $formSetup->newItem('EPCQR_MYPARAM10');
$item->setAsColor();
$item->defaultFieldValue = '#FF0000';
//$item->fieldValue = '';
//$item->fieldAttr = array() ; // fields attribute only for compatible fields like input text
//$item->fieldOverride = false; // set this var to override field output will override $fieldInputOverride and $fieldOutputOverride too
//$item->fieldInputOverride = false; // set this var to override field input
//$item->fieldOutputOverride = false; // set this var to override field output
$item = $formSetup->newItem('EPCQR_MYPARAM11')->setAsHtml();
$item->nameText = $item->getNameText().' more html text ';
$item->fieldInputOverride = '';
$item->helpText = $langs->transnoentities('HelpMessage');
$item->cssClass = 'minwidth500';
$item = $formSetup->newItem('EPCQR_MYPARAM12');
$item->fieldOverride = "Value forced, can't be modified";
$item->cssClass = 'minwidth500';
//$item = $formSetup->newItem('EPCQR_MYPARAM13')->setAsDate(); // Not yet implemented
// End of definition of parameters
$setupnotempty += count($formSetup->items);
$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
$moduledir = 'epcqr';
$myTmpObjects = array();
// TODO Scan list of objects to fill this array
$myTmpObjects['myobject'] = array('label' => 'MyObject', 'includerefgeneration' => 0, 'includedocgeneration' => 0, 'class' => 'MyObject');
$tmpobjectkey = GETPOST('object', 'aZ09');
if ($tmpobjectkey && !array_key_exists($tmpobjectkey, $myTmpObjects)) {
accessforbidden('Bad value for object. Hack attempt ?');
}
/*
* Actions
*/
if ($action == 'update') {
$bankDataSource = GETPOST('EPCQR_BANK_DATA_SOURCE', 'alpha');
$accountHolder = GETPOST('EPCQR_ACCOUNT_HOLDER', 'alphanohtml');
$iban = GETPOST('EPCQR_IBAN', 'alphanohtml');
$bic = GETPOST('EPCQR_BIC', 'alphanohtml');
$imageRatio = GETPOST('EPCQR_IMAGE_RATIO', 'alphanohtml');
// For retrocompatibility Dolibarr < 15.0
if (versioncompare(explode('.', DOL_VERSION), array(15)) < 0 && $action == 'update' && !empty($user->admin)) {
$formSetup->saveConfFromPost();
}
include DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php';
if ($action == 'updateMask') {
$maskconst = GETPOST('maskconst', 'aZ09');
$maskvalue = GETPOST('maskvalue', 'alpha');
if ($maskconst && preg_match('/_MASK$/', $maskconst)) {
$res = dolibarr_set_const($db, $maskconst, $maskvalue, 'chaine', 0, '', $conf->entity);
if (!($res > 0)) {
// Validate IBAN format (basic check)
if ($bankDataSource == 'manual' && !empty($iban)) {
$iban = str_replace(' ', '', strtoupper($iban));
if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$/', $iban)) {
setEventMessages($langs->trans("InvalidIBAN"), null, 'errors');
$error++;
}
}
// Validate BIC format (basic check)
if ($bankDataSource == 'manual' && !empty($bic)) {
$bic = str_replace(' ', '', strtoupper($bic));
if (!preg_match('/^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/', $bic)) {
setEventMessages($langs->trans("InvalidBIC"), null, 'errors');
$error++;
}
}
// Validate image ratio
if (!empty($imageRatio)) {
$imageRatio = str_replace(',', '.', $imageRatio);
if (!is_numeric($imageRatio) || (float)$imageRatio <= 0 || (float)$imageRatio > 2) {
setEventMessages($langs->trans("InvalidImageRatio"), null, 'errors');
$error++;
}
}
// Extra field visibility settings
$hideQrcode = GETPOST('EPCQR_HIDE_QRCODE', 'int') ? 1 : 0;
$hideQrcodepfad = GETPOST('EPCQR_HIDE_QRCODEPFAD', 'int') ? 1 : 0;
$hideQrcodepath = GETPOST('EPCQR_HIDE_QRCODEPATH', 'int') ? 1 : 0;
// QR Code styling settings
$fgColorPost = GETPOST('EPCQR_FG_COLOR', 'alphanohtml');
$bgTransparent = GETPOST('EPCQR_BG_TRANSPARENT', 'int') ? 1 : 0;
$bgColorPost = $bgTransparent ? 'transparent' : GETPOST('EPCQR_BG_COLOR', 'alphanohtml');
$logoPathPost = GETPOST('EPCQR_LOGO_PATH', 'alphanohtml');
$logoSizePost = GETPOST('EPCQR_LOGO_SIZE', 'int');
// Validate colors (hex format)
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $fgColorPost)) {
$fgColorPost = '#000000';
}
if (!$bgTransparent && !preg_match('/^#[0-9A-Fa-f]{6}$/', $bgColorPost)) {
$bgColorPost = '#FFFFFF';
}
// Validate logo size
if ($logoSizePost < 5) $logoSizePost = 5;
if ($logoSizePost > 30) $logoSizePost = 30;
// Module style
$moduleStylePost = GETPOST('EPCQR_MODULE_STYLE', 'alpha');
if (!in_array($moduleStylePost, array('square', 'rounded', 'dots', 'diamond'))) {
$moduleStylePost = 'square';
}
// Border style
$borderStylePost = GETPOST('EPCQR_BORDER_STYLE', 'alpha');
if (!in_array($borderStylePost, array('none', 'simple', 'rounded', 'double', 'dashed'))) {
$borderStylePost = 'none';
}
if (!$error) {
dolibarr_set_const($db, 'EPCQR_BANK_DATA_SOURCE', $bankDataSource, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_ACCOUNT_HOLDER', $accountHolder, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_IBAN', $iban, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_BIC', $bic, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_IMAGE_RATIO', $imageRatio, 'chaine', 0, '', $conf->entity);
// Save visibility settings
dolibarr_set_const($db, 'EPCQR_HIDE_QRCODE', $hideQrcode, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_HIDE_QRCODEPFAD', $hideQrcodepfad, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_HIDE_QRCODEPATH', $hideQrcodepath, 'chaine', 0, '', $conf->entity);
// Save styling settings
dolibarr_set_const($db, 'EPCQR_FG_COLOR', $fgColorPost, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_BG_COLOR', $bgColorPost, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_LOGO_PATH', $logoPathPost, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_LOGO_SIZE', $logoSizePost, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_MODULE_STYLE', $moduleStylePost, 'chaine', 0, '', $conf->entity);
dolibarr_set_const($db, 'EPCQR_BORDER_STYLE', $borderStylePost, 'chaine', 0, '', $conf->entity);
// Update extrafield visibility in database
$extrafieldsToUpdate = array(
'qrcode' => $hideQrcode,
'qrcodepfad' => $hideQrcodepfad,
'qrcodepath' => $hideQrcodepath
);
foreach ($extrafieldsToUpdate as $fieldname => $hidden) {
// enabled = '0' means hidden, '1' means visible
$enabled = $hidden ? '0' : '1';
$sql = "UPDATE ".MAIN_DB_PREFIX."extrafields SET enabled = '".$db->escape($enabled)."'";
$sql .= " WHERE name = '".$db->escape($fieldname)."' AND elementtype = 'facture'";
$db->query($sql);
}
setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
} else {
setEventMessages($langs->trans("Error"), null, 'errors');
}
} elseif ($action == 'specimen' && $tmpobjectkey) {
$modele = GETPOST('module', 'alpha');
$className = $myTmpObjects[$tmpobjectkey]['class'];
$tmpobject = new $className($db);
'@phan-var-force MyObject $tmpobject';
$tmpobject->initAsSpecimen();
// Search template files
$file = '';
$className = '';
$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
foreach ($dirmodels as $reldir) {
$file = dol_buildpath($reldir."core/modules/epcqr/doc/pdf_".$modele."_".strtolower($tmpobjectkey).".modules.php", 0);
if (file_exists($file)) {
$className = "pdf_".$modele."_".strtolower($tmpobjectkey);
break;
}
}
if ($className !== '') {
require_once $file;
$module = new $className($db);
'@phan-var-force ModelePDFMyObject $module';
'@phan-var-force ModelePDFMyObject $module';
if ($module->write_file($tmpobject, $langs) > 0) {
header("Location: ".DOL_URL_ROOT."/document.php?modulepart=epcqr-".strtolower($tmpobjectkey)."&file=SPECIMEN.pdf");
return;
} else {
setEventMessages($module->error, null, 'errors');
dol_syslog($module->error, LOG_ERR);
// Clear cache action
if ($action == 'clearcache') {
require_once __DIR__.'/../lib/qrcode.class.php';
$qrGen = new QRCodeGenerator($db);
$deleted = $qrGen->clearCache();
setEventMessages($langs->trans("CacheCleared", $deleted), null, 'mesgs');
}
} else {
setEventMessages($langs->trans("ErrorModuleNotFound"), null, 'errors');
dol_syslog($langs->trans("ErrorModuleNotFound"), LOG_ERR);
}
} elseif ($action == 'setmod') {
// TODO Check if numbering module chosen can be activated by calling method canBeActivated
if (!empty($tmpobjectkey)) {
$constforval = 'EPCQR_'.strtoupper($tmpobjectkey)."_ADDON";
dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity);
}
} elseif ($action == 'set') {
// Activate a model
$ret = addDocumentModel($value, $type, $label, $scandir);
} elseif ($action == 'del') {
$ret = delDocumentModel($value, $type);
if ($ret > 0) {
if (!empty($tmpobjectkey)) {
$constforval = 'EPCQR_'.strtoupper($tmpobjectkey).'_ADDON_PDF';
if (getDolGlobalString($constforval) == "$value") {
dolibarr_del_const($db, $constforval, $conf->entity);
}
}
}
} elseif ($action == 'setdoc') {
// Set or unset default model
if (!empty($tmpobjectkey)) {
$constforval = 'EPCQR_'.strtoupper($tmpobjectkey).'_ADDON_PDF';
if (dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity)) {
// The constant that was read before the new set
// We therefore requires a variable to have a coherent view
$conf->global->{$constforval} = $value;
}
// We disable/enable the document template (into llx_document_model table)
$ret = delDocumentModel($value, $type);
if ($ret > 0) {
$ret = addDocumentModel($value, $type, $label, $scandir);
}
}
} elseif ($action == 'unsetdoc') {
if (!empty($tmpobjectkey)) {
$constforval = 'EPCQR_'.strtoupper($tmpobjectkey).'_ADDON_PDF';
dolibarr_del_const($db, $constforval, $conf->entity);
}
}
$action = 'edit';
/*
@ -323,13 +205,12 @@ $action = 'edit';
$form = new Form($db);
$help_url = '';
$title = "EpcqrSetup";
llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-epcqr page-admin');
llxHeader('', $langs->trans($title), '', '', 0, 0, '', '', '', 'mod-epcqr page-admin');
// Subheader
$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.$langs->trans("BackToModuleList").'</a>';
$linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
@ -337,289 +218,282 @@ print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
$head = epcqrAdminPrepareHead();
print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "epcqr@epcqr");
// Setup page goes here
echo '<span class="opacitymedium">'.$langs->trans("EpcqrSetupPage").'</span><br><br>';
// Current values
$bankDataSource = getDolGlobalString('EPCQR_BANK_DATA_SOURCE', 'manual');
$accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', '');
$iban = getDolGlobalString('EPCQR_IBAN', '');
$bic = getDolGlobalString('EPCQR_BIC', '');
$imageRatio = getDolGlobalString('EPCQR_IMAGE_RATIO', '0.3');
// Extra field visibility settings
$hideQrcode = getDolGlobalInt('EPCQR_HIDE_QRCODE', 0);
$hideQrcodepfad = getDolGlobalInt('EPCQR_HIDE_QRCODEPFAD', 0);
$hideQrcodepath = getDolGlobalInt('EPCQR_HIDE_QRCODEPATH', 0);
/*if ($action == 'edit') {
print $formSetup->generateOutput(true);
print '<br>';
} elseif (!empty($formSetup->items)) {
print $formSetup->generateOutput();
print '<div class="tabsAction">';
print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=edit&token='.newToken().'">'.$langs->trans("Modify").'</a>';
print '</div>';
}
*/
if (!empty($formSetup->items)) {
print $formSetup->generateOutput(true);
print '<br>';
}
// QR Code styling settings
$fgColor = getDolGlobalString('EPCQR_FG_COLOR', '#000000');
$bgColor = getDolGlobalString('EPCQR_BG_COLOR', '#FFFFFF');
$logoPath = getDolGlobalString('EPCQR_LOGO_PATH', '');
$logoSize = getDolGlobalInt('EPCQR_LOGO_SIZE', 20);
$moduleStyle = getDolGlobalString('EPCQR_MODULE_STYLE', 'square');
$borderStyle = getDolGlobalString('EPCQR_BORDER_STYLE', 'none');
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="update">';
foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) {
if (!empty($myTmpObjectArray['includerefgeneration'])) {
// Numbering models
$setupnotempty++;
print load_fiche_titre($langs->trans("NumberingModules", $myTmpObjectArray['label']), '', '');
// Bank Data Section
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td>'.$langs->trans("Name").'</td>';
print '<td>'.$langs->trans("Description").'</td>';
print '<td class="nowrap">'.$langs->trans("Example").'</td>';
print '<td class="center" width="60">'.$langs->trans("Status").'</td>';
print '<td class="center" width="16">'.$langs->trans("ShortInfo").'</td>';
print '</tr>'."\n";
print '<td colspan="2">'.$langs->trans("BankDataSettings").'</td>';
print '</tr>';
clearstatcache();
foreach ($dirmodels as $reldir) {
$dir = dol_buildpath($reldir."core/modules/".$moduledir);
if (is_dir($dir)) {
$handle = opendir($dir);
if (is_resource($handle)) {
while (($file = readdir($handle)) !== false) {
if (strpos($file, 'mod_'.strtolower($myTmpObjectKey).'_') === 0 && substr($file, dol_strlen($file) - 3, 3) == 'php') {
$file = substr($file, 0, dol_strlen($file) - 4);
require_once $dir.'/'.$file.'.php';
$module = new $file($db);
'@phan-var-force ModeleNumRefMyObject $module';
// Show modules according to features level
if ($module->version == 'development' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) {
continue;
}
if ($module->version == 'experimental' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1) {
continue;
}
if ($module->isEnabled()) {
dol_include_once('/'.$moduledir.'/class/'.strtolower($myTmpObjectKey).'.class.php');
print '<tr class="oddeven"><td>'.$module->getName($langs)."</td><td>\n";
print $module->info($langs);
// Bank Data Source Selection
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("BankDataSource").'</td>';
print '<td>';
print '<select name="EPCQR_BANK_DATA_SOURCE" id="bankDataSource" class="flat minwidth200">';
print '<option value="system"'.($bankDataSource == 'system' ? ' selected' : '').'>'.$langs->trans("UseSystemBankAccount").'</option>';
print '<option value="manual"'.($bankDataSource == 'manual' ? ' selected' : '').'>'.$langs->trans("ManualEntry").'</option>';
print '</select>';
print '</td>';
print '</tr>';
// Show example of numbering model
print '<td class="nowrap">';
$tmp = $module->getExample();
if (preg_match('/^Error/', $tmp)) {
$langs->load("errors");
print '<div class="error">'.$langs->trans($tmp).'</div>';
} elseif ($tmp == 'NotConfigured') {
print $langs->trans($tmp);
} else {
print $tmp;
}
print '</td>'."\n";
print '<td class="center">';
$constforvar = 'EPCQR_'.strtoupper($myTmpObjectKey).'_ADDON';
$defaultifnotset = 'thevaluetousebydefault';
$activenumberingmodel = getDolGlobalString($constforvar, $defaultifnotset);
if ($activenumberingmodel == $file) {
print img_picto($langs->trans("Activated"), 'switch_on');
} else {
print '<a href="'.$_SERVER["PHP_SELF"].'?action=setmod&token='.newToken().'&object='.strtolower($myTmpObjectKey).'&value='.urlencode($file).'">';
print img_picto($langs->trans("Disabled"), 'switch_off');
print '</a>';
}
// Manual Bank Data Fields
print '<tr class="oddeven manual-fields">';
print '<td>'.$langs->trans("AccountHolder").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_ACCOUNT_HOLDER" class="flat minwidth300" value="'.dol_escape_htmltag($accountHolder).'" placeholder="'.$langs->trans("AccountHolderPlaceholder").'">';
print '</td>';
print '</tr>';
$className = $myTmpObjectArray['class'];
$mytmpinstance = new $className($db);
'@phan-var-force MyObject $mytmpinstance';
$mytmpinstance->initAsSpecimen();
// Info
$htmltooltip = '';
$htmltooltip .= ''.$langs->trans("Version").': <b>'.$module->getVersion().'</b><br>';
$nextval = $module->getNextValue($mytmpinstance);
if ("$nextval" != $langs->trans("NotAvailable")) { // Keep " on nextval
$htmltooltip .= ''.$langs->trans("NextValue").': ';
if ($nextval) {
if (preg_match('/^Error/', $nextval) || $nextval == 'NotConfigured') {
$nextval = $langs->trans($nextval);
}
$htmltooltip .= $nextval.'<br>';
} else {
$htmltooltip .= $langs->trans($module->error).'<br>';
}
}
print '<td class="center">';
print $form->textwithpicto('', $htmltooltip, 1, 'info');
print '<tr class="oddeven manual-fields">';
print '<td>'.$langs->trans("IBAN").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_IBAN" class="flat minwidth300" value="'.dol_escape_htmltag($iban).'" placeholder="DE89 3704 0044 0532 0130 00">';
print '</td>';
print '</tr>';
print "</tr>\n";
}
}
}
closedir($handle);
}
}
}
print "</table><br>\n";
}
if (!empty($myTmpObjectArray['includedocgeneration'])) {
/*
* Document templates generators
*/
$setupnotempty++;
$type = strtolower($myTmpObjectKey);
print load_fiche_titre($langs->trans("DocumentModules", $myTmpObjectKey), '', '');
// Load array def with activated templates
$def = array();
$sql = "SELECT nom";
$sql .= " FROM ".$db->prefix()."document_model";
$sql .= " WHERE type = '".$db->escape($type)."'";
$sql .= " AND entity = ".$conf->entity;
$resql = $db->query($sql);
if ($resql) {
$i = 0;
$num_rows = $db->num_rows($resql);
while ($i < $num_rows) {
$array = $db->fetch_array($resql);
array_push($def, $array[0]);
$i++;
}
} else {
dol_print_error($db);
}
print '<table class="noborder centpercent">'."\n";
print '<tr class="liste_titre">'."\n";
print '<td>'.$langs->trans("Name").'</td>';
print '<td>'.$langs->trans("Description").'</td>';
print '<td class="center" width="60">'.$langs->trans("Status")."</td>\n";
print '<td class="center" width="60">'.$langs->trans("Default")."</td>\n";
print '<td class="center" width="38">'.$langs->trans("ShortInfo").'</td>';
print '<td class="center" width="38">'.$langs->trans("Preview").'</td>';
print "</tr>\n";
clearstatcache();
foreach ($dirmodels as $reldir) {
foreach (array('', '/doc') as $valdir) {
$realpath = $reldir."core/modules/".$moduledir.$valdir;
$dir = dol_buildpath($realpath);
if (is_dir($dir)) {
$handle = opendir($dir);
if (is_resource($handle)) {
$filelist = array();
while (($file = readdir($handle)) !== false) {
$filelist[] = $file;
}
closedir($handle);
arsort($filelist);
foreach ($filelist as $file) {
if (preg_match('/\.modules\.php$/i', $file) && preg_match('/^(pdf_|doc_)/', $file)) {
if (file_exists($dir.'/'.$file)) {
$name = substr($file, 4, dol_strlen($file) - 16);
$className = substr($file, 0, dol_strlen($file) - 12);
require_once $dir.'/'.$file;
$module = new $className($db);
'@phan-var-force ModelePDFMyObject $module';
$modulequalified = 1;
if ($module->version == 'development' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) {
$modulequalified = 0;
}
if ($module->version == 'experimental' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1) {
$modulequalified = 0;
}
if ($modulequalified) {
print '<tr class="oddeven"><td width="100">';
print(empty($module->name) ? $name : $module->name);
print "</td><td>\n";
if (method_exists($module, 'info')) {
print $module->info($langs); // @phan-suppress-current-line PhanUndeclaredMethod
} else {
print $module->description;
}
print '<tr class="oddeven manual-fields">';
print '<td>'.$langs->trans("BIC").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_BIC" class="flat minwidth200" value="'.dol_escape_htmltag($bic).'" placeholder="COBADEFFXXX">';
print '</td>';
// Active
if (in_array($name, $def)) {
print '<td class="center">'."\n";
print '<a href="'.$_SERVER["PHP_SELF"].'?action=del&token='.newToken().'&value='.urlencode($name).'">';
print img_picto($langs->trans("Enabled"), 'switch_on');
print '</a>';
print '</td>';
} else {
print '<td class="center">'."\n";
print '<a href="'.$_SERVER["PHP_SELF"].'?action=set&token='.newToken().'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
print "</td>";
}
// Default
print '<td class="center">';
$constforvar = 'EPCQR_'.strtoupper($myTmpObjectKey).'_ADDON_PDF';
if (getDolGlobalString($constforvar) == $name) {
//print img_picto($langs->trans("Default"), 'on');
// Even if choice is the default value, we allow to disable it. Replace this with previous line if you need to disable unset
print '<a href="'.$_SERVER["PHP_SELF"].'?action=unsetdoc&token='.newToken().'&object='.urlencode(strtolower($myTmpObjectKey)).'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&label='.urlencode($module->name).'&amp;type='.urlencode($type).'" alt="'.$langs->trans("Disable").'">'.img_picto($langs->trans("Enabled"), 'on').'</a>';
} else {
print '<a href="'.$_SERVER["PHP_SELF"].'?action=setdoc&token='.newToken().'&object='.urlencode(strtolower($myTmpObjectKey)).'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';
}
print '</td>';
// Info
$htmltooltip = ''.$langs->trans("Name").': '.$module->name;
$htmltooltip .= '<br>'.$langs->trans("Type").': '.($module->type ? $module->type : $langs->trans("Unknown"));
if ($module->type == 'pdf') {
$htmltooltip .= '<br>'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur;
}
$htmltooltip .= '<br>'.$langs->trans("Path").': '.preg_replace('/^\//', '', $realpath).'/'.$file;
$htmltooltip .= '<br><br><u>'.$langs->trans("FeaturesSupported").':</u>';
$htmltooltip .= '<br>'.$langs->trans("Logo").': '.yn($module->option_logo, 1, 1);
$htmltooltip .= '<br>'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang, 1, 1);
print '<td class="center">';
print $form->textwithpicto('', $htmltooltip, 1, 'info');
print '</td>';
// Preview
print '<td class="center">';
if ($module->type == 'pdf') {
$newname = preg_replace('/_'.preg_quote(strtolower($myTmpObjectKey), '/').'/', '', $name);
print '<a href="'.$_SERVER["PHP_SELF"].'?action=specimen&module='.urlencode($newname).'&object='.urlencode($myTmpObjectKey).'">'.img_object($langs->trans("Preview"), 'pdf').'</a>';
} else {
print img_object($langs->transnoentitiesnoconv("PreviewNotAvailable"), 'generic');
}
print '</td>';
print "</tr>\n";
}
}
}
}
}
}
}
}
print '</tr>';
print '</table>';
// QR Code Settings Section
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td colspan="2">'.$langs->trans("QRCodeSettings").'</td>';
print '</tr>';
// Image Ratio
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("QRCodeSize").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_IMAGE_RATIO" class="flat width75" value="'.dol_escape_htmltag($imageRatio).'">';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeSizeHelp").'</span>';
print '</td>';
print '</tr>';
// Foreground Color
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeFgColor").'</td>';
print '<td>';
print '<input type="color" name="EPCQR_FG_COLOR" value="'.dol_escape_htmltag($fgColor).'" style="width: 60px; height: 30px; padding: 0; border: 1px solid #ccc;">';
print ' <input type="text" name="EPCQR_FG_COLOR_TEXT" class="flat width100" value="'.dol_escape_htmltag($fgColor).'" onchange="document.getElementsByName(\'EPCQR_FG_COLOR\')[0].value=this.value">';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeFgColorHelp").'</span>';
print '</td>';
print '</tr>';
// Background Color
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeBgColor").'</td>';
print '<td>';
$isTransparent = (strtolower($bgColor) === 'transparent' || $bgColor === '');
print '<input type="checkbox" name="EPCQR_BG_TRANSPARENT" id="bgTransparent" value="1"'.($isTransparent ? ' checked' : '').' onchange="toggleBgColor(this.checked)">';
print ' <label for="bgTransparent">'.$langs->trans("Transparent").'</label>';
print ' &nbsp; ';
print '<input type="color" name="EPCQR_BG_COLOR" id="bgColorPicker" value="'.dol_escape_htmltag($isTransparent ? '#FFFFFF' : $bgColor).'" style="width: 60px; height: 30px; padding: 0; border: 1px solid #ccc;"'.($isTransparent ? ' disabled' : '').'>';
print ' <input type="text" name="EPCQR_BG_COLOR_TEXT" id="bgColorText" class="flat width100" value="'.dol_escape_htmltag($isTransparent ? '' : $bgColor).'"'.($isTransparent ? ' disabled' : '').' onchange="document.getElementById(\'bgColorPicker\').value=this.value">';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeBgColorHelp").'</span>';
print '</td>';
print '</tr>';
print '<script>
function toggleBgColor(isTransparent) {
document.getElementById("bgColorPicker").disabled = isTransparent;
document.getElementById("bgColorText").disabled = isTransparent;
if (isTransparent) {
document.getElementById("bgColorText").value = "";
}
}
</script>';
// Logo Path
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeLogo").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_LOGO_PATH" class="flat minwidth400" value="'.dol_escape_htmltag($logoPath).'" placeholder="/var/www/dolibarr/documents/mycompany/logos/logo.png">';
print '<br><span class="opacitymedium">'.$langs->trans("QRCodeLogoHelp").'</span>';
if (!empty($logoPath)) {
if (file_exists($logoPath)) {
print '<br><span style="color: green;">✓ '.$langs->trans("FileExists").'</span>';
} else {
print '<br><span style="color: red;">✗ '.$langs->trans("FileNotFound").'</span>';
}
}
print '</td>';
print '</tr>';
// Logo Size
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeLogoSize").'</td>';
print '<td>';
print '<input type="number" name="EPCQR_LOGO_SIZE" class="flat width75" value="'.dol_escape_htmltag($logoSize).'" min="5" max="30">';
print ' % <span class="opacitymedium">'.$langs->trans("QRCodeLogoSizeHelp").'</span>';
print '</td>';
print '</tr>';
// Module Style
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeModuleStyle").'</td>';
print '<td>';
print '<select name="EPCQR_MODULE_STYLE" class="flat minwidth150">';
print '<option value="square"'.($moduleStyle == 'square' ? ' selected' : '').'>'.$langs->trans("StyleSquare").'</option>';
print '<option value="rounded"'.($moduleStyle == 'rounded' ? ' selected' : '').'>'.$langs->trans("StyleRounded").'</option>';
print '<option value="dots"'.($moduleStyle == 'dots' ? ' selected' : '').'>'.$langs->trans("StyleDots").'</option>';
print '<option value="diamond"'.($moduleStyle == 'diamond' ? ' selected' : '').'>'.$langs->trans("StyleDiamond").'</option>';
print '</select>';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeModuleStyleHelp").'</span>';
print '</td>';
print '</tr>';
// Border Style
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeBorderStyle").'</td>';
print '<td>';
print '<select name="EPCQR_BORDER_STYLE" class="flat minwidth150">';
print '<option value="none"'.($borderStyle == 'none' ? ' selected' : '').'>'.$langs->trans("BorderNone").'</option>';
print '<option value="simple"'.($borderStyle == 'simple' ? ' selected' : '').'>'.$langs->trans("BorderSimple").'</option>';
print '<option value="rounded"'.($borderStyle == 'rounded' ? ' selected' : '').'>'.$langs->trans("BorderRounded").'</option>';
print '<option value="double"'.($borderStyle == 'double' ? ' selected' : '').'>'.$langs->trans("BorderDouble").'</option>';
print '<option value="dashed"'.($borderStyle == 'dashed' ? ' selected' : '').'>'.$langs->trans("BorderDashed").'</option>';
print '</select>';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeBorderStyleHelp").'</span>';
print '</td>';
print '</tr>';
print '</table>';
// Clear Cache Button
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td colspan="2">'.$langs->trans("QRCodeCache").'</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("ClearQRCodeCache").'</td>';
print '<td>';
print '<a class="button" href="'.$_SERVER["PHP_SELF"].'?action=clearcache&token='.newToken().'">'.$langs->trans("ClearCache").'</a>';
print ' <span class="opacitymedium">'.$langs->trans("ClearCacheHelp").'</span>';
print '</td>';
print '</tr>';
print '</table>';
// Extra Field Visibility Section
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td colspan="2">'.$langs->trans("ExtraFieldVisibility").'</td>';
print '</tr>';
// Hide QR Code field (HTML with image)
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("HideQRCodeField").'</td>';
print '<td>';
print '<input type="checkbox" name="EPCQR_HIDE_QRCODE" value="1"'.($hideQrcode ? ' checked' : '').'>';
print ' <span class="opacitymedium">'.$langs->trans("HideQRCodeFieldHelp").'</span>';
print '</td>';
print '</tr>';
// Hide QR Code URL field
print '<tr class="oddeven">';
print '<td>'.$langs->trans("HideQRCodePfadField").'</td>';
print '<td>';
print '<input type="checkbox" name="EPCQR_HIDE_QRCODEPFAD" value="1"'.($hideQrcodepfad ? ' checked' : '').'>';
print ' <span class="opacitymedium">'.$langs->trans("HideQRCodePfadFieldHelp").'</span>';
print '</td>';
print '</tr>';
// Hide QR Code Path field (local file path)
print '<tr class="oddeven">';
print '<td>'.$langs->trans("HideQRCodePathField").'</td>';
print '<td>';
print '<input type="checkbox" name="EPCQR_HIDE_QRCODEPATH" value="1"'.($hideQrcodepath ? ' checked' : '').'>';
print ' <span class="opacitymedium">'.$langs->trans("HideQRCodePathFieldHelp").'</span>';
print '</td>';
print '</tr>';
print '</table>';
// Submit button
print '<br>';
print '<div class="center">';
print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
print '</div>';
print '</form>';
// JavaScript to show/hide manual fields based on selection
print '<script type="text/javascript">
$(document).ready(function() {
function toggleManualFields() {
var source = $("#bankDataSource").val();
if (source == "manual") {
$(".manual-fields").show();
} else {
$(".manual-fields").hide();
}
}
if (empty($setupnotempty)) {
print '<br>'.$langs->trans("NothingToSetup");
toggleManualFields();
$("#bankDataSource").on("change", function() {
toggleManualFields();
});
});
</script>';
// Info box about system bank account
if ($bankDataSource == 'system') {
print '<br>';
print '<div class="info">';
print $langs->trans("SystemBankAccountInfo");
// Try to show configured bank accounts
require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
$bankAccount = new Account($db);
$sql = "SELECT rowid, label, iban_prefix, bic FROM ".MAIN_DB_PREFIX."bank_account WHERE entity = ".((int) $conf->entity)." AND clos = 0";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
print '<br><br><strong>'.$langs->trans("AvailableBankAccounts").':</strong><ul>';
while ($obj = $db->fetch_object($resql)) {
print '<li>'.$obj->label;
if (!empty($obj->iban_prefix)) {
print ' - IBAN: '.substr($obj->iban_prefix, 0, 4).'****'.substr($obj->iban_prefix, -4);
}
print '</li>';
}
print '</ul>';
} else {
print '<br><br><span class="warning">'.$langs->trans("NoBankAccountConfigured").'</span>';
}
print '</div>';
}
// Page end

View file

@ -101,6 +101,72 @@ class ActionsEpcqr
return 0;
}
/**
* Hook vor ODT-Speicherung
* Fügt QR-Code-Bild in ODT ein bevor es gespeichert wird
*
* @param array $parameters Hook-Parameter (enthält odfHandler)
* @param object $object Dolibarr-Objekt
* @param string $action Aktuelle Aktion
* @return int 0 = OK, <0 = Fehler
*/
public function beforeODTSave($parameters, &$object, &$action)
{
global $conf;
dol_syslog("EPCQR Hook: beforeODTSave aufgerufen", LOG_DEBUG);
// Prüfen ob odfHandler vorhanden ist
if (empty($parameters['odfHandler'])) {
dol_syslog("EPCQR Hook: Kein odfHandler in Parametern", LOG_DEBUG);
return 0;
}
$odfHandler = $parameters['odfHandler'];
// Das Rechnungsobjekt ist in $parameters['object'], nicht in $object!
$invoice = isset($parameters['object']) ? $parameters['object'] : null;
// Prüfen ob Objekt gültig ist
if (!is_object($invoice) || empty($invoice->id)) {
dol_syslog("EPCQR Hook: Ungültiges Rechnungsobjekt in parameters", LOG_DEBUG);
return 0;
}
dol_syslog("EPCQR Hook: Verarbeite Rechnung ".$invoice->ref, LOG_DEBUG);
// Extrafelder laden falls nicht vorhanden
if (empty($invoice->array_options)) {
$invoice->fetch_optionals();
}
// QR-Code-Pfad aus Extrafeld holen
$qrcodepath = '';
if (isset($invoice->array_options['options_qrcodepath'])) {
$qrcodepath = $invoice->array_options['options_qrcodepath'];
}
dol_syslog("EPCQR Hook: QR-Code-Pfad aus Extrafeld: ".$qrcodepath, LOG_DEBUG);
if (empty($qrcodepath) || !file_exists($qrcodepath)) {
dol_syslog("EPCQR Hook: Kein QR-Code-Pfad oder Datei nicht gefunden: ".$qrcodepath, LOG_WARNING);
return 0;
}
// QR-Code als Bild in ODT einfügen
try {
// setImage erwartet: Variablenname (ohne Klammern), Bildpfad, Größenverhältnis
// Ratio 0.3 = ca. 2-3cm bei typischen QR-Code-Größen
$ratio = getDolGlobalString('EPCQR_IMAGE_RATIO', '0.3');
$odfHandler->setImage('qrcode', $qrcodepath, (float) $ratio);
dol_syslog("EPCQR Hook: QR-Code-Bild erfolgreich eingefügt: ".$qrcodepath." (ratio: ".$ratio.")", LOG_INFO);
} catch (Exception $e) {
dol_syslog("EPCQR Hook: Fehler beim Einfügen des QR-Codes: ".$e->getMessage(), LOG_ERR);
}
return 0;
}
/**
* Hook für PDF-Generierung (falls später benötigt)
*

View file

@ -66,17 +66,17 @@ class modEpcqr extends DolibarrModules
// DESCRIPTION_FLAG
// Module description, used if translation string 'ModuleEpcqrDesc' not found (Epcqr is name of module).
$this->description = 'QRCode wird generiert und als Bild sowie URL in den Extra Feldern qrcode qrcodepfad hinterlegt';
$this->description = 'Generiert EPC QR-Codes für SEPA-Überweisungen auf Rechnungen';
// Used only if file README.md and README-LL.md not found.
$this->descriptionlong = "EpcqrDescription";
$this->descriptionlong = "Generiert automatisch EPC QR-Codes (GiroCode) für Rechnungen. Unterstützt individuelle Farben, Logos, verschiedene Modul-Stile und dekorative Rahmen. Die QR-Codes werden als Extrafelder gespeichert und können in ODT-Vorlagen eingefügt werden.";
// Author
$this->editor_name = 'Alles Watt läuft';
$this->editor_url = ''; // Must be an external online web site
$this->editor_name = 'DATA IT-Solutions';
$this->editor_url = 'https://data-it-solution.de'; // Must be an external online web site
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@epcqr'
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
$this->version = '1.5';
$this->version = '2.0';
// Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
@ -87,7 +87,7 @@ class modEpcqr extends DolibarrModules
// If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
// If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
// To use a supported fa-xxx css style of font awesome, use this->picto='xxx'
$this->picto = 'fa-file';
$this->picto = 'fa-qrcode';
// Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
$this->module_parts = array(

View file

@ -34,14 +34,14 @@ function epcqr_completesubstitutionarray(&$substitutionarray, $langs, $object, $
{
global $conf, $db;
dol_syslog("EPCQR: START completesubstitutionarray für ".get_class($object), LOG_DEBUG);
// Prüfen ob Objekt gültig ist
if (!is_object($object) || empty($object->id)) {
dol_syslog("EPCQR: Object ist null oder hat keine ID - überspringe", LOG_DEBUG);
return;
}
dol_syslog("EPCQR: START completesubstitutionarray für ".get_class($object), LOG_DEBUG);
// QR-Code Pfad aus Extra-Feldern
$qrCodePath = '';

96
langs/de_DE/epcqr.lang Normal file
View file

@ -0,0 +1,96 @@
# Translation file - German
#
# Generic
#
# Module label 'ModuleEpcqrName'
ModuleEpcqrName = EPC QR-Code
# Module description 'ModuleEpcqrDesc'
ModuleEpcqrDesc = EPC QR-Codes für Rechnungen generieren (SEPA-Zahlung)
#
# Admin page
#
EpcqrSetup = EPC QR-Code Einstellungen
Settings = Einstellungen
Debug = Debug
EpcqrSetupPage = EPC QR-Code Modul Einstellungen
# Bank Data Settings
BankDataSettings = Bankverbindung
BankDataSource = Datenquelle
UseSystemBankAccount = Systembankkonto verwenden
ManualEntry = Manuelle Eingabe
AccountHolder = Kontoinhaber
AccountHolderPlaceholder = Firmenname
IBAN = IBAN
BIC = BIC/SWIFT
InvalidIBAN = Ungültiges IBAN-Format
InvalidBIC = Ungültiges BIC/SWIFT-Format
SystemBankAccountInfo = Die Bankdaten werden aus der System-Bankkonto-Konfiguration übernommen.
AvailableBankAccounts = Verfügbare Bankkonten
NoBankAccountConfigured = Kein Bankkonto im System konfiguriert.
# QR Code Settings
QRCodeSettings = QR-Code Einstellungen
QRCodeSize = QR-Code Größe (Verhältnis)
QRCodeSizeHelp = Größenverhältnis für QR-Code in ODT-Dokumenten (0.05 = sehr klein, 0.1 = klein, 0.3 = mittel, 0.5 = groß)
InvalidImageRatio = Ungültiges Größenverhältnis. Muss eine Zahl zwischen 0.01 und 2 sein.
# QR Code Styling
QRCodeFgColor = Vordergrundfarbe
QRCodeFgColorHelp = Farbe der QR-Code-Module (Standard: schwarz)
QRCodeBgColor = Hintergrundfarbe
QRCodeBgColorHelp = Hintergrundfarbe des QR-Codes (Standard: weiß)
Transparent = Transparent
QRCodeLogo = Logo (optional)
QRCodeLogoHelp = Absoluter Pfad zu einem Logo-Bild (PNG, JPG, GIF). Das Logo wird in der Mitte des QR-Codes platziert.
QRCodeLogoSize = Logo-Größe
QRCodeLogoSizeHelp = Größe des Logos in Prozent der QR-Code-Größe (5-30%, Standard: 20%)
FileExists = Datei gefunden
FileNotFound = Datei nicht gefunden
QRCodeModuleStyle = Modul-Stil
QRCodeModuleStyleHelp = Form der QR-Code-Module
StyleSquare = Quadrat (Standard)
StyleRounded = Abgerundet
StyleDots = Punkte
StyleDiamond = Diamant
# QR Code Border
QRCodeBorderStyle = Rahmen-Stil
QRCodeBorderStyleHelp = Rahmen um den QR-Code
BorderNone = Kein Rahmen
BorderSimple = Einfach
BorderRounded = Abgerundet
BorderDouble = Doppelt
BorderDashed = Gestrichelt
# QR Code Cache
QRCodeCache = QR-Code Cache
ClearQRCodeCache = Cache leeren
ClearCache = Cache leeren
ClearCacheHelp = Löscht alle gecachten QR-Codes. Notwendig nach Style-Änderungen.
CacheCleared = %s QR-Codes aus dem Cache gelöscht.
# Extra Field Visibility
ExtraFieldVisibility = Extrafeld-Sichtbarkeit (Rechnung)
HideQRCodeField = QR-Code (HTML) ausblenden
HideQRCodeFieldHelp = Blendet das QR-Code-Bildfeld im Rechnungsformular aus
HideQRCodePfadField = QR-Code URL ausblenden
HideQRCodePfadFieldHelp = Blendet das QR-Code-URL-Feld im Rechnungsformular aus
HideQRCodePathField = QR-Code Pfad ausblenden
HideQRCodePathFieldHelp = Blendet das lokale Dateipfad-Feld im Rechnungsformular aus
#
# About page
#
About = Über
EpcqrAbout = Über EPC QR-Code
EpcqrAboutPage = EPC QR-Code Modul Informationen
#
# Sample page
#
EpcqrArea = EPC QR-Code
MyPageName = Meine Seite

View file

@ -5,34 +5,94 @@
#
# Module label 'ModuleEpcqrName'
ModuleEpcqrName = Epcqr
ModuleEpcqrName = EPC QR Code
# Module description 'ModuleEpcqrDesc'
ModuleEpcqrDesc = Epcqr description
ModuleEpcqrDesc = Generate EPC QR codes for invoices (SEPA payment)
#
# Admin page
#
EpcqrSetup = Epcqr setup
EpcqrSetup = EPC QR Code Setup
Settings = Settings
EpcqrSetupPage = Epcqr setup page
NewSection=New section
EPCQR_MYPARAM1 = My param 1
EPCQR_MYPARAM1Tooltip = My param 1 tooltip
EPCQR_MYPARAM2=My param 2
EPCQR_MYPARAM2Tooltip=My param 2 tooltip
Debug = Debug
EpcqrSetupPage = EPC QR Code module settings
# Bank Data Settings
BankDataSettings = Bank Account Settings
BankDataSource = Bank Data Source
UseSystemBankAccount = Use System Bank Account
ManualEntry = Manual Entry
AccountHolder = Account Holder
AccountHolderPlaceholder = Company Name
IBAN = IBAN
BIC = BIC/SWIFT
InvalidIBAN = Invalid IBAN format
InvalidBIC = Invalid BIC/SWIFT format
SystemBankAccountInfo = Bank data will be retrieved from the system bank account configuration.
AvailableBankAccounts = Available Bank Accounts
NoBankAccountConfigured = No bank account configured in the system.
# QR Code Settings
QRCodeSettings = QR Code Settings
QRCodeSize = QR Code Size (Ratio)
QRCodeSizeHelp = Size ratio for QR code in ODT documents (0.05 = very small, 0.1 = small, 0.3 = medium, 0.5 = large)
InvalidImageRatio = Invalid size ratio. Must be a number between 0.01 and 2.
# QR Code Styling
QRCodeFgColor = Foreground Color
QRCodeFgColorHelp = Color of QR code modules (default: black)
QRCodeBgColor = Background Color
QRCodeBgColorHelp = Background color of QR code (default: white)
Transparent = Transparent
QRCodeLogo = Logo (optional)
QRCodeLogoHelp = Absolute path to a logo image (PNG, JPG, GIF). The logo will be placed in the center of the QR code.
QRCodeLogoSize = Logo Size
QRCodeLogoSizeHelp = Size of the logo as percentage of QR code size (5-30%, default: 20%)
FileExists = File found
FileNotFound = File not found
QRCodeModuleStyle = Module Style
QRCodeModuleStyleHelp = Shape of QR code modules
StyleSquare = Square (Default)
StyleRounded = Rounded
StyleDots = Dots
StyleDiamond = Diamond
# QR Code Border
QRCodeBorderStyle = Border Style
QRCodeBorderStyleHelp = Border around the QR code
BorderNone = No Border
BorderSimple = Simple
BorderRounded = Rounded
BorderDouble = Double
BorderDashed = Dashed
# QR Code Cache
QRCodeCache = QR Code Cache
ClearQRCodeCache = Clear Cache
ClearCache = Clear Cache
ClearCacheHelp = Deletes all cached QR codes. Required after style changes.
CacheCleared = %s QR codes deleted from cache.
# Extra Field Visibility
ExtraFieldVisibility = Extra Field Visibility (Invoice)
HideQRCodeField = Hide QR Code (HTML)
HideQRCodeFieldHelp = Hides the QR code image field on invoice form
HideQRCodePfadField = Hide QR Code URL
HideQRCodePfadFieldHelp = Hides the QR code URL field on invoice form
HideQRCodePathField = Hide QR Code Path
HideQRCodePathFieldHelp = Hides the local file path field on invoice form
#
# About page
#
About = About
EpcqrAbout = About Epcqr
EpcqrAboutPage = Epcqr about page
EpcqrAbout = About EPC QR Code
EpcqrAboutPage = EPC QR Code module information
#
# Sample page
#
EpcqrArea = Home Epcqr
EpcqrArea = EPC QR Code
MyPageName = My page name
#

View file

@ -30,10 +30,6 @@ function epcqrAdminPrepareHead()
{
global $langs, $conf;
// global $db;
// $extrafields = new ExtraFields($db);
// $extrafields->fetch_name_optionals_label('myobject');
$langs->load("epcqr@epcqr");
$h = 0;
@ -44,41 +40,17 @@ function epcqrAdminPrepareHead()
$head[$h][2] = 'settings';
$h++;
/*
$head[$h][0] = dol_buildpath("/epcqr/admin/myobject_extrafields.php", 1);
$head[$h][1] = $langs->trans("ExtraFields");
$nbExtrafields = (isset($extrafields->attributes['myobject']['label']) && is_countable($extrafields->attributes['myobject']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0;
if ($nbExtrafields > 0) {
$head[$h][1] .= '<span class="badge marginleftonlyshort">' . $nbExtrafields . '</span>';
}
$head[$h][2] = 'myobject_extrafields';
$head[$h][0] = dol_buildpath("/epcqr/test_qrcode.php", 1);
$head[$h][1] = $langs->trans("Debug");
$head[$h][2] = 'debug';
$h++;
$head[$h][0] = dol_buildpath("/epcqr/admin/myobjectline_extrafields.php", 1);
$head[$h][1] = $langs->trans("ExtraFieldsLines");
$nbExtrafields = (isset($extrafields->attributes['myobjectline']['label']) && is_countable($extrafields->attributes['myobjectline']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0;
if ($nbExtrafields > 0) {
$head[$h][1] .= '<span class="badge marginleftonlyshort">' . $nbExtrafields . '</span>';
}
$head[$h][2] = 'myobject_extrafieldsline';
$h++;
*/
$head[$h][0] = dol_buildpath("/epcqr/admin/about.php", 1);
$head[$h][1] = $langs->trans("About");
$head[$h][2] = 'about';
$h++;
// Show more tabs from modules
// Entries must be declared in modules descriptor with line
//$this->tabs = array(
// 'entity:+tabname:Title:@epcqr:/epcqr/mypage.php?id=__ID__'
//); // to add new tab
//$this->tabs = array(
// 'entity:-tabname:Title:@epcqr:/epcqr/mypage.php?id=__ID__'
//); // to remove a tab
complete_head_from_modules($conf, $langs, null, $head, $h, 'epcqr@epcqr');
complete_head_from_modules($conf, $langs, null, $head, $h, 'epcqr@epcqr', 'remove');
return $head;
@ -102,11 +74,47 @@ function epcqr_generateQRCodeForInvoice($invoice, $db)
$qrGen = new QRCodeGenerator($db);
// Bankverbindung aus Konfiguration laden
// TODO: Diese Werte sollten aus der Modul-Konfiguration kommen
$accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', 'Eduard Wisch');
$iban = getDolGlobalString('EPCQR_IBAN', 'DE70217625500013438147');
$bic = getDolGlobalString('EPCQR_BIC', 'GENODEF1HUM');
// Bankverbindung laden - entweder aus System oder manuell
$bankDataSource = getDolGlobalString('EPCQR_BANK_DATA_SOURCE', 'manual');
if ($bankDataSource == 'system') {
// Bankdaten aus Systembankkonto laden
require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
$accountHolder = '';
$iban = '';
$bic = '';
// Erstes aktives Bankkonto mit IBAN suchen
$sql = "SELECT rowid, label, proprio, iban_prefix, bic FROM ".MAIN_DB_PREFIX."bank_account";
$sql .= " WHERE entity = ".((int) $conf->entity)." AND clos = 0 AND iban_prefix IS NOT NULL AND iban_prefix != ''";
$sql .= " ORDER BY rowid ASC LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
$obj = $db->fetch_object($resql);
$accountHolder = !empty($obj->proprio) ? $obj->proprio : $obj->label;
$iban = $obj->iban_prefix;
$bic = $obj->bic;
dol_syslog("EPCQR: Bankdaten aus System geladen - Inhaber: ".$accountHolder.", IBAN: ".substr($iban, 0, 4)."****", LOG_DEBUG);
} else {
dol_syslog("EPCQR: Kein Systembankkonto gefunden, verwende manuelle Einstellungen", LOG_WARNING);
$accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', '');
$iban = getDolGlobalString('EPCQR_IBAN', '');
$bic = getDolGlobalString('EPCQR_BIC', '');
}
} else {
// Manuelle Bankverbindung aus Konfiguration laden
$accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', '');
$iban = getDolGlobalString('EPCQR_IBAN', '');
$bic = getDolGlobalString('EPCQR_BIC', '');
}
// Prüfen ob Bankdaten vollständig sind
if (empty($accountHolder) || empty($iban)) {
dol_syslog("EPCQR: Bankdaten unvollständig - Kontoinhaber oder IBAN fehlt", LOG_ERR);
return false;
}
// Betrag und Referenz aus Rechnung
$amount = price2num($invoice->total_ttc, 'MT');
@ -130,8 +138,8 @@ function epcqr_generateQRCodeForInvoice($invoice, $db)
$invoice->array_options['options_qrcode'] = "<img src='".$qrCodeUrl."' width='100px'>";
$invoice->insertExtraFields();
// qrcodepfad: URL zum QR-Code (Kompatibilität mit alter Version)
$invoice->array_options['options_qrcodepfad'] = $qrCodeUrl;
// qrcodepfad: URL zum QR-Code als klickbarer Link
$invoice->array_options['options_qrcodepfad'] = '<a href="'.$qrCodeUrl.'" target="_blank">'.$qrCodeUrl.'</a>';
$invoice->insertExtraFields();
dol_syslog("EPCQR: QR-Code erfolgreich generiert: ".$qrCodePath, LOG_INFO);

View file

@ -18,18 +18,30 @@
/**
* \file lib/qrcode.class.php
* \ingroup epcqr
* \brief QR-Code Generator mit lokalem Caching
* \brief QR-Code Generator mit lokalem Caching und Styling
*/
/**
* QRCode Generator Klasse
* Generiert QR-Codes und cached sie lokal für Wiederverwendung
* Generiert QR-Codes mit optionalen Styling-Optionen (Farben, Logo)
*/
class QRCodeGenerator
{
private $db;
private $cacheDir;
// Styling-Optionen
private $fgColor = array(0, 0, 0); // Vordergrundfarbe (schwarz)
private $bgColor = array(255, 255, 255); // Hintergrundfarbe (weiß)
private $bgTransparent = false; // Transparenter Hintergrund
private $logoPath = ''; // Pfad zum Logo
private $logoSize = 20; // Logo-Größe in Prozent (20% der QR-Code-Größe)
private $pixelSize = 8; // Pixel-Größe für Module
private $moduleStyle = 'square'; // Modul-Stil: square, rounded, dots, diamond
private $borderStyle = 'none'; // Rahmen-Stil: none, simple, rounded, double, dashed
private $borderWidth = 3; // Rahmenbreite in Pixeln
private $borderPadding = 8; // Abstand zwischen QR-Code und Rahmen
/**
* Constructor
*
@ -48,6 +60,70 @@ class QRCodeGenerator
if (!is_dir($this->cacheDir)) {
dol_mkdir($this->cacheDir);
}
// Styling aus Konfiguration laden
$this->loadStyleFromConfig();
}
/**
* Lädt Styling-Einstellungen aus der Dolibarr-Konfiguration
*/
private function loadStyleFromConfig()
{
// Vordergrundfarbe (Hex -> RGB)
$fgHex = getDolGlobalString('EPCQR_FG_COLOR', '#000000');
$this->fgColor = $this->hexToRgb($fgHex);
// Hintergrundfarbe (Hex -> RGB) oder transparent
$bgHex = getDolGlobalString('EPCQR_BG_COLOR', '#FFFFFF');
if (strtolower($bgHex) === 'transparent' || $bgHex === '') {
$this->bgTransparent = true;
$this->bgColor = array(255, 255, 255); // Fallback für Logo-Hintergrund
} else {
$this->bgTransparent = false;
$this->bgColor = $this->hexToRgb($bgHex);
}
// Logo-Pfad
$this->logoPath = getDolGlobalString('EPCQR_LOGO_PATH', '');
// Logo-Größe (Prozent)
$this->logoSize = getDolGlobalInt('EPCQR_LOGO_SIZE', 20);
if ($this->logoSize < 5) $this->logoSize = 5;
if ($this->logoSize > 30) $this->logoSize = 30; // Max 30% wegen Fehlerkorrektur
// Modul-Stil
$this->moduleStyle = getDolGlobalString('EPCQR_MODULE_STYLE', 'square');
if (!in_array($this->moduleStyle, array('square', 'rounded', 'dots', 'diamond'))) {
$this->moduleStyle = 'square';
}
// Rahmen-Stil
$this->borderStyle = getDolGlobalString('EPCQR_BORDER_STYLE', 'none');
if (!in_array($this->borderStyle, array('none', 'simple', 'rounded', 'double', 'dashed'))) {
$this->borderStyle = 'none';
}
}
/**
* Konvertiert Hex-Farbe zu RGB-Array
*
* @param string $hex Hex-Farbcode (z.B. #FF0000)
* @return array RGB-Array [r, g, b]
*/
private function hexToRgb($hex)
{
$hex = ltrim($hex, '#');
if (strlen($hex) == 3) {
$hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
}
return array(
hexdec(substr($hex, 0, 2)),
hexdec(substr($hex, 2, 2)),
hexdec(substr($hex, 4, 2))
);
}
/**
@ -62,8 +138,9 @@ class QRCodeGenerator
*/
public function generateEPCQRCode($accountHolder, $iban, $bic, $amount, $reference)
{
// Eindeutigen Dateinamen generieren basierend auf Parametern
$hash = md5($accountHolder.$iban.$bic.$amount.$reference);
// Eindeutigen Dateinamen generieren basierend auf Parametern UND Styling
$styleHash = md5(implode('', $this->fgColor).implode('', $this->bgColor).($this->bgTransparent ? 'T' : 'F').$this->logoPath.$this->logoSize.$this->moduleStyle.$this->borderStyle);
$hash = md5($accountHolder.$iban.$bic.$amount.$reference.$styleHash);
$filename = 'epc_'.$hash.'.png';
$filepath = $this->cacheDir.'/'.$filename;
@ -96,7 +173,8 @@ class QRCodeGenerator
public function generateQRCode($data, $prefix = 'qr')
{
// Eindeutigen Dateinamen generieren
$hash = md5($data);
$styleHash = md5(implode('', $this->fgColor).implode('', $this->bgColor).($this->bgTransparent ? 'T' : 'F').$this->logoPath.$this->logoSize.$this->moduleStyle.$this->borderStyle);
$hash = md5($data.$styleHash);
$filename = $prefix.'_'.$hash.'.png';
$filepath = $this->cacheDir.'/'.$filename;
@ -149,10 +227,7 @@ class QRCodeGenerator
}
/**
* Generiert QR-Code-Bild aus Daten
*
* Nutzt zunächst den externen Service, später kann dies durch
* eine native PHP-Implementierung ersetzt werden
* Generiert QR-Code-Bild aus Daten mit Styling
*
* @param string $data Daten für QR-Code
* @param string $filepath Zielpfad für PNG-Datei
@ -160,36 +235,604 @@ class QRCodeGenerator
*/
private function generateQRImage($data, $filepath)
{
// Methode 1: Externe API (aktuell)
// TODO: Später durch native PHP-Generierung ersetzen
$url = 'https://qr.data-it-solution.de/generate?data='.urlencode($data).'&size=300';
// TCPDF 2D Barcode Bibliothek laden
require_once DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf_barcodes_2d.php';
// Bild von URL holen
$imageData = @file_get_contents($url);
try {
// QR-Code mit hoher Fehlerkorrektur erstellen (H = 30% - notwendig für Logo)
$errorLevel = !empty($this->logoPath) && file_exists($this->logoPath) ? 'H' : 'M';
$barcodeobj = new TCPDF2DBarcode($data, 'QRCODE,'.$errorLevel);
// PNG-Daten generieren
$imageData = $barcodeobj->getBarcodePngData($this->pixelSize, $this->pixelSize, $this->fgColor);
if ($imageData === false) {
// Fallback: Versuche mit cURL
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$imageData = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
dol_syslog("QRCodeGenerator: TCPDF konnte kein PNG generieren", LOG_ERR);
return false;
}
if ($httpCode !== 200 || $imageData === false) {
return false;
// Bild aus String erstellen für weitere Verarbeitung
$image = imagecreatefromstring($imageData);
if ($image === false) {
// Fallback: Direkt speichern ohne Styling
$result = file_put_contents($filepath, $imageData);
return ($result !== false);
}
// Modul-Stil anwenden (rounded, dots, diamond) oder Standard-Verarbeitung
if ($this->moduleStyle !== 'square') {
$image = $this->applyModuleStyle($image);
} else {
return false;
// Standard: Hintergrund anwenden (transparent oder Farbe)
if ($this->bgTransparent) {
$image = $this->applyTransparentBackground($image);
} elseif ($this->bgColor != array(255, 255, 255)) {
$image = $this->applyBackgroundColor($image);
}
}
// Logo hinzufügen (wenn konfiguriert)
if (!empty($this->logoPath) && file_exists($this->logoPath)) {
$image = $this->addLogo($image);
}
// Rahmen hinzufügen (wenn konfiguriert)
if ($this->borderStyle !== 'none') {
$image = $this->addBorder($image);
}
// Bild speichern
$result = file_put_contents($filepath, $imageData);
$result = imagepng($image, $filepath);
imagedestroy($image);
return ($result !== false);
return $result;
} catch (Exception $e) {
dol_syslog("QRCodeGenerator: Exception bei QR-Generierung: ".$e->getMessage(), LOG_ERR);
return false;
}
}
/**
* Wendet Hintergrundfarbe auf das Bild an
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild
*/
private function applyBackgroundColor($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Neues Bild mit Hintergrundfarbe erstellen
$newImage = imagecreatetruecolor($width, $height);
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
imagefill($newImage, 0, 0, $bgColorAlloc);
// Vordergrundfarbe einmal allokieren
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// QR-Code-Module (dunkle Pixel) kopieren
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$rgb = imagecolorat($image, $x, $y);
// Farbwerte extrahieren (funktioniert für indexed und truecolor)
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
// Helligkeit berechnen (0 = schwarz, 255 = weiß)
$brightness = ($r + $g + $b) / 3;
// Dunkle Pixel (QR-Code-Module) = Vordergrundfarbe setzen
if ($brightness < 128) {
imagesetpixel($newImage, $x, $y, $fgColorAlloc);
}
}
}
imagedestroy($image);
return $newImage;
}
/**
* Wendet transparenten Hintergrund auf das Bild an
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit Transparenz
*/
private function applyTransparentBackground($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Neues Bild mit Alpha-Kanal erstellen
$newImage = imagecreatetruecolor($width, $height);
// Alpha-Blending deaktivieren und Alpha speichern aktivieren
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
// Transparente Farbe erstellen und füllen
$transparent = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
imagefill($newImage, 0, 0, $transparent);
// Für das Zeichnen Alpha-Blending aktivieren
imagealphablending($newImage, true);
// Vordergrundfarbe einmal allokieren
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// QR-Code-Module (dunkle Pixel) erkennen und kopieren
// Verwendet Helligkeitswert statt exakte Farbprüfung für bessere Kompatibilität
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$rgb = imagecolorat($image, $x, $y);
// Farbwerte extrahieren (funktioniert für indexed und truecolor)
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
// Helligkeit berechnen (0 = schwarz, 255 = weiß)
$brightness = ($r + $g + $b) / 3;
// Dunkle Pixel (QR-Code-Module) = Helligkeit < 128
// Helle Pixel (Hintergrund) = transparent lassen
if ($brightness < 128) {
imagesetpixel($newImage, $x, $y, $fgColorAlloc);
}
}
}
// Alpha speichern nochmal sicherstellen
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
imagedestroy($image);
return $newImage;
}
/**
* Wendet Modul-Stil auf das QR-Code-Bild an (rounded, dots, diamond)
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit gestylten Modulen
*/
private function applyModuleStyle($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Modul-Matrix extrahieren (welche Module sind dunkel)
$moduleMatrix = $this->extractModuleMatrix($image);
$moduleCount = count($moduleMatrix);
if ($moduleCount == 0) {
return $image;
}
// Neue Bildgröße berechnen (etwas Padding hinzufügen)
$moduleSize = $this->pixelSize;
$newWidth = $moduleCount * $moduleSize;
$newHeight = $moduleCount * $moduleSize;
// Neues Bild erstellen
$newImage = imagecreatetruecolor($newWidth, $newHeight);
// Alpha für transparenten Hintergrund
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
} else {
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
imagefill($newImage, 0, 0, $bgColorAlloc);
if ($this->bgTransparent) {
imagealphablending($newImage, true);
}
// Vordergrundfarbe
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// Module zeichnen
for ($row = 0; $row < $moduleCount; $row++) {
for ($col = 0; $col < $moduleCount; $col++) {
if (!empty($moduleMatrix[$row][$col])) {
$x = $col * $moduleSize;
$y = $row * $moduleSize;
switch ($this->moduleStyle) {
case 'rounded':
$this->drawRoundedModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
case 'dots':
$this->drawDotModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
case 'diamond':
$this->drawDiamondModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
default:
imagefilledrectangle($newImage, $x, $y, $x + $moduleSize - 1, $y + $moduleSize - 1, $fgColorAlloc);
}
}
}
}
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
}
imagedestroy($image);
return $newImage;
}
/**
* Extrahiert die Modul-Matrix aus dem QR-Code-Bild
*
* @param resource $image GD-Bild-Ressource
* @return array 2D-Array mit true/false für jedes Modul
*/
private function extractModuleMatrix($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Anzahl der Module berechnen
$moduleCount = (int)($width / $this->pixelSize);
$matrix = array();
for ($row = 0; $row < $moduleCount; $row++) {
$matrix[$row] = array();
for ($col = 0; $col < $moduleCount; $col++) {
// Mitte des Moduls samplen
$x = ($col * $this->pixelSize) + (int)($this->pixelSize / 2);
$y = ($row * $this->pixelSize) + (int)($this->pixelSize / 2);
if ($x < $width && $y < $height) {
$rgb = imagecolorat($image, $x, $y);
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
$brightness = ($r + $g + $b) / 3;
$matrix[$row][$col] = ($brightness < 128);
} else {
$matrix[$row][$col] = false;
}
}
}
return $matrix;
}
/**
* Zeichnet ein abgerundetes Modul
*/
private function drawRoundedModule($image, $x, $y, $size, $color)
{
$radius = (int)($size * 0.3); // 30% Radius für Rundung
// Gefülltes Rechteck mit abgerundeten Ecken simulieren
// Hauptrechteck (ohne Ecken)
imagefilledrectangle($image, $x + $radius, $y, $x + $size - $radius - 1, $y + $size - 1, $color);
imagefilledrectangle($image, $x, $y + $radius, $x + $size - 1, $y + $size - $radius - 1, $color);
// Ecken als gefüllte Kreise
imagefilledellipse($image, $x + $radius, $y + $radius, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $size - $radius - 1, $y + $radius, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $radius, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $size - $radius - 1, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
}
/**
* Zeichnet ein kreisförmiges Modul (Punkt)
*/
private function drawDotModule($image, $x, $y, $size, $color)
{
$centerX = $x + (int)($size / 2);
$centerY = $y + (int)($size / 2);
$diameter = $size - 2; // Etwas kleiner für Abstand
imagefilledellipse($image, $centerX, $centerY, $diameter, $diameter, $color);
}
/**
* Zeichnet ein rautenförmiges Modul (Diamant)
*/
private function drawDiamondModule($image, $x, $y, $size, $color)
{
$centerX = $x + (int)($size / 2);
$centerY = $y + (int)($size / 2);
$half = (int)($size / 2) - 1;
$points = array(
$centerX, $y + 1, // Oben
$x + $size - 2, $centerY, // Rechts
$centerX, $y + $size - 2, // Unten
$x + 1, $centerY // Links
);
imagefilledpolygon($image, $points, $color);
}
/**
* Fügt Logo in die Mitte des QR-Codes ein
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild
*/
private function addLogo($image)
{
// Logo laden
$logoInfo = getimagesize($this->logoPath);
if ($logoInfo === false) {
dol_syslog("QRCodeGenerator: Logo konnte nicht gelesen werden: ".$this->logoPath, LOG_WARNING);
return $image;
}
$logoType = $logoInfo[2];
$logo = null;
switch ($logoType) {
case IMAGETYPE_PNG:
$logo = imagecreatefrompng($this->logoPath);
break;
case IMAGETYPE_JPEG:
$logo = imagecreatefromjpeg($this->logoPath);
break;
case IMAGETYPE_GIF:
$logo = imagecreatefromgif($this->logoPath);
break;
default:
dol_syslog("QRCodeGenerator: Nicht unterstütztes Logo-Format", LOG_WARNING);
return $image;
}
if ($logo === false) {
return $image;
}
$qrWidth = imagesx($image);
$qrHeight = imagesy($image);
$logoOrigWidth = imagesx($logo);
$logoOrigHeight = imagesy($logo);
// Logo-Größe berechnen (Prozent der QR-Code-Größe)
$logoNewWidth = (int)($qrWidth * $this->logoSize / 100);
$logoNewHeight = (int)($logoOrigHeight * ($logoNewWidth / $logoOrigWidth));
// Logo-Position (Mitte)
$logoX = (int)(($qrWidth - $logoNewWidth) / 2);
$logoY = (int)(($qrHeight - $logoNewHeight) / 2);
// Hintergrund für Logo-Bereich (mit etwas Padding)
// Nur wenn NICHT transparent - bei transparentem Hintergrund kein Rechteck zeichnen
$padding = 4;
if (!$this->bgTransparent) {
$bgColorAlloc = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
imagefilledrectangle(
$image,
$logoX - $padding,
$logoY - $padding,
$logoX + $logoNewWidth + $padding,
$logoY + $logoNewHeight + $padding,
$bgColorAlloc
);
} else {
// Bei transparentem Hintergrund: Bereich mit Transparenz füllen
imagealphablending($image, false);
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
imagefilledrectangle(
$image,
$logoX - $padding,
$logoY - $padding,
$logoX + $logoNewWidth + $padding,
$logoY + $logoNewHeight + $padding,
$transparent
);
imagealphablending($image, true);
}
// Logo skaliert einfügen
imagecopyresampled(
$image,
$logo,
$logoX,
$logoY,
0,
0,
$logoNewWidth,
$logoNewHeight,
$logoOrigWidth,
$logoOrigHeight
);
imagedestroy($logo);
return $image;
}
/**
* Fügt einen Rahmen um den QR-Code hinzu
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit Rahmen
*/
private function addBorder($image)
{
$origWidth = imagesx($image);
$origHeight = imagesy($image);
// Neue Bildgröße mit Padding und Rahmen
$totalPadding = $this->borderPadding + $this->borderWidth;
$newWidth = $origWidth + ($totalPadding * 2);
$newHeight = $origHeight + ($totalPadding * 2);
// Neues Bild erstellen
$newImage = imagecreatetruecolor($newWidth, $newHeight);
// Alpha für transparenten Hintergrund
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
} else {
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
imagefill($newImage, 0, 0, $bgColorAlloc);
if ($this->bgTransparent) {
imagealphablending($newImage, true);
}
// Rahmenfarbe (Vordergrundfarbe)
$borderColor = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// Rahmen zeichnen je nach Stil
switch ($this->borderStyle) {
case 'simple':
$this->drawSimpleBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'rounded':
$this->drawRoundedBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'double':
$this->drawDoubleBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'dashed':
$this->drawDashedBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
}
// Original-Bild in die Mitte kopieren
imagecopy($newImage, $image, $totalPadding, $totalPadding, 0, 0, $origWidth, $origHeight);
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
}
imagedestroy($image);
return $newImage;
}
/**
* Zeichnet einen einfachen Rahmen
*/
private function drawSimpleBorder($image, $width, $height, $color)
{
imagesetthickness($image, $this->borderWidth);
$offset = (int)($this->borderWidth / 2);
imagerectangle($image, $offset, $offset, $width - $offset - 1, $height - $offset - 1, $color);
}
/**
* Zeichnet einen abgerundeten Rahmen
*/
private function drawRoundedBorder($image, $width, $height, $color)
{
$radius = 15;
$thickness = $this->borderWidth;
// Dicke Linien für die geraden Seiten
imagesetthickness($image, $thickness);
$offset = (int)($thickness / 2);
// Obere Linie (ohne Ecken)
imageline($image, $radius, $offset, $width - $radius, $offset, $color);
// Untere Linie
imageline($image, $radius, $height - $offset - 1, $width - $radius, $height - $offset - 1, $color);
// Linke Linie
imageline($image, $offset, $radius, $offset, $height - $radius, $color);
// Rechte Linie
imageline($image, $width - $offset - 1, $radius, $width - $offset - 1, $height - $radius, $color);
// Abgerundete Ecken als Bögen
imagesetthickness($image, $thickness);
// Oben-links
imagearc($image, $radius, $radius, $radius * 2, $radius * 2, 180, 270, $color);
// Oben-rechts
imagearc($image, $width - $radius - 1, $radius, $radius * 2, $radius * 2, 270, 360, $color);
// Unten-links
imagearc($image, $radius, $height - $radius - 1, $radius * 2, $radius * 2, 90, 180, $color);
// Unten-rechts
imagearc($image, $width - $radius - 1, $height - $radius - 1, $radius * 2, $radius * 2, 0, 90, $color);
}
/**
* Zeichnet einen doppelten Rahmen
*/
private function drawDoubleBorder($image, $width, $height, $color)
{
$gap = 4; // Abstand zwischen den Linien
imagesetthickness($image, 2);
// Äußerer Rahmen
imagerectangle($image, 1, 1, $width - 2, $height - 2, $color);
// Innerer Rahmen
imagerectangle($image, 1 + $gap, 1 + $gap, $width - 2 - $gap, $height - 2 - $gap, $color);
}
/**
* Zeichnet einen gestrichelten Rahmen
*/
private function drawDashedBorder($image, $width, $height, $color)
{
$dashLength = 8;
$gapLength = 4;
// Transparente Farbe für Lücken
if ($this->bgTransparent) {
$gapColor = imagecolorallocatealpha($image, 0, 0, 0, 127);
} else {
$gapColor = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
// Strichmuster erstellen
$style = array();
for ($i = 0; $i < $dashLength; $i++) {
$style[] = $color;
}
for ($i = 0; $i < $gapLength; $i++) {
$style[] = $gapColor;
}
imagesetstyle($image, $style);
imagesetthickness($image, $this->borderWidth);
$offset = (int)($this->borderWidth / 2);
// Rahmen mit Stil zeichnen
imageline($image, $offset, $offset, $width - $offset, $offset, IMG_COLOR_STYLED);
imageline($image, $width - $offset - 1, $offset, $width - $offset - 1, $height - $offset, IMG_COLOR_STYLED);
imageline($image, $width - $offset, $height - $offset - 1, $offset, $height - $offset - 1, IMG_COLOR_STYLED);
imageline($image, $offset, $height - $offset, $offset, $offset, IMG_COLOR_STYLED);
}
/**
@ -225,4 +868,14 @@ class QRCodeGenerator
dol_syslog("QRCodeGenerator: ".$deleted." alte QR-Codes gelöscht", LOG_INFO);
return $deleted;
}
/**
* Löscht alle gecachten QR-Codes (für Style-Änderungen)
*
* @return int Anzahl gelöschter Dateien
*/
public function clearCache()
{
return $this->cleanCache(0);
}
}

View file

@ -1,3 +1,27 @@
-- Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
--
-- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version.
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- EPCQR-Modul - Script wird bei jedem Dolibarr-Upgrade ausgeführt
-- Stellt sicher, dass alle Extrafelder existieren
--
-- Extrafelder für Rechnungen anlegen (ON DUPLICATE KEY UPDATE verhindert Fehler)
-- Extrafeld: qrcode (HTML-Anzeige)
INSERT INTO llx_extrafields (name, label, type, size, elementtype, fieldunique, fieldrequired, pos, alwayseditable, param, enabled, perms, list, totalizable, printable, langs, help, entity)
VALUES ('qrcode', 'EPC QR-Code', 'html', '500', 'facture', 0, 0, 800, 0, '', '1', '', '1', 0, 0, NULL, 'QR-Code als HTML-Bild', 0)
ON DUPLICATE KEY UPDATE label = 'EPC QR-Code';
-- Extrafeld: qrcodepfad (URL)
INSERT INTO llx_extrafields (name, label, type, size, elementtype, fieldunique, fieldrequired, pos, alwayseditable, param, enabled, perms, list, totalizable, printable, langs, help, entity)
VALUES ('qrcodepfad', 'QR-Code URL', 'html', '500', 'facture', 0, 0, 801, 0, '', '1', '', '0', 0, 0, NULL, 'URL zum QR-Code', 0)
ON DUPLICATE KEY UPDATE label = 'QR-Code URL';
-- Extrafeld: qrcodepath (Lokaler Pfad für ODT)
INSERT INTO llx_extrafields (name, label, type, size, elementtype, fieldunique, fieldrequired, pos, alwayseditable, param, enabled, perms, list, totalizable, printable, langs, help, entity)
VALUES ('qrcodepath', 'QR-Code Pfad', 'varchar', '255', 'facture', 0, 0, 802, 0, '', '1', '', '0', 0, 0, NULL, 'Lokaler Pfad zur QR-Code-Bilddatei', 0)
ON DUPLICATE KEY UPDATE label = 'QR-Code Pfad';

View file

@ -0,0 +1,104 @@
-- Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see https://www.gnu.org/licenses/.
--
-- EPCQR-Modul Version 2.0 - Initiale Installation
-- Erstellt alle notwendigen Extrafelder für die QR-Code-Funktionalität
--
-- ============================================
-- Extrafeld 1: qrcode (HTML-Anzeige mit <img>-Tag)
-- ============================================
INSERT INTO llx_extrafields (
name, label, type, size, elementtype,
fieldunique, fieldrequired, pos, alwayseditable,
param, enabled, perms, list, totalizable, printable,
langs, help, entity
) VALUES (
'qrcode',
'EPC QR-Code',
'html',
'500',
'facture',
0, 0, 800, 0,
'', '1', '', '1', 0, 0,
NULL,
'QR-Code als HTML-Bild für Anzeige im Formular',
0
)
ON DUPLICATE KEY UPDATE
label = 'EPC QR-Code',
type = 'html',
size = '500';
-- ============================================
-- Extrafeld 2: qrcodepfad (URL zum QR-Code)
-- ============================================
INSERT INTO llx_extrafields (
name, label, type, size, elementtype,
fieldunique, fieldrequired, pos, alwayseditable,
param, enabled, perms, list, totalizable, printable,
langs, help, entity
) VALUES (
'qrcodepfad',
'QR-Code URL',
'html',
'500',
'facture',
0, 0, 801, 0,
'', '1', '', '0', 0, 0,
NULL,
'URL zum QR-Code-Bild (klickbarer Link)',
0
)
ON DUPLICATE KEY UPDATE
label = 'QR-Code URL',
type = 'html',
size = '500';
-- ============================================
-- Extrafeld 3: qrcodepath (Lokaler Dateipfad für ODT)
-- ============================================
INSERT INTO llx_extrafields (
name, label, type, size, elementtype,
fieldunique, fieldrequired, pos, alwayseditable,
param, enabled, perms, list, totalizable, printable,
langs, help, entity
) VALUES (
'qrcodepath',
'QR-Code Bildpfad (lokal)',
'varchar',
'255',
'facture',
0, 0, 802, 0,
'', '1', '', '0', 0, 0,
NULL,
'Lokaler Pfad zur QR-Code-Bilddatei für ODT-Integration',
0
)
ON DUPLICATE KEY UPDATE
label = 'QR-Code Bildpfad (lokal)',
type = 'varchar',
size = '255';
-- ============================================
-- Tabellen-Spalten in facture_extrafields
-- ============================================
-- MySQL/MariaDB: ADD COLUMN IF NOT EXISTS erfordert MariaDB 10.0.2+
-- Für ältere Versionen werden Fehler ignoriert
ALTER TABLE llx_facture_extrafields ADD COLUMN qrcode TEXT DEFAULT NULL;
ALTER TABLE llx_facture_extrafields ADD COLUMN qrcodepfad TEXT DEFAULT NULL;
ALTER TABLE llx_facture_extrafields ADD COLUMN qrcodepath VARCHAR(255) DEFAULT NULL;

View file

@ -39,7 +39,6 @@ INSERT INTO llx_extrafields (
printable,
langs,
help,
computed,
entity
) VALUES (
'qrcodepath',
@ -59,7 +58,6 @@ INSERT INTO llx_extrafields (
0,
NULL,
'Lokaler Pfad zur QR-Code-Bilddatei für ODT-Integration',
'',
0
)
ON DUPLICATE KEY UPDATE
@ -67,6 +65,10 @@ ON DUPLICATE KEY UPDATE
type = 'varchar',
size = '255';
-- Spalte in facture_extrafields anlegen (falls nicht vorhanden)
-- Diese Spalte speichert den tatsächlichen Wert für jede Rechnung
ALTER TABLE llx_facture_extrafields ADD COLUMN IF NOT EXISTS qrcodepath VARCHAR(255) DEFAULT NULL;
-- Hinweis: Die bestehenden Extrafelder 'qrcode' und 'qrcodepfad' bleiben
-- aus Kompatibilitätsgründen bestehen:
-- - qrcode: HTML-Version mit <img>-Tag

View file

@ -30,7 +30,7 @@ print '</td></tr>';
// 2. Modul-Konfiguration prüfen
print '<tr><td>Substitutionen aktiviert</td><td>';
$module_parts = $conf->modules_parts;
$subst_active = isset($module_parts['substitutions']) && in_array('epcqr', $module_parts['substitutions']);
$subst_active = isset($module_parts['substitutions']['epcqr']) || (isset($module_parts['substitutions']) && is_array($module_parts['substitutions']) && in_array('epcqr', $module_parts['substitutions']));
print $subst_active ? '<span style="color: green;">✓ Ja</span>' : '<span style="color: red;">✗ Nein (in modEpcqr.class.php aktivieren!)</span>';
print '</td></tr>';
@ -196,10 +196,12 @@ print '<h2>7. Letzte Log-Einträge (EPCQR)</h2>';
$logFile = DOL_DATA_ROOT.'/dolibarr.log';
if (file_exists($logFile)) {
$lines = file($logFile);
// Nur die letzten 500 Zeilen lesen um Memory-Probleme zu vermeiden
$epcqrLogs = array();
$output = array();
exec('tail -500 '.escapeshellarg($logFile).' 2>/dev/null', $output);
foreach (array_reverse($lines) as $line) {
foreach (array_reverse($output) as $line) {
if (stripos($line, 'EPCQR') !== false || stripos($line, 'epcqr') !== false) {
$epcqrLogs[] = $line;
if (count($epcqrLogs) >= 10) break;
@ -209,11 +211,11 @@ if (file_exists($logFile)) {
if (!empty($epcqrLogs)) {
print '<pre style="background: #f5f5f5; padding: 10px; border: 1px solid #ddd; overflow-x: auto;">';
foreach (array_reverse($epcqrLogs) as $log) {
print htmlspecialchars($log);
print htmlspecialchars($log)."\n";
}
print '</pre>';
} else {
print '<p>Keine EPCQR-Log-Einträge gefunden.</p>';
print '<p>Keine EPCQR-Log-Einträge in den letzten 500 Zeilen gefunden.</p>';
}
} else {
print '<p style="color: orange;">Log-Datei nicht gefunden: '.$logFile.'</p>';