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:
parent
af72845f5c
commit
e8694aa0a4
3 changed files with 85 additions and 129 deletions
|
|
@ -1,5 +1,12 @@
|
||||||
# CHANGELOG MODULE DELIVERYINVOICEADDRESS FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
# 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
|
Initial version
|
||||||
|
|
|
||||||
32
README.md
32
README.md
|
|
@ -8,7 +8,7 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
|
||||||
- Lieferadresse für Versanddokumente
|
- Lieferadresse für Versanddokumente
|
||||||
- Serviceadresse für Servicedokumente
|
- Serviceadresse für Servicedokumente
|
||||||
- Automatische Erkennung des Dokumenttyps (Angebot/Auftrag/Rechnung)
|
- 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
|
## Installation
|
||||||
|
|
||||||
|
|
@ -30,7 +30,8 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
|
||||||
| Tag | Beschreibung |
|
| Tag | Beschreibung |
|
||||||
|-----|--------------|
|
|-----|--------------|
|
||||||
| `{billing_contact_firstname}` | Vorname |
|
| `{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_address}` | Straße und Hausnummer |
|
||||||
| `{billing_contact_zip}` | Postleitzahl |
|
| `{billing_contact_zip}` | Postleitzahl |
|
||||||
| `{billing_contact_town}` | Ort |
|
| `{billing_contact_town}` | Ort |
|
||||||
|
|
@ -41,7 +42,8 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
|
||||||
| Tag | Beschreibung |
|
| Tag | Beschreibung |
|
||||||
|-----|--------------|
|
|-----|--------------|
|
||||||
| `{delivery_contact_firstname}` | Vorname |
|
| `{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_address}` | Straße und Hausnummer |
|
||||||
| `{delivery_contact_zip}` | Postleitzahl |
|
| `{delivery_contact_zip}` | Postleitzahl |
|
||||||
| `{delivery_contact_town}` | Ort |
|
| `{delivery_contact_town}` | Ort |
|
||||||
|
|
@ -52,7 +54,8 @@ Dolibarr-Modul zur Verwendung von Rechnungs-, Liefer- und Serviceadressen in ODT
|
||||||
| Tag | Beschreibung |
|
| Tag | Beschreibung |
|
||||||
|-----|--------------|
|
|-----|--------------|
|
||||||
| `{service_contact_firstname}` | Vorname |
|
| `{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_address}` | Straße und Hausnummer |
|
||||||
| `{service_contact_zip}` | Postleitzahl |
|
| `{service_contact_zip}` | Postleitzahl |
|
||||||
| `{service_contact_town}` | Ort |
|
| `{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
|
### Beispiel: Rechnungsadresse mit Fallback auf Kundenadresse
|
||||||
|
|
||||||
```
|
```
|
||||||
[!-- IF {billing_contact_name} --]
|
[!-- IF {billing_contact_fullname} --]
|
||||||
{billing_contact_name}
|
{billing_contact_fullname}
|
||||||
{billing_contact_address}
|
{billing_contact_address}
|
||||||
{billing_contact_zip} {billing_contact_town}
|
{billing_contact_zip} {billing_contact_town}
|
||||||
[!-- ELSE {billing_contact_name} --]
|
[!-- ELSE {billing_contact_fullname} --]
|
||||||
{company_name}
|
{company_name}
|
||||||
{company_address}
|
{company_address}
|
||||||
{company_zip} {company_town}
|
{company_zip} {company_town}
|
||||||
[!-- ENDIF {billing_contact_name} --]
|
[!-- ENDIF {billing_contact_fullname} --]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Beispiel: Zwei Adressen nebeneinander
|
### Beispiel: Zwei Adressen nebeneinander
|
||||||
|
|
||||||
```
|
```
|
||||||
Rechnungsadresse: Lieferadresse:
|
Rechnungsadresse: Lieferadresse:
|
||||||
{billing_contact_name} {delivery_contact_name}
|
{billing_contact_fullname} {delivery_contact_fullname}
|
||||||
{billing_contact_address} {delivery_contact_address}
|
{billing_contact_address} {delivery_contact_address}
|
||||||
{billing_contact_zip} {billing_contact_town} {delivery_contact_zip} {delivery_contact_town}
|
{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 Lieferung** (SHIPPING) → `delivery_contact_*`
|
||||||
- **Kundenkontakt für Service** (SERVICE) → `service_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
|
## Technische Details
|
||||||
|
|
||||||
### Datenbankstruktur
|
### Datenbankstruktur
|
||||||
|
|
@ -111,6 +104,7 @@ Relevante Log-Einträge beginnen mit: `DeliveryInvoiceAddress:`
|
||||||
| `llx_element_contact` | Verknüpfung Dokument ↔ Kontakt |
|
| `llx_element_contact` | Verknüpfung Dokument ↔ Kontakt |
|
||||||
| `llx_c_type_contact` | Kontakttyp-Definitionen |
|
| `llx_c_type_contact` | Kontakttyp-Definitionen |
|
||||||
| `llx_socpeople` | Kontaktdaten |
|
| `llx_socpeople` | Kontaktdaten |
|
||||||
|
| `llx_c_country` | Länderbezeichnung |
|
||||||
|
|
||||||
### Hauptfunktion
|
### Hauptfunktion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,140 +1,95 @@
|
||||||
<?php
|
<?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)) {
|
if (!is_object($object) || empty($object->id)) {
|
||||||
dol_syslog("DeliveryInvoiceAddress: Object ist null oder hat keine ID - überspringe", LOG_DEBUG);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dol_syslog("DeliveryInvoiceAddress: START - Object class: ".get_class($object).", ID: ".$object->id, LOG_DEBUG);
|
// Element-Typ bestimmen (facture, propal, commande)
|
||||||
|
$elementType = $object->element ?? '';
|
||||||
// Bestimme den Element-Typ basierend auf dem Objekt
|
|
||||||
$elementType = isset($object->element) ? $object->element : '';
|
|
||||||
|
|
||||||
if (empty($elementType)) {
|
if (empty($elementType)) {
|
||||||
dol_syslog("DeliveryInvoiceAddress: Kein Element-Typ gefunden - überspringe", LOG_DEBUG);
|
|
||||||
return;
|
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
|
// Felder pro Kontakt
|
||||||
$substitutionarray['billing_contact_firstname'] = '';
|
$fields = array('firstname', 'name', 'fullname', 'address', 'zip', 'town', 'country');
|
||||||
$substitutionarray['billing_contact_name'] = '';
|
|
||||||
$substitutionarray['billing_contact_address'] = '';
|
|
||||||
$substitutionarray['billing_contact_zip'] = '';
|
|
||||||
$substitutionarray['billing_contact_town'] = '';
|
|
||||||
$substitutionarray['billing_contact_country'] = '';
|
|
||||||
|
|
||||||
$substitutionarray['service_contact_firstname'] = '';
|
// Alle Platzhalter leer initialisieren
|
||||||
$substitutionarray['service_contact_name'] = '';
|
foreach (array('billing_contact', 'service_contact', 'delivery_contact') as $prefix) {
|
||||||
$substitutionarray['service_contact_address'] = '';
|
foreach ($fields as $field) {
|
||||||
$substitutionarray['service_contact_zip'] = '';
|
$substitutionarray[$prefix.'_'.$field] = '';
|
||||||
$substitutionarray['service_contact_town'] = '';
|
}
|
||||||
$substitutionarray['service_contact_country'] = '';
|
}
|
||||||
|
|
||||||
$substitutionarray['delivery_contact_firstname'] = '';
|
// Kontaktdaten per JOIN direkt laden
|
||||||
$substitutionarray['delivery_contact_name'] = '';
|
$sql = "SELECT tc.code, sp.rowid, sp.firstname, sp.lastname, sp.address, sp.zip, sp.town,";
|
||||||
$substitutionarray['delivery_contact_address'] = '';
|
$sql .= " co.label as country";
|
||||||
$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";
|
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."element_contact as ec";
|
$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 .= " WHERE ec.element_id = ".((int) $object->id);
|
||||||
$sql .= " AND tc.element = '".$db->escape($elementType)."'";
|
$sql .= " AND tc.element = '".$db->escape($elementType)."'";
|
||||||
$sql .= " AND tc.code IN ('BILLING', 'SERVICE', 'SHIPPING', 'CUSTOMER')";
|
$sql .= " AND tc.code IN ('BILLING', 'SERVICE', 'SHIPPING', 'CUSTOMER')";
|
||||||
|
|
||||||
dol_syslog("DeliveryInvoiceAddress: SQL: ".$sql, LOG_DEBUG);
|
|
||||||
|
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
if (!$resql) {
|
if (!$resql) {
|
||||||
dol_syslog("DeliveryInvoiceAddress: SQL ERROR: ".$db->lasterror(), LOG_ERR);
|
dol_syslog("DeliveryInvoiceAddress: SQL FEHLER: ".$db->lasterror(), LOG_ERR);
|
||||||
return;
|
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)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
dol_syslog("DeliveryInvoiceAddress: Verarbeite Kontakt - ID: ".$obj->fk_socpeople.", Code: ".$obj->code, LOG_DEBUG);
|
$prefix = $codeToPrefix[$obj->code] ?? null;
|
||||||
|
if (!$prefix) {
|
||||||
$contactobj = new Contact($db);
|
continue;
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
$db->free($resql);
|
||||||
|
|
||||||
dol_syslog("DeliveryInvoiceAddress: ENDE - Alle Substitutionen gesetzt", LOG_DEBUG);
|
|
||||||
}
|
}
|
||||||
?>
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue