Version 1.6.0: Stundenübernahme-Modus, Leistungsbeschreibungen, Bugfixes, Dark Mode
- Neues Extrafeld 'stundenzettel_hours_mode' am Auftrag: Gruppiert oder Pro Stundenzettel - Leistungsbeschreibungen werden per GROUP_CONCAT in Rechnungszeilen übernommen - Bugfix: Rücknahme-Dropdown zeigt jetzt auch manuell hinzugefügte Produkte (fk_commandedet=NULL) - Bugfix: Entfällt berücksichtigt Freitext-Produkte korrekt (fk_product IS NULL) - Bugfix: NULL-Handling für fk_product in 5 SQL-Queries (card.php + stundenzettel_commande.php) - Bugfix: Rechnungsübernahme inkl. origin='added' Produkte ohne Auftragszeile - Bugfix: Tracking-Tab zeigt alle Mehraufwand/zusätzlich verbauten Produkte - Dark Mode: Hardcodierte Hintergrundfarben durch CSS-Klassen mit Variablen ersetzt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
585d530992
commit
c9cbd54fa3
6 changed files with 304 additions and 69 deletions
32
README.md
32
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
# Stundenzettel Modul für Dolibarr
|
# Stundenzettel Modul für Dolibarr
|
||||||
|
|
||||||
**Version:** 1.4.0
|
**Version:** 1.6.0
|
||||||
**Autor:** Data IT Solution
|
**Autor:** Data IT Solution
|
||||||
**Kompatibilität:** Dolibarr 16.0+
|
**Kompatibilität:** Dolibarr 16.0+
|
||||||
**Lizenz:** GPL v3
|
**Lizenz:** GPL v3
|
||||||
|
|
@ -25,7 +25,9 @@ Das Stundenzettel-Modul ermöglicht die Verwaltung von Stundenzetteln für Kunde
|
||||||
|
|
||||||
- **Stundenzettel-Freigabe**: Sperren von Stundenzetteln nach Fertigstellung
|
- **Stundenzettel-Freigabe**: Sperren von Stundenzetteln nach Fertigstellung
|
||||||
- **Rechnungsübernahme**: Automatische Übernahme aller Produkte und Leistungen in eine Rechnung
|
- **Rechnungsübernahme**: Automatische Übernahme aller Produkte und Leistungen in eine Rechnung
|
||||||
- **Stunden-Modus**: Wahlweise Übernahme als Gesamtstunden oder pro Tag
|
- **Stunden-Modus**: Wahlweise Übernahme gruppiert oder pro Stundenzettel (einstellbar pro Auftrag)
|
||||||
|
- **Rücknahme**: Bereits verbaute Produkte als zurückgenommen markieren
|
||||||
|
- **Leistungsbeschreibungen**: Arbeitsbeschreibungen werden automatisch in die Rechnung übernommen
|
||||||
|
|
||||||
### Integration
|
### Integration
|
||||||
|
|
||||||
|
|
@ -55,6 +57,17 @@ Die Moduleinstellungen finden Sie unter **Einstellungen > Module > Stundenzettel
|
||||||
| **Standard-Datum** | Aktuelles Datum oder Datum des letzten offenen Stundenzettels |
|
| **Standard-Datum** | Aktuelles Datum oder Datum des letzten offenen Stundenzettels |
|
||||||
| **Stunden-Übernahme** | Gesamtstunden auf einer Zeile oder pro Tag eine Zeile |
|
| **Stunden-Übernahme** | Gesamtstunden auf einer Zeile oder pro Tag eine Zeile |
|
||||||
|
|
||||||
|
### Stundenübernahme-Modus (pro Auftrag)
|
||||||
|
|
||||||
|
Im Auftrag kann unter den Extrafeldern der Modus der Stundenübernahme eingestellt werden:
|
||||||
|
|
||||||
|
| Modus | Beschreibung |
|
||||||
|
|-------|--------------|
|
||||||
|
| **Gruppiert (Standard)** | Alle gleichen Leistungspositionen über alle Stundenzettel zusammenrechnen |
|
||||||
|
| **Pro Stundenzettel** | Jeder Stundenzettel bekommt eigene Rechnungszeile(n) mit STZ-Referenz und Datum |
|
||||||
|
|
||||||
|
In beiden Modi werden die Leistungsbeschreibungen automatisch in die Rechnungszeile übernommen.
|
||||||
|
|
||||||
### Standard-Leistung beim Kunden
|
### Standard-Leistung beim Kunden
|
||||||
|
|
||||||
Sie können beim Kunden (unter **Kunden > Kundenkarte**) eine Standard-Leistung (Dienstleistung) hinterlegen. Diese wird dann bei allen Stundenzetteln für diesen Kunden angezeigt und kann für die Rechnungsstellung verwendet werden.
|
Sie können beim Kunden (unter **Kunden > Kundenkarte**) eine Standard-Leistung (Dienstleistung) hinterlegen. Diese wird dann bei allen Stundenzetteln für diesen Kunden angezeigt und kann für die Rechnungsstellung verwendet werden.
|
||||||
|
|
@ -77,6 +90,7 @@ Sie können beim Kunden (unter **Kunden > Kundenkarte**) eine Standard-Leistung
|
||||||
| `auftragsbeschreibung` | Auftrag | Zusätzliche Beschreibung für den Auftrag |
|
| `auftragsbeschreibung` | Auftrag | Zusätzliche Beschreibung für den Auftrag |
|
||||||
| `stundenzettel_status` | Auftrag | Status der Stundenzettel (0=Offen, 1=Freigegeben, 2=Abgerechnet) |
|
| `stundenzettel_status` | Auftrag | Status der Stundenzettel (0=Offen, 1=Freigegeben, 2=Abgerechnet) |
|
||||||
| `stundenzettel_netto` | Auftrag | Berechneter Netto-Wert aller freigegebenen Stundenzettel |
|
| `stundenzettel_netto` | Auftrag | Berechneter Netto-Wert aller freigegebenen Stundenzettel |
|
||||||
|
| `stundenzettel_hours_mode` | Auftrag | Stundenübernahme-Modus: Gruppiert oder Pro Stundenzettel |
|
||||||
| `stundenzettel_default_service` | Kunde | Standard-Dienstleistung für Stundenzettel |
|
| `stundenzettel_default_service` | Kunde | Standard-Dienstleistung für Stundenzettel |
|
||||||
|
|
||||||
## Berechtigungen
|
## Berechtigungen
|
||||||
|
|
@ -100,6 +114,20 @@ Sie können beim Kunden (unter **Kunden > Kundenkarte**) eine Standard-Leistung
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### Version 1.6.0
|
||||||
|
- **Stundenübernahme-Modus pro Auftrag**: Neues Extrafeld am Auftrag zur Wahl zwischen gruppierten Leistungen (Standard) und pro-Stundenzettel-Übernahme
|
||||||
|
- **Leistungsbeschreibungen in Rechnung**: Arbeitsbeschreibungen aus den Stundenzetteln werden automatisch in die Rechnungszeilen übernommen
|
||||||
|
- **Bugfix Rücknahme-Dropdown**: Manuell hinzugefügte Produkte (ohne Auftragszeile) werden jetzt korrekt angezeigt
|
||||||
|
- **Bugfix Entfällt**: Freitext-Produkte und Produkte ohne Auftragszeile werden korrekt berücksichtigt
|
||||||
|
- **Bugfix NULL-Handling**: Korrektes SQL-Handling für `fk_product IS NULL` bei Freitext-Produkten (5 Stellen)
|
||||||
|
- **Bugfix Rechnungsübernahme**: Produkte mit `origin='added'` ohne Auftragszeile werden jetzt in der Rechnung berücksichtigt
|
||||||
|
- **Bugfix Tracking-Tab**: Mehraufwand/zusätzlich verbaute Produkte werden vollständig in der Lieferauflistung angezeigt
|
||||||
|
|
||||||
|
### Version 1.5.0
|
||||||
|
- **Rücknahme-Bereich**: Bereits verbaute Produkte können als zurückgenommen markiert werden
|
||||||
|
- **Checkbox-Sichtbarkeit**: Verbesserte Darstellung der Produktauswahl
|
||||||
|
- **Berechtigungen**: Erweiterte Berechtigungssteuerung
|
||||||
|
|
||||||
### Version 1.4.0
|
### Version 1.4.0
|
||||||
- **Stundenzettel öffnen ohne Produktauswahl**: Button "Stundenzettel öffnen" funktioniert jetzt auch ohne Checkbox-Auswahl - öffnet oder erstellt direkt einen Stundenzettel
|
- **Stundenzettel öffnen ohne Produktauswahl**: Button "Stundenzettel öffnen" funktioniert jetzt auch ohne Checkbox-Auswahl - öffnet oder erstellt direkt einen Stundenzettel
|
||||||
- **Dezimalmengen**: Alle Mengenfelder (Produkte, Mehraufwand, Entfällt) unterstützen jetzt Kommazahlen (z.B. 0,3m Kabel)
|
- **Dezimalmengen**: Alle Mengenfelder (Produkte, Mehraufwand, Entfällt) unterstützen jetzt Kommazahlen (z.B. 0,3m Kabel)
|
||||||
|
|
|
||||||
91
card.php
91
card.php
|
|
@ -516,7 +516,7 @@ if ($action == 'add_entfaellt' && $permissiontoadd) {
|
||||||
$sqlCheck .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_used,";
|
$sqlCheck .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_used,";
|
||||||
$sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
|
$sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
|
||||||
$sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
|
$sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
|
||||||
$sqlCheck .= " WHERE sp2.fk_product = cd.fk_product AND sp2.origin = 'omitted' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_omitted";
|
$sqlCheck .= " WHERE (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp2.origin = 'omitted' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_omitted";
|
||||||
$sqlCheck .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
$sqlCheck .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
||||||
$sqlCheck .= " WHERE cd.fk_commande = ".((int)$object->fk_commande);
|
$sqlCheck .= " WHERE cd.fk_commande = ".((int)$object->fk_commande);
|
||||||
$sqlCheck .= " AND cd.fk_product = ".((int)$fk_product);
|
$sqlCheck .= " AND cd.fk_product = ".((int)$fk_product);
|
||||||
|
|
@ -586,7 +586,7 @@ if ($action == 'add_entfaellt' && $permissiontoadd) {
|
||||||
// Produkt zum Stundenzettel hinzufügen mit origin='omitted'
|
// Produkt zum Stundenzettel hinzufügen mit origin='omitted'
|
||||||
$result = $object->addProduct(
|
$result = $object->addProduct(
|
||||||
$fk_product,
|
$fk_product,
|
||||||
0, // fk_commandedet
|
$commandedet_id, // fk_commandedet (Verknüpfung zur Auftragszeile)
|
||||||
0, // fk_manager_line
|
0, // fk_manager_line
|
||||||
0, // qty_original
|
0, // qty_original
|
||||||
$qty, // qty_done (Menge die entfällt)
|
$qty, // qty_done (Menge die entfällt)
|
||||||
|
|
@ -640,6 +640,14 @@ if ($action == 'add_ruecknahme' && $permissiontoadd) {
|
||||||
if ($resqlDesc && ($objDesc = $db->fetch_object($resqlDesc))) {
|
if ($resqlDesc && ($objDesc = $db->fetch_object($resqlDesc))) {
|
||||||
$freetext_description = $objDesc->description;
|
$freetext_description = $objDesc->description;
|
||||||
}
|
}
|
||||||
|
} elseif (strpos($ruecknahme_product_raw, 'mehraufwand_') === 0) {
|
||||||
|
// Freitext-Mehraufwand aus stundenzettel_product
|
||||||
|
$sp_rowid = (int)substr($ruecknahme_product_raw, 12);
|
||||||
|
$sqlDesc = "SELECT description FROM ".MAIN_DB_PREFIX."stundenzettel_product WHERE rowid = ".((int)$sp_rowid);
|
||||||
|
$resqlDesc = $db->query($sqlDesc);
|
||||||
|
if ($resqlDesc && ($objDesc = $db->fetch_object($resqlDesc))) {
|
||||||
|
$freetext_description = $objDesc->description;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$fk_product = (int)$ruecknahme_product_raw;
|
$fk_product = (int)$ruecknahme_product_raw;
|
||||||
}
|
}
|
||||||
|
|
@ -1587,11 +1595,11 @@ elseif ($object->id > 0) {
|
||||||
// Bereits als entfällt markiert (auf allen Stundenzetteln dieses Auftrags)
|
// Bereits als entfällt markiert (auf allen Stundenzetteln dieses Auftrags)
|
||||||
$sqlOrderProducts .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
|
$sqlOrderProducts .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
|
||||||
$sqlOrderProducts .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
|
$sqlOrderProducts .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
|
||||||
$sqlOrderProducts .= " WHERE sp2.fk_product = cd.fk_product AND sp2.origin = 'omitted' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_omitted";
|
$sqlOrderProducts .= " WHERE (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp2.origin = 'omitted' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_omitted";
|
||||||
$sqlOrderProducts .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
$sqlOrderProducts .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
||||||
$sqlOrderProducts .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
$sqlOrderProducts .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
||||||
$sqlOrderProducts .= " WHERE cd.fk_commande = ".((int)$object->fk_commande);
|
$sqlOrderProducts .= " WHERE cd.fk_commande = ".((int)$object->fk_commande);
|
||||||
$sqlOrderProducts .= " AND (cd.fk_product > 0 OR (cd.fk_product = 0 AND cd.description IS NOT NULL AND cd.description != ''))";
|
$sqlOrderProducts .= " AND (cd.fk_product > 0 OR ((cd.fk_product IS NULL OR cd.fk_product = 0) AND cd.description IS NOT NULL AND cd.description != ''))";
|
||||||
$sqlOrderProducts .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
$sqlOrderProducts .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
||||||
$sqlOrderProducts .= " ORDER BY cd.rang";
|
$sqlOrderProducts .= " ORDER BY cd.rang";
|
||||||
$resqlOrderProducts = $db->query($sqlOrderProducts);
|
$resqlOrderProducts = $db->query($sqlOrderProducts);
|
||||||
|
|
@ -2055,7 +2063,7 @@ elseif ($object->id > 0) {
|
||||||
$sqlDelivered .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
$sqlDelivered .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
||||||
$sqlDelivered .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
$sqlDelivered .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
||||||
$sqlDelivered .= " WHERE cd.fk_commande = ".((int)$object->fk_commande);
|
$sqlDelivered .= " WHERE cd.fk_commande = ".((int)$object->fk_commande);
|
||||||
$sqlDelivered .= " AND (cd.fk_product > 0 OR (cd.fk_product = 0 AND cd.description IS NOT NULL AND cd.description != ''))";
|
$sqlDelivered .= " AND (cd.fk_product > 0 OR ((cd.fk_product IS NULL OR cd.fk_product = 0) AND cd.description IS NOT NULL AND cd.description != ''))";
|
||||||
$sqlDelivered .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
$sqlDelivered .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
||||||
$sqlDelivered .= " ORDER BY cd.rang";
|
$sqlDelivered .= " ORDER BY cd.rang";
|
||||||
$resqlDelivered = $db->query($sqlDelivered);
|
$resqlDelivered = $db->query($sqlDelivered);
|
||||||
|
|
@ -2068,6 +2076,69 @@ elseif ($object->id > 0) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auch verbaute Produkte ohne Auftragszeile berücksichtigen
|
||||||
|
// (origin='added' mit fk_commandedet IS NULL = in Produktliste hinzugefügt, nicht aus Auftrag)
|
||||||
|
$alreadyFoundProductIds = array();
|
||||||
|
foreach ($deliveredProducts as $dp) {
|
||||||
|
if ($dp->fk_product > 0) {
|
||||||
|
$alreadyFoundProductIds[] = (int)$dp->fk_product;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbaute Produkte mit fk_product > 0, ohne Auftragszeile
|
||||||
|
$sqlAdded = "SELECT sp.fk_product, p.ref as product_ref, p.label as product_label,";
|
||||||
|
$sqlAdded .= " SUM(sp.qty_done) as qty_delivered,";
|
||||||
|
$sqlAdded .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
|
||||||
|
$sqlAdded .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
|
||||||
|
$sqlAdded .= " WHERE sp2.fk_product = sp.fk_product AND sp2.origin = 'returned'";
|
||||||
|
$sqlAdded .= " AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_returned";
|
||||||
|
$sqlAdded .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
||||||
|
$sqlAdded .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
||||||
|
$sqlAdded .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = sp.fk_product";
|
||||||
|
$sqlAdded .= " WHERE sp.origin = 'added'";
|
||||||
|
$sqlAdded .= " AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)";
|
||||||
|
$sqlAdded .= " AND s.fk_commande = ".((int)$object->fk_commande);
|
||||||
|
$sqlAdded .= " AND sp.fk_product > 0";
|
||||||
|
if (!empty($alreadyFoundProductIds)) {
|
||||||
|
$sqlAdded .= " AND sp.fk_product NOT IN (".implode(',', $alreadyFoundProductIds).")";
|
||||||
|
}
|
||||||
|
$sqlAdded .= " GROUP BY sp.fk_product, p.ref, p.label";
|
||||||
|
$resqlAdded = $db->query($sqlAdded);
|
||||||
|
if ($resqlAdded) {
|
||||||
|
while ($objAdd = $db->fetch_object($resqlAdded)) {
|
||||||
|
$objAdd->qty_available = $objAdd->qty_delivered - $objAdd->qty_returned;
|
||||||
|
$objAdd->rowid = 0;
|
||||||
|
$objAdd->description = '';
|
||||||
|
if ($objAdd->qty_available > 0) {
|
||||||
|
$deliveredProducts[] = $objAdd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbaute Freitext-Produkte ohne Auftragszeile (fk_product = 0, fk_commandedet = NULL)
|
||||||
|
$sqlAddedFree = "SELECT sp.rowid as sp_rowid, sp.description,";
|
||||||
|
$sqlAddedFree .= " sp.qty_done as qty_delivered, 0 as qty_returned, 0 as fk_product,";
|
||||||
|
$sqlAddedFree .= " '' as product_ref, '' as product_label";
|
||||||
|
$sqlAddedFree .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
||||||
|
$sqlAddedFree .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
||||||
|
$sqlAddedFree .= " WHERE sp.origin = 'added'";
|
||||||
|
$sqlAddedFree .= " AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)";
|
||||||
|
$sqlAddedFree .= " AND s.fk_commande = ".((int)$object->fk_commande);
|
||||||
|
$sqlAddedFree .= " AND (sp.fk_product IS NULL OR sp.fk_product = 0)";
|
||||||
|
$sqlAddedFree .= " AND sp.description IS NOT NULL AND sp.description != ''";
|
||||||
|
$resqlAddedFree = $db->query($sqlAddedFree);
|
||||||
|
if ($resqlAddedFree) {
|
||||||
|
while ($objFree = $db->fetch_object($resqlAddedFree)) {
|
||||||
|
$objFree->qty_available = $objFree->qty_delivered;
|
||||||
|
$objFree->rowid = 0;
|
||||||
|
$objFree->fk_product = 0;
|
||||||
|
$objFree->is_mehraufwand_freetext = true;
|
||||||
|
if ($objFree->qty_available > 0) {
|
||||||
|
$deliveredProducts[] = $objFree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$hasDeliveredProducts = (count($deliveredProducts) > 0);
|
$hasDeliveredProducts = (count($deliveredProducts) > 0);
|
||||||
|
|
@ -2089,11 +2160,19 @@ elseif ($object->id > 0) {
|
||||||
|
|
||||||
foreach ($deliveredProducts as $dp) {
|
foreach ($deliveredProducts as $dp) {
|
||||||
if ($dp->fk_product > 0) {
|
if ($dp->fk_product > 0) {
|
||||||
print '<option value="'.$dp->fk_product.'" data-commandedet="'.$dp->rowid.'" data-max="'.formatQty($dp->qty_available).'">';
|
print '<option value="'.$dp->fk_product.'" data-commandedet="'.($dp->rowid ?? 0).'" data-max="'.formatQty($dp->qty_available).'">';
|
||||||
print $dp->product_ref.' - '.$dp->product_label;
|
print $dp->product_ref.' - '.$dp->product_label;
|
||||||
print ' ('.$langs->trans("QtyDelivered").': '.formatQty($dp->qty_available).')';
|
print ' ('.$langs->trans("QtyDelivered").': '.formatQty($dp->qty_available).')';
|
||||||
print '</option>';
|
print '</option>';
|
||||||
|
} elseif (!empty($dp->is_mehraufwand_freetext)) {
|
||||||
|
// Freitext-Mehraufwand
|
||||||
|
$desc = strip_tags($dp->description);
|
||||||
|
$descShort = (strlen($desc) > 50) ? substr($desc, 0, 47).'...' : $desc;
|
||||||
|
print '<option value="mehraufwand_'.$dp->sp_rowid.'" data-description="'.dol_escape_htmltag($desc).'" data-max="'.formatQty($dp->qty_available).'">';
|
||||||
|
print $descShort.' ('.$langs->trans("QtyDelivered").': '.formatQty($dp->qty_available).')';
|
||||||
|
print '</option>';
|
||||||
} else {
|
} else {
|
||||||
|
// Freitext aus Auftrag
|
||||||
$desc = strip_tags($dp->description);
|
$desc = strip_tags($dp->description);
|
||||||
$descShort = (strlen($desc) > 50) ? substr($desc, 0, 47).'...' : $desc;
|
$descShort = (strlen($desc) > 50) ? substr($desc, 0, 47).'...' : $desc;
|
||||||
print '<option value="freetext_'.$dp->rowid.'" data-commandedet="'.$dp->rowid.'" data-description="'.dol_escape_htmltag($desc).'" data-max="'.formatQty($dp->qty_available).'">';
|
print '<option value="freetext_'.$dp->rowid.'" data-commandedet="'.$dp->rowid.'" data-description="'.dol_escape_htmltag($desc).'" data-max="'.formatQty($dp->qty_available).'">';
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class modStundenzettel extends DolibarrModules
|
||||||
$this->descriptionlong = "Verwaltet Stundenzettel für Kundenaufträge. Ermöglicht die Dokumentation von Arbeitszeiten, verbrauchten Materialien und Notizen. Integration mit SubtotalTitle für Produktgruppen-Unterstützung.";
|
$this->descriptionlong = "Verwaltet Stundenzettel für Kundenaufträge. Ermöglicht die Dokumentation von Arbeitszeiten, verbrauchten Materialien und Notizen. Integration mit SubtotalTitle für Produktgruppen-Unterstützung.";
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
$this->version = '1.5.0';
|
$this->version = '1.6.0';
|
||||||
|
|
||||||
// Autor
|
// Autor
|
||||||
$this->editor_name = 'Data IT Solution';
|
$this->editor_name = 'Data IT Solution';
|
||||||
|
|
|
||||||
68
css/stundenzettel-mobile.css
Normal file → Executable file
68
css/stundenzettel-mobile.css
Normal file → Executable file
|
|
@ -366,11 +366,35 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
STUNDENZETTEL HINTERGRUNDFARBEN (Light Mode)
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel .stz-info-box {
|
||||||
|
background-color: var(--colorbacklineimpair1, #fff);
|
||||||
|
}
|
||||||
|
.mod-stundenzettel .stz-warning-box {
|
||||||
|
background-color: #fff8e1;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
}
|
||||||
|
.mod-stundenzettel .stz-section-header {
|
||||||
|
background-color: var(--colorbacklineimpair1, #f8f8f8);
|
||||||
|
}
|
||||||
|
.mod-stundenzettel .stz-subtable-bg {
|
||||||
|
background-color: var(--colorbacklineimpair2, #fafafa);
|
||||||
|
}
|
||||||
|
.mod-stundenzettel .stz-subtable-header {
|
||||||
|
background-color: var(--colorbacktitle1, #f0f0f0);
|
||||||
|
}
|
||||||
|
.mod-stundenzettel .stz-mehraufwand-header {
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
}
|
||||||
|
.mod-stundenzettel .stz-current-stz {
|
||||||
|
background-color: #e8f4fc;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
DARK MODE SUPPORT
|
DARK MODE SUPPORT
|
||||||
============================================ */
|
============================================ */
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
/* Nur anwenden wenn Dolibarr Dark Theme aktiv (body hat dark-Klasse) */
|
|
||||||
body.theme-eldy-dark .mod-stundenzettel .tabsAction,
|
body.theme-eldy-dark .mod-stundenzettel .tabsAction,
|
||||||
body[class*="dark"] .mod-stundenzettel .tabsAction {
|
body[class*="dark"] .mod-stundenzettel .tabsAction {
|
||||||
background: var(--colorbackbody, #1e1e1e);
|
background: var(--colorbackbody, #1e1e1e);
|
||||||
|
|
@ -381,4 +405,44 @@
|
||||||
body[class*="dark"] .mod-stundenzettel .mobile-description-row {
|
body[class*="dark"] .mod-stundenzettel .mobile-description-row {
|
||||||
background: var(--colorbacklineimpair2, #2d2d2d) !important;
|
background: var(--colorbacklineimpair2, #2d2d2d) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .stz-info-box,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .stz-info-box {
|
||||||
|
background-color: var(--colorbacklineimpair1, #2d2d2d) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .stz-warning-box,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .stz-warning-box {
|
||||||
|
background-color: var(--colorbacklineimpair1, #3a3520) !important;
|
||||||
|
border-left-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .stz-section-header,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .stz-section-header {
|
||||||
|
background-color: var(--colorbacktitle1, #333) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .stz-subtable-bg,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .stz-subtable-bg {
|
||||||
|
background-color: var(--colorbacklineimpair2, #2a2a2a) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .stz-subtable-header,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .stz-subtable-header {
|
||||||
|
background-color: var(--colorbacktitle1, #333) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .stz-mehraufwand-header,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .stz-mehraufwand-header {
|
||||||
|
background-color: var(--colorbacklineimpair1, #1e3320) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .stz-current-stz,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .stz-current-stz {
|
||||||
|
background-color: var(--colorbacklineimpair2, #1e2e3a) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-eldy-dark .mod-stundenzettel .info,
|
||||||
|
body[class*="dark"] .mod-stundenzettel .info {
|
||||||
|
background-color: var(--colorbacklineimpair1, #2d2d2d) !important;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
sql/llx_commande_extrafields_stundenzettel.sql
Normal file → Executable file
14
sql/llx_commande_extrafields_stundenzettel.sql
Normal file → Executable file
|
|
@ -12,3 +12,17 @@ ON DUPLICATE KEY UPDATE label = 'StundenzettelStatus';
|
||||||
|
|
||||||
-- Spalte in commande_extrafields hinzufügen falls nicht vorhanden
|
-- Spalte in commande_extrafields hinzufügen falls nicht vorhanden
|
||||||
ALTER TABLE llx_commande_extrafields ADD COLUMN IF NOT EXISTS stundenzettel_status INT DEFAULT 0;
|
ALTER TABLE llx_commande_extrafields ADD COLUMN IF NOT EXISTS stundenzettel_status INT DEFAULT 0;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Extrafeld für Stundenübernahme-Modus am Auftrag
|
||||||
|
-- grouped = Alle gleichen Leistungspositionen zusammenrechnen (Standard)
|
||||||
|
-- per_stz = Pro Stundenzettel eine eigene Rechnungszeile
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
INSERT INTO llx_extrafields (name, entity, elementtype, label, type, size, fieldunique, fieldrequired, pos, alwayseditable, perms, langs, list, printable, fielddefault, fieldcomputed, fk_user_author, fk_user_modif, datec, enabled, help, param)
|
||||||
|
VALUES ('stundenzettel_hours_mode', 1, 'commande', 'Stundenübernahme', 'select', '', 0, 0, 101, 1, '', '', 1, 0, 'grouped', '', NULL, NULL, NOW(), '1', 'Wie werden Arbeitsstunden in die Rechnung übertragen?',
|
||||||
|
'a:1:{s:7:"options";a:2:{s:7:"grouped";s:20:"Gruppiert (Standard)";s:7:"per_stz";s:17:"Pro Stundenzettel";}}')
|
||||||
|
ON DUPLICATE KEY UPDATE label = 'Stundenübernahme', list = 1, enabled = '1', param = 'a:1:{s:7:"options";a:2:{s:7:"grouped";s:20:"Gruppiert (Standard)";s:7:"per_stz";s:17:"Pro Stundenzettel";}}';
|
||||||
|
|
||||||
|
-- Spalte in commande_extrafields hinzufügen
|
||||||
|
ALTER TABLE llx_commande_extrafields ADD COLUMN IF NOT EXISTS stundenzettel_hours_mode VARCHAR(255) DEFAULT 'grouped';
|
||||||
|
|
|
||||||
|
|
@ -397,19 +397,25 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sammle Mehraufwand (zusätzliche Produkte mit origin = 'additional')
|
// Sammle Mehraufwand und zusätzlich verbaute Produkte
|
||||||
|
// (origin='additional' = beauftragt, origin='added' ohne fk_commandedet = verbaut ohne Auftragszeile)
|
||||||
$additionalProducts = array();
|
$additionalProducts = array();
|
||||||
$sqlAdd = "SELECT sp.fk_product, sp.product_label, sp.description, SUM(sp.qty_done) as total_qty";
|
$sqlAdd = "SELECT sp.fk_product, sp.product_label, sp.description,";
|
||||||
|
$sqlAdd .= " SUM(CASE WHEN sp.origin = 'additional' THEN sp.qty_done ELSE 0 END) as qty_additional,";
|
||||||
|
$sqlAdd .= " SUM(CASE WHEN sp.origin IN ('order', 'added') THEN sp.qty_done ELSE 0 END) as qty_added,";
|
||||||
|
$sqlAdd .= " SUM(sp.qty_done) as total_qty";
|
||||||
$sqlAdd .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
$sqlAdd .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
||||||
$sqlAdd .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
$sqlAdd .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
||||||
$sqlAdd .= " WHERE s.fk_commande = ".((int)$order->id);
|
$sqlAdd .= " WHERE s.fk_commande = ".((int)$order->id);
|
||||||
$sqlAdd .= " AND sp.origin = 'additional'";
|
$sqlAdd .= " AND (sp.origin = 'additional' OR (sp.origin = 'added' AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)))";
|
||||||
$sqlAdd .= " GROUP BY sp.fk_product, sp.product_label, sp.description";
|
$sqlAdd .= " GROUP BY sp.fk_product, sp.product_label, sp.description";
|
||||||
|
|
||||||
$resqlAdd = $db->query($sqlAdd);
|
$resqlAdd = $db->query($sqlAdd);
|
||||||
if ($resqlAdd) {
|
if ($resqlAdd) {
|
||||||
while ($objAdd = $db->fetch_object($resqlAdd)) {
|
while ($objAdd = $db->fetch_object($resqlAdd)) {
|
||||||
if (floatval($objAdd->total_qty) > 0) {
|
// Für die Rechnung zählt die verbaute Menge (added), nicht nur beauftragte (additional)
|
||||||
|
$objAdd->invoice_qty = floatval($objAdd->qty_added) > 0 ? floatval($objAdd->qty_added) : floatval($objAdd->qty_additional);
|
||||||
|
if ($objAdd->invoice_qty > 0) {
|
||||||
$additionalProducts[] = $objAdd;
|
$additionalProducts[] = $objAdd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -729,7 +735,7 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
$productResult = $facture->addline(
|
$productResult = $facture->addline(
|
||||||
$product->label, // 1: desc
|
$product->label, // 1: desc
|
||||||
$usePrice, // 2: pu_ht - kundenspezifischer Preis
|
$usePrice, // 2: pu_ht - kundenspezifischer Preis
|
||||||
$addProd->total_qty, // 3: qty
|
$addProd->invoice_qty, // 3: qty (verbaute Menge)
|
||||||
$useTvaTx, // 4: txtva - kundenspezifischer MwSt-Satz
|
$useTvaTx, // 4: txtva - kundenspezifischer MwSt-Satz
|
||||||
0, // 5: txlocaltax1
|
0, // 5: txlocaltax1
|
||||||
0, // 6: txlocaltax2
|
0, // 6: txlocaltax2
|
||||||
|
|
@ -761,7 +767,7 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
$productResult = $facture->addline(
|
$productResult = $facture->addline(
|
||||||
$addProd->description ? $addProd->description : $addProd->product_label,
|
$addProd->description ? $addProd->description : $addProd->product_label,
|
||||||
0, // 2: pu_ht
|
0, // 2: pu_ht
|
||||||
$addProd->total_qty, // 3: qty
|
$addProd->invoice_qty, // 3: qty (verbaute Menge)
|
||||||
0, // 4: txtva
|
0, // 4: txtva
|
||||||
0, // 5: txlocaltax1
|
0, // 5: txlocaltax1
|
||||||
0, // 6: txlocaltax2
|
0, // 6: txlocaltax2
|
||||||
|
|
@ -824,7 +830,9 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// ARBEITSZEITEN aus Stundenzetteln hinzufügen
|
// ARBEITSZEITEN aus Stundenzetteln hinzufügen
|
||||||
// (Gruppiert nach Leistungsposition/Produkt)
|
// Modus wird über Extrafeld am Auftrag gesteuert:
|
||||||
|
// grouped = Alle gleichen Leistungen zusammenrechnen (Standard)
|
||||||
|
// per_stz = Pro Stundenzettel eine eigene Rechnungszeile
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
// Standard-Leistung vom Kunden laden (Fallback wenn keine Leistung gewählt)
|
// Standard-Leistung vom Kunden laden (Fallback wenn keine Leistung gewählt)
|
||||||
|
|
@ -833,19 +841,42 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
$societe->fetch_optionals();
|
$societe->fetch_optionals();
|
||||||
$defaultServiceId = isset($societe->array_options['options_stundenzettel_default_service']) ? (int)$societe->array_options['options_stundenzettel_default_service'] : 0;
|
$defaultServiceId = isset($societe->array_options['options_stundenzettel_default_service']) ? (int)$societe->array_options['options_stundenzettel_default_service'] : 0;
|
||||||
|
|
||||||
// Alle Arbeitszeiten nach Leistungsposition gruppiert sammeln
|
// Stundenübernahme-Modus aus Auftrags-Extrafeld lesen
|
||||||
|
$hoursMode = isset($order->array_options['options_stundenzettel_hours_mode']) ? $order->array_options['options_stundenzettel_hours_mode'] : 'grouped';
|
||||||
|
if (empty($hoursMode)) $hoursMode = 'grouped';
|
||||||
|
|
||||||
|
if ($hoursMode == 'per_stz') {
|
||||||
|
// === PRO STUNDENZETTEL: Jeder STZ bekommt eigene Zeilen ===
|
||||||
$sqlHours = "SELECT ";
|
$sqlHours = "SELECT ";
|
||||||
|
$sqlHours .= " s.rowid as stz_id, s.ref as stz_ref, s.date_stundenzettel,";
|
||||||
$sqlHours .= " COALESCE(l.fk_product, ".(int)$defaultServiceId.") as product_id,";
|
$sqlHours .= " COALESCE(l.fk_product, ".(int)$defaultServiceId.") as product_id,";
|
||||||
$sqlHours .= " p.ref as product_ref, p.label as product_label, p.tva_tx, p.fk_unit,";
|
$sqlHours .= " p.ref as product_ref, p.label as product_label, p.tva_tx, p.fk_unit,";
|
||||||
$sqlHours .= " SUM(l.duration) as total_minutes";
|
$sqlHours .= " SUM(l.duration) as total_minutes,";
|
||||||
|
$sqlHours .= " GROUP_CONCAT(l.description ORDER BY l.rang SEPARATOR '\n') as leistung_desc";
|
||||||
$sqlHours .= " FROM ".MAIN_DB_PREFIX."stundenzettel s";
|
$sqlHours .= " FROM ".MAIN_DB_PREFIX."stundenzettel s";
|
||||||
$sqlHours .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel_leistung l ON l.fk_stundenzettel = s.rowid";
|
$sqlHours .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel_leistung l ON l.fk_stundenzettel = s.rowid";
|
||||||
$sqlHours .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = COALESCE(l.fk_product, ".(int)$defaultServiceId.")";
|
$sqlHours .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = COALESCE(l.fk_product, ".(int)$defaultServiceId.")";
|
||||||
$sqlHours .= " WHERE s.fk_commande = ".((int)$order->id);
|
$sqlHours .= " WHERE s.fk_commande = ".((int)$order->id);
|
||||||
$sqlHours .= " AND s.status >= 1"; // Nur validierte Stundenzettel
|
$sqlHours .= " AND s.status >= 1";
|
||||||
|
$sqlHours .= " GROUP BY s.rowid, s.ref, s.date_stundenzettel, COALESCE(l.fk_product, ".(int)$defaultServiceId."), p.ref, p.label, p.tva_tx, p.fk_unit";
|
||||||
|
$sqlHours .= " HAVING SUM(l.duration) > 0";
|
||||||
|
$sqlHours .= " ORDER BY s.date_stundenzettel, s.ref, p.label";
|
||||||
|
} else {
|
||||||
|
// === GRUPPIERT: Alle gleichen Leistungspositionen zusammen (Standard) ===
|
||||||
|
$sqlHours = "SELECT ";
|
||||||
|
$sqlHours .= " COALESCE(l.fk_product, ".(int)$defaultServiceId.") as product_id,";
|
||||||
|
$sqlHours .= " p.ref as product_ref, p.label as product_label, p.tva_tx, p.fk_unit,";
|
||||||
|
$sqlHours .= " SUM(l.duration) as total_minutes,";
|
||||||
|
$sqlHours .= " GROUP_CONCAT(l.description ORDER BY s.date_stundenzettel, l.rang SEPARATOR '\n') as leistung_desc";
|
||||||
|
$sqlHours .= " FROM ".MAIN_DB_PREFIX."stundenzettel s";
|
||||||
|
$sqlHours .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel_leistung l ON l.fk_stundenzettel = s.rowid";
|
||||||
|
$sqlHours .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = COALESCE(l.fk_product, ".(int)$defaultServiceId.")";
|
||||||
|
$sqlHours .= " WHERE s.fk_commande = ".((int)$order->id);
|
||||||
|
$sqlHours .= " AND s.status >= 1";
|
||||||
$sqlHours .= " GROUP BY COALESCE(l.fk_product, ".(int)$defaultServiceId."), p.ref, p.label, p.tva_tx, p.fk_unit";
|
$sqlHours .= " GROUP BY COALESCE(l.fk_product, ".(int)$defaultServiceId."), p.ref, p.label, p.tva_tx, p.fk_unit";
|
||||||
$sqlHours .= " HAVING SUM(l.duration) > 0";
|
$sqlHours .= " HAVING SUM(l.duration) > 0";
|
||||||
$sqlHours .= " ORDER BY p.label";
|
$sqlHours .= " ORDER BY p.label";
|
||||||
|
}
|
||||||
|
|
||||||
$resqlHours = $db->query($sqlHours);
|
$resqlHours = $db->query($sqlHours);
|
||||||
$hasWorkHours = ($resqlHours && $db->num_rows($resqlHours) > 0);
|
$hasWorkHours = ($resqlHours && $db->num_rows($resqlHours) > 0);
|
||||||
|
|
@ -872,7 +903,7 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbeitszeiten hinzufügen (pro Leistungsposition)
|
// Arbeitszeiten hinzufügen
|
||||||
$arbeitszeitSubtotal = 0;
|
$arbeitszeitSubtotal = 0;
|
||||||
while ($objHours = $db->fetch_object($resqlHours)) {
|
while ($objHours = $db->fetch_object($resqlHours)) {
|
||||||
$productId = (int)$objHours->product_id;
|
$productId = (int)$objHours->product_id;
|
||||||
|
|
@ -883,8 +914,22 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
$hourlyPrice = $priceInfo['price'];
|
$hourlyPrice = $priceInfo['price'];
|
||||||
$tvaTx = !empty($objHours->tva_tx) ? $objHours->tva_tx : $priceInfo['tva_tx'];
|
$tvaTx = !empty($objHours->tva_tx) ? $objHours->tva_tx : $priceInfo['tva_tx'];
|
||||||
|
|
||||||
// Beschreibung
|
// Beschreibung je nach Modus
|
||||||
$lineDesc = !empty($objHours->product_label) ? $objHours->product_label : $langs->trans('DefaultService');
|
$productLabel = !empty($objHours->product_label) ? $objHours->product_label : $langs->trans('DefaultService');
|
||||||
|
|
||||||
|
if ($hoursMode == 'per_stz') {
|
||||||
|
// Pro STZ: Produktname mit STZ-Referenz und Datum
|
||||||
|
$stzDate = dol_print_date($db->jdate($objHours->date_stundenzettel), 'day');
|
||||||
|
$lineDesc = $productLabel.' ('.$objHours->stz_ref.', '.$stzDate.')';
|
||||||
|
} else {
|
||||||
|
// Gruppiert: Nur Produktname
|
||||||
|
$lineDesc = $productLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leistungsbeschreibungen anhängen (wenn vorhanden)
|
||||||
|
if (!empty($objHours->leistung_desc)) {
|
||||||
|
$lineDesc .= "\n".$objHours->leistung_desc;
|
||||||
|
}
|
||||||
|
|
||||||
// Rechnungszeile hinzufügen
|
// Rechnungszeile hinzufügen
|
||||||
$hoursResult = $facture->addline(
|
$hoursResult = $facture->addline(
|
||||||
|
|
@ -1075,7 +1120,7 @@ if ($hasStundenzettel) {
|
||||||
$sqlRemaining .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
$sqlRemaining .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
||||||
$sqlRemaining .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
$sqlRemaining .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
||||||
$sqlRemaining .= " WHERE cd.fk_commande = ".((int)$order->id);
|
$sqlRemaining .= " WHERE cd.fk_commande = ".((int)$order->id);
|
||||||
$sqlRemaining .= " AND (cd.fk_product > 0 OR (cd.fk_product = 0 AND cd.description IS NOT NULL AND cd.description != ''))";
|
$sqlRemaining .= " AND (cd.fk_product > 0 OR ((cd.fk_product IS NULL OR cd.fk_product = 0) AND cd.description IS NOT NULL AND cd.description != ''))";
|
||||||
$sqlRemaining .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
$sqlRemaining .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
||||||
$sqlRemaining .= " HAVING qty_ordered > qty_documented";
|
$sqlRemaining .= " HAVING qty_ordered > qty_documented";
|
||||||
|
|
||||||
|
|
@ -1276,7 +1321,7 @@ if ($resql && $db->num_rows($resql) > 0) {
|
||||||
print '<div class="info" style="margin-bottom:20px;">';
|
print '<div class="info" style="margin-bottom:20px;">';
|
||||||
print '<strong>'.img_picto('', 'note', 'class="pictofixedwidth"').$langs->trans("NotesForNextVisit").':</strong><br>';
|
print '<strong>'.img_picto('', 'note', 'class="pictofixedwidth"').$langs->trans("NotesForNextVisit").':</strong><br>';
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
print '<div style="margin:5px 0; padding:5px; background:#fff; border-left:3px solid #0077b3;">';
|
print '<div class="stz-info-box" style="margin:5px 0; padding:5px; border-left:3px solid #0077b3;">';
|
||||||
print '<small class="opacitymedium">'.dol_print_date($db->jdate($obj->date_stundenzettel), 'day').' ('.$obj->ref.'):</small><br>';
|
print '<small class="opacitymedium">'.dol_print_date($db->jdate($obj->date_stundenzettel), 'day').' ('.$obj->ref.'):</small><br>';
|
||||||
print dol_htmlentitiesbr($obj->note_public);
|
print dol_htmlentitiesbr($obj->note_public);
|
||||||
print '</div>';
|
print '</div>';
|
||||||
|
|
@ -1314,7 +1359,7 @@ if ($notesStundenzettel && $notesStundenzettel->id > 0) {
|
||||||
|
|
||||||
// Merkzettel anzeigen, wenn welche vorhanden sind
|
// Merkzettel anzeigen, wenn welche vorhanden sind
|
||||||
if (count($notesToShow) > 0) {
|
if (count($notesToShow) > 0) {
|
||||||
print '<div class="info" style="margin-bottom:15px; background-color: #fff8e1; border-left: 4px solid #ffc107; padding: 10px;">';
|
print '<div class="info stz-warning-box" style="margin-bottom:15px; padding: 10px;">';
|
||||||
print '<strong>'.img_picto('', 'list', 'class="pictofixedwidth"').$langs->trans("NotesMemo").'</strong>';
|
print '<strong>'.img_picto('', 'list', 'class="pictofixedwidth"').$langs->trans("NotesMemo").'</strong>';
|
||||||
print ' <small class="opacitymedium">('.$notesStundenzettel->ref.')</small>';
|
print ' <small class="opacitymedium">('.$notesStundenzettel->ref.')</small>';
|
||||||
print '<ul style="margin: 5px 0 0 0; padding-left: 20px; list-style: none;">';
|
print '<ul style="margin: 5px 0 0 0; padding-left: 20px; list-style: none;">';
|
||||||
|
|
@ -1387,7 +1432,7 @@ if ($tab == 'stundenzettel') {
|
||||||
if ($numStz > 0) {
|
if ($numStz > 0) {
|
||||||
while ($objStz = $db->fetch_object($resqlStzList)) {
|
while ($objStz = $db->fetch_object($resqlStzList)) {
|
||||||
$isCurrentStz = ($stundenzettel_id > 0 && $objStz->rowid == $stundenzettel_id);
|
$isCurrentStz = ($stundenzettel_id > 0 && $objStz->rowid == $stundenzettel_id);
|
||||||
print '<tr class="oddeven"'.($isCurrentStz ? ' style="background-color: #e8f4fc;"' : '').'>';
|
print '<tr class="oddeven'.($isCurrentStz ? ' stz-current-stz' : '').'">';
|
||||||
|
|
||||||
// Ref
|
// Ref
|
||||||
print '<td>';
|
print '<td>';
|
||||||
|
|
@ -1621,11 +1666,11 @@ if ($tab == 'products') {
|
||||||
// qty_removed: Entfällt für dieses Produkt (origin = 'omitted')
|
// qty_removed: Entfällt für dieses Produkt (origin = 'omitted')
|
||||||
$sql .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3";
|
$sql .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3";
|
||||||
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel";
|
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel";
|
||||||
$sql .= " WHERE sp3.fk_product = cd.fk_product AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed,";
|
$sql .= " WHERE (sp3.fk_commandedet = cd.rowid OR (sp3.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed,";
|
||||||
// qty_returned: Rücknahme für dieses Produkt (origin = 'returned')
|
// qty_returned: Rücknahme für dieses Produkt (origin = 'returned')
|
||||||
$sql .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4";
|
$sql .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4";
|
||||||
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel";
|
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel";
|
||||||
$sql .= " WHERE sp4.fk_product = cd.fk_product AND sp4.origin = 'returned' AND s4.fk_commande = ".((int)$order->id)."), 0) as qty_returned";
|
$sql .= " WHERE (sp4.fk_commandedet = cd.rowid OR (sp4.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp4.origin = 'returned' AND s4.fk_commande = ".((int)$order->id)."), 0) as qty_returned";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager as m";
|
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager as m";
|
||||||
$sql .= " JOIN ".MAIN_DB_PREFIX."commandedet as cd ON cd.rowid = m.fk_commandedet";
|
$sql .= " JOIN ".MAIN_DB_PREFIX."commandedet as cd ON cd.rowid = m.fk_commandedet";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
||||||
|
|
@ -1662,15 +1707,15 @@ if ($tab == 'products') {
|
||||||
// qty_removed: Entfällt für dieses Produkt (origin = 'omitted')
|
// qty_removed: Entfällt für dieses Produkt (origin = 'omitted')
|
||||||
$sql .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3";
|
$sql .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3";
|
||||||
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel";
|
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel";
|
||||||
$sql .= " WHERE sp3.fk_product = cd.fk_product AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed,";
|
$sql .= " WHERE (sp3.fk_commandedet = cd.rowid OR (sp3.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed,";
|
||||||
// qty_returned: Rücknahme für dieses Produkt (origin = 'returned')
|
// qty_returned: Rücknahme für dieses Produkt (origin = 'returned')
|
||||||
$sql .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4";
|
$sql .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4";
|
||||||
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel";
|
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel";
|
||||||
$sql .= " WHERE sp4.fk_product = cd.fk_product AND sp4.origin = 'returned' AND s4.fk_commande = ".((int)$order->id)."), 0) as qty_returned";
|
$sql .= " WHERE (sp4.fk_commandedet = cd.rowid OR (sp4.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp4.origin = 'returned' AND s4.fk_commande = ".((int)$order->id)."), 0) as qty_returned";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
$sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
||||||
$sql .= " WHERE cd.fk_commande = ".((int)$order->id);
|
$sql .= " WHERE cd.fk_commande = ".((int)$order->id);
|
||||||
$sql .= " AND (cd.fk_product > 0 OR (cd.fk_product = 0 AND cd.description IS NOT NULL AND cd.description != ''))";
|
$sql .= " AND (cd.fk_product > 0 OR ((cd.fk_product IS NULL OR cd.fk_product = 0) AND cd.description IS NOT NULL AND cd.description != ''))";
|
||||||
$sql .= " AND (cd.special_code IS NULL OR cd.special_code = 0)"; // Keine SubtotalTitle-Spezialzeilen
|
$sql .= " AND (cd.special_code IS NULL OR cd.special_code = 0)"; // Keine SubtotalTitle-Spezialzeilen
|
||||||
$sql .= " ORDER BY cd.rang";
|
$sql .= " ORDER BY cd.rang";
|
||||||
|
|
||||||
|
|
@ -1845,7 +1890,7 @@ if ($tab == 'products') {
|
||||||
// Section-Header mit Buffering, wird nur ausgegeben wenn Produkte angezeigt werden
|
// Section-Header mit Buffering, wird nur ausgegeben wenn Produkte angezeigt werden
|
||||||
// Dezente Farbgebung: nur linker Rand farbig, heller Hintergrund
|
// Dezente Farbgebung: nur linker Rand farbig, heller Hintergrund
|
||||||
$sectionHeader = '<tr class="liste_titre section-header" data-section="section_'.$sectionId.'" onclick="toggleSection(\'section_'.$sectionId.'\')" style="cursor: pointer;">';
|
$sectionHeader = '<tr class="liste_titre section-header" data-section="section_'.$sectionId.'" onclick="toggleSection(\'section_'.$sectionId.'\')" style="cursor: pointer;">';
|
||||||
$sectionHeader .= '<td colspan="6" style="font-weight: bold; padding: 8px; border-left: 4px solid '.$section['color'].'; background-color: #f8f8f8;">';
|
$sectionHeader .= '<td class="stz-section-header" colspan="6" style="font-weight: bold; padding: 8px; border-left: 4px solid '.$section['color'].';">';
|
||||||
$sectionHeader .= '<span class="section-toggle" id="toggle_section_'.$sectionId.'" style="margin-right: 10px;">▼</span>';
|
$sectionHeader .= '<span class="section-toggle" id="toggle_section_'.$sectionId.'" style="margin-right: 10px;">▼</span>';
|
||||||
$sectionHeader .= dol_escape_htmltag($section['title']);
|
$sectionHeader .= dol_escape_htmltag($section['title']);
|
||||||
$sectionHeader .= ' <span style="opacity: 0.6; font-weight: normal;">('.$visibleProductsInSection.' '.$langs->trans("Products").')</span>';
|
$sectionHeader .= ' <span style="opacity: 0.6; font-weight: normal;">('.$visibleProductsInSection.' '.$langs->trans("Products").')</span>';
|
||||||
|
|
@ -1887,7 +1932,7 @@ if ($tab == 'products') {
|
||||||
if (count($sections) > 0) {
|
if (count($sections) > 0) {
|
||||||
// Trennzeile nur wenn es auch Sections gibt - einklappbar, dezente Farbgebung
|
// Trennzeile nur wenn es auch Sections gibt - einklappbar, dezente Farbgebung
|
||||||
print '<tr class="liste_titre section-header" data-section="section_other" onclick="toggleSection(\'section_other\')" style="cursor: pointer;">';
|
print '<tr class="liste_titre section-header" data-section="section_other" onclick="toggleSection(\'section_other\')" style="cursor: pointer;">';
|
||||||
print '<td colspan="6" style="font-weight: bold; padding: 8px; border-left: 4px solid #666; background-color: #f8f8f8;">';
|
print '<td class="stz-section-header" colspan="6" style="font-weight: bold; padding: 8px; border-left: 4px solid #666;">';
|
||||||
print '<span class="section-toggle" id="toggle_section_other" style="margin-right: 10px;">▼</span>';
|
print '<span class="section-toggle" id="toggle_section_other" style="margin-right: 10px;">▼</span>';
|
||||||
print $langs->trans("OtherProducts").' <span style="opacity: 0.6; font-weight: normal;">('.$visibleProductsWithoutSection.' '.$langs->trans("Products").')</span>';
|
print $langs->trans("OtherProducts").' <span style="opacity: 0.6; font-weight: normal;">('.$visibleProductsWithoutSection.' '.$langs->trans("Products").')</span>';
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
|
@ -1937,7 +1982,7 @@ if ($tab == 'products') {
|
||||||
if ($mehraufwandCount > 0) {
|
if ($mehraufwandCount > 0) {
|
||||||
// Mehraufwand-Header - einklappbar, dezente Farbgebung
|
// Mehraufwand-Header - einklappbar, dezente Farbgebung
|
||||||
print '<tr class="liste_titre section-header" data-section="section_mehraufwand" onclick="toggleSection(\'section_mehraufwand\')" style="cursor: pointer;">';
|
print '<tr class="liste_titre section-header" data-section="section_mehraufwand" onclick="toggleSection(\'section_mehraufwand\')" style="cursor: pointer;">';
|
||||||
print '<td colspan="6" style="font-weight: bold; padding: 8px; border-left: 4px solid #e67e22; background-color: #f8f8f8;">';
|
print '<td class="stz-section-header" colspan="6" style="font-weight: bold; padding: 8px; border-left: 4px solid #e67e22;">';
|
||||||
print '<span class="section-toggle" id="toggle_section_mehraufwand" style="margin-right: 10px;">▼</span>';
|
print '<span class="section-toggle" id="toggle_section_mehraufwand" style="margin-right: 10px;">▼</span>';
|
||||||
print '<span style="margin-right: 10px; color: #e67e22;">⚠</span>';
|
print '<span style="margin-right: 10px; color: #e67e22;">⚠</span>';
|
||||||
print $langs->trans("Mehraufwand");
|
print $langs->trans("Mehraufwand");
|
||||||
|
|
@ -2271,7 +2316,7 @@ if ($tab == 'tracking') {
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
$sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
|
||||||
$sql .= " WHERE cd.fk_commande = ".((int)$order->id);
|
$sql .= " WHERE cd.fk_commande = ".((int)$order->id);
|
||||||
$sql .= " AND (cd.fk_product > 0 OR (cd.fk_product = 0 AND cd.description IS NOT NULL AND cd.description != ''))";
|
$sql .= " AND (cd.fk_product > 0 OR ((cd.fk_product IS NULL OR cd.fk_product = 0) AND cd.description IS NOT NULL AND cd.description != ''))";
|
||||||
$sql .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
$sql .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
|
||||||
$sql .= " ORDER BY cd.rang";
|
$sql .= " ORDER BY cd.rang";
|
||||||
|
|
||||||
|
|
@ -2364,11 +2409,11 @@ if ($tab == 'tracking') {
|
||||||
// Detail-Zeile (standardmäßig eingeklappt)
|
// Detail-Zeile (standardmäßig eingeklappt)
|
||||||
if ($hasDetails) {
|
if ($hasDetails) {
|
||||||
print '<tr id="tdetail_'.$detailRowId.'" class="tracking-detail-row" style="display:none;">';
|
print '<tr id="tdetail_'.$detailRowId.'" class="tracking-detail-row" style="display:none;">';
|
||||||
print '<td colspan="5" style="padding: 0 0 0 30px; background-color: #fafafa;">';
|
print '<td class="stz-subtable-bg" colspan="5" style="padding: 0 0 0 30px;">';
|
||||||
|
|
||||||
// Sub-Tabelle für Details
|
// Sub-Tabelle für Details
|
||||||
print '<table class="noborder" style="width:100%; margin: 5px 0;">';
|
print '<table class="noborder" style="width:100%; margin: 5px 0;">';
|
||||||
print '<tr class="liste_titre" style="background-color: #f0f0f0;">';
|
print '<tr class="liste_titre stz-subtable-header">';
|
||||||
print '<th style="width:120px;">'.$langs->trans("Stundenzettel").'</th>';
|
print '<th style="width:120px;">'.$langs->trans("Stundenzettel").'</th>';
|
||||||
print '<th style="width:100px;">'.$langs->trans("Date").'</th>';
|
print '<th style="width:100px;">'.$langs->trans("Date").'</th>';
|
||||||
print '<th style="width:100px;">'.$langs->trans("Type").'</th>';
|
print '<th style="width:100px;">'.$langs->trans("Type").'</th>';
|
||||||
|
|
@ -2457,18 +2502,19 @@ if ($tab == 'tracking') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mehraufwand-Produkte laden die NICHT im Auftrag sind
|
// Mehraufwand und zusätzlich verbaute Produkte laden die NICHT im Auftrag sind
|
||||||
|
// Beauftragt (additional) und verbaut (added ohne fk_commandedet) getrennt summieren
|
||||||
$sqlMehraufwand = "SELECT sp.fk_product, sp.description,";
|
$sqlMehraufwand = "SELECT sp.fk_product, sp.description,";
|
||||||
$sqlMehraufwand .= " p.ref as product_ref, p.label as product_label,";
|
$sqlMehraufwand .= " p.ref as product_ref, p.label as product_label,";
|
||||||
$sqlMehraufwand .= " SUM(sp.qty) as qty_ordered,";
|
$sqlMehraufwand .= " SUM(CASE WHEN sp.origin = 'additional' THEN sp.qty_done ELSE 0 END) as qty_additional,";
|
||||||
$sqlMehraufwand .= " SUM(sp.qty_done) as qty_delivered";
|
$sqlMehraufwand .= " SUM(CASE WHEN sp.origin = 'added' AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0) THEN sp.qty_done ELSE 0 END) as qty_added";
|
||||||
$sqlMehraufwand .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
$sqlMehraufwand .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
||||||
$sqlMehraufwand .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
$sqlMehraufwand .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
||||||
$sqlMehraufwand .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = sp.fk_product";
|
$sqlMehraufwand .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = sp.fk_product";
|
||||||
$sqlMehraufwand .= " WHERE s.fk_commande = ".((int)$order->id);
|
$sqlMehraufwand .= " WHERE s.fk_commande = ".((int)$order->id);
|
||||||
$sqlMehraufwand .= " AND sp.origin = 'additional'";
|
$sqlMehraufwand .= " AND (sp.origin = 'additional' OR (sp.origin = 'added' AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)))";
|
||||||
if (!empty($orderProductIds)) {
|
if (!empty($orderProductIds)) {
|
||||||
$sqlMehraufwand .= " AND (sp.fk_product NOT IN (".implode(',', $orderProductIds).") OR sp.fk_product = 0)";
|
$sqlMehraufwand .= " AND (sp.fk_product NOT IN (".implode(',', $orderProductIds).") OR sp.fk_product IS NULL OR sp.fk_product = 0)";
|
||||||
}
|
}
|
||||||
$sqlMehraufwand .= " GROUP BY sp.fk_product, sp.description, p.ref, p.label";
|
$sqlMehraufwand .= " GROUP BY sp.fk_product, sp.description, p.ref, p.label";
|
||||||
$sqlMehraufwand .= " ORDER BY p.ref, sp.description";
|
$sqlMehraufwand .= " ORDER BY p.ref, sp.description";
|
||||||
|
|
@ -2479,14 +2525,18 @@ if ($tab == 'tracking') {
|
||||||
if ($resqlMehr && $db->num_rows($resqlMehr) > 0) {
|
if ($resqlMehr && $db->num_rows($resqlMehr) > 0) {
|
||||||
// Separator-Zeile für Mehraufwand
|
// Separator-Zeile für Mehraufwand
|
||||||
print '<tr class="liste_titre">';
|
print '<tr class="liste_titre">';
|
||||||
print '<td colspan="5" style="background-color: #e8f5e9;"><strong>'.$langs->trans("Mehraufwand").'</strong> <span class="opacitymedium">('.$langs->trans("MehraufwandDesc").')</span></td>';
|
print '<td class="stz-mehraufwand-header" colspan="5"><strong>'.$langs->trans("Mehraufwand").'</strong> <span class="opacitymedium">('.$langs->trans("MehraufwandDesc").')</span></td>';
|
||||||
print '</tr>';
|
print '</tr>';
|
||||||
|
|
||||||
while ($objMehr = $db->fetch_object($resqlMehr)) {
|
while ($objMehr = $db->fetch_object($resqlMehr)) {
|
||||||
$hasMehraufwandProducts = true;
|
$hasMehraufwandProducts = true;
|
||||||
$detailRowId++;
|
$detailRowId++;
|
||||||
$qty_ordered_mehr = (float)$objMehr->qty_ordered;
|
$qtyAdditional = (float)$objMehr->qty_additional;
|
||||||
$qty_delivered_mehr = (float)$objMehr->qty_delivered;
|
$qtyAdded = (float)$objMehr->qty_added;
|
||||||
|
// Beauftragt: Wenn Mehraufwand existiert, dessen Menge, sonst die verbaute Menge
|
||||||
|
$qty_ordered_mehr = ($qtyAdditional > 0) ? $qtyAdditional : $qtyAdded;
|
||||||
|
// Verbaut: Immer die tatsächlich verbaute Menge
|
||||||
|
$qty_delivered_mehr = $qtyAdded;
|
||||||
$qty_remaining_mehr = $qty_ordered_mehr - $qty_delivered_mehr;
|
$qty_remaining_mehr = $qty_ordered_mehr - $qty_delivered_mehr;
|
||||||
|
|
||||||
// Details für Mehraufwand laden
|
// Details für Mehraufwand laden
|
||||||
|
|
@ -2565,9 +2615,9 @@ if ($tab == 'tracking') {
|
||||||
// Detail-Zeile für Mehraufwand
|
// Detail-Zeile für Mehraufwand
|
||||||
if ($hasDetailsMehr) {
|
if ($hasDetailsMehr) {
|
||||||
print '<tr id="tdetail_'.$detailRowId.'" class="tracking-detail-row" style="display:none;">';
|
print '<tr id="tdetail_'.$detailRowId.'" class="tracking-detail-row" style="display:none;">';
|
||||||
print '<td colspan="5" style="padding: 0 0 0 30px; background-color: #fafafa;">';
|
print '<td class="stz-subtable-bg" colspan="5" style="padding: 0 0 0 30px;">';
|
||||||
print '<table class="noborder" style="width:100%; margin: 5px 0;">';
|
print '<table class="noborder" style="width:100%; margin: 5px 0;">';
|
||||||
print '<tr class="liste_titre" style="background-color: #f0f0f0;">';
|
print '<tr class="liste_titre stz-subtable-header">';
|
||||||
print '<th style="width:120px;">'.$langs->trans("Stundenzettel").'</th>';
|
print '<th style="width:120px;">'.$langs->trans("Stundenzettel").'</th>';
|
||||||
print '<th style="width:100px;">'.$langs->trans("Date").'</th>';
|
print '<th style="width:100px;">'.$langs->trans("Date").'</th>';
|
||||||
print '<th style="width:100px;">'.$langs->trans("Type").'</th>';
|
print '<th style="width:100px;">'.$langs->trans("Type").'</th>';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue