v1.2: Fix name-Substitution, neuer fullname-Platzhalter, Code-Refactoring

- Fix: {*_contact_name} liefert jetzt Nachname statt Vorname
- Neu: {*_contact_fullname} (Vorname + Nachname, MAIN_FIRSTNAME_NAME_POSITION)
- Refactoring: eine JOIN-Query statt N+1, Mapping-Array, Elementtyp-Check
- Dokumentation aktualisiert

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-02-23 18:20:32 +01:00
parent af72845f5c
commit e8694aa0a4
3 changed files with 85 additions and 129 deletions

View file

@ -1,5 +1,12 @@
# CHANGELOG MODULE DELIVERYINVOICEADDRESS FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
1.1
## 1.2
- Fix: `{*_contact_name}` liefert jetzt Nachname statt Vorname
- Neu: `{*_contact_fullname}` Platzhalter (Vorname + Nachname, berücksichtigt MAIN_FIRSTNAME_NAME_POSITION)
- Refactoring: Kontaktdaten per JOIN in einer Query statt N+1 Queries
- Code aufgeräumt: Mapping-Array statt if/elseif, Null-Coalescing, weniger Logging
## 1.1
Initial version

View file

@ -8,7 +8,7 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
- Lieferadresse für Versanddokumente
- Serviceadresse für Servicedokumente
- Automatische Erkennung des Dokumenttyps (Angebot/Auftrag/Rechnung)
- Fallback auf Kundenadresse wenn kein spezieller Kontakt vorhanden
- CUSTOMER-Kontakt als Fallback für BILLING bei Angeboten/Aufträgen
## Installation
@ -30,7 +30,8 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
| Tag | Beschreibung |
|-----|--------------|
| `{billing_contact_firstname}` | Vorname |
| `{billing_contact_name}` | Vorname (Fallback: Nachname) |
| `{billing_contact_name}` | Nachname |
| `{billing_contact_fullname}` | Vorname + Nachname (berücksichtigt `MAIN_FIRSTNAME_NAME_POSITION`) |
| `{billing_contact_address}` | Straße und Hausnummer |
| `{billing_contact_zip}` | Postleitzahl |
| `{billing_contact_town}` | Ort |
@ -41,7 +42,8 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
| Tag | Beschreibung |
|-----|--------------|
| `{delivery_contact_firstname}` | Vorname |
| `{delivery_contact_name}` | Vorname (Fallback: Nachname) |
| `{delivery_contact_name}` | Nachname |
| `{delivery_contact_fullname}` | Vorname + Nachname |
| `{delivery_contact_address}` | Straße und Hausnummer |
| `{delivery_contact_zip}` | Postleitzahl |
| `{delivery_contact_town}` | Ort |
@ -52,7 +54,8 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
| Tag | Beschreibung |
|-----|--------------|
| `{service_contact_firstname}` | Vorname |
| `{service_contact_name}` | Vorname (Fallback: Nachname) |
| `{service_contact_name}` | Nachname |
| `{service_contact_fullname}` | Vorname + Nachname |
| `{service_contact_address}` | Straße und Hausnummer |
| `{service_contact_zip}` | Postleitzahl |
| `{service_contact_town}` | Ort |
@ -63,22 +66,22 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
### Beispiel: Rechnungsadresse mit Fallback auf Kundenadresse
```
[!-- IF {billing_contact_name} --]
{billing_contact_name}
[!-- IF {billing_contact_fullname} --]
{billing_contact_fullname}
{billing_contact_address}
{billing_contact_zip} {billing_contact_town}
[!-- ELSE {billing_contact_name} --]
[!-- ELSE {billing_contact_fullname} --]
{company_name}
{company_address}
{company_zip} {company_town}
[!-- ENDIF {billing_contact_name} --]
[!-- ENDIF {billing_contact_fullname} --]
```
### Beispiel: Zwei Adressen nebeneinander
```
Rechnungsadresse: Lieferadresse:
{billing_contact_name} {delivery_contact_name}
{billing_contact_fullname} {delivery_contact_fullname}
{billing_contact_address} {delivery_contact_address}
{billing_contact_zip} {billing_contact_town} {delivery_contact_zip} {delivery_contact_town}
```
@ -92,16 +95,6 @@ Rechnungsadresse: Lieferadresse:
- **Kundenkontakt für Lieferung** (SHIPPING) → `delivery_contact_*`
- **Kundenkontakt für Service** (SERVICE) → `service_contact_*`
## Debugging
Bei Problemen Log-Level auf DEBUG setzen:
1. Home → Setup → Logs → Syslog aktivieren
2. Dokument generieren
3. Log prüfen (z.B. `dolibarr.log`)
Relevante Log-Einträge beginnen mit: `DeliveryInvoiceAddress:`
## Technische Details
### Datenbankstruktur
@ -111,6 +104,7 @@ Relevante Log-Einträge beginnen mit: `DeliveryInvoiceAddress:`
| `llx_element_contact` | Verknüpfung Dokument ↔ Kontakt |
| `llx_c_type_contact` | Kontakttyp-Definitionen |
| `llx_socpeople` | Kontaktdaten |
| `llx_c_country` | Länderbezeichnung |
### Hauptfunktion

View file

@ -1,140 +1,95 @@
<?php
function deliveryinvoiceaddress_completesubstitutionarray(&$substitutionarray,$langs,$object)
/**
* Substitution-Funktion für Kontaktadressen in Dokumenten.
*
* Unterstützt Angebote (propal), Aufträge (commande) und Rechnungen (facture).
* Stellt Platzhalter für Rechnungs-, Service- und Lieferkontakte bereit:
* {billing_contact_firstname}, {billing_contact_name}, {billing_contact_fullname},
* {billing_contact_address}, {billing_contact_zip}, {billing_contact_town}, {billing_contact_country}
* (analog für service_contact_* und delivery_contact_*)
*
* @param array $substitutionarray Referenz auf das Substitution-Array
* @param Translate $langs Sprachobjekt
* @param Object $object Dolibarr-Objekt (Facture, Propal, Commande)
*/
function deliveryinvoiceaddress_completesubstitutionarray(&$substitutionarray, $langs, $object)
{
global $conf,$db;
global $conf, $db;
// Prüfe ob $object existiert und eine gültige ID hat
if (!is_object($object) || empty($object->id)) {
dol_syslog("DeliveryInvoiceAddress: Object ist null oder hat keine ID - überspringe", LOG_DEBUG);
return;
}
dol_syslog("DeliveryInvoiceAddress: START - Object class: ".get_class($object).", ID: ".$object->id, LOG_DEBUG);
// Bestimme den Element-Typ basierend auf dem Objekt
$elementType = isset($object->element) ? $object->element : '';
// Element-Typ bestimmen (facture, propal, commande)
$elementType = $object->element ?? '';
if (empty($elementType)) {
dol_syslog("DeliveryInvoiceAddress: Kein Element-Typ gefunden - überspringe", LOG_DEBUG);
return;
}
dol_syslog("DeliveryInvoiceAddress: Element-Typ: ".$elementType, LOG_DEBUG);
// Platzhalter-Prefixe je Kontakttyp
$codeToPrefix = array(
'BILLING' => 'billing_contact',
'CUSTOMER' => 'billing_contact', // Fallback für Angebote/Aufträge
'SERVICE' => 'service_contact',
'SHIPPING' => 'delivery_contact',
);
// Initialisiere alle Felder
$substitutionarray['billing_contact_firstname'] = '';
$substitutionarray['billing_contact_name'] = '';
$substitutionarray['billing_contact_address'] = '';
$substitutionarray['billing_contact_zip'] = '';
$substitutionarray['billing_contact_town'] = '';
$substitutionarray['billing_contact_country'] = '';
// Felder pro Kontakt
$fields = array('firstname', 'name', 'fullname', 'address', 'zip', 'town', 'country');
$substitutionarray['service_contact_firstname'] = '';
$substitutionarray['service_contact_name'] = '';
$substitutionarray['service_contact_address'] = '';
$substitutionarray['service_contact_zip'] = '';
$substitutionarray['service_contact_town'] = '';
$substitutionarray['service_contact_country'] = '';
// Alle Platzhalter leer initialisieren
foreach (array('billing_contact', 'service_contact', 'delivery_contact') as $prefix) {
foreach ($fields as $field) {
$substitutionarray[$prefix.'_'.$field] = '';
}
}
$substitutionarray['delivery_contact_firstname'] = '';
$substitutionarray['delivery_contact_name'] = '';
$substitutionarray['delivery_contact_address'] = '';
$substitutionarray['delivery_contact_zip'] = '';
$substitutionarray['delivery_contact_town'] = '';
$substitutionarray['delivery_contact_country'] = '';
// Lade Kontakte direkt aus der DB - mit korrektem element-Typ!
$sql = "SELECT ec.rowid, ec.fk_socpeople, tc.code";
// Kontaktdaten per JOIN direkt laden
$sql = "SELECT tc.code, sp.rowid, sp.firstname, sp.lastname, sp.address, sp.zip, sp.town,";
$sql .= " co.label as country";
$sql .= " FROM ".MAIN_DB_PREFIX."element_contact as ec";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_contact as tc ON ec.fk_c_type_contact = tc.rowid";
$sql .= " JOIN ".MAIN_DB_PREFIX."c_type_contact as tc ON ec.fk_c_type_contact = tc.rowid";
$sql .= " JOIN ".MAIN_DB_PREFIX."socpeople as sp ON ec.fk_socpeople = sp.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as co ON sp.fk_pays = co.rowid";
$sql .= " WHERE ec.element_id = ".((int) $object->id);
$sql .= " AND tc.element = '".$db->escape($elementType)."'";
$sql .= " AND tc.code IN ('BILLING', 'SERVICE', 'SHIPPING', 'CUSTOMER')";
dol_syslog("DeliveryInvoiceAddress: SQL: ".$sql, LOG_DEBUG);
$resql = $db->query($sql);
if (!$resql) {
dol_syslog("DeliveryInvoiceAddress: SQL ERROR: ".$db->lasterror(), LOG_ERR);
dol_syslog("DeliveryInvoiceAddress: SQL FEHLER: ".$db->lasterror(), LOG_ERR);
return;
}
$num = $db->num_rows($resql);
dol_syslog("DeliveryInvoiceAddress: ".$num." Kontakte gefunden", LOG_DEBUG);
if ($num == 0) {
dol_syslog("DeliveryInvoiceAddress: Keine Kontakte gefunden - ENDE", LOG_DEBUG);
return;
}
require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
while ($obj = $db->fetch_object($resql)) {
dol_syslog("DeliveryInvoiceAddress: Verarbeite Kontakt - ID: ".$obj->fk_socpeople.", Code: ".$obj->code, LOG_DEBUG);
$contactobj = new Contact($db);
$result = $contactobj->fetch($obj->fk_socpeople);
if ($result > 0) {
$firstname = isset($contactobj->firstname) ? $contactobj->firstname : '';
$lastname = isset($contactobj->lastname) ? $contactobj->lastname : '';
$address = isset($contactobj->address) ? $contactobj->address : '';
$zip = isset($contactobj->zip) ? $contactobj->zip : '';
$town = isset($contactobj->town) ? $contactobj->town : '';
$country = isset($contactobj->country) ? $contactobj->country : '';
// Name-Logik: Vorname falls vorhanden, sonst Nachname
$name = !empty($firstname) ? $firstname : $lastname;
dol_syslog("DeliveryInvoiceAddress: Kontakt geladen - Vorname: ".$firstname.", Nachname: ".$lastname.", Code: ".$obj->code, LOG_DEBUG);
// BILLING und CUSTOMER werden beide als Rechnungskontakt behandelt
// BILLING hat Priorität, CUSTOMER nur wenn BILLING noch leer ist
if ($obj->code == 'BILLING') {
$substitutionarray['billing_contact_firstname'] = $firstname;
$substitutionarray['billing_contact_name'] = $name;
$substitutionarray['billing_contact_address'] = $address;
$substitutionarray['billing_contact_zip'] = $zip;
$substitutionarray['billing_contact_town'] = $town;
$substitutionarray['billing_contact_country'] = $country;
dol_syslog("DeliveryInvoiceAddress: BILLING gesetzt: ".$name." - ".$address, LOG_DEBUG);
}
elseif ($obj->code == 'CUSTOMER' && empty($substitutionarray['billing_contact_name'])) {
// CUSTOMER als Fallback für billing_contact (bei Angeboten/Aufträgen)
$substitutionarray['billing_contact_firstname'] = $firstname;
$substitutionarray['billing_contact_name'] = $name;
$substitutionarray['billing_contact_address'] = $address;
$substitutionarray['billing_contact_zip'] = $zip;
$substitutionarray['billing_contact_town'] = $town;
$substitutionarray['billing_contact_country'] = $country;
dol_syslog("DeliveryInvoiceAddress: CUSTOMER als BILLING gesetzt: ".$name." - ".$address, LOG_DEBUG);
}
elseif ($obj->code == 'SERVICE') {
$substitutionarray['service_contact_firstname'] = $firstname;
$substitutionarray['service_contact_name'] = $name;
$substitutionarray['service_contact_address'] = $address;
$substitutionarray['service_contact_zip'] = $zip;
$substitutionarray['service_contact_town'] = $town;
$substitutionarray['service_contact_country'] = $country;
dol_syslog("DeliveryInvoiceAddress: SERVICE gesetzt: ".$name." - ".$address, LOG_DEBUG);
}
elseif ($obj->code == 'SHIPPING') {
$substitutionarray['delivery_contact_firstname'] = $firstname;
$substitutionarray['delivery_contact_name'] = $name;
$substitutionarray['delivery_contact_address'] = $address;
$substitutionarray['delivery_contact_zip'] = $zip;
$substitutionarray['delivery_contact_town'] = $town;
$substitutionarray['delivery_contact_country'] = $country;
dol_syslog("DeliveryInvoiceAddress: SHIPPING gesetzt: ".$name." - ".$address, LOG_DEBUG);
}
} else {
dol_syslog("DeliveryInvoiceAddress: FEHLER beim Laden von Kontakt ID ".$obj->fk_socpeople, LOG_ERR);
$prefix = $codeToPrefix[$obj->code] ?? null;
if (!$prefix) {
continue;
}
// CUSTOMER nur als Fallback wenn BILLING noch leer
if ($obj->code == 'CUSTOMER' && !empty($substitutionarray['billing_contact_name'])) {
continue;
}
$firstname = $obj->firstname ?? '';
$lastname = $obj->lastname ?? '';
// Fullname: Vorname + Nachname, Reihenfolge je nach Dolibarr-Einstellung
$fullname = trim($firstname.' '.$lastname);
if (getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION')) {
$fullname = trim($lastname.' '.$firstname);
}
$substitutionarray[$prefix.'_firstname'] = $firstname;
$substitutionarray[$prefix.'_name'] = $lastname;
$substitutionarray[$prefix.'_fullname'] = $fullname;
$substitutionarray[$prefix.'_address'] = $obj->address ?? '';
$substitutionarray[$prefix.'_zip'] = $obj->zip ?? '';
$substitutionarray[$prefix.'_town'] = $obj->town ?? '';
$substitutionarray[$prefix.'_country'] = $obj->country ?? '';
}
$db->free($resql);
dol_syslog("DeliveryInvoiceAddress: ENDE - Alle Substitutionen gesetzt", LOG_DEBUG);
}
?>